From 17a2c81b2822934ce6de9b9f6e84a084c335daf9 Mon Sep 17 00:00:00 2001 From: Max Henzerling Date: Mon, 3 Jul 2023 22:18:24 -0700 Subject: [PATCH] Merge progress, still lots to do and to continue up the change --- .clang-format | 105 + .clang-tidy | 18 + .editorconfig | 9 +- .git-blame-ignore-revs | 19 + .github/workflows/openmw.yml | 80 + .gitignore | 6 +- .gitlab-ci.yml | 706 +- .resubmitted_merge_requests.txt | 8 + .travis.yml | 100 - AUTHORS.md | 28 +- CHANGELOG.md | 312 + CHANGELOG_PR.md | 53 - CI/Store-Symbols.ps1 | 52 + CI/activate_msvc.sh | 10 +- CI/before_install.android.sh | 4 +- CI/before_install.osx.sh | 37 +- CI/before_script.android.sh | 28 +- CI/before_script.linux.sh | 83 +- CI/before_script.msvc.sh | 578 +- CI/before_script.osx.sh | 15 +- CI/build_googletest.sh | 17 - CI/check_clang_format.sh | 8 + CI/check_cmake_format.sh | 6 + CI/check_file_names.sh | 7 + CI/file_name_exceptions.txt | 48 + CI/install_debian_deps.sh | 88 +- CI/org.openmw.OpenMW.devel.yaml | 200 + CI/run_integration_tests.sh | 10 + CI/teal_ci.sh | 12 + CI/ubuntu_gcc_preprocess.sh | 74 + CMakeLists.txt | 410 +- CONTRIBUTING.md | 14 +- README.md | 79 + apps/benchmarks/CMakeLists.txt | 32 +- .../benchmarks/detournavigator/CMakeLists.txt | 15 + .../detournavigator/navmeshtilescache.cpp | 194 +- apps/benchmarks/esm/CMakeLists.txt | 15 + apps/benchmarks/esm/benchrefid.cpp | 249 + apps/benchmarks/settings/CMakeLists.txt | 18 + apps/benchmarks/settings/access.cpp | 162 + apps/bsatool/CMakeLists.txt | 21 +- apps/bsatool/bsatool.cpp | 336 +- apps/bulletobjecttool/CMakeLists.txt | 27 + apps/bulletobjecttool/main.cpp | 206 + apps/esmtool/CMakeLists.txt | 31 +- apps/esmtool/arguments.hpp | 29 + apps/esmtool/esmtool.cpp | 976 +- apps/esmtool/labels.cpp | 527 +- apps/esmtool/labels.hpp | 47 +- apps/esmtool/record.cpp | 2648 ++--- apps/esmtool/record.hpp | 217 +- apps/esmtool/tes4.cpp | 564 + apps/esmtool/tes4.hpp | 15 + apps/essimporter/CMakeLists.txt | 18 +- apps/essimporter/convertacdt.cpp | 69 +- apps/essimporter/convertacdt.hpp | 17 +- apps/essimporter/convertcntc.cpp | 2 +- apps/essimporter/convertcntc.hpp | 2 +- apps/essimporter/convertcrec.cpp | 2 +- apps/essimporter/convertcrec.hpp | 2 +- apps/essimporter/converter.cpp | 218 +- apps/essimporter/converter.hpp | 1103 +- apps/essimporter/convertinventory.cpp | 11 +- apps/essimporter/convertinventory.hpp | 4 +- apps/essimporter/convertnpcc.cpp | 2 +- apps/essimporter/convertnpcc.hpp | 4 +- apps/essimporter/convertplayer.cpp | 41 +- apps/essimporter/convertplayer.hpp | 7 +- apps/essimporter/convertscpt.cpp | 10 +- apps/essimporter/convertscpt.hpp | 4 +- apps/essimporter/convertscri.cpp | 8 +- apps/essimporter/convertscri.hpp | 4 +- apps/essimporter/importacdt.cpp | 135 - apps/essimporter/importacdt.hpp | 11 +- apps/essimporter/importcellref.cpp | 138 +- apps/essimporter/importcellref.hpp | 10 +- apps/essimporter/importcntc.cpp | 4 +- apps/essimporter/importcrec.cpp | 7 +- apps/essimporter/importcrec.hpp | 2 +- apps/essimporter/importdial.cpp | 4 +- apps/essimporter/importer.cpp | 218 +- apps/essimporter/importer.hpp | 9 +- apps/essimporter/importercontext.hpp | 53 +- apps/essimporter/importgame.cpp | 38 +- apps/essimporter/importgame.hpp | 14 +- apps/essimporter/importinfo.cpp | 4 +- apps/essimporter/importinventory.cpp | 9 +- apps/essimporter/importinventory.hpp | 4 +- apps/essimporter/importjour.cpp | 4 +- apps/essimporter/importklst.cpp | 4 +- apps/essimporter/importklst.hpp | 2 +- apps/essimporter/importnpcc.cpp | 6 +- apps/essimporter/importnpcc.hpp | 6 +- apps/essimporter/importplayer.cpp | 15 +- apps/essimporter/importplayer.hpp | 174 +- apps/essimporter/importproj.cpp | 16 +- apps/essimporter/importproj.h | 44 +- apps/essimporter/importques.cpp | 4 +- apps/essimporter/importscpt.cpp | 6 +- apps/essimporter/importscpt.hpp | 7 +- apps/essimporter/importscri.cpp | 10 +- apps/essimporter/importscri.hpp | 2 +- apps/essimporter/importsplm.cpp | 56 +- apps/essimporter/importsplm.h | 104 +- apps/essimporter/main.cpp | 46 +- apps/launcher/CMakeLists.txt | 52 +- apps/launcher/advancedpage.cpp | 432 - apps/launcher/advancedpage.hpp | 48 - apps/launcher/datafilespage.cpp | 759 +- apps/launcher/datafilespage.hpp | 169 +- apps/launcher/graphicspage.cpp | 124 +- apps/launcher/graphicspage.hpp | 9 +- apps/launcher/importpage.cpp | 230 + apps/launcher/importpage.hpp | 64 + apps/launcher/main.cpp | 35 +- apps/launcher/maindialog.cpp | 506 +- apps/launcher/maindialog.hpp | 49 +- apps/launcher/playpage.cpp | 30 - apps/launcher/playpage.hpp | 34 - apps/launcher/settingspage.cpp | 623 +- apps/launcher/settingspage.hpp | 69 +- apps/launcher/textslotmsgbox.hpp | 6 +- apps/launcher/utils/cellnameloader.cpp | 54 +- apps/launcher/utils/cellnameloader.hpp | 26 +- apps/launcher/utils/lineedit.cpp | 15 +- apps/launcher/utils/lineedit.hpp | 9 +- apps/launcher/utils/openalutil.cpp | 31 +- apps/launcher/utils/openalutil.hpp | 7 +- apps/launcher/utils/profilescombobox.cpp | 51 +- apps/launcher/utils/profilescombobox.hpp | 15 +- apps/launcher/utils/textinputdialog.cpp | 33 +- apps/launcher/utils/textinputdialog.hpp | 13 +- apps/mwiniimporter/CMakeLists.txt | 14 +- apps/mwiniimporter/importer.cpp | 1028 +- apps/mwiniimporter/importer.hpp | 47 +- apps/mwiniimporter/main.cpp | 121 +- apps/navmeshtool/CMakeLists.txt | 31 + apps/navmeshtool/main.cpp | 263 + apps/navmeshtool/navmesh.cpp | 315 + apps/navmeshtool/navmesh.hpp | 29 + apps/navmeshtool/worldspacedata.cpp | 371 + apps/navmeshtool/worldspacedata.hpp | 96 + apps/niftest/CMakeLists.txt | 11 +- apps/niftest/niftest.cpp | 226 +- apps/opencs/CMakeLists.txt | 153 +- apps/opencs/editor.cpp | 319 +- apps/opencs/editor.hpp | 132 +- apps/opencs/main.cpp | 81 +- apps/opencs/model/doc/blacklist.cpp | 26 +- apps/opencs/model/doc/blacklist.hpp | 11 +- apps/opencs/model/doc/document.cpp | 376 +- apps/opencs/model/doc/document.hpp | 192 +- apps/opencs/model/doc/documentmanager.cpp | 105 +- apps/opencs/model/doc/documentmanager.hpp | 131 +- apps/opencs/model/doc/loader.cpp | 106 +- apps/opencs/model/doc/loader.hpp | 89 +- apps/opencs/model/doc/messages.cpp | 54 +- apps/opencs/model/doc/messages.hpp | 47 +- apps/opencs/model/doc/operation.cpp | 101 +- apps/opencs/model/doc/operation.hpp | 90 +- apps/opencs/model/doc/operationholder.cpp | 30 +- apps/opencs/model/doc/operationholder.hpp | 49 +- apps/opencs/model/doc/runner.cpp | 77 +- apps/opencs/model/doc/runner.hpp | 83 +- apps/opencs/model/doc/saving.cpp | 132 +- apps/opencs/model/doc/saving.hpp | 17 +- apps/opencs/model/doc/savingstages.cpp | 526 +- apps/opencs/model/doc/savingstages.hpp | 275 +- apps/opencs/model/doc/savingstate.cpp | 42 +- apps/opencs/model/doc/savingstate.hpp | 58 +- apps/opencs/model/doc/stage.cpp | 3 - apps/opencs/model/doc/stage.hpp | 23 +- apps/opencs/model/doc/state.hpp | 2 +- apps/opencs/model/filter/andnode.cpp | 17 +- apps/opencs/model/filter/andnode.hpp | 22 +- apps/opencs/model/filter/booleannode.cpp | 15 +- apps/opencs/model/filter/booleannode.hpp | 31 +- apps/opencs/model/filter/leafnode.cpp | 1 - apps/opencs/model/filter/leafnode.hpp | 11 +- apps/opencs/model/filter/narynode.cpp | 29 +- apps/opencs/model/filter/narynode.hpp | 30 +- apps/opencs/model/filter/node.cpp | 5 - apps/opencs/model/filter/node.hpp | 39 +- apps/opencs/model/filter/notnode.cpp | 18 +- apps/opencs/model/filter/notnode.hpp | 21 +- apps/opencs/model/filter/ornode.cpp | 22 +- apps/opencs/model/filter/ornode.hpp | 22 +- apps/opencs/model/filter/parser.cpp | 306 +- apps/opencs/model/filter/parser.hpp | 57 +- apps/opencs/model/filter/textnode.cpp | 60 +- apps/opencs/model/filter/textnode.hpp | 39 +- apps/opencs/model/filter/unarynode.cpp | 14 +- apps/opencs/model/filter/unarynode.hpp | 31 +- apps/opencs/model/filter/valuenode.cpp | 95 +- apps/opencs/model/filter/valuenode.hpp | 60 +- apps/opencs/model/prefs/boolsetting.cpp | 43 +- apps/opencs/model/prefs/boolsetting.hpp | 31 +- apps/opencs/model/prefs/category.cpp | 26 +- apps/opencs/model/prefs/category.hpp | 36 +- apps/opencs/model/prefs/coloursetting.cpp | 43 +- apps/opencs/model/prefs/coloursetting.hpp | 36 +- apps/opencs/model/prefs/doublesetting.cpp | 58 +- apps/opencs/model/prefs/doublesetting.hpp | 46 +- apps/opencs/model/prefs/enumsetting.cpp | 104 +- apps/opencs/model/prefs/enumsetting.hpp | 50 +- apps/opencs/model/prefs/intsetting.cpp | 56 +- apps/opencs/model/prefs/intsetting.hpp | 43 +- apps/opencs/model/prefs/modifiersetting.cpp | 28 +- apps/opencs/model/prefs/modifiersetting.hpp | 43 +- apps/opencs/model/prefs/setting.cpp | 72 +- apps/opencs/model/prefs/setting.hpp | 72 +- apps/opencs/model/prefs/shortcut.cpp | 19 +- apps/opencs/model/prefs/shortcut.hpp | 140 +- .../model/prefs/shortcuteventhandler.cpp | 38 +- .../model/prefs/shortcuteventhandler.hpp | 62 +- apps/opencs/model/prefs/shortcutmanager.cpp | 882 +- apps/opencs/model/prefs/shortcutmanager.hpp | 72 +- apps/opencs/model/prefs/shortcutsetting.cpp | 29 +- apps/opencs/model/prefs/shortcutsetting.hpp | 47 +- apps/opencs/model/prefs/state.cpp | 980 +- apps/opencs/model/prefs/state.hpp | 105 +- apps/opencs/model/prefs/stringsetting.cpp | 37 +- apps/opencs/model/prefs/stringsetting.hpp | 34 +- apps/opencs/model/tools/birthsigncheck.cpp | 26 +- apps/opencs/model/tools/birthsigncheck.hpp | 39 +- apps/opencs/model/tools/bodypartcheck.cpp | 49 +- apps/opencs/model/tools/bodypartcheck.hpp | 36 +- apps/opencs/model/tools/classcheck.cpp | 63 +- apps/opencs/model/tools/classcheck.hpp | 29 +- apps/opencs/model/tools/enchantmentcheck.cpp | 38 +- apps/opencs/model/tools/enchantmentcheck.hpp | 30 +- apps/opencs/model/tools/factioncheck.cpp | 54 +- apps/opencs/model/tools/factioncheck.hpp | 29 +- apps/opencs/model/tools/gmstcheck.cpp | 118 +- apps/opencs/model/tools/gmstcheck.hpp | 23 +- apps/opencs/model/tools/journalcheck.cpp | 89 +- apps/opencs/model/tools/journalcheck.hpp | 25 +- apps/opencs/model/tools/magiceffectcheck.cpp | 65 +- apps/opencs/model/tools/magiceffectcheck.hpp | 61 +- apps/opencs/model/tools/mandatoryid.cpp | 23 +- apps/opencs/model/tools/mandatoryid.hpp | 30 +- apps/opencs/model/tools/mergeoperation.cpp | 114 +- apps/opencs/model/tools/mergeoperation.hpp | 28 +- apps/opencs/model/tools/mergestages.cpp | 139 +- apps/opencs/model/tools/mergestages.hpp | 177 +- apps/opencs/model/tools/mergestate.hpp | 10 +- apps/opencs/model/tools/pathgridcheck.cpp | 92 +- apps/opencs/model/tools/pathgridcheck.hpp | 27 +- apps/opencs/model/tools/racecheck.cpp | 45 +- apps/opencs/model/tools/racecheck.hpp | 35 +- .../opencs/model/tools/referenceablecheck.cpp | 446 +- .../opencs/model/tools/referenceablecheck.hpp | 169 +- apps/opencs/model/tools/referencecheck.cpp | 58 +- apps/opencs/model/tools/referencecheck.hpp | 49 +- apps/opencs/model/tools/regioncheck.cpp | 30 +- apps/opencs/model/tools/regioncheck.hpp | 29 +- apps/opencs/model/tools/reportmodel.cpp | 127 +- apps/opencs/model/tools/reportmodel.hpp | 66 +- apps/opencs/model/tools/scriptcheck.cpp | 101 +- apps/opencs/model/tools/scriptcheck.hpp | 60 +- apps/opencs/model/tools/search.cpp | 285 +- apps/opencs/model/tools/search.hpp | 106 +- apps/opencs/model/tools/searchoperation.cpp | 35 +- apps/opencs/model/tools/searchoperation.hpp | 24 +- apps/opencs/model/tools/searchstage.cpp | 25 +- apps/opencs/model/tools/searchstage.hpp | 28 +- apps/opencs/model/tools/skillcheck.cpp | 23 +- apps/opencs/model/tools/skillcheck.hpp | 29 +- apps/opencs/model/tools/soundcheck.cpp | 27 +- apps/opencs/model/tools/soundcheck.hpp | 38 +- apps/opencs/model/tools/soundgencheck.cpp | 38 +- apps/opencs/model/tools/soundgencheck.hpp | 41 +- apps/opencs/model/tools/spellcheck.cpp | 26 +- apps/opencs/model/tools/spellcheck.hpp | 29 +- apps/opencs/model/tools/startscriptcheck.cpp | 36 +- apps/opencs/model/tools/startscriptcheck.hpp | 31 +- apps/opencs/model/tools/tools.cpp | 294 +- apps/opencs/model/tools/tools.hpp | 101 +- apps/opencs/model/tools/topicinfocheck.cpp | 193 +- apps/opencs/model/tools/topicinfocheck.hpp | 78 +- apps/opencs/model/world/actoradapter.cpp | 184 +- apps/opencs/model/world/actoradapter.hpp | 86 +- apps/opencs/model/world/cell.cpp | 12 +- apps/opencs/model/world/cell.hpp | 13 +- apps/opencs/model/world/cellcoordinates.cpp | 78 +- apps/opencs/model/world/cellcoordinates.hpp | 97 +- apps/opencs/model/world/cellselection.cpp | 35 +- apps/opencs/model/world/cellselection.hpp | 53 +- apps/opencs/model/world/collection.hpp | 625 +- apps/opencs/model/world/collectionbase.cpp | 24 +- apps/opencs/model/world/collectionbase.hpp | 125 +- apps/opencs/model/world/columnbase.cpp | 62 +- apps/opencs/model/world/columnbase.hpp | 88 +- apps/opencs/model/world/columnimp.cpp | 74 +- apps/opencs/model/world/columnimp.hpp | 2098 ++-- apps/opencs/model/world/columns.cpp | 392 +- apps/opencs/model/world/columns.hpp | 18 +- apps/opencs/model/world/commanddispatcher.cpp | 232 +- apps/opencs/model/world/commanddispatcher.hpp | 75 +- apps/opencs/model/world/commandmacro.cpp | 17 +- apps/opencs/model/world/commandmacro.hpp | 25 +- apps/opencs/model/world/commands.cpp | 304 +- apps/opencs/model/world/commands.hpp | 331 +- apps/opencs/model/world/data.cpp | 1412 +-- apps/opencs/model/world/data.hpp | 407 +- apps/opencs/model/world/defaultgmsts.cpp | 1414 ++- apps/opencs/model/world/defaultgmsts.hpp | 24 +- apps/opencs/model/world/idcollection.cpp | 58 + apps/opencs/model/world/idcollection.hpp | 141 +- .../model/world/idcompletionmanager.cpp | 72 +- .../model/world/idcompletionmanager.hpp | 24 +- apps/opencs/model/world/idtable.cpp | 261 +- apps/opencs/model/world/idtable.hpp | 140 +- apps/opencs/model/world/idtablebase.cpp | 5 +- apps/opencs/model/world/idtablebase.hpp | 77 +- apps/opencs/model/world/idtableproxymodel.cpp | 74 +- apps/opencs/model/world/idtableproxymodel.hpp | 77 +- apps/opencs/model/world/idtree.cpp | 91 +- apps/opencs/model/world/idtree.hpp | 76 +- apps/opencs/model/world/info.hpp | 5 +- apps/opencs/model/world/infocollection.cpp | 282 +- apps/opencs/model/world/infocollection.hpp | 76 +- apps/opencs/model/world/infoselectwrapper.cpp | 205 +- apps/opencs/model/world/infoselectwrapper.hpp | 32 +- .../model/world/infotableproxymodel.cpp | 49 +- .../model/world/infotableproxymodel.hpp | 46 +- apps/opencs/model/world/land.cpp | 13 +- apps/opencs/model/world/land.hpp | 9 +- apps/opencs/model/world/landtexture.cpp | 9 +- apps/opencs/model/world/landtexture.hpp | 9 +- .../world/landtexturetableproxymodel.cpp | 4 +- .../world/landtexturetableproxymodel.hpp | 15 +- apps/opencs/model/world/metadata.cpp | 22 +- apps/opencs/model/world/metadata.hpp | 13 +- .../model/world/nestedcoladapterimp.cpp | 721 +- .../model/world/nestedcoladapterimp.hpp | 299 +- apps/opencs/model/world/nestedcollection.cpp | 10 +- apps/opencs/model/world/nestedcollection.hpp | 7 +- .../model/world/nestedcolumnadapter.hpp | 10 +- .../opencs/model/world/nestedidcollection.hpp | 175 +- .../model/world/nestedinfocollection.cpp | 95 +- .../model/world/nestedinfocollection.hpp | 41 +- .../model/world/nestedtableproxymodel.cpp | 70 +- .../model/world/nestedtableproxymodel.hpp | 31 +- .../opencs/model/world/nestedtablewrapper.cpp | 6 - .../opencs/model/world/nestedtablewrapper.hpp | 20 +- apps/opencs/model/world/pathgrid.cpp | 16 +- apps/opencs/model/world/pathgrid.hpp | 17 +- apps/opencs/model/world/record.cpp | 10 +- apps/opencs/model/world/record.hpp | 72 +- apps/opencs/model/world/ref.cpp | 8 +- apps/opencs/model/world/ref.hpp | 10 +- apps/opencs/model/world/refcollection.cpp | 311 +- apps/opencs/model/world/refcollection.hpp | 73 +- apps/opencs/model/world/refidadapter.cpp | 9 - apps/opencs/model/world/refidadapter.hpp | 70 +- apps/opencs/model/world/refidadapterimp.cpp | 1670 +-- apps/opencs/model/world/refidadapterimp.hpp | 2346 ++-- apps/opencs/model/world/refidcollection.cpp | 818 +- apps/opencs/model/world/refidcollection.hpp | 162 +- apps/opencs/model/world/refiddata.cpp | 353 +- apps/opencs/model/world/refiddata.hpp | 331 +- apps/opencs/model/world/regionmap.cpp | 343 +- apps/opencs/model/world/regionmap.hpp | 137 +- apps/opencs/model/world/resources.cpp | 69 +- apps/opencs/model/world/resources.hpp | 32 +- apps/opencs/model/world/resourcesmanager.cpp | 43 +- apps/opencs/model/world/resourcesmanager.hpp | 28 +- apps/opencs/model/world/resourcetable.cpp | 102 +- apps/opencs/model/world/resourcetable.hpp | 70 +- apps/opencs/model/world/scope.cpp | 46 +- apps/opencs/model/world/scope.hpp | 7 +- apps/opencs/model/world/scriptcontext.cpp | 101 +- apps/opencs/model/world/scriptcontext.hpp | 55 +- apps/opencs/model/world/subcellcollection.hpp | 33 +- apps/opencs/model/world/tablemimedata.cpp | 158 +- apps/opencs/model/world/tablemimedata.hpp | 76 +- apps/opencs/model/world/universalid.cpp | 471 +- apps/opencs/model/world/universalid.hpp | 332 +- apps/opencs/view/doc/adjusterwidget.cpp | 82 +- apps/opencs/view/doc/adjusterwidget.hpp | 48 +- apps/opencs/view/doc/filedialog.cpp | 160 +- apps/opencs/view/doc/filedialog.hpp | 44 +- apps/opencs/view/doc/filewidget.cpp | 38 +- apps/opencs/view/doc/filewidget.hpp | 33 +- .../view/doc/globaldebugprofilemenu.cpp | 71 +- .../view/doc/globaldebugprofilemenu.hpp | 39 +- apps/opencs/view/doc/loader.cpp | 197 +- apps/opencs/view/doc/loader.hpp | 91 +- apps/opencs/view/doc/newgame.cpp | 62 +- apps/opencs/view/doc/newgame.hpp | 39 +- apps/opencs/view/doc/operation.cpp | 132 +- apps/opencs/view/doc/operation.hpp | 49 +- apps/opencs/view/doc/operations.cpp | 57 +- apps/opencs/view/doc/operations.hpp | 29 +- apps/opencs/view/doc/runlogsubview.cpp | 19 +- apps/opencs/view/doc/runlogsubview.hpp | 16 +- apps/opencs/view/doc/sizehint.cpp | 11 +- apps/opencs/view/doc/sizehint.hpp | 14 +- apps/opencs/view/doc/startup.cpp | 125 +- apps/opencs/view/doc/startup.hpp | 30 +- apps/opencs/view/doc/subview.cpp | 43 +- apps/opencs/view/doc/subview.hpp | 72 +- apps/opencs/view/doc/subviewfactory.cpp | 32 +- apps/opencs/view/doc/subviewfactory.hpp | 42 +- apps/opencs/view/doc/subviewfactoryimp.hpp | 41 +- apps/opencs/view/doc/view.cpp | 721 +- apps/opencs/view/doc/view.hpp | 284 +- apps/opencs/view/doc/viewmanager.cpp | 312 +- apps/opencs/view/doc/viewmanager.hpp | 78 +- apps/opencs/view/filter/editwidget.cpp | 218 +- apps/opencs/view/filter/editwidget.hpp | 99 +- apps/opencs/view/filter/filterbox.cpp | 60 +- apps/opencs/view/filter/filterbox.hpp | 49 +- apps/opencs/view/filter/filterdata.hpp | 19 + apps/opencs/view/filter/recordfilterbox.cpp | 35 +- apps/opencs/view/filter/recordfilterbox.hpp | 35 +- apps/opencs/view/prefs/contextmenulist.cpp | 4 +- apps/opencs/view/prefs/contextmenulist.hpp | 32 +- apps/opencs/view/prefs/dialogue.cpp | 101 +- apps/opencs/view/prefs/dialogue.hpp | 34 +- apps/opencs/view/prefs/keybindingpage.cpp | 22 +- apps/opencs/view/prefs/keybindingpage.hpp | 24 +- apps/opencs/view/prefs/page.cpp | 34 +- apps/opencs/view/prefs/page.hpp | 14 +- apps/opencs/view/prefs/pagebase.cpp | 10 +- apps/opencs/view/prefs/pagebase.hpp | 22 +- apps/opencs/view/render/actor.cpp | 36 +- apps/opencs/view/render/actor.hpp | 17 +- apps/opencs/view/render/brushdraw.cpp | 236 +- apps/opencs/view/render/brushdraw.hpp | 43 +- apps/opencs/view/render/cameracontroller.cpp | 91 +- apps/opencs/view/render/cameracontroller.hpp | 245 +- apps/opencs/view/render/cell.cpp | 358 +- apps/opencs/view/render/cell.hpp | 195 +- apps/opencs/view/render/cellarrow.cpp | 205 +- apps/opencs/view/render/cellarrow.hpp | 65 +- apps/opencs/view/render/cellborder.cpp | 17 +- apps/opencs/view/render/cellborder.hpp | 2 - apps/opencs/view/render/cellmarker.cpp | 44 +- apps/opencs/view/render/cellmarker.hpp | 55 +- apps/opencs/view/render/cellwater.cpp | 50 +- apps/opencs/view/render/cellwater.hpp | 52 +- apps/opencs/view/render/commands.cpp | 15 +- apps/opencs/view/render/commands.hpp | 3 +- apps/opencs/view/render/editmode.cpp | 63 +- apps/opencs/view/render/editmode.hpp | 130 +- apps/opencs/view/render/instancedragmodes.hpp | 5 +- apps/opencs/view/render/instancemode.cpp | 773 +- apps/opencs/view/render/instancemode.hpp | 159 +- apps/opencs/view/render/instancemovemode.cpp | 26 +- apps/opencs/view/render/instancemovemode.hpp | 7 +- .../view/render/instanceselectionmode.cpp | 383 +- .../view/render/instanceselectionmode.hpp | 97 +- apps/opencs/view/render/lighting.cpp | 48 +- apps/opencs/view/render/lighting.hpp | 25 +- apps/opencs/view/render/lightingbright.cpp | 7 +- apps/opencs/view/render/lightingbright.hpp | 14 +- apps/opencs/view/render/lightingday.cpp | 11 +- apps/opencs/view/render/lightingday.hpp | 18 +- apps/opencs/view/render/lightingnight.cpp | 11 +- apps/opencs/view/render/lightingnight.hpp | 18 +- apps/opencs/view/render/object.cpp | 493 +- apps/opencs/view/render/object.hpp | 222 +- apps/opencs/view/render/orbitcameramode.cpp | 21 +- apps/opencs/view/render/orbitcameramode.hpp | 42 +- .../view/render/pagedworldspacewidget.cpp | 601 +- .../view/render/pagedworldspacewidget.hpp | 240 +- apps/opencs/view/render/pathgrid.cpp | 211 +- apps/opencs/view/render/pathgrid.hpp | 147 +- apps/opencs/view/render/pathgridmode.cpp | 61 +- apps/opencs/view/render/pathgridmode.hpp | 67 +- .../view/render/pathgridselectionmode.cpp | 32 +- .../view/render/pathgridselectionmode.hpp | 47 +- apps/opencs/view/render/previewwidget.cpp | 104 +- apps/opencs/view/render/previewwidget.hpp | 40 +- apps/opencs/view/render/scenewidget.cpp | 1023 +- apps/opencs/view/render/scenewidget.hpp | 172 +- apps/opencs/view/render/selectionmode.cpp | 44 +- apps/opencs/view/render/selectionmode.hpp | 56 +- apps/opencs/view/render/tagbase.cpp | 10 +- apps/opencs/view/render/tagbase.hpp | 14 +- apps/opencs/view/render/terrainselection.cpp | 291 +- apps/opencs/view/render/terrainselection.hpp | 75 +- apps/opencs/view/render/terrainshapemode.cpp | 956 +- apps/opencs/view/render/terrainshapemode.hpp | 279 +- apps/opencs/view/render/terrainstorage.cpp | 117 +- apps/opencs/view/render/terrainstorage.hpp | 43 +- .../opencs/view/render/terraintexturemode.cpp | 570 +- .../opencs/view/render/terraintexturemode.hpp | 173 +- .../view/render/unpagedworldspacewidget.cpp | 214 +- .../view/render/unpagedworldspacewidget.hpp | 138 +- apps/opencs/view/render/worldspacewidget.cpp | 474 +- apps/opencs/view/render/worldspacewidget.hpp | 372 +- apps/opencs/view/tools/merge.cpp | 130 +- apps/opencs/view/tools/merge.hpp | 47 +- apps/opencs/view/tools/reportsubview.cpp | 31 +- apps/opencs/view/tools/reportsubview.hpp | 23 +- apps/opencs/view/tools/reporttable.cpp | 240 +- apps/opencs/view/tools/reporttable.hpp | 111 +- apps/opencs/view/tools/searchbox.cpp | 131 +- apps/opencs/view/tools/searchbox.hpp | 72 +- apps/opencs/view/tools/searchsubview.cpp | 126 +- apps/opencs/view/tools/searchsubview.hpp | 52 +- apps/opencs/view/tools/subviews.cpp | 15 +- apps/opencs/view/tools/subviews.hpp | 2 +- apps/opencs/view/widget/coloreditor.cpp | 35 +- apps/opencs/view/widget/coloreditor.hpp | 50 +- apps/opencs/view/widget/colorpickerpopup.cpp | 29 +- apps/opencs/view/widget/colorpickerpopup.hpp | 14 +- apps/opencs/view/widget/completerpopup.cpp | 9 +- apps/opencs/view/widget/completerpopup.hpp | 6 +- apps/opencs/view/widget/droplineedit.cpp | 16 +- apps/opencs/view/widget/droplineedit.hpp | 23 +- apps/opencs/view/widget/modebutton.cpp | 17 +- apps/opencs/view/widget/modebutton.hpp | 30 +- apps/opencs/view/widget/pushbutton.cpp | 64 +- apps/opencs/view/widget/pushbutton.hpp | 78 +- apps/opencs/view/widget/scenetool.cpp | 26 +- apps/opencs/view/widget/scenetool.hpp | 28 +- apps/opencs/view/widget/scenetoolbar.cpp | 38 +- apps/opencs/view/widget/scenetoolbar.hpp | 34 +- apps/opencs/view/widget/scenetoolmode.cpp | 131 +- apps/opencs/view/widget/scenetoolmode.hpp | 87 +- apps/opencs/view/widget/scenetoolrun.cpp | 110 +- apps/opencs/view/widget/scenetoolrun.hpp | 61 +- .../view/widget/scenetoolshapebrush.cpp | 191 +- .../view/widget/scenetoolshapebrush.hpp | 134 +- .../view/widget/scenetooltexturebrush.cpp | 261 +- .../view/widget/scenetooltexturebrush.hpp | 150 +- apps/opencs/view/widget/scenetooltoggle.cpp | 116 +- apps/opencs/view/widget/scenetooltoggle.hpp | 80 +- apps/opencs/view/widget/scenetooltoggle2.cpp | 87 +- apps/opencs/view/widget/scenetooltoggle2.hpp | 88 +- apps/opencs/view/world/bodypartcreator.cpp | 11 +- apps/opencs/view/world/bodypartcreator.hpp | 29 +- apps/opencs/view/world/cellcreator.cpp | 99 +- apps/opencs/view/world/cellcreator.hpp | 61 +- apps/opencs/view/world/colordelegate.cpp | 40 +- apps/opencs/view/world/colordelegate.hpp | 32 +- apps/opencs/view/world/creator.cpp | 22 +- apps/opencs/view/world/creator.hpp | 95 +- .../opencs/view/world/datadisplaydelegate.cpp | 96 +- .../opencs/view/world/datadisplaydelegate.hpp | 61 +- apps/opencs/view/world/dialoguecreator.cpp | 46 +- apps/opencs/view/world/dialoguecreator.hpp | 43 +- apps/opencs/view/world/dialoguespinbox.cpp | 18 +- apps/opencs/view/world/dialoguespinbox.hpp | 30 +- apps/opencs/view/world/dialoguesubview.cpp | 633 +- apps/opencs/view/world/dialoguesubview.hpp | 229 +- apps/opencs/view/world/dragdroputils.cpp | 34 +- apps/opencs/view/world/dragdroputils.hpp | 10 +- apps/opencs/view/world/dragrecordtable.cpp | 64 +- apps/opencs/view/world/dragrecordtable.hpp | 45 +- apps/opencs/view/world/enumdelegate.cpp | 101 +- apps/opencs/view/world/enumdelegate.hpp | 84 +- .../world/extendedcommandconfigurator.cpp | 57 +- .../world/extendedcommandconfigurator.hpp | 78 +- apps/opencs/view/world/genericcreator.cpp | 173 +- apps/opencs/view/world/genericcreator.hpp | 139 +- apps/opencs/view/world/globalcreator.cpp | 14 +- apps/opencs/view/world/globalcreator.hpp | 23 +- .../view/world/idcompletiondelegate.cpp | 77 +- .../view/world/idcompletiondelegate.hpp | 42 +- apps/opencs/view/world/idtypedelegate.cpp | 42 +- apps/opencs/view/world/idtypedelegate.hpp | 32 +- apps/opencs/view/world/idvalidator.cpp | 42 +- apps/opencs/view/world/idvalidator.hpp | 33 +- apps/opencs/view/world/infocreator.cpp | 105 +- apps/opencs/view/world/infocreator.hpp | 64 +- apps/opencs/view/world/landcreator.cpp | 27 +- apps/opencs/view/world/landcreator.hpp | 49 +- apps/opencs/view/world/landtexturecreator.cpp | 12 +- apps/opencs/view/world/landtexturecreator.hpp | 45 +- apps/opencs/view/world/nestedtable.cpp | 77 +- apps/opencs/view/world/nestedtable.hpp | 25 +- apps/opencs/view/world/pathgridcreator.cpp | 46 +- apps/opencs/view/world/pathgridcreator.hpp | 64 +- apps/opencs/view/world/previewsubview.cpp | 67 +- apps/opencs/view/world/previewsubview.hpp | 23 +- apps/opencs/view/world/recordbuttonbar.cpp | 145 +- apps/opencs/view/world/recordbuttonbar.hpp | 65 +- .../view/world/recordstatusdelegate.cpp | 55 +- .../view/world/recordstatusdelegate.hpp | 38 +- .../view/world/referenceablecreator.cpp | 70 +- .../view/world/referenceablecreator.hpp | 39 +- apps/opencs/view/world/referencecreator.cpp | 70 +- apps/opencs/view/world/referencecreator.hpp | 61 +- apps/opencs/view/world/regionmap.cpp | 276 +- apps/opencs/view/world/regionmap.hpp | 89 +- apps/opencs/view/world/regionmapsubview.cpp | 23 +- apps/opencs/view/world/regionmapsubview.hpp | 19 +- apps/opencs/view/world/scenesubview.cpp | 170 +- apps/opencs/view/world/scenesubview.hpp | 70 +- apps/opencs/view/world/scriptedit.cpp | 232 +- apps/opencs/view/world/scriptedit.hpp | 177 +- apps/opencs/view/world/scripterrortable.cpp | 156 +- apps/opencs/view/world/scripterrortable.hpp | 55 +- apps/opencs/view/world/scripthighlighter.cpp | 135 +- apps/opencs/view/world/scripthighlighter.hpp | 138 +- apps/opencs/view/world/scriptsubview.cpp | 269 +- apps/opencs/view/world/scriptsubview.hpp | 85 +- apps/opencs/view/world/startscriptcreator.cpp | 46 +- apps/opencs/view/world/startscriptcreator.hpp | 82 +- apps/opencs/view/world/subviews.cpp | 233 +- apps/opencs/view/world/subviews.hpp | 2 +- apps/opencs/view/world/table.cpp | 590 +- apps/opencs/view/world/table.hpp | 186 +- apps/opencs/view/world/tablebottombox.cpp | 142 +- apps/opencs/view/world/tablebottombox.hpp | 124 +- apps/opencs/view/world/tableeditidaction.cpp | 22 +- apps/opencs/view/world/tableeditidaction.hpp | 21 +- .../world/tableheadermouseeventhandler.cpp | 69 + .../world/tableheadermouseeventhandler.hpp | 30 + apps/opencs/view/world/tablesubview.cpp | 209 +- apps/opencs/view/world/tablesubview.hpp | 63 +- apps/opencs/view/world/util.cpp | 181 +- apps/opencs/view/world/util.hpp | 142 +- apps/opencs/view/world/vartypedelegate.cpp | 89 +- apps/opencs/view/world/vartypedelegate.hpp | 48 +- apps/opencs_tests/CMakeLists.txt | 33 + apps/opencs_tests/main.cpp | 7 + .../model/world/testinfocollection.cpp | 664 ++ .../model/world/testuniversalid.cpp | 188 + apps/openmw/CMakeLists.txt | 108 +- apps/openmw/android_main.cpp | 37 +- apps/openmw/engine.cpp | 916 +- apps/openmw/engine.hpp | 292 +- apps/openmw/main.cpp | 254 +- apps/openmw/mwbase/dialoguemanager.hpp | 174 +- apps/openmw/mwbase/environment.cpp | 189 +- apps/openmw/mwbase/environment.hpp | 137 +- apps/openmw/mwbase/inputmanager.hpp | 91 +- apps/openmw/mwbase/journal.hpp | 136 +- apps/openmw/mwbase/luamanager.hpp | 117 + apps/openmw/mwbase/mechanicsmanager.hpp | 504 +- apps/openmw/mwbase/scriptmanager.hpp | 49 +- apps/openmw/mwbase/soundmanager.hpp | 240 +- apps/openmw/mwbase/statemanager.hpp | 104 +- apps/openmw/mwbase/windowmanager.hpp | 630 +- apps/openmw/mwbase/world.hpp | 1219 +-- apps/openmw/mwclass/activator.cpp | 135 +- apps/openmw/mwclass/activator.hpp | 56 +- apps/openmw/mwclass/actor.cpp | 56 +- apps/openmw/mwclass/actor.hpp | 31 +- apps/openmw/mwclass/apparatus.cpp | 102 +- apps/openmw/mwclass/apparatus.hpp | 62 +- apps/openmw/mwclass/armor.cpp | 308 +- apps/openmw/mwclass/armor.hpp | 102 +- apps/openmw/mwclass/bodypart.cpp | 47 +- apps/openmw/mwclass/bodypart.hpp | 24 +- apps/openmw/mwclass/book.cpp | 146 +- apps/openmw/mwclass/book.hpp | 72 +- apps/openmw/mwclass/classes.cpp | 35 +- apps/openmw/mwclass/classmodel.hpp | 29 + apps/openmw/mwclass/clothing.cpp | 205 +- apps/openmw/mwclass/clothing.hpp | 90 +- apps/openmw/mwclass/container.cpp | 210 +- apps/openmw/mwclass/container.hpp | 101 +- apps/openmw/mwclass/creature.cpp | 611 +- apps/openmw/mwclass/creature.hpp | 191 +- apps/openmw/mwclass/creaturelevlist.cpp | 103 +- apps/openmw/mwclass/creaturelevlist.hpp | 43 +- apps/openmw/mwclass/door.cpp | 255 +- apps/openmw/mwclass/door.hpp | 73 +- apps/openmw/mwclass/esm4base.cpp | 44 + apps/openmw/mwclass/esm4base.hpp | 129 + apps/openmw/mwclass/ingredient.cpp | 131 +- apps/openmw/mwclass/ingredient.hpp | 63 +- apps/openmw/mwclass/itemlevlist.cpp | 19 +- apps/openmw/mwclass/itemlevlist.hpp | 17 +- apps/openmw/mwclass/light.cpp | 190 +- apps/openmw/mwclass/light.hpp | 94 +- apps/openmw/mwclass/light4.cpp | 27 + apps/openmw/mwclass/light4.hpp | 22 + apps/openmw/mwclass/lockpick.cpp | 123 +- apps/openmw/mwclass/lockpick.hpp | 83 +- apps/openmw/mwclass/misc.cpp | 234 +- apps/openmw/mwclass/misc.hpp | 68 +- apps/openmw/mwclass/npc.cpp | 1072 +- apps/openmw/mwclass/npc.hpp | 237 +- apps/openmw/mwclass/potion.cpp | 117 +- apps/openmw/mwclass/potion.hpp | 61 +- apps/openmw/mwclass/probe.cpp | 123 +- apps/openmw/mwclass/probe.hpp | 78 +- apps/openmw/mwclass/repair.cpp | 112 +- apps/openmw/mwclass/repair.hpp | 73 +- apps/openmw/mwclass/static.cpp | 51 +- apps/openmw/mwclass/static.hpp | 38 +- apps/openmw/mwclass/weapon.cpp | 216 +- apps/openmw/mwclass/weapon.hpp | 99 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 510 +- apps/openmw/mwdialogue/dialoguemanagerimp.hpp | 163 +- apps/openmw/mwdialogue/filter.cpp | 464 +- apps/openmw/mwdialogue/filter.hpp | 67 +- apps/openmw/mwdialogue/hypertextparser.cpp | 36 +- apps/openmw/mwdialogue/hypertextparser.hpp | 12 +- apps/openmw/mwdialogue/journalentry.cpp | 128 +- apps/openmw/mwdialogue/journalentry.hpp | 38 +- apps/openmw/mwdialogue/journalimp.cpp | 178 +- apps/openmw/mwdialogue/journalimp.hpp | 84 +- apps/openmw/mwdialogue/keywordsearch.cpp | 0 apps/openmw/mwdialogue/keywordsearch.hpp | 395 +- apps/openmw/mwdialogue/quest.cpp | 114 +- apps/openmw/mwdialogue/quest.hpp | 36 +- apps/openmw/mwdialogue/scripttest.cpp | 220 +- apps/openmw/mwdialogue/scripttest.hpp | 12 +- apps/openmw/mwdialogue/selectwrapper.cpp | 505 +- apps/openmw/mwdialogue/selectwrapper.hpp | 139 +- apps/openmw/mwdialogue/topic.cpp | 45 +- apps/openmw/mwdialogue/topic.hpp | 54 +- apps/openmw/mwgui/alchemywindow.cpp | 152 +- apps/openmw/mwgui/alchemywindow.hpp | 14 +- apps/openmw/mwgui/backgroundimage.cpp | 90 +- apps/openmw/mwgui/backgroundimage.hpp | 14 +- apps/openmw/mwgui/birth.cpp | 112 +- apps/openmw/mwgui/birth.hpp | 7 +- apps/openmw/mwgui/bookpage.cpp | 2439 +++-- apps/openmw/mwgui/bookpage.hpp | 73 +- apps/openmw/mwgui/bookwindow.cpp | 56 +- apps/openmw/mwgui/bookwindow.hpp | 66 +- apps/openmw/mwgui/charactercreation.cpp | 334 +- apps/openmw/mwgui/charactercreation.hpp | 154 +- apps/openmw/mwgui/class.cpp | 407 +- apps/openmw/mwgui/class.hpp | 79 +- apps/openmw/mwgui/companionitemmodel.cpp | 12 +- apps/openmw/mwgui/companionitemmodel.hpp | 6 +- apps/openmw/mwgui/companionwindow.cpp | 284 +- apps/openmw/mwgui/companionwindow.hpp | 4 +- apps/openmw/mwgui/confirmationdialog.cpp | 4 +- apps/openmw/mwgui/confirmationdialog.hpp | 32 +- apps/openmw/mwgui/console.cpp | 546 +- apps/openmw/mwgui/console.hpp | 122 +- apps/openmw/mwgui/container.cpp | 94 +- apps/openmw/mwgui/container.hpp | 11 +- apps/openmw/mwgui/containeritemmodel.cpp | 313 +- apps/openmw/mwgui/containeritemmodel.hpp | 21 +- apps/openmw/mwgui/controllers.cpp | 6 +- apps/openmw/mwgui/controllers.hpp | 20 +- apps/openmw/mwgui/countdialog.cpp | 24 +- apps/openmw/mwgui/countdialog.hpp | 40 +- apps/openmw/mwgui/cursor.cpp | 13 +- apps/openmw/mwgui/cursor.hpp | 12 +- apps/openmw/mwgui/debugwindow.cpp | 216 +- apps/openmw/mwgui/debugwindow.hpp | 11 +- apps/openmw/mwgui/dialogue.cpp | 400 +- apps/openmw/mwgui/dialogue.hpp | 106 +- apps/openmw/mwgui/draganddrop.cpp | 218 +- apps/openmw/mwgui/draganddrop.hpp | 5 +- apps/openmw/mwgui/enchantingdialog.cpp | 123 +- apps/openmw/mwgui/enchantingdialog.hpp | 18 +- apps/openmw/mwgui/exposedwindow.cpp | 10 +- apps/openmw/mwgui/exposedwindow.hpp | 5 +- apps/openmw/mwgui/formatting.cpp | 890 +- apps/openmw/mwgui/formatting.hpp | 206 +- apps/openmw/mwgui/hud.cpp | 157 +- apps/openmw/mwgui/hud.hpp | 18 +- apps/openmw/mwgui/inventoryitemmodel.cpp | 201 +- apps/openmw/mwgui/inventoryitemmodel.hpp | 17 +- apps/openmw/mwgui/inventorywindow.cpp | 365 +- apps/openmw/mwgui/inventorywindow.hpp | 142 +- apps/openmw/mwgui/itemchargeview.cpp | 51 +- apps/openmw/mwgui/itemchargeview.hpp | 72 +- apps/openmw/mwgui/itemmodel.cpp | 73 +- apps/openmw/mwgui/itemmodel.hpp | 60 +- apps/openmw/mwgui/itemselection.cpp | 15 +- apps/openmw/mwgui/itemselection.hpp | 4 +- apps/openmw/mwgui/itemview.cpp | 278 +- apps/openmw/mwgui/itemview.hpp | 14 +- apps/openmw/mwgui/itemwidget.cpp | 72 +- apps/openmw/mwgui/itemwidget.hpp | 17 +- apps/openmw/mwgui/jailscreen.cpp | 67 +- apps/openmw/mwgui/jailscreen.hpp | 26 +- apps/openmw/mwgui/journalbooks.cpp | 403 +- apps/openmw/mwgui/journalbooks.hpp | 21 +- apps/openmw/mwgui/journalviewmodel.cpp | 577 +- apps/openmw/mwgui/journalviewmodel.hpp | 40 +- apps/openmw/mwgui/journalwindow.cpp | 471 +- apps/openmw/mwgui/journalwindow.hpp | 12 +- apps/openmw/mwgui/keyboardnavigation.cpp | 527 +- apps/openmw/mwgui/keyboardnavigation.hpp | 2 +- apps/openmw/mwgui/layout.cpp | 57 +- apps/openmw/mwgui/layout.hpp | 95 +- apps/openmw/mwgui/levelupdialog.cpp | 173 +- apps/openmw/mwgui/levelupdialog.hpp | 20 +- apps/openmw/mwgui/loadingscreen.cpp | 148 +- apps/openmw/mwgui/loadingscreen.hpp | 13 +- apps/openmw/mwgui/mainmenu.cpp | 121 +- apps/openmw/mwgui/mainmenu.hpp | 52 +- apps/openmw/mwgui/mapwindow.cpp | 828 +- apps/openmw/mwgui/mapwindow.hpp | 99 +- apps/openmw/mwgui/merchantrepair.cpp | 228 +- apps/openmw/mwgui/merchantrepair.hpp | 34 +- apps/openmw/mwgui/messagebox.cpp | 234 +- apps/openmw/mwgui/messagebox.hpp | 115 +- apps/openmw/mwgui/mode.hpp | 90 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 39 +- apps/openmw/mwgui/pickpocketitemmodel.hpp | 15 +- apps/openmw/mwgui/postprocessorhud.cpp | 483 + apps/openmw/mwgui/postprocessorhud.hpp | 102 + apps/openmw/mwgui/quickkeysmenu.cpp | 337 +- apps/openmw/mwgui/quickkeysmenu.hpp | 51 +- apps/openmw/mwgui/race.cpp | 167 +- apps/openmw/mwgui/race.hpp | 25 +- apps/openmw/mwgui/recharge.cpp | 200 +- apps/openmw/mwgui/recharge.hpp | 51 +- apps/openmw/mwgui/referenceinterface.cpp | 8 +- apps/openmw/mwgui/referenceinterface.hpp | 3 +- apps/openmw/mwgui/repair.cpp | 226 +- apps/openmw/mwgui/repair.hpp | 53 +- apps/openmw/mwgui/resourceskin.cpp | 16 +- apps/openmw/mwgui/resourceskin.hpp | 2 +- apps/openmw/mwgui/review.cpp | 216 +- apps/openmw/mwgui/review.hpp | 46 +- apps/openmw/mwgui/savegamedialog.cpp | 157 +- apps/openmw/mwgui/savegamedialog.hpp | 20 +- apps/openmw/mwgui/screenfader.cpp | 43 +- apps/openmw/mwgui/screenfader.hpp | 15 +- apps/openmw/mwgui/scrollwindow.cpp | 24 +- apps/openmw/mwgui/scrollwindow.hpp | 35 +- apps/openmw/mwgui/settings.cpp | 292 + apps/openmw/mwgui/settings.hpp | 40 + apps/openmw/mwgui/settingswindow.cpp | 742 +- apps/openmw/mwgui/settingswindow.hpp | 142 +- apps/openmw/mwgui/sortfilteritemmodel.cpp | 268 +- apps/openmw/mwgui/sortfilteritemmodel.hpp | 47 +- apps/openmw/mwgui/soulgemdialog.cpp | 2 +- apps/openmw/mwgui/soulgemdialog.hpp | 8 +- apps/openmw/mwgui/spellbuyingwindow.cpp | 105 +- apps/openmw/mwgui/spellbuyingwindow.hpp | 54 +- apps/openmw/mwgui/spellcreationdialog.cpp | 392 +- apps/openmw/mwgui/spellcreationdialog.hpp | 46 +- apps/openmw/mwgui/spellicons.cpp | 162 +- apps/openmw/mwgui/spellicons.hpp | 18 +- apps/openmw/mwgui/spellmodel.cpp | 90 +- apps/openmw/mwgui/spellmodel.hpp | 8 +- apps/openmw/mwgui/spellview.cpp | 67 +- apps/openmw/mwgui/spellview.hpp | 11 +- apps/openmw/mwgui/spellwindow.cpp | 69 +- apps/openmw/mwgui/spellwindow.hpp | 18 +- apps/openmw/mwgui/statswatcher.cpp | 99 +- apps/openmw/mwgui/statswatcher.hpp | 37 +- apps/openmw/mwgui/statswindow.cpp | 425 +- apps/openmw/mwgui/statswindow.hpp | 109 +- apps/openmw/mwgui/textinput.cpp | 25 +- apps/openmw/mwgui/textinput.hpp | 4 +- apps/openmw/mwgui/timeadvancer.cpp | 13 +- apps/openmw/mwgui/timeadvancer.hpp | 42 +- apps/openmw/mwgui/tooltips.cpp | 456 +- apps/openmw/mwgui/tooltips.hpp | 26 +- apps/openmw/mwgui/tradeitemmodel.cpp | 41 +- apps/openmw/mwgui/tradeitemmodel.hpp | 16 +- apps/openmw/mwgui/tradewindow.cpp | 172 +- apps/openmw/mwgui/tradewindow.hpp | 128 +- apps/openmw/mwgui/trainingwindow.cpp | 140 +- apps/openmw/mwgui/trainingwindow.hpp | 7 +- apps/openmw/mwgui/travelwindow.cpp | 136 +- apps/openmw/mwgui/travelwindow.hpp | 41 +- apps/openmw/mwgui/ustring.hpp | 15 + apps/openmw/mwgui/videowidget.cpp | 189 +- apps/openmw/mwgui/videowidget.hpp | 6 +- apps/openmw/mwgui/waitdialog.cpp | 134 +- apps/openmw/mwgui/waitdialog.hpp | 6 +- apps/openmw/mwgui/widgets.cpp | 919 +- apps/openmw/mwgui/widgets.hpp | 88 +- apps/openmw/mwgui/windowbase.cpp | 38 +- apps/openmw/mwgui/windowbase.hpp | 16 +- apps/openmw/mwgui/windowmanagerimp.cpp | 1425 +-- apps/openmw/mwgui/windowmanagerimp.hpp | 613 +- apps/openmw/mwgui/windowpinnablebase.cpp | 9 +- apps/openmw/mwgui/windowpinnablebase.hpp | 4 +- apps/openmw/mwinput/actionmanager.cpp | 565 +- apps/openmw/mwinput/actionmanager.hpp | 29 +- apps/openmw/mwinput/actions.hpp | 103 +- apps/openmw/mwinput/bindingsmanager.cpp | 283 +- apps/openmw/mwinput/bindingsmanager.hpp | 43 +- apps/openmw/mwinput/controllermanager.cpp | 237 +- apps/openmw/mwinput/controllermanager.hpp | 50 +- apps/openmw/mwinput/controlswitch.cpp | 62 +- apps/openmw/mwinput/controlswitch.hpp | 8 +- apps/openmw/mwinput/gyromanager.cpp | 102 + apps/openmw/mwinput/gyromanager.hpp | 47 + apps/openmw/mwinput/inputmanagerimp.cpp | 146 +- apps/openmw/mwinput/inputmanagerimp.hpp | 75 +- apps/openmw/mwinput/keyboardmanager.cpp | 36 +- apps/openmw/mwinput/keyboardmanager.hpp | 6 +- apps/openmw/mwinput/mousemanager.cpp | 71 +- apps/openmw/mwinput/mousemanager.hpp | 16 +- apps/openmw/mwinput/sdlmappings.cpp | 218 - apps/openmw/mwinput/sensormanager.cpp | 161 +- apps/openmw/mwinput/sensormanager.hpp | 38 +- apps/openmw/mwlua/README.md | 102 + apps/openmw/mwlua/camerabindings.cpp | 129 + apps/openmw/mwlua/camerabindings.hpp | 11 + apps/openmw/mwlua/cellbindings.cpp | 238 + apps/openmw/mwlua/cellbindings.hpp | 12 + apps/openmw/mwlua/context.hpp | 28 + apps/openmw/mwlua/debugbindings.cpp | 85 + apps/openmw/mwlua/debugbindings.hpp | 13 + apps/openmw/mwlua/engineevents.cpp | 107 + apps/openmw/mwlua/engineevents.hpp | 62 + apps/openmw/mwlua/globalscripts.hpp | 49 + apps/openmw/mwlua/inputbindings.cpp | 295 + apps/openmw/mwlua/inputbindings.hpp | 13 + apps/openmw/mwlua/localscripts.cpp | 193 + apps/openmw/mwlua/localscripts.hpp | 88 + apps/openmw/mwlua/luabindings.cpp | 326 + apps/openmw/mwlua/luabindings.hpp | 25 + apps/openmw/mwlua/luaevents.cpp | 107 + apps/openmw/mwlua/luaevents.hpp | 68 + apps/openmw/mwlua/luamanagerimp.cpp | 624 ++ apps/openmw/mwlua/luamanagerimp.hpp | 196 + apps/openmw/mwlua/magicbindings.cpp | 699 ++ apps/openmw/mwlua/magicbindings.hpp | 14 + apps/openmw/mwlua/mwscriptbindings.cpp | 109 + apps/openmw/mwlua/mwscriptbindings.hpp | 15 + apps/openmw/mwlua/nearbybindings.cpp | 274 + apps/openmw/mwlua/nearbybindings.hpp | 13 + apps/openmw/mwlua/object.hpp | 77 + apps/openmw/mwlua/objectbindings.cpp | 625 ++ apps/openmw/mwlua/objectbindings.hpp | 12 + apps/openmw/mwlua/objectvariant.hpp | 59 + apps/openmw/mwlua/playerscripts.hpp | 82 + apps/openmw/mwlua/postprocessingbindings.cpp | 166 + apps/openmw/mwlua/postprocessingbindings.hpp | 13 + apps/openmw/mwlua/stats.cpp | 398 + apps/openmw/mwlua/stats.hpp | 14 + apps/openmw/mwlua/types/activator.cpp | 54 + apps/openmw/mwlua/types/actor.cpp | 362 + apps/openmw/mwlua/types/apparatus.cpp | 55 + apps/openmw/mwlua/types/armor.cpp | 95 + apps/openmw/mwlua/types/book.cpp | 109 + apps/openmw/mwlua/types/clothing.cpp | 88 + apps/openmw/mwlua/types/container.cpp | 67 + apps/openmw/mwlua/types/creature.cpp | 51 + apps/openmw/mwlua/types/door.cpp | 116 + apps/openmw/mwlua/types/ingredient.cpp | 64 + apps/openmw/mwlua/types/item.cpp | 15 + apps/openmw/mwlua/types/levelledlist.cpp | 59 + apps/openmw/mwlua/types/light.cpp | 52 + apps/openmw/mwlua/types/lockable.cpp | 87 + apps/openmw/mwlua/types/lockpick.cpp | 49 + apps/openmw/mwlua/types/misc.cpp | 88 + apps/openmw/mwlua/types/npc.cpp | 57 + apps/openmw/mwlua/types/player.cpp | 16 + apps/openmw/mwlua/types/potion.cpp | 78 + apps/openmw/mwlua/types/probe.cpp | 47 + apps/openmw/mwlua/types/repair.cpp | 47 + apps/openmw/mwlua/types/static.cpp | 37 + apps/openmw/mwlua/types/types.cpp | 246 + apps/openmw/mwlua/types/types.hpp | 101 + apps/openmw/mwlua/types/weapon.cpp | 130 + apps/openmw/mwlua/uibindings.cpp | 193 + apps/openmw/mwlua/uibindings.hpp | 13 + apps/openmw/mwlua/userdataserializer.cpp | 117 + apps/openmw/mwlua/userdataserializer.hpp | 22 + apps/openmw/mwlua/worker.cpp | 92 + apps/openmw/mwlua/worker.hpp | 46 + apps/openmw/mwlua/worldview.cpp | 122 + apps/openmw/mwlua/worldview.hpp | 92 + apps/openmw/mwmechanics/activespells.cpp | 681 +- apps/openmw/mwmechanics/activespells.hpp | 167 +- apps/openmw/mwmechanics/actor.cpp | 61 - apps/openmw/mwmechanics/actor.hpp | 58 +- apps/openmw/mwmechanics/actors.cpp | 2216 ++-- apps/openmw/mwmechanics/actors.hpp | 217 +- apps/openmw/mwmechanics/actorutil.cpp | 16 +- apps/openmw/mwmechanics/actorutil.hpp | 75 +- apps/openmw/mwmechanics/aiactivate.cpp | 71 + apps/openmw/mwmechanics/aiactivate.hpp | 44 + apps/openmw/mwmechanics/aiavoiddoor.cpp | 31 +- apps/openmw/mwmechanics/aiavoiddoor.hpp | 53 +- apps/openmw/mwmechanics/aibreathe.cpp | 8 +- apps/openmw/mwmechanics/aibreathe.hpp | 23 +- apps/openmw/mwmechanics/aicast.cpp | 16 +- apps/openmw/mwmechanics/aicast.hpp | 43 +- apps/openmw/mwmechanics/aicombat.cpp | 580 +- apps/openmw/mwmechanics/aicombat.hpp | 99 +- apps/openmw/mwmechanics/aicombataction.cpp | 174 +- apps/openmw/mwmechanics/aicombataction.hpp | 42 +- apps/openmw/mwmechanics/aiescort.cpp | 264 +- apps/openmw/mwmechanics/aiescort.hpp | 75 +- apps/openmw/mwmechanics/aiface.cpp | 6 +- apps/openmw/mwmechanics/aiface.hpp | 34 +- apps/openmw/mwmechanics/aifollow.cpp | 378 +- apps/openmw/mwmechanics/aifollow.hpp | 108 +- apps/openmw/mwmechanics/aipackage.cpp | 246 +- apps/openmw/mwmechanics/aipackage.hpp | 220 +- apps/openmw/mwmechanics/aipackagetypeid.hpp | 2 +- apps/openmw/mwmechanics/aipursue.cpp | 101 +- apps/openmw/mwmechanics/aipursue.hpp | 43 +- apps/openmw/mwmechanics/aisequence.cpp | 815 +- apps/openmw/mwmechanics/aisequence.hpp | 194 +- apps/openmw/mwmechanics/aisetting.hpp | 15 + apps/openmw/mwmechanics/aistate.hpp | 92 +- apps/openmw/mwmechanics/aistatefwd.hpp | 15 + apps/openmw/mwmechanics/aitemporarybase.hpp | 19 + apps/openmw/mwmechanics/aitimer.hpp | 20 +- apps/openmw/mwmechanics/aitravel.cpp | 119 +- apps/openmw/mwmechanics/aitravel.hpp | 141 +- apps/openmw/mwmechanics/aiwander.cpp | 432 +- apps/openmw/mwmechanics/aiwander.hpp | 178 +- apps/openmw/mwmechanics/alchemy.cpp | 281 +- apps/openmw/mwmechanics/alchemy.hpp | 167 +- apps/openmw/mwmechanics/autocalcspell.cpp | 194 +- apps/openmw/mwmechanics/autocalcspell.hpp | 24 +- apps/openmw/mwmechanics/character.cpp | 4351 +++++--- apps/openmw/mwmechanics/character.hpp | 552 +- apps/openmw/mwmechanics/combat.cpp | 355 +- apps/openmw/mwmechanics/combat.hpp | 61 +- .../creaturecustomdataresetter.hpp | 20 + apps/openmw/mwmechanics/creaturestats.cpp | 354 +- apps/openmw/mwmechanics/creaturestats.hpp | 144 +- apps/openmw/mwmechanics/difficultyscaling.cpp | 16 +- apps/openmw/mwmechanics/disease.hpp | 59 +- apps/openmw/mwmechanics/drawstate.hpp | 10 +- apps/openmw/mwmechanics/enchanting.cpp | 156 +- apps/openmw/mwmechanics/enchanting.hpp | 75 +- apps/openmw/mwmechanics/greetingstate.hpp | 14 + apps/openmw/mwmechanics/inventory.hpp | 42 + apps/openmw/mwmechanics/levelledlist.cpp | 81 + apps/openmw/mwmechanics/levelledlist.hpp | 78 +- apps/openmw/mwmechanics/linkedeffects.cpp | 75 - apps/openmw/mwmechanics/linkedeffects.hpp | 32 - apps/openmw/mwmechanics/magiceffects.cpp | 208 +- apps/openmw/mwmechanics/magiceffects.hpp | 90 +- .../mwmechanics/mechanicsmanagerimp.cpp | 985 +- .../mwmechanics/mechanicsmanagerimp.hpp | 317 +- apps/openmw/mwmechanics/movement.hpp | 5 +- apps/openmw/mwmechanics/npcstats.cpp | 359 +- apps/openmw/mwmechanics/npcstats.hpp | 153 +- apps/openmw/mwmechanics/objects.cpp | 225 +- apps/openmw/mwmechanics/objects.hpp | 32 +- apps/openmw/mwmechanics/obstacle.cpp | 60 +- apps/openmw/mwmechanics/obstacle.hpp | 57 +- apps/openmw/mwmechanics/pathfinding.cpp | 193 +- apps/openmw/mwmechanics/pathfinding.hpp | 175 +- apps/openmw/mwmechanics/pathgrid.cpp | 285 +- apps/openmw/mwmechanics/pathgrid.hpp | 102 +- apps/openmw/mwmechanics/pickpocket.cpp | 34 +- apps/openmw/mwmechanics/pickpocket.hpp | 8 +- apps/openmw/mwmechanics/recharge.cpp | 135 +- apps/openmw/mwmechanics/recharge.hpp | 4 +- apps/openmw/mwmechanics/repair.cpp | 149 +- apps/openmw/mwmechanics/repair.hpp | 4 +- apps/openmw/mwmechanics/security.cpp | 47 +- apps/openmw/mwmechanics/security.hpp | 10 +- apps/openmw/mwmechanics/setbaseaisetting.hpp | 40 + apps/openmw/mwmechanics/spellabsorption.cpp | 93 - apps/openmw/mwmechanics/spellabsorption.hpp | 18 - apps/openmw/mwmechanics/spellcasting.cpp | 478 +- apps/openmw/mwmechanics/spellcasting.hpp | 83 +- apps/openmw/mwmechanics/spelleffects.cpp | 1289 +++ apps/openmw/mwmechanics/spelleffects.hpp | 38 + apps/openmw/mwmechanics/spelllist.cpp | 95 +- apps/openmw/mwmechanics/spelllist.hpp | 55 +- apps/openmw/mwmechanics/spellpriority.cpp | 594 +- apps/openmw/mwmechanics/spellpriority.hpp | 13 +- apps/openmw/mwmechanics/spellresistance.cpp | 25 +- apps/openmw/mwmechanics/spellresistance.hpp | 8 +- apps/openmw/mwmechanics/spells.cpp | 388 +- apps/openmw/mwmechanics/spells.hpp | 117 +- apps/openmw/mwmechanics/spellutil.cpp | 200 +- apps/openmw/mwmechanics/spellutil.hpp | 35 +- apps/openmw/mwmechanics/stat.cpp | 279 +- apps/openmw/mwmechanics/stat.hpp | 161 +- apps/openmw/mwmechanics/steering.cpp | 46 +- apps/openmw/mwmechanics/steering.hpp | 29 +- apps/openmw/mwmechanics/summoning.cpp | 182 +- apps/openmw/mwmechanics/summoning.hpp | 42 +- apps/openmw/mwmechanics/tickableeffects.cpp | 228 - apps/openmw/mwmechanics/tickableeffects.hpp | 20 - apps/openmw/mwmechanics/trading.cpp | 37 +- apps/openmw/mwmechanics/typedaipackage.hpp | 26 +- apps/openmw/mwmechanics/weaponpriority.cpp | 46 +- apps/openmw/mwmechanics/weaponpriority.hpp | 16 +- apps/openmw/mwmechanics/weapontype.cpp | 326 +- apps/openmw/mwmechanics/weapontype.hpp | 273 +- apps/openmw/mwphysics/actor.cpp | 347 +- apps/openmw/mwphysics/actor.hpp | 138 +- apps/openmw/mwphysics/actorconvexcallback.cpp | 67 +- apps/openmw/mwphysics/actorconvexcallback.hpp | 9 +- .../closestnotmerayresultcallback.cpp | 28 +- .../closestnotmerayresultcallback.hpp | 3 +- apps/openmw/mwphysics/collisiontype.hpp | 23 +- apps/openmw/mwphysics/constants.hpp | 12 +- .../mwphysics/contacttestresultcallback.cpp | 8 +- .../mwphysics/contacttestresultcallback.hpp | 5 +- apps/openmw/mwphysics/contacttestwrapper.cpp | 6 +- apps/openmw/mwphysics/contacttestwrapper.h | 6 +- .../deepestnotmecontacttestresultcallback.cpp | 27 +- .../deepestnotmecontacttestresultcallback.hpp | 14 +- .../mwphysics/hasspherecollisioncallback.hpp | 51 +- apps/openmw/mwphysics/heightfield.cpp | 40 +- apps/openmw/mwphysics/heightfield.hpp | 3 +- apps/openmw/mwphysics/movementsolver.cpp | 418 +- apps/openmw/mwphysics/movementsolver.hpp | 19 +- apps/openmw/mwphysics/mtphysics.cpp | 815 +- apps/openmw/mwphysics/mtphysics.hpp | 142 +- apps/openmw/mwphysics/object.cpp | 63 +- apps/openmw/mwphysics/object.hpp | 19 +- apps/openmw/mwphysics/physicssystem.cpp | 681 +- apps/openmw/mwphysics/physicssystem.hpp | 366 +- apps/openmw/mwphysics/projectile.cpp | 124 +- apps/openmw/mwphysics/projectile.hpp | 64 +- .../mwphysics/projectileconvexcallback.cpp | 57 +- .../mwphysics/projectileconvexcallback.hpp | 4 +- apps/openmw/mwphysics/ptrholder.hpp | 54 +- apps/openmw/mwphysics/raycasting.hpp | 39 +- apps/openmw/mwphysics/stepper.cpp | 99 +- apps/openmw/mwphysics/stepper.hpp | 9 +- apps/openmw/mwphysics/trace.cpp | 166 +- apps/openmw/mwphysics/trace.h | 7 +- apps/openmw/mwrender/actoranimation.cpp | 1036 +- apps/openmw/mwrender/actoranimation.hpp | 32 +- apps/openmw/mwrender/actorspaths.cpp | 43 +- apps/openmw/mwrender/actorspaths.hpp | 23 +- apps/openmw/mwrender/animation.cpp | 883 +- apps/openmw/mwrender/animation.hpp | 965 +- apps/openmw/mwrender/bulletdebugdraw.cpp | 301 +- apps/openmw/mwrender/bulletdebugdraw.hpp | 117 +- apps/openmw/mwrender/camera.cpp | 512 +- apps/openmw/mwrender/camera.hpp | 208 +- apps/openmw/mwrender/cell.hpp | 31 +- apps/openmw/mwrender/characterpreview.cpp | 309 +- apps/openmw/mwrender/characterpreview.hpp | 29 +- apps/openmw/mwrender/creatureanimation.cpp | 451 +- apps/openmw/mwrender/creatureanimation.hpp | 18 +- apps/openmw/mwrender/effectmanager.cpp | 124 +- apps/openmw/mwrender/effectmanager.hpp | 13 +- apps/openmw/mwrender/fogmanager.cpp | 59 +- apps/openmw/mwrender/fogmanager.hpp | 11 +- apps/openmw/mwrender/globalmap.cpp | 235 +- apps/openmw/mwrender/globalmap.hpp | 30 +- apps/openmw/mwrender/groundcover.cpp | 254 +- apps/openmw/mwrender/groundcover.hpp | 60 +- apps/openmw/mwrender/landmanager.cpp | 63 +- apps/openmw/mwrender/landmanager.hpp | 7 +- apps/openmw/mwrender/localmap.cpp | 1293 +-- apps/openmw/mwrender/localmap.hpp | 78 +- apps/openmw/mwrender/luminancecalculator.cpp | 153 + apps/openmw/mwrender/luminancecalculator.hpp | 68 + apps/openmw/mwrender/navmesh.cpp | 293 +- apps/openmw/mwrender/navmesh.hpp | 62 +- apps/openmw/mwrender/navmeshmode.cpp | 16 + apps/openmw/mwrender/navmeshmode.hpp | 17 + apps/openmw/mwrender/npcanimation.cpp | 2401 +++-- apps/openmw/mwrender/npcanimation.hpp | 296 +- apps/openmw/mwrender/objectpaging.cpp | 515 +- apps/openmw/mwrender/objectpaging.hpp | 38 +- apps/openmw/mwrender/objects.cpp | 388 +- apps/openmw/mwrender/objects.hpp | 105 +- apps/openmw/mwrender/pathgrid.cpp | 220 +- apps/openmw/mwrender/pathgrid.hpp | 13 +- apps/openmw/mwrender/pingpongcanvas.cpp | 326 + apps/openmw/mwrender/pingpongcanvas.hpp | 100 + apps/openmw/mwrender/pingpongcull.cpp | 93 + apps/openmw/mwrender/pingpongcull.hpp | 35 + apps/openmw/mwrender/postprocessor.cpp | 902 ++ apps/openmw/mwrender/postprocessor.hpp | 279 + .../mwrender/precipitationocclusion.cpp | 172 + .../mwrender/precipitationocclusion.hpp | 34 + apps/openmw/mwrender/recastmesh.cpp | 29 +- apps/openmw/mwrender/recastmesh.hpp | 16 +- apps/openmw/mwrender/renderinginterface.hpp | 4 +- apps/openmw/mwrender/renderingmanager.cpp | 1060 +- apps/openmw/mwrender/renderingmanager.hpp | 144 +- apps/openmw/mwrender/ripples.cpp | 306 + apps/openmw/mwrender/ripples.hpp | 103 + apps/openmw/mwrender/ripplesimulation.cpp | 279 +- apps/openmw/mwrender/ripplesimulation.hpp | 13 +- apps/openmw/mwrender/rotatecontroller.cpp | 89 +- apps/openmw/mwrender/rotatecontroller.hpp | 44 +- apps/openmw/mwrender/screenshotmanager.cpp | 186 +- apps/openmw/mwrender/screenshotmanager.hpp | 8 +- apps/openmw/mwrender/sky.cpp | 2549 ++--- apps/openmw/mwrender/sky.hpp | 145 +- apps/openmw/mwrender/skyutil.cpp | 1210 +++ apps/openmw/mwrender/skyutil.hpp | 348 + apps/openmw/mwrender/terrainstorage.cpp | 87 +- apps/openmw/mwrender/terrainstorage.hpp | 18 +- apps/openmw/mwrender/transparentpass.cpp | 143 + apps/openmw/mwrender/transparentpass.hpp | 45 + apps/openmw/mwrender/util.cpp | 63 +- apps/openmw/mwrender/util.hpp | 11 +- apps/openmw/mwrender/viewovershoulder.cpp | 110 - apps/openmw/mwrender/viewovershoulder.hpp | 30 - apps/openmw/mwrender/vismask.hpp | 44 +- apps/openmw/mwrender/water.cpp | 1485 +-- apps/openmw/mwrender/water.hpp | 36 +- apps/openmw/mwrender/weaponanimation.cpp | 292 +- apps/openmw/mwrender/weaponanimation.hpp | 14 +- apps/openmw/mwscript/aiextensions.cpp | 790 +- apps/openmw/mwscript/aiextensions.hpp | 2 +- apps/openmw/mwscript/animationextensions.cpp | 122 +- apps/openmw/mwscript/animationextensions.hpp | 4 +- apps/openmw/mwscript/cellextensions.cpp | 337 +- apps/openmw/mwscript/cellextensions.hpp | 6 +- apps/openmw/mwscript/compilercontext.cpp | 106 +- apps/openmw/mwscript/compilercontext.hpp | 56 +- apps/openmw/mwscript/consoleextensions.cpp | 5 +- apps/openmw/mwscript/consoleextensions.hpp | 2 +- apps/openmw/mwscript/containerextensions.cpp | 629 +- apps/openmw/mwscript/containerextensions.hpp | 2 +- apps/openmw/mwscript/controlextensions.cpp | 308 +- apps/openmw/mwscript/controlextensions.hpp | 2 +- apps/openmw/mwscript/dialogueextensions.cpp | 286 +- apps/openmw/mwscript/dialogueextensions.hpp | 2 +- apps/openmw/mwscript/docs/vmformat.txt | 8 +- apps/openmw/mwscript/extensions.cpp | 52 +- apps/openmw/mwscript/extensions.hpp | 2 +- apps/openmw/mwscript/globalscripts.cpp | 204 +- apps/openmw/mwscript/globalscripts.hpp | 67 +- apps/openmw/mwscript/guiextensions.cpp | 218 +- apps/openmw/mwscript/guiextensions.hpp | 5 +- apps/openmw/mwscript/interpretercontext.cpp | 368 +- apps/openmw/mwscript/interpretercontext.hpp | 132 +- apps/openmw/mwscript/locals.cpp | 367 +- apps/openmw/mwscript/locals.hpp | 86 +- apps/openmw/mwscript/miscextensions.cpp | 1871 ++-- apps/openmw/mwscript/miscextensions.hpp | 6 +- apps/openmw/mwscript/ref.cpp | 11 +- apps/openmw/mwscript/ref.hpp | 8 +- apps/openmw/mwscript/scriptmanagerimp.cpp | 134 +- apps/openmw/mwscript/scriptmanagerimp.hpp | 82 +- apps/openmw/mwscript/skyextensions.cpp | 140 +- apps/openmw/mwscript/skyextensions.hpp | 4 +- apps/openmw/mwscript/soundextensions.cpp | 277 +- apps/openmw/mwscript/soundextensions.hpp | 5 +- apps/openmw/mwscript/statsextensions.cpp | 1775 ++-- apps/openmw/mwscript/statsextensions.hpp | 4 +- .../mwscript/transformationextensions.cpp | 1088 +- .../mwscript/transformationextensions.hpp | 4 +- apps/openmw/mwscript/userextensions.cpp | 68 +- apps/openmw/mwscript/userextensions.hpp | 2 +- apps/openmw/mwsound/alext.h | 667 +- apps/openmw/mwsound/efx-presets.h | 909 +- apps/openmw/mwsound/efx.h | 1069 +- apps/openmw/mwsound/ffmpeg_decoder.cpp | 755 +- apps/openmw/mwsound/ffmpeg_decoder.hpp | 46 +- apps/openmw/mwsound/loudness.cpp | 102 +- apps/openmw/mwsound/loudness.hpp | 79 +- apps/openmw/mwsound/movieaudiofactory.cpp | 69 +- apps/openmw/mwsound/movieaudiofactory.hpp | 2 +- apps/openmw/mwsound/openal_output.cpp | 2856 ++--- apps/openmw/mwsound/openal_output.hpp | 85 +- apps/openmw/mwsound/regionsoundselector.cpp | 20 +- apps/openmw/mwsound/regionsoundselector.hpp | 22 +- apps/openmw/mwsound/sound.hpp | 142 +- apps/openmw/mwsound/sound_buffer.cpp | 39 +- apps/openmw/mwsound/sound_buffer.hpp | 112 +- apps/openmw/mwsound/sound_decoder.hpp | 30 +- apps/openmw/mwsound/sound_output.hpp | 62 +- apps/openmw/mwsound/soundmanagerimp.cpp | 775 +- apps/openmw/mwsound/soundmanagerimp.hpp | 118 +- apps/openmw/mwsound/type.hpp | 12 +- apps/openmw/mwsound/volumesettings.cpp | 14 +- apps/openmw/mwsound/volumesettings.hpp | 20 +- apps/openmw/mwsound/watersoundupdater.cpp | 5 +- apps/openmw/mwsound/watersoundupdater.hpp | 26 +- apps/openmw/mwstate/character.cpp | 141 +- apps/openmw/mwstate/character.hpp | 77 +- apps/openmw/mwstate/charactermanager.cpp | 61 +- apps/openmw/mwstate/charactermanager.hpp | 49 +- apps/openmw/mwstate/quicksavemanager.cpp | 24 +- apps/openmw/mwstate/quicksavemanager.hpp | 18 +- apps/openmw/mwstate/statemanagerimp.cpp | 366 +- apps/openmw/mwstate/statemanagerimp.hpp | 102 +- apps/openmw/mwworld/action.cpp | 35 +- apps/openmw/mwworld/action.hpp | 43 +- apps/openmw/mwworld/actionalchemy.cpp | 10 +- apps/openmw/mwworld/actionalchemy.hpp | 4 +- apps/openmw/mwworld/actionapply.cpp | 33 +- apps/openmw/mwworld/actionapply.hpp | 27 +- apps/openmw/mwworld/actiondoor.cpp | 5 +- apps/openmw/mwworld/actiondoor.hpp | 6 +- apps/openmw/mwworld/actioneat.cpp | 21 +- apps/openmw/mwworld/actioneat.hpp | 7 +- apps/openmw/mwworld/actionequip.cpp | 37 +- apps/openmw/mwworld/actionequip.hpp | 4 +- apps/openmw/mwworld/actionharvest.cpp | 35 +- apps/openmw/mwworld/actionharvest.hpp | 8 +- apps/openmw/mwworld/actionopen.cpp | 7 +- apps/openmw/mwworld/actionopen.hpp | 9 +- apps/openmw/mwworld/actionread.cpp | 50 +- apps/openmw/mwworld/actionread.hpp | 8 +- apps/openmw/mwworld/actionrepair.cpp | 7 +- apps/openmw/mwworld/actionrepair.hpp | 4 +- apps/openmw/mwworld/actionsoulgem.cpp | 36 +- apps/openmw/mwworld/actionsoulgem.hpp | 8 +- apps/openmw/mwworld/actiontake.cpp | 18 +- apps/openmw/mwworld/actiontake.hpp | 7 +- apps/openmw/mwworld/actiontalk.cpp | 7 +- apps/openmw/mwworld/actiontalk.hpp | 9 +- apps/openmw/mwworld/actionteleport.cpp | 76 +- apps/openmw/mwworld/actionteleport.hpp | 32 +- apps/openmw/mwworld/actiontrap.cpp | 10 +- apps/openmw/mwworld/actiontrap.hpp | 22 +- apps/openmw/mwworld/cell.cpp | 85 + apps/openmw/mwworld/cell.hpp | 72 + apps/openmw/mwworld/cellpreloader.cpp | 288 +- apps/openmw/mwworld/cellpreloader.hpp | 36 +- apps/openmw/mwworld/cellref.cpp | 339 +- apps/openmw/mwworld/cellref.hpp | 172 +- apps/openmw/mwworld/cellreflist.hpp | 14 +- apps/openmw/mwworld/cellstore.cpp | 1045 +- apps/openmw/mwworld/cellstore.hpp | 762 +- apps/openmw/mwworld/cellvisitors.hpp | 8 +- apps/openmw/mwworld/class.cpp | 359 +- apps/openmw/mwworld/class.hpp | 463 +- apps/openmw/mwworld/containerstore.cpp | 1059 +- apps/openmw/mwworld/containerstore.hpp | 473 +- apps/openmw/mwworld/contentloader.hpp | 31 +- apps/openmw/mwworld/customdata.cpp | 123 +- apps/openmw/mwworld/customdata.hpp | 34 +- apps/openmw/mwworld/datetimemanager.cpp | 133 +- apps/openmw/mwworld/datetimemanager.hpp | 10 +- apps/openmw/mwworld/duration.hpp | 39 + apps/openmw/mwworld/esmloader.cpp | 87 +- apps/openmw/mwworld/esmloader.hpp | 33 +- apps/openmw/mwworld/esmstore.cpp | 825 +- apps/openmw/mwworld/esmstore.hpp | 599 +- apps/openmw/mwworld/failedaction.cpp | 14 +- apps/openmw/mwworld/failedaction.hpp | 6 +- apps/openmw/mwworld/globals.cpp | 69 +- apps/openmw/mwworld/globals.hpp | 58 +- apps/openmw/mwworld/globalvariablename.hpp | 44 + apps/openmw/mwworld/groundcoverstore.cpp | 76 + apps/openmw/mwworld/groundcoverstore.hpp | 52 + apps/openmw/mwworld/inventorystore.cpp | 597 +- apps/openmw/mwworld/inventorystore.hpp | 266 +- apps/openmw/mwworld/livecellref.cpp | 87 +- apps/openmw/mwworld/livecellref.hpp | 128 +- apps/openmw/mwworld/localscripts.cpp | 88 +- apps/openmw/mwworld/localscripts.hpp | 43 +- apps/openmw/mwworld/magiceffects.cpp | 236 + apps/openmw/mwworld/magiceffects.hpp | 19 + apps/openmw/mwworld/manualref.cpp | 120 +- apps/openmw/mwworld/manualref.hpp | 19 +- apps/openmw/mwworld/nullaction.hpp | 4 +- apps/openmw/mwworld/player.cpp | 371 +- apps/openmw/mwworld/player.hpp | 88 +- apps/openmw/mwworld/projectilemanager.cpp | 327 +- apps/openmw/mwworld/projectilemanager.hpp | 36 +- apps/openmw/mwworld/ptr.cpp | 126 +- apps/openmw/mwworld/ptr.hpp | 209 +- apps/openmw/mwworld/ptrregistry.hpp | 75 + apps/openmw/mwworld/recordcmp.hpp | 34 - apps/openmw/mwworld/refdata.cpp | 136 +- apps/openmw/mwworld/refdata.hpp | 170 +- apps/openmw/mwworld/registeredclass.hpp | 26 + apps/openmw/mwworld/scene.cpp | 1114 +- apps/openmw/mwworld/scene.hpp | 177 +- apps/openmw/mwworld/spellcaststate.hpp | 14 + apps/openmw/mwworld/store.cpp | 1407 ++- apps/openmw/mwworld/store.hpp | 434 +- apps/openmw/mwworld/timestamp.cpp | 77 +- apps/openmw/mwworld/timestamp.hpp | 43 +- apps/openmw/mwworld/weather.cpp | 1764 +++- apps/openmw/mwworld/weather.hpp | 80 +- apps/openmw/mwworld/worldimp.cpp | 2534 ++--- apps/openmw/mwworld/worldimp.hpp | 915 +- apps/openmw/mwworld/worldmodel.cpp | 460 + apps/openmw/mwworld/worldmodel.hpp | 120 + apps/openmw/options.cpp | 111 + apps/openmw/options.hpp | 11 + apps/openmw/profile.hpp | 153 + apps/openmw_test_suite/CMakeLists.txt | 166 +- .../detournavigator/asyncnavmeshupdater.cpp | 302 + .../detournavigator/generate.hpp | 46 + .../detournavigator/gettilespositions.cpp | 49 +- .../detournavigator/navigator.cpp | 1304 ++- .../detournavigator/navmeshdb.cpp | 180 + .../detournavigator/navmeshtilescache.cpp | 414 +- .../detournavigator/operators.hpp | 34 +- .../detournavigator/recastmeshbuilder.cpp | 472 +- .../detournavigator/recastmeshobject.cpp | 17 +- .../detournavigator/settings.hpp | 50 + .../detournavigator/settingsutils.cpp | 10 +- .../tilecachedrecastmeshmanager.cpp | 405 +- .../esm/test_fixed_string.cpp | 273 +- apps/openmw_test_suite/esm/testrefid.cpp | 485 + apps/openmw_test_suite/esm/variant.cpp | 196 +- apps/openmw_test_suite/esm3/readerscache.cpp | 91 + apps/openmw_test_suite/esm3/testesmwriter.cpp | 87 + apps/openmw_test_suite/esm3/testsaveload.cpp | 415 + apps/openmw_test_suite/esm4/includes.cpp | 89 + apps/openmw_test_suite/esmloader/esmdata.cpp | 114 + apps/openmw_test_suite/esmloader/load.cpp | 136 + apps/openmw_test_suite/esmloader/record.cpp | 77 + .../files/conversion_tests.cpp | 25 + apps/openmw_test_suite/files/hash.cpp | 69 + apps/openmw_test_suite/fx/lexer.cpp | 259 + apps/openmw_test_suite/fx/technique.cpp | 196 + apps/openmw_test_suite/lua/test_async.cpp | 54 + .../lua/test_configuration.cpp | 259 + apps/openmw_test_suite/lua/test_l10n.cpp | 166 + apps/openmw_test_suite/lua/test_lua.cpp | 226 + .../lua/test_scriptscontainer.cpp | 468 + .../lua/test_serialization.cpp | 279 + apps/openmw_test_suite/lua/test_storage.cpp | 116 + .../openmw_test_suite/lua/test_ui_content.cpp | 144 + .../lua/test_utilpackage.cpp | 194 + apps/openmw_test_suite/misc/compression.cpp | 31 + .../misc/progressreporter.cpp | 40 + .../misc/test_endianness.cpp | 158 +- .../misc/test_resourcehelpers.cpp | 74 + .../openmw_test_suite/misc/test_stringops.cpp | 236 +- .../mwdialogue/test_keywordsearch.cpp | 108 +- .../mwscript/test_scripts.cpp | 886 ++ .../openmw_test_suite/mwscript/test_utils.hpp | 263 + apps/openmw_test_suite/mwworld/test_store.cpp | 810 +- .../mwworld/testduration.cpp | 63 + .../mwworld/testtimestamp.cpp | 67 + apps/openmw_test_suite/nif/node.hpp | 72 + .../nifloader/testbulletnifloader.cpp | 1185 ++- .../nifosg/testnifloader.cpp | 230 + apps/openmw_test_suite/openmw/options.cpp | 413 + apps/openmw_test_suite/openmw_test_suite.cpp | 28 +- .../serialization/binaryreader.cpp | 90 + .../serialization/binarywriter.cpp | 63 + .../serialization/format.hpp | 75 + .../serialization/integration.cpp | 56 + .../serialization/sizeaccumulator.cpp | 43 + apps/openmw_test_suite/settings/parser.cpp | 321 +- .../settings/shadermanager.cpp | 70 + .../openmw_test_suite/settings/testvalues.cpp | 120 + .../openmw_test_suite/shader/parsedefines.cpp | 11 +- apps/openmw_test_suite/shader/parsefors.cpp | 9 +- apps/openmw_test_suite/shader/parselinks.cpp | 97 + .../shader/shadermanager.cpp | 267 +- apps/openmw_test_suite/sqlite3/db.cpp | 15 + apps/openmw_test_suite/sqlite3/request.cpp | 275 + apps/openmw_test_suite/sqlite3/statement.cpp | 25 + .../openmw_test_suite/sqlite3/transaction.cpp | 67 + apps/openmw_test_suite/testing_util.hpp | 92 + .../toutf8/data}/french-utf8.txt | 0 .../toutf8/data}/french-win1252.txt | 0 .../toutf8/data}/russian-utf8.txt | 0 .../toutf8/data}/russian-win1251.txt | 0 apps/openmw_test_suite/toutf8/toutf8.cpp | 166 + apps/wizard/CMakeLists.txt | 37 +- apps/wizard/componentselectionpage.cpp | 84 +- apps/wizard/componentselectionpage.hpp | 7 +- apps/wizard/conclusionpage.cpp | 28 +- apps/wizard/conclusionpage.hpp | 5 +- apps/wizard/existinginstallationpage.cpp | 119 +- apps/wizard/existinginstallationpage.hpp | 10 +- apps/wizard/importpage.cpp | 5 +- apps/wizard/importpage.hpp | 5 +- apps/wizard/inisettings.cpp | 141 +- apps/wizard/inisettings.hpp | 33 +- apps/wizard/installationpage.cpp | 205 +- apps/wizard/installationpage.hpp | 32 +- apps/wizard/installationtargetpage.cpp | 42 +- apps/wizard/installationtargetpage.hpp | 9 +- apps/wizard/intropage.cpp | 8 +- apps/wizard/intropage.hpp | 6 +- apps/wizard/languageselectionpage.cpp | 30 +- apps/wizard/languageselectionpage.hpp | 10 +- apps/wizard/main.cpp | 6 +- apps/wizard/mainwizard.cpp | 248 +- apps/wizard/mainwizard.hpp | 30 +- apps/wizard/methodselectionpage.cpp | 19 +- apps/wizard/methodselectionpage.hpp | 7 +- apps/wizard/unshield/unshieldworker.cpp | 595 +- apps/wizard/unshield/unshieldworker.hpp | 69 +- apps/wizard/utils/componentlistwidget.cpp | 26 +- apps/wizard/utils/componentlistwidget.hpp | 8 +- appveyor.yml | 78 - cmake/CheckLuaCustomAllocator.cmake | 39 + cmake/CheckOsgMultiview.cmake | 26 + cmake/FindFFmpeg.cmake | 2 +- cmake/FindGMock.cmake | 24 +- cmake/FindLZ4.cmake | 2 + cmake/FindLuaJit.cmake | 15 +- cmake/LibFindMacros.cmake | 2 +- cmake/OpenMWMacros.cmake | 139 +- cmake/SignMacApplications.cmake | 21 + components/CMakeLists.txt | 465 +- components/bsa/ba2dx10file.cpp | 666 ++ components/bsa/ba2dx10file.hpp | 67 + components/bsa/ba2file.cpp | 49 + components/bsa/ba2file.hpp | 12 + components/bsa/ba2gnrlfile.cpp | 226 + components/bsa/ba2gnrlfile.hpp | 55 + components/bsa/bsa_file.cpp | 228 +- components/bsa/bsa_file.hpp | 237 +- components/bsa/compressedbsafile.cpp | 845 +- components/bsa/compressedbsafile.hpp | 51 +- components/bsa/memorystream.cpp | 48 - components/bsa/memorystream.hpp | 43 +- components/bullethelpers/aabb.hpp | 3 +- components/bullethelpers/collisionobject.hpp | 22 + components/bullethelpers/debug.hpp | 71 + components/bullethelpers/heightfield.hpp | 14 + components/bullethelpers/operators.hpp | 71 +- .../bullethelpers/processtrianglecallback.hpp | 4 +- .../bullethelpers/transformboundingbox.hpp | 2 +- components/compiler/context.hpp | 52 +- components/compiler/controlparser.cpp | 208 +- components/compiler/controlparser.hpp | 85 +- components/compiler/declarationparser.cpp | 101 +- components/compiler/declarationparser.hpp | 45 +- components/compiler/discardparser.cpp | 42 +- components/compiler/discardparser.hpp | 53 +- components/compiler/errorhandler.cpp | 41 +- components/compiler/errorhandler.hpp | 95 +- components/compiler/exception.hpp | 21 +- components/compiler/exprparser.cpp | 457 +- components/compiler/exprparser.hpp | 128 +- components/compiler/extensions.cpp | 151 +- components/compiler/extensions.hpp | 117 +- components/compiler/extensions0.cpp | 809 +- components/compiler/extensions0.hpp | 30 +- components/compiler/fileparser.cpp | 81 +- components/compiler/fileparser.hpp | 75 +- components/compiler/generator.cpp | 1022 +- components/compiler/generator.hpp | 94 +- components/compiler/junkparser.cpp | 40 +- components/compiler/junkparser.hpp | 39 +- components/compiler/lineparser.cpp | 359 +- components/compiler/lineparser.hpp | 126 +- components/compiler/literals.cpp | 81 +- components/compiler/literals.hpp | 53 +- components/compiler/locals.cpp | 93 +- components/compiler/locals.hpp | 46 +- components/compiler/nullerrorhandler.cpp | 4 +- components/compiler/nullerrorhandler.hpp | 8 +- components/compiler/opcodes.cpp | 11 +- components/compiler/opcodes.hpp | 9 +- components/compiler/output.cpp | 47 +- components/compiler/output.hpp | 42 +- components/compiler/parser.cpp | 61 +- components/compiler/parser.hpp | 132 +- components/compiler/quickfileparser.cpp | 47 +- components/compiler/quickfileparser.hpp | 33 +- components/compiler/scanner.cpp | 496 +- components/compiler/scanner.hpp | 267 +- components/compiler/scriptparser.cpp | 72 +- components/compiler/scriptparser.hpp | 51 +- components/compiler/skipparser.cpp | 21 +- components/compiler/skipparser.hpp | 37 +- components/compiler/streamerrorhandler.cpp | 20 +- components/compiler/streamerrorhandler.hpp | 42 +- components/compiler/stringparser.cpp | 89 +- components/compiler/stringparser.hpp | 64 +- components/compiler/tokenloc.hpp | 7 +- components/config/gamesettings.cpp | 260 +- components/config/gamesettings.hpp | 61 +- components/config/launchersettings.cpp | 404 +- components/config/launchersettings.hpp | 101 +- components/config/settingsbase.hpp | 116 - .../contentselector/model/contentmodel.cpp | 449 +- .../contentselector/model/contentmodel.hpp | 70 +- components/contentselector/model/esmfile.cpp | 133 +- components/contentselector/model/esmfile.hpp | 76 +- .../contentselector/model/loadordererror.cpp | 9 +- .../contentselector/model/loadordererror.hpp | 16 +- .../contentselector/model/modelitem.cpp | 19 +- .../contentselector/model/modelitem.hpp | 20 +- .../contentselector/model/naturalsort.cpp | 55 +- .../contentselector/model/naturalsort.hpp | 8 +- components/contentselector/view/combobox.cpp | 17 +- components/contentselector/view/combobox.hpp | 11 +- .../contentselector/view/contentselector.cpp | 123 +- .../contentselector/view/contentselector.hpp | 44 +- components/crashcatcher/crashcatcher.cpp | 411 +- components/crashcatcher/crashcatcher.hpp | 15 +- .../crashcatcher/windows_crashcatcher.cpp | 47 +- .../crashcatcher/windows_crashcatcher.hpp | 14 +- .../crashcatcher/windows_crashmonitor.cpp | 201 +- .../crashcatcher/windows_crashmonitor.hpp | 62 +- components/crashcatcher/windows_crashshm.hpp | 5 +- components/debug/debugdraw.cpp | 423 + components/debug/debugdraw.hpp | 105 + components/debug/debugging.cpp | 335 +- components/debug/debugging.hpp | 128 +- components/debug/debuglog.cpp | 66 +- components/debug/debuglog.hpp | 45 +- components/debug/gldebug.cpp | 152 +- components/debug/gldebug.hpp | 17 +- components/detournavigator/agentbounds.hpp | 34 + .../detournavigator/asyncnavmeshupdater.cpp | 986 +- .../detournavigator/asyncnavmeshupdater.hpp | 222 +- components/detournavigator/changetype.hpp | 20 + .../detournavigator/collisionshapetype.cpp | 22 + .../detournavigator/collisionshapetype.hpp | 18 + .../detournavigator/commulativeaabb.cpp | 27 + .../detournavigator/commulativeaabb.hpp | 23 + .../detournavigator/dbrefgeometryobject.hpp | 57 + components/detournavigator/debug.cpp | 253 +- components/detournavigator/debug.hpp | 91 +- components/detournavigator/dtstatus.hpp | 38 - components/detournavigator/exceptions.hpp | 20 +- .../findrandompointaroundcircle.cpp | 19 +- .../findrandompointaroundcircle.hpp | 12 +- components/detournavigator/findsmoothpath.cpp | 100 +- components/detournavigator/findsmoothpath.hpp | 157 +- components/detournavigator/flags.hpp | 50 - .../detournavigator/generatenavmeshtile.cpp | 102 + .../detournavigator/generatenavmeshtile.hpp | 77 + .../detournavigator/gettilespositions.cpp | 79 + .../detournavigator/gettilespositions.hpp | 79 +- .../guardednavmeshcacheitem.hpp | 17 + .../detournavigator/heightfieldshape.hpp | 45 + components/detournavigator/makenavmesh.cpp | 995 +- components/detournavigator/makenavmesh.hpp | 36 +- components/detournavigator/navigator.cpp | 52 +- components/detournavigator/navigator.hpp | 183 +- components/detournavigator/navigatorimpl.cpp | 183 +- components/detournavigator/navigatorimpl.hpp | 81 +- components/detournavigator/navigatorstub.hpp | 88 +- components/detournavigator/navigatorutils.cpp | 40 + components/detournavigator/navigatorutils.hpp | 69 + .../detournavigator/navmeshcacheitem.cpp | 158 + .../detournavigator/navmeshcacheitem.hpp | 166 +- components/detournavigator/navmeshdata.hpp | 11 +- components/detournavigator/navmeshdb.cpp | 425 + components/detournavigator/navmeshdb.hpp | 192 + components/detournavigator/navmeshdbutils.cpp | 70 + components/detournavigator/navmeshdbutils.hpp | 17 + components/detournavigator/navmeshmanager.cpp | 269 +- components/detournavigator/navmeshmanager.hpp | 68 +- .../detournavigator/navmeshtilescache.cpp | 66 +- .../detournavigator/navmeshtilescache.hpp | 178 +- .../detournavigator/navmeshtileview.cpp | 155 +- .../detournavigator/navmeshtileview.hpp | 2 +- components/detournavigator/objectid.hpp | 23 +- .../detournavigator/objecttransform.hpp | 21 + .../detournavigator/offmeshconnection.hpp | 1 + .../offmeshconnectionsmanager.cpp | 42 + .../offmeshconnectionsmanager.hpp | 17 +- .../oscillatingrecastmeshobject.cpp | 57 - .../oscillatingrecastmeshobject.hpp | 31 - .../detournavigator/preparednavmeshdata.cpp | 47 + .../detournavigator/preparednavmeshdata.hpp | 49 + .../preparednavmeshdatatuple.hpp | 42 + components/detournavigator/raycast.cpp | 13 +- components/detournavigator/raycast.hpp | 8 +- components/detournavigator/recast.cpp | 83 + components/detournavigator/recast.hpp | 71 + .../detournavigator/recastallocutils.hpp | 4 +- components/detournavigator/recastcontext.cpp | 47 + components/detournavigator/recastcontext.hpp | 29 + .../detournavigator/recastglobalallocator.hpp | 12 +- components/detournavigator/recastmesh.cpp | 39 +- components/detournavigator/recastmesh.hpp | 203 +- .../detournavigator/recastmeshbuilder.cpp | 353 +- .../detournavigator/recastmeshbuilder.hpp | 61 +- .../detournavigator/recastmeshobject.cpp | 43 +- .../detournavigator/recastmeshobject.hpp | 74 +- .../detournavigator/recastmeshprovider.hpp | 33 + components/detournavigator/recastparams.hpp | 35 + .../detournavigator/recasttempallocator.hpp | 14 +- components/detournavigator/ref.hpp | 60 + components/detournavigator/serialization.cpp | 282 + components/detournavigator/serialization.hpp | 30 + components/detournavigator/settings.cpp | 91 +- components/detournavigator/settings.hpp | 48 +- components/detournavigator/settingsutils.hpp | 84 +- components/detournavigator/sharednavmesh.hpp | 13 - .../sharednavmeshcacheitem.hpp | 13 + components/detournavigator/stats.cpp | 40 + components/detournavigator/stats.hpp | 54 + components/detournavigator/status.hpp | 6 +- components/detournavigator/tilebounds.hpp | 57 + .../tilecachedrecastmeshmanager.cpp | 523 +- .../tilecachedrecastmeshmanager.hpp | 160 +- .../detournavigator/tilespositionsrange.hpp | 27 + components/detournavigator/updateguard.hpp | 31 + components/detournavigator/version.hpp | 17 +- components/esm/activespells.cpp | 69 - components/esm/activespells.hpp | 46 - components/esm/aipackage.cpp | 79 - components/esm/aisequence.cpp | 236 - components/esm/aisequence.hpp | 174 - components/esm/attr.cpp | 53 +- components/esm/attr.hpp | 54 +- components/esm/cellid.cpp | 61 - components/esm/cellid.hpp | 34 - components/esm/cellstate.cpp | 27 - components/esm/common.cpp | 16 + components/esm/common.hpp | 46 + components/esm/containerstate.cpp | 15 - components/esm/containerstate.hpp | 29 - components/esm/controlsstate.cpp | 43 - components/esm/creaturestate.cpp | 31 - components/esm/creaturestats.cpp | 275 - components/esm/custommarkerstate.cpp | 26 - components/esm/custommarkerstate.hpp | 30 - components/esm/debugprofile.cpp | 61 - components/esm/debugprofile.hpp | 38 - components/esm/defs.hpp | 484 +- components/esm/dialoguestate.cpp | 53 - components/esm/doorstate.hpp | 28 - components/esm/effectlist.cpp | 30 - components/esm/esm3exteriorcellrefid.cpp | 40 + components/esm/esm3exteriorcellrefid.hpp | 62 + components/esm/esmbridge.cpp | 18 + components/esm/esmbridge.hpp | 91 + components/esm/esmcommon.hpp | 321 +- components/esm/esmreader.cpp | 367 - components/esm/esmreader.hpp | 290 - components/esm/esmterrain.cpp | 67 + components/esm/esmterrain.hpp | 46 + components/esm/esmwriter.cpp | 230 - components/esm/filter.cpp | 57 - components/esm/filter.hpp | 29 - components/esm/fogstate.cpp | 94 - components/esm/format.cpp | 42 + components/esm/format.hpp | 23 + components/esm/formid.cpp | 15 + components/esm/formid.hpp | 63 + components/esm/formidrefid.cpp | 38 + components/esm/formidrefid.hpp | 52 + components/esm/fourcc.hpp | 13 + components/esm/generatedrefid.cpp | 28 + components/esm/generatedrefid.hpp | 49 + components/esm/globalmap.cpp | 43 - components/esm/globalscript.cpp | 36 - components/esm/indexrefid.cpp | 36 + components/esm/indexrefid.hpp | 61 + components/esm/inventorystate.cpp | 172 - components/esm/journalentry.cpp | 38 - components/esm/loadacti.hpp | 28 - components/esm/loadalch.hpp | 44 - components/esm/loadappa.hpp | 48 - components/esm/loadarmo.hpp | 106 - components/esm/loadbody.hpp | 70 - components/esm/loadbook.hpp | 38 - components/esm/loadbsgn.hpp | 32 - components/esm/loadclas.cpp | 112 - components/esm/loadclas.hpp | 84 - components/esm/loadclot.hpp | 58 - components/esm/loadcont.hpp | 62 - components/esm/loadcrea.hpp | 103 - components/esm/loaddial.cpp | 147 - components/esm/loaddial.hpp | 70 - components/esm/loaddoor.cpp | 79 - components/esm/loaddoor.hpp | 27 - components/esm/loadench.hpp | 56 - components/esm/loadfact.hpp | 73 - components/esm/loadglob.cpp | 51 - components/esm/loadglob.hpp | 37 - components/esm/loadgmst.cpp | 34 - components/esm/loadgmst.hpp | 39 - components/esm/loadinfo.cpp | 158 - components/esm/loadinfo.hpp | 118 - components/esm/loadingr.hpp | 41 - components/esm/loadland.hpp | 185 - components/esm/loadlevlist.hpp | 93 - components/esm/loadligh.hpp | 57 - components/esm/loadlock.hpp | 38 - components/esm/loadltex.hpp | 36 - components/esm/loadmgef.cpp | 626 -- components/esm/loadmgef.hpp | 263 - components/esm/loadmisc.hpp | 42 - components/esm/loadnpc.hpp | 151 - components/esm/loadpgrd.hpp | 65 - components/esm/loadprob.hpp | 38 - components/esm/loadrace.hpp | 79 - components/esm/loadregn.hpp | 61 - components/esm/loadrepa.hpp | 38 - components/esm/loadscpt.hpp | 63 - components/esm/loadskil.cpp | 200 - components/esm/loadskil.hpp | 89 - components/esm/loadsndg.hpp | 45 - components/esm/loadsoun.hpp | 33 - components/esm/loadspel.hpp | 55 - components/esm/loadsscr.hpp | 37 - components/esm/loadstat.hpp | 38 - components/esm/loadtes3.cpp | 86 - components/esm/loadweap.hpp | 114 - components/esm/locals.cpp | 27 - components/esm/luascripts.cpp | 198 + components/esm/luascripts.hpp | 107 + components/esm/magiceffects.cpp | 29 - components/esm/magiceffects.hpp | 59 - components/esm/mappings.cpp | 134 - components/esm/mappings.hpp | 16 - components/esm/npcstate.cpp | 37 - components/esm/npcstats.cpp | 209 - components/esm/objectstate.cpp | 162 - components/esm/player.cpp | 86 - components/esm/player.hpp | 44 - components/esm/projectilestate.cpp | 68 - components/esm/queststate.cpp | 18 - components/esm/quickkeys.cpp | 42 - components/esm/quickkeys.hpp | 28 - components/esm/records.hpp | 103 +- components/esm/refid.cpp | 259 + components/esm/refid.hpp | 194 + components/esm/savedgame.cpp | 53 - components/esm/serializerefid.hpp | 111 + components/esm/spelllist.cpp | 28 - components/esm/spellstate.cpp | 126 - components/esm/spellstate.hpp | 54 - components/esm/stolenitems.cpp | 47 - components/esm/stringrefid.cpp | 132 + components/esm/stringrefid.hpp | 66 + components/esm/typetraits.hpp | 35 + components/esm/util.cpp | 23 + components/esm/util.hpp | 142 +- components/esm/variant.cpp | 273 - components/esm/variant.hpp | 97 - components/esm/variantimp.cpp | 164 - components/esm3/activespells.cpp | 123 + components/esm3/activespells.hpp | 73 + components/esm3/aipackage.cpp | 91 + components/{esm => esm3}/aipackage.hpp | 42 +- components/esm3/aisequence.cpp | 286 + components/esm3/aisequence.hpp | 173 + components/{esm => esm3}/animationstate.cpp | 2 +- components/{esm => esm3}/animationstate.hpp | 8 +- components/esm3/cellid.cpp | 109 + components/esm3/cellid.hpp | 36 + components/esm3/cellref.cpp | 284 + components/esm3/cellref.hpp | 111 + components/esm3/cellstate.cpp | 32 + components/{esm => esm3}/cellstate.hpp | 15 +- components/esm3/containerstate.cpp | 20 + components/esm3/containerstate.hpp | 23 + components/esm3/controlsstate.cpp | 55 + components/{esm => esm3}/controlsstate.hpp | 4 +- .../{esm => esm3}/creaturelevliststate.cpp | 12 +- .../{esm => esm3}/creaturelevliststate.hpp | 14 +- components/esm3/creaturestate.cpp | 36 + components/{esm => esm3}/creaturestate.hpp | 18 +- components/esm3/creaturestats.cpp | 307 + components/{esm => esm3}/creaturestats.hpp | 62 +- components/esm3/custommarkerstate.cpp | 26 + components/esm3/custommarkerstate.hpp | 32 + components/esm3/debugprofile.cpp | 64 + components/esm3/debugprofile.hpp | 44 + components/esm3/dialoguestate.cpp | 55 + components/{esm => esm3}/dialoguestate.hpp | 11 +- components/{esm => esm3}/doorstate.cpp | 12 +- components/esm3/doorstate.hpp | 22 + components/esm3/effectlist.cpp | 33 + components/{esm => esm3}/effectlist.hpp | 12 +- components/esm3/esmreader.cpp | 529 + components/esm3/esmreader.hpp | 347 + components/esm3/esmwriter.cpp | 355 + components/{esm => esm3}/esmwriter.hpp | 115 +- components/esm3/filter.cpp | 61 + components/esm3/filter.hpp | 35 + components/esm3/fogstate.cpp | 100 + components/{esm => esm3}/fogstate.hpp | 4 +- components/esm3/formatversion.hpp | 30 + components/esm3/globalmap.cpp | 45 + components/{esm => esm3}/globalmap.hpp | 10 +- components/esm3/globalscript.cpp | 41 + components/{esm => esm3}/globalscript.hpp | 11 +- components/esm3/infoorder.hpp | 114 + components/esm3/inventorystate.cpp | 178 + components/{esm => esm3}/inventorystate.hpp | 14 +- components/esm3/journalentry.cpp | 43 + components/{esm => esm3}/journalentry.hpp | 12 +- components/{esm => esm3}/loadacti.cpp | 33 +- components/esm3/loadacti.hpp | 33 + components/{esm => esm3}/loadalch.cpp | 41 +- components/esm3/loadalch.hpp | 47 + components/{esm => esm3}/loadappa.cpp | 37 +- components/esm3/loadappa.hpp | 54 + components/{esm => esm3}/loadarmo.cpp | 64 +- components/esm3/loadarmo.hpp | 112 + components/{esm => esm3}/loadbody.cpp | 40 +- components/esm3/loadbody.hpp | 78 + components/{esm => esm3}/loadbook.cpp | 49 +- components/esm3/loadbook.hpp | 43 + components/{esm => esm3}/loadbsgn.cpp | 29 +- components/esm3/loadbsgn.hpp | 37 + components/esm3/loadcell.cpp | 342 + components/esm3/loadcell.hpp | 199 + components/esm3/loadclas.cpp | 96 + components/esm3/loadclas.hpp | 62 + components/{esm => esm3}/loadclot.cpp | 49 +- components/esm3/loadclot.hpp | 63 + components/{esm => esm3}/loadcont.cpp | 52 +- components/esm3/loadcont.hpp | 66 + components/{esm => esm3}/loadcrea.cpp | 76 +- components/esm3/loadcrea.hpp | 101 + components/esm3/loaddial.cpp | 123 + components/esm3/loaddial.hpp | 71 + components/esm3/loaddoor.cpp | 78 + components/esm3/loaddoor.hpp | 33 + components/{esm => esm3}/loadench.cpp | 27 +- components/esm3/loadench.hpp | 60 + components/{esm => esm3}/loadfact.cpp | 60 +- components/esm3/loadfact.hpp | 80 + components/esm3/loadglob.cpp | 50 + components/esm3/loadglob.hpp | 42 + components/esm3/loadgmst.cpp | 33 + components/esm3/loadgmst.hpp | 44 + components/esm3/loadinfo.cpp | 157 + components/esm3/loadinfo.hpp | 120 + components/{esm => esm3}/loadingr.cpp | 57 +- components/esm3/loadingr.hpp | 47 + components/{esm => esm3}/loadland.cpp | 169 +- components/esm3/loadland.hpp | 189 + components/{esm => esm3}/loadlevlist.cpp | 36 +- components/esm3/loadlevlist.hpp | 98 + components/{esm => esm3}/loadligh.cpp | 47 +- components/esm3/loadligh.hpp | 64 + components/{esm => esm3}/loadlock.cpp | 39 +- components/esm3/loadlock.hpp | 44 + components/{esm => esm3}/loadltex.cpp | 26 +- components/esm3/loadltex.hpp | 41 + components/esm3/loadmgef.cpp | 646 ++ components/esm3/loadmgef.hpp | 282 + components/{esm => esm3}/loadmisc.cpp | 41 +- components/esm3/loadmisc.hpp | 52 + components/{esm => esm3}/loadnpc.cpp | 113 +- components/esm3/loadnpc.hpp | 149 + components/{esm => esm3}/loadpgrd.cpp | 76 +- components/esm3/loadpgrd.hpp | 74 + components/{esm => esm3}/loadprob.cpp | 39 +- components/esm3/loadprob.hpp | 44 + components/{esm => esm3}/loadrace.cpp | 47 +- components/esm3/loadrace.hpp | 85 + components/{esm => esm3}/loadregn.cpp | 70 +- components/esm3/loadregn.hpp | 61 + components/{esm => esm3}/loadrepa.cpp | 39 +- components/esm3/loadrepa.hpp | 44 + components/{esm => esm3}/loadscpt.cpp | 60 +- components/esm3/loadscpt.hpp | 66 + components/esm3/loadskil.cpp | 136 + components/esm3/loadskil.hpp | 110 + components/{esm => esm3}/loadsndg.cpp | 43 +- components/esm3/loadsndg.hpp | 50 + components/{esm => esm3}/loadsoun.cpp | 27 +- components/esm3/loadsoun.hpp | 39 + components/{esm => esm3}/loadspel.cpp | 31 +- components/esm3/loadspel.hpp | 60 + components/{esm => esm3}/loadsscr.cpp | 23 +- components/esm3/loadsscr.hpp | 42 + components/{esm => esm3}/loadstat.cpp | 25 +- components/esm3/loadstat.hpp | 45 + components/esm3/loadtes3.cpp | 86 + components/{esm => esm3}/loadtes3.hpp | 17 +- components/{esm => esm3}/loadweap.cpp | 48 +- components/esm3/loadweap.hpp | 134 + components/esm3/locals.cpp | 32 + components/{esm => esm3}/locals.hpp | 8 +- components/esm3/magiceffects.cpp | 35 + components/esm3/magiceffects.hpp | 55 + components/esm3/mappings.cpp | 134 + components/esm3/mappings.hpp | 16 + components/esm3/npcstate.cpp | 42 + components/{esm => esm3}/npcstate.hpp | 18 +- components/esm3/npcstats.cpp | 211 + components/{esm => esm3}/npcstats.hpp | 24 +- components/esm3/objectstate.cpp | 184 + components/{esm => esm3}/objectstate.hpp | 30 +- components/esm3/player.cpp | 121 + components/esm3/player.hpp | 44 + components/esm3/projectilestate.cpp | 73 + components/{esm => esm3}/projectilestate.hpp | 22 +- components/esm3/queststate.cpp | 23 + components/{esm => esm3}/queststate.hpp | 7 +- components/esm3/quickkeys.cpp | 36 + components/esm3/quickkeys.hpp | 39 + components/esm3/readerscache.cpp | 89 + components/esm3/readerscache.hpp | 83 + components/esm3/savedgame.cpp | 56 + components/{esm => esm3}/savedgame.hpp | 19 +- components/esm3/spelllist.cpp | 32 + components/{esm => esm3}/spelllist.hpp | 12 +- components/esm3/spellstate.cpp | 114 + components/esm3/spellstate.hpp | 56 + components/{esm => esm3}/statstate.cpp | 25 +- components/{esm => esm3}/statstate.hpp | 6 +- components/esm3/stolenitems.cpp | 45 + components/{esm => esm3}/stolenitems.hpp | 7 +- components/{esm => esm3}/transport.cpp | 14 +- components/{esm => esm3}/transport.hpp | 9 +- components/esm3/typetraits.hpp | 35 + components/esm3/variant.cpp | 280 + components/esm3/variant.hpp | 113 + components/esm3/variantimp.cpp | 168 + components/{esm => esm3}/variantimp.hpp | 14 +- components/{esm => esm3}/weatherstate.cpp | 147 +- components/{esm => esm3}/weatherstate.hpp | 73 +- .../{esmterrain => esm3terrain}/storage.cpp | 365 +- .../{esmterrain => esm3terrain}/storage.hpp | 92 +- components/esm4/actor.hpp | 163 + components/esm4/cellgrid.hpp | 40 + components/esm4/common.hpp | 702 ++ components/esm4/dialogue.hpp | 46 + components/esm4/effect.hpp | 56 + components/esm4/formid.cpp | 41 + components/esm4/formid.hpp | 37 + components/esm4/grid.hpp | 41 + components/esm4/grouptype.hpp | 49 + components/esm4/inventory.hpp | 55 + components/esm4/lighting.hpp | 81 + components/esm4/loadachr.cpp | 111 + components/esm4/loadachr.hpp | 67 + components/esm4/loadacre.cpp | 96 + components/esm4/loadacre.hpp | 64 + components/esm4/loadacti.cpp | 104 + components/esm4/loadacti.hpp | 71 + components/esm4/loadalch.cpp | 114 + components/esm4/loadalch.hpp | 89 + components/esm4/loadaloc.cpp | 164 + components/esm4/loadaloc.hpp | 85 + components/esm4/loadammo.cpp | 158 + components/esm4/loadammo.hpp | 94 + components/esm4/loadanio.cpp | 76 + components/esm4/loadanio.hpp | 60 + components/esm4/loadappa.cpp | 97 + components/esm4/loadappa.hpp | 72 + components/esm4/loadarma.cpp | 136 + components/esm4/loadarma.hpp | 67 + components/esm4/loadarmo.cpp | 215 + components/esm4/loadarmo.hpp | 197 + components/esm4/loadaspc.cpp | 86 + components/esm4/loadaspc.hpp | 63 + components/esm4/loadbook.cpp | 119 + components/esm4/loadbook.hpp | 115 + components/esm4/loadbptd.cpp | 113 + components/esm4/loadbptd.hpp | 127 + components/esm4/loadcell.cpp | 248 + components/esm4/loadcell.hpp | 119 + components/esm4/loadclas.cpp | 73 + components/esm4/loadclas.hpp | 63 + components/esm4/loadclfm.cpp | 72 + components/esm4/loadclfm.hpp | 67 + components/esm4/loadclot.cpp | 109 + components/esm4/loadclot.hpp | 84 + components/esm4/loadcont.cpp | 103 + components/esm4/loadcont.hpp | 72 + components/esm4/loadcrea.cpp | 204 + components/esm4/loadcrea.hpp | 148 + components/esm4/loaddial.cpp | 113 + components/esm4/loaddial.hpp | 66 + components/esm4/loaddobj.cpp | 106 + components/esm4/loaddobj.hpp | 97 + components/esm4/loaddoor.cpp | 97 + components/esm4/loaddoor.hpp | 77 + components/esm4/loadeyes.cpp | 69 + components/esm4/loadeyes.hpp | 65 + components/esm4/loadflor.cpp | 90 + components/esm4/loadflor.hpp | 81 + components/esm4/loadflst.cpp | 67 + components/esm4/loadflst.hpp | 57 + components/esm4/loadfurn.cpp | 94 + components/esm4/loadfurn.hpp | 65 + components/esm4/loadglob.cpp | 75 + components/esm4/loadglob.hpp | 57 + components/esm4/loadgmst.cpp | 75 + components/esm4/loadgmst.hpp | 27 + components/esm4/loadgras.cpp | 73 + components/esm4/loadgras.hpp | 95 + components/esm4/loadgrup.hpp | 152 + components/esm4/loadhair.cpp | 78 + components/esm4/loadhair.hpp | 68 + components/esm4/loadhdpt.cpp | 110 + components/esm4/loadhdpt.hpp | 64 + components/esm4/loadidle.cpp | 81 + components/esm4/loadidle.hpp | 62 + components/esm4/loadidlm.cpp | 94 + components/esm4/loadidlm.hpp | 61 + components/esm4/loadimod.cpp | 74 + components/esm4/loadimod.hpp | 56 + components/esm4/loadinfo.cpp | 211 + components/esm4/loadinfo.hpp | 86 + components/esm4/loadingr.cpp | 125 + components/esm4/loadingr.hpp | 84 + components/esm4/loadkeym.cpp | 94 + components/esm4/loadkeym.hpp | 74 + components/esm4/loadland.cpp | 241 + components/esm4/loadland.hpp | 139 + components/esm4/loadlgtm.cpp | 79 + components/esm4/loadlgtm.hpp | 59 + components/esm4/loadligh.cpp | 119 + components/esm4/loadligh.hpp | 113 + components/esm4/loadltex.cpp | 101 + components/esm4/loadltex.hpp | 72 + components/esm4/loadlvlc.cpp | 126 + components/esm4/loadlvlc.hpp | 68 + components/esm4/loadlvli.cpp | 134 + components/esm4/loadlvli.hpp | 70 + components/esm4/loadlvln.cpp | 110 + components/esm4/loadlvln.hpp | 67 + components/esm4/loadmato.cpp | 67 + components/esm4/loadmato.hpp | 55 + components/esm4/loadmisc.cpp | 95 + components/esm4/loadmisc.hpp | 78 + components/esm4/loadmset.cpp | 144 + components/esm4/loadmset.hpp | 96 + components/esm4/loadmstt.cpp | 81 + components/esm4/loadmstt.hpp | 57 + components/esm4/loadmusc.cpp | 73 + components/esm4/loadmusc.hpp | 57 + components/esm4/loadnavi.cpp | 358 + components/esm4/loadnavi.hpp | 115 + components/esm4/loadnavm.cpp | 261 + components/esm4/loadnavm.hpp | 110 + components/esm4/loadnote.cpp | 78 + components/esm4/loadnote.hpp | 57 + components/esm4/loadnpc.cpp | 327 + components/esm4/loadnpc.hpp | 227 + components/esm4/loadotft.cpp | 71 + components/esm4/loadotft.hpp | 57 + components/esm4/loadpack.cpp | 179 + components/esm4/loadpack.hpp | 108 + components/esm4/loadpgrd.cpp | 159 + components/esm4/loadpgrd.hpp | 93 + components/esm4/loadpgre.cpp | 90 + components/esm4/loadpgre.hpp | 56 + components/esm4/loadpwat.cpp | 67 + components/esm4/loadpwat.hpp | 56 + components/esm4/loadqust.cpp | 175 + components/esm4/loadqust.hpp | 83 + components/esm4/loadrace.cpp | 701 ++ components/esm4/loadrace.hpp | 171 + components/esm4/loadrefr.cpp | 342 + components/esm4/loadrefr.hpp | 123 + components/esm4/loadregn.cpp | 152 + components/esm4/loadregn.hpp | 95 + components/esm4/loadroad.cpp | 110 + components/esm4/loadroad.hpp | 86 + components/esm4/loadsbsp.cpp | 67 + components/esm4/loadsbsp.hpp | 61 + components/esm4/loadscol.cpp | 69 + components/esm4/loadscol.hpp | 56 + components/esm4/loadscpt.cpp | 155 + components/esm4/loadscpt.hpp | 57 + components/esm4/loadscrl.cpp | 87 + components/esm4/loadscrl.hpp | 65 + components/esm4/loadsgst.cpp | 110 + components/esm4/loadsgst.hpp | 73 + components/esm4/loadslgm.cpp | 91 + components/esm4/loadslgm.hpp | 73 + components/esm4/loadsndr.cpp | 90 + components/esm4/loadsndr.hpp | 83 + components/esm4/loadsoun.cpp | 83 + components/esm4/loadsoun.hpp | 98 + components/esm4/loadstat.cpp | 92 + components/esm4/loadstat.hpp | 66 + components/esm4/loadtact.cpp | 87 + components/esm4/loadtact.hpp | 73 + components/esm4/loadterm.cpp | 99 + components/esm4/loadterm.hpp | 63 + components/esm4/loadtes4.cpp | 113 + components/esm4/loadtes4.hpp | 71 + components/esm4/loadtree.cpp | 78 + components/esm4/loadtree.hpp | 61 + components/esm4/loadtxst.cpp | 89 + components/esm4/loadtxst.hpp | 63 + components/esm4/loadweap.cpp | 193 + components/esm4/loadweap.hpp | 107 + components/esm4/loadwrld.cpp | 205 + components/esm4/loadwrld.hpp | 139 + components/esm4/magiceffectid.hpp | 179 + components/esm4/reader.cpp | 938 ++ components/esm4/reader.hpp | 374 + components/esm4/readerutils.hpp | 86 + components/esm4/records.hpp | 85 + components/esm4/reference.hpp | 68 + components/esm4/script.hpp | 387 + components/esm4/typetraits.hpp | 139 + components/esm4/vertex.hpp | 40 + components/esmloader/esmdata.cpp | 81 + components/esmloader/esmdata.hpp | 54 + components/esmloader/lessbyid.hpp | 32 + components/esmloader/load.cpp | 363 + components/esmloader/load.hpp | 46 + components/esmloader/record.hpp | 45 + components/fallback/fallback.cpp | 104 +- components/fallback/fallback.hpp | 27 +- components/fallback/validate.cpp | 259 +- components/fallback/validate.hpp | 26 +- components/files/androidpath.cpp | 90 +- components/files/androidpath.hpp | 58 +- components/files/collections.cpp | 55 +- components/files/collections.hpp | 43 +- components/files/configfileparser.cpp | 291 + components/files/configfileparser.hpp | 17 + components/files/configurationmanager.cpp | 639 +- components/files/configurationmanager.hpp | 111 +- components/files/constrainedfilestream.cpp | 116 +- components/files/constrainedfilestream.hpp | 25 +- components/files/constrainedfilestreambuf.cpp | 88 + components/files/constrainedfilestreambuf.hpp | 31 + components/files/conversion.cpp | 29 + components/files/conversion.hpp | 19 + components/files/escape.cpp | 145 - components/files/escape.hpp | 191 - components/files/fixedpath.hpp | 152 +- components/files/hash.cpp | 43 + components/files/hash.hpp | 14 + components/files/istreamptr.hpp | 12 + components/files/linuxpath.cpp | 224 +- components/files/linuxpath.hpp | 68 +- components/files/lowlevelfile.cpp | 358 - components/files/lowlevelfile.hpp | 54 - components/files/macospath.cpp | 202 +- components/files/macospath.hpp | 82 +- components/files/memorystream.hpp | 2 +- components/files/multidircollection.cpp | 79 +- components/files/multidircollection.hpp | 90 +- components/files/openfile.cpp | 18 + components/files/openfile.hpp | 14 + components/files/qtconfigpath.hpp | 27 + components/files/qtconversion.cpp | 28 + components/files/qtconversion.hpp | 18 + components/files/streamwithbuffer.hpp | 24 + components/files/windowspath.cpp | 182 +- components/files/windowspath.hpp | 102 +- components/fontloader/fontloader.cpp | 643 +- components/fontloader/fontloader.hpp | 24 +- components/fx/lexer.cpp | 316 + components/fx/lexer.hpp | 76 + components/fx/lexer_types.hpp | 168 + components/fx/parse_constants.hpp | 132 + components/fx/pass.cpp | 365 + components/fx/pass.hpp | 83 + components/fx/stateupdater.cpp | 71 + components/fx/stateupdater.hpp | 279 + components/fx/technique.cpp | 1058 ++ components/fx/technique.hpp | 328 + components/fx/types.hpp | 308 + components/fx/widgets.cpp | 174 + components/fx/widgets.hpp | 304 + components/interpreter/context.hpp | 92 +- components/interpreter/controlopcodes.hpp | 72 +- components/interpreter/defines.cpp | 279 +- components/interpreter/defines.hpp | 11 +- components/interpreter/docs/vmformat.txt | 2 +- components/interpreter/genericopcodes.hpp | 117 +- components/interpreter/installopcodes.cpp | 139 +- components/interpreter/installopcodes.hpp | 4 +- components/interpreter/interpreter.cpp | 161 +- components/interpreter/interpreter.hpp | 79 +- components/interpreter/localopcodes.hpp | 424 +- components/interpreter/mathopcodes.hpp | 134 +- components/interpreter/miscopcodes.hpp | 233 +- components/interpreter/opcodes.hpp | 18 +- components/interpreter/program.hpp | 20 + components/interpreter/runtime.cpp | 91 +- components/interpreter/runtime.hpp | 61 +- components/interpreter/types.hpp | 40 +- components/l10n/manager.cpp | 103 + components/l10n/manager.hpp | 49 + components/l10n/messagebundles.cpp | 190 + components/l10n/messagebundles.hpp | 60 + .../loadinglistener/loadinglistener.hpp | 34 +- components/loadinglistener/reporter.cpp | 41 + components/loadinglistener/reporter.hpp | 32 + components/lua/asyncpackage.cpp | 93 + components/lua/asyncpackage.hpp | 57 + components/lua/configuration.cpp | 257 + components/lua/configuration.hpp | 54 + components/lua/l10n.cpp | 71 + components/lua/l10n.hpp | 16 + components/lua/luastate.cpp | 444 + components/lua/luastate.hpp | 283 + components/lua/scriptscontainer.cpp | 653 ++ components/lua/scriptscontainer.hpp | 274 + components/lua/serialization.cpp | 384 + components/lua/serialization.hpp | 59 + components/lua/shapes/box.cpp | 45 + components/lua/shapes/box.hpp | 31 + components/lua/storage.cpp | 248 + components/lua/storage.hpp | 109 + components/lua/utilpackage.cpp | 333 + components/lua/utilpackage.hpp | 46 + components/lua_ui/adapter.cpp | 57 + components/lua_ui/adapter.hpp | 31 + components/lua_ui/alignment.cpp | 18 + components/lua_ui/alignment.hpp | 18 + components/lua_ui/container.cpp | 53 + components/lua_ui/container.hpp | 27 + components/lua_ui/content.cpp | 22 + components/lua_ui/content.hpp | 110 + components/lua_ui/content.lua | 140 + components/lua_ui/element.cpp | 256 + components/lua_ui/element.hpp | 45 + components/lua_ui/flex.cpp | 107 + components/lua_ui/flex.hpp | 54 + components/lua_ui/image.cpp | 71 + components/lua_ui/image.hpp | 35 + components/lua_ui/layers.cpp | 28 + components/lua_ui/layers.hpp | 66 + components/lua_ui/properties.hpp | 87 + components/lua_ui/registerscriptsettings.hpp | 13 + components/lua_ui/resources.cpp | 20 + components/lua_ui/resources.hpp | 40 + components/lua_ui/scriptsettings.cpp | 58 + components/lua_ui/scriptsettings.hpp | 23 + components/lua_ui/text.cpp | 56 + components/lua_ui/text.hpp | 28 + components/lua_ui/textedit.cpp | 76 + components/lua_ui/textedit.hpp | 31 + components/lua_ui/util.cpp | 53 + components/lua_ui/util.hpp | 16 + components/lua_ui/widget.cpp | 411 + components/lua_ui/widget.hpp | 184 + components/lua_ui/window.cpp | 86 + components/lua_ui/window.hpp | 34 + components/misc/algorithm.hpp | 32 +- components/misc/barrier.hpp | 62 +- components/misc/budgetmeasurement.hpp | 57 +- components/misc/color.cpp | 61 + components/misc/color.hpp | 34 + components/misc/compression.cpp | 43 + components/misc/compression.hpp | 14 + components/misc/constants.hpp | 54 +- components/misc/convert.hpp | 51 +- components/misc/coordinateconverter.hpp | 114 +- components/misc/endianness.hpp | 6 +- components/misc/frameratelimiter.hpp | 56 +- components/misc/guarded.hpp | 122 +- components/misc/hash.hpp | 20 +- components/misc/helpviewer.cpp | 4 +- components/misc/helpviewer.hpp | 6 +- components/misc/math.hpp | 16 + components/misc/mathutil.hpp | 60 +- components/misc/messageformatparser.cpp | 2 +- components/misc/messageformatparser.hpp | 40 +- components/misc/notnullptr.hpp | 33 + components/misc/objectpool.hpp | 80 +- components/misc/osguservalues.cpp | 8 + components/misc/osguservalues.hpp | 16 + components/misc/pathhelpers.hpp | 46 + components/misc/progressreporter.hpp | 52 + components/misc/resourcehelpers.cpp | 170 +- components/misc/resourcehelpers.hpp | 36 +- components/misc/rng.cpp | 125 +- components/misc/rng.hpp | 66 +- components/misc/stringops.hpp | 305 - components/misc/strings/algorithm.hpp | 191 + components/misc/strings/conversion.hpp | 150 + components/misc/strings/format.hpp | 79 + components/misc/strings/lower.hpp | 62 + components/misc/strongtypedef.hpp | 44 + components/misc/thread.cpp | 16 +- components/misc/thread.hpp | 2 - components/misc/timeconvert.hpp | 54 + components/misc/timer.hpp | 39 +- components/misc/tuplehelpers.hpp | 15 + components/misc/tuplemeta.hpp | 35 + components/misc/typetraits.hpp | 23 + components/misc/utf8qtextstream.hpp | 22 + components/misc/utf8stream.hpp | 154 +- components/misc/weakcache.hpp | 24 +- components/myguiplatform/additivelayer.cpp | 2 +- components/myguiplatform/additivelayer.hpp | 2 +- components/myguiplatform/myguicompat.h | 12 - components/myguiplatform/myguidatamanager.cpp | 109 +- components/myguiplatform/myguidatamanager.hpp | 69 +- components/myguiplatform/myguiloglistener.cpp | 93 +- components/myguiplatform/myguiloglistener.hpp | 140 +- components/myguiplatform/myguiplatform.cpp | 75 +- components/myguiplatform/myguiplatform.hpp | 23 +- .../myguiplatform/myguirendermanager.cpp | 1052 +- .../myguiplatform/myguirendermanager.hpp | 145 +- components/myguiplatform/myguitexture.cpp | 48 +- components/myguiplatform/myguitexture.hpp | 46 +- components/myguiplatform/scalinglayer.cpp | 51 +- components/myguiplatform/scalinglayer.hpp | 9 +- components/navmeshtool/protocol.cpp | 177 + components/navmeshtool/protocol.hpp | 68 + components/nif/base.cpp | 32 + components/nif/base.hpp | 99 +- components/nif/controlled.cpp | 65 +- components/nif/controlled.hpp | 209 +- components/nif/controller.cpp | 481 +- components/nif/controller.hpp | 509 +- components/nif/data.cpp | 888 +- components/nif/data.hpp | 470 +- components/nif/effect.cpp | 110 +- components/nif/effect.hpp | 120 +- components/nif/exception.hpp | 21 + components/nif/extra.cpp | 215 +- components/nif/extra.hpp | 165 +- components/nif/niffile.cpp | 668 +- components/nif/niffile.hpp | 252 +- components/nif/nifkey.hpp | 275 +- components/nif/nifstream.cpp | 26 +- components/nif/nifstream.hpp | 446 +- components/nif/niftypes.hpp | 86 +- components/nif/node.cpp | 337 + components/nif/node.hpp | 634 +- components/nif/parent.hpp | 15 + components/nif/physics.cpp | 443 + components/nif/physics.hpp | 430 + components/nif/property.cpp | 431 +- components/nif/property.hpp | 728 +- components/nif/record.hpp | 258 +- components/nif/recordptr.hpp | 337 +- components/nifbullet/bulletnifloader.cpp | 643 +- components/nifbullet/bulletnifloader.hpp | 61 +- components/nifosg/controller.cpp | 1148 +- components/nifosg/controller.hpp | 173 +- components/nifosg/matrixtransform.cpp | 65 +- components/nifosg/matrixtransform.hpp | 16 +- components/nifosg/nifloader.cpp | 2145 ++-- components/nifosg/nifloader.hpp | 18 +- components/nifosg/particle.cpp | 1027 +- components/nifosg/particle.hpp | 71 +- components/platform/file.hpp | 68 + components/platform/file.posix.cpp | 106 + components/platform/file.stdio.cpp | 96 + components/platform/file.win32.cpp | 103 + components/platform/platform.cpp | 23 + components/platform/platform.hpp | 11 + components/process/processinvoker.cpp | 126 +- components/process/processinvoker.hpp | 31 +- components/resource/animation.cpp | 13 +- components/resource/animation.hpp | 33 +- components/resource/bulletshape.cpp | 136 +- components/resource/bulletshape.hpp | 77 +- components/resource/bulletshapemanager.cpp | 363 +- components/resource/bulletshapemanager.hpp | 11 +- components/resource/errormarker.cpp | 1400 +++ components/resource/errormarker.hpp | 14 + components/resource/foreachbulletobject.cpp | 165 + components/resource/foreachbulletobject.hpp | 47 + components/resource/imagemanager.cpp | 81 +- components/resource/imagemanager.hpp | 9 +- components/resource/keyframemanager.cpp | 250 +- components/resource/keyframemanager.hpp | 35 +- components/resource/multiobjectcache.cpp | 24 +- components/resource/multiobjectcache.hpp | 12 +- components/resource/niffilemanager.cpp | 22 +- components/resource/niffilemanager.hpp | 4 +- components/resource/objectcache.hpp | 84 +- components/resource/resourcemanager.hpp | 16 +- components/resource/resourcesystem.cpp | 40 +- components/resource/resourcesystem.hpp | 5 +- components/resource/scenemanager.cpp | 724 +- components/resource/scenemanager.hpp | 134 +- components/resource/stats.cpp | 869 +- components/resource/stats.hpp | 28 +- components/sceneutil/actorutil.cpp | 2 +- components/sceneutil/actorutil.hpp | 2 +- components/sceneutil/agentpath.cpp | 26 +- components/sceneutil/agentpath.hpp | 7 +- components/sceneutil/attach.cpp | 118 +- components/sceneutil/attach.hpp | 17 +- components/sceneutil/clearcolor.hpp | 53 + components/sceneutil/clone.cpp | 38 +- components/sceneutil/clone.hpp | 10 +- components/sceneutil/color.cpp | 152 + components/sceneutil/color.hpp | 208 + components/sceneutil/controller.cpp | 41 +- components/sceneutil/controller.hpp | 22 +- .../sceneutil/cullsafeboundsvisitor.hpp | 64 + components/sceneutil/depth.cpp | 176 + components/sceneutil/depth.hpp | 193 + components/sceneutil/detourdebugdraw.cpp | 46 +- components/sceneutil/detourdebugdraw.hpp | 11 +- components/sceneutil/extradata.cpp | 67 + components/sceneutil/extradata.hpp | 36 + components/sceneutil/keyframe.hpp | 26 +- components/sceneutil/lightcommon.cpp | 33 + components/sceneutil/lightcommon.hpp | 35 + components/sceneutil/lightcontroller.cpp | 12 +- components/sceneutil/lightcontroller.hpp | 12 +- components/sceneutil/lightmanager.cpp | 1101 +- components/sceneutil/lightmanager.hpp | 228 +- components/sceneutil/lightutil.cpp | 100 +- components/sceneutil/lightutil.hpp | 14 +- components/sceneutil/morphgeometry.cpp | 350 +- components/sceneutil/morphgeometry.hpp | 24 +- components/sceneutil/mwshadowtechnique.cpp | 538 +- components/sceneutil/mwshadowtechnique.hpp | 67 +- components/sceneutil/navmesh.cpp | 297 +- components/sceneutil/navmesh.hpp | 16 +- components/sceneutil/nodecallback.hpp | 42 + components/sceneutil/optimizer.cpp | 181 +- components/sceneutil/optimizer.hpp | 33 +- components/sceneutil/osgacontroller.cpp | 91 +- components/sceneutil/osgacontroller.hpp | 56 +- components/sceneutil/pathgridutil.cpp | 98 +- components/sceneutil/pathgridutil.hpp | 6 +- .../sceneutil/positionattitudetransform.cpp | 69 +- .../sceneutil/positionattitudetransform.hpp | 51 +- components/sceneutil/recastmesh.cpp | 55 +- components/sceneutil/recastmesh.hpp | 6 +- components/sceneutil/riggeometry.cpp | 626 +- components/sceneutil/riggeometry.hpp | 23 +- .../sceneutil/riggeometryosgaextension.cpp | 308 + .../sceneutil/riggeometryosgaextension.hpp | 73 + components/sceneutil/rtt.cpp | 264 + components/sceneutil/rtt.hpp | 115 + components/sceneutil/screencapture.cpp | 165 + components/sceneutil/screencapture.hpp | 59 + components/sceneutil/serialize.cpp | 305 +- components/sceneutil/shadow.cpp | 96 +- components/sceneutil/shadow.hpp | 25 +- components/sceneutil/shadowsbin.cpp | 314 +- components/sceneutil/shadowsbin.hpp | 44 +- components/sceneutil/skeleton.cpp | 322 +- components/sceneutil/skeleton.hpp | 12 +- components/sceneutil/statesetupdater.cpp | 97 +- components/sceneutil/statesetupdater.hpp | 54 +- components/sceneutil/textkeymap.hpp | 56 +- components/sceneutil/unrefqueue.cpp | 45 +- components/sceneutil/unrefqueue.hpp | 32 +- components/sceneutil/util.cpp | 487 +- components/sceneutil/util.hpp | 79 +- components/sceneutil/visitor.cpp | 60 +- components/sceneutil/visitor.hpp | 39 +- components/sceneutil/waterutil.cpp | 39 +- components/sceneutil/workqueue.cpp | 213 +- components/sceneutil/workqueue.hpp | 20 +- components/sceneutil/writescene.cpp | 7 +- components/sceneutil/writescene.hpp | 4 +- components/sdlutil/events.hpp | 132 +- components/sdlutil/gl4es_init.cpp | 41 +- components/sdlutil/gl4es_init.h | 6 +- components/sdlutil/imagetosurface.cpp | 36 +- components/sdlutil/imagetosurface.hpp | 4 +- components/sdlutil/sdlcursormanager.cpp | 279 +- components/sdlutil/sdlcursormanager.hpp | 14 +- components/sdlutil/sdlgraphicswindow.cpp | 502 +- components/sdlutil/sdlgraphicswindow.hpp | 161 +- components/sdlutil/sdlinputwrapper.cpp | 178 +- components/sdlutil/sdlinputwrapper.hpp | 12 +- components/sdlutil/sdlmappings.cpp | 251 + .../sdlutil}/sdlmappings.hpp | 12 +- components/sdlutil/sdlvideowrapper.cpp | 47 +- components/sdlutil/sdlvideowrapper.hpp | 11 +- components/serialization/binaryreader.hpp | 83 + components/serialization/binarywriter.hpp | 90 + components/serialization/format.hpp | 75 + components/serialization/osgyaml.hpp | 64 + components/serialization/sizeaccumulator.hpp | 41 + components/settings/categories.hpp | 16 +- components/settings/categories/camera.hpp | 33 + components/settings/categories/cells.hpp | 39 + components/settings/categories/fog.hpp | 37 + components/settings/categories/game.hpp | 80 + components/settings/categories/general.hpp | 38 + .../settings/categories/groundcover.hpp | 30 + components/settings/categories/gui.hpp | 41 + components/settings/categories/hud.hpp | 25 + components/settings/categories/input.hpp | 45 + components/settings/categories/lua.hpp | 33 + components/settings/categories/map.hpp | 33 + components/settings/categories/models.hpp | 49 + components/settings/categories/navigator.hpp | 69 + components/settings/categories/physics.hpp | 27 + .../settings/categories/postprocessing.hpp | 29 + components/settings/categories/saves.hpp | 28 + components/settings/categories/shaders.hpp | 51 + components/settings/categories/shadows.hpp | 49 + components/settings/categories/sound.hpp | 34 + components/settings/categories/stereo.hpp | 30 + components/settings/categories/stereoview.hpp | 64 + components/settings/categories/terrain.hpp | 43 + components/settings/categories/video.hpp | 36 + components/settings/categories/water.hpp | 33 + components/settings/categories/windows.hpp | 167 + components/settings/parser.cpp | 123 +- components/settings/parser.hpp | 10 +- components/settings/sanitizer.hpp | 15 + components/settings/sanitizerimpl.cpp | 210 + components/settings/sanitizerimpl.hpp | 43 + components/settings/settings.cpp | 484 +- components/settings/settings.hpp | 166 +- components/settings/settingvalue.hpp | 356 + components/settings/shadermanager.hpp | 170 + components/settings/values.cpp | 27 + components/settings/values.hpp | 231 + components/shader/removedalphafunc.cpp | 8 +- components/shader/removedalphafunc.hpp | 13 +- components/shader/shadermanager.cpp | 598 +- components/shader/shadermanager.hpp | 114 +- components/shader/shadervisitor.cpp | 571 +- components/shader/shadervisitor.hpp | 29 +- components/sqlite3/db.cpp | 34 + components/sqlite3/db.hpp | 21 + components/sqlite3/request.hpp | 291 + components/sqlite3/statement.cpp | 25 + components/sqlite3/statement.hpp | 37 + components/sqlite3/transaction.cpp | 58 + components/sqlite3/transaction.hpp | 35 + components/sqlite3/types.hpp | 13 + components/std140/ubo.hpp | 159 + components/stereo/frustum.cpp | 142 + components/stereo/frustum.hpp | 71 + components/stereo/multiview.cpp | 847 ++ components/stereo/multiview.hpp | 159 + components/stereo/stereomanager.cpp | 453 + components/stereo/stereomanager.hpp | 166 + components/stereo/types.cpp | 163 + components/stereo/types.hpp | 61 + components/terrain/buffercache.cpp | 337 +- components/terrain/buffercache.hpp | 8 +- components/terrain/cellborder.cpp | 162 +- components/terrain/cellborder.hpp | 21 +- components/terrain/chunkmanager.cpp | 460 +- components/terrain/chunkmanager.hpp | 16 +- components/terrain/compositemaprenderer.cpp | 323 +- components/terrain/compositemaprenderer.hpp | 23 +- components/terrain/heightcull.hpp | 44 + components/terrain/material.cpp | 142 +- components/terrain/material.hpp | 12 +- components/terrain/quadtreenode.cpp | 251 +- components/terrain/quadtreenode.hpp | 8 +- components/terrain/quadtreeworld.cpp | 1051 +- components/terrain/quadtreeworld.hpp | 59 +- components/terrain/storage.hpp | 37 +- components/terrain/terraindrawable.cpp | 265 +- components/terrain/terraindrawable.hpp | 18 +- components/terrain/terraingrid.cpp | 201 +- components/terrain/terraingrid.hpp | 20 +- components/terrain/texturemanager.cpp | 80 +- components/terrain/texturemanager.hpp | 1 - components/terrain/view.hpp | 23 + components/terrain/viewdata.cpp | 421 +- components/terrain/viewdata.hpp | 83 +- components/terrain/world.cpp | 248 +- components/terrain/world.hpp | 96 +- components/to_utf8/gen_iconv.cpp | 145 +- components/to_utf8/tables_gen.hpp | 1256 +-- components/to_utf8/tests/.gitignore | 1 - .../to_utf8/tests/output/to_utf8_test.out | 4 - components/to_utf8/tests/test.sh | 18 - components/to_utf8/tests/to_utf8_test.cpp | 59 - components/to_utf8/to_utf8.cpp | 310 +- components/to_utf8/to_utf8.hpp | 86 +- components/translation/translation.cpp | 59 +- components/translation/translation.hpp | 20 +- components/version/version.cpp | 56 +- components/version/version.hpp | 9 +- components/vfs/archive.hpp | 15 +- components/vfs/bsaarchive.cpp | 73 - components/vfs/bsaarchive.hpp | 93 +- components/vfs/filesystemarchive.cpp | 59 +- components/vfs/filesystemarchive.hpp | 21 +- components/vfs/manager.cpp | 114 +- components/vfs/manager.hpp | 91 +- components/vfs/pathutil.hpp | 56 + components/vfs/registerarchives.cpp | 38 +- components/vfs/registerarchives.hpp | 2 +- components/widgets/box.cpp | 191 +- components/widgets/box.hpp | 50 +- components/widgets/fontwrapper.hpp | 26 +- components/widgets/imagebutton.cpp | 25 +- components/widgets/imagebutton.hpp | 3 +- components/widgets/list.cpp | 72 +- components/widgets/list.hpp | 11 +- components/widgets/numericeditbox.cpp | 24 +- components/widgets/numericeditbox.hpp | 8 +- components/widgets/sharedstatebutton.cpp | 8 +- components/widgets/sharedstatebutton.hpp | 9 +- components/widgets/tags.cpp | 89 +- components/widgets/tags.hpp | 7 +- components/widgets/widgets.cpp | 5 +- components/widgets/windowcaption.cpp | 4 +- components/widgets/windowcaption.hpp | 2 +- components/windows.hpp | 9 + docker/Dockerfile.ubuntu | 23 + docker/README.md | 17 + docker/build.sh | 13 + docs/Dockerfile | 11 + docs/README.md | 73 + docs/build_docs.sh | 6 + docs/prepare_docker_image.sh | 5 + docs/requirements.txt | 5 + docs/source/.gitattributes | 1 + docs/source/_static/luadoc.css | 113 + docs/source/conf.py | 7 +- docs/source/generate_luadoc.sh | 33 + .../source/install_luadocumentor_in_docker.sh | 35 + docs/source/luadoc_data_paths.sh | 10 + .../installation/install-game-files.rst | 20 +- .../manuals/installation/install-openmw.rst | 7 + docs/source/manuals/openmw-cs/cell-view.rst | 37 + docs/source/manuals/openmw-cs/index.rst | 7 + .../source/manuals/openmw-cs/record-types.rst | 154 +- .../openmw-cs/records-drag-and-drop.rst | 78 + .../manuals/openmw-cs/tables-assets.rst | 108 + .../manuals/openmw-cs/tables-characters.rst | 302 + docs/source/manuals/openmw-cs/tables-file.rst | 37 + .../manuals/openmw-cs/tables-mechanics.rst | 177 + .../source/manuals/openmw-cs/tables-world.rst | 288 + docs/source/manuals/openmw-cs/tables.rst | 99 - docs/source/reference/documentationHowTo.rst | 6 +- docs/source/reference/index.rst | 4 +- .../reference/lua-scripting/aipackages.rst | 177 + docs/source/reference/lua-scripting/api.rst | 91 + .../lua-scripting/engine_handlers.rst | 121 + .../source/reference/lua-scripting/events.rst | 13 + docs/source/reference/lua-scripting/index.rst | 16 + .../lua-scripting/interface_activation.rst | 6 + .../reference/lua-scripting/interface_ai.rst | 6 + .../lua-scripting/interface_camera.rst | 6 + .../lua-scripting/interface_controls.rst | 6 + .../lua-scripting/interface_mwui.rst | 6 + .../lua-scripting/interface_settings.rst | 6 + .../reference/lua-scripting/iterables.rst | 50 + .../reference/lua-scripting/openmw_async.rst | 5 + .../lua-scripting/openmw_aux_calendar.rst | 5 + .../lua-scripting/openmw_aux_time.rst | 5 + .../reference/lua-scripting/openmw_aux_ui.rst | 5 + .../lua-scripting/openmw_aux_util.rst | 5 + .../reference/lua-scripting/openmw_camera.rst | 5 + .../reference/lua-scripting/openmw_core.rst | 5 + .../reference/lua-scripting/openmw_debug.rst | 5 + .../reference/lua-scripting/openmw_input.rst | 6 + .../reference/lua-scripting/openmw_nearby.rst | 5 + .../lua-scripting/openmw_postprocessing.rst | 5 + .../reference/lua-scripting/openmw_self.rst | 5 + .../lua-scripting/openmw_storage.rst | 5 + .../reference/lua-scripting/openmw_types.rst | 5 + .../reference/lua-scripting/openmw_ui.rst | 5 + .../reference/lua-scripting/openmw_util.rst | 5 + .../reference/lua-scripting/openmw_world.rst | 5 + .../reference/lua-scripting/overview.rst | 709 ++ .../lua-scripting/setting_renderers.rst | 126 + .../lua-scripting/tables/aux_packages.rst | 11 + .../lua-scripting/tables/packages.rst | 33 + docs/source/reference/lua-scripting/teal.rst | 45 + .../lua-scripting/user_interface.rst | 133 + .../lua-scripting/widgets/container.rst | 8 + .../reference/lua-scripting/widgets/flex.rst | 47 + .../reference/lua-scripting/widgets/image.rst | 25 + .../reference/lua-scripting/widgets/text.rst | 44 + .../lua-scripting/widgets/textedit.rst | 55 + .../lua-scripting/widgets/widget.rst | 109 + .../reference/modding/custom-models/index.rst | 3 +- ...line-blender-collada-animated-creature.rst | 225 + ...pipeline-blender-collada-static-models.rst | 222 + .../pipeline-blender-collada.rst | 127 - .../modding/custom-shader-effects.rst | 53 + .../reference/modding/doors-and-teleports.rst | 54 + docs/source/reference/modding/extended.rst | 10 + docs/source/reference/modding/font.rst | 54 +- docs/source/reference/modding/index.rst | 5 + .../source/reference/modding/localisation.rst | 109 + docs/source/reference/modding/music.rst | 26 + docs/source/reference/modding/paths.rst | 2 +- .../source/reference/modding/settings/GUI.rst | 20 +- .../reference/modding/settings/camera.rst | 141 +- .../reference/modding/settings/cells.rst | 5 +- .../source/reference/modding/settings/fog.rst | 50 + .../reference/modding/settings/game.rst | 112 +- .../reference/modding/settings/general.rst | 69 +- .../modding/settings/groundcover.rst | 16 +- .../reference/modding/settings/index.rst | 4 + .../reference/modding/settings/input.rst | 21 +- .../source/reference/modding/settings/lua.rst | 100 + .../source/reference/modding/settings/map.rst | 30 +- .../reference/modding/settings/models.rst | 56 +- .../reference/modding/settings/navigator.rst | 83 +- .../reference/modding/settings/physics.rst | 24 +- .../modding/settings/postprocessing.rst | 53 + .../reference/modding/settings/shaders.rst | 61 +- .../reference/modding/settings/stereo.rst | 64 + .../reference/modding/settings/stereoview.rst | 218 + .../reference/modding/settings/terrain.rst | 40 +- .../reference/modding/settings/video.rst | 46 +- .../reference/modding/settings/water.rst | 15 + .../reference/modding/settings/windows.rst | 88 +- docs/source/reference/modding/sky-system.rst | 87 +- .../reference/modding/sound-effects.rst | 373 + .../convert-bump-mapped-mods.rst | 12 +- .../source/reference/postprocessing/index.rst | 12 + docs/source/reference/postprocessing/lua.rst | 139 + .../source/reference/postprocessing/omwfx.rst | 900 ++ .../reference/postprocessing/overview.rst | 52 + docs/tlconfig.lua | 6 + extern/Base64/CMakeLists.txt | 6 +- extern/CMakeLists.txt | 152 +- extern/icufilters.json | 19 + extern/oics/CMakeLists.txt | 4 + extern/oics/ICSChannel.cpp | 530 +- extern/oics/ICSChannel.h | 252 +- extern/oics/ICSChannelListener.h | 96 +- extern/oics/ICSControl.cpp | 322 +- extern/oics/ICSControl.h | 214 +- extern/oics/ICSControlListener.h | 96 +- extern/oics/ICSInputControlSystem.cpp | 1634 +-- extern/oics/ICSInputControlSystem.h | 562 +- .../oics/ICSInputControlSystem_joystick.cpp | 620 +- .../oics/ICSInputControlSystem_keyboard.cpp | 314 +- extern/oics/ICSInputControlSystem_mouse.cpp | 1132 +- extern/oics/ICSPrerequisites.cpp | 54 +- extern/oics/ICSPrerequisites.h | 220 +- extern/osg-ffmpeg-videoplayer/CMakeLists.txt | 12 +- .../osg-ffmpeg-videoplayer/audiofactory.hpp | 2 +- extern/osg-ffmpeg-videoplayer/videoplayer.cpp | 4 +- extern/osg-ffmpeg-videoplayer/videoplayer.hpp | 2 +- extern/osg-ffmpeg-videoplayer/videostate.cpp | 113 +- extern/osg-ffmpeg-videoplayer/videostate.hpp | 30 +- extern/osgQt/CMakeLists.txt | 17 +- extern/osgQt/CompositeOsgRenderer.cpp | 119 + extern/osgQt/CompositeOsgRenderer.hpp | 46 + extern/osgQt/GraphicsWindowQt | 155 - extern/osgQt/GraphicsWindowQt.cpp | 615 -- extern/osgQt/osgQOpenGLWidget.cpp | 96 + extern/osgQt/osgQOpenGLWidget.hpp | 68 + extern/smhasher/CMakeLists.txt | 2 + extern/smhasher/MurmurHash3.cpp | 152 + extern/smhasher/MurmurHash3.h | 33 + extern/sol3/README.md | 3 + extern/sol3/sol/as_args.hpp | 54 + extern/sol3/sol/as_returns.hpp | 62 + extern/sol3/sol/assert.hpp | 99 + extern/sol3/sol/base_traits.hpp | 123 + extern/sol3/sol/bind_traits.hpp | 553 + extern/sol3/sol/bytecode.hpp | 121 + extern/sol3/sol/call.hpp | 961 ++ extern/sol3/sol/compatibility.hpp | 51 + extern/sol3/sol/compatibility/compat-5.3.c.h | 900 ++ extern/sol3/sol/compatibility/compat-5.3.h | 424 + extern/sol3/sol/compatibility/compat-5.4.h | 25 + extern/sol3/sol/compatibility/lua_version.hpp | 218 + extern/sol3/sol/coroutine.hpp | 251 + extern/sol3/sol/debug.hpp | 52 + extern/sol3/sol/demangle.hpp | 192 + extern/sol3/sol/deprecate.hpp | 44 + extern/sol3/sol/detail/build_version.hpp | 232 + extern/sol3/sol/dump_handler.hpp | 77 + extern/sol3/sol/ebco.hpp | 160 + extern/sol3/sol/environment.hpp | 261 + extern/sol3/sol/epilogue.hpp | 39 + extern/sol3/sol/error.hpp | 89 + extern/sol3/sol/error_handler.hpp | 175 + extern/sol3/sol/forward.hpp | 266 + extern/sol3/sol/forward_detail.hpp | 56 + extern/sol3/sol/function.hpp | 142 + extern/sol3/sol/function_result.hpp | 78 + extern/sol3/sol/function_types.hpp | 769 ++ extern/sol3/sol/function_types_core.hpp | 46 + extern/sol3/sol/function_types_overloaded.hpp | 65 + extern/sol3/sol/function_types_stateful.hpp | 158 + extern/sol3/sol/function_types_stateless.hpp | 385 + extern/sol3/sol/function_types_templated.hpp | 154 + extern/sol3/sol/in_place.hpp | 48 + extern/sol3/sol/inheritance.hpp | 195 + extern/sol3/sol/load_result.hpp | 150 + extern/sol3/sol/lua_table.hpp | 95 + extern/sol3/sol/lua_value.hpp | 162 + extern/sol3/sol/make_reference.hpp | 74 + extern/sol3/sol/metatable.hpp | 203 + extern/sol3/sol/object.hpp | 150 + extern/sol3/sol/object_base.hpp | 86 + extern/sol3/sol/optional.hpp | 83 + extern/sol3/sol/optional_implementation.hpp | 2303 ++++ extern/sol3/sol/overload.hpp | 49 + extern/sol3/sol/packaged_coroutine.hpp | 262 + extern/sol3/sol/pairs_iterator.hpp | 275 + extern/sol3/sol/pointer_like.hpp | 102 + extern/sol3/sol/policies.hpp | 98 + extern/sol3/sol/prologue.hpp | 47 + extern/sol3/sol/property.hpp | 151 + extern/sol3/sol/protect.hpp | 53 + extern/sol3/sol/protected_function.hpp | 385 + extern/sol3/sol/protected_function_result.hpp | 233 + extern/sol3/sol/protected_handler.hpp | 108 + extern/sol3/sol/proxy_base.hpp | 68 + extern/sol3/sol/raii.hpp | 162 + extern/sol3/sol/reference.hpp | 900 ++ extern/sol3/sol/resolve.hpp | 173 + extern/sol3/sol/sol.hpp | 79 + extern/sol3/sol/stack.hpp | 351 + extern/sol3/sol/stack/detail/pairs.hpp | 98 + extern/sol3/sol/stack_check.hpp | 30 + extern/sol3/sol/stack_check_get.hpp | 30 + extern/sol3/sol/stack_check_get_qualified.hpp | 139 + .../sol3/sol/stack_check_get_unqualified.hpp | 186 + extern/sol3/sol/stack_check_qualified.hpp | 89 + extern/sol3/sol/stack_check_unqualified.hpp | 639 ++ extern/sol3/sol/stack_core.hpp | 1456 +++ extern/sol3/sol/stack_field.hpp | 279 + extern/sol3/sol/stack_get.hpp | 30 + extern/sol3/sol/stack_get_qualified.hpp | 37 + extern/sol3/sol/stack_get_unqualified.hpp | 1064 ++ extern/sol3/sol/stack_guard.hpp | 67 + extern/sol3/sol/stack_iterator.hpp | 153 + extern/sol3/sol/stack_pop.hpp | 51 + extern/sol3/sol/stack_probe.hpp | 94 + extern/sol3/sol/stack_proxy.hpp | 64 + extern/sol3/sol/stack_proxy_base.hpp | 96 + extern/sol3/sol/stack_push.hpp | 1382 +++ extern/sol3/sol/stack_reference.hpp | 314 + extern/sol3/sol/state.hpp | 62 + extern/sol3/sol/state_handling.hpp | 194 + extern/sol3/sol/state_view.hpp | 874 ++ extern/sol3/sol/string_view.hpp | 45 + extern/sol3/sol/table.hpp | 116 + extern/sol3/sol/table_core.hpp | 733 ++ extern/sol3/sol/table_iterator.hpp | 114 + extern/sol3/sol/table_proxy.hpp | 337 + extern/sol3/sol/thread.hpp | 189 + extern/sol3/sol/tie.hpp | 98 + extern/sol3/sol/traits.hpp | 739 ++ extern/sol3/sol/trampoline.hpp | 214 + extern/sol3/sol/tuple.hpp | 93 + extern/sol3/sol/types.hpp | 1496 +++ extern/sol3/sol/unicode.hpp | 308 + extern/sol3/sol/unique_usertype_traits.hpp | 240 + extern/sol3/sol/unsafe_function.hpp | 170 + extern/sol3/sol/unsafe_function_result.hpp | 180 + extern/sol3/sol/userdata.hpp | 134 + extern/sol3/sol/usertype.hpp | 131 + extern/sol3/sol/usertype_container.hpp | 1562 +++ extern/sol3/sol/usertype_container_launch.hpp | 432 + extern/sol3/sol/usertype_core.hpp | 214 + extern/sol3/sol/usertype_proxy.hpp | 187 + extern/sol3/sol/usertype_storage.hpp | 1161 ++ extern/sol3/sol/usertype_traits.hpp | 61 + extern/sol3/sol/variadic_args.hpp | 181 + extern/sol3/sol/variadic_results.hpp | 103 + extern/sol3/sol/version.hpp | 831 ++ extern/sol3/sol/wrapper.hpp | 280 + extern/sol_config/sol/config.hpp | 18 + files/CMakeLists.txt | 11 +- files/data-mw/CMakeLists.txt | 26 + files/data-mw/l10n/Calendar/de.yaml | 39 + files/data-mw/l10n/Calendar/en.yaml | 39 + files/data-mw/l10n/Calendar/fr.yaml | 42 + files/data-mw/l10n/Calendar/gmst.yaml | 16 + files/data-mw/l10n/Calendar/ru.yaml | 40 + files/data-mw/l10n/Calendar/sv.yaml | 7 + files/data-mw/l10n/Interface/gmst.yaml | 9 + files/data-mw/l10n/OMWEngine/gmst.yaml | 56 + files/data-mw/openmw_aux/calendarconfig.lua | 7 + files/data/CMakeLists.txt | 188 + files/data/builtin.omwscripts | 19 + .../fonts}/DejaVuFontLicense.txt | 0 .../fonts/DejaVuLGCSansMono.omwfont} | 4 +- .../fonts}/DejaVuLGCSansMono.ttf | Bin files/data/fonts/DemonicLetters.omwfont | 15 + files/data/fonts/DemonicLetters.ttf | Bin 0 -> 21188 bytes .../data/fonts/DemonicLettersFontLicense.txt | 97 + files/data/fonts/MysticCards.omwfont | 27 + files/data/fonts/MysticCards.ttf | Bin 0 -> 33108 bytes files/data/fonts/MysticCardsFontLicense.txt | 97 + files/data/l10n/Calendar/en.yaml | 41 + files/data/l10n/Interface/de.yaml | 27 + files/data/l10n/Interface/en.yaml | 24 + files/data/l10n/Interface/fr.yaml | 24 + files/data/l10n/Interface/ru.yaml | 21 + files/data/l10n/Interface/sv.yaml | 16 + files/data/l10n/OMWCamera/de.yaml | 72 + files/data/l10n/OMWCamera/en.yaml | 72 + files/data/l10n/OMWCamera/fr.yaml | 73 + files/data/l10n/OMWCamera/ru.yaml | 71 + files/data/l10n/OMWCamera/sv.yaml | 71 + files/data/l10n/OMWControls/en.yaml | 16 + files/data/l10n/OMWControls/fr.yaml | 16 + files/data/l10n/OMWControls/ru.yaml | 16 + files/data/l10n/OMWControls/sv.yaml | 15 + files/data/l10n/OMWEngine/de.yaml | 163 + files/data/l10n/OMWEngine/en.yaml | 159 + files/data/l10n/OMWEngine/fr.yaml | 158 + files/data/l10n/OMWEngine/ru.yaml | 159 + files/data/l10n/OMWEngine/sv.yaml | 161 + files/data/l10n/OMWShaders/de.yaml | 34 + files/data/l10n/OMWShaders/en.yaml | 50 + files/data/l10n/OMWShaders/fr.yaml | 50 + files/data/l10n/OMWShaders/ru.yaml | 50 + files/data/l10n/OMWShaders/sv.yaml | 50 + .../{ => data}/mygui/OpenMWResourcePlugin.xml | 0 files/{ => data}/mygui/RussoOne-Regular.ttf | Bin files/{ => data}/mygui/core.skin | 0 files/{ => data}/mygui/core.xml | 1 + files/{ => data}/mygui/core_layouteditor.xml | 0 .../mygui/openmw_alchemy_window.layout | 2 +- files/{ => data}/mygui/openmw_book.layout | 4 +- files/{ => data}/mygui/openmw_box.skin.xml | 0 files/{ => data}/mygui/openmw_button.skin.xml | 0 .../mygui/openmw_chargen_birth.layout | 4 +- .../mygui/openmw_chargen_class.layout | 4 +- .../openmw_chargen_class_description.layout | 2 +- .../mygui/openmw_chargen_create_class.layout | 4 +- ...penmw_chargen_generate_class_result.layout | 4 +- .../mygui/openmw_chargen_race.layout | 4 +- files/data/mygui/openmw_chargen_review.layout | 67 + .../openmw_chargen_select_attribute.layout | 28 + .../mygui/openmw_chargen_select_skill.layout | 50 + ...penmw_chargen_select_specialization.layout | 4 +- .../mygui/openmw_companion_window.layout | 2 +- .../mygui/openmw_confirmation_dialog.layout | 6 +- files/{ => data}/mygui/openmw_console.layout | 20 +- .../{ => data}/mygui/openmw_console.skin.xml | 0 .../mygui/openmw_container_window.layout | 2 +- .../mygui/openmw_count_window.layout | 10 +- .../mygui/openmw_debug_window.layout | 4 +- .../mygui/openmw_debug_window.skin.xml | 0 .../mygui/openmw_dialogue_window.layout | 0 .../mygui/openmw_dialogue_window.skin.xml | 0 files/{ => data}/mygui/openmw_edit.skin.xml | 0 .../mygui/openmw_edit_effect.layout | 14 +- .../{ => data}/mygui/openmw_edit_note.layout | 6 +- .../mygui/openmw_enchanting_dialog.layout | 2 +- files/{ => data}/mygui/openmw_hud.layout | 37 +- .../{ => data}/mygui/openmw_hud_box.skin.xml | 0 .../mygui/openmw_hud_energybar.skin.xml | 0 files/{ => data}/mygui/openmw_infobox.layout | 2 +- .../openmw_interactive_messagebox.layout | 2 +- ...nmw_interactive_messagebox_notransp.layout | 2 +- .../mygui/openmw_inventory_window.layout | 0 .../mygui/openmw_itemselection_dialog.layout | 4 +- .../mygui/openmw_jail_screen.layout | 0 files/{ => data}/mygui/openmw_journal.layout | 4 +- .../{ => data}/mygui/openmw_journal.skin.xml | 4 +- files/{ => data}/mygui/openmw_layers.xml | 18 +- files/data/mygui/openmw_levelup_dialog.layout | 47 + files/{ => data}/mygui/openmw_list.skin.xml | 8 + .../mygui/openmw_loading_screen.layout | 4 +- files/data/mygui/openmw_lua.xml | 23 + .../mygui/openmw_magicselection_dialog.layout | 4 +- files/{ => data}/mygui/openmw_mainmenu.layout | 0 .../{ => data}/mygui/openmw_mainmenu.skin.xml | 0 .../{ => data}/mygui/openmw_map_window.layout | 0 .../mygui/openmw_map_window.skin.xml | 0 .../mygui/openmw_merchantrepair.layout | 2 +- .../{ => data}/mygui/openmw_messagebox.layout | 0 .../mygui/openmw_persuasion_dialog.layout | 12 +- files/{ => data}/mygui/openmw_pointer.xml | 0 .../mygui/openmw_postprocessor_hud.layout | 113 + .../mygui/openmw_postprocessor_hud.skin.xml | 88 + .../{ => data}/mygui/openmw_progress.skin.xml | 0 .../mygui/openmw_quickkeys_menu.layout | 4 +- .../mygui/openmw_quickkeys_menu_assign.layout | 4 +- .../mygui/openmw_recharge_dialog.layout | 2 +- files/{ => data}/mygui/openmw_repair.layout | 2 +- files/{ => data}/mygui/openmw_resources.xml | 35 +- .../mygui/openmw_savegame_dialog.layout | 23 +- .../mygui/openmw_screen_fader.layout} | 2 +- .../mygui/openmw_screen_fader_hit.layout} | 2 +- files/{ => data}/mygui/openmw_scroll.layout | 0 files/{ => data}/mygui/openmw_scroll.skin.xml | 7 + files/{ => data}/mygui/openmw_settings.xml | 2 +- .../mygui/openmw_settings_window.layout | 604 +- .../mygui/openmw_spell_buying_window.layout | 2 +- .../mygui/openmw_spell_window.layout | 0 .../mygui/openmw_spellcreation_dialog.layout | 2 +- files/data/mygui/openmw_stats_window.layout | 134 + files/{ => data}/mygui/openmw_text.skin.xml | 0 .../{ => data}/mygui/openmw_text_input.layout | 4 +- files/{ => data}/mygui/openmw_tooltips.layout | 0 .../mygui/openmw_trade_window.layout | 2 +- .../mygui/openmw_trainingwindow.layout | 2 +- .../mygui/openmw_travel_window.layout | 2 +- .../mygui/openmw_wait_dialog.layout | 2 +- .../openmw_wait_dialog_progressbar.layout | 0 .../{ => data}/mygui/openmw_windows.skin.xml | 6 +- files/{ => data}/mygui/skins.xml | 5 +- files/{ => data}/mygui/tes3mp_chat.layout | 0 files/{ => data}/mygui/tes3mp_chat.skin.xml | 0 .../mygui/tes3mp_dialog_list.layout | 0 files/{ => data}/mygui/tes3mp_login.layout | 0 files/{ => data}/mygui/tes3mp_login.skin.xml | 0 .../{ => data}/mygui/tes3mp_text_input.layout | 0 files/data/openmw_aux/calendar.lua | 155 + files/data/openmw_aux/calendarconfig.lua | 9 + files/data/openmw_aux/time.lua | 104 + files/data/openmw_aux/ui.lua | 36 + files/data/openmw_aux/util.lua | 113 + files/data/scripts/omw/activationhandlers.lua | 86 + files/data/scripts/omw/ai.lua | 117 + files/data/scripts/omw/camera/camera.lua | 292 + .../omw/camera/first_person_auto_switch.lua | 52 + .../data/scripts/omw/camera/head_bobbing.lua | 61 + files/data/scripts/omw/camera/move360.lua | 79 + files/data/scripts/omw/camera/settings.lua | 102 + .../data/scripts/omw/camera/third_person.lua | 164 + files/data/scripts/omw/cellhandlers.lua | 65 + files/data/scripts/omw/console/global.lua | 81 + files/data/scripts/omw/console/local.lua | 71 + files/data/scripts/omw/console/player.lua | 159 + .../omw/mechanics/playercontroller.lua | 46 + files/data/scripts/omw/mwui/borders.lua | 203 + files/data/scripts/omw/mwui/constants.lua | 13 + files/data/scripts/omw/mwui/filters.lua | 31 + files/data/scripts/omw/mwui/init.lua | 154 + files/data/scripts/omw/mwui/space.lua | 39 + files/data/scripts/omw/mwui/text.lua | 39 + files/data/scripts/omw/mwui/textEdit.lua | 28 + files/data/scripts/omw/playercontrols.lua | 189 + files/data/scripts/omw/settings/common.lua | 143 + files/data/scripts/omw/settings/global.lua | 20 + files/data/scripts/omw/settings/player.lua | 153 + files/data/scripts/omw/settings/render.lua | 423 + files/data/scripts/omw/settings/renderers.lua | 267 + files/data/shaders/adjustments.omwfx | 38 + files/data/shaders/bloomlinear.omwfx | 222 + files/data/shaders/debug.omwfx | 45 + .../textures/omw}/water_nm.png | Bin .../textures/omw_menu_scroll_center_h.dds | Bin .../textures/omw_menu_scroll_center_v.dds | Bin .../textures/omw_menu_scroll_down.dds | Bin .../textures/omw_menu_scroll_left.dds | Bin .../textures/omw_menu_scroll_right.dds | Bin .../textures/omw_menu_scroll_up.dds | Bin .../tango/16x16/edit-clear.png} | Bin .../launcher/icons/tango/16x16/go-bottom.png | Bin 663 -> 0 bytes files/launcher/icons/tango/16x16/go-down.png | Bin 683 -> 0 bytes files/launcher/icons/tango/16x16/go-top.png | Bin 636 -> 0 bytes files/launcher/icons/tango/16x16/go-up.png | Bin 652 -> 0 bytes .../icons/tango/16x16/view-refresh.png | Bin 0 -> 912 bytes .../icons/tango/48x48/emblem-system.png | Bin 3423 -> 0 bytes .../icons/tango/48x48/preferences-system.png | Bin 3718 -> 0 bytes .../icons/tango/48x48/video-display.png | Bin 2547 -> 0 bytes files/launcher/icons/tango/index.theme | 7 +- files/launcher/images/down.png | Bin 362 -> 0 bytes files/launcher/images/openmw-header.png | Bin 63374 -> 48950 bytes files/launcher/images/playpage-background.png | Bin 233350 -> 0 bytes files/launcher/launcher.qrc | 14 +- files/lua_api/CMakeLists.txt | 26 + files/lua_api/README.md | 13 + files/lua_api/coroutine.doclua | 71 + files/lua_api/global.doclua | 264 + files/lua_api/math.doclua | 200 + files/lua_api/openmw/async.lua | 59 + files/lua_api/openmw/camera.lua | 234 + files/lua_api/openmw/core.lua | 717 ++ files/lua_api/openmw/debug.lua | 70 + files/lua_api/openmw/input.lua | 330 + files/lua_api/openmw/interfaces.lua | 26 + files/lua_api/openmw/nearby.lua | 232 + files/lua_api/openmw/postprocessing.lua | 132 + files/lua_api/openmw/self.lua | 57 + files/lua_api/openmw/storage.lua | 99 + files/lua_api/openmw/types.lua | 1636 +++ files/lua_api/openmw/ui.lua | 284 + files/lua_api/openmw/util.lua | 601 ++ files/lua_api/openmw/world.lua | 146 + files/lua_api/os.doclua | 64 + files/lua_api/string.doclua | 231 + files/lua_api/table.doclua | 66 + files/mygui/openmw_chargen_review.layout | 124 - .../openmw_chargen_select_attribute.layout | 33 - .../mygui/openmw_chargen_select_skill.layout | 68 - files/mygui/openmw_levelup_dialog.layout | 168 - files/mygui/openmw_stats_window.layout | 246 - files/opencs/edit-clone.png | Bin 472 -> 0 bytes files/opencs/edit-delete.png | Bin 680 -> 0 bytes files/opencs/edit-preview.png | Bin 525 -> 0 bytes files/opencs/magicrabbit.png | Bin 1820 -> 0 bytes files/opencs/map.png | Bin 1477 -> 0 bytes files/opencs/random-item.png | Bin 1698 -> 0 bytes files/opencs/raster/GMST.png | Bin 820 -> 0 bytes files/opencs/raster/Info.png | Bin 1234 -> 0 bytes files/opencs/raster/LandTexture.png | Bin 2662 -> 0 bytes files/opencs/raster/PathGrid.png | Bin 1297 -> 0 bytes files/opencs/raster/activator.png | Bin 1913 -> 0 bytes files/opencs/raster/added.png | Bin 615 -> 0 bytes files/opencs/raster/apparatus.png | Bin 1440 -> 0 bytes files/opencs/raster/armor.png | Bin 1641 -> 0 bytes files/opencs/raster/attribute.png | Bin 1788 -> 0 bytes files/opencs/raster/base.png | Bin 460 -> 0 bytes files/opencs/raster/birthsign.png | Bin 2454 -> 0 bytes files/opencs/raster/body-part.png | Bin 1248 -> 0 bytes files/opencs/raster/book.png | Bin 1599 -> 0 bytes files/opencs/raster/cell.png | Bin 1403 -> 0 bytes files/opencs/raster/class.png | Bin 2283 -> 0 bytes files/opencs/raster/clothing.png | Bin 1377 -> 0 bytes files/opencs/raster/container.png | Bin 1526 -> 0 bytes files/opencs/raster/creature.png | Bin 2297 -> 0 bytes files/opencs/raster/dialogoue-info.png | Bin 1851 -> 0 bytes files/opencs/raster/dialogoue-journal.png | Bin 1991 -> 0 bytes files/opencs/raster/dialogoue-regular.png | Bin 1486 -> 0 bytes files/opencs/raster/dialogue-greeting.png | Bin 1948 -> 0 bytes files/opencs/raster/dialogue-persuasion.png | Bin 1987 -> 0 bytes files/opencs/raster/dialogue-speech.png | Bin 1987 -> 0 bytes files/opencs/raster/door.png | Bin 1627 -> 0 bytes files/opencs/raster/enchantment.png | Bin 1812 -> 0 bytes files/opencs/raster/faction.png | Bin 1858 -> 0 bytes files/opencs/raster/filter.png | Bin 1375 -> 0 bytes files/opencs/raster/globvar.png | Bin 2394 -> 0 bytes files/opencs/raster/ingredient.png | Bin 1384 -> 0 bytes files/opencs/raster/land.png | Bin 1220 -> 0 bytes files/opencs/raster/landpaint.png | Bin 1361 -> 0 bytes files/opencs/raster/leveled-creature.png | Bin 2150 -> 0 bytes files/opencs/raster/light.png | Bin 1199 -> 0 bytes files/opencs/raster/lockpick.png | Bin 671 -> 0 bytes files/opencs/raster/magic-effect.png | Bin 1702 -> 0 bytes files/opencs/raster/magicrabbit.png | Bin 1820 -> 0 bytes files/opencs/raster/map.png | Bin 1477 -> 0 bytes files/opencs/raster/miscellaneous.png | Bin 1716 -> 0 bytes files/opencs/raster/modified.png | Bin 1320 -> 0 bytes files/opencs/raster/npc.png | Bin 2143 -> 0 bytes files/opencs/raster/potion.png | Bin 1582 -> 0 bytes files/opencs/raster/probe.png | Bin 587 -> 0 bytes files/opencs/raster/race.png | Bin 1834 -> 0 bytes files/opencs/raster/random-item.png | Bin 1698 -> 0 bytes files/opencs/raster/random.png | Bin 1892 -> 0 bytes files/opencs/raster/removed.png | Bin 1251 -> 0 bytes files/opencs/raster/repair.png | Bin 1115 -> 0 bytes .../scene-exterior-parts/0_backdrop.png | Bin 769 -> 0 bytes .../raster/scene-exterior-parts/1_grid.png | Bin 1246 -> 0 bytes .../raster/scene-exterior-parts/2_arrows.png | Bin 1251 -> 0 bytes .../scene-exterior-parts/3_cell_marker.png | Bin 808 -> 0 bytes .../raster/scene-exterior-parts/4_terrain.png | Bin 1670 -> 0 bytes .../raster/scene-exterior-parts/5_divider.png | Bin 290 -> 0 bytes .../composite_the_icons.sh | 18 - .../raster/scene-exterior-parts/mask.png | Bin 3540 -> 0 bytes .../opencs/raster/scene-play-rev9_masked.xcf | Bin 8365 -> 0 bytes .../scene-view-parts/0_sky_backdrop.png | Bin 216 -> 0 bytes .../raster/scene-view-parts/10_bridge.png | Bin 1848 -> 0 bytes .../scene-view-parts/11_terrain_front.png | Bin 1215 -> 0 bytes .../scene-view-parts/12_water_front.png | Bin 593 -> 0 bytes .../raster/scene-view-parts/13_pathgrid.png | Bin 751 -> 0 bytes .../raster/scene-view-parts/14_divider.png | Bin 224 -> 0 bytes .../1_terrain_very_distant.png | Bin 906 -> 0 bytes .../scene-view-parts/2_fog_very_distant.png | Bin 334 -> 0 bytes .../scene-view-parts/3_water_backdrop.png | Bin 824 -> 0 bytes .../scene-view-parts/4_water_distant.png | Bin 668 -> 0 bytes .../scene-view-parts/5_terrain_distant.png | Bin 1152 -> 0 bytes .../raster/scene-view-parts/6_fog_distant.png | Bin 1541 -> 0 bytes .../scene-view-parts/7_terrain_back.png | Bin 1567 -> 0 bytes .../raster/scene-view-parts/8_water_back.png | Bin 638 -> 0 bytes .../raster/scene-view-parts/9_fog_back.png | Bin 1426 -> 0 bytes files/opencs/raster/scene-view-parts/README | 10 - .../composite_the_32_icons.sh | 21 - files/opencs/raster/scene-view-parts/mask.png | Bin 1903 -> 0 bytes files/opencs/raster/script.png | Bin 952 -> 0 bytes files/opencs/raster/skill.png | Bin 1676 -> 0 bytes files/opencs/raster/sound.png | Bin 1144 -> 0 bytes files/opencs/raster/soundgen.png | Bin 2149 -> 0 bytes files/opencs/raster/spell.png | Bin 2071 -> 0 bytes files/opencs/raster/static.png | Bin 1297 -> 0 bytes files/opencs/raster/weapon.png | Bin 1003 -> 0 bytes files/opencs/resources.qrc | 3 - files/opencs/scalable/Palette.svg | 569 - files/opencs/scalable/editor-icons.svg | 9395 +++++++++++++++++ .../scalable/referenceable-record/.directory | 7 - .../referenceable-record/activator.svg | 1022 -- .../referenceable-record/apparatus.svg | 1058 -- .../scalable/referenceable-record/book.svg | 687 -- .../scalable/referenceable-record/book2.svgz | Bin 110316 -> 0 bytes .../referenceable-record/container.svg | 1899 ---- .../referenceable-record/ingredient.svg | 1107 -- .../scalable/referenceable-record/light.svg | 1508 --- .../scalable/referenceable-record/potion.svg | 1161 -- .../referenceable-record/random-item.svg | 156 - .../scalable/referenceable-record/repair.svg | 1280 --- .../scalable/referenceable-record/static.svg | 1141 -- .../scalable/referenceable-record/weapon.svg | 1206 --- files/opencs/scalable/scene-exterior.svg | 599 -- files/opencs/scalable/scene-play-rev9.svg | 1006 -- .../scalable/scene-view-status-rev14.svg | 924 -- files/opencs/scalable/status/.directory | 5 - files/opencs/scalable/status/added.svg | 932 -- files/opencs/scalable/status/base.svg | 942 -- files/opencs/scalable/status/modified.svg | 1155 -- files/opencs/scalable/status/removed.svg | 935 -- files/opencs/scalable/top-level/gmst.svg | 1047 -- .../scalable/top-level/topic-regular.svg | 1045 -- files/openmw.appdata.xml | 10 +- files/openmw.cfg | 36 +- files/openmw.cfg.local | 37 +- files/settings-default.cfg | 426 +- files/shaders/CMakeLists.txt | 88 +- .../bs/default.frag} | 64 +- .../bs/default.vert} | 18 +- .../shaders/compatibility/bs/nolighting.frag | 66 + .../shaders/compatibility/bs/nolighting.vert | 67 + files/shaders/compatibility/debug.frag | 23 + files/shaders/compatibility/debug.vert | 30 + files/shaders/compatibility/depthclipped.frag | 19 + files/shaders/compatibility/depthclipped.vert | 22 + files/shaders/compatibility/fog.glsl | 41 + .../shaders/compatibility/fullscreen_tri.frag | 10 + .../shaders/compatibility/fullscreen_tri.vert | 13 + .../groundcover.frag} | 33 +- .../groundcover.vert} | 14 +- files/shaders/compatibility/gui.frag | 11 + files/shaders/compatibility/gui.vert | 11 + .../compatibility/luminance/luminance.frag | 14 + .../compatibility/luminance/resolve.frag | 18 + .../compatibility/multiview_resolve.frag | 18 + .../compatibility/multiview_resolve.vert | 9 + .../objects.frag} | 135 +- .../objects.vert} | 31 +- .../compatibility/ripples_blobber.frag | 28 + .../compatibility/ripples_simulate.frag | 33 + files/shaders/compatibility/s360.frag | 21 + .../s360.vert} | 0 .../shadowcasting.frag} | 5 +- .../shadowcasting.vert} | 3 +- .../{ => compatibility}/shadows_fragment.glsl | 13 +- .../{ => compatibility}/shadows_vertex.glsl | 14 +- files/shaders/compatibility/sky.frag | 89 + files/shaders/compatibility/sky.vert | 21 + .../terrain.frag} | 42 +- .../terrain.vert} | 10 +- .../{ => compatibility}/vertexcolors.glsl | 0 .../water.frag} | 173 +- files/shaders/compatibility/water.vert | 30 + files/shaders/core/gui.frag | 15 + files/shaders/core/gui.vert | 13 + files/shaders/core/ripples_blobber.comp | 31 + files/shaders/core/ripples_simulate.comp | 31 + files/shaders/lib/core/fragment.glsl | 42 + files/shaders/lib/core/fragment.h.glsl | 20 + .../shaders/lib/core/fragment_multiview.glsl | 46 + files/shaders/lib/core/vertex.glsl | 20 + files/shaders/lib/core/vertex.h.glsl | 6 + files/shaders/lib/core/vertex_multiview.glsl | 26 + files/shaders/{ => lib/light}/lighting.glsl | 5 + .../{ => lib/light}/lighting_util.glsl | 15 +- files/shaders/lib/luminance/constants.glsl | 11 + files/shaders/{ => lib/material}/alpha.glsl | 40 +- .../shaders/{ => lib/material}/parallax.glsl | 5 + files/shaders/lib/particle/occlusion.glsl | 15 + files/shaders/lib/particle/soft.glsl | 48 + files/shaders/lib/sky/passes.glsl | 12 + .../util/coordinates.glsl} | 21 +- files/shaders/lib/util/quickstep.glsl | 12 + files/shaders/lib/view/depth.glsl | 24 + files/shaders/lib/water/fresnel.glsl | 25 + files/shaders/lib/water/rain_ripples.glsl | 126 + files/shaders/lib/water/ripples.glsl | 29 + files/shaders/nv_nolighting_fragment.glsl | 55 - files/shaders/nv_nolighting_vertex.glsl | 49 - files/shaders/water_vertex.glsl | 26 - files/ui/contentselector.ui | 18 +- files/ui/datafilespage.ui | 341 +- files/ui/directorypicker.ui | 47 + files/ui/graphicspage.ui | 668 +- files/ui/importpage.ui | 146 + files/ui/mainwindow.ui | 141 +- files/ui/playpage.ui | 189 - files/ui/settingspage.ui | 1488 ++- files/ui/wizard/importpage.ui | 15 + files/vfs/CMakeLists.txt | 18 - manual/opencs/creating_file.tex | 48 +- manual/opencs/files_and_directories.tex | 234 +- manual/opencs/filters.tex | 404 +- manual/opencs/main.tex | 74 +- manual/opencs/recordtypes.tex | 36 +- manual/opencs/tables.tex | 188 +- manual/opencs/windows.tex | 106 +- scripts/HOWTO-benchmark.md | 112 + .../test_lua_api/builtin.omwscripts | 1 + .../integration_tests/test_lua_api/openmw.cfg | 6 + .../integration_tests/test_lua_api/player.lua | 118 + .../integration_tests/test_lua_api/test.lua | 108 + .../test_lua_api/test.omwscripts | 3 + .../testing_util/testing_util.lua | 116 + scripts/find_missing_merge_requests.py | 161 + scripts/generate_teal_declarations.sh | 44 + scripts/integration_tests.py | 121 + scripts/osg_stats.py | 333 + scripts/preprocessed_file_size_stats.py | 42 + scripts/preprocessed_file_size_stats_diff.py | 46 + 3350 files changed, 308020 insertions(+), 159908 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .git-blame-ignore-revs create mode 100644 .github/workflows/openmw.yml create mode 100644 .resubmitted_merge_requests.txt delete mode 100644 .travis.yml delete mode 100644 CHANGELOG_PR.md create mode 100644 CI/Store-Symbols.ps1 delete mode 100755 CI/build_googletest.sh create mode 100755 CI/check_clang_format.sh create mode 100755 CI/check_cmake_format.sh create mode 100755 CI/check_file_names.sh create mode 100644 CI/file_name_exceptions.txt create mode 100644 CI/org.openmw.OpenMW.devel.yaml create mode 100755 CI/run_integration_tests.sh create mode 100755 CI/teal_ci.sh create mode 100755 CI/ubuntu_gcc_preprocess.sh create mode 100644 apps/benchmarks/detournavigator/CMakeLists.txt create mode 100644 apps/benchmarks/esm/CMakeLists.txt create mode 100644 apps/benchmarks/esm/benchrefid.cpp create mode 100644 apps/benchmarks/settings/CMakeLists.txt create mode 100644 apps/benchmarks/settings/access.cpp create mode 100644 apps/bulletobjecttool/CMakeLists.txt create mode 100644 apps/bulletobjecttool/main.cpp create mode 100644 apps/esmtool/arguments.hpp create mode 100644 apps/esmtool/tes4.cpp create mode 100644 apps/esmtool/tes4.hpp delete mode 100644 apps/essimporter/importacdt.cpp delete mode 100644 apps/launcher/advancedpage.cpp delete mode 100644 apps/launcher/advancedpage.hpp create mode 100644 apps/launcher/importpage.cpp create mode 100644 apps/launcher/importpage.hpp delete mode 100644 apps/launcher/playpage.cpp delete mode 100644 apps/launcher/playpage.hpp create mode 100644 apps/navmeshtool/CMakeLists.txt create mode 100644 apps/navmeshtool/main.cpp create mode 100644 apps/navmeshtool/navmesh.cpp create mode 100644 apps/navmeshtool/navmesh.hpp create mode 100644 apps/navmeshtool/worldspacedata.cpp create mode 100644 apps/navmeshtool/worldspacedata.hpp delete mode 100644 apps/opencs/model/doc/stage.cpp delete mode 100644 apps/opencs/model/filter/node.cpp create mode 100644 apps/opencs/model/world/idcollection.cpp delete mode 100644 apps/opencs/model/world/refidadapter.cpp create mode 100644 apps/opencs/view/filter/filterdata.hpp create mode 100644 apps/opencs/view/world/tableheadermouseeventhandler.cpp create mode 100644 apps/opencs/view/world/tableheadermouseeventhandler.hpp create mode 100644 apps/opencs_tests/CMakeLists.txt create mode 100644 apps/opencs_tests/main.cpp create mode 100644 apps/opencs_tests/model/world/testinfocollection.cpp create mode 100644 apps/opencs_tests/model/world/testuniversalid.cpp create mode 100644 apps/openmw/mwbase/luamanager.hpp create mode 100644 apps/openmw/mwclass/classmodel.hpp create mode 100644 apps/openmw/mwclass/esm4base.cpp create mode 100644 apps/openmw/mwclass/esm4base.hpp create mode 100644 apps/openmw/mwclass/light4.cpp create mode 100644 apps/openmw/mwclass/light4.hpp delete mode 100644 apps/openmw/mwdialogue/keywordsearch.cpp create mode 100644 apps/openmw/mwgui/postprocessorhud.cpp create mode 100644 apps/openmw/mwgui/postprocessorhud.hpp create mode 100644 apps/openmw/mwgui/settings.cpp create mode 100644 apps/openmw/mwgui/settings.hpp create mode 100644 apps/openmw/mwgui/ustring.hpp create mode 100644 apps/openmw/mwinput/gyromanager.cpp create mode 100644 apps/openmw/mwinput/gyromanager.hpp delete mode 100644 apps/openmw/mwinput/sdlmappings.cpp create mode 100644 apps/openmw/mwlua/README.md create mode 100644 apps/openmw/mwlua/camerabindings.cpp create mode 100644 apps/openmw/mwlua/camerabindings.hpp create mode 100644 apps/openmw/mwlua/cellbindings.cpp create mode 100644 apps/openmw/mwlua/cellbindings.hpp create mode 100644 apps/openmw/mwlua/context.hpp create mode 100644 apps/openmw/mwlua/debugbindings.cpp create mode 100644 apps/openmw/mwlua/debugbindings.hpp create mode 100644 apps/openmw/mwlua/engineevents.cpp create mode 100644 apps/openmw/mwlua/engineevents.hpp create mode 100644 apps/openmw/mwlua/globalscripts.hpp create mode 100644 apps/openmw/mwlua/inputbindings.cpp create mode 100644 apps/openmw/mwlua/inputbindings.hpp create mode 100644 apps/openmw/mwlua/localscripts.cpp create mode 100644 apps/openmw/mwlua/localscripts.hpp create mode 100644 apps/openmw/mwlua/luabindings.cpp create mode 100644 apps/openmw/mwlua/luabindings.hpp create mode 100644 apps/openmw/mwlua/luaevents.cpp create mode 100644 apps/openmw/mwlua/luaevents.hpp create mode 100644 apps/openmw/mwlua/luamanagerimp.cpp create mode 100644 apps/openmw/mwlua/luamanagerimp.hpp create mode 100644 apps/openmw/mwlua/magicbindings.cpp create mode 100644 apps/openmw/mwlua/magicbindings.hpp create mode 100644 apps/openmw/mwlua/mwscriptbindings.cpp create mode 100644 apps/openmw/mwlua/mwscriptbindings.hpp create mode 100644 apps/openmw/mwlua/nearbybindings.cpp create mode 100644 apps/openmw/mwlua/nearbybindings.hpp create mode 100644 apps/openmw/mwlua/object.hpp create mode 100644 apps/openmw/mwlua/objectbindings.cpp create mode 100644 apps/openmw/mwlua/objectbindings.hpp create mode 100644 apps/openmw/mwlua/objectvariant.hpp create mode 100644 apps/openmw/mwlua/playerscripts.hpp create mode 100644 apps/openmw/mwlua/postprocessingbindings.cpp create mode 100644 apps/openmw/mwlua/postprocessingbindings.hpp create mode 100644 apps/openmw/mwlua/stats.cpp create mode 100644 apps/openmw/mwlua/stats.hpp create mode 100644 apps/openmw/mwlua/types/activator.cpp create mode 100644 apps/openmw/mwlua/types/actor.cpp create mode 100644 apps/openmw/mwlua/types/apparatus.cpp create mode 100644 apps/openmw/mwlua/types/armor.cpp create mode 100644 apps/openmw/mwlua/types/book.cpp create mode 100644 apps/openmw/mwlua/types/clothing.cpp create mode 100644 apps/openmw/mwlua/types/container.cpp create mode 100644 apps/openmw/mwlua/types/creature.cpp create mode 100644 apps/openmw/mwlua/types/door.cpp create mode 100644 apps/openmw/mwlua/types/ingredient.cpp create mode 100644 apps/openmw/mwlua/types/item.cpp create mode 100644 apps/openmw/mwlua/types/levelledlist.cpp create mode 100644 apps/openmw/mwlua/types/light.cpp create mode 100644 apps/openmw/mwlua/types/lockable.cpp create mode 100644 apps/openmw/mwlua/types/lockpick.cpp create mode 100644 apps/openmw/mwlua/types/misc.cpp create mode 100644 apps/openmw/mwlua/types/npc.cpp create mode 100644 apps/openmw/mwlua/types/player.cpp create mode 100644 apps/openmw/mwlua/types/potion.cpp create mode 100644 apps/openmw/mwlua/types/probe.cpp create mode 100644 apps/openmw/mwlua/types/repair.cpp create mode 100644 apps/openmw/mwlua/types/static.cpp create mode 100644 apps/openmw/mwlua/types/types.cpp create mode 100644 apps/openmw/mwlua/types/types.hpp create mode 100644 apps/openmw/mwlua/types/weapon.cpp create mode 100644 apps/openmw/mwlua/uibindings.cpp create mode 100644 apps/openmw/mwlua/uibindings.hpp create mode 100644 apps/openmw/mwlua/userdataserializer.cpp create mode 100644 apps/openmw/mwlua/userdataserializer.hpp create mode 100644 apps/openmw/mwlua/worker.cpp create mode 100644 apps/openmw/mwlua/worker.hpp create mode 100644 apps/openmw/mwlua/worldview.cpp create mode 100644 apps/openmw/mwlua/worldview.hpp delete mode 100644 apps/openmw/mwmechanics/actor.cpp create mode 100644 apps/openmw/mwmechanics/aisetting.hpp create mode 100644 apps/openmw/mwmechanics/aistatefwd.hpp create mode 100644 apps/openmw/mwmechanics/aitemporarybase.hpp create mode 100644 apps/openmw/mwmechanics/creaturecustomdataresetter.hpp create mode 100644 apps/openmw/mwmechanics/greetingstate.hpp create mode 100644 apps/openmw/mwmechanics/inventory.hpp create mode 100644 apps/openmw/mwmechanics/levelledlist.cpp delete mode 100644 apps/openmw/mwmechanics/linkedeffects.cpp delete mode 100644 apps/openmw/mwmechanics/linkedeffects.hpp create mode 100644 apps/openmw/mwmechanics/setbaseaisetting.hpp delete mode 100644 apps/openmw/mwmechanics/spellabsorption.cpp delete mode 100644 apps/openmw/mwmechanics/spellabsorption.hpp create mode 100644 apps/openmw/mwmechanics/spelleffects.cpp create mode 100644 apps/openmw/mwmechanics/spelleffects.hpp delete mode 100644 apps/openmw/mwmechanics/tickableeffects.cpp delete mode 100644 apps/openmw/mwmechanics/tickableeffects.hpp create mode 100644 apps/openmw/mwrender/luminancecalculator.cpp create mode 100644 apps/openmw/mwrender/luminancecalculator.hpp create mode 100644 apps/openmw/mwrender/navmeshmode.cpp create mode 100644 apps/openmw/mwrender/navmeshmode.hpp create mode 100644 apps/openmw/mwrender/pingpongcanvas.cpp create mode 100644 apps/openmw/mwrender/pingpongcanvas.hpp create mode 100644 apps/openmw/mwrender/pingpongcull.cpp create mode 100644 apps/openmw/mwrender/pingpongcull.hpp create mode 100644 apps/openmw/mwrender/postprocessor.cpp create mode 100644 apps/openmw/mwrender/postprocessor.hpp create mode 100644 apps/openmw/mwrender/precipitationocclusion.cpp create mode 100644 apps/openmw/mwrender/precipitationocclusion.hpp create mode 100644 apps/openmw/mwrender/ripples.cpp create mode 100644 apps/openmw/mwrender/ripples.hpp create mode 100644 apps/openmw/mwrender/skyutil.cpp create mode 100644 apps/openmw/mwrender/skyutil.hpp create mode 100644 apps/openmw/mwrender/transparentpass.cpp create mode 100644 apps/openmw/mwrender/transparentpass.hpp delete mode 100644 apps/openmw/mwrender/viewovershoulder.cpp delete mode 100644 apps/openmw/mwrender/viewovershoulder.hpp create mode 100644 apps/openmw/mwworld/cell.cpp create mode 100644 apps/openmw/mwworld/cell.hpp create mode 100644 apps/openmw/mwworld/duration.hpp create mode 100644 apps/openmw/mwworld/globalvariablename.hpp create mode 100644 apps/openmw/mwworld/groundcoverstore.cpp create mode 100644 apps/openmw/mwworld/groundcoverstore.hpp create mode 100644 apps/openmw/mwworld/magiceffects.cpp create mode 100644 apps/openmw/mwworld/magiceffects.hpp create mode 100644 apps/openmw/mwworld/ptrregistry.hpp delete mode 100644 apps/openmw/mwworld/recordcmp.hpp create mode 100644 apps/openmw/mwworld/registeredclass.hpp create mode 100644 apps/openmw/mwworld/spellcaststate.hpp create mode 100644 apps/openmw/mwworld/worldmodel.cpp create mode 100644 apps/openmw/mwworld/worldmodel.hpp create mode 100644 apps/openmw/options.cpp create mode 100644 apps/openmw/options.hpp create mode 100644 apps/openmw/profile.hpp create mode 100644 apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp create mode 100644 apps/openmw_test_suite/detournavigator/generate.hpp create mode 100644 apps/openmw_test_suite/detournavigator/navmeshdb.cpp create mode 100644 apps/openmw_test_suite/detournavigator/settings.hpp create mode 100644 apps/openmw_test_suite/esm/testrefid.cpp create mode 100644 apps/openmw_test_suite/esm3/readerscache.cpp create mode 100644 apps/openmw_test_suite/esm3/testesmwriter.cpp create mode 100644 apps/openmw_test_suite/esm3/testsaveload.cpp create mode 100644 apps/openmw_test_suite/esm4/includes.cpp create mode 100644 apps/openmw_test_suite/esmloader/esmdata.cpp create mode 100644 apps/openmw_test_suite/esmloader/load.cpp create mode 100644 apps/openmw_test_suite/esmloader/record.cpp create mode 100644 apps/openmw_test_suite/files/conversion_tests.cpp create mode 100644 apps/openmw_test_suite/files/hash.cpp create mode 100644 apps/openmw_test_suite/fx/lexer.cpp create mode 100644 apps/openmw_test_suite/fx/technique.cpp create mode 100644 apps/openmw_test_suite/lua/test_async.cpp create mode 100644 apps/openmw_test_suite/lua/test_configuration.cpp create mode 100644 apps/openmw_test_suite/lua/test_l10n.cpp create mode 100644 apps/openmw_test_suite/lua/test_lua.cpp create mode 100644 apps/openmw_test_suite/lua/test_scriptscontainer.cpp create mode 100644 apps/openmw_test_suite/lua/test_serialization.cpp create mode 100644 apps/openmw_test_suite/lua/test_storage.cpp create mode 100644 apps/openmw_test_suite/lua/test_ui_content.cpp create mode 100644 apps/openmw_test_suite/lua/test_utilpackage.cpp create mode 100644 apps/openmw_test_suite/misc/compression.cpp create mode 100644 apps/openmw_test_suite/misc/progressreporter.cpp create mode 100644 apps/openmw_test_suite/misc/test_resourcehelpers.cpp create mode 100644 apps/openmw_test_suite/mwscript/test_scripts.cpp create mode 100644 apps/openmw_test_suite/mwscript/test_utils.hpp create mode 100644 apps/openmw_test_suite/mwworld/testduration.cpp create mode 100644 apps/openmw_test_suite/mwworld/testtimestamp.cpp create mode 100644 apps/openmw_test_suite/nif/node.hpp create mode 100644 apps/openmw_test_suite/nifosg/testnifloader.cpp create mode 100644 apps/openmw_test_suite/openmw/options.cpp create mode 100644 apps/openmw_test_suite/serialization/binaryreader.cpp create mode 100644 apps/openmw_test_suite/serialization/binarywriter.cpp create mode 100644 apps/openmw_test_suite/serialization/format.hpp create mode 100644 apps/openmw_test_suite/serialization/integration.cpp create mode 100644 apps/openmw_test_suite/serialization/sizeaccumulator.cpp create mode 100644 apps/openmw_test_suite/settings/shadermanager.cpp create mode 100644 apps/openmw_test_suite/settings/testvalues.cpp create mode 100644 apps/openmw_test_suite/shader/parselinks.cpp create mode 100644 apps/openmw_test_suite/sqlite3/db.cpp create mode 100644 apps/openmw_test_suite/sqlite3/request.cpp create mode 100644 apps/openmw_test_suite/sqlite3/statement.cpp create mode 100644 apps/openmw_test_suite/sqlite3/transaction.cpp create mode 100644 apps/openmw_test_suite/testing_util.hpp rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/french-utf8.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/french-win1252.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/russian-utf8.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/russian-win1251.txt (100%) create mode 100644 apps/openmw_test_suite/toutf8/toutf8.cpp delete mode 100644 appveyor.yml create mode 100644 cmake/CheckLuaCustomAllocator.cmake create mode 100644 cmake/CheckOsgMultiview.cmake create mode 100644 cmake/SignMacApplications.cmake create mode 100644 components/bsa/ba2dx10file.cpp create mode 100644 components/bsa/ba2dx10file.hpp create mode 100644 components/bsa/ba2file.cpp create mode 100644 components/bsa/ba2file.hpp create mode 100644 components/bsa/ba2gnrlfile.cpp create mode 100644 components/bsa/ba2gnrlfile.hpp delete mode 100644 components/bsa/memorystream.cpp create mode 100644 components/bullethelpers/collisionobject.hpp create mode 100644 components/bullethelpers/debug.hpp create mode 100644 components/bullethelpers/heightfield.hpp delete mode 100644 components/config/settingsbase.hpp create mode 100644 components/debug/debugdraw.cpp create mode 100644 components/debug/debugdraw.hpp create mode 100644 components/detournavigator/agentbounds.hpp create mode 100644 components/detournavigator/changetype.hpp create mode 100644 components/detournavigator/collisionshapetype.cpp create mode 100644 components/detournavigator/collisionshapetype.hpp create mode 100644 components/detournavigator/commulativeaabb.cpp create mode 100644 components/detournavigator/commulativeaabb.hpp create mode 100644 components/detournavigator/dbrefgeometryobject.hpp delete mode 100644 components/detournavigator/dtstatus.hpp create mode 100644 components/detournavigator/generatenavmeshtile.cpp create mode 100644 components/detournavigator/generatenavmeshtile.hpp create mode 100644 components/detournavigator/gettilespositions.cpp create mode 100644 components/detournavigator/guardednavmeshcacheitem.hpp create mode 100644 components/detournavigator/heightfieldshape.hpp create mode 100644 components/detournavigator/navigatorutils.cpp create mode 100644 components/detournavigator/navigatorutils.hpp create mode 100644 components/detournavigator/navmeshcacheitem.cpp create mode 100644 components/detournavigator/navmeshdb.cpp create mode 100644 components/detournavigator/navmeshdb.hpp create mode 100644 components/detournavigator/navmeshdbutils.cpp create mode 100644 components/detournavigator/navmeshdbutils.hpp create mode 100644 components/detournavigator/objecttransform.hpp delete mode 100644 components/detournavigator/oscillatingrecastmeshobject.cpp delete mode 100644 components/detournavigator/oscillatingrecastmeshobject.hpp create mode 100644 components/detournavigator/preparednavmeshdata.cpp create mode 100644 components/detournavigator/preparednavmeshdata.hpp create mode 100644 components/detournavigator/preparednavmeshdatatuple.hpp create mode 100644 components/detournavigator/recast.cpp create mode 100644 components/detournavigator/recast.hpp create mode 100644 components/detournavigator/recastcontext.cpp create mode 100644 components/detournavigator/recastcontext.hpp create mode 100644 components/detournavigator/recastmeshprovider.hpp create mode 100644 components/detournavigator/recastparams.hpp create mode 100644 components/detournavigator/ref.hpp create mode 100644 components/detournavigator/serialization.cpp create mode 100644 components/detournavigator/serialization.hpp delete mode 100644 components/detournavigator/sharednavmesh.hpp create mode 100644 components/detournavigator/sharednavmeshcacheitem.hpp create mode 100644 components/detournavigator/stats.cpp create mode 100644 components/detournavigator/stats.hpp create mode 100644 components/detournavigator/tilespositionsrange.hpp create mode 100644 components/detournavigator/updateguard.hpp delete mode 100644 components/esm/activespells.cpp delete mode 100644 components/esm/activespells.hpp delete mode 100644 components/esm/aipackage.cpp delete mode 100644 components/esm/aisequence.cpp delete mode 100644 components/esm/aisequence.hpp delete mode 100644 components/esm/cellid.cpp delete mode 100644 components/esm/cellid.hpp delete mode 100644 components/esm/cellstate.cpp create mode 100644 components/esm/common.cpp create mode 100644 components/esm/common.hpp delete mode 100644 components/esm/containerstate.cpp delete mode 100644 components/esm/containerstate.hpp delete mode 100644 components/esm/controlsstate.cpp delete mode 100644 components/esm/creaturestate.cpp delete mode 100644 components/esm/creaturestats.cpp delete mode 100644 components/esm/custommarkerstate.cpp delete mode 100644 components/esm/custommarkerstate.hpp delete mode 100644 components/esm/debugprofile.cpp delete mode 100644 components/esm/debugprofile.hpp delete mode 100644 components/esm/dialoguestate.cpp delete mode 100644 components/esm/doorstate.hpp delete mode 100644 components/esm/effectlist.cpp create mode 100644 components/esm/esm3exteriorcellrefid.cpp create mode 100644 components/esm/esm3exteriorcellrefid.hpp create mode 100644 components/esm/esmbridge.cpp create mode 100644 components/esm/esmbridge.hpp delete mode 100644 components/esm/esmreader.cpp delete mode 100644 components/esm/esmreader.hpp create mode 100644 components/esm/esmterrain.cpp create mode 100644 components/esm/esmterrain.hpp delete mode 100644 components/esm/esmwriter.cpp delete mode 100644 components/esm/filter.cpp delete mode 100644 components/esm/filter.hpp delete mode 100644 components/esm/fogstate.cpp create mode 100644 components/esm/format.cpp create mode 100644 components/esm/format.hpp create mode 100644 components/esm/formid.cpp create mode 100644 components/esm/formid.hpp create mode 100644 components/esm/formidrefid.cpp create mode 100644 components/esm/formidrefid.hpp create mode 100644 components/esm/fourcc.hpp create mode 100644 components/esm/generatedrefid.cpp create mode 100644 components/esm/generatedrefid.hpp delete mode 100644 components/esm/globalmap.cpp delete mode 100644 components/esm/globalscript.cpp create mode 100644 components/esm/indexrefid.cpp create mode 100644 components/esm/indexrefid.hpp delete mode 100644 components/esm/inventorystate.cpp delete mode 100644 components/esm/journalentry.cpp delete mode 100644 components/esm/loadacti.hpp delete mode 100644 components/esm/loadalch.hpp delete mode 100644 components/esm/loadappa.hpp delete mode 100644 components/esm/loadarmo.hpp delete mode 100644 components/esm/loadbody.hpp delete mode 100644 components/esm/loadbook.hpp delete mode 100644 components/esm/loadbsgn.hpp delete mode 100644 components/esm/loadclas.cpp delete mode 100644 components/esm/loadclas.hpp delete mode 100644 components/esm/loadclot.hpp delete mode 100644 components/esm/loadcont.hpp delete mode 100644 components/esm/loadcrea.hpp delete mode 100644 components/esm/loaddial.cpp delete mode 100644 components/esm/loaddial.hpp delete mode 100644 components/esm/loaddoor.cpp delete mode 100644 components/esm/loaddoor.hpp delete mode 100644 components/esm/loadench.hpp delete mode 100644 components/esm/loadfact.hpp delete mode 100644 components/esm/loadglob.cpp delete mode 100644 components/esm/loadglob.hpp delete mode 100644 components/esm/loadgmst.cpp delete mode 100644 components/esm/loadgmst.hpp delete mode 100644 components/esm/loadinfo.cpp delete mode 100644 components/esm/loadinfo.hpp delete mode 100644 components/esm/loadingr.hpp delete mode 100644 components/esm/loadland.hpp delete mode 100644 components/esm/loadlevlist.hpp delete mode 100644 components/esm/loadligh.hpp delete mode 100644 components/esm/loadlock.hpp delete mode 100644 components/esm/loadltex.hpp delete mode 100644 components/esm/loadmgef.cpp delete mode 100644 components/esm/loadmgef.hpp delete mode 100644 components/esm/loadmisc.hpp delete mode 100644 components/esm/loadnpc.hpp delete mode 100644 components/esm/loadpgrd.hpp delete mode 100644 components/esm/loadprob.hpp delete mode 100644 components/esm/loadrace.hpp delete mode 100644 components/esm/loadregn.hpp delete mode 100644 components/esm/loadrepa.hpp delete mode 100644 components/esm/loadscpt.hpp delete mode 100644 components/esm/loadskil.cpp delete mode 100644 components/esm/loadskil.hpp delete mode 100644 components/esm/loadsndg.hpp delete mode 100644 components/esm/loadsoun.hpp delete mode 100644 components/esm/loadspel.hpp delete mode 100644 components/esm/loadsscr.hpp delete mode 100644 components/esm/loadstat.hpp delete mode 100644 components/esm/loadtes3.cpp delete mode 100644 components/esm/loadweap.hpp delete mode 100644 components/esm/locals.cpp create mode 100644 components/esm/luascripts.cpp create mode 100644 components/esm/luascripts.hpp delete mode 100644 components/esm/magiceffects.cpp delete mode 100644 components/esm/magiceffects.hpp delete mode 100644 components/esm/mappings.cpp delete mode 100644 components/esm/mappings.hpp delete mode 100644 components/esm/npcstate.cpp delete mode 100644 components/esm/npcstats.cpp delete mode 100644 components/esm/objectstate.cpp delete mode 100644 components/esm/player.cpp delete mode 100644 components/esm/player.hpp delete mode 100644 components/esm/projectilestate.cpp delete mode 100644 components/esm/queststate.cpp delete mode 100644 components/esm/quickkeys.cpp delete mode 100644 components/esm/quickkeys.hpp create mode 100644 components/esm/refid.cpp create mode 100644 components/esm/refid.hpp delete mode 100644 components/esm/savedgame.cpp create mode 100644 components/esm/serializerefid.hpp delete mode 100644 components/esm/spelllist.cpp delete mode 100644 components/esm/spellstate.cpp delete mode 100644 components/esm/spellstate.hpp delete mode 100644 components/esm/stolenitems.cpp create mode 100644 components/esm/stringrefid.cpp create mode 100644 components/esm/stringrefid.hpp create mode 100644 components/esm/typetraits.hpp create mode 100644 components/esm/util.cpp delete mode 100644 components/esm/variant.cpp delete mode 100644 components/esm/variant.hpp delete mode 100644 components/esm/variantimp.cpp create mode 100644 components/esm3/activespells.cpp create mode 100644 components/esm3/activespells.hpp create mode 100644 components/esm3/aipackage.cpp rename components/{esm => esm3}/aipackage.hpp (72%) create mode 100644 components/esm3/aisequence.cpp create mode 100644 components/esm3/aisequence.hpp rename components/{esm => esm3}/animationstate.cpp (96%) rename components/{esm => esm3}/animationstate.hpp (82%) create mode 100644 components/esm3/cellid.cpp create mode 100644 components/esm3/cellid.hpp create mode 100644 components/esm3/cellref.cpp create mode 100644 components/esm3/cellref.hpp create mode 100644 components/esm3/cellstate.cpp rename components/{esm => esm3}/cellstate.hpp (61%) create mode 100644 components/esm3/containerstate.cpp create mode 100644 components/esm3/containerstate.hpp create mode 100644 components/esm3/controlsstate.cpp rename components/{esm => esm3}/controlsstate.hpp (91%) rename components/{esm => esm3}/creaturelevliststate.cpp (53%) rename components/{esm => esm3}/creaturelevliststate.hpp (55%) create mode 100644 components/esm3/creaturestate.cpp rename components/{esm => esm3}/creaturestate.hpp (56%) create mode 100644 components/esm3/creaturestats.cpp rename components/{esm => esm3}/creaturestats.hpp (56%) create mode 100644 components/esm3/custommarkerstate.cpp create mode 100644 components/esm3/custommarkerstate.hpp create mode 100644 components/esm3/debugprofile.cpp create mode 100644 components/esm3/debugprofile.hpp create mode 100644 components/esm3/dialoguestate.cpp rename components/{esm => esm3}/dialoguestate.hpp (59%) rename components/{esm => esm3}/doorstate.cpp (63%) create mode 100644 components/esm3/doorstate.hpp create mode 100644 components/esm3/effectlist.cpp rename components/{esm => esm3}/effectlist.hpp (83%) create mode 100644 components/esm3/esmreader.cpp create mode 100644 components/esm3/esmreader.hpp create mode 100644 components/esm3/esmwriter.cpp rename components/{esm => esm3}/esmwriter.hpp (50%) create mode 100644 components/esm3/filter.cpp create mode 100644 components/esm3/filter.hpp create mode 100644 components/esm3/fogstate.cpp rename components/{esm => esm3}/fogstate.hpp (86%) create mode 100644 components/esm3/formatversion.hpp create mode 100644 components/esm3/globalmap.cpp rename components/{esm => esm3}/globalmap.hpp (78%) create mode 100644 components/esm3/globalscript.cpp rename components/{esm => esm3}/globalscript.hpp (64%) create mode 100644 components/esm3/infoorder.hpp create mode 100644 components/esm3/inventorystate.cpp rename components/{esm => esm3}/inventorystate.hpp (61%) create mode 100644 components/esm3/journalentry.cpp rename components/{esm => esm3}/journalentry.hpp (66%) rename components/{esm => esm3}/loadacti.cpp (58%) create mode 100644 components/esm3/loadacti.hpp rename components/{esm => esm3}/loadalch.cpp (62%) create mode 100644 components/esm3/loadalch.hpp rename components/{esm => esm3}/loadappa.cpp (64%) create mode 100644 components/esm3/loadappa.hpp rename components/{esm => esm3}/loadarmo.cpp (60%) create mode 100644 components/esm3/loadarmo.hpp rename components/{esm => esm3}/loadbody.cpp (60%) create mode 100644 components/esm3/loadbody.hpp rename components/{esm => esm3}/loadbook.cpp (60%) create mode 100644 components/esm3/loadbook.hpp rename components/{esm => esm3}/loadbsgn.cpp (66%) create mode 100644 components/esm3/loadbsgn.hpp create mode 100644 components/esm3/loadcell.cpp create mode 100644 components/esm3/loadcell.hpp create mode 100644 components/esm3/loadclas.cpp create mode 100644 components/esm3/loadclas.hpp rename components/{esm => esm3}/loadclot.cpp (59%) create mode 100644 components/esm3/loadclot.hpp rename components/{esm => esm3}/loadcont.cpp (65%) create mode 100644 components/esm3/loadcont.hpp rename components/{esm => esm3}/loadcrea.cpp (68%) create mode 100644 components/esm3/loadcrea.hpp create mode 100644 components/esm3/loaddial.cpp create mode 100644 components/esm3/loaddial.hpp create mode 100644 components/esm3/loaddoor.cpp create mode 100644 components/esm3/loaddoor.hpp rename components/{esm => esm3}/loadench.cpp (66%) create mode 100644 components/esm3/loadench.hpp rename components/{esm => esm3}/loadfact.cpp (60%) create mode 100644 components/esm3/loadfact.hpp create mode 100644 components/esm3/loadglob.cpp create mode 100644 components/esm3/loadglob.hpp create mode 100644 components/esm3/loadgmst.cpp create mode 100644 components/esm3/loadgmst.hpp create mode 100644 components/esm3/loadinfo.cpp create mode 100644 components/esm3/loadinfo.hpp rename components/{esm => esm3}/loadingr.cpp (57%) create mode 100644 components/esm3/loadingr.hpp rename components/{esm => esm3}/loadland.cpp (70%) create mode 100644 components/esm3/loadland.hpp rename components/{esm => esm3}/loadlevlist.cpp (75%) create mode 100644 components/esm3/loadlevlist.hpp rename components/{esm => esm3}/loadligh.cpp (59%) create mode 100644 components/esm3/loadligh.hpp rename components/{esm => esm3}/loadlock.cpp (62%) create mode 100644 components/esm3/loadlock.hpp rename components/{esm => esm3}/loadltex.cpp (65%) create mode 100644 components/esm3/loadltex.hpp create mode 100644 components/esm3/loadmgef.cpp create mode 100644 components/esm3/loadmgef.hpp rename components/{esm => esm3}/loadmisc.cpp (61%) create mode 100644 components/esm3/loadmisc.hpp rename components/{esm => esm3}/loadnpc.cpp (67%) create mode 100644 components/esm3/loadnpc.hpp rename components/{esm => esm3}/loadpgrd.cpp (73%) create mode 100644 components/esm3/loadpgrd.hpp rename components/{esm => esm3}/loadprob.cpp (63%) create mode 100644 components/esm3/loadprob.hpp rename components/{esm => esm3}/loadrace.cpp (59%) create mode 100644 components/esm3/loadrace.hpp rename components/{esm => esm3}/loadregn.cpp (54%) create mode 100644 components/esm3/loadregn.hpp rename components/{esm => esm3}/loadrepa.cpp (63%) create mode 100644 components/esm3/loadrepa.hpp rename components/{esm => esm3}/loadscpt.cpp (79%) create mode 100644 components/esm3/loadscpt.hpp create mode 100644 components/esm3/loadskil.cpp create mode 100644 components/esm3/loadskil.hpp rename components/{esm => esm3}/loadsndg.cpp (53%) create mode 100644 components/esm3/loadsndg.hpp rename components/{esm => esm3}/loadsoun.cpp (66%) create mode 100644 components/esm3/loadsoun.hpp rename components/{esm => esm3}/loadspel.cpp (66%) create mode 100644 components/esm3/loadspel.hpp rename components/{esm => esm3}/loadsscr.cpp (66%) create mode 100644 components/esm3/loadsscr.hpp rename components/{esm => esm3}/loadstat.cpp (59%) create mode 100644 components/esm3/loadstat.hpp create mode 100644 components/esm3/loadtes3.cpp rename components/{esm => esm3}/loadtes3.hpp (78%) rename components/{esm => esm3}/loadweap.cpp (62%) create mode 100644 components/esm3/loadweap.hpp create mode 100644 components/esm3/locals.cpp rename components/{esm => esm3}/locals.hpp (71%) create mode 100644 components/esm3/magiceffects.cpp create mode 100644 components/esm3/magiceffects.hpp create mode 100644 components/esm3/mappings.cpp create mode 100644 components/esm3/mappings.hpp create mode 100644 components/esm3/npcstate.cpp rename components/{esm => esm3}/npcstate.hpp (60%) create mode 100644 components/esm3/npcstats.cpp rename components/{esm => esm3}/npcstats.hpp (60%) create mode 100644 components/esm3/objectstate.cpp rename components/{esm => esm3}/objectstate.hpp (71%) create mode 100644 components/esm3/player.cpp create mode 100644 components/esm3/player.hpp create mode 100644 components/esm3/projectilestate.cpp rename components/{esm => esm3}/projectilestate.hpp (60%) create mode 100644 components/esm3/queststate.cpp rename components/{esm => esm3}/queststate.hpp (63%) create mode 100644 components/esm3/quickkeys.cpp create mode 100644 components/esm3/quickkeys.hpp create mode 100644 components/esm3/readerscache.cpp create mode 100644 components/esm3/readerscache.hpp create mode 100644 components/esm3/savedgame.cpp rename components/{esm => esm3}/savedgame.hpp (68%) create mode 100644 components/esm3/spelllist.cpp rename components/{esm => esm3}/spelllist.hpp (69%) create mode 100644 components/esm3/spellstate.cpp create mode 100644 components/esm3/spellstate.hpp rename components/{esm => esm3}/statstate.cpp (77%) rename components/{esm => esm3}/statstate.hpp (78%) create mode 100644 components/esm3/stolenitems.cpp rename components/{esm => esm3}/stolenitems.hpp (58%) rename components/{esm => esm3}/transport.cpp (65%) rename components/{esm => esm3}/transport.hpp (78%) create mode 100644 components/esm3/typetraits.hpp create mode 100644 components/esm3/variant.cpp create mode 100644 components/esm3/variant.hpp create mode 100644 components/esm3/variantimp.cpp rename components/{esm => esm3}/variantimp.hpp (88%) rename components/{esm => esm3}/weatherstate.cpp (60%) rename components/{esm => esm3}/weatherstate.hpp (82%) rename components/{esmterrain => esm3terrain}/storage.cpp (51%) rename components/{esmterrain => esm3terrain}/storage.hpp (60%) create mode 100644 components/esm4/actor.hpp create mode 100644 components/esm4/cellgrid.hpp create mode 100644 components/esm4/common.hpp create mode 100644 components/esm4/dialogue.hpp create mode 100644 components/esm4/effect.hpp create mode 100644 components/esm4/formid.cpp create mode 100644 components/esm4/formid.hpp create mode 100644 components/esm4/grid.hpp create mode 100644 components/esm4/grouptype.hpp create mode 100644 components/esm4/inventory.hpp create mode 100644 components/esm4/lighting.hpp create mode 100644 components/esm4/loadachr.cpp create mode 100644 components/esm4/loadachr.hpp create mode 100644 components/esm4/loadacre.cpp create mode 100644 components/esm4/loadacre.hpp create mode 100644 components/esm4/loadacti.cpp create mode 100644 components/esm4/loadacti.hpp create mode 100644 components/esm4/loadalch.cpp create mode 100644 components/esm4/loadalch.hpp create mode 100644 components/esm4/loadaloc.cpp create mode 100644 components/esm4/loadaloc.hpp create mode 100644 components/esm4/loadammo.cpp create mode 100644 components/esm4/loadammo.hpp create mode 100644 components/esm4/loadanio.cpp create mode 100644 components/esm4/loadanio.hpp create mode 100644 components/esm4/loadappa.cpp create mode 100644 components/esm4/loadappa.hpp create mode 100644 components/esm4/loadarma.cpp create mode 100644 components/esm4/loadarma.hpp create mode 100644 components/esm4/loadarmo.cpp create mode 100644 components/esm4/loadarmo.hpp create mode 100644 components/esm4/loadaspc.cpp create mode 100644 components/esm4/loadaspc.hpp create mode 100644 components/esm4/loadbook.cpp create mode 100644 components/esm4/loadbook.hpp create mode 100644 components/esm4/loadbptd.cpp create mode 100644 components/esm4/loadbptd.hpp create mode 100644 components/esm4/loadcell.cpp create mode 100644 components/esm4/loadcell.hpp create mode 100644 components/esm4/loadclas.cpp create mode 100644 components/esm4/loadclas.hpp create mode 100644 components/esm4/loadclfm.cpp create mode 100644 components/esm4/loadclfm.hpp create mode 100644 components/esm4/loadclot.cpp create mode 100644 components/esm4/loadclot.hpp create mode 100644 components/esm4/loadcont.cpp create mode 100644 components/esm4/loadcont.hpp create mode 100644 components/esm4/loadcrea.cpp create mode 100644 components/esm4/loadcrea.hpp create mode 100644 components/esm4/loaddial.cpp create mode 100644 components/esm4/loaddial.hpp create mode 100644 components/esm4/loaddobj.cpp create mode 100644 components/esm4/loaddobj.hpp create mode 100644 components/esm4/loaddoor.cpp create mode 100644 components/esm4/loaddoor.hpp create mode 100644 components/esm4/loadeyes.cpp create mode 100644 components/esm4/loadeyes.hpp create mode 100644 components/esm4/loadflor.cpp create mode 100644 components/esm4/loadflor.hpp create mode 100644 components/esm4/loadflst.cpp create mode 100644 components/esm4/loadflst.hpp create mode 100644 components/esm4/loadfurn.cpp create mode 100644 components/esm4/loadfurn.hpp create mode 100644 components/esm4/loadglob.cpp create mode 100644 components/esm4/loadglob.hpp create mode 100644 components/esm4/loadgmst.cpp create mode 100644 components/esm4/loadgmst.hpp create mode 100644 components/esm4/loadgras.cpp create mode 100644 components/esm4/loadgras.hpp create mode 100644 components/esm4/loadgrup.hpp create mode 100644 components/esm4/loadhair.cpp create mode 100644 components/esm4/loadhair.hpp create mode 100644 components/esm4/loadhdpt.cpp create mode 100644 components/esm4/loadhdpt.hpp create mode 100644 components/esm4/loadidle.cpp create mode 100644 components/esm4/loadidle.hpp create mode 100644 components/esm4/loadidlm.cpp create mode 100644 components/esm4/loadidlm.hpp create mode 100644 components/esm4/loadimod.cpp create mode 100644 components/esm4/loadimod.hpp create mode 100644 components/esm4/loadinfo.cpp create mode 100644 components/esm4/loadinfo.hpp create mode 100644 components/esm4/loadingr.cpp create mode 100644 components/esm4/loadingr.hpp create mode 100644 components/esm4/loadkeym.cpp create mode 100644 components/esm4/loadkeym.hpp create mode 100644 components/esm4/loadland.cpp create mode 100644 components/esm4/loadland.hpp create mode 100644 components/esm4/loadlgtm.cpp create mode 100644 components/esm4/loadlgtm.hpp create mode 100644 components/esm4/loadligh.cpp create mode 100644 components/esm4/loadligh.hpp create mode 100644 components/esm4/loadltex.cpp create mode 100644 components/esm4/loadltex.hpp create mode 100644 components/esm4/loadlvlc.cpp create mode 100644 components/esm4/loadlvlc.hpp create mode 100644 components/esm4/loadlvli.cpp create mode 100644 components/esm4/loadlvli.hpp create mode 100644 components/esm4/loadlvln.cpp create mode 100644 components/esm4/loadlvln.hpp create mode 100644 components/esm4/loadmato.cpp create mode 100644 components/esm4/loadmato.hpp create mode 100644 components/esm4/loadmisc.cpp create mode 100644 components/esm4/loadmisc.hpp create mode 100644 components/esm4/loadmset.cpp create mode 100644 components/esm4/loadmset.hpp create mode 100644 components/esm4/loadmstt.cpp create mode 100644 components/esm4/loadmstt.hpp create mode 100644 components/esm4/loadmusc.cpp create mode 100644 components/esm4/loadmusc.hpp create mode 100644 components/esm4/loadnavi.cpp create mode 100644 components/esm4/loadnavi.hpp create mode 100644 components/esm4/loadnavm.cpp create mode 100644 components/esm4/loadnavm.hpp create mode 100644 components/esm4/loadnote.cpp create mode 100644 components/esm4/loadnote.hpp create mode 100644 components/esm4/loadnpc.cpp create mode 100644 components/esm4/loadnpc.hpp create mode 100644 components/esm4/loadotft.cpp create mode 100644 components/esm4/loadotft.hpp create mode 100644 components/esm4/loadpack.cpp create mode 100644 components/esm4/loadpack.hpp create mode 100644 components/esm4/loadpgrd.cpp create mode 100644 components/esm4/loadpgrd.hpp create mode 100644 components/esm4/loadpgre.cpp create mode 100644 components/esm4/loadpgre.hpp create mode 100644 components/esm4/loadpwat.cpp create mode 100644 components/esm4/loadpwat.hpp create mode 100644 components/esm4/loadqust.cpp create mode 100644 components/esm4/loadqust.hpp create mode 100644 components/esm4/loadrace.cpp create mode 100644 components/esm4/loadrace.hpp create mode 100644 components/esm4/loadrefr.cpp create mode 100644 components/esm4/loadrefr.hpp create mode 100644 components/esm4/loadregn.cpp create mode 100644 components/esm4/loadregn.hpp create mode 100644 components/esm4/loadroad.cpp create mode 100644 components/esm4/loadroad.hpp create mode 100644 components/esm4/loadsbsp.cpp create mode 100644 components/esm4/loadsbsp.hpp create mode 100644 components/esm4/loadscol.cpp create mode 100644 components/esm4/loadscol.hpp create mode 100644 components/esm4/loadscpt.cpp create mode 100644 components/esm4/loadscpt.hpp create mode 100644 components/esm4/loadscrl.cpp create mode 100644 components/esm4/loadscrl.hpp create mode 100644 components/esm4/loadsgst.cpp create mode 100644 components/esm4/loadsgst.hpp create mode 100644 components/esm4/loadslgm.cpp create mode 100644 components/esm4/loadslgm.hpp create mode 100644 components/esm4/loadsndr.cpp create mode 100644 components/esm4/loadsndr.hpp create mode 100644 components/esm4/loadsoun.cpp create mode 100644 components/esm4/loadsoun.hpp create mode 100644 components/esm4/loadstat.cpp create mode 100644 components/esm4/loadstat.hpp create mode 100644 components/esm4/loadtact.cpp create mode 100644 components/esm4/loadtact.hpp create mode 100644 components/esm4/loadterm.cpp create mode 100644 components/esm4/loadterm.hpp create mode 100644 components/esm4/loadtes4.cpp create mode 100644 components/esm4/loadtes4.hpp create mode 100644 components/esm4/loadtree.cpp create mode 100644 components/esm4/loadtree.hpp create mode 100644 components/esm4/loadtxst.cpp create mode 100644 components/esm4/loadtxst.hpp create mode 100644 components/esm4/loadweap.cpp create mode 100644 components/esm4/loadweap.hpp create mode 100644 components/esm4/loadwrld.cpp create mode 100644 components/esm4/loadwrld.hpp create mode 100644 components/esm4/magiceffectid.hpp create mode 100644 components/esm4/reader.cpp create mode 100644 components/esm4/reader.hpp create mode 100644 components/esm4/readerutils.hpp create mode 100644 components/esm4/records.hpp create mode 100644 components/esm4/reference.hpp create mode 100644 components/esm4/script.hpp create mode 100644 components/esm4/typetraits.hpp create mode 100644 components/esm4/vertex.hpp create mode 100644 components/esmloader/esmdata.cpp create mode 100644 components/esmloader/esmdata.hpp create mode 100644 components/esmloader/lessbyid.hpp create mode 100644 components/esmloader/load.cpp create mode 100644 components/esmloader/load.hpp create mode 100644 components/esmloader/record.hpp create mode 100644 components/files/configfileparser.cpp create mode 100644 components/files/configfileparser.hpp create mode 100644 components/files/constrainedfilestreambuf.cpp create mode 100644 components/files/constrainedfilestreambuf.hpp create mode 100644 components/files/conversion.cpp create mode 100644 components/files/conversion.hpp delete mode 100644 components/files/escape.cpp delete mode 100644 components/files/escape.hpp create mode 100644 components/files/hash.cpp create mode 100644 components/files/hash.hpp create mode 100644 components/files/istreamptr.hpp delete mode 100644 components/files/lowlevelfile.cpp delete mode 100644 components/files/lowlevelfile.hpp create mode 100644 components/files/openfile.cpp create mode 100644 components/files/openfile.hpp create mode 100644 components/files/qtconfigpath.hpp create mode 100644 components/files/qtconversion.cpp create mode 100644 components/files/qtconversion.hpp create mode 100644 components/files/streamwithbuffer.hpp create mode 100644 components/fx/lexer.cpp create mode 100644 components/fx/lexer.hpp create mode 100644 components/fx/lexer_types.hpp create mode 100644 components/fx/parse_constants.hpp create mode 100644 components/fx/pass.cpp create mode 100644 components/fx/pass.hpp create mode 100644 components/fx/stateupdater.cpp create mode 100644 components/fx/stateupdater.hpp create mode 100644 components/fx/technique.cpp create mode 100644 components/fx/technique.hpp create mode 100644 components/fx/types.hpp create mode 100644 components/fx/widgets.cpp create mode 100644 components/fx/widgets.hpp create mode 100644 components/interpreter/program.hpp create mode 100644 components/l10n/manager.cpp create mode 100644 components/l10n/manager.hpp create mode 100644 components/l10n/messagebundles.cpp create mode 100644 components/l10n/messagebundles.hpp create mode 100644 components/loadinglistener/reporter.cpp create mode 100644 components/loadinglistener/reporter.hpp create mode 100644 components/lua/asyncpackage.cpp create mode 100644 components/lua/asyncpackage.hpp create mode 100644 components/lua/configuration.cpp create mode 100644 components/lua/configuration.hpp create mode 100644 components/lua/l10n.cpp create mode 100644 components/lua/l10n.hpp create mode 100644 components/lua/luastate.cpp create mode 100644 components/lua/luastate.hpp create mode 100644 components/lua/scriptscontainer.cpp create mode 100644 components/lua/scriptscontainer.hpp create mode 100644 components/lua/serialization.cpp create mode 100644 components/lua/serialization.hpp create mode 100644 components/lua/shapes/box.cpp create mode 100644 components/lua/shapes/box.hpp create mode 100644 components/lua/storage.cpp create mode 100644 components/lua/storage.hpp create mode 100644 components/lua/utilpackage.cpp create mode 100644 components/lua/utilpackage.hpp create mode 100644 components/lua_ui/adapter.cpp create mode 100644 components/lua_ui/adapter.hpp create mode 100644 components/lua_ui/alignment.cpp create mode 100644 components/lua_ui/alignment.hpp create mode 100644 components/lua_ui/container.cpp create mode 100644 components/lua_ui/container.hpp create mode 100644 components/lua_ui/content.cpp create mode 100644 components/lua_ui/content.hpp create mode 100644 components/lua_ui/content.lua create mode 100644 components/lua_ui/element.cpp create mode 100644 components/lua_ui/element.hpp create mode 100644 components/lua_ui/flex.cpp create mode 100644 components/lua_ui/flex.hpp create mode 100644 components/lua_ui/image.cpp create mode 100644 components/lua_ui/image.hpp create mode 100644 components/lua_ui/layers.cpp create mode 100644 components/lua_ui/layers.hpp create mode 100644 components/lua_ui/properties.hpp create mode 100644 components/lua_ui/registerscriptsettings.hpp create mode 100644 components/lua_ui/resources.cpp create mode 100644 components/lua_ui/resources.hpp create mode 100644 components/lua_ui/scriptsettings.cpp create mode 100644 components/lua_ui/scriptsettings.hpp create mode 100644 components/lua_ui/text.cpp create mode 100644 components/lua_ui/text.hpp create mode 100644 components/lua_ui/textedit.cpp create mode 100644 components/lua_ui/textedit.hpp create mode 100644 components/lua_ui/util.cpp create mode 100644 components/lua_ui/util.hpp create mode 100644 components/lua_ui/widget.cpp create mode 100644 components/lua_ui/widget.hpp create mode 100644 components/lua_ui/window.cpp create mode 100644 components/lua_ui/window.hpp create mode 100644 components/misc/color.cpp create mode 100644 components/misc/color.hpp create mode 100644 components/misc/compression.cpp create mode 100644 components/misc/compression.hpp create mode 100644 components/misc/math.hpp create mode 100644 components/misc/notnullptr.hpp create mode 100644 components/misc/osguservalues.cpp create mode 100644 components/misc/osguservalues.hpp create mode 100644 components/misc/pathhelpers.hpp create mode 100644 components/misc/progressreporter.hpp delete mode 100644 components/misc/stringops.hpp create mode 100644 components/misc/strings/algorithm.hpp create mode 100644 components/misc/strings/conversion.hpp create mode 100644 components/misc/strings/format.hpp create mode 100644 components/misc/strings/lower.hpp create mode 100644 components/misc/strongtypedef.hpp create mode 100644 components/misc/timeconvert.hpp create mode 100644 components/misc/tuplehelpers.hpp create mode 100644 components/misc/tuplemeta.hpp create mode 100644 components/misc/typetraits.hpp create mode 100644 components/misc/utf8qtextstream.hpp delete mode 100644 components/myguiplatform/myguicompat.h create mode 100644 components/navmeshtool/protocol.cpp create mode 100644 components/navmeshtool/protocol.hpp create mode 100644 components/nif/base.cpp create mode 100644 components/nif/exception.hpp create mode 100644 components/nif/parent.hpp create mode 100644 components/nif/physics.cpp create mode 100644 components/nif/physics.hpp create mode 100644 components/platform/file.hpp create mode 100644 components/platform/file.posix.cpp create mode 100644 components/platform/file.stdio.cpp create mode 100644 components/platform/file.win32.cpp create mode 100644 components/platform/platform.cpp create mode 100644 components/platform/platform.hpp create mode 100644 components/resource/errormarker.cpp create mode 100644 components/resource/errormarker.hpp create mode 100644 components/resource/foreachbulletobject.cpp create mode 100644 components/resource/foreachbulletobject.hpp create mode 100644 components/sceneutil/clearcolor.hpp create mode 100644 components/sceneutil/color.cpp create mode 100644 components/sceneutil/color.hpp create mode 100644 components/sceneutil/cullsafeboundsvisitor.hpp create mode 100644 components/sceneutil/depth.cpp create mode 100644 components/sceneutil/depth.hpp create mode 100644 components/sceneutil/extradata.cpp create mode 100644 components/sceneutil/extradata.hpp create mode 100644 components/sceneutil/lightcommon.cpp create mode 100644 components/sceneutil/lightcommon.hpp create mode 100644 components/sceneutil/nodecallback.hpp create mode 100644 components/sceneutil/riggeometryosgaextension.cpp create mode 100644 components/sceneutil/riggeometryosgaextension.hpp create mode 100644 components/sceneutil/rtt.cpp create mode 100644 components/sceneutil/rtt.hpp create mode 100644 components/sceneutil/screencapture.cpp create mode 100644 components/sceneutil/screencapture.hpp create mode 100644 components/sdlutil/sdlmappings.cpp rename {apps/openmw/mwinput => components/sdlutil}/sdlmappings.hpp (50%) create mode 100644 components/serialization/binaryreader.hpp create mode 100644 components/serialization/binarywriter.hpp create mode 100644 components/serialization/format.hpp create mode 100644 components/serialization/osgyaml.hpp create mode 100644 components/serialization/sizeaccumulator.hpp create mode 100644 components/settings/categories/camera.hpp create mode 100644 components/settings/categories/cells.hpp create mode 100644 components/settings/categories/fog.hpp create mode 100644 components/settings/categories/game.hpp create mode 100644 components/settings/categories/general.hpp create mode 100644 components/settings/categories/groundcover.hpp create mode 100644 components/settings/categories/gui.hpp create mode 100644 components/settings/categories/hud.hpp create mode 100644 components/settings/categories/input.hpp create mode 100644 components/settings/categories/lua.hpp create mode 100644 components/settings/categories/map.hpp create mode 100644 components/settings/categories/models.hpp create mode 100644 components/settings/categories/navigator.hpp create mode 100644 components/settings/categories/physics.hpp create mode 100644 components/settings/categories/postprocessing.hpp create mode 100644 components/settings/categories/saves.hpp create mode 100644 components/settings/categories/shaders.hpp create mode 100644 components/settings/categories/shadows.hpp create mode 100644 components/settings/categories/sound.hpp create mode 100644 components/settings/categories/stereo.hpp create mode 100644 components/settings/categories/stereoview.hpp create mode 100644 components/settings/categories/terrain.hpp create mode 100644 components/settings/categories/video.hpp create mode 100644 components/settings/categories/water.hpp create mode 100644 components/settings/categories/windows.hpp create mode 100644 components/settings/sanitizer.hpp create mode 100644 components/settings/sanitizerimpl.cpp create mode 100644 components/settings/sanitizerimpl.hpp create mode 100644 components/settings/settingvalue.hpp create mode 100644 components/settings/shadermanager.hpp create mode 100644 components/settings/values.cpp create mode 100644 components/settings/values.hpp create mode 100644 components/sqlite3/db.cpp create mode 100644 components/sqlite3/db.hpp create mode 100644 components/sqlite3/request.hpp create mode 100644 components/sqlite3/statement.cpp create mode 100644 components/sqlite3/statement.hpp create mode 100644 components/sqlite3/transaction.cpp create mode 100644 components/sqlite3/transaction.hpp create mode 100644 components/sqlite3/types.hpp create mode 100644 components/std140/ubo.hpp create mode 100644 components/stereo/frustum.cpp create mode 100644 components/stereo/frustum.hpp create mode 100644 components/stereo/multiview.cpp create mode 100644 components/stereo/multiview.hpp create mode 100644 components/stereo/stereomanager.cpp create mode 100644 components/stereo/stereomanager.hpp create mode 100644 components/stereo/types.cpp create mode 100644 components/stereo/types.hpp create mode 100644 components/terrain/heightcull.hpp create mode 100644 components/terrain/view.hpp delete mode 100644 components/to_utf8/tests/.gitignore delete mode 100644 components/to_utf8/tests/output/to_utf8_test.out delete mode 100755 components/to_utf8/tests/test.sh delete mode 100644 components/to_utf8/tests/to_utf8_test.cpp delete mode 100644 components/vfs/bsaarchive.cpp create mode 100644 components/vfs/pathutil.hpp create mode 100644 components/windows.hpp create mode 100644 docker/Dockerfile.ubuntu create mode 100644 docker/README.md create mode 100755 docker/build.sh create mode 100644 docs/Dockerfile create mode 100644 docs/README.md create mode 100755 docs/build_docs.sh create mode 100755 docs/prepare_docker_image.sh create mode 100644 docs/source/.gitattributes create mode 100644 docs/source/_static/luadoc.css create mode 100755 docs/source/generate_luadoc.sh create mode 100755 docs/source/install_luadocumentor_in_docker.sh create mode 100755 docs/source/luadoc_data_paths.sh create mode 100644 docs/source/manuals/openmw-cs/cell-view.rst create mode 100644 docs/source/manuals/openmw-cs/records-drag-and-drop.rst create mode 100644 docs/source/manuals/openmw-cs/tables-assets.rst create mode 100644 docs/source/manuals/openmw-cs/tables-characters.rst create mode 100644 docs/source/manuals/openmw-cs/tables-file.rst create mode 100644 docs/source/manuals/openmw-cs/tables-mechanics.rst create mode 100644 docs/source/manuals/openmw-cs/tables-world.rst create mode 100644 docs/source/reference/lua-scripting/aipackages.rst create mode 100644 docs/source/reference/lua-scripting/api.rst create mode 100644 docs/source/reference/lua-scripting/engine_handlers.rst create mode 100644 docs/source/reference/lua-scripting/events.rst create mode 100644 docs/source/reference/lua-scripting/index.rst create mode 100644 docs/source/reference/lua-scripting/interface_activation.rst create mode 100644 docs/source/reference/lua-scripting/interface_ai.rst create mode 100644 docs/source/reference/lua-scripting/interface_camera.rst create mode 100644 docs/source/reference/lua-scripting/interface_controls.rst create mode 100644 docs/source/reference/lua-scripting/interface_mwui.rst create mode 100644 docs/source/reference/lua-scripting/interface_settings.rst create mode 100644 docs/source/reference/lua-scripting/iterables.rst create mode 100644 docs/source/reference/lua-scripting/openmw_async.rst create mode 100644 docs/source/reference/lua-scripting/openmw_aux_calendar.rst create mode 100644 docs/source/reference/lua-scripting/openmw_aux_time.rst create mode 100644 docs/source/reference/lua-scripting/openmw_aux_ui.rst create mode 100644 docs/source/reference/lua-scripting/openmw_aux_util.rst create mode 100644 docs/source/reference/lua-scripting/openmw_camera.rst create mode 100644 docs/source/reference/lua-scripting/openmw_core.rst create mode 100644 docs/source/reference/lua-scripting/openmw_debug.rst create mode 100644 docs/source/reference/lua-scripting/openmw_input.rst create mode 100644 docs/source/reference/lua-scripting/openmw_nearby.rst create mode 100644 docs/source/reference/lua-scripting/openmw_postprocessing.rst create mode 100644 docs/source/reference/lua-scripting/openmw_self.rst create mode 100644 docs/source/reference/lua-scripting/openmw_storage.rst create mode 100644 docs/source/reference/lua-scripting/openmw_types.rst create mode 100644 docs/source/reference/lua-scripting/openmw_ui.rst create mode 100644 docs/source/reference/lua-scripting/openmw_util.rst create mode 100644 docs/source/reference/lua-scripting/openmw_world.rst create mode 100644 docs/source/reference/lua-scripting/overview.rst create mode 100644 docs/source/reference/lua-scripting/setting_renderers.rst create mode 100644 docs/source/reference/lua-scripting/tables/aux_packages.rst create mode 100644 docs/source/reference/lua-scripting/tables/packages.rst create mode 100644 docs/source/reference/lua-scripting/teal.rst create mode 100644 docs/source/reference/lua-scripting/user_interface.rst create mode 100644 docs/source/reference/lua-scripting/widgets/container.rst create mode 100644 docs/source/reference/lua-scripting/widgets/flex.rst create mode 100644 docs/source/reference/lua-scripting/widgets/image.rst create mode 100644 docs/source/reference/lua-scripting/widgets/text.rst create mode 100644 docs/source/reference/lua-scripting/widgets/textedit.rst create mode 100644 docs/source/reference/lua-scripting/widgets/widget.rst create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature.rst create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst delete mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada.rst create mode 100644 docs/source/reference/modding/custom-shader-effects.rst create mode 100644 docs/source/reference/modding/doors-and-teleports.rst create mode 100644 docs/source/reference/modding/localisation.rst create mode 100644 docs/source/reference/modding/music.rst create mode 100644 docs/source/reference/modding/settings/lua.rst create mode 100644 docs/source/reference/modding/settings/postprocessing.rst create mode 100644 docs/source/reference/modding/settings/stereo.rst create mode 100644 docs/source/reference/modding/settings/stereoview.rst create mode 100644 docs/source/reference/modding/sound-effects.rst create mode 100644 docs/source/reference/postprocessing/index.rst create mode 100644 docs/source/reference/postprocessing/lua.rst create mode 100644 docs/source/reference/postprocessing/omwfx.rst create mode 100644 docs/source/reference/postprocessing/overview.rst create mode 100644 docs/tlconfig.lua create mode 100644 extern/icufilters.json create mode 100644 extern/osgQt/CompositeOsgRenderer.cpp create mode 100644 extern/osgQt/CompositeOsgRenderer.hpp delete mode 100644 extern/osgQt/GraphicsWindowQt delete mode 100644 extern/osgQt/GraphicsWindowQt.cpp create mode 100644 extern/osgQt/osgQOpenGLWidget.cpp create mode 100644 extern/osgQt/osgQOpenGLWidget.hpp create mode 100644 extern/smhasher/CMakeLists.txt create mode 100644 extern/smhasher/MurmurHash3.cpp create mode 100644 extern/smhasher/MurmurHash3.h create mode 100644 extern/sol3/README.md create mode 100644 extern/sol3/sol/as_args.hpp create mode 100644 extern/sol3/sol/as_returns.hpp create mode 100644 extern/sol3/sol/assert.hpp create mode 100644 extern/sol3/sol/base_traits.hpp create mode 100644 extern/sol3/sol/bind_traits.hpp create mode 100644 extern/sol3/sol/bytecode.hpp create mode 100644 extern/sol3/sol/call.hpp create mode 100644 extern/sol3/sol/compatibility.hpp create mode 100644 extern/sol3/sol/compatibility/compat-5.3.c.h create mode 100644 extern/sol3/sol/compatibility/compat-5.3.h create mode 100644 extern/sol3/sol/compatibility/compat-5.4.h create mode 100644 extern/sol3/sol/compatibility/lua_version.hpp create mode 100644 extern/sol3/sol/coroutine.hpp create mode 100644 extern/sol3/sol/debug.hpp create mode 100644 extern/sol3/sol/demangle.hpp create mode 100644 extern/sol3/sol/deprecate.hpp create mode 100644 extern/sol3/sol/detail/build_version.hpp create mode 100644 extern/sol3/sol/dump_handler.hpp create mode 100644 extern/sol3/sol/ebco.hpp create mode 100644 extern/sol3/sol/environment.hpp create mode 100644 extern/sol3/sol/epilogue.hpp create mode 100644 extern/sol3/sol/error.hpp create mode 100644 extern/sol3/sol/error_handler.hpp create mode 100644 extern/sol3/sol/forward.hpp create mode 100644 extern/sol3/sol/forward_detail.hpp create mode 100644 extern/sol3/sol/function.hpp create mode 100644 extern/sol3/sol/function_result.hpp create mode 100644 extern/sol3/sol/function_types.hpp create mode 100644 extern/sol3/sol/function_types_core.hpp create mode 100644 extern/sol3/sol/function_types_overloaded.hpp create mode 100644 extern/sol3/sol/function_types_stateful.hpp create mode 100644 extern/sol3/sol/function_types_stateless.hpp create mode 100644 extern/sol3/sol/function_types_templated.hpp create mode 100644 extern/sol3/sol/in_place.hpp create mode 100644 extern/sol3/sol/inheritance.hpp create mode 100644 extern/sol3/sol/load_result.hpp create mode 100644 extern/sol3/sol/lua_table.hpp create mode 100644 extern/sol3/sol/lua_value.hpp create mode 100644 extern/sol3/sol/make_reference.hpp create mode 100644 extern/sol3/sol/metatable.hpp create mode 100644 extern/sol3/sol/object.hpp create mode 100644 extern/sol3/sol/object_base.hpp create mode 100644 extern/sol3/sol/optional.hpp create mode 100644 extern/sol3/sol/optional_implementation.hpp create mode 100644 extern/sol3/sol/overload.hpp create mode 100644 extern/sol3/sol/packaged_coroutine.hpp create mode 100644 extern/sol3/sol/pairs_iterator.hpp create mode 100644 extern/sol3/sol/pointer_like.hpp create mode 100644 extern/sol3/sol/policies.hpp create mode 100644 extern/sol3/sol/prologue.hpp create mode 100644 extern/sol3/sol/property.hpp create mode 100644 extern/sol3/sol/protect.hpp create mode 100644 extern/sol3/sol/protected_function.hpp create mode 100644 extern/sol3/sol/protected_function_result.hpp create mode 100644 extern/sol3/sol/protected_handler.hpp create mode 100644 extern/sol3/sol/proxy_base.hpp create mode 100644 extern/sol3/sol/raii.hpp create mode 100644 extern/sol3/sol/reference.hpp create mode 100644 extern/sol3/sol/resolve.hpp create mode 100644 extern/sol3/sol/sol.hpp create mode 100644 extern/sol3/sol/stack.hpp create mode 100644 extern/sol3/sol/stack/detail/pairs.hpp create mode 100644 extern/sol3/sol/stack_check.hpp create mode 100644 extern/sol3/sol/stack_check_get.hpp create mode 100644 extern/sol3/sol/stack_check_get_qualified.hpp create mode 100644 extern/sol3/sol/stack_check_get_unqualified.hpp create mode 100644 extern/sol3/sol/stack_check_qualified.hpp create mode 100644 extern/sol3/sol/stack_check_unqualified.hpp create mode 100644 extern/sol3/sol/stack_core.hpp create mode 100644 extern/sol3/sol/stack_field.hpp create mode 100644 extern/sol3/sol/stack_get.hpp create mode 100644 extern/sol3/sol/stack_get_qualified.hpp create mode 100644 extern/sol3/sol/stack_get_unqualified.hpp create mode 100644 extern/sol3/sol/stack_guard.hpp create mode 100644 extern/sol3/sol/stack_iterator.hpp create mode 100644 extern/sol3/sol/stack_pop.hpp create mode 100644 extern/sol3/sol/stack_probe.hpp create mode 100644 extern/sol3/sol/stack_proxy.hpp create mode 100644 extern/sol3/sol/stack_proxy_base.hpp create mode 100644 extern/sol3/sol/stack_push.hpp create mode 100644 extern/sol3/sol/stack_reference.hpp create mode 100644 extern/sol3/sol/state.hpp create mode 100644 extern/sol3/sol/state_handling.hpp create mode 100644 extern/sol3/sol/state_view.hpp create mode 100644 extern/sol3/sol/string_view.hpp create mode 100644 extern/sol3/sol/table.hpp create mode 100644 extern/sol3/sol/table_core.hpp create mode 100644 extern/sol3/sol/table_iterator.hpp create mode 100644 extern/sol3/sol/table_proxy.hpp create mode 100644 extern/sol3/sol/thread.hpp create mode 100644 extern/sol3/sol/tie.hpp create mode 100644 extern/sol3/sol/traits.hpp create mode 100644 extern/sol3/sol/trampoline.hpp create mode 100644 extern/sol3/sol/tuple.hpp create mode 100644 extern/sol3/sol/types.hpp create mode 100644 extern/sol3/sol/unicode.hpp create mode 100644 extern/sol3/sol/unique_usertype_traits.hpp create mode 100644 extern/sol3/sol/unsafe_function.hpp create mode 100644 extern/sol3/sol/unsafe_function_result.hpp create mode 100644 extern/sol3/sol/userdata.hpp create mode 100644 extern/sol3/sol/usertype.hpp create mode 100644 extern/sol3/sol/usertype_container.hpp create mode 100644 extern/sol3/sol/usertype_container_launch.hpp create mode 100644 extern/sol3/sol/usertype_core.hpp create mode 100644 extern/sol3/sol/usertype_proxy.hpp create mode 100644 extern/sol3/sol/usertype_storage.hpp create mode 100644 extern/sol3/sol/usertype_traits.hpp create mode 100644 extern/sol3/sol/variadic_args.hpp create mode 100644 extern/sol3/sol/variadic_results.hpp create mode 100644 extern/sol3/sol/version.hpp create mode 100644 extern/sol3/sol/wrapper.hpp create mode 100644 extern/sol_config/sol/config.hpp create mode 100644 files/data-mw/CMakeLists.txt create mode 100644 files/data-mw/l10n/Calendar/de.yaml create mode 100644 files/data-mw/l10n/Calendar/en.yaml create mode 100644 files/data-mw/l10n/Calendar/fr.yaml create mode 100644 files/data-mw/l10n/Calendar/gmst.yaml create mode 100644 files/data-mw/l10n/Calendar/ru.yaml create mode 100644 files/data-mw/l10n/Calendar/sv.yaml create mode 100644 files/data-mw/l10n/Interface/gmst.yaml create mode 100644 files/data-mw/l10n/OMWEngine/gmst.yaml create mode 100644 files/data-mw/openmw_aux/calendarconfig.lua create mode 100644 files/data/CMakeLists.txt create mode 100644 files/data/builtin.omwscripts rename files/{mygui => data/fonts}/DejaVuFontLicense.txt (100%) rename files/{mygui/openmw_font.xml => data/fonts/DejaVuLGCSansMono.omwfont} (90%) rename files/{mygui => data/fonts}/DejaVuLGCSansMono.ttf (100%) create mode 100644 files/data/fonts/DemonicLetters.omwfont create mode 100644 files/data/fonts/DemonicLetters.ttf create mode 100644 files/data/fonts/DemonicLettersFontLicense.txt create mode 100644 files/data/fonts/MysticCards.omwfont create mode 100644 files/data/fonts/MysticCards.ttf create mode 100644 files/data/fonts/MysticCardsFontLicense.txt create mode 100644 files/data/l10n/Calendar/en.yaml create mode 100644 files/data/l10n/Interface/de.yaml create mode 100644 files/data/l10n/Interface/en.yaml create mode 100644 files/data/l10n/Interface/fr.yaml create mode 100644 files/data/l10n/Interface/ru.yaml create mode 100644 files/data/l10n/Interface/sv.yaml create mode 100644 files/data/l10n/OMWCamera/de.yaml create mode 100644 files/data/l10n/OMWCamera/en.yaml create mode 100644 files/data/l10n/OMWCamera/fr.yaml create mode 100644 files/data/l10n/OMWCamera/ru.yaml create mode 100644 files/data/l10n/OMWCamera/sv.yaml create mode 100644 files/data/l10n/OMWControls/en.yaml create mode 100644 files/data/l10n/OMWControls/fr.yaml create mode 100644 files/data/l10n/OMWControls/ru.yaml create mode 100644 files/data/l10n/OMWControls/sv.yaml create mode 100644 files/data/l10n/OMWEngine/de.yaml create mode 100644 files/data/l10n/OMWEngine/en.yaml create mode 100644 files/data/l10n/OMWEngine/fr.yaml create mode 100644 files/data/l10n/OMWEngine/ru.yaml create mode 100644 files/data/l10n/OMWEngine/sv.yaml create mode 100644 files/data/l10n/OMWShaders/de.yaml create mode 100644 files/data/l10n/OMWShaders/en.yaml create mode 100644 files/data/l10n/OMWShaders/fr.yaml create mode 100644 files/data/l10n/OMWShaders/ru.yaml create mode 100644 files/data/l10n/OMWShaders/sv.yaml rename files/{ => data}/mygui/OpenMWResourcePlugin.xml (100%) rename files/{ => data}/mygui/RussoOne-Regular.ttf (100%) rename files/{ => data}/mygui/core.skin (100%) rename files/{ => data}/mygui/core.xml (86%) rename files/{ => data}/mygui/core_layouteditor.xml (100%) rename files/{ => data}/mygui/openmw_alchemy_window.layout (98%) rename files/{ => data}/mygui/openmw_book.layout (94%) rename files/{ => data}/mygui/openmw_box.skin.xml (100%) rename files/{ => data}/mygui/openmw_button.skin.xml (100%) rename files/{ => data}/mygui/openmw_chargen_birth.layout (86%) rename files/{ => data}/mygui/openmw_chargen_class.layout (96%) rename files/{ => data}/mygui/openmw_chargen_class_description.layout (86%) rename files/{ => data}/mygui/openmw_chargen_create_class.layout (96%) rename files/{ => data}/mygui/openmw_chargen_generate_class_result.layout (89%) rename files/{ => data}/mygui/openmw_chargen_race.layout (95%) create mode 100644 files/data/mygui/openmw_chargen_review.layout create mode 100644 files/data/mygui/openmw_chargen_select_attribute.layout create mode 100644 files/data/mygui/openmw_chargen_select_skill.layout rename files/{ => data}/mygui/openmw_chargen_select_specialization.layout (89%) rename files/{ => data}/mygui/openmw_companion_window.layout (94%) rename files/{ => data}/mygui/openmw_confirmation_dialog.layout (80%) rename files/{ => data}/mygui/openmw_console.layout (57%) rename files/{ => data}/mygui/openmw_console.skin.xml (100%) rename files/{ => data}/mygui/openmw_container_window.layout (94%) rename files/{ => data}/mygui/openmw_count_window.layout (79%) rename files/{ => data}/mygui/openmw_debug_window.layout (73%) rename files/{ => data}/mygui/openmw_debug_window.skin.xml (100%) rename files/{ => data}/mygui/openmw_dialogue_window.layout (100%) rename files/{ => data}/mygui/openmw_dialogue_window.skin.xml (100%) rename files/{ => data}/mygui/openmw_edit.skin.xml (100%) rename files/{ => data}/mygui/openmw_edit_effect.layout (87%) rename files/{ => data}/mygui/openmw_edit_note.layout (81%) rename files/{ => data}/mygui/openmw_enchanting_dialog.layout (99%) rename files/{ => data}/mygui/openmw_hud.layout (97%) rename files/{ => data}/mygui/openmw_hud_box.skin.xml (100%) rename files/{ => data}/mygui/openmw_hud_energybar.skin.xml (100%) rename files/{ => data}/mygui/openmw_infobox.layout (83%) rename files/{ => data}/mygui/openmw_interactive_messagebox.layout (86%) rename files/{ => data}/mygui/openmw_interactive_messagebox_notransp.layout (85%) rename files/{ => data}/mygui/openmw_inventory_window.layout (100%) rename files/{ => data}/mygui/openmw_itemselection_dialog.layout (82%) rename files/{ => data}/mygui/openmw_jail_screen.layout (100%) rename files/{ => data}/mygui/openmw_journal.layout (97%) rename files/{ => data}/mygui/openmw_journal.skin.xml (91%) rename files/{ => data}/mygui/openmw_layers.xml (63%) create mode 100644 files/data/mygui/openmw_levelup_dialog.layout rename files/{ => data}/mygui/openmw_list.skin.xml (97%) rename files/{ => data}/mygui/openmw_loading_screen.layout (71%) create mode 100644 files/data/mygui/openmw_lua.xml rename files/{ => data}/mygui/openmw_magicselection_dialog.layout (84%) rename files/{ => data}/mygui/openmw_mainmenu.layout (100%) rename files/{ => data}/mygui/openmw_mainmenu.skin.xml (100%) rename files/{ => data}/mygui/openmw_map_window.layout (100%) rename files/{ => data}/mygui/openmw_map_window.skin.xml (100%) rename files/{ => data}/mygui/openmw_merchantrepair.layout (95%) rename files/{ => data}/mygui/openmw_messagebox.layout (100%) rename files/{ => data}/mygui/openmw_persuasion_dialog.layout (83%) rename files/{ => data}/mygui/openmw_pointer.xml (100%) create mode 100644 files/data/mygui/openmw_postprocessor_hud.layout create mode 100644 files/data/mygui/openmw_postprocessor_hud.skin.xml rename files/{ => data}/mygui/openmw_progress.skin.xml (100%) rename files/{ => data}/mygui/openmw_quickkeys_menu.layout (95%) rename files/{ => data}/mygui/openmw_quickkeys_menu_assign.layout (90%) rename files/{ => data}/mygui/openmw_recharge_dialog.layout (95%) rename files/{ => data}/mygui/openmw_repair.layout (96%) rename files/{ => data}/mygui/openmw_resources.xml (86%) rename files/{ => data}/mygui/openmw_savegame_dialog.layout (78%) rename files/{mygui/openmw_screen_fader_hit.layout => data/mygui/openmw_screen_fader.layout} (53%) rename files/{mygui/openmw_screen_fader.layout => data/mygui/openmw_screen_fader_hit.layout} (54%) rename files/{ => data}/mygui/openmw_scroll.layout (100%) rename files/{ => data}/mygui/openmw_scroll.skin.xml (62%) rename files/{ => data}/mygui/openmw_settings.xml (80%) rename files/{ => data}/mygui/openmw_settings_window.layout (61%) rename files/{ => data}/mygui/openmw_spell_buying_window.layout (95%) rename files/{ => data}/mygui/openmw_spell_window.layout (100%) rename files/{ => data}/mygui/openmw_spellcreation_dialog.layout (98%) create mode 100644 files/data/mygui/openmw_stats_window.layout rename files/{ => data}/mygui/openmw_text.skin.xml (100%) rename files/{ => data}/mygui/openmw_text_input.layout (78%) rename files/{ => data}/mygui/openmw_tooltips.layout (100%) rename files/{ => data}/mygui/openmw_trade_window.layout (98%) rename files/{ => data}/mygui/openmw_trainingwindow.layout (94%) rename files/{ => data}/mygui/openmw_travel_window.layout (95%) rename files/{ => data}/mygui/openmw_wait_dialog.layout (95%) rename files/{ => data}/mygui/openmw_wait_dialog_progressbar.layout (100%) rename files/{ => data}/mygui/openmw_windows.skin.xml (99%) rename files/{ => data}/mygui/skins.xml (84%) rename files/{ => data}/mygui/tes3mp_chat.layout (100%) rename files/{ => data}/mygui/tes3mp_chat.skin.xml (100%) rename files/{ => data}/mygui/tes3mp_dialog_list.layout (100%) rename files/{ => data}/mygui/tes3mp_login.layout (100%) rename files/{ => data}/mygui/tes3mp_login.skin.xml (100%) rename files/{ => data}/mygui/tes3mp_text_input.layout (100%) create mode 100644 files/data/openmw_aux/calendar.lua create mode 100644 files/data/openmw_aux/calendarconfig.lua create mode 100644 files/data/openmw_aux/time.lua create mode 100644 files/data/openmw_aux/ui.lua create mode 100644 files/data/openmw_aux/util.lua create mode 100644 files/data/scripts/omw/activationhandlers.lua create mode 100644 files/data/scripts/omw/ai.lua create mode 100644 files/data/scripts/omw/camera/camera.lua create mode 100644 files/data/scripts/omw/camera/first_person_auto_switch.lua create mode 100644 files/data/scripts/omw/camera/head_bobbing.lua create mode 100644 files/data/scripts/omw/camera/move360.lua create mode 100644 files/data/scripts/omw/camera/settings.lua create mode 100644 files/data/scripts/omw/camera/third_person.lua create mode 100644 files/data/scripts/omw/cellhandlers.lua create mode 100644 files/data/scripts/omw/console/global.lua create mode 100644 files/data/scripts/omw/console/local.lua create mode 100644 files/data/scripts/omw/console/player.lua create mode 100644 files/data/scripts/omw/mechanics/playercontroller.lua create mode 100644 files/data/scripts/omw/mwui/borders.lua create mode 100644 files/data/scripts/omw/mwui/constants.lua create mode 100644 files/data/scripts/omw/mwui/filters.lua create mode 100644 files/data/scripts/omw/mwui/init.lua create mode 100644 files/data/scripts/omw/mwui/space.lua create mode 100644 files/data/scripts/omw/mwui/text.lua create mode 100644 files/data/scripts/omw/mwui/textEdit.lua create mode 100644 files/data/scripts/omw/playercontrols.lua create mode 100644 files/data/scripts/omw/settings/common.lua create mode 100644 files/data/scripts/omw/settings/global.lua create mode 100644 files/data/scripts/omw/settings/player.lua create mode 100644 files/data/scripts/omw/settings/render.lua create mode 100644 files/data/scripts/omw/settings/renderers.lua create mode 100644 files/data/shaders/adjustments.omwfx create mode 100644 files/data/shaders/bloomlinear.omwfx create mode 100644 files/data/shaders/debug.omwfx rename files/{shaders => data/textures/omw}/water_nm.png (100%) rename files/{vfs => data}/textures/omw_menu_scroll_center_h.dds (100%) rename files/{vfs => data}/textures/omw_menu_scroll_center_v.dds (100%) rename files/{vfs => data}/textures/omw_menu_scroll_down.dds (100%) rename files/{vfs => data}/textures/omw_menu_scroll_left.dds (100%) rename files/{vfs => data}/textures/omw_menu_scroll_right.dds (100%) rename files/{vfs => data}/textures/omw_menu_scroll_up.dds (100%) rename files/launcher/{images/clear.png => icons/tango/16x16/edit-clear.png} (100%) delete mode 100644 files/launcher/icons/tango/16x16/go-bottom.png delete mode 100644 files/launcher/icons/tango/16x16/go-down.png delete mode 100644 files/launcher/icons/tango/16x16/go-top.png delete mode 100644 files/launcher/icons/tango/16x16/go-up.png create mode 100644 files/launcher/icons/tango/16x16/view-refresh.png delete mode 100644 files/launcher/icons/tango/48x48/emblem-system.png delete mode 100644 files/launcher/icons/tango/48x48/preferences-system.png delete mode 100644 files/launcher/icons/tango/48x48/video-display.png delete mode 100644 files/launcher/images/down.png mode change 100644 => 100755 files/launcher/images/openmw-header.png delete mode 100644 files/launcher/images/playpage-background.png create mode 100644 files/lua_api/CMakeLists.txt create mode 100644 files/lua_api/README.md create mode 100644 files/lua_api/coroutine.doclua create mode 100644 files/lua_api/global.doclua create mode 100644 files/lua_api/math.doclua create mode 100644 files/lua_api/openmw/async.lua create mode 100644 files/lua_api/openmw/camera.lua create mode 100644 files/lua_api/openmw/core.lua create mode 100644 files/lua_api/openmw/debug.lua create mode 100644 files/lua_api/openmw/input.lua create mode 100644 files/lua_api/openmw/interfaces.lua create mode 100644 files/lua_api/openmw/nearby.lua create mode 100644 files/lua_api/openmw/postprocessing.lua create mode 100644 files/lua_api/openmw/self.lua create mode 100644 files/lua_api/openmw/storage.lua create mode 100644 files/lua_api/openmw/types.lua create mode 100644 files/lua_api/openmw/ui.lua create mode 100644 files/lua_api/openmw/util.lua create mode 100644 files/lua_api/openmw/world.lua create mode 100644 files/lua_api/os.doclua create mode 100644 files/lua_api/string.doclua create mode 100644 files/lua_api/table.doclua delete mode 100644 files/mygui/openmw_chargen_review.layout delete mode 100644 files/mygui/openmw_chargen_select_attribute.layout delete mode 100644 files/mygui/openmw_chargen_select_skill.layout delete mode 100644 files/mygui/openmw_levelup_dialog.layout delete mode 100644 files/mygui/openmw_stats_window.layout delete mode 100644 files/opencs/edit-clone.png delete mode 100644 files/opencs/edit-delete.png delete mode 100644 files/opencs/edit-preview.png delete mode 100644 files/opencs/magicrabbit.png delete mode 100644 files/opencs/map.png delete mode 100644 files/opencs/random-item.png delete mode 100644 files/opencs/raster/GMST.png delete mode 100644 files/opencs/raster/Info.png delete mode 100644 files/opencs/raster/LandTexture.png delete mode 100644 files/opencs/raster/PathGrid.png delete mode 100644 files/opencs/raster/activator.png delete mode 100644 files/opencs/raster/added.png delete mode 100644 files/opencs/raster/apparatus.png delete mode 100644 files/opencs/raster/armor.png delete mode 100644 files/opencs/raster/attribute.png delete mode 100644 files/opencs/raster/base.png delete mode 100644 files/opencs/raster/birthsign.png delete mode 100644 files/opencs/raster/body-part.png delete mode 100644 files/opencs/raster/book.png delete mode 100644 files/opencs/raster/cell.png delete mode 100644 files/opencs/raster/class.png delete mode 100644 files/opencs/raster/clothing.png delete mode 100644 files/opencs/raster/container.png delete mode 100644 files/opencs/raster/creature.png delete mode 100644 files/opencs/raster/dialogoue-info.png delete mode 100644 files/opencs/raster/dialogoue-journal.png delete mode 100644 files/opencs/raster/dialogoue-regular.png delete mode 100644 files/opencs/raster/dialogue-greeting.png delete mode 100644 files/opencs/raster/dialogue-persuasion.png delete mode 100644 files/opencs/raster/dialogue-speech.png delete mode 100644 files/opencs/raster/door.png delete mode 100644 files/opencs/raster/enchantment.png delete mode 100644 files/opencs/raster/faction.png delete mode 100644 files/opencs/raster/filter.png delete mode 100644 files/opencs/raster/globvar.png delete mode 100644 files/opencs/raster/ingredient.png delete mode 100644 files/opencs/raster/land.png delete mode 100644 files/opencs/raster/landpaint.png delete mode 100644 files/opencs/raster/leveled-creature.png delete mode 100644 files/opencs/raster/light.png delete mode 100644 files/opencs/raster/lockpick.png delete mode 100644 files/opencs/raster/magic-effect.png delete mode 100644 files/opencs/raster/magicrabbit.png delete mode 100644 files/opencs/raster/map.png delete mode 100644 files/opencs/raster/miscellaneous.png delete mode 100644 files/opencs/raster/modified.png delete mode 100644 files/opencs/raster/npc.png delete mode 100644 files/opencs/raster/potion.png delete mode 100644 files/opencs/raster/probe.png delete mode 100644 files/opencs/raster/race.png delete mode 100644 files/opencs/raster/random-item.png delete mode 100644 files/opencs/raster/random.png delete mode 100644 files/opencs/raster/removed.png delete mode 100644 files/opencs/raster/repair.png delete mode 100644 files/opencs/raster/scene-exterior-parts/0_backdrop.png delete mode 100644 files/opencs/raster/scene-exterior-parts/1_grid.png delete mode 100644 files/opencs/raster/scene-exterior-parts/2_arrows.png delete mode 100644 files/opencs/raster/scene-exterior-parts/3_cell_marker.png delete mode 100644 files/opencs/raster/scene-exterior-parts/4_terrain.png delete mode 100644 files/opencs/raster/scene-exterior-parts/5_divider.png delete mode 100755 files/opencs/raster/scene-exterior-parts/composite_the_icons.sh delete mode 100644 files/opencs/raster/scene-exterior-parts/mask.png delete mode 100644 files/opencs/raster/scene-play-rev9_masked.xcf delete mode 100644 files/opencs/raster/scene-view-parts/0_sky_backdrop.png delete mode 100644 files/opencs/raster/scene-view-parts/10_bridge.png delete mode 100644 files/opencs/raster/scene-view-parts/11_terrain_front.png delete mode 100644 files/opencs/raster/scene-view-parts/12_water_front.png delete mode 100644 files/opencs/raster/scene-view-parts/13_pathgrid.png delete mode 100644 files/opencs/raster/scene-view-parts/14_divider.png delete mode 100644 files/opencs/raster/scene-view-parts/1_terrain_very_distant.png delete mode 100644 files/opencs/raster/scene-view-parts/2_fog_very_distant.png delete mode 100644 files/opencs/raster/scene-view-parts/3_water_backdrop.png delete mode 100644 files/opencs/raster/scene-view-parts/4_water_distant.png delete mode 100644 files/opencs/raster/scene-view-parts/5_terrain_distant.png delete mode 100644 files/opencs/raster/scene-view-parts/6_fog_distant.png delete mode 100644 files/opencs/raster/scene-view-parts/7_terrain_back.png delete mode 100644 files/opencs/raster/scene-view-parts/8_water_back.png delete mode 100644 files/opencs/raster/scene-view-parts/9_fog_back.png delete mode 100644 files/opencs/raster/scene-view-parts/README delete mode 100755 files/opencs/raster/scene-view-parts/composite_the_32_icons.sh delete mode 100644 files/opencs/raster/scene-view-parts/mask.png delete mode 100644 files/opencs/raster/script.png delete mode 100644 files/opencs/raster/skill.png delete mode 100644 files/opencs/raster/sound.png delete mode 100644 files/opencs/raster/soundgen.png delete mode 100644 files/opencs/raster/spell.png delete mode 100644 files/opencs/raster/static.png delete mode 100644 files/opencs/raster/weapon.png delete mode 100644 files/opencs/scalable/Palette.svg create mode 100644 files/opencs/scalable/editor-icons.svg delete mode 100644 files/opencs/scalable/referenceable-record/.directory delete mode 100644 files/opencs/scalable/referenceable-record/activator.svg delete mode 100644 files/opencs/scalable/referenceable-record/apparatus.svg delete mode 100644 files/opencs/scalable/referenceable-record/book.svg delete mode 100644 files/opencs/scalable/referenceable-record/book2.svgz delete mode 100644 files/opencs/scalable/referenceable-record/container.svg delete mode 100644 files/opencs/scalable/referenceable-record/ingredient.svg delete mode 100644 files/opencs/scalable/referenceable-record/light.svg delete mode 100644 files/opencs/scalable/referenceable-record/potion.svg delete mode 100644 files/opencs/scalable/referenceable-record/random-item.svg delete mode 100644 files/opencs/scalable/referenceable-record/repair.svg delete mode 100644 files/opencs/scalable/referenceable-record/static.svg delete mode 100644 files/opencs/scalable/referenceable-record/weapon.svg delete mode 100644 files/opencs/scalable/scene-exterior.svg delete mode 100644 files/opencs/scalable/scene-play-rev9.svg delete mode 100644 files/opencs/scalable/scene-view-status-rev14.svg delete mode 100644 files/opencs/scalable/status/.directory delete mode 100644 files/opencs/scalable/status/added.svg delete mode 100644 files/opencs/scalable/status/base.svg delete mode 100644 files/opencs/scalable/status/modified.svg delete mode 100644 files/opencs/scalable/status/removed.svg delete mode 100644 files/opencs/scalable/top-level/gmst.svg delete mode 100644 files/opencs/scalable/top-level/topic-regular.svg rename files/shaders/{nv_default_fragment.glsl => compatibility/bs/default.frag} (57%) rename files/shaders/{nv_default_vertex.glsl => compatibility/bs/default.vert} (77%) create mode 100644 files/shaders/compatibility/bs/nolighting.frag create mode 100644 files/shaders/compatibility/bs/nolighting.vert create mode 100644 files/shaders/compatibility/debug.frag create mode 100644 files/shaders/compatibility/debug.vert create mode 100644 files/shaders/compatibility/depthclipped.frag create mode 100644 files/shaders/compatibility/depthclipped.vert create mode 100644 files/shaders/compatibility/fog.glsl create mode 100644 files/shaders/compatibility/fullscreen_tri.frag create mode 100644 files/shaders/compatibility/fullscreen_tri.vert rename files/shaders/{groundcover_fragment.glsl => compatibility/groundcover.frag} (73%) rename files/shaders/{groundcover_vertex.glsl => compatibility/groundcover.vert} (95%) create mode 100644 files/shaders/compatibility/gui.frag create mode 100644 files/shaders/compatibility/gui.vert create mode 100644 files/shaders/compatibility/luminance/luminance.frag create mode 100644 files/shaders/compatibility/luminance/resolve.frag create mode 100644 files/shaders/compatibility/multiview_resolve.frag create mode 100644 files/shaders/compatibility/multiview_resolve.vert rename files/shaders/{objects_fragment.glsl => compatibility/objects.frag} (65%) rename files/shaders/{objects_vertex.glsl => compatibility/objects.vert} (81%) create mode 100644 files/shaders/compatibility/ripples_blobber.frag create mode 100644 files/shaders/compatibility/ripples_simulate.frag create mode 100644 files/shaders/compatibility/s360.frag rename files/shaders/{s360_vertex.glsl => compatibility/s360.vert} (100%) rename files/shaders/{shadowcasting_fragment.glsl => compatibility/shadowcasting.frag} (85%) rename files/shaders/{shadowcasting_vertex.glsl => compatibility/shadowcasting.vert} (91%) rename files/shaders/{ => compatibility}/shadows_fragment.glsl (93%) rename files/shaders/{ => compatibility}/shadows_vertex.glsl (72%) create mode 100644 files/shaders/compatibility/sky.frag create mode 100644 files/shaders/compatibility/sky.vert rename files/shaders/{terrain_fragment.glsl => compatibility/terrain.frag} (71%) rename files/shaders/{terrain_vertex.glsl => compatibility/terrain.vert} (84%) rename files/shaders/{ => compatibility}/vertexcolors.glsl (100%) rename files/shaders/{water_fragment.glsl => compatibility/water.frag} (64%) create mode 100644 files/shaders/compatibility/water.vert create mode 100644 files/shaders/core/gui.frag create mode 100644 files/shaders/core/gui.vert create mode 100644 files/shaders/core/ripples_blobber.comp create mode 100644 files/shaders/core/ripples_simulate.comp create mode 100644 files/shaders/lib/core/fragment.glsl create mode 100644 files/shaders/lib/core/fragment.h.glsl create mode 100644 files/shaders/lib/core/fragment_multiview.glsl create mode 100644 files/shaders/lib/core/vertex.glsl create mode 100644 files/shaders/lib/core/vertex.h.glsl create mode 100644 files/shaders/lib/core/vertex_multiview.glsl rename files/shaders/{ => lib/light}/lighting.glsl (97%) rename files/shaders/{ => lib/light}/lighting_util.glsl (96%) create mode 100644 files/shaders/lib/luminance/constants.glsl rename files/shaders/{ => lib/material}/alpha.glsl (72%) rename files/shaders/{ => lib/material}/parallax.glsl (81%) create mode 100644 files/shaders/lib/particle/occlusion.glsl create mode 100644 files/shaders/lib/particle/soft.glsl create mode 100644 files/shaders/lib/sky/passes.glsl rename files/shaders/{s360_fragment.glsl => lib/util/coordinates.glsl} (73%) create mode 100644 files/shaders/lib/util/quickstep.glsl create mode 100644 files/shaders/lib/view/depth.glsl create mode 100644 files/shaders/lib/water/fresnel.glsl create mode 100644 files/shaders/lib/water/rain_ripples.glsl create mode 100644 files/shaders/lib/water/ripples.glsl delete mode 100644 files/shaders/nv_nolighting_fragment.glsl delete mode 100644 files/shaders/nv_nolighting_vertex.glsl delete mode 100644 files/shaders/water_vertex.glsl create mode 100644 files/ui/directorypicker.ui create mode 100644 files/ui/importpage.ui delete mode 100644 files/ui/playpage.ui delete mode 100644 files/vfs/CMakeLists.txt create mode 100644 scripts/HOWTO-benchmark.md create mode 100644 scripts/data/integration_tests/test_lua_api/builtin.omwscripts create mode 100644 scripts/data/integration_tests/test_lua_api/openmw.cfg create mode 100644 scripts/data/integration_tests/test_lua_api/player.lua create mode 100644 scripts/data/integration_tests/test_lua_api/test.lua create mode 100644 scripts/data/integration_tests/test_lua_api/test.omwscripts create mode 100644 scripts/data/integration_tests/testing_util/testing_util.lua create mode 100755 scripts/find_missing_merge_requests.py create mode 100755 scripts/generate_teal_declarations.sh create mode 100755 scripts/integration_tests.py create mode 100755 scripts/osg_stats.py create mode 100755 scripts/preprocessed_file_size_stats.py create mode 100755 scripts/preprocessed_file_size_stats_diff.py diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..a4dcc74a3 --- /dev/null +++ b/.clang-format @@ -0,0 +1,105 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + IndentBraces: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BreakStringLiterals: true +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: CaseSensitive +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++20 +TabWidth: 4 +UseTab: Never +StatementMacros: + - META_Object + - META_StateAttribute + - META_Node diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..d63006331 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,18 @@ +Checks: > + -*, + boost-*, + portability-*, + clang-analyzer-*, + -clang-analyzer-optin*, + -clang-analyzer-cplusplus.NewDeleteLeaks, + -clang-analyzer-core.CallAndMessage, + -modernize-avoid-bind +WarningsAsErrors: > + -*, + boost-*, + portability-*, + clang-analyzer-*, + -clang-analyzer-optin*, + -clang-analyzer-cplusplus.NewDeleteLeaks, + -clang-analyzer-core.CallAndMessage +HeaderFilterRegex: '^(apps|components)' diff --git a/.editorconfig b/.editorconfig index 307f5e58f..97db8e0b5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,12 @@ indent_style = space indent_size = 4 insert_final_newline = true -[*.glsl] +[*.{glsl,vert,tesc,tese,geom,frag,comp}] indent_style = space indent_size = 4 -insert_final_newline = false \ No newline at end of file +insert_final_newline = true + +[{CMakeLists.txt,*.cmake}] +indent_style = space +indent_size = 4 +insert_final_newline = true diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..c3e22b841 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,19 @@ +# This file lists revisions meant to be ignored by `git blame`. +# Pass `--ignore-revs-file .git-blame-ignore-revs` to `git blame` to make your life easier. + +# Author: Alexei Kotov +# Date: Fri Sep 2 02:52:49 2022 +0000 +# Reformat NIF record type mapping +8df0587793a07ec556dc9cb575cd2af4204c456b + +# Author: AnyOldName3 +# Date: Fri Sep 16 00:53:24 2022 +0100 +# Renormalise line endings +84f8a6848a8b05502d7618ca7af8cca74f2c3bae + +# Author: clang-format-bot +# Date: 9/22/2022 9:26:05 PM +# Apply clang-format to code base +ddb0522bbf2aa8aa7c9e139ff7395fb8ed6a841f + +88ec8a95231341e7962b85716510d414e9f0c424 diff --git a/.github/workflows/openmw.yml b/.github/workflows/openmw.yml new file mode 100644 index 000000000..2b429df0a --- /dev/null +++ b/.github/workflows/openmw.yml @@ -0,0 +1,80 @@ +name: CMake + +on: + push: + branches: + - 'master' + pull_request: + branches: [ master ] + +env: + BUILD_TYPE: RelWithDebInfo + +jobs: + Ubuntu: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Add OpenMW PPA Dependencies + run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update + + - name: Install Building Dependencies + run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic + + - name: Prime ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} + max-size: 1000M + + - name: Configure + run: cmake . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=1 -DUSE_SYSTEM_TINYXML=1 -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=install + + - name: Build + run: make -j3 + + - name: Test + run: ./openmw_test_suite + + # - name: Install + # shell: bash + # run: cmake --install . + + # - name: Create Artifact + # shell: bash + # working-directory: install + # run: | + # ls -laR + # 7z a ../build_artifact.7z . + + # - name: Upload Artifact + # uses: actions/upload-artifact@v1 + # with: + # path: ./build_artifact.7z + # name: build_artifact.7z + + MacOS: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install Building Dependencies + run: CI/before_install.osx.sh + + - name: Prime ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} + max-size: 1000M + + - name: Configure + run: | + rm -fr build # remove the build directory + CI/before_script.osx.sh + - name: Build + run: | + cd build + make -j $(sysctl -n hw.logicalcpu) package diff --git a/.gitignore b/.gitignore index 4cf92b549..d79664f15 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,6 @@ CMakeLists.txt.user* .vscode ## resources -data resources /*.cfg /*.desktop @@ -73,6 +72,7 @@ components/ui_contentselector.h docs/mainpage.hpp docs/Doxyfile docs/DoxyfilePages +docs/source/reference/lua-scripting/generated_html moc_*.cxx *.cxx_parameters *qrc_launcher.cxx @@ -85,3 +85,7 @@ moc_*.cxx *.[ao] *.so venv/ + +## operating system files +.DS_Store +Thumbs.db diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ceba2841f..a76c4622d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,131 +1,483 @@ +default: + interruptible: true + # Note: We set `needs` on each job to control the job DAG. # See https://docs.gitlab.com/ee/ci/yaml/#needs stages: + - checks - build + - test -.Debian_Image: +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS + when: never + - if: $CI_COMMIT_BRANCH + +# https://blog.nimbleways.com/let-s-make-faster-gitlab-ci-cd-pipelines/ +variables: + FF_USE_NEW_SHELL_ESCAPE: "true" + FF_USE_FASTZIP: "true" + # These can be specified per job or per pipeline + ARTIFACT_COMPRESSION_LEVEL: "fast" + CACHE_COMPRESSION_LEVEL: "fast" + +.Ubuntu_Image: tags: - docker - linux - image: debian:bullseye + image: ubuntu:22.04 + rules: + - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" -.Debian: - extends: .Debian_Image +Ubuntu_GCC_preprocess: + extends: .Ubuntu_Image + cache: + key: Ubuntu_GCC_preprocess.ubuntu_22.04.v1 + paths: + - apt-cache/ + - .cache/pip/ + stage: build + variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + before_script: + - CI/install_debian_deps.sh openmw-deps openmw-deps-dynamic gcc_preprocess + - pip3 install --user click termtables + script: + - CI/ubuntu_gcc_preprocess.sh + +.Ubuntu: + extends: .Ubuntu_Image cache: paths: - apt-cache/ - ccache/ stage: build + variables: + CMAKE_EXE_LINKER_FLAGS: -fuse-ld=mold script: + - df -h - export CCACHE_BASEDIR="`pwd`" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.linux.sh - cd build - cmake --build . -- -j $(nproc) + - df -h + - du -sh . + - find . | grep '\.o$' | xargs rm -f + - df -h + - du -sh . - cmake --install . - - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite; fi - - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi + - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite --gtest_output="xml:openmw_tests.xml"; fi + - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw-cs-tests --gtest_output="xml:openmw_cs_tests.xml"; fi + - if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi + - if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_esm_refid_benchmark; fi + - if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_settings_access_benchmark; fi - ccache -s + - df -h + - if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then gcovr --xml-pretty --exclude-unreachable-branches --print-summary --root "${CI_PROJECT_DIR}" -j $(nproc) -o ../coverage.xml; fi + - ls | grep -v -e '^extern$' -e '^install$' -e '^openmw_tests.xml$' -e '^openmw_cs_tests.xml$' | xargs -I '{}' rm -rf './{}' + - cd .. + - df -h + - du -sh build/ + - du -sh build/install/ + - du -sh apt-cache/ + - du -sh ccache/ artifacts: paths: - build/install/ Coverity: - extends: .Debian_Image + tags: + - docker + - linux + image: ubuntu:20.04 stage: build rules: - - if: '$CI_PIPELINE_SOURCE == "schedule"' + - if: $CI_PIPELINE_SOURCE == "schedule" + cache: + key: Coverity.ubuntu_20.04.v1 + paths: + - apt-cache/ + - ccache/ + variables: + CCACHE_SIZE: 2G + CC: clang-11 + CXX: clang++-11 + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -O0 before_script: - - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity - - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN + - CI/install_debian_deps.sh coverity openmw-deps openmw-deps-dynamic + - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 + --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz script: + - export CCACHE_BASEDIR="$(pwd)" + - export CCACHE_DIR="$(pwd)/ccache" + - mkdir -pv "${CCACHE_DIR}" + - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.linux.sh - # Add more than just `openmw` once we can build everything under 3h - - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw + - cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache + # Remove the specific targets and build everything once we can do it under 3h + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) + - ccache -s after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL - --form file=@cov-int.tar.gz --form version="`git describe --tags`" - --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" - variables: - CC: gcc - CXX: g++ - timeout: 8h + --form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" + --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" + artifacts: + paths: + - /builds/OpenMW/openmw/cov-int/build-log.txt -Debian_GCC: - extends: .Debian +Ubuntu_GCC: + extends: .Ubuntu cache: - key: Debian_GCC.v2 + key: Ubuntu_GCC.ubuntu_22.04.v1 before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic variables: CC: gcc CXX: g++ - CCACHE_SIZE: 3G + CCACHE_SIZE: 4G # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h -Debian_GCC_tests: - extends: Debian_GCC +Ubuntu_GCC_asan: + extends: Ubuntu_GCC cache: - key: Debian_GCC_tests.v2 + key: Ubuntu_GCC_asan.ubuntu_22.04.v1 + variables: + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak + CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak -fuse-ld=mold + BUILD_OPENMW_ONLY: 1 + +Clang_Format: + extends: .Ubuntu_Image + stage: checks + cache: + key: Ubuntu_Clang_Format.ubuntu_22.04.v1 + paths: + - apt-cache/ + variables: + CLANG_FORMAT: clang-format-14 + before_script: + - CI/install_debian_deps.sh openmw-clang-format + script: + - CI/check_cmake_format.sh + - CI/check_file_names.sh + - CI/check_clang_format.sh + +Teal: + stage: checks + extends: .Ubuntu_Image + before_script: + - apt-get update + - apt-get -y install curl wget make build-essential libreadline-dev git-core zip unzip + script: + - CI/teal_ci.sh + artifacts: + paths: + - teal_declarations.zip + +Ubuntu_GCC_Debug: + extends: .Ubuntu + cache: + key: Ubuntu_GCC_Debug.ubuntu_22.04.v1 + before_script: + - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic + variables: + CC: gcc + CXX: g++ + CCACHE_SIZE: 4G + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -O0 + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + timeout: 2h + +Ubuntu_GCC_tests: + extends: Ubuntu_GCC + cache: + key: Ubuntu_GCC_tests.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/*_tests.xml -Debian_GCC_Static_Deps: - extends: Debian_GCC +.Ubuntu_GCC_tests_Debug: + extends: Ubuntu_GCC cache: - key: Debian_GCC_Static_Deps - paths: - - apt-cache/ - - ccache/ - - build/extern/fetched/ + key: Ubuntu_GCC_tests_Debug.ubuntu_22.04.v1 + variables: + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O0 -D_GLIBCXX_ASSERTIONS + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/*_tests.xml + +Ubuntu_GCC_tests_asan: + extends: Ubuntu_GCC + cache: + key: Ubuntu_GCC_tests_asan.ubuntu_22.04.v1 + variables: + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak + CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-subtract -fsanitize=leak -fuse-ld=mold + ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/*_tests.xml + +Ubuntu_GCC_tests_ubsan: + extends: Ubuntu_GCC + cache: + key: Ubuntu_GCC_tests_ubsan.ubuntu_22.04.v1 + variables: + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O0 -fsanitize=undefined + UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/*_tests.xml + +.Ubuntu_GCC_tests_tsan: + extends: Ubuntu_GCC + cache: + key: Ubuntu_GCC_tests_tsan.ubuntu_22.04.v1 + variables: + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=thread -fPIE + CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread -fuse-ld=mold + TSAN_OPTIONS: second_deadlock_stack=1:halt_on_error=1 + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/*_tests.xml + +Ubuntu_GCC_tests_coverage: + extends: .Ubuntu_GCC_tests_Debug + cache: + key: Ubuntu_GCC_tests_coverage.ubuntu_22.04.v1 + variables: + BUILD_WITH_CODE_COVERAGE: 1 before_script: - - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static + - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic openmw-coverage + coverage: /^\s*lines:\s*\d+.\d+\%/ + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + coverage_report: + coverage_format: cobertura + path: coverage.xml + junit: build/*_tests.xml + +.Ubuntu_Static_Deps: + extends: Ubuntu_Clang + rules: + - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" + changes: + - "**/CMakeLists.txt" + - "cmake/**/*" + - "CI/**/*" + - ".gitlab-ci.yml" + cache: + key: Ubuntu_Static_Deps.ubuntu_22.04.v1 + paths: + - apt-cache/ + - ccache/ + - build/extern/fetched/ + before_script: + - CI/install_debian_deps.sh clang openmw-deps openmw-deps-static variables: CI_OPENMW_USE_STATIC_DEPS: 1 + CC: clang + CXX: clang++ + CXXFLAGS: -O0 + timeout: 3h -Debian_GCC_Static_Deps_tests: - extends: Debian_GCC_Static_Deps +.Ubuntu_Static_Deps_tests: + extends: .Ubuntu_Static_Deps cache: - key: Debian_GCC_Static_Deps_tests + key: Ubuntu_Static_Deps_tests.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 + CC: clang + CXX: clang++ + CXXFLAGS: -O0 + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/*_tests.xml -Debian_Clang: - extends: .Debian +Ubuntu_Clang: + extends: .Ubuntu before_script: - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic cache: - key: Debian_Clang.v2 + key: Ubuntu_Clang.ubuntu_22.04.v2 variables: CC: clang CXX: clang++ CCACHE_SIZE: 2G # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. - timeout: 2h + timeout: 3h -Debian_Clang_tests: - extends: Debian_Clang +.Ubuntu_Clang_Tidy_Base: + extends: Ubuntu_Clang + before_script: + - CI/install_debian_deps.sh clang clang-tidy openmw-deps openmw-deps-dynamic cache: - key: Debian_Clang_tests.v2 + key: Ubuntu_Clang_Tidy.ubuntu_22.04.v1 + variables: + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -O0 + CI_CLANG_TIDY: 1 + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + script: + - mkdir -pv "${CCACHE_DIR}" + - ccache -z -M "${CCACHE_SIZE}" + - CI/before_script.linux.sh + - cd build + - find . -name *.o -exec touch {} \; + - cmake --build . -- -j $(nproc) ${BUILD_TARGETS} + - ccache -s + artifacts: + paths: + - build/ + expire_in: 12h + timeout: 3h + +Ubuntu_Clang_Tidy_components: + extends: .Ubuntu_Clang_Tidy_Base + variables: + BUILD_TARGETS: components components_qt oics osg-ffmpeg-videoplayer osgQt + timeout: 3h + +Ubuntu_Clang_Tidy_openmw: + extends: .Ubuntu_Clang_Tidy_Base + needs: + - Ubuntu_Clang_Tidy_components + variables: + BUILD_TARGETS: openmw + timeout: 3h + +Ubuntu_Clang_Tidy_openmw-cs: + extends: .Ubuntu_Clang_Tidy_Base + needs: + - Ubuntu_Clang_Tidy_components + variables: + BUILD_TARGETS: openmw-cs openmw-cs-tests + timeout: 3h + +Ubuntu_Clang_Tidy_other: + extends: .Ubuntu_Clang_Tidy_Base + needs: + - Ubuntu_Clang_Tidy_components + variables: + BUILD_TARGETS: bsatool esmtool openmw-launcher openmw-iniimporter openmw-essimporter openmw-wizard niftest openmw_test_suite openmw-navmeshtool openmw-bulletobjecttool + timeout: 3h + +.Ubuntu_Clang_tests: + extends: Ubuntu_Clang + cache: + key: Ubuntu_Clang_tests.ubuntu_22.04.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/*_tests.xml + +Ubuntu_Clang_tests_Debug: + extends: Ubuntu_Clang + cache: + key: Ubuntu_Clang_tests_Debug.ubuntu_22.04.v1 + variables: + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + CMAKE_BUILD_TYPE: Debug + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/*_tests.xml + +.Ubuntu_integration_tests_base: + extends: .Ubuntu_Image + stage: test + variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + cache: + paths: + - .cache/pip + - apt-cache/ + before_script: + - CI/install_debian_deps.sh $OPENMW_DEPS + - pip3 install --user numpy matplotlib termtables click + script: + - CI/run_integration_tests.sh + after_script: + - if [[ -f /tmp/openmw-crash.log ]]; then cat /tmp/openmw-crash.log; fi + +Ubuntu_Clang_integration_tests: + extends: .Ubuntu_integration_tests_base + needs: + - Ubuntu_Clang + cache: + key: Ubuntu_Clang_integration_tests.ubuntu_22.04.v2 + variables: + OPENMW_DEPS: openmw-integration-tests + +Ubuntu_GCC_integration_tests_asan: + extends: .Ubuntu_integration_tests_base + needs: + - Ubuntu_GCC_asan + cache: + key: Ubuntu_GCC_integration_tests_asan.ubuntu_22.04.v1 + variables: + OPENMW_DEPS: openmw-integration-tests libasan6 + ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=0 .MacOS: - image: macos-11-xcode-12 - tags: - - shared-macos-amd64 stage: build - only: - variables: - - $CI_PROJECT_ID == "7107382" cache: paths: - ccache/ @@ -138,78 +490,89 @@ Debian_Clang_tests: - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.osx.sh - cd build; make -j $(sysctl -n hw.logicalcpu) package - - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done + - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}_${CI_JOB_ID}.dmg"; done - ccache -s artifacts: paths: - build/OpenMW-*.dmg - "build/**/*.log" -macOS11_Xcode12: +macOS13_Xcode14_arm64: extends: .MacOS - image: macos-11-xcode-12 - allow_failure: true + image: macos-12-xcode-14 + tags: + - saas-macos-medium-m1 cache: - key: macOS11_Xcode12.v1 + key: macOS12_Xcode14_arm64.v1 variables: CCACHE_SIZE: 3G -macOS10.15_Xcode11: - extends: .MacOS - image: macos-10.15-xcode-11 - cache: - key: macOS10.15_Xcode11.v1 - variables: - CCACHE_SIZE: 3G - -variables: &engine-targets - targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" - package: "Engine" - -variables: &cs-targets - targets: "openmw-cs,bsatool,esmtool,niftest" - package: "CS" - -variables: &tests-targets - targets: "openmw_test_suite,openmw_detournavigator_navmeshtilescache_benchmark" - package: "Tests" - .Windows_Ninja_Base: tags: - windows + rules: + - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: + - Get-Volume - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 + - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y + - choco install ccache -y - choco install vswhere -y - choco install ninja -y - choco install python -y - refreshenv + - | + function Make-SafeFileName { + param( + [Parameter(Mandatory=$true)] + [String] + $FileName + ) + [IO.Path]::GetInvalidFileNameChars() | ForEach-Object { + $FileName = $FileName.Replace($_, '_') + } + return $FileName + } stage: build script: + - Get-Volume - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t + - $env:CCACHE_BASEDIR = Get-Location + - $env:CCACHE_DIR = "$(Get-Location)\ccache" + - New-Item -Type Directory -Force -Path $env:CCACHE_DIR + - New-Item -Type File -Force -Path MSVC2019_64_Ninja\.cmake\api\v1\query\codemodel-v2 + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview -E + - Get-Volume - cd MSVC2019_64_Ninja - .\ActivateMSVC.ps1 - - cmake --build . --config $config --target ($targets.Split(',')) + - cmake --build . --config $config + - ccache --show-stats - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt + - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + Push-Location .. + ..\CI\Store-Symbols.ps1 + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: + - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: ninja-v2 + key: ninja-v7 paths: + - ccache - deps - MSVC2019_64_Ninja/deps/Qt artifacts: @@ -225,54 +588,32 @@ variables: &tests-targets - MSVC2019_64_Ninja/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + timeout: 2h -Windows_Ninja_Engine_Release: +.Windows_Ninja_Release: extends: - .Windows_Ninja_Base variables: - <<: *engine-targets config: "Release" -Windows_Ninja_Engine_Debug: +.Windows_Ninja_Release_MultiView: extends: - .Windows_Ninja_Base variables: - <<: *engine-targets - config: "Debug" - -Windows_Ninja_Engine_RelWithDebInfo: - extends: - - .Windows_Ninja_Base - variables: - <<: *engine-targets - config: "RelWithDebInfo" - -Windows_Ninja_CS_Release: - extends: - - .Windows_Ninja_Base - variables: - <<: *cs-targets + multiview: "-M" config: "Release" -Windows_Ninja_CS_Debug: +.Windows_Ninja_Debug: extends: - .Windows_Ninja_Base variables: - <<: *cs-targets config: "Debug" -Windows_Ninja_CS_RelWithDebInfo: +.Windows_Ninja_RelWithDebInfo: extends: - .Windows_Ninja_Base variables: - <<: *cs-targets - config: "RelWithDebInfo" - -Windows_Ninja_Tests_RelWithDebInfo: - extends: .Windows_Ninja_Base - stage: build - variables: - <<: *tests-targets config: "RelWithDebInfo" # Gitlab can't successfully execute following binaries due to unknown reason # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" @@ -280,37 +621,67 @@ Windows_Ninja_Tests_RelWithDebInfo: .Windows_MSBuild_Base: tags: - windows + rules: + - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: + - Get-Volume - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 + - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y + - choco install ccache -y - choco install vswhere -y - choco install python -y - refreshenv + - | + function Make-SafeFileName { + param( + [Parameter(Mandatory=$true)] + [String] + $FileName + ) + [IO.Path]::GetInvalidFileNameChars() | ForEach-Object { + $FileName = $FileName.Replace($_, '_') + } + return $FileName + } stage: build script: + - Get-Volume - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t + - $env:CCACHE_BASEDIR = Get-Location + - $env:CCACHE_DIR = "$(Get-Location)\ccache" + - New-Item -Type Directory -Force -Path $env:CCACHE_DIR + - New-Item -Type File -Force -Path MSVC2019_64\.cmake\api\v1\query\codemodel-v2 + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E - cd MSVC2019_64 - - cmake --build . --config $config --target ($targets.Split(',')) + - Get-Volume + - cmake --build . --config $config + - ccache --show-stats - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt + - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + Push-Location .. + ..\CI\Store-Symbols.ps1 + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: + - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: msbuild-v2 + key: msbuild-v7 paths: + - ccache - deps - MSVC2019_64/deps/Qt artifacts: @@ -326,78 +697,52 @@ Windows_Ninja_Tests_RelWithDebInfo: - MSVC2019_64/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*/*.log + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + timeout: 2h -Windows_MSBuild_Engine_Release: +.Windows_MSBuild_Release: extends: - .Windows_MSBuild_Base variables: - <<: *engine-targets config: "Release" -Windows_MSBuild_Engine_Debug: +.Windows_MSBuild_Debug: extends: - .Windows_MSBuild_Base variables: - <<: *engine-targets config: "Debug" -Windows_MSBuild_Engine_RelWithDebInfo: +Windows_MSBuild_RelWithDebInfo: extends: - .Windows_MSBuild_Base variables: - <<: *engine-targets - config: "RelWithDebInfo" - -Windows_MSBuild_CS_Release: - extends: - - .Windows_MSBuild_Base - variables: - <<: *cs-targets - config: "Release" - -Windows_MSBuild_CS_Debug: - extends: - - .Windows_MSBuild_Base - variables: - <<: *cs-targets - config: "Debug" - -Windows_MSBuild_CS_RelWithDebInfo: - extends: - - .Windows_MSBuild_Base - variables: - <<: *cs-targets - config: "RelWithDebInfo" - -Windows_MSBuild_Tests_RelWithDebInfo: - extends: .Windows_MSBuild_Base - stage: build - variables: - <<: *tests-targets config: "RelWithDebInfo" # Gitlab can't successfully execute following binaries due to unknown reason # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" + # temporarily enabled while we're linking these on the downloads page + rules: + # run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it + - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "schedule" -Debian_AndroidNDK_arm64-v8a: +.Ubuntu_AndroidNDK_arm64-v8a: tags: - linux - image: debian:bullseye + image: psi29a/android-ndk:focal-ndk22 + rules: + - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" variables: CCACHE_SIZE: 3G cache: - key: Debian_AndroidNDK_arm64-v8a.v3 + key: Ubuntu__Focal_AndroidNDK_r22b_arm64-v8a.v2 paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: - - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - - echo "deb http://deb.debian.org/debian unstable main contrib" > /etc/apt/sources.list - - echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections - - apt-get update -yq - - apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer + - CI/install_debian_deps.sh android stage: build script: + - df -h - export CCACHE_BASEDIR="`pwd`" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" - ccache -z -M "${CCACHE_SIZE}" @@ -405,10 +750,55 @@ Debian_AndroidNDK_arm64-v8a: - CI/before_script.android.sh - cd build - cmake --build . -- -j $(nproc) - - cmake --install . + # - cmake --install . # no one uses builds anyway, disable until 'no space left' is resolved - ccache -s + - df -h + - ls | grep -v -e '^extern$' -e '^install$' | xargs -I '{}' rm -rf './{}' + - cd .. + - df -h + - du -sh build/ + # - du -sh build/install/ # no install dir because it's commented out above + - du -sh apt-cache/ + - du -sh ccache/ + - du -sh build/extern/fetched/ artifacts: paths: - build/install/ # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 1h30m + +.FindMissingMergeRequests: + image: python:latest + stage: build + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + cache: + key: FindMissingMergeRequests.v1 + paths: + - .cache/pip + before_script: + - pip3 install --user requests click discord_webhook + script: + - scripts/find_missing_merge_requests.py --project_id=$CI_PROJECT_ID --ignored_mrs_path=$CI_PROJECT_DIR/.resubmitted_merge_requests.txt + +.flatpak: + image: 'docker.io/bilelmoussaoui/flatpak-github-actions' + stage: build + script: + - flatpak install -y flathub org.kde.Platform/x86_64/5.15-21.08 + - flatpak install -y flathub org.kde.Sdk/x86_64/5.15-21.08 + - flatpak-builder --ccache --force-clean --repo=repo build CI/org.openmw.OpenMW.devel.yaml + - flatpak build-bundle ./repo openmw.flatpak org.openmw.OpenMW.devel + cache: + key: flatpak + paths: + - ".flatpak-builder" + artifacts: + untracked: false + paths: + - "openmw.flatpak" + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + # Flatpak Builds compile all dependencies aswell so need more time. Build results of libraries are cached + timeout: 4h diff --git a/.resubmitted_merge_requests.txt b/.resubmitted_merge_requests.txt new file mode 100644 index 000000000..1585a60ec --- /dev/null +++ b/.resubmitted_merge_requests.txt @@ -0,0 +1,8 @@ +1471 +1450 +1420 +1314 +1216 +1172 +1160 +1051 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 864a1e2cc..000000000 --- a/.travis.yml +++ /dev/null @@ -1,100 +0,0 @@ -language: cpp -branches: - only: - - master - - coverity_scan - - /openmw-.*$/ - - /^[0-9]+\.[0-9]+\.[0-9]+.*$/ -env: - global: - # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created - # via the "travis encrypt" command using the project repo's public key - - secure: "1QK0yVyoOB+gf2I7XzvhXu9w/5lq4stBXIwJbVCTjz4Q4XVHCosURaW1MAgKzMrPnbFEwjyn5uQ8BwsvvfkuN1AZD0YXITgc7gyI+J1wQ/p/ljxRxglakU6WEgsTs2J5z9UmGac4YTXg+quK7YP3rv+zuGim2I2rhzImejyzp0Ym3kRCnNcy+SGBsiRaevRJMe00Ch8zGAbEhduQGeSoS6W0rcu02DNlQKiq5NktWsXR+TWWWVfIeIlQR/lbPsCd0pdxMaMv2QCY0rVbwrYxWJwr/Qe45dAdWp+8/C3PbXpeMSGxlLa33nJNX4Lf/djxbjm8KWk6edaXPajrjR/0iwcpwq0jg2Jt6XfEdnJt35F1gpXlc04sxStjG45uloOKCFYT0wdhIO1Lq+hDP54wypQl+JInd5qC001O7pwhVxO36EgKWqo8HD+BqGDBwsNj2engy9Qcp3wO6G0rLBPB3CrZsk9wrHVv5cSiQSLMhId3Xviu3ZI2qEDA+kgTvxrKrsnMj4bILVCyG5Ka2Mj22wIDW9e8oIab9oTdujax3DTN1GkD6QuOAGzwDsNwGASsgfoeZ+FUhgM75RlBWGMilgkmnF7EJ0oAXLEpjtABnEr2d4qHv+y08kOuTDBLB9ExzCIj024dYYYNLZrqPKx0ncHuCMG2QNj2aJAJEZtj1rQ=" -cache: ccache -addons: - apt: - sources: - - sourceline: 'ppa:openmw/openmw' - - ubuntu-toolchain-r-test - packages: [ - # Dev - cmake, clang-tools-7, gcc-8, g++-8, ccache, - # Boost - libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, - # FFmpeg - libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev, - # Audio, Video and Misc. deps - libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev, - # The other ones from OpenMW ppa - libbullet-dev, libopenscenegraph-3.4-dev, libmygui-dev, - # tes3mp stuff - libboost-dev, libqt5opengl5-dev, libluajit-5.1-dev - ] - coverity_scan: - project: - name: "TES3MP/openmw-tes3mp" - description: "" - branch_pattern: coverity_scan - notification_email: koncord@tes3mp.com - build_command_prepend: "cov-configure --comptype gcc --compiler gcc-8 --template; cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE" - build_command: "make VERBOSE=1 -j3" -matrix: - include: - - os: linux - env: - - ANALYZE="scan-build-7 --use-cc clang-7 --use-c++ clang++-7 " - - MATRIX_CC="CC=clang-7 && CXX=clang++-7" - compiler: clang - sudo: required - dist: bionic - - os: linux - env: - - MATRIX_CC="CC=gcc-8 && CXX=g++-8" - sudo: required - dist: bionic - - os: linux - env: - - MATRIX_CC="CC=clang-7 && CXX=clang++-7" - sudo: required - dist: bionic - allow_failures: - - env: - - MATRIX_CC="CC=clang-7 && CXX=clang++-7" - - env: - - ANALYZE="scan-build-7 --use-cc clang-7 --use-c++ clang++-7 " - - MATRIX_CC="CC=clang-7 && CXX=clang++-7" - -before_install: - - ./CI/before_install.${TRAVIS_OS_NAME}.sh -before_script: - - ccache -z - - ./CI/before_script.${TRAVIS_OS_NAME}.sh -script: - - cd ./build - - ${ANALYZE} make -j3; fi - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi - - if [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - - cd "${TRAVIS_BUILD_DIR}" - - ccache -s -#deploy: -# provider: script -# script: ./CI/deploy.osx.sh -# skip_cleanup: true -# on: -# branch: master -# condition: "$TRAVIS_EVENT_TYPE = cron && $TRAVIS_OS_NAME = osx" -# repo: TES3MP/openmw-tes3mp -#notifications: -# email: -# recipients: -# - corrmage+travis-ci@gmail.com -# on_success: change -# on_failure: always -# irc: -# channels: -# - "chat.freenode.net#openmw" -# on_success: change -# on_failure: always -# use_notice: true diff --git a/AUTHORS.md b/AUTHORS.md index 5ade21d41..633404eb0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -27,8 +27,12 @@ Programmers Alexander Olofsson (Ananace) Alex Rice Alex S (docwest) + Alexey Yaryshev (skeevert) Allofich + Andreas Stöckel Andrei Kortunov (akortunov) + Andrew Appuhamy (andrew-app) + Andrzej Głuszak (agluszak) AnyOldName3 Ardekantur Armin Preiml @@ -42,6 +46,7 @@ Programmers Austin Salgat (Salgat) Ben Shealy (bentsherman) Berulacks + Bo Svensson Britt Mathis (galdor557) Capostrophic Carl Maxwell @@ -54,8 +59,9 @@ Programmers Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) crussell187 - DanielVukelich + Dan Vukelich (sanchezman) darkf + Dave Corley (S3ctor) David Cernat (davidcernat) Declan Millar (declan-millar) devnexen @@ -67,16 +73,19 @@ Programmers David Teviotdale (dteviot) Diggory Hardy Dmitry Marakasov (AMDmi3) + Duncan Frost (duncans_pumpkin) Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) + Eris Caffee (eris) eroen escondida Evgeniy Mineev (sandstranger) Federico Guerra (FedeWar) Fil Krynicki (filkry) Finbar Crago (finbar-crago) + Florent Teppe (Tetramir) Florian Weber (Florianjw) Frédéric Chardon (fr3dz10) Gaëtan Dezeiraud (Brouilles) @@ -88,15 +97,18 @@ Programmers Haoda Wang (h313) hristoast Internecine + Ivan Beloborodov (myrix) Jackerty Jacob Essex (Yacoby) Jacob Turnbull (Tankinfrank) Jake Westrip (16bitint) James Carty (MrTopCat) + James Deciutiis (JamesDeciutiis) James Moore (moore.work) James Stephens (james-h-stephens) Jan-Peter Nilsson (peppe) Jan Borsodi (am0s) + JanuarySnow Jason Hooks (jhooks) jeaye jefetienne @@ -108,6 +120,7 @@ Programmers John Blomberg (fstp) Jordan Ayers Jordan Milne + Josquin Frei Josua Grawitter Jules Blok (Armada651) julianko @@ -118,6 +131,7 @@ Programmers Kurnevsky Evgeny (kurnevsky) Lars Söderberg (Lazaroth) lazydev + Léo Peltier Leon Krieg (lkrieg) Leon Saunders (emoose) logzero @@ -146,6 +160,7 @@ Programmers Miroslav Puda (pakanek) MiroslavR Mitchell Schwitzer (schwitzerm) + Mitten.O naclander Narmo Nat Meo (Utopium) @@ -154,8 +169,10 @@ Programmers Nialsy Nick Crawford (nighthawk469) Nikolay Kasyanov (corristo) + Noah Gooder nobrakal Nolan Poe (nopoe) + Nurivan Gomez (Nuri-G) Oleg Chkan (mrcheko) Paul Cercueil (pcercuei) Paul McElroy (Greendogo) @@ -170,6 +187,7 @@ Programmers PlutonicOverkill Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) + Randy Davin (Kindi) rdimesio rexelion riothamus @@ -186,6 +204,7 @@ Programmers Sergey Shambir (sergey-shambir) sergoz ShadowRadiance + Shihan42 Siimacore Simon Meulenbeek (simonmb) sir_herrbatka @@ -207,16 +226,20 @@ Programmers tlmullis tri4ng1e Thoronador + Tobias Tribble (zackhasacat) + Tom Lowe (Vulpen) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) unelsson uramer viadanna + Vidi_Aquam Vincent Heuken Vladimir Panteleev (CyberShadow) + vocollapse Wang Ryu (bzzt) Will Herrmann (Thunderforge) - vocollapse + Wolfgang Lieff xyzz Yohaulticetl Yuri Krupenin @@ -237,6 +260,7 @@ Documentation Joakim Berg (lysol90) Ryan Tucker (Ravenwing) sir_herrbatka + David Nagy (zuzaman) Packagers --------- diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dacef572..2d8b69806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,313 @@ +0.49.0 +------ + + Bug #2623: Snowy Granius doesn't prioritize conjuration spells + Bug #3842: Body part skeletons override the main skeleton + Bug #4127: Weapon animation looks choppy + Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game + Bug #4382: Sound output device does not change when it should + Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely + Bug #4754: Stack of ammunition cannot be equipped partially + Bug #4816: GetWeaponDrawn returns 1 before weapon is attached + Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses + Bug #5129: Stuttering animation on Centurion Archer + Bug #5371: Keyframe animation tracks are used for any file that begins with an X + Bug #5714: Touch spells cast using ExplodeSpell don't always explode + Bug #5849: Paralysis breaks landing + Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla + Bug #5883: Immobile creatures don't cause water ripples + Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load + Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex + Bug #6313: Followers with high Fight can turn hostile + Bug #6427: Enemy health bar disappears before damaging effect ends + Bug #6550: Cloned body parts don't inherit texture effects + Bug #6645: Enemy block sounds align with animation instead of blocked hits + Bug #6657: Distant terrain tiles become black when using FWIW mod + Bug #6661: Saved games that have no preview screenshot cause issues or crashes + Bug #6716: mwscript comparison operator handling is too restrictive + Bug #6807: Ultimate Galleon is not working properly + Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands + Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack + Bug #6939: OpenMW-CS: ID columns are too short + Bug #6949: Sun Damage effect doesn't work in quasi exteriors + Bug #6964: Nerasa Dralor Won't Follow + Bug #6973: Fade in happens after the scene load and is shown + Bug #6974: Only harmful effects are reflected + Bug #6977: Sun damage implementation does not match research + Bug #6986: Sound magic effect does not make noise + Bug #6987: Set/Mod Blindness should not darken the screen + Bug #6992: Crossbow reloading doesn't look the same as in Morrowind + Bug #6993: Shooting your last round of ammunition causes the attack animation to cancel + Bug #7009: Falling actors teleport to the ground without receiving any damage on cell loading + Bug #7034: Misc items defined in one content file are not treated as keys if another content file uses them as such + Bug #7042: Weapon follow animations that immediately follow the hit animations cause multiple hits + Bug #7044: Changing a class' services does not affect autocalculated NPCs + Bug #7054: Quests aren't sorted by name + Bug #7064: NPCs don't report crime if the player is casting offensive spells on them while sneaking + Bug #7077: OpenMW fails to load certain particle effects in .osgt format + Bug #7084: Resurrecting an actor doesn't take into account base record changes + Bug #7088: Deleting last save game of last character doesn't clear character name/details + Bug #7092: BSA archives from higher priority directories don't take priority + Bug #7122: Teleportation to underwater should cancel active water walking effect + Bug #7131: MyGUI log spam when post processing HUD is open + Bug #7163: Myar Aranath: Wheat breaks the GUI + Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty + Bug #7229: Error marker loading failure is not handled + Bug #7243: Supporting loading external files from VFS from esm files + Bug #7298: Water ripples from projectiles sometimes are not spawned + Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes + Bug #7413: Generated wilderness cells don't spawn fish + Bug #7415: Unbreakable lock discrepancies + Bug #7428: AutoCalc flag is not used to calculate enchantment costs + Feature #3537: Shader-based water ripples + Feature #5492: Let rain and snow collide with statics + Feature #6447: Add LOD support to Object Paging + Feature #6491: Add support for Qt6 + Feature #6726: Lua API for creating new objects + Feature #6922: Improve launcher appearance + Feature #6933: Support high-resolution cursor textures + Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData + Feature #6979: Add support of loading and displaying LOD assets purely based on their filename extension + Feature #6983: PCVisionBonus script functions + Feature #6995: Localize the "show effect duration" option + Feature #7058: Implement TestModels (T3D) console command + Feature #7087: Block resolution change in the Windowed Fullscreen mode + Feature #7125: Remembering console commands between sessions + Feature #7129: Add support for non-adaptive VSync + Feature #7130: Ability to set MyGUI logging verbosity + Feature #7148: Optimize string literal lookup in mwscript + Feature #7194: Ori to show texture paths + Feature #7214: Searching in the in-game console + Task #7113: Move from std::atoi to std::from_char + Task #7117: Replace boost::scoped_array with std::vector + Task #7151: Do not use std::strerror to get errno error message + Task #7394: Drop support for --fs-strict + +0.48.0 +------ + + Bug #1751: Birthsign abilities increase modified attribute values instead of base ones + Bug #1930: Followers are still fighting if a target stops combat with a leader + Bug #2036: SetStat and ModStat instructions aren't implemented the same way as in Morrowind + Bug #3246: ESSImporter: Most NPCs are dead on save load + Bug #3488: AI combat aiming is too slow + Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear + Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) + Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change + Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes + Bug #3855: AI sometimes spams defensive spells + Bug #3867: All followers attack player when one follower enters combat with player + Bug #3905: Great House Dagoth issues + Bug #4175: Objects "vibrate" when extremely far from (0,0) + Bug #4203: Resurrecting an actor doesn't close the loot GUI + Bug #4227: Spellcasting restrictions are checked before spellcasting animations are played + Bug #4310: Spell description is centered + Bug #4374: Player rotation reset when nearing area that hasn't been loaded yet + Bug #4376: Moved actors don't respawn in their original cells + Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node + Bug #4526: Crash when additional maps are applied over a model with out of bounds UV + Bug #4602: Robert's Bodies: crash inside createInstance() + Bug #4700: OpenMW-CS: Incorrect command implementation + Bug #4744: Invisible particles aren't always processed + Bug #4949: Incorrect particle lighting + Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations + Bug #5088: Sky abruptly changes direction during certain weather transitions + Bug #5100: Persuasion doesn't always clamp the resulting disposition + Bug #5120: Scripted object spawning updates physics system + Bug #5192: Actor turn rate is too slow + Bug #5207: Loose summons can be present in scene + Bug #5279: Ingame console stops auto-scrolling after clicking output + Bug #5318: Aiescort behaves differently from vanilla + Bug #5377: Console does not appear after using menutest in inventory + Bug #5379: Wandering NPCs falling through cantons + Bug #5394: Windows snapping no longer works + Bug #5434: Pinned windows shouldn't cover breath progress bar + Bug #5453: Magic effect VFX are offset for creatures + Bug #5483: AutoCalc flag is not used to calculate spells cost + Bug #5508: Engine binary links to Qt without using it + Bug #5592: Weapon idle animations do not work properly + Bug #5596: Effects in constant spells should not be merged + Bug #5621: Drained stats cannot be restored + Bug #5766: Active grid object paging - disappearing textures + Bug #5788: Texture editing parses the selected indexes wrongly + Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention + Bug #5842: GetDisposition adds temporary disposition change from different actors + Bug #5858: Visible modal windows and dropdowns crashing game on exit + Bug #5863: GetEffect should return true after the player has teleported + Bug #5913: Failed assertion during Ritual of Trees quest + Bug #5937: Lights always need to be rotated by 90 degrees + Bug #5976: Invisibility is broken when the attack starts instead of when it ends + Bug #5978: NPCs and Creatures talk to and headtrack a player character with a 75% chameleon effect or more + Bug #5989: Simple water isn't affected by texture filter settings + Bug #6037: Launcher: Morrowind content language cannot be set to English + Bug #6049: Main Theme on OpenMW should begin on the second video like Vanilla. + Bug #6051: NaN water height in ESM file is not handled gracefully + Bug #6054: Hotkey items can be equipped while in ready to attack stance + Bug #6066: Addtopic "return" does not work from within script. No errors thrown + Bug #6067: ESP loader fails for certain subrecord orders + Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends + Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime + Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed + Bug #6109: Crash when playing a custom made menu_background file + Bug #6115: Showmap overzealous matching + Bug #6118: Creature landing sound counts as a footstep + Bug #6123: NPC with broken script freezes the game on hello + Bug #6129: Player avatar not displayed correctly for large window sizes when GUI scaling active + Bug #6131: Item selection in the avatar window not working correctly for large window sizes + Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player + Bug #6142: Groundcover plugins change cells flags + Bug #6143: Capturing a screenshot renders the engine temporarily unresponsive + Bug #6154: Levitating player character is floating rather than on the floor when teleported back from Magas Volar + Bug #6165: Paralyzed player character can pickup items when the inventory is open + Bug #6168: Weather particles flicker for a frame at start of storms + Bug #6172: Some creatures can't open doors + Bug #6174: Spellmaking and Enchanting sliders differences from vanilla + Bug #6177: Followers of player follower stop following after waiting for a day + Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla + Bug #6191: Encumbrance messagebox timer works incorrectly + Bug #6197: Infinite Casting Loop + Bug #6253: Multiple instances of Reflect stack additively + Bug #6255: Reflect is different from vanilla + Bug #6256: Crash on exit with enabled shadows and statically linked OpenSceneGraph + Bug #6258: Barter menu glitches out when modifying prices + Bug #6273: Respawning NPCs rotation is inconsistent + Bug #6276: Deleted groundcover instances are not deleted in game + Bug #6282: Laura craft doesn't follow the player character + Bug #6283: Avis Dorsey follows you after her death + Bug #6285: OpenMW-CS: Brush template drawing and terrain selection drawing performance is very bad + Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod + Bug #6302: Teleporting disabled actor breaks its disabled state + Bug #6303: After "go to jail" weapon can be stuck in the ready to attack state + Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken + Bug #6321: Arrow enchantments should always be applied to the target + Bug #6322: Total sold/cost should reset to 0 when there are no items offered + Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house + Bug #6324: Special Slave Companions: Can't buy the slave companions + Bug #6326: Detect Enchantment/Key should detect items in unresolved containers + Bug #6327: Blocking roots the character in place + Bug #6333: Werewolf stat changes should be implemented as damage/fortifications + Bug #6343: Magic projectile speed doesn't take race weight into account + Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures + Bug #6354: SFX abruptly cut off after crossing max distance + Bug #6358: Changeweather command does not report an error when entering non-existent region + Bug #6363: Some scripts in Morrowland fail to work + Bug #6376: Creatures should be able to use torches + Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation + Bug #6389: Maximum light distance setting doesn't affect water reflections + Bug #6395: Translations with longer tab titles may cause tabs to disappear from the options menu + Bug #6396: Inputting certain Unicode characters triggers an assertion + Bug #6416: Morphs are applied to the wrong target + Bug #6417: OpenMW doesn't always use the right node to accumulate movement + Bug #6429: Wyrmhaven: Can't add AI packages to player + Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened + Bug #6451: Weapon summoned from Cast When Used item will have the name "None" + Bug #6473: Strings from NIF should be parsed only to first null terminator + Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime + Bug #6517: Rotations for KeyframeData in NIFs should be optional + Bug #6519: Effects tooltips for ingredients work incorrectly + Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary + Bug #6544: Far from world origin objects jitter when camera is still + Bug #6545: Player character momentum is preserved when going to a different cell + Bug #6559: Weapon condition inconsistency between melee and ranged critical / sneak / KO attacks + Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere + Bug #6606: Quests with multiple IDs cannot always be restarted + Bug #6653: With default settings the in-game console doesn't fit into screen + Bug #6655: Constant effect absorb attribute causes the game to break + Bug #6667: Pressing the Esc key while resting or waiting causes black screen. + Bug #6670: Dialogue order is incorrect + Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer + Bug #6682: HitOnMe doesn't fire as intended + Bug #6697: Shaders vertex lighting incorrectly clamped + Bug #6705: OpenMW CS: A typo in the Creature levelled list + Bug #6711: Log time differs from real time + Bug #6717: Broken script causes interpreter stack corruption + Bug #6718: Throwable weapons cause arrow enchantment effect to be applied to the whole body + Bug #6730: LoopGroup stalls animation after playing :Stop frame until another animation is played + Bug #6753: Info records without a DATA subrecords are loaded incorrectly + Bug #6794: Light sources are attached to mesh bounds centers instead of mesh origins when AttachLight NiNode is missing + Bug #6799: Game crashes if an NPC has no Class attached + Bug #6849: ImageButton texture is not scaled properly + Bug #6860: Sinnammu randomly strafes while running on water + Bug #6869: Hits queue stagger during swing animation + Bug #6890: SDL_PeepEvents errors are not handled + Bug #6895: Removing a negative number of items from a script, makes the script terminate with an error + Bug #6896: Sounds played using PlaySound3D are cut off as the emitter leaves the cell + Bug #6898: Accessing the Quick Inventory menu does not work while in menu mode + Bug #6901: Morrowind.exe soul gem usage discrepancy + Bug #6909: Using enchanted items has no animation + Bug #6910: Torches should not be extinguished when not being held + Bug #6913: Constant effect enchanted items don't break invisibility + Bug #6923: Dispose of corpse prevents respawning after load + Bug #6937: Divided by Nix Hounds quest is broken + Bug #7008: Race condition on initializing a vector of reserved node names + Bug #7121: Crash on TimeStamp construction with invalid hour value + Bug #7251: Force shaders setting still renders some drawables with FFP + Feature #890: OpenMW-CS: Column filtering + Feature #1465: "Reset" argument for AI functions + Feature #2491: Ability to make OpenMW "portable" + Feature #2554: OpenMW-CS: Modifying an object in the cell view should trigger the instances table to scroll to the corresponding record + Feature #2766: Warn user if their version of Morrowind is not the latest. + Feature #2780: A way to see current OpenMW version in the console + Feature #2858: Add a tab to the launcher for handling datafolders + Feature #3180: Support uncompressed colour-mapped TGA files + Feature #3245: OpenMW-CS: Instance editing grid + Feature #3616: Allow Zoom levels on the World Map + Feature #3668: Support palettized DDS files + Feature #4067: Post Processing + Feature #4297: Implement APPLIED_ONCE flag for magic effects + Feature #4414: Handle duration of EXTRA SPELL magic effect + Feature #4595: Unique object identifier + Feature #4974: Overridable MyGUI layout + Feature #4975: Built-in TrueType fonts + Feature #5198: Implement "Magic effect expired" event + Feature #5454: Clear active spells from actor when he disappears from scene + Feature #5489: MCP: Telekinesis fix for activators + Feature #5701: Convert osgAnimation::RigGeometry to double-buffered custom version + Feature #5737: OpenMW-CS: Handle instance move from one cell to another + Feature #5928: Allow Glow in the Dahrk to be disabled + Feature #5996: Support Lua scripts in OpenMW + Feature #6017: Separate persistent and temporary cell references when saving + Feature #6019: Add antialias alpha test to the launcher or enable by default if possible + Feature #6032: Reverse-z depth buffer + Feature #6078: Do not clear depth buffer for first-person meshes + Feature #6128: Soft Particles + Feature #6171: In-game log viewer + Feature #6189: Navigation mesh disk cache + Feature #6199: Support FBO Rendering + Feature #6248: Embedded error marker mesh + Feature #6249: Alpha testing support for Collada + Feature #6251: OpenMW-CS: Set instance movement based on camera zoom + Feature #6288: OpenMW-CS: Preserve "blocked" record flags when saving + Feature #6360: More realistic raindrop ripples + Feature #6380: Treat commas as whitespace in scripts + Feature #6419: Don't grey out topics if they can produce another topic reference + Feature #6443: Support NiStencilProperty + Feature #6496: Handle NCC flag in NIF files + Feature #6534: Shader-based object texture blending + Feature #6541: Gloss-mapping + Feature #6557: Add support for controller gyroscope + Feature #6592: Support for NiTriShape particle emitters + Feature #6600: Support NiSortAdjustNode + Feature #6631: Support FFMPEG 5 + Feature #6684: Support NiFltAnimationNode + Feature #6699: Support Ignored flag + Feature #6700: Support windowed fullscreen + Feature #6706: Save the size of the Options window + Feature #6721: OpenMW-CS: Add option to open records in new window + Feature #6823: Animation layering for osgAnimation formats + Feature #6867: Add a way to localize hardcoded strings in GUI + Feature #6888: Add switch for armor degradation fix + Feature #6925: Allow to use a mouse wheel to rotate a head in the race selection menu + Feature #6941: Allow users to easily change font size and ttf resolution + Feature #7434: Exponential fog + Feature #7435: Sky blending + Task #5534: Remove support for OSG 3.4 + Task #6161: Refactor Sky to use shaders and be GLES/GL3 friendly + Task #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly + Task #6435: Add support for MSVC 2022 + Task #6564: Remove predefined data paths `data="?global?data"`, `data=./data` + 0.47.0 ------ @@ -180,6 +490,7 @@ Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes Feature #6033: Include pathgrid to navigation mesh Feature #6034: Find path based on area cost depending on NPC stats + Feature #7161: OpenMW-CS: Make adding and filtering TopicInfos easier Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation @@ -1907,6 +2218,7 @@ Bug #2025: Missing mouse-over text for non affordable items Bug #2028: [MOD: Tamriel Rebuilt] Crashing when trying to enter interior cell "Ruinous Keep, Great Hall" Bug #2029: Ienith Brothers Thiev's Guild quest journal entry not adding + Bug #3066: Editor doesn't check if IDs and other strings are longer than their hardcoded field length Feature #471: Editor: Special case implementation for top-level window with single sub-window Feature #472: Editor: Sub-Window re-use settings Feature #704: Font colors import from fallback settings diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md deleted file mode 100644 index 100bc376f..000000000 --- a/CHANGELOG_PR.md +++ /dev/null @@ -1,53 +0,0 @@ -*** PLEASE PUT YOUR ISSUE DESCRIPTION FOR DUMMIES HERE FOR REVIEW *** - -- I'm just a placeholder description (#1337) -- I'm also just a placeholder description, but I'm a more recent one (#42) - -*** - -0.47.0 ------- - -The OpenMW team is proud to announce the release of version 0.47.0! Grab it from our Downloads Page for all operating systems. ***short summary: XXX *** - -Check out the release video (***add link***) and the OpenMW-CS release video (***add link***) by the ***add flattering adjective*** Atahualpa, and see below for the full list of changes. - -Known Issues: -- To use generic Linux binaries, Qt4 and libpng12 must be installed on your system -- On macOS, launching OpenMW from OpenMW-CS requires OpenMW.app and OpenMW-CS.app to be siblings - -New Features: -- Dialogue to split item stacks now displays the name of the trapped soul for stacks of soul gems (#5362) -- Basics of Collada animations are now supported via osgAnimation plugin (#5456) - -New Editor Features: -- Instance selection modes are now implemented (centred cube, corner-dragged cube, sphere) with four user-configurable actions (select only, add to selection, remove from selection, invert selection) (#3171) - -Bug Fixes: -- NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676) -- Targetting non-unique actors in scripts is now supported (#2311) -- Guards no longer ignore attacks of invisible players but rather initiate dialogue and flee if the player resists being arrested (#4774) -- Changing the dialogue window without closing it no longer clears the dialogue history in order to allow, e.g., emulation of three-way dialogue via ForceGreeting (#5358) -- Scripts which try to start a non-existent global script now skip that step and continue execution instead of breaking (#5364) -- Selecting already equipped spells or magic items via hotkey no longer triggers the equip sound to play (#5367) -- 'Scale' argument in levelled creature lists is now taken into account when spawning creatures from such lists (#5369) -- Morrowind legacy madness: Using a key on a trapped door/container now only disarms the trap if the door/container is locked (#5370) - -Editor Bug Fixes: -- Deleted and moved objects within a cell are now saved properly (#832) -- Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357) -- Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) -- Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) -- Cell borders are now properly redrawn when undoing/redoing terrain changes (#5473) -- Loading mods now keeps the master index (#5675) -- Flicker and crashing on XFCE4 fixed (#5703) -- Collada models render properly in the Editor (#5713) -- Terrain-selection grid is now properly updated when undoing/redoing terrain changes (#6022) -- Tool outline and select/edit actions in "Terrain land editing" mode now ignore references (#6023) -- Primary-select and secondary-select actions in "Terrain land editing" mode now behave like in "Instance editing" mode (#6024) -- Using the circle brush to select terrain in the "Terrain land editing" mode no longer selects vertices outside the circle (#6035) -- Vertices at the NW and SE corners of a cell can now also be selected in "Terrain land editing" mode if the adjacent cells aren't loaded yet (#6036) - -Miscellaneous: -- Prevent save-game bloating by using an appropriate fog texture format (#5108) -- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363) diff --git a/CI/Store-Symbols.ps1 b/CI/Store-Symbols.ps1 new file mode 100644 index 000000000..6328a2a2f --- /dev/null +++ b/CI/Store-Symbols.ps1 @@ -0,0 +1,52 @@ +if (-Not (Test-Path CMakeCache.txt)) +{ + Write-Error "This script must be run from the build directory." +} + +if (-Not (Test-Path .cmake\api\v1\reply)) +{ + New-Item -Type File -Force .cmake\api\v1\query\codemodel-v2 + cmake . +} + +try +{ + Push-Location .cmake\api\v1\reply + + $index = Get-Content -Raw index-*.json | ConvertFrom-Json + + $codemodel = Get-Content -Raw $index.reply."codemodel-v2".jsonFile | ConvertFrom-Json + + $targets = @() + $codemodel.configurations | ForEach-Object { + $_.targets | ForEach-Object { + $target = Get-Content -Raw $_.jsonFile | ConvertFrom-Json + if ($target.type -eq "EXECUTABLE" -or $target.type -eq "SHARED_LIBRARY") + { + $targets += $target + } + } + } + + $artifacts = @() + $targets | ForEach-Object { + $_.artifacts | ForEach-Object { + $artifacts += $_.path + } + } +} +finally +{ + Pop-Location +} + +if (-not (Test-Path symstore-venv)) +{ + python -m venv symstore-venv +} +if (-not (Test-Path symstore-venv\Scripts\symstore.exe)) +{ + symstore-venv\Scripts\pip install symstore==0.3.3 +} +$artifacts = $artifacts | Where-Object { Test-Path $_ } +symstore-venv\Scripts\symstore --compress .\SymStore @artifacts diff --git a/CI/activate_msvc.sh b/CI/activate_msvc.sh index 233f01743..c62ea4ca6 100644 --- a/CI/activate_msvc.sh +++ b/CI/activate_msvc.sh @@ -23,11 +23,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then fi command -v unixPathAsWindows >/dev/null 2>&1 || function unixPathAsWindows { - if command -v cygpath >/dev/null 2>&1; then - cygpath -w $1 - else - echo "$1" | sed "s,^/\([^/]\)/,\\1:/," | sed "s,/,\\\\,g" - fi + if command -v cygpath >/dev/null 2>&1; then + cygpath -w $1 + else + echo "$1" | sed "s,^/\([^/]\)/,\\1:/," | sed "s,/,\\\\,g" + fi } diff --git a/CI/before_install.android.sh b/CI/before_install.android.sh index 0243a9609..712ded276 100755 --- a/CI/before_install.android.sh +++ b/CI/before_install.android.sh @@ -1,4 +1,4 @@ #!/bin/sh -ex -curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201129.zip -o ~/openmw-android-deps.zip -unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null +curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20211114.zip -o ~/openmw-android-deps.zip +unzip -o ~/openmw-android-deps -d /android-ndk-r22/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 1ca0fc611..68569039b 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,22 +1,39 @@ #!/bin/sh -ex -# workaround python issue on travis -[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true -[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true -[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true +export HOMEBREW_NO_EMOJI=1 + +brew uninstall --ignore-dependencies python@3.8 || true +brew uninstall --ignore-dependencies python@3.9 || true +brew uninstall --ignore-dependencies qt@5 || true +brew uninstall --ignore-dependencies jpeg || true + +brew tap --repair +brew update --quiet # Some of these tools can come from places other than brew, so check before installing +brew reinstall xquartz fontconfig freetype harfbuzz brotli + +# Fix: can't open file: @loader_path/libbrotlicommon.1.dylib (No such file or directory) +BREW_LIB_PATH="$(brew --prefix)/lib" +install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlidec.1.dylib +install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlienc.1.dylib + command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake -command -v qmake >/dev/null 2>&1 || brew install qt@5 -export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6 +command -v qmake >/dev/null 2>&1 || brew install qt@6 + +# Install deps +brew install icu4c yaml-cpp sqlite ccache --version cmake --version qmake --version -curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20210617.zip -o ~/openmw-deps.zip -unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null +if [[ "${MACOS_AMD64}" ]]; then + curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20221113.zip -o ~/openmw-deps.zip +else + curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20230223_arm64.zip -o ~/openmw-deps.zip +fi + +unzip -o ~/openmw-deps.zip -d /tmp > /dev/null -# additional libraries -[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfig \ No newline at end of file diff --git a/CI/before_script.android.sh b/CI/before_script.android.sh index 3ea429f1b..cbf8d6490 100755 --- a/CI/before_script.android.sh +++ b/CI/before_script.android.sh @@ -3,13 +3,31 @@ # hack to work around: FFmpeg version is too old, 3.2 is required sed -i s/"NOT FFVER_OK"/"FALSE"/ CMakeLists.txt +# Silence a git warning +git config --global advice.detachedHead false + mkdir -p build cd build +# Build a version of ICU for the host so that it can use the tools during the cross-compilation +mkdir -p icu-host-build +cd icu-host-build +if [ -r ../extern/fetched/icu/icu4c/source/configure ]; then + ICU_SOURCE_DIR=../extern/fetched/icu/icu4c/source +else + wget https://github.com/unicode-org/icu/archive/refs/tags/release-70-1.zip + unzip release-70-1.zip + ICU_SOURCE_DIR=./icu-release-70-1/icu4c/source +fi +${ICU_SOURCE_DIR}/configure --disable-tests --disable-samples --disable-icuio --disable-extras CC="ccache gcc" CXX="ccache g++" +make -j $(nproc) +cd .. + cmake \ --DCMAKE_TOOLCHAIN_FILE=/usr/lib/android-sdk/ndk-bundle/build/cmake/android.toolchain.cmake \ +-DCMAKE_TOOLCHAIN_FILE=/android-ndk-r22/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-21 \ +-DANDROID_LD=deprecated \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_INSTALL_PREFIX=install \ @@ -21,7 +39,11 @@ cmake \ -DBUILD_ESSIMPORTER=0 \ -DBUILD_OPENCS=0 \ -DBUILD_WIZARD=0 \ +-DBUILD_NAVMESHTOOL=OFF \ +-DBUILD_BULLETOBJECTTOOL=OFF \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ --DOPENMW_USE_SYSTEM_OSG=OFF \ --DOPENMW_USE_SYSTEM_BULLET=OFF \ +-DOPENMW_USE_SYSTEM_SQLITE3=OFF \ +-DOPENMW_USE_SYSTEM_YAML_CPP=OFF \ +-DOPENMW_USE_SYSTEM_ICU=OFF \ +-DOPENMW_ICU_HOST_BUILD_DIR="$(pwd)/icu-host-build" \ .. diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 44fce54b5..383875294 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -4,35 +4,81 @@ set -xeo pipefail free -m +# Silence a git warning +git config --global advice.detachedHead false + BUILD_UNITTESTS=OFF BUILD_BENCHMARKS=OFF if [[ "${BUILD_TESTS_ONLY}" ]]; then - export GOOGLETEST_DIR="${PWD}/googletest/build/install" - env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh BUILD_UNITTESTS=ON BUILD_BENCHMARKS=ON fi +# setup our basic cmake build options declare -a CMAKE_CONF_OPTS=( -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=install - -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=OFF -DUSE_SYSTEM_TINYXML=ON +<<<<<<< HEAD -DCMAKE_INSTALL_PREFIX=install -DRakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a -DRakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a +======= + -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON + -DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 ) +if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then + CMAKE_CONF_OPTS+=( + -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" + ) +fi + if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then CMAKE_CONF_OPTS+=( -DOPENMW_USE_SYSTEM_MYGUI=OFF -DOPENMW_USE_SYSTEM_OSG=OFF -DOPENMW_USE_SYSTEM_BULLET=OFF + -DOPENMW_USE_SYSTEM_SQLITE3=OFF + -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=OFF + ) +fi + +if [[ $CI_CLANG_TIDY ]]; then + CMAKE_CONF_OPTS+=( + -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*" + -DBUILD_UNITTESTS=ON + -DBUILD_OPENCS_TESTS=ON + -DBUILD_BENCHMARKS=ON + ) +fi + + +if [[ "${CMAKE_BUILD_TYPE}" ]]; then + CMAKE_CONF_OPTS+=( + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + ) +else + CMAKE_CONF_OPTS+=( + -DCMAKE_BUILD_TYPE=RelWithDebInfo + ) +fi + +if [[ "${CMAKE_CXX_FLAGS_DEBUG}" ]]; then + CMAKE_CONF_OPTS+=( + -DCMAKE_CXX_FLAGS_DEBUG="${CMAKE_CXX_FLAGS_DEBUG}" + ) +fi + +if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then + CMAKE_CONF_OPTS+=( + -DBUILD_WITH_CODE_COVERAGE="${BUILD_WITH_CODE_COVERAGE}" ) fi @@ -47,6 +93,16 @@ fi export RAKNET_ROOT=~/CrabNet if [[ "${BUILD_TESTS_ONLY}" ]]; then + + # flags specific to our test suite + CXX_FLAGS="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" + if [[ "${CXX}" == 'clang++' ]]; then + CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments" + fi + CMAKE_CONF_OPTS+=( + -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" + ) + ${ANALYZE} cmake \ "${CMAKE_CONF_OPTS[@]}" \ -DBUILD_OPENMW=OFF \ @@ -59,10 +115,27 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then -DBUILD_ESSIMPORTER=OFF \ -DBUILD_OPENCS=OFF \ -DBUILD_WIZARD=OFF \ + -DBUILD_NAVMESHTOOL=OFF \ + -DBUILD_BULLETOBJECTTOOL=OFF \ + -DBUILD_NIFTEST=OFF \ -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \ + -DBUILD_OPENCS_TESTS=${BUILD_UNITTESTS} \ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ - -DGTEST_ROOT="${GOOGLETEST_DIR}" \ - -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ + .. +elif [[ "${BUILD_OPENMW_ONLY}" ]]; then + ${ANALYZE} cmake \ + "${CMAKE_CONF_OPTS[@]}" \ + -DBUILD_OPENMW=ON \ + -DBUILD_BSATOOL=OFF \ + -DBUILD_ESMTOOL=OFF \ + -DBUILD_LAUNCHER=OFF \ + -DBUILD_MWINIIMPORTER=OFF \ + -DBUILD_ESSIMPORTER=OFF \ + -DBUILD_OPENCS=OFF \ + -DBUILD_WIZARD=OFF \ + -DBUILD_NAVMESHTOOL=OFF \ + -DBUILD_BULLETOBJECTTOOL=OFF \ + -DBUILD_NIFTEST=OFF \ .. else ${ANALYZE} cmake \ diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index bb662c9de..a1af220e8 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -62,6 +62,7 @@ VERBOSE="" STRIP="" SKIP_DOWNLOAD="" SKIP_EXTRACT="" +USE_CCACHE="" KEEP="" UNITY_BUILD="" VS_VERSION="" @@ -74,6 +75,9 @@ TEST_FRAMEWORK="" GOOGLE_INSTALL_ROOT="" INSTALL_PREFIX="." BUILD_BENCHMARKS="" +OSG_MULTIVIEW_BUILD="" +USE_WERROR="" +USE_CLANG_TIDY="" ACTIVATE_MSVC="" SINGLE_CONFIG="" @@ -100,6 +104,9 @@ while [ $# -gt 0 ]; do e ) SKIP_EXTRACT=true ;; + C ) + USE_CCACHE=true ;; + k ) KEEP=true ;; @@ -112,7 +119,7 @@ while [ $# -gt 0 ]; do n ) NMAKE=true ;; - + N ) NINJA=true ;; @@ -137,6 +144,15 @@ while [ $# -gt 0 ]; do b ) BUILD_BENCHMARKS=true ;; + M ) + OSG_MULTIVIEW_BUILD=true ;; + + E ) + USE_WERROR=true ;; + + T ) + USE_CLANG_TIDY=true ;; + h ) cat < + -v <2019/2022> Choose the Visual Studio version to use. -n Produce NMake makefiles instead of a Visual Studio solution. Cannot be used with -N. @@ -173,6 +191,12 @@ Options: CMake install prefix -b Build benchmarks + -M + Use a multiview build of OSG + -E + Use warnings as errors (/WX) + -T + Run clang-tidy EOF wrappedExit 0 ;; @@ -258,10 +282,10 @@ download() { if [ -z $VERBOSE ]; then RET=0 - curl --silent --retry 10 -Ly 5 -o $FILE $URL || RET=$? + curl --silent --fail --retry 10 -Ly 5 -o $FILE $URL || RET=$? else RET=0 - curl --retry 10 -Ly 5 -o $FILE $URL || RET=$? + curl --fail --retry 10 -Ly 5 -o $FILE $URL || RET=$? fi if [ $RET -ne 0 ]; then @@ -338,34 +362,49 @@ if [ -z $PLATFORM ]; then fi if [ -z $VS_VERSION ]; then - VS_VERSION="2017" + VS_VERSION="2019" fi case $VS_VERSION in + 17|17.0|2022 ) + GENERATOR="Visual Studio 17 2022" + TOOLSET="vc143" + MSVC_REAL_VER="17" + MSVC_VER="14.3" + MSVC_DISPLAY_YEAR="2022" + + OSG_MSVC_YEAR="2019" + MYGUI_MSVC_YEAR="2019" + LUA_MSVC_YEAR="2019" + QT_MSVC_YEAR="2019" + BULLET_MSVC_YEAR="2019" + + BOOST_VER="1.80.0" + BOOST_VER_URL="1_80_0" + BOOST_VER_SDK="108000" + ;; + 16|16.0|2019 ) GENERATOR="Visual Studio 16 2019" TOOLSET="vc142" MSVC_REAL_VER="16" MSVC_VER="14.2" - MSVC_YEAR="2015" - MSVC_REAL_YEAR="2019" MSVC_DISPLAY_YEAR="2019" - BOOST_VER="1.71.0" - BOOST_VER_URL="1_71_0" - BOOST_VER_SDK="107100" + + OSG_MSVC_YEAR="2019" + MYGUI_MSVC_YEAR="2019" + LUA_MSVC_YEAR="2019" + QT_MSVC_YEAR="2019" + BULLET_MSVC_YEAR="2019" + + BOOST_VER="1.80.0" + BOOST_VER_URL="1_80_0" + BOOST_VER_SDK="108000" ;; 15|15.0|2017 ) - GENERATOR="Visual Studio 15 2017" - TOOLSET="vc141" - MSVC_REAL_VER="15" - MSVC_VER="14.1" - MSVC_YEAR="2015" - MSVC_REAL_YEAR="2017" - MSVC_DISPLAY_YEAR="2017" - BOOST_VER="1.67.0" - BOOST_VER_URL="1_67_0" - BOOST_VER_SDK="106700" + echo "Visual Studio 2017 is no longer supported" + wrappedExit 1 ;; 14|14.0|2015 ) @@ -398,10 +437,6 @@ case $PLATFORM in ;; esac -if [ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ]; then - GENERATOR="${GENERATOR} Win64" -fi - if [ -n "$NMAKE" ]; then GENERATOR="NMake Makefiles" SINGLE_CONFIG=true @@ -485,7 +520,7 @@ for i in ${!CONFIGURATIONS[@]}; do esac done -if [ $MSVC_REAL_VER -ge 16 ] && [ -z "$NMAKE" ] && [ -z "$NINJA" ]; then +if [ -z "$NMAKE" ] && [ -z "$NINJA" ]; then if [ $BITS -eq 64 ]; then add_cmake_opts "-G\"$GENERATOR\" -A x64" else @@ -503,6 +538,39 @@ if ! [ -z $UNITY_BUILD ]; then add_cmake_opts "-DOPENMW_UNITY_BUILD=True" fi +if ! [ -z $USE_CCACHE ]; then + add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" +fi + +# turn on LTO by default +add_cmake_opts "-DOPENMW_LTO_BUILD=True" + +if ! [ -z "$USE_WERROR" ]; then + add_cmake_opts "-DOPENMW_MSVC_WERROR=ON" +fi + +if ! [ -z "$USE_CLANG_TIDY" ]; then + add_cmake_opts "-DCMAKE_CXX_CLANG_TIDY=\"clang-tidy --warnings-as-errors=*\"" +fi + +BULLET_VER="2.89" +FFMPEG_VER="4.2.2" +ICU_VER="70_1" +LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd" +LZ4_VER="1.9.2" +OPENAL_VER="1.23.0" +QT_VER="5.15.2" + +OSG_ARCHIVE_NAME="OSGoS 3.6.5" +OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}" +OSG_ARCHIVE_REPO_URL="https://gitlab.com/OpenMW/openmw-deps/-/raw/main" +if ! [ -z $OSG_MULTIVIEW_BUILD ]; then + OSG_ARCHIVE_NAME="OSG-3.6-multiview" + OSG_ARCHIVE="OSG-3.6-multiview-d2ee5aa8-msvc${OSG_MSVC_YEAR}-win${BITS}" + OSG_ARCHIVE_REPO_URL="https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr-ovr_multiview" +fi + + echo echo "===================================" echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}" @@ -527,63 +595,67 @@ if [ -z $SKIP_DOWNLOAD ]; then fi # Bullet - download "Bullet 2.89" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z" \ - "Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z" + download "Bullet ${BULLET_VER}" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-${BULLET_VER}-msvc${BULLET_MSVC_YEAR}-win${BITS}-double-mt.7z" \ + "Bullet-${BULLET_VER}-msvc${BULLET_MSVC_YEAR}-win${BITS}-double-mt.7z" # FFmpeg - download "FFmpeg 4.2.2" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-win${BITS}.zip" \ - "ffmpeg-4.2.2-win${BITS}.zip" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-dev-win${BITS}.zip" \ - "ffmpeg-4.2.2-dev-win${BITS}.zip" + download "FFmpeg ${FFMPEG_VER}" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-${FFMPEG_VER}-win${BITS}.zip" \ + "ffmpeg-${FFMPEG_VER}-win${BITS}.zip" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-${FFMPEG_VER}-dev-win${BITS}.zip" \ + "ffmpeg-${FFMPEG_VER}-dev-win${BITS}.zip" # MyGUI - download "MyGUI 3.4.0" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \ - "MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" + download "MyGUI 3.4.1" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" \ + "MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" if [ -n "$PDBS" ]; then download "MyGUI symbols" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \ - "MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" \ + "MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" fi # OpenAL - download "OpenAL-Soft 1.20.1" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.20.1.zip" \ - "OpenAL-Soft-1.20.1.zip" + download "OpenAL-Soft ${OPENAL_VER}" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-${OPENAL_VER}.zip" \ + "OpenAL-Soft-${OPENAL_VER}.zip" - # OSG - download "OpenSceneGraph 3.6.5" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \ - "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" + # OSGoS + download "${OSG_ARCHIVE_NAME}" \ + "${OSG_ARCHIVE_REPO_URL}/windows/${OSG_ARCHIVE}.7z" \ + "${OSG_ARCHIVE}.7z" if [ -n "$PDBS" ]; then - download "OpenSceneGraph symbols" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \ - "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" + download "${OSG_ARCHIVE_NAME} symbols" \ + "${OSG_ARCHIVE_REPO_URL}/windows/${OSG_ARCHIVE}-sym.7z" \ + "${OSG_ARCHIVE}-sym.7z" fi # SDL2 - download "SDL 2.0.12" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.12.zip" \ - "SDL2-2.0.12.zip" + download "SDL 2.24.0" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-devel-2.24.0-VC.zip" \ + "SDL2-devel-2.24.0-VC.zip" # LZ4 - download "LZ4 1.9.2" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/lz4_win${BITS}_v1_9_2.7z" \ - "lz4_win${BITS}_v1_9_2.7z" + download "LZ4 ${LZ4_VER}" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/lz4_win${BITS}_v${LZ4_VER//./_}.7z" \ + "lz4_win${BITS}_v${LZ4_VER//./_}.7z" - # Google test and mock - if [ ! -z $TEST_FRAMEWORK ]; then - echo "Google test 1.10.0..." - if [ -d googletest ]; then - printf " Google test exists, skipping." - else - git clone -b release-1.10.0 https://github.com/google/googletest.git - fi - fi + # LuaJIT + download "LuaJIT ${LUAJIT_VER}" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/LuaJIT-${LUAJIT_VER}-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" \ + "LuaJIT-${LUAJIT_VER}-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" + + # ICU + download "ICU ${ICU_VER/_/.}"\ + "https://github.com/unicode-org/icu/releases/download/release-${ICU_VER/_/-}/icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" \ + "icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" + + download "zlib 1.2.11"\ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/zlib-1.2.11-msvc2017-win64.7z" \ + "zlib-1.2.11-msvc2017-win64.7z" fi cd .. #/.. @@ -620,7 +692,6 @@ echo "---------------------------------------------------" echo -# Boost if [ -z $APPVEYOR ]; then printf "Boost ${BOOST_VER}... " else @@ -668,35 +739,33 @@ fi } cd $DEPS echo -# Bullet -printf "Bullet 2.89... " +printf "Bullet ${BULLET_VER}... " { cd $DEPS_INSTALL if [ -d Bullet ]; then printf -- "Exists. (No version checking) " elif [ -z $SKIP_EXTRACT ]; then rm -rf Bullet - eval 7z x -y "${DEPS}/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z" $STRIP - mv "Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double" Bullet + eval 7z x -y "${DEPS}/Bullet-${BULLET_VER}-msvc${BULLET_MSVC_YEAR}-win${BITS}-double-mt.7z" $STRIP + mv "Bullet-${BULLET_VER}-msvc${BULLET_MSVC_YEAR}-win${BITS}-double-mt" Bullet fi add_cmake_opts -DBULLET_ROOT="$(real_pwd)/Bullet" echo Done. } cd $DEPS echo -# FFmpeg -printf "FFmpeg 4.2.2... " +printf "FFmpeg ${FFMPEG_VER}... " { cd $DEPS_INSTALL - if [ -d FFmpeg ] && grep "4.2.2" FFmpeg/README.txt > /dev/null; then + if [ -d FFmpeg ] && grep "${FFMPEG_VER}" FFmpeg/README.txt > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf FFmpeg - eval 7z x -y "${DEPS}/ffmpeg-4.2.2-win${BITS}.zip" $STRIP - eval 7z x -y "${DEPS}/ffmpeg-4.2.2-dev-win${BITS}.zip" $STRIP - mv "ffmpeg-4.2.2-win${BITS}-shared" FFmpeg - cp -r "ffmpeg-4.2.2-win${BITS}-dev/"* FFmpeg/ - rm -rf "ffmpeg-4.2.2-win${BITS}-dev" + eval 7z x -y "${DEPS}/ffmpeg-${FFMPEG_VER}-win${BITS}.zip" $STRIP + eval 7z x -y "${DEPS}/ffmpeg-${FFMPEG_VER}-dev-win${BITS}.zip" $STRIP + mv "ffmpeg-${FFMPEG_VER}-win${BITS}-shared" FFmpeg + cp -r "ffmpeg-${FFMPEG_VER}-win${BITS}-dev/"* FFmpeg/ + rm -rf "ffmpeg-${FFMPEG_VER}-win${BITS}-dev" fi export FFMPEG_HOME="$(real_pwd)/FFmpeg" for config in ${CONFIGURATIONS[@]}; do @@ -709,21 +778,20 @@ printf "FFmpeg 4.2.2... " } cd $DEPS echo -# MyGUI -printf "MyGUI 3.4.0... " +printf "MyGUI 3.4.1... " { cd $DEPS_INSTALL if [ -d MyGUI ] && \ grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ grep "MYGUI_VERSION_MINOR 4" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ - grep "MYGUI_VERSION_PATCH 0" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null + grep "MYGUI_VERSION_PATCH 1" 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.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP - [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP - mv "MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}" MyGUI + eval 7z x -y "${DEPS}/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" $STRIP + [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" $STRIP + mv "MyGUI-3.4.1-msvc${MYGUI_MSVC_YEAR}-win${BITS}" MyGUI fi export MYGUI_HOME="$(real_pwd)/MyGUI" for CONFIGURATION in ${CONFIGURATIONS[@]}; do @@ -740,27 +808,25 @@ printf "MyGUI 3.4.0... " } cd $DEPS echo -# OpenAL -printf "OpenAL-Soft 1.20.1... " +printf "OpenAL-Soft ${OPENAL_VER}... " { - if [ -d openal-soft-1.20.1-bin ]; then + if [ -d openal-soft-${OPENAL_VER}-bin ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf openal-soft-1.20.1-bin - eval 7z x -y OpenAL-Soft-1.20.1.zip $STRIP + rm -rf openal-soft-${OPENAL_VER}-bin + eval 7z x -y OpenAL-Soft-${OPENAL_VER}.zip $STRIP fi - OPENAL_SDK="$(real_pwd)/openal-soft-1.20.1-bin" + OPENAL_SDK="$(real_pwd)/openal-soft-${OPENAL_VER}-bin" add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \ -DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib" for config in ${CONFIGURATIONS[@]}; do - add_runtime_dlls $config "$(pwd)/openal-soft-1.20.1-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll" + add_runtime_dlls $config "$(pwd)/openal-soft-${OPENAL_VER}-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll" done echo Done. } cd $DEPS echo -# OSG -printf "OSG 3.6.5... " +printf "${OSG_ARCHIVE_NAME}... " { cd $DEPS_INSTALL if [ -d OSG ] && \ @@ -768,157 +834,147 @@ printf "OSG 3.6.5... " grep "OPENSCENEGRAPH_MINOR_VERSION 6" OSG/include/osg/Version > /dev/null && \ grep "OPENSCENEGRAPH_PATCH_VERSION 5" OSG/include/osg/Version > /dev/null then - printf "Exists. " + printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf OSG - eval 7z x -y "${DEPS}/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP - [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP - mv "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}" OSG + eval 7z x -y "${DEPS}/${OSG_ARCHIVE}.7z" $STRIP + [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/${OSG_ARCHIVE}-sym.7z" $STRIP + mv "${OSG_ARCHIVE}" OSG fi OSG_SDK="$(real_pwd)/OSG" add_cmake_opts -DOSG_DIR="$OSG_SDK" for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then SUFFIX="d" + SUFFIX_UPCASE="D" else SUFFIX="" + SUFFIX_UPCASE="" fi - add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \ - "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll - add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll + + if ! [ -z $OSG_MULTIVIEW_BUILD ]; then + add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{ot21-OpenThreads,libpng16}${SUFFIX}.dll \ + "$(pwd)/OSG/bin/osg162-osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow,Sim}${SUFFIX}.dll + else + add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,icuuc58,libpng16}${SUFFIX}.dll \ + "$(pwd)/OSG/bin/libxml2"${SUFFIX_UPCASE}.dll \ + "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow,Sim}${SUFFIX}.dll + add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/icudt58.dll" + if [ $CONFIGURATION == "Debug" ]; then + add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{boost_system-vc141-mt-gd-1_63,collada-dom2.4-dp-vc141-mt-d}.dll + else + add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{boost_system-vc141-mt-1_63,collada-dom2.4-dp-vc141-mt}.dll + fi + fi + add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dae,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll done echo Done. } cd $DEPS echo -# Qt -if [ -z $APPVEYOR ]; then - printf "Qt 5.15.0... " -else - printf "Qt 5.13 AppVeyor... " -fi +printf "Qt ${QT_VER}... " { if [ $BITS -eq 64 ]; then SUFFIX="_64" else SUFFIX="" fi - if [ -z $APPVEYOR ]; then - cd $DEPS_INSTALL - qt_version="5.15.0" - if [ "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" == "win64_msvc2017_64" ]; then - echo "This combination of options is known not to work. Falling back to Qt 5.14.2." - qt_version="5.14.2" - fi + cd $DEPS_INSTALL - QT_SDK="$(real_pwd)/Qt/${qt_version}/msvc${MSVC_REAL_YEAR}${SUFFIX}" - if [ -d "Qt/${qt_version}" ]; then - printf "Exists. " - elif [ -z $SKIP_EXTRACT ]; then - if [ $MISSINGPYTHON -ne 0 ]; then - echo "Can't be automatically installed without Python." - wrappedExit 1 - fi + QT_SDK="$(real_pwd)/Qt/${QT_VER}/msvc${QT_MSVC_YEAR}${SUFFIX}" - pushd "$DEPS" > /dev/null - if ! [ -d 'aqt-venv' ]; then - echo " Creating Virtualenv for aqt..." - run_cmd python -m venv aqt-venv - fi - if [ -d 'aqt-venv/bin' ]; then - VENV_BIN_DIR='bin' - elif [ -d 'aqt-venv/Scripts' ]; then - VENV_BIN_DIR='Scripts' - else - echo "Error: Failed to create virtualenv in expected location." - wrappedExit 1 - fi - - # check version - aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ] - if [ $? -eq 0 ]; then - echo " Installing aqt wheel into virtualenv..." - run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 - fi - popd > /dev/null - - rm -rf Qt - - mkdir Qt - cd Qt - - run_cmd "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install $qt_version windows desktop "win${BITS}_msvc${MSVC_REAL_YEAR}${SUFFIX}" - - printf " Cleaning up extraneous data... " - rm -rf Qt/{aqtinstall.log,Tools} - - echo Done. - fi - - cd $QT_SDK - add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ - -DCMAKE_PREFIX_PATH="$QT_SDK" - for CONFIGURATION in ${CONFIGURATIONS[@]}; do - if [ $CONFIGURATION == "Debug" ]; then - DLLSUFFIX="d" - else - DLLSUFFIX="" - fi - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll - add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" - add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" - done - echo Done. - else - QT_SDK="C:/Qt/5.13/msvc2017${SUFFIX}" - add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ - -DCMAKE_PREFIX_PATH="$QT_SDK" - for CONFIGURATION in ${CONFIGURATIONS[@]}; do - if [ $CONFIGURATION == "Debug" ]; then - DLLSUFFIX="d" - else - DLLSUFFIX="" - fi - DIR=$(windowsPathAsUnix "${QT_SDK}") - add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll - add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll" - add_qt_style_dlls $CONFIGURATION "${DIR}/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" - done - echo Done. - fi -} -cd $DEPS -echo -# SDL2 -printf "SDL 2.0.12... " -{ - if [ -d SDL2-2.0.12 ]; then + if [ -d "Qt/${QT_VER}" ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf SDL2-2.0.12 - eval 7z x -y SDL2-2.0.12.zip $STRIP + if [ $MISSINGPYTHON -ne 0 ]; then + echo "Can't be automatically installed without Python." + wrappedExit 1 + fi + + pushd "$DEPS" > /dev/null + if ! [ -d 'aqt-venv' ]; then + echo " Creating Virtualenv for aqt..." + run_cmd python -m venv aqt-venv + fi + if [ -d 'aqt-venv/bin' ]; then + VENV_BIN_DIR='bin' + elif [ -d 'aqt-venv/Scripts' ]; then + VENV_BIN_DIR='Scripts' + else + echo "Error: Failed to create virtualenv in expected location." + wrappedExit 1 + fi + + # check version + aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ] + if [ $? -eq 0 ]; then + echo " Installing aqt wheel into virtualenv..." + run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 + fi + popd > /dev/null + + rm -rf Qt + + mkdir Qt + cd Qt + + run_cmd "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install ${QT_VER} windows desktop "win${BITS}_msvc${QT_MSVC_YEAR}${SUFFIX}" + + printf " Cleaning up extraneous data... " + rm -rf Qt/{aqtinstall.log,Tools} + + echo Done. fi - export SDL2DIR="$(real_pwd)/SDL2-2.0.12" - for config in ${CONFIGURATIONS[@]}; do - add_runtime_dlls $config "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll" + + cd $QT_SDK + add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ + -DCMAKE_PREFIX_PATH="$QT_SDK" + for CONFIGURATION in ${CONFIGURATIONS[@]}; do + if [ $CONFIGURATION == "Debug" ]; then + DLLSUFFIX="d" + else + DLLSUFFIX="" + fi + if [ "${QT_VER:0:1}" -eq "6" ]; then + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets}${DLLSUFFIX}.dll + else + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll + fi + add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" done echo Done. } cd $DEPS echo -# LZ4 -printf "LZ4 1.9.2... " +printf "SDL 2.24.0... " { - if [ -d LZ4_1.9.2 ]; then + if [ -d SDL2-2.24.0 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf LZ4_1.9.2 - eval 7z x -y lz4_win${BITS}_v1_9_2.7z -o$(real_pwd)/LZ4_1.9.2 $STRIP + rm -rf SDL2-2.24.0 + eval 7z x -y SDL2-devel-2.24.0-VC.zip $STRIP fi - export LZ4DIR="$(real_pwd)/LZ4_1.9.2" + export SDL2DIR="$(real_pwd)/SDL2-2.24.0" + for config in ${CONFIGURATIONS[@]}; do + add_runtime_dlls $config "$(pwd)/SDL2-2.24.0/lib/x${ARCHSUFFIX}/SDL2.dll" + done + echo Done. +} +cd $DEPS +echo +printf "LZ4 ${LZ4_VER}... " +{ + if [ -d LZ4_${LZ4_VER} ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf LZ4_${LZ4_VER} + eval 7z x -y lz4_win${BITS}_v${LZ4_VER//./_}.7z -o$(real_pwd)/LZ4_${LZ4_VER} $STRIP + fi + export LZ4DIR="$(real_pwd)/LZ4_${LZ4_VER}" add_cmake_opts -DLZ4_INCLUDE_DIR="${LZ4DIR}/include" \ -DLZ4_LIBRARY="${LZ4DIR}/lib/liblz4.lib" for CONFIGURATION in ${CONFIGURATIONS[@]}; do @@ -928,65 +984,74 @@ printf "LZ4 1.9.2... " SUFFIX="" LZ4_CONFIGURATION="Release" fi - add_runtime_dlls $CONFIGURATION "$(pwd)/LZ4_1.9.2/bin/${LZ4_CONFIGURATION}/liblz4.dll" + add_runtime_dlls $CONFIGURATION "$(pwd)/LZ4_${LZ4_VER}/bin/${LZ4_CONFIGURATION}/liblz4.dll" done echo Done. } cd $DEPS echo -# Google Test and Google Mock -if [ ! -z $TEST_FRAMEWORK ]; then - printf "Google test 1.10.0 ..." - - cd googletest - mkdir -p build${MSVC_REAL_YEAR} - - cd build${MSVC_REAL_YEAR} - - GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest" - +printf "LuaJIT ${LUAJIT_VER}... " +{ + if [ -d LuaJIT ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf LuaJIT + eval 7z x -y LuaJIT-${LUAJIT_VER}-msvc${LUA_MSVC_YEAR}-win${BITS}.7z -o$(real_pwd)/LuaJIT $STRIP + fi + export LUAJIT_DIR="$(real_pwd)/LuaJIT" + add_cmake_opts -DLuaJit_INCLUDE_DIR="${LUAJIT_DIR}/include" \ + -DLuaJit_LIBRARY="${LUAJIT_DIR}/lib/lua51.lib" for CONFIGURATION in ${CONFIGURATIONS[@]}; do - # FindGMock.cmake mentions Release explicitly, but not RelWithDebInfo. Only one optimised library config can be used, so go for the safer one. - GTEST_CONFIG=$([ $CONFIGURATION == "RelWithDebInfo" ] && echo "Release" || echo "$CONFIGURATION" ) - if [ $GTEST_CONFIG == "Debug" ]; then - DEBUG_SUFFIX="d" - else - DEBUG_SUFFIX="" - fi - - if [ ! -f "$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib" ]; then - # Always use MSBuild solution files as they don't need the environment activating - cmake .. -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "Visual Studio $MSVC_REAL_VER $MSVC_REAL_YEAR$([ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ] && echo " Win64")" $([ $MSVC_REAL_VER -ge 16 ] && echo "-A $([ $BITS -eq 64 ] && echo "x64" || echo "Win32")") -DBUILD_SHARED_LIBS=1 - cmake --build . --config "${GTEST_CONFIG}" - cmake --install . --config "${GTEST_CONFIG}" --prefix "${GOOGLE_INSTALL_ROOT}" - fi - - add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll" - add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll" - add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll" - add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll" + add_runtime_dlls $CONFIGURATION "$(pwd)/LuaJIT/bin/lua51.dll" done - - add_cmake_opts -DBUILD_UNITTESTS=yes - # FindGTest and FindGMock do not work perfectly on Windows - # but we can help them by telling them everything we know about installation - add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT" - add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT" - add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest.lib" - add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main.lib" - add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock.lib" - add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main.lib" - add_cmake_opts -DGTEST_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtestd.lib" - add_cmake_opts -DGTEST_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtest_maind.lib" - add_cmake_opts -DGMOCK_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmockd.lib" - add_cmake_opts -DGMOCK_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmock_maind.lib" - add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True - add_cmake_opts -DGTEST_LIBRARY_TYPE=SHARED - add_cmake_opts -DGTEST_MAIN_LIBRARY_TYPE=SHARED - echo Done. +} -fi +cd $DEPS +echo +printf "ICU ${ICU_VER/_/.}... " +{ + if [ -d ICU-${ICU_VER} ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf ICU-${ICU_VER} + eval 7z x -y icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip -o$(real_pwd)/ICU-${ICU_VER} $STRIP + fi + ICU_ROOT="$(real_pwd)/ICU-${ICU_VER}" + add_cmake_opts -DICU_ROOT="${ICU_ROOT}" \ + -DICU_INCLUDE_DIR="${ICU_ROOT}/include" \ + -DICU_I18N_LIBRARY="${ICU_ROOT}/lib${BITS}/icuin.lib " \ + -DICU_UC_LIBRARY="${ICU_ROOT}/lib${BITS}/icuuc.lib " \ + -DICU_DEBUG=ON + + for config in ${CONFIGURATIONS[@]}; do + add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icudt${ICU_VER/_*/}.dll" + add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icuin${ICU_VER/_*/}.dll" + add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icuuc${ICU_VER/_*/}.dll" + done + echo Done. +} + +cd $DEPS +echo +printf "zlib 1.2.11... " +{ + if [ -d zlib-1.2.11-msvc2017-win64 ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf zlib-1.2.11-msvc2017-win64 + eval 7z x -y zlib-1.2.11-msvc2017-win64.7z $STRIP + fi + add_cmake_opts -DZLIB_ROOT="$(real_pwd)/zlib-1.2.11-msvc2017-win64" + for config in ${CONFIGURATIONS[@]}; do + if [ $CONFIGURATION == "Debug" ]; then + add_runtime_dlls $config "$(pwd)/zlib-1.2.11-msvc2017-win64/bin/zlibd.dll" + else + add_runtime_dlls $config "$(pwd)/zlib-1.2.11-msvc2017-win64/bin/zlib.dll" + fi + done + echo Done. +} echo cd $DEPS_INSTALL/.. @@ -994,6 +1059,8 @@ echo echo "Setting up OpenMW build..." add_cmake_opts -DOPENMW_MP_BUILD=on add_cmake_opts -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" +add_cmake_opts -DOPENMW_USE_SYSTEM_SQLITE3=OFF +add_cmake_opts -DOPENMW_USE_SYSTEM_YAML_CPP=OFF if [ ! -z $CI ]; then case $STEP in components ) @@ -1077,6 +1144,11 @@ if [ "${BUILD_BENCHMARKS}" ]; then add_cmake_opts -DBUILD_BENCHMARKS=ON fi +if [ -n "${TEST_FRAMEWORK}" ]; then + add_cmake_opts -DBUILD_UNITTESTS=ON + add_cmake_opts -DBUILD_OPENCS_TESTS=ON +fi + if [ -n "$ACTIVATE_MSVC" ]; then echo -n "- Activating MSVC in the current shell... " command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; } diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 265e05b8e..85d66d86e 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -3,8 +3,13 @@ export CXX=clang++ export CC=clang -DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" -QT_PATH=$(brew --prefix qt@5) +# Silence a git warning +git config --global advice.detachedHead false + +DEPENDENCIES_ROOT="/tmp/openmw-deps" + +QT_PATH=$(brew --prefix qt@6) +ICU_PATH=$(brew --prefix icu4c) CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache mkdir build cd build @@ -16,14 +21,18 @@ cmake \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ --D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \ +-D CMAKE_OSX_DEPLOYMENT_TARGET="10.15" \ -D CMAKE_BUILD_TYPE=RELEASE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ +-D OPENMW_USE_SYSTEM_RECASTNAVIGATION=TRUE \ -D BUILD_OPENMW=TRUE \ -D BUILD_OPENCS=TRUE \ -D BUILD_ESMTOOL=TRUE \ -D BUILD_BSATOOL=TRUE \ -D BUILD_ESSIMPORTER=TRUE \ -D BUILD_NIFTEST=TRUE \ +-D BUILD_NAVMESHTOOL=TRUE \ +-D BUILD_BULLETOBJECTTOOL=TRUE \ +-D ICU_ROOT="$ICU_PATH" \ -G"Unix Makefiles" \ .. diff --git a/CI/build_googletest.sh b/CI/build_googletest.sh deleted file mode 100755 index a9a50fee7..000000000 --- a/CI/build_googletest.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -ex - -git clone -b release-1.10.0 https://github.com/google/googletest.git -cd googletest -mkdir build -cd build -cmake \ - -D CMAKE_C_COMPILER="${CC}" \ - -D CMAKE_CXX_COMPILER="${CXX}" \ - -D CMAKE_C_COMPILER_LAUNCHER=ccache \ - -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -D CMAKE_BUILD_TYPE="${CONFIGURATION}" \ - -D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \ - -G "${GENERATOR}" \ - .. -cmake --build . --config "${CONFIGURATION}" -- -j $(nproc) -cmake --install . --config "${CONFIGURATION}" diff --git a/CI/check_clang_format.sh b/CI/check_clang_format.sh new file mode 100755 index 000000000..53d4ca4a6 --- /dev/null +++ b/CI/check_clang_format.sh @@ -0,0 +1,8 @@ +#!/bin/bash -ex + +set -o pipefail + +CLANG_FORMAT="${CLANG_FORMAT:-clang-format}" +git ls-files -- ':(exclude)extern/' '*.cpp' '*.hpp' '*.h' | + xargs -I '{}' -P $(nproc) bash -ec "\"${CLANG_FORMAT:?}\" --dry-run -Werror \"\${0:?}\" &> /dev/null || \"${CLANG_FORMAT:?}\" \"\${0:?}\" | git diff --color=always --no-index \"\${0:?}\" -" '{}' || + ( echo "clang-format differences detected"; exit -1 ) diff --git a/CI/check_cmake_format.sh b/CI/check_cmake_format.sh new file mode 100755 index 000000000..40cd0b77f --- /dev/null +++ b/CI/check_cmake_format.sh @@ -0,0 +1,6 @@ +#!/bin/bash -ex + +git ls-files -- ':(exclude)extern/' 'CMakeLists.txt' '*.cmake' | + xargs grep -P '^\s*\t' && + ( echo 'CMake files contain leading tab character. Use only spaces for indentation'; exit -1 ) +exit 0 diff --git a/CI/check_file_names.sh b/CI/check_file_names.sh new file mode 100755 index 000000000..b04416659 --- /dev/null +++ b/CI/check_file_names.sh @@ -0,0 +1,7 @@ +#!/bin/bash -ex + +git ls-files -- ':(exclude)extern/' '*.cpp' '*.hpp' '*.h' | + grep -vP '/[a-z0-9]+\.(cpp|hpp|h)$' | + grep -vFf CI/file_name_exceptions.txt && + ( echo 'File names do not follow the naming convention, see https://wiki.openmw.org/index.php?title=Naming_Conventions#Files'; exit -1 ) +exit 0 diff --git a/CI/file_name_exceptions.txt b/CI/file_name_exceptions.txt new file mode 100644 index 000000000..5035d73f2 --- /dev/null +++ b/CI/file_name_exceptions.txt @@ -0,0 +1,48 @@ +apps/openmw/android_main.cpp +apps/openmw/mwsound/efx-presets.h +apps/openmw/mwsound/ffmpeg_decoder.cpp +apps/openmw/mwsound/ffmpeg_decoder.hpp +apps/openmw/mwsound/openal_output.cpp +apps/openmw/mwsound/openal_output.hpp +apps/openmw/mwsound/sound_buffer.cpp +apps/openmw/mwsound/sound_buffer.hpp +apps/openmw/mwsound/sound_decoder.hpp +apps/openmw/mwsound/sound_output.hpp +apps/openmw_test_suite/esm/test_fixed_string.cpp +apps/openmw_test_suite/files/conversion_tests.cpp +apps/openmw_test_suite/lua/test_async.cpp +apps/openmw_test_suite/lua/test_configuration.cpp +apps/openmw_test_suite/lua/test_l10n.cpp +apps/openmw_test_suite/lua/test_lua.cpp +apps/openmw_test_suite/lua/test_scriptscontainer.cpp +apps/openmw_test_suite/lua/test_serialization.cpp +apps/openmw_test_suite/lua/test_storage.cpp +apps/openmw_test_suite/lua/test_ui_content.cpp +apps/openmw_test_suite/lua/test_utilpackage.cpp +apps/openmw_test_suite/misc/test_endianness.cpp +apps/openmw_test_suite/misc/test_resourcehelpers.cpp +apps/openmw_test_suite/misc/test_stringops.cpp +apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +apps/openmw_test_suite/mwscript/test_scripts.cpp +apps/openmw_test_suite/mwscript/test_utils.hpp +apps/openmw_test_suite/mwworld/test_store.cpp +apps/openmw_test_suite/openmw_test_suite.cpp +apps/openmw_test_suite/testing_util.hpp +components/bsa/bsa_file.cpp +components/bsa/bsa_file.hpp +components/crashcatcher/windows_crashcatcher.cpp +components/crashcatcher/windows_crashcatcher.hpp +components/crashcatcher/windows_crashmonitor.cpp +components/crashcatcher/windows_crashmonitor.hpp +components/crashcatcher/windows_crashshm.hpp +components/fx/lexer_types.hpp +components/fx/parse_constants.hpp +components/platform/file.posix.cpp +components/platform/file.stdio.cpp +components/platform/file.win32.cpp +components/sdlutil/gl4es_init.cpp +components/sdlutil/gl4es_init.h +components/to_utf8/gen_iconv.cpp +components/to_utf8/tables_gen.hpp +components/to_utf8/to_utf8.cpp +components/to_utf8/to_utf8.hpp diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 2f905314b..58277522b 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -9,26 +9,38 @@ print_help() { } declare -rA GROUPED_DEPS=( - [gcc]="binutils gcc g++ libc-dev" - [clang]="binutils clang" + [gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold" + [clang]="binutils clang make cmake ccache curl unzip git pkg-config mold" + [coverity]="binutils clang-11 make cmake ccache curl unzip git pkg-config" + [gcc_preprocess]=" + binutils + build-essential + clang + cmake + curl + gcc + git + libclang-dev + ninja-build + python3-clang + python3-pip + unzip + " # Common dependencies for building OpenMW. [openmw-deps]=" - make cmake ccache git pkg-config - - libboost-filesystem-dev libboost-program-options-dev + libboost-program-options-dev libboost-system-dev libboost-iostreams-dev - + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev - libbullet-dev liblz4-dev libpng-dev libjpeg-dev - ca-certificates + libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev + librecast-dev libsqlite3-dev ca-certificates libicu-dev libyaml-cpp-dev " - # TODO: add librecastnavigation-dev when debian is ready # These dependencies can alternatively be built and linked statically. - [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" - [coverity]="curl" + [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev libcollada-dom-dev" + [clang-tidy]="clang-tidy" # Pre-requisites for building MyGUI and OSG for static linking. # @@ -40,10 +52,53 @@ declare -rA GROUPED_DEPS=( # * JPEG: libjpeg-dev # * PNG: libpng-dev [openmw-deps-static]=" - make cmake - ccache curl unzip libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev + libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev " + + [openmw-coverage]="gcovr" + + [openmw-integration-tests]=" + ca-certificates + gdb + git + git-lfs + libavcodec58 + libavformat58 + libavutil56 + libboost-iostreams1.74.0 + libboost-program-options1.74.0 + libboost-system1.74.0 + libbullet3.24 + libcollada-dom2.5-dp0 + libicu70 + libjpeg8 + libluajit-5.1-2 + liblz4-1 + libmyguiengine3debian1v5 + libopenal1 + libopenscenegraph161 + libpng16-16 + libqt5opengl5 + librecast1 + libsdl2-2.0-0 + libsqlite3-0 + libswresample3 + libswscale5 + libtinyxml2.6.2v5 + libyaml-cpp0.7 + python3-pip + xvfb + " + + [libasan6]="libasan6" + + [android]="binutils build-essential cmake ccache curl unzip git pkg-config" + + [openmw-clang-format]=" + clang-format-14 + git-core + " ) if [[ $# -eq 0 ]]; then @@ -61,7 +116,10 @@ for group in "$@"; do done export APT_CACHE_DIR="${PWD}/apt-cache" +export DEBIAN_FRONTEND=noninteractive set -x mkdir -pv "$APT_CACHE_DIR" -apt-get update -yq -apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" +apt-get update -yqq +apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/dev/null +add-apt-repository -y ppa:openmw/openmw +apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null diff --git a/CI/org.openmw.OpenMW.devel.yaml b/CI/org.openmw.OpenMW.devel.yaml new file mode 100644 index 000000000..39889168c --- /dev/null +++ b/CI/org.openmw.OpenMW.devel.yaml @@ -0,0 +1,200 @@ +--- +app-id: org.openmw.OpenMW.devel +runtime: org.kde.Platform +runtime-version: '5.15-21.08' +sdk: org.kde.Sdk +command: openmw-launcher +rename-appdata-file: openmw.appdata.xml +finish-args: + - "--share=ipc" + - "--socket=x11" + - "--device=all" + - "--filesystem=host" + - "--socket=pulseaudio" +build-options: + cflags: "-O2 -g" + cxxflags: "-O2 -g" +cleanup: + - "/include" + - "/lib/pkgconfig" + - "/lib/cmake" + - "/share/pkgconfig" + - "/share/aclocal" + - "/share/doc" + - "/man" + - "/share/man" + - "/share/gtk-doc" + - "/share/vala" + - "*.la" + - "*.a" + +modules: + - name: boost + buildsystem: simple + build-commands: + - ./bootstrap.sh --prefix=/app --with-libraries=filesystem,iostreams,program_options,system + - ./b2 headers + - ./b2 install + sources: + - type: archive + url: https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz + sha256: aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a + + - name: collada-dom + buildsystem: cmake-ninja + config-opts: + - "-DOPT_COLLADA14=1" + - "-DOPT_COLLADA15=0" + sources: + - type: archive + url: https://github.com/rdiankov/collada-dom/archive/c1e20b7d6ff806237030fe82f126cb86d661f063.zip + sha256: 6c51cd068c7d6760b587391884942caaac8a515d138535041e42d00d3e5c9152 + + - name: ffmpeg + config-opts: + - "--disable-static" + - "--enable-shared" + - "--disable-programs" + - "--disable-doc" + - "--disable-avdevice" + - "--disable-avfilter" + - "--disable-postproc" + + - "--disable-encoders" + - "--disable-muxers" + - "--disable-protocols" + - "--disable-indevs" + - "--disable-devices" + - "--disable-filters" + sources: + - type: archive + url: http://ffmpeg.org/releases/ffmpeg-4.3.2.tar.xz + sha256: 46e4e64f1dd0233cbc0934b9f1c0da676008cad34725113fb7f802cfa84ccddb + cleanup: + - "/share/ffmpeg" + + - name: openscenegraph + buildsystem: cmake-ninja + config-opts: + - "-DBUILD_OSG_PLUGINS_BY_DEFAULT=0" + - "-DBUILD_OSG_PLUGIN_OSG=1" + - "-DBUILD_OSG_PLUGIN_DDS=1" + - "-DBUILD_OSG_PLUGIN_DAE=1" + - "-DBUILD_OSG_PLUGIN_TGA=1" + - "-DBUILD_OSG_PLUGIN_BMP=1" + - "-DBUILD_OSG_PLUGIN_JPEG=1" + - "-DBUILD_OSG_PLUGIN_PNG=1" + - "-DBUILD_OSG_DEPRECATED_SERIALIZERS=0" + - "-DBUILD_OSG_APPLICATIONS=0" + - "-DCMAKE_BUILD_TYPE=Release" + build-options: + env: + COLLADA_DIR: /app/include/collada-dom2.5 + sources: + - type: archive + url: https://github.com/openmw/osg/archive/76e061739610bc9a3420a59e7c9395e742ce2f97.zip + sha256: fa1100362eae260192819d65d90b29ec0b88fdf80e30cee677730b7a0d68637e + + - name: bullet + # The cmake + ninja buildsystem doesn't install the required binaries correctly + buildsystem: cmake + config-opts: + - "-DBUILD_BULLET2_DEMOS=0" + - "-DBUILD_BULLET3=0" + - "-DBUILD_CPU_DEMOS=0" + - "-DBUILD_EXTRAS=0" + - "-DBUILD_OPENGL3_DEMOS=0" + - "-DBUILD_UNIT_TESTS=0" + - "-DCMAKE_BUILD_TYPE=Release" + - "-DUSE_GLUT=0" + - "-DUSE_GRAPHICAL_BENCHMARK=0" + - "-DUSE_DOUBLE_PRECISION=on" + - "-DBULLET2_MULTITHREADING=on" + sources: + - type: archive + url: https://github.com/bulletphysics/bullet3/archive/93be7e644024e92df13b454a4a0b0fcd02b21b10.zip + sha256: 82968fbf20a92c51bc71ac9ee8f6381ecf3420c7cbb881ffb7bb633fa13b27f9 + + - name: mygui + buildsystem: cmake-ninja + config-opts: + - "-DCMAKE_BUILD_TYPE=Release" + - "-DMYGUI_RENDERSYSTEM=1" + - "-DMYGUI_BUILD_DEMOS=0" + - "-DMYGUI_BUILD_TOOLS=0" + - "-DMYGUI_BUILD_PLUGINS=0" + sources: + - type: archive + url: https://github.com/MyGUI/mygui/archive/refs/tags/MyGUI3.4.1.tar.gz + sha256: bdf730bdeb4ad89e6b8223967db01aa5274d2b93adc2c0d6aa4842faeed4de1a + + - name: libunshield + buildsystem: cmake-ninja + config-opts: + - "-DCMAKE_BUILD_TYPE=Release" + sources: + - type: archive + url: https://github.com/twogood/unshield/archive/1.4.3.tar.gz + sha256: aa8c978dc0eb1158d266eaddcd1852d6d71620ddfc82807fe4bf2e19022b7bab + + - name: lz4 + buildsystem: simple + build-commands: + - "make lib" + - "PREFIX=/app make install" + sources: + - type: archive + url: https://github.com/lz4/lz4/archive/refs/tags/v1.9.3.tar.gz + sha256: 030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1 + + - name: recastnavigation + buildsystem: cmake-ninja + config-opts: + - "-DCMAKE_BUILD_TYPE=Release" + - "-DRECASTNAVIGATION_DEMO=no" + - "-DRECASTNAVIGATION_TESTS=no" + - "-DRECASTNAVIGATION_EXAMPLES=no" + sources: + - type: archive + url: https://github.com/recastnavigation/recastnavigation/archive/c5cbd53024c8a9d8d097a4371215e3342d2fdc87.zip + sha256: 53dacfd7bead4d3b0c9a04a648caed3e7c3900e0aba765c15dee26b50f6103c6 + + - name: yaml-cpp + buildsystem: cmake-ninja + sources: + - type: archive + url: https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip + sha256: 4d5e664a7fb2d7445fc548cc8c0e1aa7b1a496540eb382d137e2cc263e6d3ef5 + + - name: LuaJIT + buildsystem: simple + build-commands: + - make install PREFIX=/app + sources: + - type: archive + url: https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.0.5.zip + sha256: 2adbe397a5b6b8ab22fa8396507ce852a2495db50e50734b3daa1ffcadd9eeb4 + + - name: openmw + builddir: true + buildsystem: cmake-ninja + config-opts: + - "-DBUILD_BSATOOL=no" + - "-DBUILD_ESMTOOL=no" + - "-DCMAKE_BUILD_TYPE=Release" + - "-DICONDIR=/app/share/icons" + - "-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=yes" + sources: + - type: dir + path: .. + - type: shell + commands: + - "sed -i 's:/wiki:/old-wiki:' ./files/openmw.appdata.xml" + - "sed -i 's:>org.openmw.launcher.desktop<:>org.openmw.OpenMW.devel.desktop<:' ./files/openmw.appdata.xml" + - "sed -i 's:Icon=openmw:Icon=org.openmw.OpenMW.devel.png:' ./files/org.openmw.launcher.desktop" + - "sed -i 's:Icon=openmw-cs:Icon=org.openmw.OpenMW.OpenCS.devel.png:' ./files/org.openmw.cs.desktop" + post-install: + - "mv /app/share/applications/org.openmw.launcher.desktop /app/share/applications/org.openmw.OpenMW.devel.desktop" + - "mv /app/share/applications/org.openmw.cs.desktop /app/share/applications/org.openmw.OpenMW.OpenCS.devel.desktop" + - "mv /app/share/icons/openmw.png /app/share/icons/org.openmw.OpenMW.devel.png" + - "mv /app/share/icons/openmw-cs.png /app/share/icons/org.openmw.OpenMW.OpenCS.devel.png" diff --git a/CI/run_integration_tests.sh b/CI/run_integration_tests.sh new file mode 100755 index 000000000..d7b025df5 --- /dev/null +++ b/CI/run_integration_tests.sh @@ -0,0 +1,10 @@ +#!/bin/bash -ex + +git clone --depth=1 https://gitlab.com/OpenMW/example-suite.git + +xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \ + scripts/integration_tests.py --omw build/install/bin/openmw --workdir integration_tests_output example-suite/ + +ls integration_tests_output/*.osg_stats.log | while read v; do + scripts/osg_stats.py --stats '.*' --regexp_match < "${v}" +done diff --git a/CI/teal_ci.sh b/CI/teal_ci.sh new file mode 100755 index 000000000..cc0757740 --- /dev/null +++ b/CI/teal_ci.sh @@ -0,0 +1,12 @@ +docs/source/install_luadocumentor_in_docker.sh +PATH=$PATH:~/luarocks/bin + +pushd . +echo "Install Teal Cyan" +git clone https://github.com/teal-language/cyan.git --depth 1 +cd cyan +luarocks make cyan-dev-1.rockspec +popd + +scripts/generate_teal_declarations.sh ./teal_declarations +zip teal_declarations.zip -r teal_declarations diff --git a/CI/ubuntu_gcc_preprocess.sh b/CI/ubuntu_gcc_preprocess.sh new file mode 100755 index 000000000..05d7528e4 --- /dev/null +++ b/CI/ubuntu_gcc_preprocess.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +set -xeo pipefail + +SRC="${PWD:?}" +VERSION=$(git rev-parse HEAD) + +mkdir -p build +cd build + +cmake \ + -G Ninja \ + -D CMAKE_C_COMPILER=gcc \ + -D CMAKE_CXX_COMPILER=g++ \ + -D USE_SYSTEM_TINYXML=ON \ + -D OPENMW_USE_SYSTEM_RECASTNAVIGATION=ON \ + -D CMAKE_BUILD_TYPE=Release \ + -D CMAKE_C_FLAGS_RELEASE='-DNDEBUG -E -w' \ + -D CMAKE_CXX_FLAGS_RELEASE='-DNDEBUG -E -w' \ + -D CMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -D BUILD_BENCHMARKS=ON \ + -D BUILD_BSATOOL=ON \ + -D BUILD_BULLETOBJECTTOOL=ON \ + -D BUILD_ESMTOOL=ON \ + -D BUILD_ESSIMPORTER=ON \ + -D BUILD_LAUNCHER=ON \ + -D BUILD_LAUNCHER_TESTS=ON \ + -D BUILD_MWINIIMPORTER=ON \ + -D BUILD_NAVMESHTOOL=ON \ + -D BUILD_NIFTEST=ON \ + -D BUILD_OPENCS=ON \ + -D BUILD_OPENCS_TESTS=ON \ + -D BUILD_OPENMW=ON \ + -D BUILD_UNITTESTS=ON \ + -D BUILD_WIZARD=ON \ + "${SRC}" +cmake --build . --parallel + +cd .. + +scripts/preprocessed_file_size_stats.py --remove_prefix "${SRC}/" build > "${VERSION:?}.json" +ls -al "${VERSION:?}.json" + +if [[ "${GENERATE_ONLY}" ]]; then + exit 0 +fi + +git remote add target "${CI_MERGE_REQUEST_PROJECT_URL:-https://gitlab.com/OpenMW/openmw.git}" + +TARGET_BRANCH="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-master}" + +git fetch target "${TARGET_BRANCH:?}" + +if [[ "${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" ]]; then + git remote add source "${CI_MERGE_REQUEST_SOURCE_PROJECT_URL}" + git fetch --unshallow source "${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" +elif [[ "${CI_COMMIT_BRANCH}" ]]; then + git fetch origin "${CI_COMMIT_BRANCH:?}" +else + git fetch origin +fi + +BASE_VERSION=$(git merge-base "target/${TARGET_BRANCH:?}" "${VERSION:?}") + +# Save and use scripts from this commit because they may be absent or different in the base version +cp scripts/preprocessed_file_size_stats.py scripts/preprocessed_file_size_stats.bak.py +cp CI/ubuntu_gcc_preprocess.sh CI/ubuntu_gcc_preprocess.bak.sh +git checkout "${BASE_VERSION:?}" +mv scripts/preprocessed_file_size_stats.bak.py scripts/preprocessed_file_size_stats.py +mv CI/ubuntu_gcc_preprocess.bak.sh CI/ubuntu_gcc_preprocess.sh +env GENERATE_ONLY=1 CI/ubuntu_gcc_preprocess.sh +git checkout --force "${VERSION:?}" + +scripts/preprocessed_file_size_stats_diff.py "${BASE_VERSION:?}.json" "${VERSION:?}.json" diff --git a/CMakeLists.txt b/CMakeLists.txt index 59d81eb9b..3f4247be2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,38 @@ -project(OpenMW) cmake_minimum_required(VERSION 3.1.0) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) # for link time optimization, remove if cmake version is >= 3.9 if(POLICY CMP0069) # LTO - cmake_policy(SET CMP0069 NEW) + cmake_policy(SET CMP0069 NEW) endif() # for position-independent executable, remove if cmake version is >= 3.14 if(POLICY CMP0083) - cmake_policy(SET CMP0083 NEW) + cmake_policy(SET CMP0083 NEW) endif() +# to link with freetype library +if(POLICY CMP0079) + cmake_policy(SET CMP0079 NEW) +endif() + +# don't add /W3 flag by default for MSVC +if(POLICY CMP0092) + cmake_policy(SET CMP0092 NEW) +endif() + +# set the timestamps of extracted contents to the time of extraction +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() + +project(OpenMW) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(GNUInstallDirs) + option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF) if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) @@ -32,6 +52,7 @@ option(BUILD_DOCS "Build documentation." OFF ) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) +<<<<<<< HEAD option(BUILD_OPENMW_MP "Build OpenMW-MP" ON) option(BUILD_BROWSER "Build tes3mp Server Browser" ON) option(BUILD_MASTER "Build tes3mp Master Server" OFF) @@ -40,8 +61,18 @@ set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 a if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) set(USE_QT FALSE) +======= +option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) +option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON) +option(BUILD_OPENCS_TESTS "Build OpenMW Construction Set tests" OFF) + +set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. + +if (BUILD_LAUNCHER OR BUILD_OPENCS OR BUILD_WIZARD OR BUILD_OPENCS_TESTS) + set(USE_QT TRUE) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 else() - set(USE_QT TRUE) + set(USE_QT FALSE) endif() # If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them. @@ -68,7 +99,7 @@ endif() message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 47) +set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -112,7 +143,7 @@ configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_ option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) -option(QT_STATIC "Link static build of QT into the binaries" FALSE) +option(QT_STATIC "Link static build of Qt into the binaries" FALSE) option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) if(OPENMW_USE_SYSTEM_BULLET) @@ -147,6 +178,11 @@ else() endif() option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_recastnavigation_static_default}) +option(OPENMW_USE_SYSTEM_SQLITE3 "Use system provided SQLite3 library" ON) + +option(OPENMW_USE_SYSTEM_BENCHMARK "Use system Google Benchmark library." OFF) +option(OPENMW_USE_SYSTEM_GOOGLETEST "Use system Google Test library." OFF) + option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) @@ -162,18 +198,27 @@ option(OPENMW_OSX_DEPLOYMENT OFF) if (MSVC) option(OPENMW_MP_BUILD "Build OpenMW with /MP flag" OFF) + if (OPENMW_MP_BUILD) + add_compile_options(/MP) + endif() + + # \bigobj is required: + # 1) for OPENMW_UNITY_BUILD; + # 2) to compile lua bindings in components, openmw and tests, because sol3 is heavily templated. + # there should be no relevant downsides to having it on: + # https://docs.microsoft.com/en-us/cpp/build/reference/bigobj-increase-number-of-sections-in-dot-obj-file + add_compile_options(/bigobj) endif() # Set up common paths if (APPLE) - set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "../Resources/resources" CACHE PATH "location of OpenMW resources files") elseif(UNIX) # Paths SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "Where to install libraries") - SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/games/" CACHE PATH "Set data path prefix") + SET(DATAROOTDIR "${CMAKE_INSTALL_DATAROOTDIR}" CACHE PATH "Sets the root of data directories to a non-default location") + SET(GLOBAL_DATA_PATH "${CMAKE_INSTALL_PREFIX}/share/games/" CACHE PATH "Set data path prefix") SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") @@ -184,10 +229,8 @@ elseif(UNIX) ENDIF() SET(SYSCONFDIR "${GLOBAL_CONFIG_PATH}/openmw" CACHE PATH "Set config dir") - set(MORROWIND_DATA_FILES "${DATADIR}/data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "${DATADIR}/resources" CACHE PATH "location of OpenMW resources files") else() - set(MORROWIND_DATA_FILES "data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "resources" CACHE PATH "location of OpenMW resources files") endif(APPLE) @@ -195,8 +238,14 @@ if (WIN32) option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) endif() +<<<<<<< HEAD find_package(RakNet REQUIRED) include_directories(${RakNet_INCLUDES}) +======= +if(MSVC) + add_compile_options("/utf-8") +endif() +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 # Dependencies find_package(OpenGL REQUIRED) @@ -204,12 +253,22 @@ find_package(OpenGL REQUIRED) find_package(LZ4 REQUIRED) if (USE_QT) +<<<<<<< HEAD find_package(Qt5Core 5.9 REQUIRED) # Temporary adjustment for TES3MP CI find_package(Qt5Widgets REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5OpenGL REQUIRED) # Instruct CMake to run moc automatically when needed. #set(CMAKE_AUTOMOC ON) +======= + find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5) + if (QT_VERSION_MAJOR VERSION_EQUAL 5) + find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL REQUIRED) + else() + find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets REQUIRED) + endif() + message(STATUS "Using Qt${QT_VERSION}") +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 endif() # Start of tes3mp addition @@ -218,17 +277,20 @@ endif() IF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition set(USED_OSG_COMPONENTS + osgAnimation osgDB - osgViewer - osgText osgGA - osgParticle - osgUtil osgFX + osgParticle + osgText + osgUtil osgShadow - osgAnimation) + osgSim + osgViewer + ) set(USED_OSG_PLUGINS osgdb_bmp + osgdb_dae osgdb_dds osgdb_freetype osgdb_jpeg @@ -242,6 +304,37 @@ set(USED_OSG_PLUGINS ENDIF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition +if(NOT COLLADA_DOM_VERSION_MAJOR) + set(COLLADA_DOM_VERSION_MAJOR 2) +endif() + +if(NOT COLLADA_DOM_VERSION_MINOR) + set(COLLADA_DOM_VERSION_MINOR 5) +endif() + +find_package(collada_dom 2.5) + +option(OPENMW_USE_SYSTEM_ICU "Use system ICU library instead of internal. If disabled, requires autotools" ON) +if(OPENMW_USE_SYSTEM_ICU) + find_package(ICU REQUIRED COMPONENTS uc i18n data) +endif() + +option(OPENMW_USE_SYSTEM_YAML_CPP "Use system yaml-cpp library instead of internal." ON) +if(OPENMW_USE_SYSTEM_YAML_CPP) + set(_yaml_cpp_static_default OFF) +else() + set(_yaml_cpp_static_default ON) +endif() +option(YAML_CPP_STATIC "Link static build of yaml-cpp into the binaries" ${_yaml_cpp_static_default}) +if (OPENMW_USE_SYSTEM_YAML_CPP) + find_package(yaml-cpp REQUIRED) +endif() + +if ((BUILD_UNITTESTS OR BUILD_OPENCS_TESTS) AND OPENMW_USE_SYSTEM_GOOGLETEST) + find_package(GTest 1.10 REQUIRED) + find_package(GMock 1.10 REQUIRED) +endif() + add_subdirectory(extern) # Sound setup @@ -326,7 +419,12 @@ if (WIN32) add_definitions(-DSDL_MAIN_HANDLED) # Get rid of useless crud from windows.h - add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) + add_definitions( + -DNOMINMAX # name conflict with std::min, std::max + -DWIN32_LEAN_AND_MEAN + -DNOMB # name conflict with MWGui::MessageBox + -DNOGDI # name conflict with osgAnimation::MorphGeometry::RELATIVE + ) endif() # Start of tes3mp addition @@ -397,10 +495,7 @@ endif() IF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition if(OPENMW_USE_SYSTEM_OSG) - find_package(OpenSceneGraph 3.4.0 REQUIRED ${USED_OSG_COMPONENTS}) - if (${OPENSCENEGRAPH_VERSION} VERSION_GREATER 3.6.2 AND ${OPENSCENEGRAPH_VERSION} VERSION_LESS 3.6.5) - message(FATAL_ERROR "OpenSceneGraph version ${OPENSCENEGRAPH_VERSION} has critical regressions which cause crashes. Please upgrade to 3.6.5 or later. We strongly recommend using the tip of the official 'OpenSceneGraph-3.6' branch or the tip of '3.6' OpenMW/osg (OSGoS).") - endif() + find_package(OpenSceneGraph 3.6.5 REQUIRED ${USED_OSG_COMPONENTS}) # Bump to 3.6.6 when released if(OSG_STATIC) find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS}) @@ -421,7 +516,12 @@ ELSE(BUILD_OPENMW OR BUILD_OPENCS) ENDIF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition -set(BOOST_COMPONENTS system filesystem program_options iostreams) +include(cmake/CheckOsgMultiview.cmake) +if(HAVE_MULTIVIEW) + add_definitions(-DOSG_HAS_MULTIVIEW) +endif(HAVE_MULTIVIEW) + +set(BOOST_COMPONENTS system program_options iostreams) if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) if(MSVC) @@ -438,13 +538,21 @@ endif() set(Boost_NO_BOOST_CMAKE ON) find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) +<<<<<<< HEAD # Start of tes3mp addition # # Don't require certain dependencies for the server IF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition +======= + +if(Boost_VERSION_STRING VERSION_GREATER_EQUAL 1.77.0) + find_package(Boost 1.77.0 REQUIRED COMPONENTS atomic) +endif() + +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if(OPENMW_USE_SYSTEM_MYGUI) - find_package(MyGUI 3.2.2 REQUIRED) + find_package(MyGUI 3.4.1 REQUIRED) endif() # End of tes3mp addition # @@ -458,11 +566,32 @@ find_package(SDL2 2.0.9 REQUIRED) IF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition find_package(OpenAL REQUIRED) +<<<<<<< HEAD # End of tes3mp addition # # Don't require certain dependencies for the server ENDIF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition +======= +find_package(ZLIB REQUIRED) + +option(USE_LUAJIT "Switch Lua/LuaJit (TRUE is highly recommended)" TRUE) +if(USE_LUAJIT) + find_package(LuaJit REQUIRED) + set(LUA_INCLUDE_DIR ${LuaJit_INCLUDE_DIR}) + set(LUA_LIBRARIES ${LuaJit_LIBRARIES}) +else(USE_LUAJIT) + find_package(Lua REQUIRED) + add_compile_definitions(NO_LUAJIT) +endif(USE_LUAJIT) +if (NOT WIN32) + include(cmake/CheckLuaCustomAllocator.cmake) +endif() + +# C++ library binding to Lua +set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3) +set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 include_directories( BEFORE SYSTEM @@ -473,12 +602,16 @@ include_directories( ${OPENAL_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} + ${LUA_INCLUDE_DIR} + ${SOL_INCLUDE_DIR} + ${SOL_CONFIG_DIR} + ${ICU_INCLUDE_DIRS} ) -link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) +link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${COLLADA_DOM_LIBRARY_DIRS}) if(MYGUI_STATIC) - add_definitions(-DMYGUI_STATIC) + add_definitions(-DMYGUI_STATIC) endif (MYGUI_STATIC) if (APPLE) @@ -489,9 +622,8 @@ if (APPLE) "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY) endif (APPLE) -if (NOT APPLE) - set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR}) - set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR}) +if (NOT APPLE) # this is modified for macOS use later in "apps/open[mw|cs]/CMakeLists.txt" + set(OPENMW_RESOURCES_ROOT ${OpenMW_BINARY_DIR}) endif () add_subdirectory(files/) @@ -575,7 +707,7 @@ endif() 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 -pedantic -Wno-long-long") + set(OPENMW_CXX_FLAGS "-Wall -Wextra -Wundef -Wextra-semi -Wno-unused-parameter -pedantic -Wno-long-long -Wnon-virtual-dtor -Wunused ${OPENMW_CXX_FLAGS}") add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON ) if (APPLE) @@ -585,12 +717,12 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-potentially-evaluated-expression") + set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-potentially-evaluated-expression") endif () endif() - if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.6) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter") + if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) + set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-unused-but-set-parameter -Wduplicated-branches -Wduplicated-cond -Wlogical-op") endif() endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) @@ -608,12 +740,16 @@ add_subdirectory (extern/oics) ENDIF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition add_subdirectory (extern/Base64) +<<<<<<< HEAD # Start of tes3mp addition # # Don't require certain dependencies for the server IF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition if (BUILD_OPENCS) +======= +if (BUILD_OPENCS OR BUILD_OPENCS_TESTS) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 add_subdirectory (extern/osgQt) endif() # Start of tes3mp addition @@ -622,9 +758,12 @@ endif() ENDIF(BUILD_OPENMW OR BUILD_OPENCS) # End of tes3mp addition +if (OPENMW_CXX_FLAGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}") +endif() + # Components add_subdirectory (components) -target_compile_definitions(components PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") # Apps and tools if (BUILD_OPENMW_MP) @@ -640,15 +779,15 @@ if (BUILD_OPENMW) endif() if (BUILD_BSATOOL) - add_subdirectory( apps/bsatool ) + add_subdirectory( apps/bsatool ) endif() if (BUILD_ESMTOOL) - add_subdirectory( apps/esmtool ) + add_subdirectory( apps/esmtool ) endif() if (BUILD_LAUNCHER) - add_subdirectory( apps/launcher ) + add_subdirectory( apps/launcher ) endif() if (BUILD_BROWSER) @@ -656,19 +795,19 @@ if (BUILD_BROWSER) endif() if (BUILD_MWINIIMPORTER) - add_subdirectory( apps/mwiniimporter ) + add_subdirectory( apps/mwiniimporter ) endif() if (BUILD_ESSIMPORTER) - add_subdirectory (apps/essimporter ) + add_subdirectory (apps/essimporter ) endif() -if (BUILD_OPENCS) - add_subdirectory (apps/opencs) +if (BUILD_OPENCS OR BUILD_OPENCS_TESTS) + add_subdirectory (apps/opencs) endif() if (BUILD_WIZARD) - add_subdirectory(apps/wizard) + add_subdirectory(apps/wizard) endif() if (BUILD_NIFTEST) @@ -677,19 +816,34 @@ endif(BUILD_NIFTEST) # UnitTests if (BUILD_UNITTESTS) - add_subdirectory( apps/openmw_test_suite ) + add_subdirectory( apps/openmw_test_suite ) endif() if (BUILD_BENCHMARKS) - add_subdirectory(apps/benchmarks) + add_subdirectory(apps/benchmarks) +endif() + +if (BUILD_NAVMESHTOOL) + add_subdirectory(apps/navmeshtool) +endif() + +if (BUILD_BULLETOBJECTTOOL) + add_subdirectory( apps/bulletobjecttool ) +endif() + +if (BUILD_OPENCS_TESTS) + add_subdirectory(apps/opencs_tests) endif() if (WIN32) - if (MSVC) - if (OPENMW_MP_BUILD) - set( MT_BUILD "/MP") - endif() + if (MSVC) + foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) + string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) + set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(SolutionDir)$(Configuration)" ) + set( CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(ProjectDir)$(Configuration)" ) + endforeach( OUTPUTCONFIG ) +<<<<<<< HEAD foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(SolutionDir)$(Configuration)" ) @@ -781,41 +935,154 @@ if (WIN32) set_target_properties(tes3mp PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") else() set_target_properties(tes3mp PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") +======= + if (USE_DEBUG_CONSOLE AND BUILD_OPENMW) + set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") + set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") + set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$:_CONSOLE>) + elseif (BUILD_OPENMW) + # Turn off debug console, debug output will be written to visual studio output instead + set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") + set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 endif() - endif() - if (BUILD_WIZARD) - set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") - endif() + if (BUILD_OPENMW) + # Release builds don't use the debug console + set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") + set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") + endif() - if (BUILD_BENCHMARKS) - set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") - endif() - endif(MSVC) + # Play a bit with the warning levels +<<<<<<< HEAD # TODO: At some point release builds should not use the console but rather write to a log file #set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") #set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") +======= + set(WARNINGS "/W4") + + set(WARNINGS_DISABLE + 4100 # Unreferenced formal parameter (-Wunused-parameter) + 4127 # Conditional expression is constant + 4996 # Function was declared deprecated + 5054 # Deprecated operations between enumerations of different types caused by Qt headers + ) + + if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" ) + set(WARNINGS_DISABLE ${WARNINGS_DISABLE} + 4866 # compiler may not enforce left-to-right evaluation order for call + ) + endif() + + if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.1" ) + set(WARNINGS_DISABLE ${WARNINGS_DISABLE} + 4275 # non dll-interface class 'MyGUI::delegates::IDelegateUnlink' used as base for dll-interface class 'MyGUI::Widget' + ) + endif() + + foreach(d ${WARNINGS_DISABLE}) + set(WARNINGS "${WARNINGS} /wd${d}") + endforeach(d) + + if(OPENMW_MSVC_WERROR) + set(WARNINGS "${WARNINGS} /WX") + endif() + + set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}") + set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS}") + + if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920) + target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE) + endif() + + if (BUILD_BSATOOL) + set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_ESMTOOL) + set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_ESSIMPORTER) + set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_LAUNCHER) + set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_MWINIIMPORTER) + set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_OPENCS) + set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_OPENMW) + set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_WIZARD) + set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_UNITTESTS) + set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_BENCHMARKS) + set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_NAVMESHTOOL) + set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_BULLETOBJECTTOOL) + set(WARNINGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + endif(MSVC) + + # TODO: At some point release builds should not use the console but rather write to a log file + #set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") + #set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") +endif() + +if (BUILD_OPENMW AND APPLE) + target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1) + target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 endif() # Apple bundling if (OPENMW_OSX_DEPLOYMENT AND APPLE) - if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4) - message(FATAL_ERROR "macOS packaging is broken in early CMake 3.13 releases, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use at least 3.13.4 or an older version like 3.12.4") + if (CMAKE_VERSION VERSION_LESS 3.19) + message(FATAL_ERROR "macOS packaging requires CMake 3.19 or higher to sign macOS app bundles.") endif () - get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) + get_property(QT_COCOA_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) get_filename_component(QT_COCOA_PLUGIN_DIR "${QT_COCOA_PLUGIN_PATH}" DIRECTORY) get_filename_component(QT_COCOA_PLUGIN_GROUP "${QT_COCOA_PLUGIN_DIR}" NAME) get_filename_component(QT_COCOA_PLUGIN_NAME "${QT_COCOA_PLUGIN_PATH}" NAME) configure_file("${QT_COCOA_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) + + get_property(QT_QMACSTYLE_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QMacStylePlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QMACSTYLE_PLUGIN_DIR "${QT_QMACSTYLE_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QMACSTYLE_PLUGIN_GROUP "${QT_QMACSTYLE_PLUGIN_DIR}" NAME) + get_filename_component(QT_QMACSTYLE_PLUGIN_NAME "${QT_QMACSTYLE_PLUGIN_PATH}" NAME) + configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) + configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY) if (BUILD_OPENCS) - get_property(OPENCS_BUNDLE_NAME_TMP TARGET openmw-cs PROPERTY OUTPUT_NAME) - set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") - configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) - configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) + get_property(OPENCS_BUNDLE_NAME_TMP TARGET openmw-cs PROPERTY OUTPUT_NAME) + set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") + configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) + configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) + configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) endif () install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime) @@ -884,7 +1151,9 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) install_plugins_for_bundle("${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS) set(PLUGINS ${PLUGINS} "${INSTALLED_OPENMW_APP}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") + set(PLUGINS ${PLUGINS} "${INSTALLED_OPENMW_APP}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}") set(OPENCS_PLUGINS ${OPENCS_PLUGINS} "${INSTALLED_OPENCS_APP}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") + set(OPENCS_PLUGINS ${OPENCS_PLUGINS} "${INSTALLED_OPENCS_APP}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}") install(CODE " function(gp_item_default_embedded_path_override item default_embedded_path_var) @@ -897,6 +1166,9 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) fixup_bundle(\"${INSTALLED_OPENMW_APP}\" \"${PLUGINS}\" \"\") fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\") " COMPONENT Runtime) + + set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_SOURCE_DIR}/cmake/SignMacApplications.cmake) + include(CPack) elseif(NOT APPLE) get_generator_is_multi_config(multi_config) @@ -929,7 +1201,6 @@ elseif(NOT APPLE) 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}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".") INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION ".") # Start of tes3mp addition INSTALL(FILES "${INSTALL_SOURCE}/tes3mp-client-default.cfg" DESTINATION ".") @@ -1053,9 +1324,12 @@ elseif(NOT APPLE) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" ) ENDIF(BUILD_WIZARD) - - # Install licenses - INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" ) + if(BUILD_NAVMESHTOOL) + install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" ) + endif() + IF(BUILD_BULLETOBJECTTOOL) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-bulletobjecttool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_BULLETOBJECTTOOL) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 264db49cc..4cdb164a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,9 +22,9 @@ Pull Request Guidelines To facilitate the review process, your pull request description should include the following, if applicable: * A link back to the bug report or forum discussion that prompted the change. Note: when linking bugs, use the syntax ```[Bug #xyz](https://gitlab.com/OpenMW/openmw/issues/#xyz)``` to create a clickable link. Writing only 'Bug #xyz' will unfortunately create a link to the Github pull request with that number instead. -* Summary of the changes made -* Reasoning / motivation behind the change -* What testing you have carried out to verify the change +* Summary of the changes made. +* Reasoning / motivation behind the change. +* What testing you have carried out to verify the change. Furthermore, we advise to: @@ -51,9 +51,9 @@ OpenMW, in its default configuration, is meant to be a faithful reimplementation That said, we may sometimes evaluate such issues on an individual basis. Common exceptions to the above would be: -* Issues so glaring that they would severely limit the capabilities of the engine in the future (for example, the scripting engine not being allowed to access objects in remote cells) -* Bugs where the intent is very obvious, and that have little to no balancing impact (e.g. the bug were being tired made it easier to repair items, instead of harder) -* Bugs that were fixed in an official patch for Morrowind +* Issues so glaring that they would severely limit the capabilities of the engine in the future (for example, the scripting engine not being allowed to access objects in remote cells). +* Bugs where the intent is very obvious, and that have little to no balancing impact (e.g. the bug were being tired made it easier to repair items, instead of harder). +* Bugs that were fixed in an official patch for Morrowind. Feature additions policy ===================== @@ -99,7 +99,7 @@ Code Review Merging ======= -To be able to merge PRs, commit priviledges are required. If you do not have the priviledges, just ping someone that does have them with a short comment like "Looks good to me @user". +To be able to merge PRs, commit privileges are required. If you do not have the privileges, just ping someone that does have them with a short comment like "Looks good to me @user". The person to merge the PR may either use github's Merge button or if using the command line, use the ```--no-ff``` flag (so a merge commit is created, just like with Github's merge button) and include the pull request number in the commit description. diff --git a/README.md b/README.md index 664ce81c5..9062541b7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ TES3MP ====== +<<<<<<< HEAD Copyright (c) 2008-2015, OpenMW Team Copyright (c) 2016-2022, David Cernat & Stanislav Zhukov @@ -12,11 +13,32 @@ TES3MP is a project adding multiplayer functionality to [OpenMW](https://github. Font Licenses: * DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/TES3MP/TES3MP/blob/master/files/mygui/DejaVuFontLicense.txt) for more information) +======= +OpenMW is an open-source open-world RPG game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. + +OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. + +* Version: 0.49.0 +* License: GPLv3 (see [LICENSE](https://gitlab.com/OpenMW/openmw/-/raw/master/LICENSE) for more information) +* Website: https://www.openmw.org +* IRC: #openmw on irc.libera.chat +* Discord: https://discord.gg/bWuqq2e + + +Font Licenses: +* DejaVuLGCSansMono.ttf: custom (see [files/data/fonts/DejaVuFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DejaVuFontLicense.txt) for more information) +* DemonicLetters.ttf: SIL Open Font License (see [files/data/fonts/DemonicLettersFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DemonicLettersFontLicense.txt) for more information) +* MysticCards.ttf: SIL Open Font License (see [files/data/fonts/MysticCardsFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/MysticCardsFontLicense.txt) for more information) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 Project status -------------- +<<<<<<< HEAD [Version changelog](https://github.com/TES3MP/TES3MP/blob/master/tes3mp-changelog.md) +======= +The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 As of version 0.8.1, TES3MP is fully playable, providing very extensive player, NPC, world and quest synchronization, as well as state saving and loading, all of which are highly customizable via [serverside Lua scripts](https://github.com/TES3MP/CoreScripts). @@ -27,7 +49,17 @@ TES3MP now also has a [VR branch](https://github.com/TES3MP/TES3MP/tree/0.8.1-vr Donations --------------- +<<<<<<< HEAD You can benefit the project by donating on Patreon to our two developers, [David Cernat](https://www.patreon.com/davidcernat) and [Koncord](https://www.patreon.com/Koncord), as well as by supporting [OpenMW](https://openmw.org). +======= +* [Official forums](https://forum.openmw.org/) +* [Installation instructions](https://openmw.readthedocs.io/en/latest/manuals/installation/index.html) +* [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup) +* [Testing the game](https://wiki.openmw.org/index.php?title=Testing) +* [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted) +* [Report a bug](https://gitlab.com/OpenMW/openmw/issues) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug! +* [Known issues](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=Bug) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 Contributing --------------- @@ -36,7 +68,54 @@ Helping us with documentation, bug hunting and video showcases is always greatly For code contributions, it's best to start out with modestly sized fixes and features and work your way up. There are so many different possible implementations of more major features – many of which would cause undesirable code or vision conflicts with OpenMW – that those should be talked over in advance with the existing developers before effort is spent on them. +<<<<<<< HEAD Feel free to contact the [team members](https://github.com/TES3MP/TES3MP/blob/master/tes3mp-credits.md) for any questions you might have. +======= + Syntax: openmw + Allowed options: + --help print help message + --version print version information and quit + --data arg (=data) set data directories (later directories + have higher priority) + --data-local arg set local data directory (highest + priority) + --fallback-archive arg (=fallback-archive) + set fallback BSA archives (later + archives have higher priority) + --resources arg (=resources) set resources directory + --start arg set initial cell + --content arg content file(s): esm/esp, or + omwgame/omwaddon + --no-sound [=arg(=1)] (=0) disable all sounds + --script-verbose [=arg(=1)] (=0) verbose script output + --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue + scripts) at startup + --script-all-dialogue [=arg(=1)] (=0) compile all dialogue scripts at startup + --script-console [=arg(=1)] (=0) enable console-only script + functionality + --script-run arg select a file containing a list of + console commands that is executed on + startup + --script-warn [=arg(=1)] (=1) handling of warnings when compiling + scripts + 0 - ignore warning + 1 - show warning but consider script as + correctly compiled anyway + 2 - treat warnings as errors + --script-blacklist arg ignore the specified script (if the use + of the blacklist is enabled) + --script-blacklist-use [=arg(=1)] (=1) + enable script blacklisting + --load-savegame arg load a save game file on game startup + (specify an absolute filename or a + filename relative to the current + working directory) + --skip-menu [=arg(=1)] (=0) skip main menu on game startup + --new-game [=arg(=1)] (=0) run new game sequence (ignored if + skip-menu=0) + --encoding arg (=win1252) Character encoding used in OpenMW game + messages: +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 Getting started --------------- diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt index f9aa9aad4..8039f2a3d 100644 --- a/apps/benchmarks/CMakeLists.txt +++ b/apps/benchmarks/CMakeLists.txt @@ -1,27 +1,7 @@ -cmake_minimum_required(VERSION 3.11) - -set(BENCHMARK_ENABLE_TESTING OFF) -set(BENCHMARK_ENABLE_INSTALL OFF) -set(BENCHMARK_ENABLE_GTEST_TESTS OFF) - -set(SAVED_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - -string(REPLACE "-Wsuggest-override" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") -string(REPLACE "-Wundef" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") -include(FetchContent) -FetchContent_Declare(benchmark - URL https://github.com/google/benchmark/archive/refs/tags/v1.5.2.zip - URL_HASH MD5=49395b757a7c4656d70f1328d93efd00 - SOURCE_DIR fetched/benchmark -) -FetchContent_MakeAvailableExcludeFromAll(benchmark) - -set(CMAKE_CXX_FLAGS "${SAVED_CMAKE_CXX_FLAGS}") - -openmw_add_executable(openmw_detournavigator_navmeshtilescache_benchmark detournavigator/navmeshtilescache.cpp) -target_compile_features(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE cxx_std_17) -target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark benchmark::benchmark components) - -if (UNIX AND NOT APPLE) - target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT}) +if(OPENMW_USE_SYSTEM_BENCHMARK) + find_package(benchmark REQUIRED) endif() + +add_subdirectory(detournavigator) +add_subdirectory(esm) +add_subdirectory(settings) diff --git a/apps/benchmarks/detournavigator/CMakeLists.txt b/apps/benchmarks/detournavigator/CMakeLists.txt new file mode 100644 index 000000000..198cf2bd3 --- /dev/null +++ b/apps/benchmarks/detournavigator/CMakeLists.txt @@ -0,0 +1,15 @@ +openmw_add_executable(openmw_detournavigator_navmeshtilescache_benchmark navmeshtilescache.cpp) +target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark benchmark::benchmark components) + +if (UNIX AND NOT APPLE) + target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT}) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE --coverage) + target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark gcov) +endif() diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 2c7a981ad..3be1c8762 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -1,10 +1,11 @@ #include #include +#include +#include #include #include -#include namespace { @@ -12,23 +13,22 @@ namespace struct Key { - osg::Vec3f mAgentHalfExtents; + AgentBounds mAgentBounds; TilePosition mTilePosition; RecastMesh mRecastMesh; - std::vector mOffMeshConnections; }; struct Item { Key mKey; - NavMeshData mValue; + PreparedNavMeshData mValue; }; template - TilePosition generateTilePosition(int max, Random& random) + osg::Vec2i generateVec2i(int max, Random& random) { std::uniform_int_distribution distribution(0, max); - return TilePosition(distribution(random), distribution(random)); + return osg::Vec2i(distribution(random), distribution(random)); } template @@ -56,11 +56,16 @@ namespace { switch (index) { - case 0: return AreaType_null; - case 1: return AreaType_water; - case 2: return AreaType_door; - case 3: return AreaType_pathgrid; - case 4: return AreaType_ground; + case 0: + return AreaType_null; + case 1: + return AreaType_water; + case 2: + return AreaType_door; + case 3: + return AreaType_pathgrid; + case 4: + return AreaType_ground; } return AreaType_null; } @@ -69,7 +74,7 @@ namespace AreaType generateAreaType(Random& random) { std::uniform_int_distribution distribution(0, 4); - return toAreaType(distribution(random));; + return toAreaType(distribution(random)); } template @@ -81,47 +86,77 @@ namespace template void generateWater(OutputIterator out, std::size_t count, Random& random) { - std::uniform_real_distribution distribution(0.0, 1.0); + std::uniform_real_distribution distribution(0.0, 1.0); std::generate_n(out, count, [&] { - const btVector3 shift(distribution(random), distribution(random), distribution(random)); - return RecastMesh::Water {1, btTransform(btMatrix3x3::getIdentity(), shift)}; + return CellWater{ generateVec2i(1000, random), Water{ ESM::Land::REAL_SIZE, distribution(random) } }; }); } - template - void generateOffMeshConnection(OutputIterator out, std::size_t count, Random& random) + template + Mesh generateMesh(std::size_t triangles, Random& random) { - std::uniform_real_distribution distribution(0.0, 1.0); - std::generate_n(out, count, [&] { - const osg::Vec3f start(distribution(random), distribution(random), distribution(random)); - const osg::Vec3f end(distribution(random), distribution(random), distribution(random)); - return OffMeshConnection {start, end, generateAreaType(random)}; - }); + std::uniform_real_distribution distribution(0.0, 1.0); + std::vector vertices; + std::vector indices; + std::vector areaTypes; + if (distribution(random) < 0.939) + { + generateVertices(std::back_inserter(vertices), triangles * 2.467, random); + generateIndices(std::back_inserter(indices), static_cast(vertices.size() / 3) - 1, + vertices.size() * 1.279, random); + generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random); + } + return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes)); + } + + template + Heightfield generateHeightfield(Random& random) + { + std::uniform_real_distribution distribution(0.0, 1.0); + Heightfield result; + result.mCellPosition = generateVec2i(1000, random); + result.mCellSize = ESM::Land::REAL_SIZE; + result.mMinHeight = distribution(random); + result.mMaxHeight = result.mMinHeight + 1.0; + result.mLength = static_cast(ESM::Land::LAND_SIZE); + std::generate_n( + std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&] { return distribution(random); }); + result.mOriginalSize = ESM::Land::LAND_SIZE; + result.mMinX = 0; + result.mMinY = 0; + return result; + } + + template + FlatHeightfield generateFlatHeightfield(Random& random) + { + std::uniform_real_distribution distribution(0.0, 1.0); + FlatHeightfield result; + result.mCellPosition = generateVec2i(1000, random); + result.mCellSize = ESM::Land::REAL_SIZE; + result.mHeight = distribution(random); + return result; } template Key generateKey(std::size_t triangles, Random& random) { + const CollisionShapeType agentShapeType = CollisionShapeType::Aabb; const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random); - const TilePosition tilePosition = generateTilePosition(10000, random); - const std::size_t generation = std::uniform_int_distribution(0, 100)(random); - const std::size_t revision = std::uniform_int_distribution(0, 10000)(random); - std::vector vertices; - generateVertices(std::back_inserter(vertices), triangles * 1.98, random); - std::vector indices; - generateIndices(std::back_inserter(indices), static_cast(vertices.size() / 3) - 1, vertices.size() * 1.53, random); - std::vector areaTypes; - generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random); - std::vector water; - generateWater(std::back_inserter(water), 2, random); - RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices), - std::move(areaTypes), std::move(water)); - std::vector offMeshConnections; - generateOffMeshConnection(std::back_inserter(offMeshConnections), 300, random); - return Key {agentHalfExtents, tilePosition, std::move(recastMesh), std::move(offMeshConnections)}; + const TilePosition tilePosition = generateVec2i(10000, random); + const Version version{ + .mGeneration = std::uniform_int_distribution(0, 100)(random), + .mRevision = std::uniform_int_distribution(0, 10000)(random), + }; + Mesh mesh = generateMesh(triangles, random); + std::vector water; + generateWater(std::back_inserter(water), 1, random); + RecastMesh recastMesh(version, std::move(mesh), std::move(water), { generateHeightfield(random) }, + { generateFlatHeightfield(random) }, {}); + return Key{ AgentBounds{ agentShapeType, agentHalfExtents }, tilePosition, std::move(recastMesh) }; } - constexpr std::size_t trianglesPerTile = 310; + constexpr std::size_t trianglesPerTile = 239; template void generateKeys(OutputIterator out, std::size_t count, Random& random) @@ -137,7 +172,7 @@ namespace while (true) { Key key = generateKey(trianglesPerTile, random); - cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); + cache.set(key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique()); *out++ = std::move(key); const std::size_t newSize = cache.getStats().mNavMeshCacheSize; if (size >= newSize) @@ -156,22 +191,53 @@ namespace generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random); std::size_t n = 0; - while (state.KeepRunning()) + for (auto _ : state) { const auto& key = keys[n++ % keys.size()]; - const auto result = cache.get(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections); + const auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); benchmark::DoNotOptimize(result); } } - constexpr auto getFromFilledCache_1m_100hit = getFromFilledCache<1 * 1024 * 1024, 100>; - constexpr auto getFromFilledCache_4m_100hit = getFromFilledCache<4 * 1024 * 1024, 100>; - constexpr auto getFromFilledCache_16m_100hit = getFromFilledCache<16 * 1024 * 1024, 100>; - constexpr auto getFromFilledCache_64m_100hit = getFromFilledCache<64 * 1024 * 1024, 100>; - constexpr auto getFromFilledCache_1m_70hit = getFromFilledCache<1 * 1024 * 1024, 70>; - constexpr auto getFromFilledCache_4m_70hit = getFromFilledCache<4 * 1024 * 1024, 70>; - constexpr auto getFromFilledCache_16m_70hit = getFromFilledCache<16 * 1024 * 1024, 70>; - constexpr auto getFromFilledCache_64m_70hit = getFromFilledCache<64 * 1024 * 1024, 70>; + void getFromFilledCache_1m_100hit(benchmark::State& state) + { + getFromFilledCache<1 * 1024 * 1024, 100>(state); + } + + void getFromFilledCache_4m_100hit(benchmark::State& state) + { + getFromFilledCache<4 * 1024 * 1024, 100>(state); + } + + void getFromFilledCache_16m_100hit(benchmark::State& state) + { + getFromFilledCache<16 * 1024 * 1024, 100>(state); + } + + void getFromFilledCache_64m_100hit(benchmark::State& state) + { + getFromFilledCache<64 * 1024 * 1024, 100>(state); + } + + void getFromFilledCache_1m_70hit(benchmark::State& state) + { + getFromFilledCache<1 * 1024 * 1024, 70>(state); + } + + void getFromFilledCache_4m_70hit(benchmark::State& state) + { + getFromFilledCache<4 * 1024 * 1024, 70>(state); + } + + void getFromFilledCache_16m_70hit(benchmark::State& state) + { + getFromFilledCache<16 * 1024 * 1024, 70>(state); + } + + void getFromFilledCache_64m_70hit(benchmark::State& state) + { + getFromFilledCache<64 * 1024 * 1024, 70>(state); + } template void setToBoundedNonEmptyCache(benchmark::State& state) @@ -187,15 +253,31 @@ namespace while (state.KeepRunning()) { const auto& key = keys[n++ % keys.size()]; - const auto result = cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); + const auto result = cache.set( + key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique()); benchmark::DoNotOptimize(result); } } - constexpr auto setToBoundedNonEmptyCache_1m = setToBoundedNonEmptyCache<1 * 1024 * 1024>; - constexpr auto setToBoundedNonEmptyCache_4m = setToBoundedNonEmptyCache<4 * 1024 * 1024>; - constexpr auto setToBoundedNonEmptyCache_16m = setToBoundedNonEmptyCache<16 * 1024 * 1024>; - constexpr auto setToBoundedNonEmptyCache_64m = setToBoundedNonEmptyCache<64 * 1024 * 1024>; + void setToBoundedNonEmptyCache_1m(benchmark::State& state) + { + setToBoundedNonEmptyCache<1 * 1024 * 1024>(state); + } + + void setToBoundedNonEmptyCache_4m(benchmark::State& state) + { + setToBoundedNonEmptyCache<4 * 1024 * 1024>(state); + } + + void setToBoundedNonEmptyCache_16m(benchmark::State& state) + { + setToBoundedNonEmptyCache<16 * 1024 * 1024>(state); + } + + void setToBoundedNonEmptyCache_64m(benchmark::State& state) + { + setToBoundedNonEmptyCache<64 * 1024 * 1024>(state); + } } // namespace BENCHMARK(getFromFilledCache_1m_100hit); diff --git a/apps/benchmarks/esm/CMakeLists.txt b/apps/benchmarks/esm/CMakeLists.txt new file mode 100644 index 000000000..e7ecfff41 --- /dev/null +++ b/apps/benchmarks/esm/CMakeLists.txt @@ -0,0 +1,15 @@ +openmw_add_executable(openmw_esm_refid_benchmark benchrefid.cpp) +target_link_libraries(openmw_esm_refid_benchmark benchmark::benchmark components) + +if (UNIX AND NOT APPLE) + target_link_libraries(openmw_esm_refid_benchmark ${CMAKE_THREAD_LIBS_INIT}) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw_esm_refid_benchmark PRIVATE ) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + target_compile_options(openmw_esm_refid_benchmark PRIVATE --coverage) + target_link_libraries(openmw_esm_refid_benchmark gcov) +endif() diff --git a/apps/benchmarks/esm/benchrefid.cpp b/apps/benchmarks/esm/benchrefid.cpp new file mode 100644 index 000000000..b12f494ab --- /dev/null +++ b/apps/benchmarks/esm/benchrefid.cpp @@ -0,0 +1,249 @@ +#include + +#include "components/esm/refid.hpp" + +#include +#include +#include +#include +#include + +namespace +{ + constexpr std::size_t refIdsCount = 64 * 1024; + + template + std::string generateText(std::size_t size, Random& random) + { + std::uniform_int_distribution distribution('A', 'z'); + std::string result; + result.reserve(size); + std::generate_n(std::back_inserter(result), size, [&] { return distribution(random); }); + return result; + } + + template + std::vector generateStringRefIds(std::size_t size, Random& random) + { + std::vector result; + result.reserve(refIdsCount); + std::generate_n( + std::back_inserter(result), refIdsCount, [&] { return ESM::StringRefId(generateText(size, random)); }); + return result; + } + + template + std::vector generateSerializedRefIds(const std::vector& generated, Serialize&& serialize) + { + std::vector result; + result.reserve(generated.size()); + for (ESM::RefId refId : generated) + result.push_back(serialize(refId)); + return result; + } + + template + std::vector generateSerializedStringRefIds(std::size_t size, Random& random, Serialize&& serialize) + { + return generateSerializedRefIds(generateStringRefIds(size, random), serialize); + } + + template + std::vector generateIndexRefIds(Random& random) + { + std::vector result; + result.reserve(refIdsCount); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + std::generate_n(std::back_inserter(result), refIdsCount, + [&] { return ESM::IndexRefId(ESM::REC_ARMO, distribution(random)); }); + return result; + } + + template + std::vector generateSerializedIndexRefIds(Random& random, Serialize&& serialize) + { + return generateSerializedRefIds(generateIndexRefIds(random), serialize); + } + + template + std::vector generateGeneratedRefIds(Random& random) + { + std::vector result; + result.reserve(refIdsCount); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + std::generate_n( + std::back_inserter(result), refIdsCount, [&] { return ESM::GeneratedRefId(distribution(random)); }); + return result; + } + + template + std::vector generateSerializedGeneratedRefIds(Random& random, Serialize&& serialize) + { + return generateSerializedRefIds(generateGeneratedRefIds(random), serialize); + } + + template + std::vector generateESM3ExteriorCellRefIds(Random& random) + { + std::vector result; + result.reserve(refIdsCount); + std::uniform_int_distribution distribution(-100, 100); + std::generate_n(std::back_inserter(result), refIdsCount, + [&] { return ESM::ESM3ExteriorCellRefId(distribution(random), distribution(random)); }); + return result; + } + + template + std::vector generateSerializedESM3ExteriorCellRefIds(Random& random, Serialize&& serialize) + { + return generateSerializedRefIds(generateESM3ExteriorCellRefIds(random), serialize); + } + + void serializeRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector refIds = generateStringRefIds(state.range(0), random); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(refIds[i].serialize()); + if (++i >= refIds.size()) + i = 0; + } + } + + void deserializeRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector serializedRefIds + = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serialize(); }); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(ESM::RefId::deserialize(serializedRefIds[i])); + if (++i >= serializedRefIds.size()) + i = 0; + } + } + + void serializeTextStringRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector refIds = generateStringRefIds(state.range(0), random); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(refIds[i].serializeText()); + if (++i >= refIds.size()) + i = 0; + } + } + + void deserializeTextStringRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector serializedRefIds + = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); }); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); + if (++i >= serializedRefIds.size()) + i = 0; + } + } + + void serializeTextGeneratedRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector refIds = generateGeneratedRefIds(random); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(refIds[i].serializeText()); + if (++i >= refIds.size()) + i = 0; + } + } + + void deserializeTextGeneratedRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector serializedRefIds + = generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); + if (++i >= serializedRefIds.size()) + i = 0; + } + } + + void serializeTextIndexRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector refIds = generateIndexRefIds(random); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(refIds[i].serializeText()); + if (++i >= refIds.size()) + i = 0; + } + } + + void deserializeTextIndexRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector serializedRefIds + = generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); + if (++i >= serializedRefIds.size()) + i = 0; + } + } + + void serializeTextESM3ExteriorCellRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector refIds = generateESM3ExteriorCellRefIds(random); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(refIds[i].serializeText()); + if (++i >= refIds.size()) + i = 0; + } + } + + void deserializeTextESM3ExteriorCellRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector serializedRefIds + = generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); + if (++i >= serializedRefIds.size()) + i = 0; + } + } +} + +BENCHMARK(serializeRefId)->RangeMultiplier(4)->Range(8, 64); +BENCHMARK(deserializeRefId)->RangeMultiplier(4)->Range(8, 64); +BENCHMARK(serializeTextStringRefId)->RangeMultiplier(4)->Range(8, 64); +BENCHMARK(deserializeTextStringRefId)->RangeMultiplier(4)->Range(8, 64); +BENCHMARK(serializeTextGeneratedRefId); +BENCHMARK(deserializeTextGeneratedRefId); +BENCHMARK(serializeTextIndexRefId); +BENCHMARK(deserializeTextIndexRefId); +BENCHMARK(serializeTextESM3ExteriorCellRefId); +BENCHMARK(deserializeTextESM3ExteriorCellRefId); + +BENCHMARK_MAIN(); diff --git a/apps/benchmarks/settings/CMakeLists.txt b/apps/benchmarks/settings/CMakeLists.txt new file mode 100644 index 000000000..8e3bd7375 --- /dev/null +++ b/apps/benchmarks/settings/CMakeLists.txt @@ -0,0 +1,18 @@ +openmw_add_executable(openmw_settings_access_benchmark access.cpp) +target_link_libraries(openmw_settings_access_benchmark benchmark::benchmark components) + +target_compile_definitions(openmw_settings_access_benchmark + PRIVATE OPENMW_PROJECT_SOURCE_DIR=u8"${PROJECT_SOURCE_DIR}") + +if (UNIX AND NOT APPLE) + target_link_libraries(openmw_settings_access_benchmark ${CMAKE_THREAD_LIBS_INIT}) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw_settings_access_benchmark PRIVATE ) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + target_compile_options(openmw_settings_access_benchmark PRIVATE --coverage) + target_link_libraries(openmw_settings_access_benchmark gcov) +endif() diff --git a/apps/benchmarks/settings/access.cpp b/apps/benchmarks/settings/access.cpp new file mode 100644 index 000000000..aecac2dac --- /dev/null +++ b/apps/benchmarks/settings/access.cpp @@ -0,0 +1,162 @@ +#include + +#include "components/misc/strings/conversion.hpp" +#include "components/settings/parser.hpp" +#include "components/settings/settings.hpp" +#include "components/settings/values.hpp" + +namespace +{ + void settingsManager(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::Manager::getFloat("sky blending start", "Fog")); + } + } + + void settingsManager2(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera")); + benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing")); + } + } + + void settingsManager3(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera")); + benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing")); + benchmark::DoNotOptimize(Settings::Manager::getInt("reflection detail", "Water")); + } + } + + void localStatic(benchmark::State& state) + { + for (auto _ : state) + { + static const float v = Settings::Manager::getFloat("sky blending start", "Fog"); + benchmark::DoNotOptimize(v); + } + } + + void localStatic2(benchmark::State& state) + { + for (auto _ : state) + { + static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); + static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + } + } + + void localStatic3(benchmark::State& state) + { + for (auto _ : state) + { + static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); + static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); + static const int v3 = Settings::Manager::getInt("reflection detail", "Water"); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); + } + } + + void settingsStorage(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::fog().mSkyBlendingStart.get()); + } + } + + void settingsStorage2(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); + benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); + } + } + + void settingsStorage3(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); + benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); + benchmark::DoNotOptimize(Settings::water().mReflectionDetail.get()); + } + } + + void settingsStorageGet(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::get("Fog", "sky blending start")); + } + } + + void settingsStorageGet2(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::get("Post Processing", "transparent postpass")); + benchmark::DoNotOptimize(Settings::get("Camera", "near clip")); + } + } + + void settingsStorageGet3(benchmark::State& state) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(Settings::get("Post Processing", "transparent postpass")); + benchmark::DoNotOptimize(Settings::get("Camera", "near clip")); + benchmark::DoNotOptimize(Settings::get("Water", "reflection detail")); + } + } +} + +BENCHMARK(settingsManager); +BENCHMARK(localStatic); +BENCHMARK(settingsStorage); +BENCHMARK(settingsStorageGet); + +BENCHMARK(settingsManager2); +BENCHMARK(localStatic2); +BENCHMARK(settingsStorage2); +BENCHMARK(settingsStorageGet2); + +BENCHMARK(settingsManager3); +BENCHMARK(localStatic3); +BENCHMARK(settingsStorage3); +BENCHMARK(settingsStorageGet3); + +int main(int argc, char* argv[]) +{ + const std::filesystem::path settingsDefaultPath = std::filesystem::path{ OPENMW_PROJECT_SOURCE_DIR } / "files" + / Misc::StringUtils::stringToU8String("settings-default.cfg"); + + Settings::SettingsFileParser parser; + parser.loadSettingsFile(settingsDefaultPath, Settings::Manager::mDefaultSettings); + + Settings::StaticValues::initDefaults(); + + Settings::Manager::mUserSettings = Settings::Manager::mDefaultSettings; + Settings::Manager::mUserSettings.erase({ "Camera", "near clip" }); + Settings::Manager::mUserSettings.erase({ "Post Processing", "transparent postpass" }); + Settings::Manager::mUserSettings.erase({ "Water", "reflection detail" }); + + Settings::StaticValues::init(); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + + return 0; +} diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt index ec0615ff9..0ee6d8472 100644 --- a/apps/bsatool/CMakeLists.txt +++ b/apps/bsatool/CMakeLists.txt @@ -1,20 +1,27 @@ set(BSATOOL - bsatool.cpp + bsatool.cpp ) source_group(apps\\bsatool FILES ${BSATOOL}) # Main executable openmw_add_executable(bsatool - ${BSATOOL} + ${BSATOOL} ) target_link_libraries(bsatool - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} - components + ${Boost_PROGRAM_OPTIONS_LIBRARY} + components ) if (BUILD_WITH_CODE_COVERAGE) - add_definitions (--coverage) - target_link_libraries(bsatool gcov) + target_compile_options(bsatool PRIVATE --coverage) + target_link_libraries(bsatool gcov) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(bsatool PRIVATE + + + + ) endif() diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 8e8cf8918..e2029f324 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -1,63 +1,69 @@ -#include +#include +#include #include +#include #include #include -#include -#include +#include +#include #include -#include +#include +#include +#include +#include #define BSATOOL_VERSION 1.1 // Create local aliases for brevity namespace bpo = boost::program_options; -namespace bfs = boost::filesystem; struct Arguments { std::string mode; - std::string filename; - std::string extractfile; - std::string addfile; - std::string outdir; + std::filesystem::path filename; + std::filesystem::path extractfile; + std::filesystem::path addfile; + std::filesystem::path outdir; bool longformat; bool fullpath; }; -bool parseOptions (int argc, char** argv, Arguments &info) +bool parseOptions(int argc, char** argv, Arguments& info) { - bpo::options_description desc("Inspect and extract files from Bethesda BSA archives\n\n" - "Usages:\n" - " bsatool list [-l] archivefile\n" - " List the files presents in the input archive.\n\n" - " bsatool extract [-f] archivefile [file_to_extract] [output_directory]\n" - " Extract a file from the input archive.\n\n" - " bsatool extractall archivefile [output_directory]\n" - " Extract all files from the input archive.\n\n" - " bsatool add [-a] archivefile file_to_add\n" - " Add a file to the input archive.\n\n" - " bsatool create [-c] archivefile\n" - " Create an archive.\n\n" - "Allowed options"); + bpo::options_description desc(R"(Inspect and extract files from Bethesda BSA archives - desc.add_options() - ("help,h", "print help message.") - ("version,v", "print version information and quit.") - ("long,l", "Include extra information in archive listing.") - ("full-path,f", "Create directory hierarchy on file extraction " - "(always true for extractall).") - ; +Usages: + bsatool list [-l] archivefile\n + List the files presents in the input archive. + + bsatool extract [-f] archivefile [file_to_extract] [output_directory] + Extract a file from the input archive. + + bsatool extractall archivefile [output_directory] + Extract all files from the input archive. + + bsatool add [-a] archivefile file_to_add + Add a file to the input archive. + + bsatool create [-c] archivefile + Create an archive. +Allowed options)"); + + auto addOption = desc.add_options(); + addOption("help,h", "print help message."); + addOption("version,v", "print version information and quit."); + addOption("long,l", "Include extra information in archive listing."); + addOption("full-path,f", "Create directory hierarchy on file extraction (always true for extractall)."); // input-file is hidden and used as a positional argument bpo::options_description hidden("Hidden Options"); - hidden.add_options() - ( "mode,m", bpo::value(), "bsatool mode") - ( "input-file,i", bpo::value< std::vector >(), "input file") - ; + auto addHiddenOption = hidden.add_options(); + addHiddenOption("mode,m", bpo::value(), "bsatool mode"); + addHiddenOption("input-file,i", bpo::value(), "input file"); bpo::positional_options_description p; p.add("mode", 1).add("input-file", 3); @@ -69,81 +75,82 @@ bool parseOptions (int argc, char** argv, Arguments &info) bpo::variables_map variables; try { - bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) - .options(all).positional(p).run(); + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).options(all).positional(p).run(); bpo::store(valid_opts, variables); } - catch(std::exception &e) + catch (std::exception& e) { - std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" - << desc << std::endl; + std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl; return false; } bpo::notify(variables); - if (variables.count ("help")) + if (variables.count("help")) { std::cout << desc << std::endl; return false; } - if (variables.count ("version")) + if (variables.count("version")) { std::cout << "BSATool version " << BSATOOL_VERSION << std::endl; return false; } if (!variables.count("mode")) { - std::cout << "ERROR: no mode specified!\n\n" - << desc << std::endl; + std::cout << "ERROR: no mode specified!\n\n" << desc << std::endl; return false; } info.mode = variables["mode"].as(); - if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create")) + if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" + || info.mode == "create")) { - std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" - << desc << std::endl; + std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n" << desc << std::endl; return false; } if (!variables.count("input-file")) { - std::cout << "\nERROR: missing BSA archive\n\n" - << desc << std::endl; + std::cout << "\nERROR: missing BSA archive\n\n" << desc << std::endl; return false; } - info.filename = variables["input-file"].as< std::vector >()[0]; + auto inputFiles = variables["input-file"].as(); + + info.filename = inputFiles[0].u8string(); // This call to u8string is redundant, but required to build on MSVC 14.26 + // due to implementation bugs. // Default output to the working directory - info.outdir = "."; + info.outdir = std::filesystem::current_path(); if (info.mode == "extract") { - if (variables["input-file"].as< std::vector >().size() < 2) + if (inputFiles.size() < 2) { - std::cout << "\nERROR: file to extract unspecified\n\n" - << desc << std::endl; + std::cout << "\nERROR: file to extract unspecified\n\n" << desc << std::endl; return false; } - if (variables["input-file"].as< std::vector >().size() > 1) - info.extractfile = variables["input-file"].as< std::vector >()[1]; - if (variables["input-file"].as< std::vector >().size() > 2) - info.outdir = variables["input-file"].as< std::vector >()[2]; + if (inputFiles.size() > 1) + info.extractfile = inputFiles[1].u8string(); // This call to u8string is redundant, but required to build on + // MSVC 14.26 due to implementation bugs. + if (inputFiles.size() > 2) + info.outdir = inputFiles[2].u8string(); // This call to u8string is redundant, but required to build on + // MSVC 14.26 due to implementation bugs. } else if (info.mode == "add") { - if (variables["input-file"].as< std::vector >().size() < 1) + if (inputFiles.empty()) { - std::cout << "\nERROR: file to add unspecified\n\n" - << desc << std::endl; + std::cout << "\nERROR: file to add unspecified\n\n" << desc << std::endl; return false; } - if (variables["input-file"].as< std::vector >().size() > 1) - info.addfile = variables["input-file"].as< std::vector >()[1]; + if (inputFiles.size() > 1) + info.addfile = inputFiles[1].u8string(); // This call to u8string is redundant, but required to build on + // MSVC 14.26 due to implementation bugs. } - else if (variables["input-file"].as< std::vector >().size() > 1) - info.outdir = variables["input-file"].as< std::vector >()[1]; + else if (inputFiles.size() > 1) + info.outdir = inputFiles[1].u8string(); // This call to u8string is redundant, but required to build on + // MSVC 14.26 due to implementation bugs. info.longformat = variables.count("long") != 0; info.fullpath = variables.count("full-path") != 0; @@ -151,65 +158,14 @@ bool parseOptions (int argc, char** argv, Arguments &info) return true; } -int list(std::unique_ptr& bsa, Arguments& info); -int extract(std::unique_ptr& bsa, Arguments& info); -int extractAll(std::unique_ptr& bsa, Arguments& info); -int add(std::unique_ptr& bsa, Arguments& info); - -int main(int argc, char** argv) -{ - try - { - Arguments info; - if(!parseOptions (argc, argv, info)) - return 1; - - // Open file - std::unique_ptr bsa; - - Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(info.filename); - - if (bsaVersion == Bsa::BSAVER_COMPRESSED) - bsa = std::make_unique(Bsa::CompressedBSAFile()); - else - bsa = std::make_unique(Bsa::BSAFile()); - - if (info.mode == "create") - { - bsa->open(info.filename); - return 0; - } - - bsa->open(info.filename); - - if (info.mode == "list") - return list(bsa, info); - else if (info.mode == "extract") - return extract(bsa, info); - else if (info.mode == "extractall") - return extractAll(bsa, info); - else if (info.mode == "add") - return add(bsa, info); - else - { - std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; - return 1; - } - } - catch (std::exception& e) - { - std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl; - return 2; - } -} - -int list(std::unique_ptr& bsa, Arguments& info) +template +int list(std::unique_ptr& bsa, Arguments& info) { // List all files - const Bsa::BSAFile::FileList &files = bsa->getList(); + const auto& files = bsa->getList(); for (const auto& file : files) { - if(info.longformat) + if (info.longformat) { // Long format std::ios::fmtflags f(std::cout.flags()); @@ -225,48 +181,57 @@ int list(std::unique_ptr& bsa, Arguments& info) return 0; } -int extract(std::unique_ptr& bsa, Arguments& info) +template +int extract(std::unique_ptr& bsa, Arguments& info) { - std::string archivePath = info.extractfile; - Misc::StringUtils::replaceAll(archivePath, "/", "\\"); + auto archivePath = info.extractfile.u8string(); + Misc::StringUtils::replaceAll(archivePath, u8"/", u8"\\"); - std::string extractPath = info.extractfile; - Misc::StringUtils::replaceAll(extractPath, "\\", "/"); + auto extractPath = info.extractfile.u8string(); + Misc::StringUtils::replaceAll(extractPath, u8"\\", u8"/"); - if (!bsa->exists(archivePath.c_str())) + Files::IStreamPtr stream; + // Get a stream for the file to extract + for (auto it = bsa->getList().rbegin(); it != bsa->getList().rend(); ++it) { - std::cout << "ERROR: file '" << archivePath << "' not found\n"; - std::cout << "In archive: " << info.filename << std::endl; + if (Misc::StringUtils::ciEqual(Misc::StringUtils::stringToU8String(it->name()), archivePath)) + { + stream = bsa->getFile(&*it); + break; + } + } + if (!stream) + { + std::cout << "ERROR: file '" << Misc::StringUtils::u8StringToString(archivePath) << "' not found\n"; + std::cout << "In archive: " << Files::pathToUnicodeString(info.filename) << std::endl; return 3; } // Get the target path (the path the file will be extracted to) - bfs::path relPath (extractPath); - bfs::path outdir (info.outdir); + std::filesystem::path relPath(extractPath); - bfs::path target; + std::filesystem::path target; if (info.fullpath) - target = outdir / relPath; + target = info.outdir / relPath; else - target = outdir / relPath.filename(); + target = info.outdir / relPath.filename(); // Create the directory hierarchy - bfs::create_directories(target.parent_path()); + std::filesystem::create_directories(target.parent_path()); - bfs::file_status s = bfs::status(target.parent_path()); - if (!bfs::is_directory(s)) + std::filesystem::file_status s = std::filesystem::status(target.parent_path()); + if (!std::filesystem::is_directory(s)) { - std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl; + std::cout << "ERROR: " << Files::pathToUnicodeString(target.parent_path()) << " is not a directory." + << std::endl; return 3; } - // Get a stream for the file to extract - Files::IStreamPtr stream = bsa->getFile(archivePath.c_str()); - - bfs::ofstream out(target, std::ios::binary); + std::ofstream out(target, std::ios::binary); // Write the file to disk - std::cout << "Extracting " << info.extractfile << " to " << target << std::endl; + std::cout << "Extracting " << Files::pathToUnicodeString(info.extractfile) << " to " + << Files::pathToUnicodeString(target) << std::endl; out << stream->rdbuf(); out.close(); @@ -274,34 +239,34 @@ int extract(std::unique_ptr& bsa, Arguments& info) return 0; } -int extractAll(std::unique_ptr& bsa, Arguments& info) +template +int extractAll(std::unique_ptr& bsa, Arguments& info) { - for (const auto &file : bsa->getList()) + for (const auto& file : bsa->getList()) { std::string extractPath(file.name()); Misc::StringUtils::replaceAll(extractPath, "\\", "/"); // Get the target path (the path the file will be extracted to) - bfs::path target (info.outdir); - target /= extractPath; + auto target = info.outdir; + target /= Misc::StringUtils::stringToU8String(extractPath); // Create the directory hierarchy - bfs::create_directories(target.parent_path()); + std::filesystem::create_directories(target.parent_path()); - bfs::file_status s = bfs::status(target.parent_path()); - if (!bfs::is_directory(s)) + std::filesystem::file_status s = std::filesystem::status(target.parent_path()); + if (!std::filesystem::is_directory(s)) { std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl; return 3; } // Get a stream for the file to extract - // (inefficient because getFile iter on the list again) - Files::IStreamPtr data = bsa->getFile(file.name()); - bfs::ofstream out(target, std::ios::binary); + Files::IStreamPtr data = bsa->getFile(&file); + std::ofstream out(target, std::ios::binary); // Write the file to disk - std::cout << "Extracting " << target << std::endl; + std::cout << "Extracting " << Files::pathToUnicodeString(target) << std::endl; out << data->rdbuf(); out.close(); } @@ -309,10 +274,71 @@ int extractAll(std::unique_ptr& bsa, Arguments& info) return 0; } -int add(std::unique_ptr& bsa, Arguments& info) +template +int add(std::unique_ptr& bsa, Arguments& info) { - boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in); - bsa->addFile(info.addfile, stream); + std::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in); + bsa->addFile(Files::pathToUnicodeString(info.addfile), stream); return 0; } + +template +int call(Arguments& info) +{ + std::unique_ptr bsa = std::make_unique(); + if (info.mode == "create") + { + bsa->open(info.filename); + return 0; + } + + bsa->open(info.filename); + + if (info.mode == "list") + return list(bsa, info); + else if (info.mode == "extract") + return extract(bsa, info); + else if (info.mode == "extractall") + return extractAll(bsa, info); + else if (info.mode == "add") + return add(bsa, info); + else + { + std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; + return 1; + } +} + +int main(int argc, char** argv) +{ + try + { + Arguments info; + if (!parseOptions(argc, argv, info)) + return 1; + + // Open file + + Bsa::BsaVersion bsaVersion = Bsa::BSAFile::detectVersion(info.filename); + + switch (bsaVersion) + { + case Bsa::BSAVER_COMPRESSED: + return call(info); + case Bsa::BSAVER_BA2_GNRL: + return call(info); + case Bsa::BSAVER_BA2_DX10: + return call(info); + case Bsa::BSAVER_UNCOMPRESSED: + return call(info); + default: + throw std::runtime_error("Unrecognised BSA archive"); + } + } + catch (std::exception& e) + { + std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl; + return 2; + } +} diff --git a/apps/bulletobjecttool/CMakeLists.txt b/apps/bulletobjecttool/CMakeLists.txt new file mode 100644 index 000000000..924f66301 --- /dev/null +++ b/apps/bulletobjecttool/CMakeLists.txt @@ -0,0 +1,27 @@ +set(BULLETMESHTOOL + main.cpp +) +source_group(apps\\bulletobjecttool FILES ${BULLETMESHTOOL}) + +openmw_add_executable(openmw-bulletobjecttool ${BULLETMESHTOOL}) + +target_link_libraries(openmw-bulletobjecttool + ${Boost_PROGRAM_OPTIONS_LIBRARY} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + target_compile_options(openmw-bulletobjecttool PRIVATE --coverage) + target_link_libraries(openmw-bulletobjecttool gcov) +endif() + +if (WIN32) + install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".") +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw-bulletobjecttool PRIVATE + + + ) +endif() diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp new file mode 100644 index 000000000..2610061af --- /dev/null +++ b/apps/bulletobjecttool/main.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + namespace bpo = boost::program_options; + + using StringsVector = std::vector; + + constexpr std::string_view applicationName = "BulletObjectTool"; + + bpo::options_description makeOptionsDescription() + { + using Fallback::FallbackMap; + + bpo::options_description result; + auto addOption = result.add_options(); + addOption("help", "print help message"); + + addOption("version", "print version information and quit"); + + addOption("data", + bpo::value() + ->default_value(Files::MaybeQuotedPathContainer(), "data") + ->multitoken() + ->composing(), + "set data directories (later directories have higher priority)"); + + addOption("data-local", + bpo::value()->default_value( + Files::MaybeQuotedPathContainer::value_type(), ""), + "set local data directory (highest priority)"); + + addOption("fallback-archive", + bpo::value()->default_value(StringsVector(), "fallback-archive")->multitoken()->composing(), + "set fallback BSA archives (later archives have higher priority)"); + + addOption("resources", + bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), + "set resources directory"); + + addOption("content", bpo::value()->default_value(StringsVector(), "")->multitoken()->composing(), + "content file(s): esm/esp, or omwgame/omwaddon/omwscripts"); + + addOption("encoding", bpo::value()->default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, " + "Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default"); + + addOption("fallback", bpo::value()->default_value(FallbackMap(), "")->multitoken()->composing(), + "fallback values"); + + Files::ConfigurationManager::addCommonOptions(result); + + return result; + } + + struct WriteArray + { + const float (&mValue)[3]; + + friend std::ostream& operator<<(std::ostream& stream, const WriteArray& value) + { + for (std::size_t i = 0; i < 2; ++i) + stream << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue[i] << ", "; + return stream << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue[2]; + } + }; + + int runBulletObjectTool(int argc, char* argv[]) + { + Platform::init(); + + bpo::options_description desc = makeOptionsDescription(); + + bpo::parsed_options options = bpo::command_line_parser(argc, argv).options(desc).allow_unregistered().run(); + bpo::variables_map variables; + + bpo::store(options, variables); + bpo::notify(variables); + + if (variables.find("help") != variables.end()) + { + getRawStdout() << desc << std::endl; + return 0; + } + + Files::ConfigurationManager config; + config.readConfiguration(variables, desc); + + setupLogging(config.getLogPath(), applicationName); + + const std::string encoding(variables["encoding"].as()); + Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding)); + + Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); + + auto local = variables["data-local"].as(); + if (!local.empty()) + dataDirs.push_back(std::move(local)); + + config.filterOutNonExistingPaths(dataDirs); + + const auto resDir = variables["resources"].as(); + const auto v = Version::getOpenmwVersion(resDir); + Log(Debug::Info) << v.describe(); + dataDirs.insert(dataDirs.begin(), resDir / "vfs"); + const auto fileCollections = Files::Collections(dataDirs); + const auto archives = variables["fallback-archive"].as(); + const auto contentFiles = variables["content"].as(); + + Fallback::Map::init(variables["fallback"].as().mMap); + + VFS::Manager vfs; + + VFS::registerArchives(&vfs, fileCollections, archives, true); + + Settings::Manager::load(config); + + ESM::ReadersCache readers; + EsmLoader::Query query; + query.mLoadActivators = true; + query.mLoadCells = true; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = true; + query.mLoadLands = true; + query.mLoadStatics = true; + const EsmLoader::EsmData esmData + = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); + + Resource::ImageManager imageManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); + Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); + + Resource::forEachBulletObject( + readers, vfs, bulletShapeManager, esmData, [](const ESM::Cell& cell, const Resource::BulletObject& object) { + Log(Debug::Verbose) << "Found bullet object in " << (cell.isExterior() ? "exterior" : "interior") + << " cell \"" << cell.getDescription() << "\":" + << " fileName=\"" << object.mShape->mFileName << '"' + << " fileHash=" << Misc::StringUtils::toHex(object.mShape->mFileHash) + << " collisionShape=" << std::boolalpha + << (object.mShape->mCollisionShape == nullptr) + << " avoidCollisionShape=" << std::boolalpha + << (object.mShape->mAvoidCollisionShape == nullptr) << " position=(" + << WriteArray{ object.mPosition.pos } << ')' << " rotation=(" + << WriteArray{ object.mPosition.rot } << ')' + << " scale=" << std::setprecision(std::numeric_limits::max_exponent10) + << object.mScale; + }); + + Log(Debug::Info) << "Done"; + + return 0; + } +} + +int main(int argc, char* argv[]) +{ + return wrapApplication(runBulletObjectTool, argc, argv, applicationName); +} diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 122ca2f3a..ad95b2b8e 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -1,23 +1,34 @@ set(ESMTOOL - esmtool.cpp - labels.hpp - labels.cpp - record.hpp - record.cpp + esmtool.cpp + labels.hpp + labels.cpp + record.hpp + record.cpp + arguments.hpp + tes4.hpp + tes4.cpp ) source_group(apps\\esmtool FILES ${ESMTOOL}) # Main executable openmw_add_executable(esmtool - ${ESMTOOL} + ${ESMTOOL} ) target_link_libraries(esmtool - ${Boost_PROGRAM_OPTIONS_LIBRARY} - components + ${Boost_PROGRAM_OPTIONS_LIBRARY} + components ) if (BUILD_WITH_CODE_COVERAGE) - add_definitions (--coverage) - target_link_libraries(esmtool gcov) + target_compile_options(esmtool PRIVATE --coverage) + target_link_libraries(esmtool gcov) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(esmtool PRIVATE + + + + ) endif() diff --git a/apps/esmtool/arguments.hpp b/apps/esmtool/arguments.hpp new file mode 100644 index 000000000..296b69708 --- /dev/null +++ b/apps/esmtool/arguments.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_ESMTOOL_ARGUMENTS_H +#define OPENMW_ESMTOOL_ARGUMENTS_H + +#include +#include +#include + +#include + +namespace EsmTool +{ + struct Arguments + { + std::optional mRawFormat; + bool quiet_given = false; + bool loadcells_given = false; + bool plain_given = false; + + std::string mode; + std::string encoding; + std::filesystem::path filename; + std::filesystem::path outname; + + std::vector types; + std::string name; + }; +} + +#endif diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 60483f981..092be66e9 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -1,220 +1,205 @@ -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include "arguments.hpp" +#include "labels.hpp" #include "record.hpp" +#include "tes4.hpp" -#define ESMTOOL_VERSION 1.2 - -// Create a local alias for brevity -namespace bpo = boost::program_options; - -struct ESMData +namespace { - std::string author; - std::string description; - unsigned int version; - std::vector masters; - std::deque mRecords; - // Value: (Reference, Deleted flag) - std::map > > mCellRefs; - std::map mRecordStats; + using namespace EsmTool; - static const std::set sLabeledRec; -}; + constexpr unsigned majorVersion = 1; + constexpr unsigned minorVersion = 3; -static const int sLabeledRecIds[] = { - ESM::REC_GLOB, ESM::REC_CLAS, ESM::REC_FACT, ESM::REC_RACE, ESM::REC_SOUN, - ESM::REC_REGN, ESM::REC_BSGN, ESM::REC_LTEX, ESM::REC_STAT, ESM::REC_DOOR, - ESM::REC_MISC, ESM::REC_WEAP, ESM::REC_CONT, ESM::REC_SPEL, ESM::REC_CREA, - ESM::REC_BODY, ESM::REC_LIGH, ESM::REC_ENCH, ESM::REC_NPC_, ESM::REC_ARMO, - ESM::REC_CLOT, ESM::REC_REPA, ESM::REC_ACTI, ESM::REC_APPA, ESM::REC_LOCK, - ESM::REC_PROB, ESM::REC_INGR, ESM::REC_BOOK, ESM::REC_ALCH, ESM::REC_LEVI, - ESM::REC_LEVC, ESM::REC_SNDG, ESM::REC_CELL, ESM::REC_DIAL -}; + // Create a local alias for brevity + namespace bpo = boost::program_options; -const std::set ESMData::sLabeledRec = - std::set(sLabeledRecIds, sLabeledRecIds + 34); + struct ESMData + { + ESM::Header mHeader; + std::deque> mRecords; + // Value: (Reference, Deleted flag) + std::map>> mCellRefs; + std::map mRecordStats; + }; -// Based on the legacy struct -struct Arguments -{ - bool raw_given; - bool quiet_given; - bool loadcells_given; - bool plain_given; + bool parseOptions(int argc, char** argv, Arguments& info) + { + bpo::options_description desc(R"(Inspect and extract from Morrowind ES files (ESM, ESP, ESS) +Syntax: esmtool [options] mode infile [outfile] +Allowed modes: + dump Dumps all readable data from the input file. + clone Clones the input file to the output file. + comp Compares the given files. - std::string mode; - std::string encoding; - std::string filename; - std::string outname; - - std::vector types; - std::string name; - - ESMData data; - ESM::ESMReader reader; - ESM::ESMWriter writer; -}; - -bool parseOptions (int argc, char** argv, Arguments &info) -{ - bpo::options_description desc("Inspect and extract from Morrowind ES files (ESM, ESP, ESS)\nSyntax: esmtool [options] mode infile [outfile]\nAllowed modes:\n dump\t Dumps all readable data from the input file.\n clone\t Clones the input file to the output file.\n comp\t Compares the given files.\n\nAllowed options"); - - desc.add_options() - ("help,h", "print help message.") - ("version,v", "print version information and quit.") - ("raw,r", "Show an unformatted list of all records and subrecords.") +Allowed options)"); + auto addOption = desc.add_options(); + addOption("help,h", "print help message."); + addOption("version,v", "print version information and quit."); + addOption("raw,r", bpo::value(), + "Show an unformatted list of all records and subrecords of given format:\n" + "\n\tTES3" + "\n\tTES4"); // The intention is that this option would interact better // with other modes including clone, dump, and raw. - ("type,t", bpo::value< std::vector >(), - "Show only records of this type (four character record code). May " - "be specified multiple times. Only affects dump mode.") - ("name,n", bpo::value(), - "Show only the record with this name. Only affects dump mode.") - ("plain,p", "Print contents of dialogs, books and scripts. " - "(skipped by default)" - "Only affects dump mode.") - ("quiet,q", "Suppress all record information. Useful for speed tests.") - ("loadcells,C", "Browse through contents of all cells.") + addOption("type,t", bpo::value>(), + "Show only records of this type (four character record code). May " + "be specified multiple times. Only affects dump mode."); + addOption("name,n", bpo::value(), "Show only the record with this name. Only affects dump mode."); + addOption("plain,p", + "Print contents of dialogs, books and scripts. " + "(skipped by default)" + "Only affects dump mode."); + addOption("quiet,q", "Suppress all record information. Useful for speed tests."); + addOption("loadcells,C", "Browse through contents of all cells."); - ( "encoding,e", bpo::value(&(info.encoding))-> - default_value("win1252"), - "Character encoding used in ESMTool:\n" - "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" - "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" - "\n\twin1252 - Western European (Latin) alphabet, used by default") - ; + addOption("encoding,e", bpo::value(&(info.encoding))->default_value("win1252"), + "Character encoding used in ESMTool:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, " + "Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default"); - std::string finalText = "\nIf no option is given, the default action is to parse all records in the archive\nand display diagnostic information."; + std::string finalText + = "\nIf no option is given, the default action is to parse all records in the archive\nand display " + "diagnostic information."; - // input-file is hidden and used as a positional argument - bpo::options_description hidden("Hidden Options"); + // input-file is hidden and used as a positional argument + bpo::options_description hidden("Hidden Options"); + auto addHiddenOption = hidden.add_options(); + addHiddenOption("mode,m", bpo::value(), "esmtool mode"); + addHiddenOption("input-file,i", bpo::value(), "input file"); - hidden.add_options() - ( "mode,m", bpo::value(), "esmtool mode") - ( "input-file,i", bpo::value< std::vector >(), "input file") - ; + bpo::positional_options_description p; + p.add("mode", 1).add("input-file", 2); - bpo::positional_options_description p; - p.add("mode", 1).add("input-file", 2); + // there might be a better way to do this + bpo::options_description all; + all.add(desc).add(hidden); + bpo::variables_map variables; - // there might be a better way to do this - bpo::options_description all; - all.add(desc).add(hidden); - bpo::variables_map variables; + try + { + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).options(all).positional(p).run(); - try - { - bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) - .options(all).positional(p).run(); + bpo::store(valid_opts, variables); + } + catch (std::exception& e) + { + std::cout << "ERROR parsing arguments: " << e.what() << std::endl; + return false; + } - bpo::store(valid_opts, variables); - } - catch(std::exception &e) - { - std::cout << "ERROR parsing arguments: " << e.what() << std::endl; - return false; + bpo::notify(variables); + + if (variables.count("help")) + { + std::cout << desc << finalText << std::endl; + return false; + } + if (variables.count("version")) + { + std::cout << "ESMTool version " << majorVersion << '.' << minorVersion << std::endl; + return false; + } + if (!variables.count("mode")) + { + std::cout << "No mode specified!\n\n" << desc << finalText << std::endl; + return false; + } + + if (variables.count("type") > 0) + info.types = variables["type"].as>(); + if (variables.count("name") > 0) + info.name = variables["name"].as(); + + info.mode = variables["mode"].as(); + if (!(info.mode == "dump" || info.mode == "clone" || info.mode == "comp")) + { + std::cout << "\nERROR: invalid mode \"" << info.mode << "\"\n\n" << desc << finalText << std::endl; + return false; + } + + if (!variables.count("input-file")) + { + std::cout << "\nERROR: missing ES file\n\n"; + std::cout << desc << finalText << std::endl; + return false; + } + + // handling gracefully the user adding multiple files + /* if (variables["input-file"].as< std::vector >().size() > 1) + { + std::cout << "\nERROR: more than one ES file specified\n\n"; + std::cout << desc << finalText << std::endl; + return false; + }*/ + + const auto inputFiles = variables["input-file"].as(); + info.filename = inputFiles[0].u8string(); // This call to u8string is redundant, but required to build on + // MSVC 14.26 due to implementation bugs. + if (inputFiles.size() > 1) + info.outname = inputFiles[1].u8string(); // This call to u8string is redundant, but required to build on + // MSVC 14.26 due to implementation bugs. + + if (const auto it = variables.find("raw"); it != variables.end()) + info.mRawFormat = ESM::parseFormat(it->second.as()); + + info.quiet_given = variables.count("quiet") != 0; + info.loadcells_given = variables.count("loadcells") != 0; + info.plain_given = variables.count("plain") != 0; + + // Font encoding settings + info.encoding = variables["encoding"].as(); + if (info.encoding != "win1250" && info.encoding != "win1251" && info.encoding != "win1252") + { + std::cout << info.encoding << " is not a valid encoding option.\n"; + info.encoding = "win1252"; + } + std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl; + + return true; } - bpo::notify(variables); + void loadCell(const Arguments& info, ESM::Cell& cell, ESM::ESMReader& esm, ESMData* data); - if (variables.count ("help")) - { - std::cout << desc << finalText << std::endl; - return false; - } - if (variables.count ("version")) - { - std::cout << "ESMTool version " << ESMTOOL_VERSION << std::endl; - return false; - } - if (!variables.count("mode")) - { - std::cout << "No mode specified!" << std::endl << std::endl - << desc << finalText << std::endl; - return false; - } + int load(const Arguments& info, ESMData* data); + int clone(const Arguments& info); + int comp(const Arguments& info); - if (variables.count("type") > 0) - info.types = variables["type"].as< std::vector >(); - if (variables.count("name") > 0) - info.name = variables["name"].as(); - - info.mode = variables["mode"].as(); - if (!(info.mode == "dump" || info.mode == "clone" || info.mode == "comp")) - { - std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"" << std::endl << std::endl - << desc << finalText << std::endl; - return false; - } - - if ( !variables.count("input-file") ) - { - std::cout << "\nERROR: missing ES file\n\n"; - std::cout << desc << finalText << std::endl; - return false; - } - - // handling gracefully the user adding multiple files -/* if (variables["input-file"].as< std::vector >().size() > 1) - { - std::cout << "\nERROR: more than one ES file specified\n\n"; - std::cout << desc << finalText << std::endl; - return false; - }*/ - - info.filename = variables["input-file"].as< std::vector >()[0]; - if (variables["input-file"].as< std::vector >().size() > 1) - info.outname = variables["input-file"].as< std::vector >()[1]; - - info.raw_given = variables.count ("raw") != 0; - info.quiet_given = variables.count ("quiet") != 0; - info.loadcells_given = variables.count ("loadcells") != 0; - info.plain_given = variables.count("plain") != 0; - - // Font encoding settings - info.encoding = variables["encoding"].as(); - if(info.encoding != "win1250" && info.encoding != "win1251" && info.encoding != "win1252") - { - std::cout << info.encoding << " is not a valid encoding option." << std::endl; - info.encoding = "win1252"; - } - std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl; - - return true; } -void printRaw(ESM::ESMReader &esm); -void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info); - -int load(Arguments& info); -int clone(Arguments& info); -int comp(Arguments& info); - -int main(int argc, char**argv) +int main(int argc, char** argv) { try { Arguments info; - if(!parseOptions (argc, argv, info)) + if (!parseOptions(argc, argv, info)) return 1; if (info.mode == "dump") - return load(info); + return load(info, nullptr); else if (info.mode == "clone") return clone(info); else if (info.mode == "comp") @@ -234,340 +219,383 @@ int main(int argc, char**argv) return 0; } -void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) +namespace { - bool quiet = (info.quiet_given || info.mode == "clone"); - bool save = (info.mode == "clone"); - // Skip back to the beginning of the reference list - // FIXME: Changes to the references backend required to support multiple plugins have - // almost certainly broken this following line. I'll leave it as is for now, so that - // the compiler does not complain. - cell.restore(esm, 0); - - // Loop through all the references - ESM::CellRef ref; - if(!quiet) std::cout << " References:\n"; - - bool deleted = false; - while(cell.getNextRef(esm, ref, deleted)) + void loadCell(const Arguments& info, ESM::Cell& cell, ESM::ESMReader& esm, ESMData* data) { - if (save) { - info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); - } + bool quiet = (info.quiet_given || info.mode == "clone"); + bool save = (info.mode == "clone"); - if(quiet) continue; + // Skip back to the beginning of the reference list + // FIXME: Changes to the references backend required to support multiple plugins have + // almost certainly broken this following line. I'll leave it as is for now, so that + // the compiler does not complain. + cell.restore(esm, 0); - std::cout << " Refnum: " << ref.mRefNum.mIndex << std::endl; - std::cout << " ID: " << ref.mRefID << std::endl; - std::cout << " Position: (" << ref.mPos.pos[0] << ", " << ref.mPos.pos[1] << ", " << ref.mPos.pos[2] << ")" << std::endl; - if (ref.mScale != 1.f) - std::cout << " Scale: " << ref.mScale << std::endl; - if (!ref.mOwner.empty()) - std::cout << " Owner: " << ref.mOwner << std::endl; - if (!ref.mGlobalVariable.empty()) - std::cout << " Global: " << ref.mGlobalVariable << std::endl; - if (!ref.mFaction.empty()) - std::cout << " Faction: " << ref.mFaction << std::endl; - if (!ref.mFaction.empty() || ref.mFactionRank != -2) - std::cout << " Faction rank: " << ref.mFactionRank << std::endl; - std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << std::endl; - std::cout << " Uses/health: " << ref.mChargeInt << std::endl; - std::cout << " Gold value: " << ref.mGoldValue << std::endl; - std::cout << " Blocked: " << static_cast(ref.mReferenceBlocked) << std::endl; - std::cout << " Deleted: " << deleted << std::endl; - if (!ref.mKey.empty()) - std::cout << " Key: " << ref.mKey << std::endl; - std::cout << " Lock level: " << ref.mLockLevel << std::endl; - if (!ref.mTrap.empty()) - std::cout << " Trap: " << ref.mTrap << std::endl; - if (!ref.mSoul.empty()) - std::cout << " Soul: " << ref.mSoul << std::endl; - if (ref.mTeleport) + // Loop through all the references + ESM::CellRef ref; + if (!quiet) + std::cout << " References:\n"; + + bool deleted = false; + ESM::MovedCellRef movedCellRef; + bool moved = false; + while (cell.getNextRef(esm, ref, deleted, movedCellRef, moved)) { - std::cout << " Destination position: (" << ref.mDoorDest.pos[0] << ", " - << ref.mDoorDest.pos[1] << ", " << ref.mDoorDest.pos[2] << ")" << std::endl; - if (!ref.mDestCell.empty()) - std::cout << " Destination cell: " << ref.mDestCell << std::endl; + if (data != nullptr && save) + data->mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); + + if (quiet) + continue; + + std::cout << " - Refnum: " << ref.mRefNum.mIndex << '\n'; + std::cout << " ID: " << ref.mRefID << '\n'; + std::cout << " Position: (" << ref.mPos.pos[0] << ", " << ref.mPos.pos[1] << ", " << ref.mPos.pos[2] + << ")\n"; + if (ref.mScale != 1.f) + std::cout << " Scale: " << ref.mScale << '\n'; + if (!ref.mOwner.empty()) + std::cout << " Owner: " << ref.mOwner << '\n'; + if (!ref.mGlobalVariable.empty()) + std::cout << " Global: " << ref.mGlobalVariable << '\n'; + if (!ref.mFaction.empty()) + std::cout << " Faction: " << ref.mFaction << '\n'; + if (!ref.mFaction.empty() || ref.mFactionRank != -2) + std::cout << " Faction rank: " << ref.mFactionRank << '\n'; + std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << '\n'; + std::cout << " Uses/health: " << ref.mChargeInt << '\n'; + std::cout << " Gold value: " << ref.mGoldValue << '\n'; + std::cout << " Blocked: " << static_cast(ref.mReferenceBlocked) << '\n'; + std::cout << " Deleted: " << deleted << '\n'; + if (!ref.mKey.empty()) + std::cout << " Key: " << ref.mKey << '\n'; + std::cout << " Lock level: " << ref.mLockLevel << '\n'; + if (!ref.mTrap.empty()) + std::cout << " Trap: " << ref.mTrap << '\n'; + if (!ref.mSoul.empty()) + std::cout << " Soul: " << ref.mSoul << '\n'; + if (ref.mTeleport) + { + std::cout << " Destination position: (" << ref.mDoorDest.pos[0] << ", " << ref.mDoorDest.pos[1] + << ", " << ref.mDoorDest.pos[2] << ")\n"; + if (!ref.mDestCell.empty()) + std::cout << " Destination cell: " << ref.mDestCell << '\n'; + } + std::cout << " Moved: " << std::boolalpha << moved << std::noboolalpha << '\n'; + if (moved) + { + std::cout << " Moved refnum: " << movedCellRef.mRefNum.mIndex << '\n'; + std::cout << " Moved content file: " << movedCellRef.mRefNum.mContentFile << '\n'; + std::cout << " Target: " << movedCellRef.mTarget[0] << ", " << movedCellRef.mTarget[1] << '\n'; + } } } -} -void printRaw(ESM::ESMReader &esm) -{ - while(esm.hasMoreRecs()) + void printRawTes3(const std::filesystem::path& path) { - ESM::NAME n = esm.getRecName(); - std::cout << "Record: " << n.toString() << std::endl; - esm.getRecHeader(); - while(esm.hasMoreSubs()) + std::cout << "TES3 RAW file listing: " << Files::pathToUnicodeString(path) << '\n'; + ESM::ESMReader esm; + esm.openRaw(path); + while (esm.hasMoreRecs()) { - size_t offs = esm.getFileOffset(); - esm.getSubName(); - esm.skipHSub(); - n = esm.retSubName(); - std::ios::fmtflags f(std::cout.flags()); - std::cout << " " << n.toString() << " - " << esm.getSubSize() - << " bytes @ 0x" << std::hex << offs << "\n"; - std::cout.flags(f); + ESM::NAME n = esm.getRecName(); + std::cout << "Record: " << n.toStringView() << '\n'; + esm.getRecHeader(); + while (esm.hasMoreSubs()) + { + size_t offs = esm.getFileOffset(); + esm.getSubName(); + esm.skipHSub(); + n = esm.retSubName(); + std::ios::fmtflags f(std::cout.flags()); + std::cout << " " << n.toStringView() << " - " << esm.getSubSize() << " bytes @ 0x" << std::hex + << offs << '\n'; + std::cout.flags(f); + } } } -} -int load(Arguments& info) -{ - ESM::ESMReader& esm = info.reader; - ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); - esm.setEncoder(&encoder); + int loadTes3(const Arguments& info, std::unique_ptr&& stream, ESMData* data) + { + std::cout << "Loading TES3 file: " << info.filename << '\n'; - std::string filename = info.filename; - std::cout << "Loading file: " << filename << std::endl; + ESM::ESMReader esm; + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding)); + esm.setEncoder(&encoder); - std::unordered_set skipped; + std::unordered_set skipped; - try { - - if(info.raw_given && info.mode == "dump") + try { - std::cout << "RAW file listing:\n"; + bool quiet = (info.quiet_given || info.mode == "clone"); + bool loadCells = (info.loadcells_given || info.mode == "clone"); + bool save = (info.mode == "clone"); - esm.openRaw(filename); + esm.open(std::move(stream), info.filename); - printRaw(esm); + if (data != nullptr) + data->mHeader = esm.getHeader(); + if (!quiet) + { + std::cout << "Author: " << esm.getAuthor() << '\n' + << "Description: " << esm.getDesc() << '\n' + << "File format version: " << esm.getFVer() << '\n'; + std::vector masterData = esm.getGameFiles(); + if (!masterData.empty()) + { + std::cout << "Masters:" << '\n'; + for (const auto& master : masterData) + std::cout << " " << master.name << ", " << master.size << " bytes\n"; + } + } + + // Loop through all records + while (esm.hasMoreRecs()) + { + const ESM::NAME n = esm.getRecName(); + uint32_t flags; + esm.getRecHeader(flags); + + auto record = EsmTool::RecordBase::create(n); + if (record == nullptr) + { + if (!quiet && skipped.count(n.toInt()) == 0) + { + std::cout << "Skipping " << n.toStringView() << " records.\n"; + skipped.emplace(n.toInt()); + } + + esm.skipRecord(); + if (quiet) + break; + std::cout << " Skipping\n"; + + continue; + } + + record->setFlags(static_cast(flags)); + record->setPrintPlain(info.plain_given); + record->load(esm); + + // Is the user interested in this record type? + bool interested = true; + if (!info.types.empty() + && std::find(info.types.begin(), info.types.end(), n.toStringView()) == info.types.end()) + interested = false; + + if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) + interested = false; + + if (!quiet && interested) + { + std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n" + << "Record flags: " << recordFlags(record->getFlags()) << '\n'; + record->print(); + } + + if (record->getType().toInt() == ESM::REC_CELL && loadCells && interested) + { + loadCell(info, record->cast()->get(), esm, data); + } + + if (data != nullptr) + { + if (save) + data->mRecords.push_back(std::move(record)); + ++data->mRecordStats[n.toInt()]; + } + } + } + catch (const std::exception& e) + { + std::cout << "\nERROR:\n\n " << e.what() << std::endl; + if (data != nullptr) + data->mRecords.clear(); + return 1; + } + + return 0; + } + + int load(const Arguments& info, ESMData* data) + { + if (info.mRawFormat.has_value() && info.mode == "dump") + { + switch (*info.mRawFormat) + { + case ESM::Format::Tes3: + printRawTes3(info.filename); + break; + case ESM::Format::Tes4: + std::cout << "Printing raw TES4 file is not supported: " + << Files::pathToUnicodeString(info.filename) << "\n"; + break; + } return 0; } - bool quiet = (info.quiet_given || info.mode == "clone"); - bool loadCells = (info.loadcells_given || info.mode == "clone"); - bool save = (info.mode == "clone"); - - esm.open(filename); - - info.data.author = esm.getAuthor(); - info.data.description = esm.getDesc(); - info.data.masters = esm.getGameFiles(); - - if (!quiet) + auto stream = Files::openBinaryInputFileStream(info.filename); + if (!stream->is_open()) { - std::cout << "Author: " << esm.getAuthor() << std::endl - << "Description: " << esm.getDesc() << std::endl - << "File format version: " << esm.getFVer() << std::endl; - std::vector masterData = esm.getGameFiles(); - if (!masterData.empty()) - { - std::cout << "Masters:" << std::endl; - for(const auto& master : masterData) - std::cout << " " << master.name << ", " << master.size << " bytes" << std::endl; - } + std::cout << "Failed to open file " << info.filename << ": " << std::generic_category().message(errno) + << '\n'; + return -1; } - // Loop through all records - while(esm.hasMoreRecs()) - { - const ESM::NAME n = esm.getRecName(); - uint32_t flags; - esm.getRecHeader(flags); + const ESM::Format format = ESM::readFormat(*stream); + stream->seekg(0); - EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); - if (record == nullptr) - { - if (skipped.count(n.intval) == 0) + switch (format) + { + case ESM::Format::Tes3: + return loadTes3(info, std::move(stream), data); + case ESM::Format::Tes4: + if (data != nullptr) { - std::cout << "Skipping " << n.toString() << " records." << std::endl; - skipped.emplace(n.intval); + std::cout << "Collecting data from esm file is not supported for TES4\n"; + return -1; } - - esm.skipRecord(); - if (quiet) break; - std::cout << " Skipping\n"; - - continue; - } - - record->setFlags(static_cast(flags)); - record->setPrintPlain(info.plain_given); - record->load(esm); - - // Is the user interested in this record type? - bool interested = true; - if (!info.types.empty()) - { - std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), n.toString()); - if (match == info.types.end()) interested = false; - } - - if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) - interested = false; - - if(!quiet && interested) - { - std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n"; - record->print(); - } - - if (record->getType().intval == ESM::REC_CELL && loadCells && interested) - { - loadCell(record->cast()->get(), esm, info); - } - - if (save) - { - info.data.mRecords.push_back(record); - } - else - { - delete record; - } - ++info.data.mRecordStats[n.intval]; + return loadTes4(info, std::move(stream)); } - } catch(std::exception &e) { - std::cout << "\nERROR:\n\n " << e.what() << std::endl; + std::cout << "Unsupported ESM format: " << ESM::NAME(format).toStringView() << '\n'; - for (const EsmTool::RecordBase* record : info.data.mRecords) - delete record; - - info.data.mRecords.clear(); - return 1; + return -1; } - return 0; -} - -#include - -int clone(Arguments& info) -{ - if (info.outname.empty()) + int clone(const Arguments& info) { - std::cout << "You need to specify an output name" << std::endl; - return 1; - } - - if (load(info) != 0) - { - std::cout << "Failed to load, aborting." << std::endl; - return 1; - } - - size_t recordCount = info.data.mRecords.size(); - - int digitCount = 1; // For a nicer output - if (recordCount > 0) - digitCount = (int)std::log10(recordCount) + 1; - - std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl; - - int i = 0; - for (std::pair stat : info.data.mRecordStats) - { - ESM::NAME name; - name.intval = stat.first; - int amount = stat.second; - std::cout << std::setw(digitCount) << amount << " " << name.toString() << " "; - if (++i % 3 == 0) - std::cout << std::endl; - } - - if (i % 3 != 0) - std::cout << std::endl; - - std::cout << std::endl << "Saving records to: " << info.outname << "..." << std::endl; - - ESM::ESMWriter& esm = info.writer; - ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); - esm.setEncoder(&encoder); - esm.setAuthor(info.data.author); - esm.setDescription(info.data.description); - esm.setVersion(info.data.version); - esm.setRecordCount (recordCount); - - for (const ESM::Header::MasterData &master : info.data.masters) - esm.addMaster(master.name, master.size); - - std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary); - esm.save(save); - - int saved = 0; - for (EsmTool::RecordBase* record : info.data.mRecords) - { - if (i <= 0) - break; - - const ESM::NAME& typeName = record->getType(); - - esm.startRecord(typeName.toString(), record->getFlags()); - - record->save(esm); - if (typeName.intval == ESM::REC_CELL) { - ESM::Cell *ptr = &record->cast()->get(); - if (!info.data.mCellRefs[ptr].empty()) - { - for (std::pair &ref : info.data.mCellRefs[ptr]) - ref.first.save(esm, ref.second); - } - } - - esm.endRecord(typeName.toString()); - - saved++; - int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount)*100); - if (perc % 10 == 0) + if (info.outname.empty()) { - std::cerr << "\r" << perc << "%"; + std::cout << "You need to specify an output name" << std::endl; + return 1; } + + ESMData data; + if (load(info, &data) != 0) + { + std::cout << "Failed to load, aborting." << std::endl; + return 1; + } + + size_t recordCount = data.mRecords.size(); + + int digitCount = 1; // For a nicer output + if (recordCount > 0) + digitCount = (int)std::log10(recordCount) + 1; + + std::cout << "Loaded " << recordCount << " records:\n\n"; + + int i = 0; + for (std::pair stat : data.mRecordStats) + { + ESM::NAME name; + name = stat.first; + int amount = stat.second; + std::cout << std::setw(digitCount) << amount << " " << name.toStringView() << " "; + if (++i % 3 == 0) + std::cout << '\n'; + } + + if (i % 3 != 0) + std::cout << '\n'; + + std::cout << "\nSaving records to: " << Files::pathToUnicodeString(info.outname) << "...\n"; + + ESM::ESMWriter esm; + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding)); + esm.setEncoder(&encoder); + esm.setHeader(data.mHeader); + esm.setVersion(ESM::VER_13); + esm.setRecordCount(recordCount); + + std::fstream save(info.outname, std::fstream::out | std::fstream::binary); + esm.save(save); + + int saved = 0; + for (auto& record : data.mRecords) + { + if (i <= 0) + break; + + const ESM::NAME typeName = record->getType(); + + esm.startRecord(typeName, record->getFlags()); + + record->save(esm); + if (typeName.toInt() == ESM::REC_CELL) + { + ESM::Cell* ptr = &record->cast()->get(); + if (!data.mCellRefs[ptr].empty()) + { + for (std::pair& ref : data.mCellRefs[ptr]) + ref.first.save(esm, ref.second); + } + } + + esm.endRecord(typeName); + + saved++; + int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount) * 100); + if (perc % 10 == 0) + { + std::cerr << "\r" << perc << "%"; + } + } + + std::cout << "\rDone!" << std::endl; + + esm.close(); + save.close(); + + return 0; } - std::cout << "\rDone!" << std::endl; + int comp(const Arguments& info) + { + if (info.filename.empty() || info.outname.empty()) + { + std::cout << "You need to specify two input files" << std::endl; + return 1; + } - esm.close(); - save.close(); + Arguments fileOne; + Arguments fileTwo; + + fileOne.mode = "clone"; + fileTwo.mode = "clone"; + + fileOne.encoding = info.encoding; + fileTwo.encoding = info.encoding; + + fileOne.filename = info.filename; + fileTwo.filename = info.outname; + + ESMData dataOne; + if (load(fileOne, &dataOne) != 0) + { + std::cout << "Failed to load " << Files::pathToUnicodeString(info.filename) << ", aborting comparison." + << std::endl; + return 1; + } + + ESMData dataTwo; + if (load(fileTwo, &dataTwo) != 0) + { + std::cout << "Failed to load " << Files::pathToUnicodeString(info.outname) << ", aborting comparison." + << std::endl; + return 1; + } + + if (dataOne.mRecords.size() != dataTwo.mRecords.size()) + { + std::cout << "Not equal, different amount of records." << std::endl; + return 1; + } + + return 0; + } - return 0; -} - -int comp(Arguments& info) -{ - if (info.filename.empty() || info.outname.empty()) - { - std::cout << "You need to specify two input files" << std::endl; - return 1; - } - - Arguments fileOne; - Arguments fileTwo; - - fileOne.raw_given = false; - fileTwo.raw_given = false; - - fileOne.mode = "clone"; - fileTwo.mode = "clone"; - - fileOne.encoding = info.encoding; - fileTwo.encoding = info.encoding; - - fileOne.filename = info.filename; - fileTwo.filename = info.outname; - - if (load(fileOne) != 0) - { - std::cout << "Failed to load " << info.filename << ", aborting comparison." << std::endl; - return 1; - } - - if (load(fileTwo) != 0) - { - std::cout << "Failed to load " << info.outname << ", aborting comparison." << std::endl; - return 1; - } - - if (fileOne.data.mRecords.size() != fileTwo.data.mRecords.size()) - { - std::cout << "Not equal, different amount of records." << std::endl; - return 1; - } - - return 0; } diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 24e3605eb..5f2026b72 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -1,25 +1,25 @@ #include "labels.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include -std::string bodyPartLabel(int idx) +std::string_view bodyPartLabel(int idx) { if (idx >= 0 && idx <= 26) { - static const char *bodyPartLabels[] = { + static constexpr std::string_view bodyPartLabels[] = { "Head", "Hair", "Neck", @@ -46,7 +46,7 @@ std::string bodyPartLabel(int idx) "Right Shoulder", "Left Shoulder", "Weapon", - "Tail" + "Tail", }; return bodyPartLabels[idx]; } @@ -54,11 +54,11 @@ std::string bodyPartLabel(int idx) return "Invalid"; } -std::string meshPartLabel(int idx) +std::string_view meshPartLabel(int idx) { if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail) { - static const char *meshPartLabels[] = { + static constexpr std::string_view meshPartLabels[] = { "Head", "Hair", "Neck", @@ -73,7 +73,7 @@ std::string meshPartLabel(int idx) "Knee", "Upper Leg", "Clavicle", - "Tail" + "Tail", }; return meshPartLabels[idx]; } @@ -81,14 +81,14 @@ std::string meshPartLabel(int idx) return "Invalid"; } -std::string meshTypeLabel(int idx) +std::string_view meshTypeLabel(int idx) { if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor) { - static const char *meshTypeLabels[] = { + static constexpr std::string_view meshTypeLabels[] = { "Skin", "Clothing", - "Armor" + "Armor", }; return meshTypeLabels[idx]; } @@ -96,11 +96,11 @@ std::string meshTypeLabel(int idx) return "Invalid"; } -std::string clothingTypeLabel(int idx) +std::string_view clothingTypeLabel(int idx) { if (idx >= 0 && idx <= 9) { - static const char *clothingTypeLabels[] = { + static constexpr std::string_view clothingTypeLabels[] = { "Pants", "Shoes", "Shirt", @@ -110,7 +110,7 @@ std::string clothingTypeLabel(int idx) "Left Glove", "Skirt", "Ring", - "Amulet" + "Amulet", }; return clothingTypeLabels[idx]; } @@ -118,11 +118,11 @@ std::string clothingTypeLabel(int idx) return "Invalid"; } -std::string armorTypeLabel(int idx) +std::string_view armorTypeLabel(int idx) { if (idx >= 0 && idx <= 10) { - static const char *armorTypeLabels[] = { + static constexpr std::string_view armorTypeLabels[] = { "Helmet", "Cuirass", "Left Pauldron", @@ -133,7 +133,7 @@ std::string armorTypeLabel(int idx) "Right Gauntlet", "Shield", "Left Bracer", - "Right Bracer" + "Right Bracer", }; return armorTypeLabels[idx]; } @@ -141,16 +141,16 @@ std::string armorTypeLabel(int idx) return "Invalid"; } -std::string dialogTypeLabel(int idx) +std::string_view dialogTypeLabel(int idx) { if (idx >= 0 && idx <= 4) { - static const char *dialogTypeLabels[] = { + static constexpr std::string_view dialogTypeLabels[] = { "Topic", "Voice", "Greeting", "Persuasion", - "Journal" + "Journal", }; return dialogTypeLabels[idx]; } @@ -160,16 +160,16 @@ std::string dialogTypeLabel(int idx) return "Invalid"; } -std::string questStatusLabel(int idx) +std::string_view questStatusLabel(int idx) { if (idx >= 0 && idx <= 4) { - static const char *questStatusLabels[] = { + static constexpr std::string_view questStatusLabels[] = { "None", "Name", "Finished", "Restart", - "Deleted" + "Deleted", }; return questStatusLabels[idx]; } @@ -177,11 +177,11 @@ std::string questStatusLabel(int idx) return "Invalid"; } -std::string creatureTypeLabel(int idx) +std::string_view creatureTypeLabel(int idx) { if (idx >= 0 && idx <= 3) { - static const char *creatureTypeLabels[] = { + static constexpr std::string_view creatureTypeLabels[] = { "Creature", "Daedra", "Undead", @@ -193,11 +193,11 @@ std::string creatureTypeLabel(int idx) return "Invalid"; } -std::string soundTypeLabel(int idx) +std::string_view soundTypeLabel(int idx) { if (idx >= 0 && idx <= 7) { - static const char *soundTypeLabels[] = { + static constexpr std::string_view soundTypeLabels[] = { "Left Foot", "Right Foot", "Swim Left", @@ -205,7 +205,7 @@ std::string soundTypeLabel(int idx) "Moan", "Roar", "Scream", - "Land" + "Land", }; return soundTypeLabels[idx]; } @@ -213,11 +213,11 @@ std::string soundTypeLabel(int idx) return "Invalid"; } -std::string weaponTypeLabel(int idx) +std::string_view weaponTypeLabel(int idx) { if (idx >= 0 && idx <= 13) { - static const char *weaponTypeLabels[] = { + static constexpr std::string_view weaponTypeLabels[] = { "Short Blade One Hand", "Long Blade One Hand", "Long Blade Two Hand", @@ -231,7 +231,7 @@ std::string weaponTypeLabel(int idx) "Marksman Crossbow", "Marksman Thrown", "Arrow", - "Bolt" + "Bolt", }; return weaponTypeLabels[idx]; } @@ -239,21 +239,29 @@ std::string weaponTypeLabel(int idx) return "Invalid"; } -std::string aiTypeLabel(int type) +std::string_view aiTypeLabel(ESM::AiPackageType type) { - if (type == ESM::AI_Wander) return "Wander"; - else if (type == ESM::AI_Travel) return "Travel"; - else if (type == ESM::AI_Follow) return "Follow"; - else if (type == ESM::AI_Escort) return "Escort"; - else if (type == ESM::AI_Activate) return "Activate"; - else return "Invalid"; + switch (type) + { + case ESM::AI_Wander: + return "Wander"; + case ESM::AI_Travel: + return "Travel"; + case ESM::AI_Follow: + return "Follow"; + case ESM::AI_Escort: + return "Escort"; + case ESM::AI_Activate: + return "Activate"; + } + return "Invalid"; } -std::string magicEffectLabel(int idx) +std::string_view magicEffectLabel(int idx) { if (idx >= 0 && idx <= 142) { - const char* magicEffectLabels [] = { + static constexpr std::string_view magicEffectLabels[] = { "Water Breathing", "Swift Swim", "Water Walking", @@ -396,7 +404,7 @@ std::string magicEffectLabel(int idx) "sEffectSummonCreature02", "sEffectSummonCreature03", "sEffectSummonCreature04", - "sEffectSummonCreature05" + "sEffectSummonCreature05", }; return magicEffectLabels[idx]; } @@ -404,11 +412,11 @@ std::string magicEffectLabel(int idx) return "Invalid"; } -std::string attributeLabel(int idx) +std::string_view attributeLabel(int idx) { if (idx >= 0 && idx <= 7) { - const char* attributeLabels [] = { + static constexpr std::string_view attributeLabels[] = { "Strength", "Intelligence", "Willpower", @@ -416,7 +424,7 @@ std::string attributeLabel(int idx) "Speed", "Endurance", "Personality", - "Luck" + "Luck", }; return attributeLabels[idx]; } @@ -424,17 +432,17 @@ std::string attributeLabel(int idx) return "Invalid"; } -std::string spellTypeLabel(int idx) +std::string_view spellTypeLabel(int idx) { if (idx >= 0 && idx <= 5) { - const char* spellTypeLabels [] = { + static constexpr std::string_view spellTypeLabels[] = { "Spells", "Abilities", "Blight Disease", "Disease", "Curse", - "Powers" + "Powers", }; return spellTypeLabels[idx]; } @@ -442,14 +450,14 @@ std::string spellTypeLabel(int idx) return "Invalid"; } -std::string specializationLabel(int idx) +std::string_view specializationLabel(int idx) { if (idx >= 0 && idx <= 2) { - const char* specializationLabels [] = { + static constexpr std::string_view specializationLabels[] = { "Combat", "Magic", - "Stealth" + "Stealth", }; return specializationLabels[idx]; } @@ -457,11 +465,11 @@ std::string specializationLabel(int idx) return "Invalid"; } -std::string skillLabel(int idx) +std::string_view skillLabel(int idx) { if (idx >= 0 && idx <= 26) { - const char* skillLabels [] = { + static constexpr std::string_view skillLabels[] = { "Block", "Armorer", "Medium Armor", @@ -488,7 +496,7 @@ std::string skillLabel(int idx) "Marksman", "Mercantile", "Speechcraft", - "Hand-to-hand" + "Hand-to-hand", }; return skillLabels[idx]; } @@ -496,11 +504,11 @@ std::string skillLabel(int idx) return "Invalid"; } -std::string apparatusTypeLabel(int idx) +std::string_view apparatusTypeLabel(int idx) { if (idx >= 0 && idx <= 3) { - const char* apparatusTypeLabels [] = { + static constexpr std::string_view apparatusTypeLabels[] = { "Mortar", "Alembic", "Calcinator", @@ -512,14 +520,14 @@ std::string apparatusTypeLabel(int idx) return "Invalid"; } -std::string rangeTypeLabel(int idx) +std::string_view rangeTypeLabel(int idx) { if (idx >= 0 && idx <= 2) { - const char* rangeTypeLabels [] = { + static constexpr std::string_view rangeTypeLabels[] = { "Self", "Touch", - "Target" + "Target", }; return rangeTypeLabels[idx]; } @@ -527,17 +535,17 @@ std::string rangeTypeLabel(int idx) return "Invalid"; } -std::string schoolLabel(int idx) +std::string_view schoolLabel(int idx) { if (idx >= 0 && idx <= 5) { - const char* schoolLabels [] = { + static constexpr std::string_view schoolLabels[] = { "Alteration", "Conjuration", "Destruction", "Illusion", "Mysticism", - "Restoration" + "Restoration", }; return schoolLabels[idx]; } @@ -545,15 +553,15 @@ std::string schoolLabel(int idx) return "Invalid"; } -std::string enchantTypeLabel(int idx) +std::string_view enchantTypeLabel(int idx) { if (idx >= 0 && idx <= 3) { - const char* enchantTypeLabels [] = { + static constexpr std::string_view enchantTypeLabels[] = { "Cast Once", "Cast When Strikes", "Cast When Used", - "Constant Effect" + "Constant Effect", }; return enchantTypeLabels[idx]; } @@ -561,11 +569,11 @@ std::string enchantTypeLabel(int idx) return "Invalid"; } -std::string ruleFunction(int idx) +std::string_view ruleFunction(int idx) { if (idx >= 0 && idx <= 72) { - std::string ruleFunctions[] = { + static constexpr std::string_view ruleFunctions[] = { "Reaction Low", "Reaction High", "Rank Requirement", @@ -638,7 +646,7 @@ std::string ruleFunction(int idx) "Alarm", "Flee", "Should Attack", - "Werewolf" + "Werewolf", }; return ruleFunctions[idx]; } @@ -653,13 +661,15 @@ std::string ruleFunction(int idx) std::string bodyPartFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::BodyPart::BPF_Female) properties += "Female "; - if (flags & ESM::BodyPart::BPF_NotPlayable) properties += "NotPlayable "; - int unused = (0xFFFFFFFF ^ - (ESM::BodyPart::BPF_Female| - ESM::BodyPart::BPF_NotPlayable)); - if (flags & unused) properties += "Invalid "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::BodyPart::BPF_Female) + properties += "Female "; + if (flags & ESM::BodyPart::BPF_NotPlayable) + properties += "NotPlayable "; + int unused = (0xFFFFFFFF ^ (ESM::BodyPart::BPF_Female | ESM::BodyPart::BPF_NotPlayable)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -667,20 +677,23 @@ std::string bodyPartFlags(int flags) std::string cellFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::Cell::HasWater) properties += "HasWater "; - if (flags & ESM::Cell::Interior) properties += "Interior "; - if (flags & ESM::Cell::NoSleep) properties += "NoSleep "; - if (flags & ESM::Cell::QuasiEx) properties += "QuasiEx "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Cell::HasWater) + properties += "HasWater "; + if (flags & ESM::Cell::Interior) + properties += "Interior "; + if (flags & ESM::Cell::NoSleep) + properties += "NoSleep "; + if (flags & ESM::Cell::QuasiEx) + properties += "QuasiEx "; // This used value is not in the ESM component. - if (flags & 0x00000040) properties += "Unknown "; - int unused = (0xFFFFFFFF ^ - (ESM::Cell::HasWater| - ESM::Cell::Interior| - ESM::Cell::NoSleep| - ESM::Cell::QuasiEx| - 0x00000040)); - if (flags & unused) properties += "Invalid "; + if (flags & 0x00000040) + properties += "Unknown "; + int unused = (0xFFFFFFFF + ^ (ESM::Cell::HasWater | ESM::Cell::Interior | ESM::Cell::NoSleep | ESM::Cell::QuasiEx | 0x00000040)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -688,15 +701,17 @@ std::string cellFlags(int flags) std::string containerFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::Container::Unknown) properties += "Unknown "; - if (flags & ESM::Container::Organic) properties += "Organic "; - if (flags & ESM::Container::Respawn) properties += "Respawn "; - int unused = (0xFFFFFFFF ^ - (ESM::Container::Unknown| - ESM::Container::Organic| - ESM::Container::Respawn)); - if (flags & unused) properties += "Invalid "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Container::Unknown) + properties += "Unknown "; + if (flags & ESM::Container::Organic) + properties += "Organic "; + if (flags & ESM::Container::Respawn) + properties += "Respawn "; + int unused = (0xFFFFFFFF ^ (ESM::Container::Unknown | ESM::Container::Organic | ESM::Container::Respawn)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -704,25 +719,29 @@ std::string containerFlags(int flags) std::string creatureFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::Creature::Base) properties += "Base "; - if (flags & ESM::Creature::Walks) properties += "Walks "; - if (flags & ESM::Creature::Swims) properties += "Swims "; - if (flags & ESM::Creature::Flies) properties += "Flies "; - if (flags & ESM::Creature::Bipedal) properties += "Bipedal "; - if (flags & ESM::Creature::Respawn) properties += "Respawn "; - if (flags & ESM::Creature::Weapon) properties += "Weapon "; - if (flags & ESM::Creature::Essential) properties += "Essential "; - int unused = (0xFFFFFFFF ^ - (ESM::Creature::Base| - ESM::Creature::Walks| - ESM::Creature::Swims| - ESM::Creature::Flies| - ESM::Creature::Bipedal| - ESM::Creature::Respawn| - ESM::Creature::Weapon| - ESM::Creature::Essential)); - if (flags & unused) properties += "Invalid "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Creature::Base) + properties += "Base "; + if (flags & ESM::Creature::Walks) + properties += "Walks "; + if (flags & ESM::Creature::Swims) + properties += "Swims "; + if (flags & ESM::Creature::Flies) + properties += "Flies "; + if (flags & ESM::Creature::Bipedal) + properties += "Bipedal "; + if (flags & ESM::Creature::Respawn) + properties += "Respawn "; + if (flags & ESM::Creature::Weapon) + properties += "Weapon "; + if (flags & ESM::Creature::Essential) + properties += "Essential "; + int unused = (0xFFFFFFFF + ^ (ESM::Creature::Base | ESM::Creature::Walks | ESM::Creature::Swims | ESM::Creature::Flies + | ESM::Creature::Bipedal | ESM::Creature::Respawn | ESM::Creature::Weapon | ESM::Creature::Essential)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%02X)", flags); return properties; } @@ -730,9 +749,12 @@ std::string creatureFlags(int flags) std::string enchantmentFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::Enchantment::Autocalc) properties += "Autocalc "; - if (flags & (0xFFFFFFFF ^ ESM::Enchantment::Autocalc)) properties += "Invalid "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Enchantment::Autocalc) + properties += "Autocalc "; + if (flags & (0xFFFFFFFF ^ ESM::Enchantment::Autocalc)) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -743,11 +765,16 @@ std::string landFlags(int flags) // The ESM component says that this first four bits are used, but // only the first three bits are used as far as I can tell. // There's also no enumeration of the bit in the ESM component. - if (flags == 0) properties += "[None] "; - if (flags & 0x00000001) properties += "Unknown1 "; - if (flags & 0x00000004) properties += "Unknown3 "; - if (flags & 0x00000002) properties += "Unknown2 "; - if (flags & 0xFFFFFFF8) properties += "Invalid "; + if (flags == 0) + properties += "[None] "; + if (flags & 0x00000001) + properties += "Unknown1 "; + if (flags & 0x00000004) + properties += "Unknown3 "; + if (flags & 0x00000002) + properties += "Unknown2 "; + if (flags & 0xFFFFFFF8) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -755,13 +782,15 @@ std::string landFlags(int flags) std::string itemListFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::ItemLevList::AllLevels) properties += "AllLevels "; - if (flags & ESM::ItemLevList::Each) properties += "Each "; - int unused = (0xFFFFFFFF ^ - (ESM::ItemLevList::AllLevels| - ESM::ItemLevList::Each)); - if (flags & unused) properties += "Invalid "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::ItemLevList::AllLevels) + properties += "AllLevels "; + if (flags & ESM::ItemLevList::Each) + properties += "Each "; + int unused = (0xFFFFFFFF ^ (ESM::ItemLevList::AllLevels | ESM::ItemLevList::Each)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -769,10 +798,13 @@ std::string itemListFlags(int flags) std::string creatureListFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::CreatureLevList::AllLevels) properties += "AllLevels "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::CreatureLevList::AllLevels) + properties += "AllLevels "; int unused = (0xFFFFFFFF ^ ESM::CreatureLevList::AllLevels); - if (flags & unused) properties += "Invalid "; + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -780,27 +812,31 @@ std::string creatureListFlags(int flags) std::string lightFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::Light::Dynamic) properties += "Dynamic "; - if (flags & ESM::Light::Fire) properties += "Fire "; - if (flags & ESM::Light::Carry) properties += "Carry "; - if (flags & ESM::Light::Flicker) properties += "Flicker "; - if (flags & ESM::Light::FlickerSlow) properties += "FlickerSlow "; - if (flags & ESM::Light::Pulse) properties += "Pulse "; - if (flags & ESM::Light::PulseSlow) properties += "PulseSlow "; - if (flags & ESM::Light::Negative) properties += "Negative "; - if (flags & ESM::Light::OffDefault) properties += "OffDefault "; - int unused = (0xFFFFFFFF ^ - (ESM::Light::Dynamic| - ESM::Light::Fire| - ESM::Light::Carry| - ESM::Light::Flicker| - ESM::Light::FlickerSlow| - ESM::Light::Pulse| - ESM::Light::PulseSlow| - ESM::Light::Negative| - ESM::Light::OffDefault)); - if (flags & unused) properties += "Invalid "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Light::Dynamic) + properties += "Dynamic "; + if (flags & ESM::Light::Fire) + properties += "Fire "; + if (flags & ESM::Light::Carry) + properties += "Carry "; + if (flags & ESM::Light::Flicker) + properties += "Flicker "; + if (flags & ESM::Light::FlickerSlow) + properties += "FlickerSlow "; + if (flags & ESM::Light::Pulse) + properties += "Pulse "; + if (flags & ESM::Light::PulseSlow) + properties += "PulseSlow "; + if (flags & ESM::Light::Negative) + properties += "Negative "; + if (flags & ESM::Light::OffDefault) + properties += "OffDefault "; + int unused = (0xFFFFFFFF + ^ (ESM::Light::Dynamic | ESM::Light::Fire | ESM::Light::Carry | ESM::Light::Flicker | ESM::Light::FlickerSlow + | ESM::Light::Pulse | ESM::Light::PulseSlow | ESM::Light::Negative | ESM::Light::OffDefault)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -808,27 +844,47 @@ std::string lightFlags(int flags) std::string magicEffectFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::MagicEffect::TargetAttribute) properties += "TargetAttribute "; - if (flags & ESM::MagicEffect::TargetSkill) properties += "TargetSkill "; - if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration "; - if (flags & ESM::MagicEffect::NoMagnitude) properties += "NoMagnitude "; - if (flags & ESM::MagicEffect::Harmful) properties += "Harmful "; - if (flags & ESM::MagicEffect::ContinuousVfx) properties += "ContinuousVFX "; - if (flags & ESM::MagicEffect::CastSelf) properties += "CastSelf "; - if (flags & ESM::MagicEffect::CastTouch) properties += "CastTouch "; - if (flags & ESM::MagicEffect::CastTarget) properties += "CastTarget "; - if (flags & ESM::MagicEffect::AppliedOnce) properties += "AppliedOnce "; - if (flags & ESM::MagicEffect::Stealth) properties += "Stealth "; - if (flags & ESM::MagicEffect::NonRecastable) properties += "NonRecastable "; - if (flags & ESM::MagicEffect::IllegalDaedra) properties += "IllegalDaedra "; - if (flags & ESM::MagicEffect::Unreflectable) properties += "Unreflectable "; - if (flags & ESM::MagicEffect::CasterLinked) properties += "CasterLinked "; - if (flags & ESM::MagicEffect::AllowSpellmaking) properties += "AllowSpellmaking "; - if (flags & ESM::MagicEffect::AllowEnchanting) properties += "AllowEnchanting "; - if (flags & ESM::MagicEffect::NegativeLight) properties += "NegativeLight "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::MagicEffect::TargetAttribute) + properties += "TargetAttribute "; + if (flags & ESM::MagicEffect::TargetSkill) + properties += "TargetSkill "; + if (flags & ESM::MagicEffect::NoDuration) + properties += "NoDuration "; + if (flags & ESM::MagicEffect::NoMagnitude) + properties += "NoMagnitude "; + if (flags & ESM::MagicEffect::Harmful) + properties += "Harmful "; + if (flags & ESM::MagicEffect::ContinuousVfx) + properties += "ContinuousVFX "; + if (flags & ESM::MagicEffect::CastSelf) + properties += "CastSelf "; + if (flags & ESM::MagicEffect::CastTouch) + properties += "CastTouch "; + if (flags & ESM::MagicEffect::CastTarget) + properties += "CastTarget "; + if (flags & ESM::MagicEffect::AppliedOnce) + properties += "AppliedOnce "; + if (flags & ESM::MagicEffect::Stealth) + properties += "Stealth "; + if (flags & ESM::MagicEffect::NonRecastable) + properties += "NonRecastable "; + if (flags & ESM::MagicEffect::IllegalDaedra) + properties += "IllegalDaedra "; + if (flags & ESM::MagicEffect::Unreflectable) + properties += "Unreflectable "; + if (flags & ESM::MagicEffect::CasterLinked) + properties += "CasterLinked "; + if (flags & ESM::MagicEffect::AllowSpellmaking) + properties += "AllowSpellmaking "; + if (flags & ESM::MagicEffect::AllowEnchanting) + properties += "AllowEnchanting "; + if (flags & ESM::MagicEffect::NegativeLight) + properties += "NegativeLight "; - if (flags & 0xFFFC0000) properties += "Invalid "; + if (flags & 0xFFFC0000) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -836,21 +892,24 @@ std::string magicEffectFlags(int flags) std::string npcFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::NPC::Base) properties += "Base "; - if (flags & ESM::NPC::Autocalc) properties += "Autocalc "; - if (flags & ESM::NPC::Female) properties += "Female "; - if (flags & ESM::NPC::Respawn) properties += "Respawn "; - if (flags & ESM::NPC::Essential) properties += "Essential "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::NPC::Base) + properties += "Base "; + if (flags & ESM::NPC::Autocalc) + properties += "Autocalc "; + if (flags & ESM::NPC::Female) + properties += "Female "; + if (flags & ESM::NPC::Respawn) + properties += "Respawn "; + if (flags & ESM::NPC::Essential) + properties += "Essential "; // Whether corpses persist is a bit that is unaccounted for, // however relatively few NPCs have this bit set. - int unused = (0xFF ^ - (ESM::NPC::Base| - ESM::NPC::Autocalc| - ESM::NPC::Female| - ESM::NPC::Respawn| - ESM::NPC::Essential)); - if (flags & unused) properties += "Invalid "; + int unused + = (0xFF ^ (ESM::NPC::Base | ESM::NPC::Autocalc | ESM::NPC::Female | ESM::NPC::Respawn | ESM::NPC::Essential)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%02X)", flags); return properties; } @@ -858,14 +917,16 @@ std::string npcFlags(int flags) std::string raceFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; + if (flags == 0) + properties += "[None] "; // All races have the playable flag set in Bethesda files. - if (flags & ESM::Race::Playable) properties += "Playable "; - if (flags & ESM::Race::Beast) properties += "Beast "; - int unused = (0xFFFFFFFF ^ - (ESM::Race::Playable| - ESM::Race::Beast)); - if (flags & unused) properties += "Invalid "; + if (flags & ESM::Race::Playable) + properties += "Playable "; + if (flags & ESM::Race::Beast) + properties += "Beast "; + int unused = (0xFFFFFFFF ^ (ESM::Race::Playable | ESM::Race::Beast)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -873,15 +934,17 @@ std::string raceFlags(int flags) std::string spellFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; - if (flags & ESM::Spell::F_Autocalc) properties += "Autocalc "; - if (flags & ESM::Spell::F_PCStart) properties += "PCStart "; - if (flags & ESM::Spell::F_Always) properties += "Always "; - int unused = (0xFFFFFFFF ^ - (ESM::Spell::F_Autocalc| - ESM::Spell::F_PCStart| - ESM::Spell::F_Always)); - if (flags & unused) properties += "Invalid "; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Spell::F_Autocalc) + properties += "Autocalc "; + if (flags & ESM::Spell::F_PCStart) + properties += "PCStart "; + if (flags & ESM::Spell::F_Always) + properties += "Always "; + int unused = (0xFFFFFFFF ^ (ESM::Spell::F_Autocalc | ESM::Spell::F_PCStart | ESM::Spell::F_Always)); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -889,16 +952,38 @@ std::string spellFlags(int flags) std::string weaponFlags(int flags) { std::string properties; - if (flags == 0) properties += "[None] "; + if (flags == 0) + properties += "[None] "; // The interpretation of the flags are still unclear to me. // Apparently you can't be Silver without being Magical? Many of // the "Magical" weapons don't have enchantments of any sort. - if (flags & ESM::Weapon::Magical) properties += "Magical "; - if (flags & ESM::Weapon::Silver) properties += "Silver "; - int unused = (0xFFFFFFFF ^ - (ESM::Weapon::Magical| - ESM::Weapon::Silver)); - if (flags & unused) properties += "Invalid "; + if (flags & ESM::Weapon::Magical) + properties += "Magical "; + if (flags & ESM::Weapon::Silver) + properties += "Silver "; + int unused = (0xFFFFFFFF ^ (ESM::Weapon::Magical | ESM::Weapon::Silver)); + if (flags & unused) + properties += "Invalid "; + properties += Misc::StringUtils::format("(0x%08X)", flags); + return properties; +} + +std::string recordFlags(uint32_t flags) +{ + std::string properties; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::FLAG_Deleted) + properties += "Deleted "; + if (flags & ESM::FLAG_Persistent) + properties += "Persistent "; + if (flags & ESM::FLAG_Ignored) + properties += "Ignored "; + if (flags & ESM::FLAG_Blocked) + properties += "Blocked "; + int unused = ~(ESM::FLAG_Deleted | ESM::FLAG_Persistent | ESM::FLAG_Ignored | ESM::FLAG_Blocked); + if (flags & unused) + properties += "Invalid "; properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp index b06480a97..2baa8a3fd 100644 --- a/apps/esmtool/labels.hpp +++ b/apps/esmtool/labels.hpp @@ -2,26 +2,29 @@ #define OPENMW_ESMTOOL_LABELS_H #include +#include -std::string bodyPartLabel(int idx); -std::string meshPartLabel(int idx); -std::string meshTypeLabel(int idx); -std::string clothingTypeLabel(int idx); -std::string armorTypeLabel(int idx); -std::string dialogTypeLabel(int idx); -std::string questStatusLabel(int idx); -std::string creatureTypeLabel(int idx); -std::string soundTypeLabel(int idx); -std::string weaponTypeLabel(int idx); +#include + +std::string_view bodyPartLabel(int idx); +std::string_view meshPartLabel(int idx); +std::string_view meshTypeLabel(int idx); +std::string_view clothingTypeLabel(int idx); +std::string_view armorTypeLabel(int idx); +std::string_view dialogTypeLabel(int idx); +std::string_view questStatusLabel(int idx); +std::string_view creatureTypeLabel(int idx); +std::string_view soundTypeLabel(int idx); +std::string_view weaponTypeLabel(int idx); // This function's a bit different because the types are record types, // not consecutive values. -std::string aiTypeLabel(int type); +std::string_view aiTypeLabel(ESM::AiPackageType type); // This one's also a bit different, because it enumerates dialog // select rule functions, not types. Structurally, it still converts // indexes to strings for display. -std::string ruleFunction(int idx); +std::string_view ruleFunction(int idx); // The labels below here can all be loaded from GMSTs, but are not // currently because among other things, that requires loading the @@ -32,15 +35,15 @@ std::string ruleFunction(int idx); // and the indexes for referencing the types in other records in the // database. Then a single label function could work for all types. -std::string magicEffectLabel(int idx); -std::string attributeLabel(int idx); -std::string spellTypeLabel(int idx); -std::string specializationLabel(int idx); -std::string skillLabel(int idx); -std::string apparatusTypeLabel(int idx); -std::string rangeTypeLabel(int idx); -std::string schoolLabel(int idx); -std::string enchantTypeLabel(int idx); +std::string_view magicEffectLabel(int idx); +std::string_view attributeLabel(int idx); +std::string_view spellTypeLabel(int idx); +std::string_view specializationLabel(int idx); +std::string_view skillLabel(int idx); +std::string_view apparatusTypeLabel(int idx); +std::string_view rangeTypeLabel(int idx); +std::string_view schoolLabel(int idx); +std::string_view enchantTypeLabel(int idx); // The are the flag functions that convert a bitmask into a list of // human readble strings representing the set bits. @@ -60,6 +63,8 @@ std::string raceFlags(int flags); std::string spellFlags(int flags); std::string weaponFlags(int flags); +std::string recordFlags(uint32_t flags); + // Missing flags functions: // aiServicesFlags, possibly more diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 55170e232..8227fcfcf 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -4,1353 +4,1417 @@ #include #include -#include +#include +#include +#include +#include namespace { -void printAIPackage(const ESM::AIPackage& p) -{ - std::cout << " AI Type: " << aiTypeLabel(p.mType) - << " (" << Misc::StringUtils::format("0x%08X", p.mType) << ")" << std::endl; - if (p.mType == ESM::AI_Wander) + void printAIPackage(const ESM::AIPackage& p) { - std::cout << " Distance: " << p.mWander.mDistance << std::endl; - std::cout << " Duration: " << p.mWander.mDuration << std::endl; - std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl; - if (p.mWander.mShouldRepeat != 1) - std::cout << " Should repeat: " << (bool)(p.mWander.mShouldRepeat != 0) << std::endl; - - std::cout << " Idle: "; - for (int i = 0; i != 8; i++) - std::cout << (int)p.mWander.mIdle[i] << " "; - std::cout << std::endl; - } - else if (p.mType == ESM::AI_Travel) - { - std::cout << " Travel Coordinates: (" << p.mTravel.mX << "," - << p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl; - std::cout << " Travel Unknown: " << p.mTravel.mUnk << std::endl; - } - else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort) - { - std::cout << " Follow Coordinates: (" << p.mTarget.mX << "," - << p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl; - std::cout << " Duration: " << p.mTarget.mDuration << std::endl; - std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl; - std::cout << " Unknown: " << p.mTarget.mUnk << std::endl; - } - else if (p.mType == ESM::AI_Activate) - { - std::cout << " Name: " << p.mActivate.mName.toString() << std::endl; - std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl; - } - else { - std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl; - } - - if (!p.mCellName.empty()) - std::cout << " Cell Name: " << p.mCellName << std::endl; -} - -std::string ruleString(const ESM::DialInfo::SelectStruct& ss) -{ - std::string rule = ss.mSelectRule; - - if (rule.length() < 5) - return "INVALID"; - - char type = rule[1]; - char indicator = rule[2]; - - std::string type_str = "INVALID"; - std::string func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1,3)); - int func; - std::istringstream iss(rule.substr(2,2)); - iss >> func; - - switch(type) - { - case '1': - type_str = "Function"; - func_str = ruleFunction(func); - break; - case '2': - if (indicator == 's') type_str = "Global short"; - else if (indicator == 'l') type_str = "Global long"; - else if (indicator == 'f') type_str = "Global float"; - break; - case '3': - if (indicator == 's') type_str = "Local short"; - else if (indicator == 'l') type_str = "Local long"; - else if (indicator == 'f') type_str = "Local float"; - break; - case '4': if (indicator == 'J') type_str = "Journal"; break; - case '5': if (indicator == 'I') type_str = "Item type"; break; - case '6': if (indicator == 'D') type_str = "NPC Dead"; break; - case '7': if (indicator == 'X') type_str = "Not ID"; break; - case '8': if (indicator == 'F') type_str = "Not Faction"; break; - case '9': if (indicator == 'C') type_str = "Not Class"; break; - case 'A': if (indicator == 'R') type_str = "Not Race"; break; - case 'B': if (indicator == 'L') type_str = "Not Cell"; break; - case 'C': if (indicator == 's') type_str = "Not Local"; break; - default: break; - } - - // Append the variable name to the function string if any. - if (type != '1') func_str = rule.substr(5); - - // In the previous switch, we assumed that the second char was X - // for all types not qual to one. If this wasn't true, go back to - // the error message. - if (type != '1' && rule[3] != 'X') - func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1,3)); - - char oper = rule[4]; - std::string oper_str = "??"; - switch (oper) - { - case '0': oper_str = "=="; break; - case '1': oper_str = "!="; break; - case '2': oper_str = "> "; break; - case '3': oper_str = ">="; break; - case '4': oper_str = "< "; break; - case '5': oper_str = "<="; break; - default: break; - } - - std::ostringstream stream; - stream << ss.mValue; - - std::string result = Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); - return result; -} - -void printEffectList(const ESM::EffectList& effects) -{ - int i = 0; - for (const ESM::ENAMstruct& effect : effects.mList) - { - std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mEffectID) - << " (" << effect.mEffectID << ")" << std::endl; - if (effect.mSkill != -1) - std::cout << " Skill: " << skillLabel(effect.mSkill) - << " (" << (int)effect.mSkill << ")" << std::endl; - if (effect.mAttribute != -1) - std::cout << " Attribute: " << attributeLabel(effect.mAttribute) - << " (" << (int)effect.mAttribute << ")" << std::endl; - std::cout << " Range: " << rangeTypeLabel(effect.mRange) - << " (" << effect.mRange << ")" << std::endl; - // Area is always zero if range type is "Self" - if (effect.mRange != ESM::RT_Self) - std::cout << " Area: " << effect.mArea << std::endl; - std::cout << " Duration: " << effect.mDuration << std::endl; - std::cout << " Magnitude: " << effect.mMagnMin << "-" << effect.mMagnMax << std::endl; - i++; - } -} - -void printTransport(const std::vector& transport) -{ - for (const ESM::Transport::Dest& dest : transport) - { - std::cout << " Destination Position: " - << Misc::StringUtils::format("%12.3f", dest.mPos.pos[0]) << "," - << Misc::StringUtils::format("%12.3f", dest.mPos.pos[1]) << "," - << Misc::StringUtils::format("%12.3f", dest.mPos.pos[2]) << ")" << std::endl; - std::cout << " Destination Rotation: " - << Misc::StringUtils::format("%9.6f", dest.mPos.rot[0]) << "," - << Misc::StringUtils::format("%9.6f", dest.mPos.rot[1]) << "," - << Misc::StringUtils::format("%9.6f", dest.mPos.rot[2]) << ")" << std::endl; - if (!dest.mCellName.empty()) - std::cout << " Destination Cell: " << dest.mCellName << std::endl; - } -} - -} - -namespace EsmTool { - -RecordBase * -RecordBase::create(const ESM::NAME type) -{ - RecordBase *record = nullptr; - - switch (type.intval) { - case ESM::REC_ACTI: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_ALCH: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_APPA: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_ARMO: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_BODY: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_BOOK: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_BSGN: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_CELL: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_CLAS: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_CLOT: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_CONT: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_CREA: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_DIAL: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_DOOR: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_ENCH: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_FACT: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_GLOB: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_GMST: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_INFO: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_INGR: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_LAND: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_LEVI: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_LEVC: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_LIGH: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_LOCK: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_LTEX: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_MISC: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_MGEF: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_NPC_: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_PGRD: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_PROB: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_RACE: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_REGN: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_REPA: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_SCPT: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_SKIL: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_SNDG: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_SOUN: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_SPEL: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_STAT: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_WEAP: - { - record = new EsmTool::Record; - break; - } - case ESM::REC_SSCR: - { - record = new EsmTool::Record; - break; - } - default: - record = nullptr; - } - if (record) { - record->mType = type; - } - return record; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; - printEffectList(mData.mEffects); - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - if (!mData.mEnchant.empty()) - std::cout << " Enchantment: " << mData.mEnchant << std::endl; - std::cout << " Type: " << armorTypeLabel(mData.mData.mType) - << " (" << mData.mData.mType << ")" << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Health: " << mData.mData.mHealth << std::endl; - std::cout << " Armor: " << mData.mData.mArmor << std::endl; - std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; - for (const ESM::PartReference &part : mData.mParts.mParts) - { - std::cout << " Body Part: " << bodyPartLabel(part.mPart) - << " (" << (int)(part.mPart) << ")" << std::endl; - std::cout << " Male Name: " << part.mMale << std::endl; - if (!part.mFemale.empty()) - std::cout << " Female Name: " << part.mFemale << std::endl; - } - - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) - << " (" << mData.mData.mType << ")" << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Quality: " << mData.mData.mQuality << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Race: " << mData.mRace << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Type: " << meshTypeLabel(mData.mData.mType) - << " (" << (int)mData.mData.mType << ")" << std::endl; - std::cout << " Flags: " << bodyPartFlags(mData.mData.mFlags) << std::endl; - std::cout << " Part: " << meshPartLabel(mData.mData.mPart) - << " (" << (int)mData.mData.mPart << ")" << std::endl; - std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - if (!mData.mEnchant.empty()) - std::cout << " Enchantment: " << mData.mEnchant << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " IsScroll: " << mData.mData.mIsScroll << std::endl; - std::cout << " SkillId: " << mData.mData.mSkillId << std::endl; - std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; - if (mPrintPlain) - { - std::cout << " Text:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mText << std::endl; - std::cout << "END----------------------------------------" << std::endl; - } - else - { - std::cout << " Text: [skipped]" << std::endl; - } - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Texture: " << mData.mTexture << std::endl; - std::cout << " Description: " << mData.mDescription << std::endl; - for (const std::string &power : mData.mPowers.mList) - std::cout << " Power: " << power << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - // None of the cells have names... - if (!mData.mName.empty()) - std::cout << " Name: " << mData.mName << std::endl; - if (!mData.mRegion.empty()) - std::cout << " Region: " << mData.mRegion << std::endl; - std::cout << " Flags: " << cellFlags(mData.mData.mFlags) << std::endl; - - std::cout << " Coordinates: " << " (" << mData.getGridX() << "," - << mData.getGridY() << ")" << std::endl; - - if (mData.mData.mFlags & ESM::Cell::Interior && - !(mData.mData.mFlags & ESM::Cell::QuasiEx)) - { - if (mData.hasAmbient()) + std::cout << " AI Type: " << aiTypeLabel(p.mType) << " (" << Misc::StringUtils::format("0x%08X", p.mType) + << ")" << std::endl; + if (p.mType == ESM::AI_Wander) { - // TODO: see if we can change the integer representation to something more sensible - std::cout << " Ambient Light Color: " << mData.mAmbi.mAmbient << std::endl; - std::cout << " Sunlight Color: " << mData.mAmbi.mSunlight << std::endl; - std::cout << " Fog Color: " << mData.mAmbi.mFog << std::endl; - std::cout << " Fog Density: " << mData.mAmbi.mFogDensity << std::endl; + std::cout << " Distance: " << p.mWander.mDistance << std::endl; + std::cout << " Duration: " << p.mWander.mDuration << std::endl; + std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl; + if (p.mWander.mShouldRepeat != 1) + std::cout << " Should repeat: " << static_cast(p.mWander.mShouldRepeat != 0) << std::endl; + + std::cout << " Idle: "; + for (int i = 0; i != 8; i++) + std::cout << (int)p.mWander.mIdle[i] << " "; + std::cout << std::endl; + } + else if (p.mType == ESM::AI_Travel) + { + std::cout << " Travel Coordinates: (" << p.mTravel.mX << "," << p.mTravel.mY << "," << p.mTravel.mZ + << ")" << std::endl; + std::cout << " Should repeat: " << static_cast(p.mTravel.mShouldRepeat != 0) << std::endl; + } + else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort) + { + std::cout << " Follow Coordinates: (" << p.mTarget.mX << "," << p.mTarget.mY << "," << p.mTarget.mZ + << ")" << std::endl; + std::cout << " Duration: " << p.mTarget.mDuration << std::endl; + std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl; + std::cout << " Should repeat: " << static_cast(p.mTarget.mShouldRepeat != 0) << std::endl; + } + else if (p.mType == ESM::AI_Activate) + { + std::cout << " Name: " << p.mActivate.mName.toString() << std::endl; + std::cout << " Should repeat: " << static_cast(p.mActivate.mShouldRepeat != 0) << std::endl; } else { - std::cout << " No Ambient Information" << std::endl; + std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl; } - std::cout << " Water Level: " << mData.mWater << std::endl; + + if (!p.mCellName.empty()) + std::cout << " Cell Name: " << p.mCellName << std::endl; } - else - std::cout << " Map Color: " << Misc::StringUtils::format("0x%08X", mData.mMapColor) << std::endl; - std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; - std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Description: " << mData.mDescription << std::endl; - std::cout << " Playable: " << mData.mData.mIsPlayable << std::endl; - std::cout << " AutoCalc: " << mData.mData.mCalc << std::endl; - std::cout << " Attribute1: " << attributeLabel(mData.mData.mAttribute[0]) - << " (" << mData.mData.mAttribute[0] << ")" << std::endl; - std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1]) - << " (" << mData.mData.mAttribute[1] << ")" << std::endl; - std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization) - << " (" << mData.mData.mSpecialization << ")" << std::endl; - for (int i = 0; i != 5; i++) - std::cout << " Minor Skill: " << skillLabel(mData.mData.mSkills[i][0]) - << " (" << mData.mData.mSkills[i][0] << ")" << std::endl; - for (int i = 0; i != 5; i++) - std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) - << " (" << mData.mData.mSkills[i][1] << ")" << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - if (!mData.mEnchant.empty()) - std::cout << " Enchantment: " << mData.mEnchant << std::endl; - std::cout << " Type: " << clothingTypeLabel(mData.mData.mType) - << " (" << mData.mData.mType << ")" << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; - for (const ESM::PartReference &part : mData.mParts.mParts) + std::string ruleString(const ESM::DialInfo::SelectStruct& ss) { - std::cout << " Body Part: " << bodyPartLabel(part.mPart) - << " (" << (int)(part.mPart) << ")" << std::endl; - std::cout << " Male Name: " << part.mMale << std::endl; - if (!part.mFemale.empty()) - std::cout << " Female Name: " << part.mFemale << std::endl; - } - std::cout << " Deleted: " << mIsDeleted << std::endl; -} + std::string rule = ss.mSelectRule; -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Flags: " << containerFlags(mData.mFlags) << std::endl; - std::cout << " Weight: " << mData.mWeight << std::endl; - for (const ESM::ContItem &item : mData.mInventory.mList) - std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) - << " Item: " << item.mItem << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} + if (rule.length() < 5) + return "INVALID"; -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl; - std::cout << " Blood Type: " << mData.mBloodType+1 << std::endl; - std::cout << " Original: " << mData.mOriginal << std::endl; - std::cout << " Scale: " << mData.mScale << std::endl; + char type = rule[1]; + char indicator = rule[2]; - std::cout << " Type: " << creatureTypeLabel(mData.mData.mType) - << " (" << mData.mData.mType << ")" << std::endl; - std::cout << " Level: " << mData.mData.mLevel << std::endl; + std::string type_str = "INVALID"; + std::string func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3)); + int func = Misc::StringUtils::toNumeric(rule.substr(2, 2), 0); - std::cout << " Attributes:" << std::endl; - std::cout << " Strength: " << mData.mData.mStrength << std::endl; - std::cout << " Intelligence: " << mData.mData.mIntelligence << std::endl; - std::cout << " Willpower: " << mData.mData.mWillpower << std::endl; - std::cout << " Agility: " << mData.mData.mAgility << std::endl; - std::cout << " Speed: " << mData.mData.mSpeed << std::endl; - std::cout << " Endurance: " << mData.mData.mEndurance << std::endl; - std::cout << " Personality: " << mData.mData.mPersonality << std::endl; - std::cout << " Luck: " << mData.mData.mLuck << std::endl; - - std::cout << " Health: " << mData.mData.mHealth << std::endl; - std::cout << " Magicka: " << mData.mData.mMana << std::endl; - std::cout << " Fatigue: " << mData.mData.mFatigue << std::endl; - std::cout << " Soul: " << mData.mData.mSoul << std::endl; - std::cout << " Combat: " << mData.mData.mCombat << std::endl; - std::cout << " Magic: " << mData.mData.mMagic << std::endl; - std::cout << " Stealth: " << mData.mData.mStealth << std::endl; - std::cout << " Attack1: " << mData.mData.mAttack[0] - << "-" << mData.mData.mAttack[1] << std::endl; - std::cout << " Attack2: " << mData.mData.mAttack[2] - << "-" << mData.mData.mAttack[3] << std::endl; - std::cout << " Attack3: " << mData.mData.mAttack[4] - << "-" << mData.mData.mAttack[5] << std::endl; - std::cout << " Gold: " << mData.mData.mGold << std::endl; - - for (const ESM::ContItem &item : mData.mInventory.mList) - std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) - << " Item: " << item.mItem << std::endl; - - for (const std::string &spell : mData.mSpells.mList) - std::cout << " Spell: " << spell << std::endl; - - printTransport(mData.getTransport()); - - std::cout << " Artificial Intelligence: " << std::endl; - std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl; - std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; - std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; - std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; - std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; - std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; - std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; - std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; - - for (const ESM::AIPackage &package : mData.mAiPackage.mList) - printAIPackage(package); - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Type: " << dialogTypeLabel(mData.mType) - << " (" << (int)mData.mType << ")" << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; - // Sadly, there are no DialInfos, because the loader dumps as it - // loads, rather than loading and then dumping. :-( Anyone mind if - // I change this? - for (const ESM::DialInfo &info : mData.mInfo) - std::cout << "INFO!" << info.mId << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " OpenSound: " << mData.mOpenSound << std::endl; - std::cout << " CloseSound: " << mData.mCloseSound << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Type: " << enchantTypeLabel(mData.mData.mType) - << " (" << mData.mData.mType << ")" << std::endl; - std::cout << " Cost: " << mData.mData.mCost << std::endl; - std::cout << " Charge: " << mData.mData.mCharge << std::endl; - std::cout << " Flags: " << enchantmentFlags(mData.mData.mFlags) << std::endl; - printEffectList(mData.mEffects); - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Hidden: " << mData.mData.mIsHidden << std::endl; - std::cout << " Attribute1: " << attributeLabel(mData.mData.mAttribute[0]) - << " (" << mData.mData.mAttribute[0] << ")" << std::endl; - std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1]) - << " (" << mData.mData.mAttribute[1] << ")" << std::endl; - for (int skill : mData.mData.mSkills) - if (skill != -1) - std::cout << " Skill: " << skillLabel(skill) << " (" << skill << ")" << std::endl; - for (int i = 0; i != 10; i++) - if (!mData.mRanks[i].empty()) + switch (type) { - std::cout << " Rank: " << mData.mRanks[i] << std::endl; - std::cout << " Attribute1 Requirement: " - << mData.mData.mRankData[i].mAttribute1 << std::endl; - std::cout << " Attribute2 Requirement: " - << mData.mData.mRankData[i].mAttribute2 << std::endl; - std::cout << " One Skill at Level: " - << mData.mData.mRankData[i].mPrimarySkill << std::endl; - std::cout << " Two Skills at Level: " - << mData.mData.mRankData[i].mFavouredSkill << std::endl; - std::cout << " Faction Reaction: " - << mData.mData.mRankData[i].mFactReaction << std::endl; + case '1': + type_str = "Function"; + func_str = std::string(ruleFunction(func)); + break; + case '2': + if (indicator == 's') + type_str = "Global short"; + else if (indicator == 'l') + type_str = "Global long"; + else if (indicator == 'f') + type_str = "Global float"; + break; + case '3': + if (indicator == 's') + type_str = "Local short"; + else if (indicator == 'l') + type_str = "Local long"; + else if (indicator == 'f') + type_str = "Local float"; + break; + case '4': + if (indicator == 'J') + type_str = "Journal"; + break; + case '5': + if (indicator == 'I') + type_str = "Item type"; + break; + case '6': + if (indicator == 'D') + type_str = "NPC Dead"; + break; + case '7': + if (indicator == 'X') + type_str = "Not ID"; + break; + case '8': + if (indicator == 'F') + type_str = "Not Faction"; + break; + case '9': + if (indicator == 'C') + type_str = "Not Class"; + break; + case 'A': + if (indicator == 'R') + type_str = "Not Race"; + break; + case 'B': + if (indicator == 'L') + type_str = "Not Cell"; + break; + case 'C': + if (indicator == 's') + type_str = "Not Local"; + break; + default: + break; } - for (const auto &reaction : mData.mReactions) - std::cout << " Reaction: " << reaction.second << " = " << reaction.first << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} -template<> -void Record::print() -{ - std::cout << " " << mData.mValue << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} + // Append the variable name to the function string if any. + if (type != '1') + func_str = rule.substr(5); -template<> -void Record::print() -{ - std::cout << " " << mData.mValue << std::endl; -} + // In the previous switch, we assumed that the second char was X + // for all types not qual to one. If this wasn't true, go back to + // the error message. + if (type != '1' && rule[3] != 'X') + func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3)); -template<> -void Record::print() -{ - std::cout << " Id: " << mData.mId << std::endl; - if (!mData.mPrev.empty()) - std::cout << " Previous ID: " << mData.mPrev << std::endl; - if (!mData.mNext.empty()) - std::cout << " Next ID: " << mData.mNext << std::endl; - std::cout << " Text: " << mData.mResponse << std::endl; - if (!mData.mActor.empty()) - std::cout << " Actor: " << mData.mActor << std::endl; - if (!mData.mRace.empty()) - std::cout << " Race: " << mData.mRace << std::endl; - if (!mData.mClass.empty()) - std::cout << " Class: " << mData.mClass << std::endl; - std::cout << " Factionless: " << mData.mFactionLess << std::endl; - if (!mData.mFaction.empty()) - std::cout << " NPC Faction: " << mData.mFaction << std::endl; - if (mData.mData.mRank != -1) - std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl; - if (!mData.mPcFaction.empty()) - std::cout << " PC Faction: " << mData.mPcFaction << std::endl; - // CHANGE? non-standard capitalization mPCrank -> mPCRank (mPcRank?) - if (mData.mData.mPCrank != -1) - std::cout << " PC Rank: " << (int)mData.mData.mPCrank << std::endl; - if (!mData.mCell.empty()) - std::cout << " Cell: " << mData.mCell << std::endl; - if (mData.mData.mDisposition > 0) - std::cout << " Disposition/Journal index: " << mData.mData.mDisposition << std::endl; - if (mData.mData.mGender != ESM::DialInfo::NA) - std::cout << " Gender: " << mData.mData.mGender << std::endl; - if (!mData.mSound.empty()) - std::cout << " Sound File: " << mData.mSound << std::endl; + char oper = rule[4]; + std::string oper_str = "??"; + switch (oper) + { + case '0': + oper_str = "=="; + break; + case '1': + oper_str = "!="; + break; + case '2': + oper_str = "> "; + break; + case '3': + oper_str = ">="; + break; + case '4': + oper_str = "< "; + break; + case '5': + oper_str = "<="; + break; + default: + break; + } + std::ostringstream stream; + stream << ss.mValue; - std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus) - << " (" << mData.mQuestStatus << ")" << std::endl; - std::cout << " Unknown1: " << mData.mData.mUnknown1 << std::endl; - std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl; + std::string result + = Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); + return result; + } - for (const ESM::DialInfo::SelectStruct &rule : mData.mSelects) - std::cout << " Select Rule: " << ruleString(rule) << std::endl; - - if (!mData.mResultScript.empty()) + void printEffectList(const ESM::EffectList& effects) { + int i = 0; + for (const ESM::ENAMstruct& effect : effects.mList) + { + std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mEffectID) << " (" << effect.mEffectID + << ")" << std::endl; + if (effect.mSkill != -1) + std::cout << " Skill: " << skillLabel(effect.mSkill) << " (" << (int)effect.mSkill << ")" + << std::endl; + if (effect.mAttribute != -1) + std::cout << " Attribute: " << attributeLabel(effect.mAttribute) << " (" << (int)effect.mAttribute + << ")" << std::endl; + std::cout << " Range: " << rangeTypeLabel(effect.mRange) << " (" << effect.mRange << ")" << std::endl; + // Area is always zero if range type is "Self" + if (effect.mRange != ESM::RT_Self) + std::cout << " Area: " << effect.mArea << std::endl; + std::cout << " Duration: " << effect.mDuration << std::endl; + std::cout << " Magnitude: " << effect.mMagnMin << "-" << effect.mMagnMax << std::endl; + i++; + } + } + + void printTransport(const std::vector& transport) + { + for (const ESM::Transport::Dest& dest : transport) + { + std::cout << " Destination Position: " << Misc::StringUtils::format("%12.3f", dest.mPos.pos[0]) << "," + << Misc::StringUtils::format("%12.3f", dest.mPos.pos[1]) << "," + << Misc::StringUtils::format("%12.3f", dest.mPos.pos[2]) << ")" << std::endl; + std::cout << " Destination Rotation: " << Misc::StringUtils::format("%9.6f", dest.mPos.rot[0]) << "," + << Misc::StringUtils::format("%9.6f", dest.mPos.rot[1]) << "," + << Misc::StringUtils::format("%9.6f", dest.mPos.rot[2]) << ")" << std::endl; + if (!dest.mCellName.empty()) + std::cout << " Destination Cell: " << dest.mCellName << std::endl; + } + } +} + +namespace EsmTool +{ + void CellState::load(ESM::ESMReader& reader, bool& deleted) + { + mCellState.mId = reader.getCellId(); + mCellState.load(reader); + if (mCellState.mHasFogOfWar) + mFogState.load(reader); + deleted = false; + reader.skipRecord(); + } + + std::unique_ptr RecordBase::create(const ESM::NAME type) + { + std::unique_ptr record; + + switch (type.toInt()) + { + case ESM::REC_ACTI: + { + record = std::make_unique>(); + break; + } + case ESM::REC_ALCH: + { + record = std::make_unique>(); + break; + } + case ESM::REC_APPA: + { + record = std::make_unique>(); + break; + } + case ESM::REC_ARMO: + { + record = std::make_unique>(); + break; + } + case ESM::REC_BODY: + { + record = std::make_unique>(); + break; + } + case ESM::REC_BOOK: + { + record = std::make_unique>(); + break; + } + case ESM::REC_BSGN: + { + record = std::make_unique>(); + break; + } + case ESM::REC_CELL: + { + record = std::make_unique>(); + break; + } + case ESM::REC_CLAS: + { + record = std::make_unique>(); + break; + } + case ESM::REC_CLOT: + { + record = std::make_unique>(); + break; + } + case ESM::REC_CONT: + { + record = std::make_unique>(); + break; + } + case ESM::REC_CREA: + { + record = std::make_unique>(); + break; + } + case ESM::REC_DIAL: + { + record = std::make_unique>(); + break; + } + case ESM::REC_DOOR: + { + record = std::make_unique>(); + break; + } + case ESM::REC_ENCH: + { + record = std::make_unique>(); + break; + } + case ESM::REC_FACT: + { + record = std::make_unique>(); + break; + } + case ESM::REC_GLOB: + { + record = std::make_unique>(); + break; + } + case ESM::REC_GMST: + { + record = std::make_unique>(); + break; + } + case ESM::REC_INFO: + { + record = std::make_unique>(); + break; + } + case ESM::REC_INGR: + { + record = std::make_unique>(); + break; + } + case ESM::REC_LAND: + { + record = std::make_unique>(); + break; + } + case ESM::REC_LEVI: + { + record = std::make_unique>(); + break; + } + case ESM::REC_LEVC: + { + record = std::make_unique>(); + break; + } + case ESM::REC_LIGH: + { + record = std::make_unique>(); + break; + } + case ESM::REC_LOCK: + { + record = std::make_unique>(); + break; + } + case ESM::REC_LTEX: + { + record = std::make_unique>(); + break; + } + case ESM::REC_MISC: + { + record = std::make_unique>(); + break; + } + case ESM::REC_MGEF: + { + record = std::make_unique>(); + break; + } + case ESM::REC_NPC_: + { + record = std::make_unique>(); + break; + } + case ESM::REC_PGRD: + { + record = std::make_unique>(); + break; + } + case ESM::REC_PROB: + { + record = std::make_unique>(); + break; + } + case ESM::REC_RACE: + { + record = std::make_unique>(); + break; + } + case ESM::REC_REGN: + { + record = std::make_unique>(); + break; + } + case ESM::REC_REPA: + { + record = std::make_unique>(); + break; + } + case ESM::REC_SCPT: + { + record = std::make_unique>(); + break; + } + case ESM::REC_SKIL: + { + record = std::make_unique>(); + break; + } + case ESM::REC_SNDG: + { + record = std::make_unique>(); + break; + } + case ESM::REC_SOUN: + { + record = std::make_unique>(); + break; + } + case ESM::REC_SPEL: + { + record = std::make_unique>(); + break; + } + case ESM::REC_STAT: + { + record = std::make_unique>(); + break; + } + case ESM::REC_WEAP: + { + record = std::make_unique>(); + break; + } + case ESM::REC_SSCR: + { + record = std::make_unique>(); + break; + } + case ESM::REC_CSTA: + { + record = std::make_unique>(); + break; + } + default: + break; + } + if (record) + { + record->mType = type; + } + return record; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; + printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mEnchant.empty()) + std::cout << " Enchantment: " << mData.mEnchant << std::endl; + std::cout << " Type: " << armorTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Health: " << mData.mData.mHealth << std::endl; + std::cout << " Armor: " << mData.mData.mArmor << std::endl; + std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; + for (const ESM::PartReference& part : mData.mParts.mParts) + { + std::cout << " Body Part: " << bodyPartLabel(part.mPart) << " (" << (int)(part.mPart) << ")" << std::endl; + std::cout << " Male Name: " << part.mMale << std::endl; + if (!part.mFemale.empty()) + std::cout << " Female Name: " << part.mFemale << std::endl; + } + + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" + << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Quality: " << mData.mData.mQuality << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Race: " << mData.mRace << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Type: " << meshTypeLabel(mData.mData.mType) << " (" << (int)mData.mData.mType << ")" + << std::endl; + std::cout << " Flags: " << bodyPartFlags(mData.mData.mFlags) << std::endl; + std::cout << " Part: " << meshPartLabel(mData.mData.mPart) << " (" << (int)mData.mData.mPart << ")" + << std::endl; + std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mEnchant.empty()) + std::cout << " Enchantment: " << mData.mEnchant << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " IsScroll: " << mData.mData.mIsScroll << std::endl; + std::cout << " SkillId: " << mData.mData.mSkillId << std::endl; + std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; if (mPrintPlain) { - std::cout << " Result Script:" << std::endl; + std::cout << " Text:" << std::endl; std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mResultScript << std::endl; + std::cout << mData.mText << std::endl; std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Result Script: [skipped]" << std::endl; + std::cout << " Text: [skipped]" << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } - std::cout << " Deleted: " << mIsDeleted << std::endl; -} -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - for (int i = 0; i !=4; i++) + template <> + void Record::print() { - // A value of -1 means no effect - if (mData.mData.mEffectID[i] == -1) continue; - std::cout << " Effect: " << magicEffectLabel(mData.mData.mEffectID[i]) - << " (" << mData.mData.mEffectID[i] << ")" << std::endl; - std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i]) - << " (" << mData.mData.mSkills[i] << ")" << std::endl; - std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) - << " (" << mData.mData.mAttributes[i] << ")" << std::endl; - } - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Coordinates: (" << mData.mX << "," << mData.mY << ")" << std::endl; - std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; - std::cout << " DataTypes: " << mData.mDataTypes << std::endl; - - if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes)) - { - std::cout << " Height Offset: " << data->mHeightOffset << std::endl; - // Lots of missing members. - std::cout << " Unknown1: " << data->mUnk1 << std::endl; - std::cout << " Unknown2: " << data->mUnk2 << std::endl; - } - mData.unloadData(); - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; - std::cout << " Flags: " << creatureListFlags(mData.mFlags) << std::endl; - std::cout << " Number of items: " << mData.mList.size() << std::endl; - for (const ESM::LevelledListBase::LevelItem &item : mData.mList) - std::cout << " Creature: Level: " << item.mLevel - << " Creature: " << item.mId << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; - std::cout << " Flags: " << itemListFlags(mData.mFlags) << std::endl; - std::cout << " Number of items: " << mData.mList.size() << std::endl; - for (const ESM::LevelledListBase::LevelItem &item : mData.mList) - std::cout << " Inventory: Level: " << item.mLevel - << " Item: " << item.mId << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - if (!mData.mName.empty()) std::cout << " Name: " << mData.mName << std::endl; - if (!mData.mModel.empty()) - std::cout << " Model: " << mData.mModel << std::endl; - if (!mData.mIcon.empty()) - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Flags: " << lightFlags(mData.mData.mFlags) << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Sound: " << mData.mSound << std::endl; - std::cout << " Duration: " << mData.mData.mTime << std::endl; - std::cout << " Radius: " << mData.mData.mRadius << std::endl; - std::cout << " Color: " << mData.mData.mColor << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Quality: " << mData.mData.mQuality << std::endl; - std::cout << " Uses: " << mData.mData.mUses << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Quality: " << mData.mData.mQuality << std::endl; - std::cout << " Uses: " << mData.mData.mUses << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Quality: " << mData.mData.mQuality << std::endl; - std::cout << " Uses: " << mData.mData.mUses << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Id: " << mData.mId << std::endl; - std::cout << " Index: " << mData.mIndex << std::endl; - std::cout << " Texture: " << mData.mTexture << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Index: " << magicEffectLabel(mData.mIndex) - << " (" << mData.mIndex << ")" << std::endl; - std::cout << " Description: " << mData.mDescription << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - std::cout << " Flags: " << magicEffectFlags(mData.mData.mFlags) << std::endl; - std::cout << " Particle Texture: " << mData.mParticle << std::endl; - if (!mData.mCasting.empty()) - std::cout << " Casting Static: " << mData.mCasting << std::endl; - if (!mData.mCastSound.empty()) - std::cout << " Casting Sound: " << mData.mCastSound << std::endl; - if (!mData.mBolt.empty()) - std::cout << " Bolt Static: " << mData.mBolt << std::endl; - if (!mData.mBoltSound.empty()) - std::cout << " Bolt Sound: " << mData.mBoltSound << std::endl; - if (!mData.mHit.empty()) - std::cout << " Hit Static: " << mData.mHit << std::endl; - if (!mData.mHitSound.empty()) - std::cout << " Hit Sound: " << mData.mHitSound << std::endl; - if (!mData.mArea.empty()) - std::cout << " Area Static: " << mData.mArea << std::endl; - if (!mData.mAreaSound.empty()) - std::cout << " Area Sound: " << mData.mAreaSound << std::endl; - std::cout << " School: " << schoolLabel(mData.mData.mSchool) - << " (" << mData.mData.mSchool << ")" << std::endl; - std::cout << " Base Cost: " << mData.mData.mBaseCost << std::endl; - std::cout << " Unknown 1: " << mData.mData.mUnknown1 << std::endl; - std::cout << " Speed: " << mData.mData.mSpeed << std::endl; - std::cout << " Unknown 2: " << mData.mData.mUnknown2 << std::endl; - std::cout << " RGB Color: " << "(" - << mData.mData.mRed << "," - << mData.mData.mGreen << "," - << mData.mData.mBlue << ")" << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Is Key: " << mData.mData.mIsKey << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Animation: " << mData.mModel << std::endl; - std::cout << " Hair Model: " << mData.mHair << std::endl; - std::cout << " Head Model: " << mData.mHead << std::endl; - std::cout << " Race: " << mData.mRace << std::endl; - std::cout << " Class: " << mData.mClass << std::endl; - if (!mData.mScript.empty()) - std::cout << " Script: " << mData.mScript << std::endl; - if (!mData.mFaction.empty()) - std::cout << " Faction: " << mData.mFaction << std::endl; - std::cout << " Flags: " << npcFlags((int)mData.mFlags) << std::endl; - if (mData.mBloodType != 0) - std::cout << " Blood Type: " << mData.mBloodType+1 << std::endl; - - if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - { - std::cout << " Level: " << mData.mNpdt.mLevel << std::endl; - std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl; - std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl; - std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl; - std::cout << " Gold: " << mData.mNpdt.mGold << std::endl; + std::cout << " Texture: " << mData.mTexture << std::endl; + std::cout << " Description: " << mData.mDescription << std::endl; + for (const auto& power : mData.mPowers.mList) + std::cout << " Power: " << power << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } - else { - std::cout << " Level: " << mData.mNpdt.mLevel << std::endl; - std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl; - std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl; - std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl; + + template <> + void Record::print() + { + // None of the cells have names... + if (!mData.mName.empty()) + std::cout << " Name: " << mData.mName << std::endl; + if (!mData.mRegion.empty()) + std::cout << " Region: " << mData.mRegion << std::endl; + std::cout << " Flags: " << cellFlags(mData.mData.mFlags) << std::endl; + + std::cout << " Coordinates: " + << " (" << mData.getGridX() << "," << mData.getGridY() << ")" << std::endl; + + if (mData.mData.mFlags & ESM::Cell::Interior && !(mData.mData.mFlags & ESM::Cell::QuasiEx)) + { + if (mData.hasAmbient()) + { + // TODO: see if we can change the integer representation to something more sensible + std::cout << " Ambient Light Color: " << mData.mAmbi.mAmbient << std::endl; + std::cout << " Sunlight Color: " << mData.mAmbi.mSunlight << std::endl; + std::cout << " Fog Color: " << mData.mAmbi.mFog << std::endl; + std::cout << " Fog Density: " << mData.mAmbi.mFogDensity << std::endl; + } + else + { + std::cout << " No Ambient Information" << std::endl; + } + std::cout << " Water Level: " << mData.mWater << std::endl; + } + else + std::cout << " Map Color: " << Misc::StringUtils::format("0x%08X", mData.mMapColor) << std::endl; + std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; + std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Description: " << mData.mDescription << std::endl; + std::cout << " Playable: " << mData.mData.mIsPlayable << std::endl; + std::cout << " AI Services: " << Misc::StringUtils::format("0x%08X", mData.mData.mServices) << std::endl; + for (size_t i = 0; i < mData.mData.mAttribute.size(); ++i) + std::cout << " Attribute" << (i + 1) << ": " << attributeLabel(mData.mData.mAttribute[i]) << " (" + << mData.mData.mAttribute[i] << ")" << std::endl; + std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization) << " (" + << mData.mData.mSpecialization << ")" << std::endl; + for (const auto& skills : mData.mData.mSkills) + std::cout << " Minor Skill: " << skillLabel(skills[0]) << " (" << skills[0] << ")" << std::endl; + for (const auto& skills : mData.mData.mSkills) + std::cout << " Major Skill: " << skillLabel(skills[1]) << " (" << skills[1] << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mEnchant.empty()) + std::cout << " Enchantment: " << mData.mEnchant << std::endl; + std::cout << " Type: " << clothingTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" + << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; + for (const ESM::PartReference& part : mData.mParts.mParts) + { + std::cout << " Body Part: " << bodyPartLabel(part.mPart) << " (" << (int)(part.mPart) << ")" << std::endl; + std::cout << " Male Name: " << part.mMale << std::endl; + if (!part.mFemale.empty()) + std::cout << " Female Name: " << part.mFemale << std::endl; + } + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Flags: " << containerFlags(mData.mFlags) << std::endl; + std::cout << " Weight: " << mData.mWeight << std::endl; + for (const ESM::ContItem& item : mData.mInventory.mList) + std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) + << " Item: " << item.mItem << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl; + std::cout << " Blood Type: " << mData.mBloodType + 1 << std::endl; + std::cout << " Original: " << mData.mOriginal << std::endl; + std::cout << " Scale: " << mData.mScale << std::endl; + + std::cout << " Type: " << creatureTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" + << std::endl; + std::cout << " Level: " << mData.mData.mLevel << std::endl; std::cout << " Attributes:" << std::endl; - std::cout << " Strength: " << (int)mData.mNpdt.mStrength << std::endl; - std::cout << " Intelligence: " << (int)mData.mNpdt.mIntelligence << std::endl; - std::cout << " Willpower: " << (int)mData.mNpdt.mWillpower << std::endl; - std::cout << " Agility: " << (int)mData.mNpdt.mAgility << std::endl; - std::cout << " Speed: " << (int)mData.mNpdt.mSpeed << std::endl; - std::cout << " Endurance: " << (int)mData.mNpdt.mEndurance << std::endl; - std::cout << " Personality: " << (int)mData.mNpdt.mPersonality << std::endl; - std::cout << " Luck: " << (int)mData.mNpdt.mLuck << std::endl; + std::cout << " Strength: " << mData.mData.mStrength << std::endl; + std::cout << " Intelligence: " << mData.mData.mIntelligence << std::endl; + std::cout << " Willpower: " << mData.mData.mWillpower << std::endl; + std::cout << " Agility: " << mData.mData.mAgility << std::endl; + std::cout << " Speed: " << mData.mData.mSpeed << std::endl; + std::cout << " Endurance: " << mData.mData.mEndurance << std::endl; + std::cout << " Personality: " << mData.mData.mPersonality << std::endl; + std::cout << " Luck: " << mData.mData.mLuck << std::endl; - std::cout << " Skills:" << std::endl; - for (int i = 0; i != ESM::Skill::Length; i++) - std::cout << " " << skillLabel(i) << ": " - << (int)(mData.mNpdt.mSkills[i]) << std::endl; + std::cout << " Health: " << mData.mData.mHealth << std::endl; + std::cout << " Magicka: " << mData.mData.mMana << std::endl; + std::cout << " Fatigue: " << mData.mData.mFatigue << std::endl; + std::cout << " Soul: " << mData.mData.mSoul << std::endl; + std::cout << " Combat: " << mData.mData.mCombat << std::endl; + std::cout << " Magic: " << mData.mData.mMagic << std::endl; + std::cout << " Stealth: " << mData.mData.mStealth << std::endl; + std::cout << " Attack1: " << mData.mData.mAttack[0] << "-" << mData.mData.mAttack[1] << std::endl; + std::cout << " Attack2: " << mData.mData.mAttack[2] << "-" << mData.mData.mAttack[3] << std::endl; + std::cout << " Attack3: " << mData.mData.mAttack[4] << "-" << mData.mData.mAttack[5] << std::endl; + std::cout << " Gold: " << mData.mData.mGold << std::endl; - std::cout << " Health: " << mData.mNpdt.mHealth << std::endl; - std::cout << " Magicka: " << mData.mNpdt.mMana << std::endl; - std::cout << " Fatigue: " << mData.mNpdt.mFatigue << std::endl; - std::cout << " Gold: " << mData.mNpdt.mGold << std::endl; + for (const ESM::ContItem& item : mData.mInventory.mList) + std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) + << " Item: " << item.mItem << std::endl; + + for (const auto& spell : mData.mSpells.mList) + std::cout << " Spell: " << spell << std::endl; + + printTransport(mData.getTransport()); + + std::cout << " Artificial Intelligence: " << std::endl; + std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl; + std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; + std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; + std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; + std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; + std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; + std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; + std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; + + for (const ESM::AIPackage& package : mData.mAiPackage.mList) + printAIPackage(package); + std::cout << " Deleted: " << mIsDeleted << std::endl; } - for (const ESM::ContItem &item : mData.mInventory.mList) - std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) - << " Item: " << item.mItem << std::endl; - - for (const std::string &spell : mData.mSpells.mList) - std::cout << " Spell: " << spell << std::endl; - - printTransport(mData.getTransport()); - - std::cout << " Artificial Intelligence: " << std::endl; - std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl; - std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; - std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; - std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; - std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; - std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; - std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; - std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; - - for (const ESM::AIPackage &package : mData.mAiPackage.mList) - printAIPackage(package); - - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Cell: " << mData.mCell << std::endl; - std::cout << " Coordinates: (" << mData.mData.mX << "," << mData.mData.mY << ")" << std::endl; - std::cout << " Unknown S1: " << mData.mData.mS1 << std::endl; - if ((unsigned int)mData.mData.mS2 != mData.mPoints.size()) - std::cout << " Reported Point Count: " << mData.mData.mS2 << std::endl; - std::cout << " Point Count: " << mData.mPoints.size() << std::endl; - std::cout << " Edge Count: " << mData.mEdges.size() << std::endl; - - int i = 0; - for (const ESM::Pathgrid::Point &point : mData.mPoints) + template <> + void Record::print() { - std::cout << " Point[" << i << "]:" << std::endl; - std::cout << " Coordinates: (" << point.mX << "," - << point.mY << "," << point.mZ << ")" << std::endl; - std::cout << " Auto-Generated: " << (int)point.mAutogenerated << std::endl; - std::cout << " Connections: " << (int)point.mConnectionNum << std::endl; - std::cout << " Unknown: " << point.mUnknown << std::endl; - i++; + std::cout << " StringId: " << mData.mStringId << std::endl; + std::cout << " Type: " << dialogTypeLabel(mData.mType) << " (" << (int)mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + // Sadly, there are no DialInfos, because the loader dumps as it + // loads, rather than loading and then dumping. :-( Anyone mind if + // I change this? + for (const ESM::DialInfo& info : mData.mInfo) + std::cout << "INFO!" << info.mId << std::endl; } - i = 0; - for (const ESM::Pathgrid::Edge &edge : mData.mEdges) + template <> + void Record::print() { - std::cout << " Edge[" << i << "]: " << edge.mV0 << " -> " << edge.mV1 << std::endl; - if (edge.mV0 >= mData.mData.mS2 || edge.mV1 >= mData.mData.mS2) - std::cout << " BAD POINT IN EDGE!" << std::endl; - i++; - } - - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - static const char *sAttributeNames[8] = - { - "Strength", "Intelligence", "Willpower", "Agility", - "Speed", "Endurance", "Personality", "Luck" - }; - - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Description: " << mData.mDescription << std::endl; - std::cout << " Flags: " << raceFlags(mData.mData.mFlags) << std::endl; - - for (int i=0; i<2; ++i) - { - bool male = i==0; - - std::cout << (male ? " Male:" : " Female:") << std::endl; - - for (int j=0; j<8; ++j) - std::cout << " " << sAttributeNames[j] << ": " - << mData.mData.mAttributeValues[j].getValue (male) << std::endl; - - std::cout << " Height: " << mData.mData.mHeight.getValue (male) << std::endl; - std::cout << " Weight: " << mData.mData.mWeight.getValue (male) << std::endl; - } - - for (int i = 0; i != 7; i++) - // Not all races have 7 skills. - if (mData.mData.mBonus[i].mSkill != -1) - std::cout << " Skill: " - << skillLabel(mData.mData.mBonus[i].mSkill) - << " (" << mData.mData.mBonus[i].mSkill << ") = " - << mData.mData.mBonus[i].mBonus << std::endl; - - for (const std::string &power : mData.mPowers.mList) - std::cout << " Power: " << power << std::endl; - - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - - std::cout << " Weather:" << std::endl; - std::cout << " Clear: " << (int)mData.mData.mClear << std::endl; - std::cout << " Cloudy: " << (int)mData.mData.mCloudy << std::endl; - std::cout << " Foggy: " << (int)mData.mData.mFoggy << std::endl; - std::cout << " Overcast: " << (int)mData.mData.mOvercast << std::endl; - std::cout << " Rain: " << (int)mData.mData.mOvercast << std::endl; - std::cout << " Thunder: " << (int)mData.mData.mThunder << std::endl; - std::cout << " Ash: " << (int)mData.mData.mAsh << std::endl; - std::cout << " Blight: " << (int)mData.mData.mBlight << std::endl; - std::cout << " UnknownA: " << (int)mData.mData.mA << std::endl; - std::cout << " UnknownB: " << (int)mData.mData.mB << std::endl; - std::cout << " Map Color: " << mData.mMapColor << std::endl; - if (!mData.mSleepList.empty()) - std::cout << " Sleep List: " << mData.mSleepList << std::endl; - for (const ESM::Region::SoundRef &soundref : mData.mSoundList) - std::cout << " Sound: " << (int)soundref.mChance << " = " << soundref.mSound << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mId << std::endl; - - std::cout << " Num Shorts: " << mData.mData.mNumShorts << std::endl; - std::cout << " Num Longs: " << mData.mData.mNumLongs << std::endl; - std::cout << " Num Floats: " << mData.mData.mNumFloats << std::endl; - std::cout << " Script Data Size: " << mData.mData.mScriptDataSize << std::endl; - std::cout << " Table Size: " << mData.mData.mStringTableSize << std::endl; - - for (const std::string &variable : mData.mVarNames) - std::cout << " Variable: " << variable << std::endl; - - std::cout << " ByteCode: "; - for (const unsigned char &byte : mData.mScriptData) - std::cout << Misc::StringUtils::format("%02X", (int)(byte)); - std::cout << std::endl; - - if (mPrintPlain) - { - std::cout << " Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mScriptText << std::endl; - std::cout << "END----------------------------------------" << std::endl; - } - else - { - std::cout << " Script: [skipped]" << std::endl; - } - - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " ID: " << skillLabel(mData.mIndex) - << " (" << mData.mIndex << ")" << std::endl; - std::cout << " Description: " << mData.mDescription << std::endl; - std::cout << " Governing Attribute: " << attributeLabel(mData.mData.mAttribute) - << " (" << mData.mData.mAttribute << ")" << std::endl; - std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization) - << " (" << mData.mData.mSpecialization << ")" << std::endl; - for (int i = 0; i != 4; i++) - std::cout << " UseValue[" << i << "]:" << mData.mData.mUseValue[i] << std::endl; -} - -template<> -void Record::print() -{ - if (!mData.mCreature.empty()) - std::cout << " Creature: " << mData.mCreature << std::endl; - std::cout << " Sound: " << mData.mSound << std::endl; - std::cout << " Type: " << soundTypeLabel(mData.mType) - << " (" << mData.mType << ")" << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Sound: " << mData.mSound << std::endl; - std::cout << " Volume: " << (int)mData.mData.mVolume << std::endl; - if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) - std::cout << " Range: " << (int)mData.mData.mMinRange << " - " - << (int)mData.mData.mMaxRange << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Type: " << spellTypeLabel(mData.mData.mType) - << " (" << mData.mData.mType << ")" << std::endl; - std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; - std::cout << " Cost: " << mData.mData.mCost << std::endl; - printEffectList(mData.mEffects); - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Start Script: " << mData.mId << std::endl; - std::cout << " Start Data: " << mData.mData << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} - -template<> -void Record::print() -{ - std::cout << " Model: " << mData.mModel << std::endl; -} - -template<> -void Record::print() -{ - // No names on VFX bolts - if (!mData.mName.empty()) std::cout << " Name: " << mData.mName << std::endl; - std::cout << " Model: " << mData.mModel << std::endl; - // No icons on VFX bolts or magic bolts - if (!mData.mIcon.empty()) - std::cout << " Icon: " << mData.mIcon << std::endl; - if (!mData.mScript.empty()) + std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; - if (!mData.mEnchant.empty()) - std::cout << " Enchantment: " << mData.mEnchant << std::endl; - std::cout << " Type: " << weaponTypeLabel(mData.mData.mType) - << " (" << mData.mData.mType << ")" << std::endl; - std::cout << " Flags: " << weaponFlags(mData.mData.mFlags) << std::endl; - std::cout << " Weight: " << mData.mData.mWeight << std::endl; - std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " Health: " << mData.mData.mHealth << std::endl; - std::cout << " Speed: " << mData.mData.mSpeed << std::endl; - std::cout << " Reach: " << mData.mData.mReach << std::endl; - std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; - if (mData.mData.mChop[0] != 0 && mData.mData.mChop[1] != 0) - std::cout << " Chop: " << (int)mData.mData.mChop[0] << "-" - << (int)mData.mData.mChop[1] << std::endl; - if (mData.mData.mSlash[0] != 0 && mData.mData.mSlash[1] != 0) - std::cout << " Slash: " << (int)mData.mData.mSlash[0] << "-" - << (int)mData.mData.mSlash[1] << std::endl; - if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) - std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" - << (int)mData.mData.mThrust[1] << std::endl; - std::cout << " Deleted: " << mIsDeleted << std::endl; -} + std::cout << " OpenSound: " << mData.mOpenSound << std::endl; + std::cout << " CloseSound: " << mData.mCloseSound << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } -template<> -std::string Record::getId() const -{ - return mData.mName; -} + template <> + void Record::print() + { + std::cout << " Type: " << enchantTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; + std::cout << " Cost: " << mData.mData.mCost << std::endl; + std::cout << " Charge: " << mData.mData.mCharge << std::endl; + std::cout << " Flags: " << enchantmentFlags(mData.mData.mFlags) << std::endl; + printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; + } -template<> -std::string Record::getId() const -{ - return std::string(); // No ID for Land record -} + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Hidden: " << mData.mData.mIsHidden << std::endl; + for (size_t i = 0; i < mData.mData.mAttribute.size(); ++i) + std::cout << " Attribute" << (i + 1) << ": " << attributeLabel(mData.mData.mAttribute[i]) << " (" + << mData.mData.mAttribute[i] << ")" << std::endl; + for (int skill : mData.mData.mSkills) + if (skill != -1) + std::cout << " Skill: " << skillLabel(skill) << " (" << skill << ")" << std::endl; + for (size_t i = 0; i != mData.mData.mRankData.size(); i++) + if (!mData.mRanks[i].empty()) + { + std::cout << " Rank: " << mData.mRanks[i] << std::endl; + std::cout << " Attribute1 Requirement: " << mData.mData.mRankData[i].mAttribute1 << std::endl; + std::cout << " Attribute2 Requirement: " << mData.mData.mRankData[i].mAttribute2 << std::endl; + std::cout << " One Skill at Level: " << mData.mData.mRankData[i].mPrimarySkill << std::endl; + std::cout << " Two Skills at Level: " << mData.mData.mRankData[i].mFavouredSkill << std::endl; + std::cout << " Faction Reaction: " << mData.mData.mRankData[i].mFactReaction << std::endl; + } + for (const auto& reaction : mData.mReactions) + std::cout << " Reaction: " << reaction.second << " = " << reaction.first << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } -template<> -std::string Record::getId() const -{ - return std::string(); // No ID for MagicEffect record -} + template <> + void Record::print() + { + std::cout << " " << mData.mValue << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } -template<> -std::string Record::getId() const -{ - return std::string(); // No ID for Pathgrid record -} + template <> + void Record::print() + { + std::cout << " " << mData.mValue << std::endl; + } -template<> -std::string Record::getId() const -{ - return std::string(); // No ID for Skill record -} + template <> + void Record::print() + { + std::cout << " Id: " << mData.mId << std::endl; + if (!mData.mPrev.empty()) + std::cout << " Previous ID: " << mData.mPrev << std::endl; + if (!mData.mNext.empty()) + std::cout << " Next ID: " << mData.mNext << std::endl; + std::cout << " Text: " << mData.mResponse << std::endl; + if (!mData.mActor.empty()) + std::cout << " Actor: " << mData.mActor << std::endl; + if (!mData.mRace.empty()) + std::cout << " Race: " << mData.mRace << std::endl; + if (!mData.mClass.empty()) + std::cout << " Class: " << mData.mClass << std::endl; + std::cout << " Factionless: " << mData.mFactionLess << std::endl; + if (!mData.mFaction.empty()) + std::cout << " NPC Faction: " << mData.mFaction << std::endl; + if (mData.mData.mRank != -1) + std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl; + if (!mData.mPcFaction.empty()) + std::cout << " PC Faction: " << mData.mPcFaction << std::endl; + // CHANGE? non-standard capitalization mPCrank -> mPCRank (mPcRank?) + if (mData.mData.mPCrank != -1) + std::cout << " PC Rank: " << (int)mData.mData.mPCrank << std::endl; + if (!mData.mCell.empty()) + std::cout << " Cell: " << mData.mCell << std::endl; + if (mData.mData.mDisposition > 0) + std::cout << " Disposition/Journal index: " << mData.mData.mDisposition << std::endl; + if (mData.mData.mGender != ESM::DialInfo::NA) + std::cout << " Gender: " << static_cast(mData.mData.mGender) << std::endl; + if (!mData.mSound.empty()) + std::cout << " Sound File: " << mData.mSound << std::endl; + + std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus) << " (" << mData.mQuestStatus << ")" + << std::endl; + std::cout << " Unknown1: " << mData.mData.mUnknown1 << std::endl; + std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl; + + for (const ESM::DialInfo::SelectStruct& rule : mData.mSelects) + std::cout << " Select Rule: " << ruleString(rule) << std::endl; + + if (!mData.mResultScript.empty()) + { + if (mPrintPlain) + { + std::cout << " Result Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mResultScript << std::endl; + std::cout << "END----------------------------------------" << std::endl; + } + else + { + std::cout << " Result Script: [skipped]" << std::endl; + } + } + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + for (int i = 0; i != 4; i++) + { + // A value of -1 means no effect + if (mData.mData.mEffectID[i] == -1) + continue; + std::cout << " Effect: " << magicEffectLabel(mData.mData.mEffectID[i]) << " (" << mData.mData.mEffectID[i] + << ")" << std::endl; + std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i]) << " (" << mData.mData.mSkills[i] << ")" + << std::endl; + std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) << " (" + << mData.mData.mAttributes[i] << ")" << std::endl; + } + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Coordinates: (" << mData.mX << "," << mData.mY << ")" << std::endl; + std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; + std::cout << " DataTypes: " << mData.mDataTypes << std::endl; + + if (const ESM::Land::LandData* data = mData.getLandData(mData.mDataTypes)) + { + std::cout << " Height Offset: " << data->mHeightOffset << std::endl; + // Lots of missing members. + std::cout << " Unknown1: " << data->mUnk1 << std::endl; + std::cout << " Unknown2: " << static_cast(data->mUnk2) << std::endl; + } + mData.unloadData(); + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; + std::cout << " Flags: " << creatureListFlags(mData.mFlags) << std::endl; + std::cout << " Number of items: " << mData.mList.size() << std::endl; + for (const ESM::LevelledListBase::LevelItem& item : mData.mList) + std::cout << " Creature: Level: " << item.mLevel << " Creature: " << item.mId << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; + std::cout << " Flags: " << itemListFlags(mData.mFlags) << std::endl; + std::cout << " Number of items: " << mData.mList.size() << std::endl; + for (const ESM::LevelledListBase::LevelItem& item : mData.mList) + std::cout << " Inventory: Level: " << item.mLevel << " Item: " << item.mId << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + if (!mData.mName.empty()) + std::cout << " Name: " << mData.mName << std::endl; + if (!mData.mModel.empty()) + std::cout << " Model: " << mData.mModel << std::endl; + if (!mData.mIcon.empty()) + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Flags: " << lightFlags(mData.mData.mFlags) << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Sound: " << mData.mSound << std::endl; + std::cout << " Duration: " << mData.mData.mTime << std::endl; + std::cout << " Radius: " << mData.mData.mRadius << std::endl; + std::cout << " Color: " << mData.mData.mColor << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Quality: " << mData.mData.mQuality << std::endl; + std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Quality: " << mData.mData.mQuality << std::endl; + std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Quality: " << mData.mData.mQuality << std::endl; + std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Id: " << mData.mId << std::endl; + std::cout << " Index: " << mData.mIndex << std::endl; + std::cout << " Texture: " << mData.mTexture << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Index: " << magicEffectLabel(mData.mIndex) << " (" << mData.mIndex << ")" << std::endl; + std::cout << " Description: " << mData.mDescription << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + std::cout << " Flags: " << magicEffectFlags(mData.mData.mFlags) << std::endl; + std::cout << " Particle Texture: " << mData.mParticle << std::endl; + if (!mData.mCasting.empty()) + std::cout << " Casting Static: " << mData.mCasting << std::endl; + if (!mData.mCastSound.empty()) + std::cout << " Casting Sound: " << mData.mCastSound << std::endl; + if (!mData.mBolt.empty()) + std::cout << " Bolt Static: " << mData.mBolt << std::endl; + if (!mData.mBoltSound.empty()) + std::cout << " Bolt Sound: " << mData.mBoltSound << std::endl; + if (!mData.mHit.empty()) + std::cout << " Hit Static: " << mData.mHit << std::endl; + if (!mData.mHitSound.empty()) + std::cout << " Hit Sound: " << mData.mHitSound << std::endl; + if (!mData.mArea.empty()) + std::cout << " Area Static: " << mData.mArea << std::endl; + if (!mData.mAreaSound.empty()) + std::cout << " Area Sound: " << mData.mAreaSound << std::endl; + std::cout << " School: " << schoolLabel(ESM::MagicSchool::skillRefIdToIndex(mData.mData.mSchool)) << " (" + << mData.mData.mSchool << ")" << std::endl; + std::cout << " Base Cost: " << mData.mData.mBaseCost << std::endl; + std::cout << " Unknown 1: " << mData.mData.mUnknown1 << std::endl; + std::cout << " Speed: " << mData.mData.mSpeed << std::endl; + std::cout << " Unknown 2: " << mData.mData.mUnknown2 << std::endl; + std::cout << " RGB Color: " + << "(" << mData.mData.mRed << "," << mData.mData.mGreen << "," << mData.mData.mBlue << ")" + << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Is Key: " << (mData.mData.mFlags & ESM::Miscellaneous::Key) << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Animation: " << mData.mModel << std::endl; + std::cout << " Hair Model: " << mData.mHair << std::endl; + std::cout << " Head Model: " << mData.mHead << std::endl; + std::cout << " Race: " << mData.mRace << std::endl; + std::cout << " Class: " << mData.mClass << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mFaction.empty()) + std::cout << " Faction: " << mData.mFaction << std::endl; + std::cout << " Flags: " << npcFlags((int)mData.mFlags) << std::endl; + if (mData.mBloodType != 0) + std::cout << " Blood Type: " << mData.mBloodType + 1 << std::endl; + + if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + { + std::cout << " Level: " << mData.mNpdt.mLevel << std::endl; + std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl; + std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl; + std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl; + std::cout << " Gold: " << mData.mNpdt.mGold << std::endl; + } + else + { + std::cout << " Level: " << mData.mNpdt.mLevel << std::endl; + std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl; + std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl; + std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl; + + std::cout << " Attributes:" << std::endl; + std::cout << " Strength: " << (int)mData.mNpdt.mStrength << std::endl; + std::cout << " Intelligence: " << (int)mData.mNpdt.mIntelligence << std::endl; + std::cout << " Willpower: " << (int)mData.mNpdt.mWillpower << std::endl; + std::cout << " Agility: " << (int)mData.mNpdt.mAgility << std::endl; + std::cout << " Speed: " << (int)mData.mNpdt.mSpeed << std::endl; + std::cout << " Endurance: " << (int)mData.mNpdt.mEndurance << std::endl; + std::cout << " Personality: " << (int)mData.mNpdt.mPersonality << std::endl; + std::cout << " Luck: " << (int)mData.mNpdt.mLuck << std::endl; + + std::cout << " Skills:" << std::endl; + for (size_t i = 0; i != mData.mNpdt.mSkills.size(); i++) + std::cout << " " << skillLabel(i) << ": " << int(mData.mNpdt.mSkills[i]) << std::endl; + + std::cout << " Health: " << mData.mNpdt.mHealth << std::endl; + std::cout << " Magicka: " << mData.mNpdt.mMana << std::endl; + std::cout << " Fatigue: " << mData.mNpdt.mFatigue << std::endl; + std::cout << " Gold: " << mData.mNpdt.mGold << std::endl; + } + + for (const ESM::ContItem& item : mData.mInventory.mList) + std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) + << " Item: " << item.mItem << std::endl; + + for (const auto& spell : mData.mSpells.mList) + std::cout << " Spell: " << spell << std::endl; + + printTransport(mData.getTransport()); + + std::cout << " Artificial Intelligence: " << std::endl; + std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl; + std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; + std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; + std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; + std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; + std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; + std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; + std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; + + for (const ESM::AIPackage& package : mData.mAiPackage.mList) + printAIPackage(package); + + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Cell: " << mData.mCell << std::endl; + std::cout << " Coordinates: (" << mData.mData.mX << "," << mData.mData.mY << ")" << std::endl; + std::cout << " Unknown S1: " << mData.mData.mS1 << std::endl; + if (static_cast(mData.mData.mS2) != mData.mPoints.size()) + std::cout << " Reported Point Count: " << mData.mData.mS2 << std::endl; + std::cout << " Point Count: " << mData.mPoints.size() << std::endl; + std::cout << " Edge Count: " << mData.mEdges.size() << std::endl; + + int i = 0; + for (const ESM::Pathgrid::Point& point : mData.mPoints) + { + std::cout << " Point[" << i << "]:" << std::endl; + std::cout << " Coordinates: (" << point.mX << "," << point.mY << "," << point.mZ << ")" << std::endl; + std::cout << " Auto-Generated: " << (int)point.mAutogenerated << std::endl; + std::cout << " Connections: " << (int)point.mConnectionNum << std::endl; + std::cout << " Unknown: " << point.mUnknown << std::endl; + i++; + } + + i = 0; + for (const ESM::Pathgrid::Edge& edge : mData.mEdges) + { + std::cout << " Edge[" << i << "]: " << edge.mV0 << " -> " << edge.mV1 << std::endl; + if (edge.mV0 >= static_cast(mData.mData.mS2) || edge.mV1 >= static_cast(mData.mData.mS2)) + std::cout << " BAD POINT IN EDGE!" << std::endl; + i++; + } + + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + static const char* sAttributeNames[ESM::Attribute::Length] + = { "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", "Luck" }; + + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Description: " << mData.mDescription << std::endl; + std::cout << " Flags: " << raceFlags(mData.mData.mFlags) << std::endl; + + for (int i = 0; i < 2; ++i) + { + bool male = i == 0; + + std::cout << (male ? " Male:" : " Female:") << std::endl; + + for (int j = 0; j < ESM::Attribute::Length; ++j) + std::cout << " " << sAttributeNames[j] << ": " << mData.mData.mAttributeValues[j].getValue(male) + << std::endl; + + std::cout << " Height: " << mData.mData.mHeight.getValue(male) << std::endl; + std::cout << " Weight: " << mData.mData.mWeight.getValue(male) << std::endl; + } + + for (const auto& bonus : mData.mData.mBonus) + // Not all races have 7 skills. + if (bonus.mSkill != -1) + std::cout << " Skill: " << skillLabel(bonus.mSkill) << " (" << bonus.mSkill << ") = " << bonus.mBonus + << std::endl; + + for (const auto& power : mData.mPowers.mList) + std::cout << " Power: " << power << std::endl; + + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + + std::cout << " Weather:" << std::endl; + std::cout << " Clear: " << (int)mData.mData.mClear << std::endl; + std::cout << " Cloudy: " << (int)mData.mData.mCloudy << std::endl; + std::cout << " Foggy: " << (int)mData.mData.mFoggy << std::endl; + std::cout << " Overcast: " << (int)mData.mData.mOvercast << std::endl; + std::cout << " Rain: " << (int)mData.mData.mOvercast << std::endl; + std::cout << " Thunder: " << (int)mData.mData.mThunder << std::endl; + std::cout << " Ash: " << (int)mData.mData.mAsh << std::endl; + std::cout << " Blight: " << (int)mData.mData.mBlight << std::endl; + std::cout << " Snow: " << (int)mData.mData.mSnow << std::endl; + std::cout << " Blizzard: " << (int)mData.mData.mBlizzard << std::endl; + std::cout << " Map Color: " << mData.mMapColor << std::endl; + if (!mData.mSleepList.empty()) + std::cout << " Sleep List: " << mData.mSleepList << std::endl; + for (const ESM::Region::SoundRef& soundref : mData.mSoundList) + std::cout << " Sound: " << (int)soundref.mChance << " = " << soundref.mSound << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mId << std::endl; + + std::cout << " Num Shorts: " << mData.mData.mNumShorts << std::endl; + std::cout << " Num Longs: " << mData.mData.mNumLongs << std::endl; + std::cout << " Num Floats: " << mData.mData.mNumFloats << std::endl; + std::cout << " Script Data Size: " << mData.mData.mScriptDataSize << std::endl; + std::cout << " Table Size: " << mData.mData.mStringTableSize << std::endl; + + for (const std::string& variable : mData.mVarNames) + std::cout << " Variable: " << variable << std::endl; + + std::cout << " ByteCode: "; + for (const unsigned char& byte : mData.mScriptData) + std::cout << Misc::StringUtils::format("%02X", (int)(byte)); + std::cout << std::endl; + + if (mPrintPlain) + { + std::cout << " Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mScriptText << std::endl; + std::cout << "END----------------------------------------" << std::endl; + } + else + { + std::cout << " Script: [skipped]" << std::endl; + } + + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " ID: " << skillLabel(mData.mIndex) << " (" << mData.mIndex << ")" << std::endl; + std::cout << " Description: " << mData.mDescription << std::endl; + std::cout << " Governing Attribute: " << attributeLabel(mData.mData.mAttribute) << " (" + << mData.mData.mAttribute << ")" << std::endl; + std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization) << " (" + << mData.mData.mSpecialization << ")" << std::endl; + for (int i = 0; i != 4; i++) + std::cout << " UseValue[" << i << "]:" << mData.mData.mUseValue[i] << std::endl; + } + + template <> + void Record::print() + { + if (!mData.mCreature.empty()) + std::cout << " Creature: " << mData.mCreature << std::endl; + std::cout << " Sound: " << mData.mSound << std::endl; + std::cout << " Type: " << soundTypeLabel(mData.mType) << " (" << mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Sound: " << mData.mSound << std::endl; + std::cout << " Volume: " << (int)mData.mData.mVolume << std::endl; + if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) + std::cout << " Range: " << (int)mData.mData.mMinRange << " - " << (int)mData.mData.mMaxRange << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Type: " << spellTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; + std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; + std::cout << " Cost: " << mData.mData.mCost << std::endl; + printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Start Script: " << mData.mId << std::endl; + std::cout << " Start Data: " << mData.mData << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Model: " << mData.mModel << std::endl; + } + + template <> + void Record::print() + { + // No names on VFX bolts + if (!mData.mName.empty()) + std::cout << " Name: " << mData.mName << std::endl; + std::cout << " Model: " << mData.mModel << std::endl; + // No icons on VFX bolts or magic bolts + if (!mData.mIcon.empty()) + std::cout << " Icon: " << mData.mIcon << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mEnchant.empty()) + std::cout << " Enchantment: " << mData.mEnchant << std::endl; + std::cout << " Type: " << weaponTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; + std::cout << " Flags: " << weaponFlags(mData.mData.mFlags) << std::endl; + std::cout << " Weight: " << mData.mData.mWeight << std::endl; + std::cout << " Value: " << mData.mData.mValue << std::endl; + std::cout << " Health: " << mData.mData.mHealth << std::endl; + std::cout << " Speed: " << mData.mData.mSpeed << std::endl; + std::cout << " Reach: " << mData.mData.mReach << std::endl; + std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; + if (mData.mData.mChop[0] != 0 && mData.mData.mChop[1] != 0) + std::cout << " Chop: " << (int)mData.mData.mChop[0] << "-" << (int)mData.mData.mChop[1] << std::endl; + if (mData.mData.mSlash[0] != 0 && mData.mData.mSlash[1] != 0) + std::cout << " Slash: " << (int)mData.mData.mSlash[0] << "-" << (int)mData.mData.mSlash[1] << std::endl; + if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) + std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" << (int)mData.mData.mThrust[1] << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; + } + + template <> + void Record::print() + { + std::cout << " Id:" << std::endl; + std::cout << " CellId: " << mData.mCellState.mId << std::endl; + std::cout << " Index:" << std::endl; + std::cout << " WaterLevel: " << mData.mCellState.mWaterLevel << std::endl; + std::cout << " HasFogOfWar: " << mData.mCellState.mHasFogOfWar << std::endl; + std::cout << " LastRespawn:" << std::endl; + std::cout << " Day:" << mData.mCellState.mLastRespawn.mDay << std::endl; + std::cout << " Hour:" << mData.mCellState.mLastRespawn.mHour << std::endl; + if (mData.mCellState.mHasFogOfWar) + { + std::cout << " NorthMarkerAngle: " << mData.mFogState.mNorthMarkerAngle << std::endl; + std::cout << " Bounds:" << std::endl; + std::cout << " MinX: " << mData.mFogState.mBounds.mMinX << std::endl; + std::cout << " MinY: " << mData.mFogState.mBounds.mMinY << std::endl; + std::cout << " MaxX: " << mData.mFogState.mBounds.mMaxX << std::endl; + std::cout << " MaxY: " << mData.mFogState.mBounds.mMaxY << std::endl; + for (const ESM::FogTexture& fogTexture : mData.mFogState.mFogTextures) + { + std::cout << " FogTexture:" << std::endl; + std::cout << " X: " << fogTexture.mX << std::endl; + std::cout << " Y: " << fogTexture.mY << std::endl; + std::cout << " ImageData: (" << fogTexture.mImageData.size() << ")" << std::endl; + } + } + } + + template <> + std::string Record::getId() const + { + return mData.mName; + } + + template <> + std::string Record::getId() const + { + return std::string(); // No ID for Land record + } + + template <> + std::string Record::getId() const + { + return std::string(); // No ID for MagicEffect record + } + + template <> + std::string Record::getId() const + { + return std::string(); // No ID for Pathgrid record + } + + template <> + std::string Record::getId() const + { + return std::string(); // No ID for Skill record + } + + template <> + std::string Record::getId() const + { + std::ostringstream stream; + stream << mData.mCellState.mId; + return stream.str(); + } } // end namespace diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index bbb3dd098..eab133b6b 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -1,9 +1,12 @@ #ifndef OPENMW_ESMTOOL_RECORD_H #define OPENMW_ESMTOOL_RECORD_H +#include #include #include +#include +#include namespace ESM { @@ -13,7 +16,8 @@ namespace ESM namespace EsmTool { - template class Record; + template + class Record; class RecordBase { @@ -24,45 +28,48 @@ namespace EsmTool bool mPrintPlain; public: - RecordBase () - : mFlags(0) - , mPrintPlain(false) + RecordBase() + : mFlags(0) + , mPrintPlain(false) { } - virtual ~RecordBase() {} + virtual ~RecordBase() = default; virtual std::string getId() const = 0; - uint32_t getFlags() const { - return mFlags; - } + uint32_t getFlags() const { return mFlags; } - void setFlags(uint32_t flags) { - mFlags = flags; - } + void setFlags(uint32_t flags) { mFlags = flags; } - ESM::NAME getType() const { - return mType; - } + ESM::NAME getType() const { return mType; } - void setPrintPlain(bool plain) { - mPrintPlain = plain; - } + void setPrintPlain(bool plain) { mPrintPlain = plain; } - virtual void load(ESM::ESMReader &esm) = 0; - virtual void save(ESM::ESMWriter &esm) = 0; + virtual void load(ESM::ESMReader& esm) = 0; + virtual void save(ESM::ESMWriter& esm) = 0; virtual void print() = 0; - static RecordBase *create(ESM::NAME type); + static std::unique_ptr create(ESM::NAME type); // just make it a bit shorter template - Record *cast() { - return static_cast *>(this); + Record* cast() + { + return static_cast*>(this); } }; + struct CellState + { + ESM::CellState mCellState; + ESM::FogState mFogState; + + void load(ESM::ESMReader& reader, bool& deleted); + + void save(ESM::ESMWriter& /*writer*/, bool /*deleted*/) {} + }; + template class Record : public RecordBase { @@ -72,75 +79,119 @@ namespace EsmTool public: Record() : mIsDeleted(false) - {} - - std::string getId() const override { - return mData.mId; + { } - T &get() { - return mData; - } + std::string getId() const override { return mData.mId.toDebugString(); } - void save(ESM::ESMWriter &esm) override { - mData.save(esm, mIsDeleted); - } + T& get() { return mData; } - void load(ESM::ESMReader &esm) override { - mData.load(esm, mIsDeleted); - } + void save(ESM::ESMWriter& esm) override { mData.save(esm, mIsDeleted); } + + void load(ESM::ESMReader& esm) override { mData.load(esm, mIsDeleted); } void print() override; }; - - template<> std::string Record::getId() const; - template<> std::string Record::getId() const; - template<> std::string Record::getId() const; - template<> std::string Record::getId() const; - template<> std::string Record::getId() const; - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); - template<> void Record::print(); + template <> + std::string Record::getId() const; + template <> + std::string Record::getId() const; + template <> + std::string Record::getId() const; + template <> + std::string Record::getId() const; + template <> + std::string Record::getId() const; + template <> + std::string Record::getId() const; + + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); + template <> + void Record::print(); } #endif diff --git a/apps/esmtool/tes4.cpp b/apps/esmtool/tes4.cpp new file mode 100644 index 000000000..8eaf1b346 --- /dev/null +++ b/apps/esmtool/tes4.cpp @@ -0,0 +1,564 @@ +#include "tes4.hpp" +#include "arguments.hpp" +#include "labels.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace EsmTool +{ + namespace + { + struct Params + { + const bool mQuite; + + explicit Params(const Arguments& info) + : mQuite(info.quiet_given || info.mode == "clone") + { + } + }; + + std::string toString(ESM4::GroupType type) + { + switch (type) + { + case ESM4::Grp_RecordType: + return "RecordType"; + case ESM4::Grp_WorldChild: + return "WorldChild"; + case ESM4::Grp_InteriorCell: + return "InteriorCell"; + case ESM4::Grp_InteriorSubCell: + return "InteriorSubCell"; + case ESM4::Grp_ExteriorCell: + return "ExteriorCell"; + case ESM4::Grp_ExteriorSubCell: + return "ExteriorSubCell"; + case ESM4::Grp_CellChild: + return "CellChild"; + case ESM4::Grp_TopicChild: + return "TopicChild"; + case ESM4::Grp_CellPersistentChild: + return "CellPersistentChild"; + case ESM4::Grp_CellTemporaryChild: + return "CellTemporaryChild"; + case ESM4::Grp_CellVisibleDistChild: + return "CellVisibleDistChild"; + } + + return "Unknown (" + std::to_string(type) + ")"; + } + + template + struct WriteArray + { + std::string_view mPrefix; + const T& mValue; + + explicit WriteArray(std::string_view prefix, const T& value) + : mPrefix(prefix) + , mValue(value) + { + } + }; + + template + struct WriteData + { + const T& mValue; + + explicit WriteData(const T& value) + : mValue(value) + { + } + }; + + template + std::ostream& operator<<(std::ostream& stream, const WriteArray& write) + { + for (const auto& value : write.mValue) + stream << write.mPrefix << value; + return stream; + } + + template + std::ostream& operator<<(std::ostream& stream, const WriteData& /*write*/) + { + return stream << " ?"; + } + + std::ostream& operator<<(std::ostream& stream, const WriteData& write) + { + std::visit([&](const auto& v) { stream << v; }, write.mValue); + return stream; + } + + template + void readTypedRecord(const Params& params, ESM4::Reader& reader) + { + reader.getRecordData(); + + T value; + value.load(reader); + + if (params.mQuite) + return; + + std::cout << "\n Record: " << ESM::NAME(reader.hdr().record.typeId).toStringView(); + if constexpr (ESM4::hasFormId) + std::cout << "\n FormId: 0x" << ESM4::formIdToString(value.mFormId); + if constexpr (ESM::hasId) + { + if constexpr (std::is_same_v) + std::cout << "\n FormId: 0x" << ESM4::formIdToString(value.mId); + else + std::cout << "\n Id: " << value.mId; + } + if constexpr (ESM4::hasFlags) + std::cout << "\n Record flags: " << recordFlags(value.mFlags); + if constexpr (ESM4::hasParentFormId) + std::cout << "\n ParentFormId: 0x" << ESM4::formIdToString(value.mParentFormId); + if constexpr (ESM4::hasParent) + std::cout << "\n Parent: " << value.mParent; + if constexpr (ESM4::hasEditorId) + std::cout << "\n EditorId: " << value.mEditorId; + if constexpr (ESM::hasModel) + std::cout << "\n Model: " << value.mModel; + if constexpr (ESM4::hasNif) + std::cout << "\n Nif:" << WriteArray("\n - ", value.mNif); + if constexpr (ESM4::hasKf) + std::cout << "\n Kf:" << WriteArray("\n - ", value.mKf); + if constexpr (ESM4::hasType) + std::cout << "\n Type: " << value.mType; + if constexpr (ESM4::hasValue) + std::cout << "\n Value: " << value.mValue; + if constexpr (ESM4::hasData) + std::cout << "\n Data: " << WriteData(value.mData); + std::cout << '\n'; + } + + bool readRecord(const Params& params, ESM4::Reader& reader) + { + switch (static_cast(reader.hdr().record.typeId)) + { + case ESM4::REC_AACT: + break; + case ESM4::REC_ACHR: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ACRE: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ACTI: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ADDN: + break; + case ESM4::REC_ALCH: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ALOC: + readTypedRecord(params, reader); + return true; + case ESM4::REC_AMMO: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ANIO: + readTypedRecord(params, reader); + return true; + case ESM4::REC_APPA: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ARMA: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ARMO: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ARTO: + break; + case ESM4::REC_ASPC: + readTypedRecord(params, reader); + return true; + case ESM4::REC_ASTP: + break; + case ESM4::REC_AVIF: + break; + case ESM4::REC_BOOK: + readTypedRecord(params, reader); + return true; + case ESM4::REC_BPTD: + readTypedRecord(params, reader); + return true; + case ESM4::REC_CAMS: + break; + case ESM4::REC_CCRD: + break; + case ESM4::REC_CELL: + readTypedRecord(params, reader); + return true; + case ESM4::REC_CLAS: + readTypedRecord(params, reader); + return true; + case ESM4::REC_CLFM: + readTypedRecord(params, reader); + return true; + case ESM4::REC_CLMT: + break; + case ESM4::REC_CLOT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_CMNY: + break; + case ESM4::REC_COBJ: + break; + case ESM4::REC_COLL: + break; + case ESM4::REC_CONT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_CPTH: + break; + case ESM4::REC_CREA: + readTypedRecord(params, reader); + return true; + case ESM4::REC_CSTY: + break; + case ESM4::REC_DEBR: + break; + case ESM4::REC_DIAL: + readTypedRecord(params, reader); + return true; + case ESM4::REC_DLBR: + break; + case ESM4::REC_DLVW: + break; + case ESM4::REC_DOBJ: + readTypedRecord(params, reader); + return true; + case ESM4::REC_DOOR: + readTypedRecord(params, reader); + return true; + case ESM4::REC_DUAL: + break; + case ESM4::REC_ECZN: + break; + case ESM4::REC_EFSH: + break; + case ESM4::REC_ENCH: + break; + case ESM4::REC_EQUP: + break; + case ESM4::REC_EXPL: + break; + case ESM4::REC_EYES: + readTypedRecord(params, reader); + return true; + case ESM4::REC_FACT: + break; + case ESM4::REC_FLOR: + readTypedRecord(params, reader); + return true; + case ESM4::REC_FLST: + readTypedRecord(params, reader); + return true; + case ESM4::REC_FSTP: + break; + case ESM4::REC_FSTS: + break; + case ESM4::REC_FURN: + readTypedRecord(params, reader); + return true; + case ESM4::REC_GLOB: + readTypedRecord(params, reader); + return true; + case ESM4::REC_GMST: + readTypedRecord(params, reader); + return true; + case ESM4::REC_GRAS: + readTypedRecord(params, reader); + return true; + case ESM4::REC_GRUP: + break; + case ESM4::REC_HAIR: + readTypedRecord(params, reader); + return true; + case ESM4::REC_HAZD: + break; + case ESM4::REC_HDPT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_IDLE: + // FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm + // readTypedRecord(params, reader); + return true; + break; + case ESM4::REC_IDLM: + readTypedRecord(params, reader); + return true; + case ESM4::REC_IMAD: + break; + case ESM4::REC_IMGS: + break; + case ESM4::REC_IMOD: + readTypedRecord(params, reader); + return true; + case ESM4::REC_INFO: + readTypedRecord(params, reader); + return true; + case ESM4::REC_INGR: + readTypedRecord(params, reader); + return true; + case ESM4::REC_IPCT: + break; + case ESM4::REC_IPDS: + break; + case ESM4::REC_KEYM: + readTypedRecord(params, reader); + return true; + case ESM4::REC_KYWD: + break; + case ESM4::REC_LAND: + readTypedRecord(params, reader); + return true; + case ESM4::REC_LCRT: + break; + case ESM4::REC_LCTN: + break; + case ESM4::REC_LGTM: + readTypedRecord(params, reader); + return true; + case ESM4::REC_LIGH: + readTypedRecord(params, reader); + return true; + case ESM4::REC_LSCR: + break; + case ESM4::REC_LTEX: + readTypedRecord(params, reader); + return true; + case ESM4::REC_LVLC: + readTypedRecord(params, reader); + return true; + case ESM4::REC_LVLI: + readTypedRecord(params, reader); + return true; + case ESM4::REC_LVLN: + readTypedRecord(params, reader); + return true; + case ESM4::REC_LVSP: + break; + case ESM4::REC_MATO: + readTypedRecord(params, reader); + return true; + case ESM4::REC_MATT: + break; + case ESM4::REC_MESG: + break; + case ESM4::REC_MGEF: + break; + case ESM4::REC_MISC: + readTypedRecord(params, reader); + return true; + case ESM4::REC_MOVT: + break; + case ESM4::REC_MSET: + readTypedRecord(params, reader); + return true; + case ESM4::REC_MSTT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_MUSC: + readTypedRecord(params, reader); + return true; + case ESM4::REC_MUST: + break; + case ESM4::REC_NAVI: + readTypedRecord(params, reader); + return true; + case ESM4::REC_NAVM: + readTypedRecord(params, reader); + return true; + case ESM4::REC_NOTE: + readTypedRecord(params, reader); + return true; + case ESM4::REC_NPC_: + readTypedRecord(params, reader); + return true; + case ESM4::REC_OTFT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_PACK: + readTypedRecord(params, reader); + return true; + case ESM4::REC_PERK: + break; + case ESM4::REC_PGRD: + readTypedRecord(params, reader); + return true; + case ESM4::REC_PGRE: + readTypedRecord(params, reader); + return true; + case ESM4::REC_PHZD: + break; + case ESM4::REC_PROJ: + break; + case ESM4::REC_PWAT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_QUST: + readTypedRecord(params, reader); + return true; + case ESM4::REC_RACE: + readTypedRecord(params, reader); + return true; + case ESM4::REC_REFR: + readTypedRecord(params, reader); + return true; + case ESM4::REC_REGN: + readTypedRecord(params, reader); + return true; + case ESM4::REC_RELA: + break; + case ESM4::REC_REVB: + break; + case ESM4::REC_RFCT: + break; + case ESM4::REC_ROAD: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SBSP: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SCEN: + break; + case ESM4::REC_SCOL: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SCPT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SCRL: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SGST: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SHOU: + break; + case ESM4::REC_SLGM: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SMBN: + break; + case ESM4::REC_SMEN: + break; + case ESM4::REC_SMQN: + break; + case ESM4::REC_SNCT: + break; + case ESM4::REC_SNDR: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SOPM: + break; + case ESM4::REC_SOUN: + readTypedRecord(params, reader); + return true; + case ESM4::REC_SPEL: + break; + case ESM4::REC_SPGD: + break; + case ESM4::REC_STAT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_TACT: + readTypedRecord(params, reader); + return true; + case ESM4::REC_TERM: + readTypedRecord(params, reader); + return true; + case ESM4::REC_TES4: + readTypedRecord(params, reader); + return true; + case ESM4::REC_TREE: + readTypedRecord(params, reader); + return true; + case ESM4::REC_TXST: + readTypedRecord(params, reader); + return true; + case ESM4::REC_VTYP: + break; + case ESM4::REC_WATR: + break; + case ESM4::REC_WEAP: + readTypedRecord(params, reader); + return true; + case ESM4::REC_WOOP: + break; + case ESM4::REC_WRLD: + readTypedRecord(params, reader); + return true; + case ESM4::REC_WTHR: + break; + } + + if (!params.mQuite) + std::cout << "\n Unsupported record: " << ESM::NAME(reader.hdr().record.typeId).toStringView() << '\n'; + return false; + } + + } + + int loadTes4(const Arguments& info, std::unique_ptr&& stream) + { + std::cout << "Loading TES4 file: " << info.filename << '\n'; + + try + { + const ToUTF8::StatelessUtf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding)); + ESM4::Reader reader(std::move(stream), info.filename, nullptr, &encoder, true); + const Params params(info); + + if (!params.mQuite) + { + std::cout << "Author: " << reader.getAuthor() << '\n' + << "Description: " << reader.getDesc() << '\n' + << "File format version: " << reader.esmVersion() << '\n'; + + if (const std::vector& masterData = reader.getGameFiles(); !masterData.empty()) + { + std::cout << "Masters:" << '\n'; + for (const auto& master : masterData) + std::cout << " " << master.name << ", " << master.size << " bytes\n"; + } + } + + auto visitorRec = [¶ms](ESM4::Reader& reader) { return readRecord(params, reader); }; + auto visitorGroup = [¶ms](ESM4::Reader& reader) { + if (params.mQuite) + return; + auto groupType = static_cast(reader.hdr().group.type); + std::cout << "\nGroup: " << toString(groupType) << " " + << ESM::NAME(reader.hdr().group.typeId).toStringView() << '\n'; + }; + ESM4::ReaderUtils::readAll(reader, visitorRec, visitorGroup); + } + catch (const std::exception& e) + { + std::cout << "\nERROR:\n\n " << e.what() << std::endl; + return -1; + } + + return 0; + } +} diff --git a/apps/esmtool/tes4.hpp b/apps/esmtool/tes4.hpp new file mode 100644 index 000000000..8149b2604 --- /dev/null +++ b/apps/esmtool/tes4.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESMTOOL_TES4_H +#define OPENMW_ESMTOOL_TES4_H + +#include +#include +#include + +namespace EsmTool +{ + struct Arguments; + + int loadTes4(const Arguments& info, std::unique_ptr&& stream); +} + +#endif diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index 0e742ff54..3928d88e8 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -5,7 +5,6 @@ set(ESSIMPORTER_FILES importnpcc.cpp importcrec.cpp importcellref.cpp - importacdt.cpp importinventory.cpp importklst.cpp importcntc.cpp @@ -36,15 +35,24 @@ openmw_add_executable(openmw-essimporter target_link_libraries(openmw-essimporter ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} components ) if (BUILD_WITH_CODE_COVERAGE) - add_definitions (--coverage) - target_link_libraries(openmw-essimporter gcov) + target_compile_options(openmw-essimporter PRIVATE --coverage) + target_link_libraries(openmw-essimporter gcov) endif() if (WIN32) - INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") + INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") endif(WIN32) + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw-essimporter PRIVATE + + + + + + ) +endif() diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp index 5c2bcc402..8342310cf 100644 --- a/apps/essimporter/convertacdt.cpp +++ b/apps/essimporter/convertacdt.cpp @@ -1,8 +1,8 @@ -#include #include #include +#include -#include +#include #include "convertacdt.hpp" @@ -18,36 +18,36 @@ namespace ESSImport return mwIndex; } - void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats) + void convertACDT(const ACDT& acdt, ESM::CreatureStats& cStats) { - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) { int writeIndex = translateDynamicIndex(i); cStats.mDynamic[writeIndex].mBase = acdt.mDynamic[i][1]; - cStats.mDynamic[writeIndex].mMod = acdt.mDynamic[i][1]; + cStats.mDynamic[writeIndex].mMod = 0.f; cStats.mDynamic[writeIndex].mCurrent = acdt.mDynamic[i][0]; } - for (int i=0; i<8; ++i) + for (int i = 0; i < 8; ++i) { - cStats.mAttributes[i].mBase = static_cast(acdt.mAttributes[i][1]); - cStats.mAttributes[i].mMod = static_cast(acdt.mAttributes[i][0]); - cStats.mAttributes[i].mCurrent = static_cast(acdt.mAttributes[i][0]); + cStats.mAttributes[i].mBase = acdt.mAttributes[i][1]; + cStats.mAttributes[i].mMod = 0.f; + cStats.mAttributes[i].mCurrent = acdt.mAttributes[i][0]; } cStats.mGoldPool = acdt.mGoldPool; cStats.mTalkedTo = (acdt.mFlags & TalkedToPlayer) != 0; cStats.mAttacked = (acdt.mFlags & Attacked) != 0; } - void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats) + void convertACSC(const ACSC& acsc, ESM::CreatureStats& cStats) { cStats.mDead = (acsc.mFlags & Dead) != 0; } - void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats) + void convertNpcData(const ActorData& actorData, ESM::NpcStats& npcStats) { - for (int i=0; i -#include -#include -#include +#include +#include +#include +#include #include "importacdt.hpp" @@ -14,13 +14,12 @@ namespace ESSImport // OpenMW uses Health,Magicka,Fatigue, MW uses Health,Fatigue,Magicka int translateDynamicIndex(int mwIndex); + void convertACDT(const ACDT& acdt, ESM::CreatureStats& cStats); + void convertACSC(const ACSC& acsc, ESM::CreatureStats& cStats); - void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats); - void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats); + void convertNpcData(const ActorData& actorData, ESM::NpcStats& npcStats); - void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats); - - void convertANIS (const ANIS& anis, ESM::AnimationState& state); + void convertANIS(const ANIS& anis, ESM::AnimationState& state); } #endif diff --git a/apps/essimporter/convertcntc.cpp b/apps/essimporter/convertcntc.cpp index 426ef4496..2b461ae3a 100644 --- a/apps/essimporter/convertcntc.cpp +++ b/apps/essimporter/convertcntc.cpp @@ -5,7 +5,7 @@ namespace ESSImport { - void convertCNTC(const CNTC &cntc, ESM::ContainerState &state) + void convertCNTC(const CNTC& cntc, ESM::ContainerState& state) { convertInventory(cntc.mInventory, state.mInventory); } diff --git a/apps/essimporter/convertcntc.hpp b/apps/essimporter/convertcntc.hpp index c299d87a1..2dc51949b 100644 --- a/apps/essimporter/convertcntc.hpp +++ b/apps/essimporter/convertcntc.hpp @@ -3,7 +3,7 @@ #include "importcntc.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/convertcrec.cpp b/apps/essimporter/convertcrec.cpp index 34e1c0070..f8233bcbf 100644 --- a/apps/essimporter/convertcrec.cpp +++ b/apps/essimporter/convertcrec.cpp @@ -5,7 +5,7 @@ namespace ESSImport { - void convertCREC(const CREC &crec, ESM::CreatureState &state) + void convertCREC(const CREC& crec, ESM::CreatureState& state) { convertInventory(crec.mInventory, state.mInventory); } diff --git a/apps/essimporter/convertcrec.hpp b/apps/essimporter/convertcrec.hpp index 7d317f03e..fa2e7e807 100644 --- a/apps/essimporter/convertcrec.hpp +++ b/apps/essimporter/convertcrec.hpp @@ -3,7 +3,7 @@ #include "importcrec.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index e0756602d..4cf528c83 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -1,17 +1,17 @@ #include "converter.hpp" -#include #include +#include #include -#include -#include +#include +#include #include -#include "convertcrec.hpp" #include "convertcntc.hpp" +#include "convertcrec.hpp" #include "convertscri.hpp" namespace @@ -19,7 +19,7 @@ namespace void convertImage(char* data, int size, int width, int height, GLenum pf, const std::string& out) { - osg::ref_ptr image (new osg::Image); + osg::ref_ptr image(new osg::Image); image->allocateImage(width, height, 1, pf, GL_UNSIGNED_BYTE); memcpy(image->data(), data, size); image->flipVertical(); @@ -27,7 +27,6 @@ namespace osgDB::writeImageFile(*image, out); } - void convertCellRef(const ESSImport::CellRef& cellref, ESM::ObjectState& objstate) { objstate.mEnabled = cellref.mEnabled; @@ -35,11 +34,11 @@ namespace objstate.mRef.mRefNum = cellref.mRefNum; if (cellref.mDeleted) objstate.mCount = 0; - convertSCRI(cellref.mSCRI, objstate.mLocals); + convertSCRI(cellref.mActorData.mSCRI, objstate.mLocals); objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); - if (cellref.mHasANIS) - convertANIS(cellref.mANIS, objstate.mAnimationState); + if (cellref.mActorData.mHasANIS) + convertANIS(cellref.mActorData.mANIS, objstate.mAnimationState); } bool isIndexedRefId(const std::string& indexedRefId) @@ -51,28 +50,28 @@ namespace return false; // entirely numeric refid, this is a reference to // a dynamically created record e.g. player-enchanted weapon - std::string index = indexedRefId.substr(indexedRefId.size()-8); + std::string index = indexedRefId.substr(indexedRefId.size() - 8); return index.find_first_not_of("0123456789ABCDEF") == std::string::npos; } void splitIndexedRefId(const std::string& indexedRefId, int& refIndex, std::string& refId) { std::stringstream stream; - stream << std::hex << indexedRefId.substr(indexedRefId.size()-8,8); + stream << std::hex << indexedRefId.substr(indexedRefId.size() - 8, 8); stream >> refIndex; - refId = indexedRefId.substr(0,indexedRefId.size()-8); + refId = indexedRefId.substr(0, indexedRefId.size() - 8); } int convertActorId(const std::string& indexedRefId, ESSImport::Context& context) { if (isIndexedRefId(indexedRefId)) { - int refIndex; + int refIndex = 0; std::string refId; splitIndexedRefId(indexedRefId, refIndex, refId); - auto it = context.mActorIdMap.find(std::make_pair(refIndex, refId)); + auto it = context.mActorIdMap.find(std::make_pair(refIndex, ESM::RefId::stringRefId(refId))); if (it == context.mActorIdMap.end()) return -1; return it->second; @@ -89,14 +88,13 @@ namespace namespace ESSImport { - struct MAPH { unsigned int size; unsigned int value; }; - void ConvertFMAP::read(ESM::ESMReader &esm) + void ConvertFMAP::read(ESM::ESMReader& esm) { MAPH maph; esm.getHNT(maph, "MAPH"); @@ -104,63 +102,63 @@ namespace ESSImport esm.getSubNameIs("MAPD"); esm.getSubHeader(); data.resize(esm.getSubSize()); - esm.getExact(&data[0], data.size()); + esm.getExact(data.data(), data.size()); mGlobalMapImage = new osg::Image; mGlobalMapImage->allocateImage(maph.size, maph.size, 1, GL_RGB, GL_UNSIGNED_BYTE); - memcpy(mGlobalMapImage->data(), &data[0], data.size()); + memcpy(mGlobalMapImage->data(), data.data(), data.size()); // to match openmw size // FIXME: filtering? - mGlobalMapImage->scaleImage(maph.size*2, maph.size*2, 1, GL_UNSIGNED_BYTE); + mGlobalMapImage->scaleImage(maph.size * 2, maph.size * 2, 1, GL_UNSIGNED_BYTE); } - void ConvertFMAP::write(ESM::ESMWriter &esm) + void ConvertFMAP::write(ESM::ESMWriter& esm) { int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly - // with the 512x512 map the game has by default - int cellSize = mGlobalMapImage->s()/numcells; + // with the 512x512 map the game has by default + int cellSize = mGlobalMapImage->s() / numcells; // Note the upper left corner of the (0,0) cell should be at (width/2, height/2) - mContext->mGlobalMapState.mBounds.mMinX = -numcells/2; - mContext->mGlobalMapState.mBounds.mMaxX = (numcells-1)/2; - mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2; - mContext->mGlobalMapState.mBounds.mMaxY = numcells/2; + mContext->mGlobalMapState.mBounds.mMinX = -numcells / 2; + mContext->mGlobalMapState.mBounds.mMaxX = (numcells - 1) / 2; + mContext->mGlobalMapState.mBounds.mMinY = -(numcells - 1) / 2; + mContext->mGlobalMapState.mBounds.mMaxY = numcells / 2; - osg::ref_ptr image2 (new osg::Image); - int width = cellSize*numcells; - int height = cellSize*numcells; + osg::ref_ptr image2(new osg::Image); + int width = cellSize * numcells; + int height = cellSize * numcells; std::vector data; - data.resize(width*height*4, 0); + data.resize(width * height * 4, 0); image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); - memcpy(image2->data(), &data[0], data.size()); + memcpy(image2->data(), data.data(), data.size()); - for (const auto & exploredCell : mContext->mExploredCells) + for (const auto& exploredCell : mContext->mExploredCells) { if (exploredCell.first > mContext->mGlobalMapState.mBounds.mMaxX - || exploredCell.first < mContext->mGlobalMapState.mBounds.mMinX - || exploredCell.second > mContext->mGlobalMapState.mBounds.mMaxY - || exploredCell.second < mContext->mGlobalMapState.mBounds.mMinY) + || exploredCell.first < mContext->mGlobalMapState.mBounds.mMinX + || exploredCell.second > mContext->mGlobalMapState.mBounds.mMaxY + || exploredCell.second < mContext->mGlobalMapState.mBounds.mMinY) { // out of bounds, I think this could happen, since the original engine had a fixed-size map continue; } - int imageLeftSrc = mGlobalMapImage->s()/2; - int imageTopSrc = mGlobalMapImage->t()/2; + int imageLeftSrc = mGlobalMapImage->s() / 2; + int imageTopSrc = mGlobalMapImage->t() / 2; imageLeftSrc += exploredCell.first * cellSize; imageTopSrc -= exploredCell.second * cellSize; - int imageLeftDst = width/2; - int imageTopDst = height/2; + int imageLeftDst = width / 2; + int imageTopDst = height / 2; imageLeftDst += exploredCell.first * cellSize; imageTopDst -= exploredCell.second * cellSize; - for (int x=0; xdata(imageLeftSrc+x, imageTopSrc+y, 0); - *(unsigned int*)image2->data(imageLeftDst+x, imageTopDst+y, 0) = col; + unsigned int col = *(unsigned int*)mGlobalMapImage->data(imageLeftSrc + x, imageTopSrc + y, 0); + *(unsigned int*)image2->data(imageLeftDst + x, imageTopDst + y, 0) = col; } } @@ -177,7 +175,8 @@ namespace ESSImport osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream); if (!result.success()) { - std::cerr << "Error: can't write global map image: " << result.message() << " code " << result.status() << std::endl; + std::cerr << "Error: can't write global map image: " << result.message() << " code " << result.status() + << std::endl; return; } @@ -189,7 +188,7 @@ namespace ESSImport esm.endRecord(ESM::REC_GMAP); } - void ConvertCell::read(ESM::ESMReader &esm) + void ConvertCell::read(ESM::ESMReader& esm) { ESM::Cell cell; bool isDeleted = false; @@ -203,9 +202,9 @@ namespace ESSImport } // note if the player is in a nameless exterior cell, we will assign the cellId later based on player position - if (cell.mName == mContext->mPlayerCellName) + if (Misc::StringUtils::ciEqual(cell.mName, mContext->mPlayerCellName)) { - mContext->mPlayer.mCellId = cell.getCellId(); + mContext->mPlayer.mCellId = cell.mId; } Cell newcell; @@ -234,15 +233,15 @@ namespace ESSImport esm.getExact(nam8, 32); - newcell.mFogOfWar.reserve(16*16); - for (int x=0; x<16; ++x) + newcell.mFogOfWar.reserve(16 * 16); + for (int x = 0; x < 16; ++x) { - for (int y=0; y<16; ++y) + for (int y = 0; y < 16; ++y) { - size_t pos = x*16+y; - size_t bytepos = pos/8; - assert(bytepos<32); - int bit = pos%8; + size_t pos = x * 16 + y; + size_t bytepos = pos / 8; + assert(bytepos < 32); + int bit = pos % 8; newcell.mFogOfWar.push_back(((nam8[bytepos] >> bit) & (0x1)) ? 0xffffffff : 0x000000ff); } } @@ -252,7 +251,8 @@ namespace ESSImport std::ostringstream filename; filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga"; - convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, GL_RGBA, filename.str()); + convertImage( + (char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size() * 4, 16, 16, GL_RGBA, filename.str()); } } @@ -268,17 +268,17 @@ namespace ESSImport } std::vector cellrefs; - while (esm.hasMoreSubs() && esm.isNextSub("FRMR")) + while (esm.hasMoreSubs() && esm.peekNextSub("FRMR")) { CellRef ref; - ref.load (esm); + ref.load(esm); cellrefs.push_back(ref); } while (esm.isNextSub("MPCD")) { float notepos[3]; - esm.getHT(notepos, 3*sizeof(float)); + esm.getHTSized<3 * sizeof(float)>(notepos); // Markers seem to be arranged in a 32*32 grid, notepos has grid-indices. // This seems to be the reason markers can't be placed everywhere in interior cells, @@ -301,35 +301,37 @@ namespace ESSImport marker.mWorldX = notepos[0]; marker.mWorldY = notepos[1]; marker.mNote = note; - marker.mCell = cell.getCellId(); + marker.mCell = cell.mId; mMarkers.push_back(marker); } newcell.mRefs = cellrefs; - if (cell.isExterior()) mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; else mIntCells[cell.mName] = newcell; } - void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) + void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm) { ESM::Cell esmcell = cell.mCell; esm.startRecord(ESM::REC_CSTA); ESM::CellState csta; csta.mHasFogOfWar = 0; - csta.mId = esmcell.getCellId(); - csta.mId.save(esm); + csta.mLastRespawn.mDay = 0; + csta.mLastRespawn.mHour = 0; + csta.mId = esmcell.mId; + csta.mIsInterior = !esmcell.isExterior(); + esm.writeCellId(csta.mId); // TODO csta.mLastRespawn; // shouldn't be needed if we respawn on global schedule like in original MW csta.mWaterLevel = esmcell.mWater; csta.save(esm); - for (const auto & cellref : cell.mRefs) + for (const auto& cellref : cell.mRefs) { - ESM::CellRef out (cellref); + ESM::CellRef out(cellref); // TODO: use mContext->mCreatures/mNpcs @@ -337,88 +339,90 @@ namespace ESSImport { // non-indexed RefNum, i.e. no CREC/NPCC/CNTC record associated with it // this could be any type of object really (even creatures/npcs too) - out.mRefID = cellref.mIndexedRefId; - std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); + out.mRefID = ESM::RefId::stringRefId(cellref.mIndexedRefId); ESM::ObjectState objstate; objstate.blank(); objstate.mRef = out; - objstate.mRef.mRefID = idLower; + objstate.mRef.mRefID = out.mRefID; objstate.mHasCustomState = false; convertCellRef(cellref, objstate); - esm.writeHNT ("OBJE", 0); + esm.writeHNT("OBJE", 0); objstate.save(esm); continue; } else { - int refIndex; - splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID); + int refIndex = 0; + std::string outStringId; + splitIndexedRefId(cellref.mIndexedRefId, refIndex, outStringId); + out.mRefID = ESM::RefId::stringRefId(outStringId); - std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); - - std::map, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find( - std::make_pair(refIndex, out.mRefID)); + auto npccIt = mContext->mNpcChanges.find(std::make_pair(refIndex, out.mRefID)); if (npccIt != mContext->mNpcChanges.end()) { ESM::NpcState objstate; objstate.blank(); objstate.mRef = out; - objstate.mRef.mRefID = idLower; + objstate.mRef.mRefID = out.mRefID; // TODO: need more micromanagement here so we don't overwrite values // from the ESM with default values - if (cellref.mHasACDT) - convertACDT(cellref.mACDT, objstate.mCreatureStats); - if (cellref.mHasACSC) - convertACSC(cellref.mACSC, objstate.mCreatureStats); - convertNpcData(cellref, objstate.mNpcStats); + if (cellref.mActorData.mHasACDT) + convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats); + else + objstate.mCreatureStats.mMissingACDT = true; + if (cellref.mActorData.mHasACSC) + convertACSC(cellref.mActorData.mACSC, objstate.mCreatureStats); + convertNpcData(cellref.mActorData, objstate.mNpcStats); convertNPCC(npccIt->second, objstate); convertCellRef(cellref, objstate); objstate.mCreatureStats.mActorId = mContext->generateActorId(); - mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); + mContext->mActorIdMap.insert( + std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); - esm.writeHNT ("OBJE", ESM::REC_NPC_); + esm.writeHNT("OBJE", ESM::REC_NPC_); objstate.save(esm); continue; } - std::map, CNTC>::const_iterator cntcIt = mContext->mContainerChanges.find( - std::make_pair(refIndex, out.mRefID)); + auto cntcIt = mContext->mContainerChanges.find(std::make_pair(refIndex, out.mRefID)); if (cntcIt != mContext->mContainerChanges.end()) { ESM::ContainerState objstate; objstate.blank(); objstate.mRef = out; - objstate.mRef.mRefID = idLower; + objstate.mRef.mRefID = out.mRefID; convertCNTC(cntcIt->second, objstate); convertCellRef(cellref, objstate); - esm.writeHNT ("OBJE", ESM::REC_CONT); + esm.writeHNT("OBJE", ESM::REC_CONT); objstate.save(esm); continue; } - std::map, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find( - std::make_pair(refIndex, out.mRefID)); + auto crecIt = mContext->mCreatureChanges.find(std::make_pair(refIndex, out.mRefID)); if (crecIt != mContext->mCreatureChanges.end()) { ESM::CreatureState objstate; objstate.blank(); objstate.mRef = out; - objstate.mRef.mRefID = idLower; + objstate.mRef.mRefID = out.mRefID; // TODO: need more micromanagement here so we don't overwrite values // from the ESM with default values - if (cellref.mHasACDT) - convertACDT(cellref.mACDT, objstate.mCreatureStats); - if (cellref.mHasACSC) - convertACSC(cellref.mACSC, objstate.mCreatureStats); + if (cellref.mActorData.mHasACDT) + convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats); + else + objstate.mCreatureStats.mMissingACDT = true; + if (cellref.mActorData.mHasACSC) + convertACSC(cellref.mActorData.mACSC, objstate.mCreatureStats); convertCREC(crecIt->second, objstate); convertCellRef(cellref, objstate); objstate.mCreatureStats.mActorId = mContext->generateActorId(); - mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); + mContext->mActorIdMap.insert( + std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId)); - esm.writeHNT ("OBJE", ESM::REC_CREA); + esm.writeHNT("OBJE", ESM::REC_CREA); objstate.save(esm); continue; } @@ -432,15 +436,15 @@ namespace ESSImport esm.endRecord(ESM::REC_CSTA); } - void ConvertCell::write(ESM::ESMWriter &esm) + void ConvertCell::write(ESM::ESMWriter& esm) { - for (const auto & cell : mIntCells) + for (const auto& cell : mIntCells) writeCell(cell.second, esm); - for (const auto & cell : mExtCells) + for (const auto& cell : mExtCells) writeCell(cell.second, esm); - for (const auto & marker : mMarkers) + for (const auto& marker : mMarkers) { esm.startRecord(ESM::REC_MARK); marker.save(esm); @@ -462,7 +466,7 @@ namespace ESSImport ESM::ProjectileState out; convertBaseState(out, pnam); - out.mBowId = pnam.mBowId.toString(); + out.mBowId = ESM::RefId::stringRefId(pnam.mBowId.toString()); out.mVelocity = pnam.mVelocity; out.mAttackStrength = pnam.mAttackStrength; @@ -476,16 +480,18 @@ namespace ESSImport convertBaseState(out, pnam); auto it = std::find_if(mContext->mActiveSpells.begin(), mContext->mActiveSpells.end(), - [&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; }); + [&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; }); if (it == mContext->mActiveSpells.end()) { - std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (invalid spell link)" << std::endl; + std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() + << "\" (invalid spell link)" << std::endl; continue; } - out.mSpellId = it->mSPDT.mId.toString(); + out.mSpellId = ESM::RefId::stringRefId(it->mSPDT.mId.toString()); out.mSpeed = pnam.mSpeed * 0.001f; // not sure where this factor comes from + out.mSlot = 0; esm.startRecord(ESM::REC_MPRJ); out.save(esm); @@ -496,11 +502,11 @@ namespace ESSImport void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam) { - base.mId = pnam.mArrowId.toString(); + base.mId = ESM::RefId::stringRefId(pnam.mArrowId.toString()); base.mPosition = pnam.mPosition; osg::Quat orient; - orient.makeRotate(osg::Vec3f(0,1,0), pnam.mVelocity); + orient.makeRotate(osg::Vec3f(0, 1, 0), pnam.mVelocity); base.mOrientation = orient; base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext); diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 9a1923c2b..93b4e2c81 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -6,619 +6,622 @@ #include #include -#include -#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include -#include "importcrec.hpp" #include "importcntc.hpp" +#include "importcrec.hpp" -#include "importercontext.hpp" #include "importcellref.hpp" -#include "importklst.hpp" +#include "importdial.hpp" +#include "importercontext.hpp" #include "importgame.hpp" #include "importinfo.hpp" -#include "importdial.hpp" -#include "importques.hpp" #include "importjour.hpp" -#include "importscpt.hpp" +#include "importklst.hpp" #include "importproj.h" +#include "importques.hpp" +#include "importscpt.hpp" #include "importsplm.h" #include "convertacdt.hpp" #include "convertnpcc.hpp" -#include "convertscpt.hpp" #include "convertplayer.hpp" +#include "convertscpt.hpp" +#include namespace ESSImport { -class Converter -{ -public: - /// @return the order for writing this converter's records to the output file, in relation to other converters - virtual int getStage() { return 1; } - - virtual ~Converter() {} - - void setContext(Context& context) { mContext = &context; } - - /// @note The load method of ESM records accept the deleted flag as a parameter. - /// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored. - virtual void read(ESM::ESMReader& esm) + class Converter { - } + public: + /// @return the order for writing this converter's records to the output file, in relation to other converters + virtual int getStage() { return 1; } - /// Called after the input file has been read in completely, which may be necessary - /// if the conversion process relies on information in other records - virtual void write(ESM::ESMWriter& esm) - { + virtual ~Converter() = default; - } + void setContext(Context& context) { mContext = &context; } -protected: - Context* mContext; -}; + /// @note The load method of ESM records accept the deleted flag as a parameter. + /// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored. + virtual void read(ESM::ESMReader& esm) {} -/// Default converter: simply reads the record and writes it unmodified to the output -template -class DefaultConverter : public Converter -{ -public: - int getStage() override { return 0; } + /// Called after the input file has been read in completely, which may be necessary + /// if the conversion process relies on information in other records + virtual void write(ESM::ESMWriter& esm) {} - void read(ESM::ESMReader& esm) override - { - T record; - bool isDeleted = false; - - record.load(esm, isDeleted); - mRecords[record.mId] = record; - } - - void write(ESM::ESMWriter& esm) override - { - for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) - { - esm.startRecord(T::sRecordId); - it->second.save(esm); - esm.endRecord(T::sRecordId); - } - } - -protected: - std::map mRecords; -}; - -class ConvertNPC : public Converter -{ -public: - void read(ESM::ESMReader &esm) override - { - ESM::NPC npc; - bool isDeleted = false; - - npc.load(esm, isDeleted); - if (npc.mId != "player") - { - // Handles changes to the NPC struct, but since there is no index here - // it will apply to ALL instances of the class. seems to be the reason for the - // "feature" in MW where changing AI settings of one guard will change it for all guards of that refID. - mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc; - } - else - { - mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel; - mContext->mPlayerBase = npc; - ESM::SpellState::SpellParams empty; - // FIXME: player start spells and birthsign spells aren't listed here, - // need to fix openmw to account for this - for (const auto & spell : npc.mSpells.mList) - mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty; - - // Clear the list now that we've written it, this prevents issues cropping up with - // ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal. - mContext->mPlayerBase.mSpells.mList.clear(); - - // Same with inventory. Actually it's strange this would contain something, since there's already an - // inventory list in NPCC. There seems to be a fair amount of redundancy in this format. - mContext->mPlayerBase.mInventory.mList.clear(); - } - } -}; - -class ConvertCREA : public Converter -{ -public: - void read(ESM::ESMReader &esm) override - { - // See comment in ConvertNPC - ESM::Creature creature; - bool isDeleted = false; - - creature.load(esm, isDeleted); - mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature; - } -}; - -// Do we need ConvertCONT? -// I've seen a CONT record in a certain save file, but the container contents in it -// were identical to a corresponding CNTC record. See previous comment about redundancy... - -class ConvertGlobal : public DefaultConverter -{ -public: - void read(ESM::ESMReader &esm) override - { - ESM::Global global; - bool isDeleted = false; - - global.load(esm, isDeleted); - if (Misc::StringUtils::ciEqual(global.mId, "gamehour")) - mContext->mHour = global.mValue.getFloat(); - if (Misc::StringUtils::ciEqual(global.mId, "day")) - mContext->mDay = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(global.mId, "month")) - mContext->mMonth = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(global.mId, "year")) - mContext->mYear = global.mValue.getInteger(); - mRecords[global.mId] = global; - } -}; - -class ConvertClass : public DefaultConverter -{ -public: - void read(ESM::ESMReader &esm) override - { - ESM::Class class_; - bool isDeleted = false; - - class_.load(esm, isDeleted); - if (class_.mId == "NEWCLASSID_CHARGEN") - mContext->mCustomPlayerClassName = class_.mName; - - mRecords[class_.mId] = class_; - } -}; - -class ConvertBook : public DefaultConverter -{ -public: - void read(ESM::ESMReader &esm) override - { - ESM::Book book; - bool isDeleted = false; - - book.load(esm, isDeleted); - if (book.mData.mSkillId == -1) - mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); - - mRecords[book.mId] = book; - } -}; - -class ConvertNPCC : public Converter -{ -public: - void read(ESM::ESMReader &esm) override - { - std::string id = esm.getHNString("NAME"); - NPCC npcc; - npcc.load(esm); - if (id == "PlayerSaveGame") - { - convertNPCC(npcc, mContext->mPlayer.mObject); - } - else - { - int index = npcc.mNPDT.mIndex; - mContext->mNpcChanges.insert(std::make_pair(std::make_pair(index,id), npcc)); - } - } -}; - -class ConvertREFR : public Converter -{ -public: - void read(ESM::ESMReader &esm) override - { - REFR refr; - refr.load(esm); - assert(refr.mRefID == "PlayerSaveGame"); - mContext->mPlayer.mObject.mPosition = refr.mPos; - - ESM::CreatureStats& cStats = mContext->mPlayer.mObject.mCreatureStats; - convertACDT(refr.mActorData.mACDT, cStats); - - ESM::NpcStats& npcStats = mContext->mPlayer.mObject.mNpcStats; - convertNpcData(refr.mActorData, npcStats); - - mSelectedSpell = refr.mActorData.mSelectedSpell; - if (!refr.mActorData.mSelectedEnchantItem.empty()) - { - ESM::InventoryState& invState = mContext->mPlayer.mObject.mInventory; - - for (unsigned int i=0; imPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam, mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState); - } - void write(ESM::ESMWriter &esm) override - { - esm.startRecord(ESM::REC_ENAB); - esm.writeHNT("TELE", mTeleportingEnabled); - esm.writeHNT("LEVT", mLevitationEnabled); - esm.endRecord(ESM::REC_ENAB); - - esm.startRecord(ESM::REC_CAM_); - esm.writeHNT("FIRS", mFirstPersonCam); - esm.endRecord(ESM::REC_CAM_); - } -private: - bool mFirstPersonCam; - bool mTeleportingEnabled; - bool mLevitationEnabled; -}; - -class ConvertCNTC : public Converter -{ - void read(ESM::ESMReader &esm) override - { - std::string id = esm.getHNString("NAME"); - CNTC cntc; - cntc.load(esm); - mContext->mContainerChanges.insert(std::make_pair(std::make_pair(cntc.mIndex,id), cntc)); - } -}; - -class ConvertCREC : public Converter -{ -public: - void read(ESM::ESMReader &esm) override - { - std::string id = esm.getHNString("NAME"); - CREC crec; - crec.load(esm); - mContext->mCreatureChanges.insert(std::make_pair(std::make_pair(crec.mIndex,id), crec)); - } -}; - -class ConvertFMAP : public Converter -{ -public: - void read(ESM::ESMReader &esm) override; - void write(ESM::ESMWriter &esm) override; - -private: - osg::ref_ptr mGlobalMapImage; -}; - -class ConvertCell : public Converter -{ -public: - void read(ESM::ESMReader& esm) override; - void write(ESM::ESMWriter& esm) override; - -private: - struct Cell - { - ESM::Cell mCell; - std::vector mRefs; - std::vector mFogOfWar; + protected: + Context* mContext; }; - std::map mIntCells; - std::map, Cell> mExtCells; - - std::vector mMarkers; - - void writeCell(const Cell& cell, ESM::ESMWriter &esm); -}; - -class ConvertKLST : public Converter -{ -public: - void read(ESM::ESMReader& esm) override + /// Default converter: simply reads the record and writes it unmodified to the output + template + class DefaultConverter : public Converter { - KLST klst; - klst.load(esm); - mKillCounter = klst.mKillCounter; + public: + int getStage() override { return 0; } - mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills; - } - - void write(ESM::ESMWriter &esm) override - { - esm.startRecord(ESM::REC_DCOU); - for (std::map::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it) + void read(ESM::ESMReader& esm) override { - esm.writeHNString("ID__", it->first); - esm.writeHNT ("COUN", it->second); + T record; + bool isDeleted = false; + + record.load(esm, isDeleted); + mRecords[record.mId] = record; } - esm.endRecord(ESM::REC_DCOU); - } -private: - std::map mKillCounter; -}; - -class ConvertFACT : public Converter -{ -public: - void read(ESM::ESMReader& esm) override - { - ESM::Faction faction; - bool isDeleted = false; - - faction.load(esm, isDeleted); - std::string id = Misc::StringUtils::lowerCase(faction.mId); - - for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) + void write(ESM::ESMWriter& esm) override { - std::string faction2 = Misc::StringUtils::lowerCase(it->first); - mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second)); - } - } -}; - -/// Stolen items -class ConvertSTLN : public Converter -{ -public: - void read(ESM::ESMReader &esm) override - { - std::string itemid = esm.getHNString("NAME"); - Misc::StringUtils::lowerCaseInPlace(itemid); - - while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) - { - if (esm.retSubName().toString() == "FNAM") + for (auto it = mRecords.begin(); it != mRecords.end(); ++it) { - std::string factionid = esm.getHString(); - mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(factionid), true)); + esm.startRecord(T::sRecordId); + it->second.save(esm); + esm.endRecord(T::sRecordId); + } + } + + protected: + std::map mRecords; + }; + + class ConvertNPC : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + ESM::NPC npc; + bool isDeleted = false; + + npc.load(esm, isDeleted); + if (npc.mId != "player") + { + // Handles changes to the NPC struct, but since there is no index here + // it will apply to ALL instances of the class. seems to be the reason for the + // "feature" in MW where changing AI settings of one guard will change it for all guards of that refID. + mContext->mNpcs[npc.mId] = npc; } else { - std::string ownerid = esm.getHString(); - mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); + mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel; + mContext->mPlayerBase = npc; + // FIXME: player start spells and birthsign spells aren't listed here, + // need to fix openmw to account for this + mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList; + + // Clear the list now that we've written it, this prevents issues cropping up with + // ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal. + mContext->mPlayerBase.mSpells.mList.clear(); + + // Same with inventory. Actually it's strange this would contain something, since there's already an + // inventory list in NPCC. There seems to be a fair amount of redundancy in this format. + mContext->mPlayerBase.mInventory.mList.clear(); } } - } - void write(ESM::ESMWriter &esm) override + }; + + class ConvertCREA : public Converter { - ESM::StolenItems items; - for (std::map >::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it) + public: + void read(ESM::ESMReader& esm) override { - std::map, int> owners; - for (const auto & ownerIt : it->second) + // See comment in ConvertNPC + ESM::Creature creature; + bool isDeleted = false; + + creature.load(esm, isDeleted); + mContext->mCreatures[creature.mId] = creature; + } + }; + + // Do we need ConvertCONT? + // I've seen a CONT record in a certain save file, but the container contents in it + // were identical to a corresponding CNTC record. See previous comment about redundancy... + + class ConvertGlobal : public DefaultConverter + { + public: + void read(ESM::ESMReader& esm) override + { + ESM::Global global; + bool isDeleted = false; + + global.load(esm, isDeleted); + if (global.mId == "gamehour") + mContext->mHour = global.mValue.getFloat(); + if (global.mId == "day") + mContext->mDay = global.mValue.getInteger(); + if (global.mId == "month") + mContext->mMonth = global.mValue.getInteger(); + if (global.mId == "year") + mContext->mYear = global.mValue.getInteger(); + mRecords[global.mId] = global; + } + }; + + class ConvertClass : public DefaultConverter + { + public: + void read(ESM::ESMReader& esm) override + { + ESM::Class class_; + bool isDeleted = false; + + class_.load(esm, isDeleted); + if (class_.mId == "NEWCLASSID_CHARGEN") + mContext->mCustomPlayerClassName = class_.mName; + + mRecords[class_.mId] = class_; + } + }; + + class ConvertBook : public DefaultConverter + { + public: + void read(ESM::ESMReader& esm) override + { + ESM::Book book; + bool isDeleted = false; + + book.load(esm, isDeleted); + if (book.mData.mSkillId == -1) + mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(book.mId); + + mRecords[book.mId] = book; + } + }; + + class ConvertNPCC : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + auto id = esm.getHNRefId("NAME"); + NPCC npcc; + npcc.load(esm); + if (id == "PlayerSaveGame") { - owners.insert(std::make_pair(std::make_pair(ownerIt.first, ownerIt.second) - // Since OpenMW doesn't suffer from the owner contamination bug, - // it needs a count argument. But for legacy savegames, we don't know - // this count, so must assume all items of that ID are stolen, - // like vanilla MW did. - ,std::numeric_limits::max())); + convertNPCC(npcc, mContext->mPlayer.mObject); + } + else + { + int index = npcc.mNPDT.mIndex; + mContext->mNpcChanges.insert(std::make_pair(std::make_pair(index, id), npcc)); + } + } + }; + + class ConvertREFR : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + CellRef refr; + refr.load(esm); + assert(refr.mIndexedRefId == "PlayerSaveGame"); + mContext->mPlayer.mObject.mPosition = refr.mPos; + + ESM::CreatureStats& cStats = mContext->mPlayer.mObject.mCreatureStats; + convertACDT(refr.mActorData.mACDT, cStats); + + ESM::NpcStats& npcStats = mContext->mPlayer.mObject.mNpcStats; + convertNpcData(refr.mActorData, npcStats); + + mSelectedSpell = refr.mActorData.mSelectedSpell; + if (!refr.mActorData.mSelectedEnchantItem.empty()) + { + ESM::InventoryState& invState = mContext->mPlayer.mObject.mInventory; + + for (size_t i = 0; i < invState.mItems.size(); ++i) + { + // FIXME: in case of conflict (multiple items with this refID) use the already equipped one? + if (invState.mItems[i].mRef.mRefID == ESM::RefId::stringRefId(refr.mActorData.mSelectedEnchantItem)) + invState.mSelectedEnchantItem = i; + } + } + } + void write(ESM::ESMWriter& esm) override + { + esm.startRecord(ESM::REC_ASPL); + esm.writeHNString("ID__", mSelectedSpell); + esm.endRecord(ESM::REC_ASPL); + } + + private: + std::string mSelectedSpell; + }; + + class ConvertPCDT : public Converter + { + public: + ConvertPCDT() + : mFirstPersonCam(true) + , mTeleportingEnabled(true) + , mLevitationEnabled(true) + { + } + + void read(ESM::ESMReader& esm) override + { + PCDT pcdt; + pcdt.load(esm); + + convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam, + mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState); + } + void write(ESM::ESMWriter& esm) override + { + esm.startRecord(ESM::REC_ENAB); + esm.writeHNT("TELE", mTeleportingEnabled); + esm.writeHNT("LEVT", mLevitationEnabled); + esm.endRecord(ESM::REC_ENAB); + + esm.startRecord(ESM::REC_CAM_); + esm.writeHNT("FIRS", mFirstPersonCam); + esm.endRecord(ESM::REC_CAM_); + } + + private: + bool mFirstPersonCam; + bool mTeleportingEnabled; + bool mLevitationEnabled; + }; + + class ConvertCNTC : public Converter + { + void read(ESM::ESMReader& esm) override + { + auto id = esm.getHNRefId("NAME"); + CNTC cntc; + cntc.load(esm); + mContext->mContainerChanges.insert(std::make_pair(std::make_pair(cntc.mIndex, id), cntc)); + } + }; + + class ConvertCREC : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + auto id = esm.getHNRefId("NAME"); + CREC crec; + crec.load(esm); + mContext->mCreatureChanges.insert(std::make_pair(std::make_pair(crec.mIndex, id), crec)); + } + }; + + class ConvertFMAP : public Converter + { + public: + void read(ESM::ESMReader& esm) override; + void write(ESM::ESMWriter& esm) override; + + private: + osg::ref_ptr mGlobalMapImage; + }; + + class ConvertCell : public Converter + { + public: + void read(ESM::ESMReader& esm) override; + void write(ESM::ESMWriter& esm) override; + + private: + struct Cell + { + ESM::Cell mCell; + std::vector mRefs; + std::vector mFogOfWar; + }; + + std::map mIntCells; + std::map, Cell> mExtCells; + + std::vector mMarkers; + + void writeCell(const Cell& cell, ESM::ESMWriter& esm); + }; + + class ConvertKLST : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + KLST klst; + klst.load(esm); + mKillCounter = klst.mKillCounter; + + mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills; + } + + void write(ESM::ESMWriter& esm) override + { + esm.startRecord(ESM::REC_DCOU); + for (auto it = mKillCounter.begin(); it != mKillCounter.end(); ++it) + { + esm.writeHNString("ID__", it->first); + esm.writeHNT("COUN", it->second); + } + esm.endRecord(ESM::REC_DCOU); + } + + private: + std::map mKillCounter; + }; + + class ConvertFACT : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + ESM::Faction faction; + bool isDeleted = false; + + faction.load(esm, isDeleted); + const auto& id = faction.mId; + + for (auto it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) + { + const auto& faction2 = it->first; + mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second)); + } + } + }; + + /// Stolen items + class ConvertSTLN : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + std::string itemid = esm.getHNString("NAME"); + Misc::StringUtils::lowerCaseInPlace(itemid); + + while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) + { + if (esm.retSubName().toString() == "FNAM") + { + std::string factionid = esm.getHString(); + mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(factionid), true)); + } + else + { + std::string ownerid = esm.getHString(); + mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); + } + } + } + void write(ESM::ESMWriter& esm) override + { + ESM::StolenItems items; + for (auto it = mStolenItems.begin(); it != mStolenItems.end(); ++it) + { + std::map, int> owners; + for (const auto& ownerIt : it->second) + { + owners.insert(std::make_pair(std::make_pair(ESM::RefId::stringRefId(ownerIt.first), ownerIt.second) + // Since OpenMW doesn't suffer from the owner contamination bug, + // it needs a count argument. But for legacy savegames, we don't know + // this count, so must assume all items of that ID are stolen, + // like vanilla MW did. + , + std::numeric_limits::max())); + } + + items.mStolenItems.insert(std::make_pair(ESM::RefId::stringRefId(it->first), owners)); } - items.mStolenItems.insert(std::make_pair(it->first, owners)); + esm.startRecord(ESM::REC_STLN); + items.write(esm); + esm.endRecord(ESM::REC_STLN); } - esm.startRecord(ESM::REC_STLN); - items.write(esm); - esm.endRecord(ESM::REC_STLN); - } + private: + typedef std::pair Owner; // -private: - typedef std::pair Owner; // + std::map> mStolenItems; + }; - std::map > mStolenItems; -}; - -/// Seen responses for a dialogue topic? -/// Each DIAL record is followed by a number of INFO records, I believe, just like in ESMs -/// Dialogue conversion problems: -/// - Journal is stored in one continuous HTML markup rather than each entry separately with associated info ID. -/// - Seen dialogue responses only store the INFO id, rather than the fulltext. -/// - Quest stages only store the INFO id, rather than the journal entry fulltext. -class ConvertINFO : public Converter -{ -public: - void read(ESM::ESMReader& esm) override + /// Seen responses for a dialogue topic? + /// Each DIAL record is followed by a number of INFO records, I believe, just like in ESMs + /// Dialogue conversion problems: + /// - Journal is stored in one continuous HTML markup rather than each entry separately with associated info ID. + /// - Seen dialogue responses only store the INFO id, rather than the fulltext. + /// - Quest stages only store the INFO id, rather than the journal entry fulltext. + class ConvertINFO : public Converter { - INFO info; - info.load(esm); - } -}; - -class ConvertDIAL : public Converter -{ -public: - void read(ESM::ESMReader& esm) override - { - std::string id = esm.getHNString("NAME"); - DIAL dial; - dial.load(esm); - if (dial.mIndex > 0) - mDials[id] = dial; - } - void write(ESM::ESMWriter &esm) override - { - for (std::map::const_iterator it = mDials.begin(); it != mDials.end(); ++it) + public: + void read(ESM::ESMReader& esm) override { - esm.startRecord(ESM::REC_QUES); - ESM::QuestState state; - state.mFinished = 0; - state.mState = it->second.mIndex; - state.mTopic = Misc::StringUtils::lowerCase(it->first); - state.save(esm); - esm.endRecord(ESM::REC_QUES); + INFO info; + info.load(esm); } - } -private: - std::map mDials; -}; + }; -class ConvertQUES : public Converter -{ -public: - void read(ESM::ESMReader& esm) override + class ConvertDIAL : public Converter { - std::string id = esm.getHNString("NAME"); - QUES quest; - quest.load(esm); - } -}; - -class ConvertJOUR : public Converter -{ -public: - void read(ESM::ESMReader& esm) override - { - JOUR journal; - journal.load(esm); - } -}; - -class ConvertGAME : public Converter -{ -public: - ConvertGAME() - : mHasGame(false) - { - } - - void read(ESM::ESMReader &esm) override - { - mGame.load(esm); - mHasGame = true; - } - - int validateWeatherID(int weatherID) - { - if(weatherID >= -1 && weatherID < 10) + public: + void read(ESM::ESMReader& esm) override { - return weatherID; + std::string id = esm.getHNString("NAME"); + DIAL dial; + dial.load(esm); + if (dial.mIndex > 0) + mDials[id] = dial; } - else + void write(ESM::ESMWriter& esm) override { - std::stringstream error; - error << "Invalid weather ID:" << weatherID << std::endl; - throw std::runtime_error(error.str()); + for (auto it = mDials.begin(); it != mDials.end(); ++it) + { + esm.startRecord(ESM::REC_QUES); + ESM::QuestState state; + state.mFinished = 0; + state.mState = it->second.mIndex; + state.mTopic = ESM::RefId::stringRefId(it->first); + state.save(esm); + esm.endRecord(ESM::REC_QUES); + } } - } - void write(ESM::ESMWriter &esm) override - { - if (!mHasGame) - return; - esm.startRecord(ESM::REC_WTHR); - ESM::WeatherState weather; - weather.mTimePassed = 0.0f; - weather.mFastForward = false; - weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour; - weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f); - weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather); - weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather); - weather.mQueuedWeather = -1; - // TODO: Determine how ModRegion modifiers are saved in Morrowind. - weather.save(esm); - esm.endRecord(ESM::REC_WTHR); - } + private: + std::map mDials; + }; -private: - bool mHasGame; - GAME mGame; -}; - -/// Running global script -class ConvertSCPT : public Converter -{ -public: - void read(ESM::ESMReader &esm) override + class ConvertQUES : public Converter { - SCPT script; - script.load(esm); - ESM::GlobalScript out; - convertSCPT(script, out); - mScripts.push_back(out); - } - void write(ESM::ESMWriter &esm) override - { - for (const auto & script : mScripts) + public: + void read(ESM::ESMReader& esm) override { - esm.startRecord(ESM::REC_GSCR); - script.save(esm); - esm.endRecord(ESM::REC_GSCR); + std::string id = esm.getHNString("NAME"); + QUES quest; + quest.load(esm); } - } -private: - std::vector mScripts; -}; + }; -/// Projectile converter -class ConvertPROJ : public Converter -{ -public: - int getStage() override { return 2; } - void read(ESM::ESMReader& esm) override; - void write(ESM::ESMWriter& esm) override; -private: - void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam); - PROJ mProj; -}; + class ConvertJOUR : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + JOUR journal; + journal.load(esm); + } + }; -class ConvertSPLM : public Converter -{ -public: - void read(ESM::ESMReader& esm) override; - void write(ESM::ESMWriter& esm) override; -private: - SPLM mSPLM; -}; + class ConvertGAME : public Converter + { + public: + ConvertGAME() + : mHasGame(false) + { + } + + void read(ESM::ESMReader& esm) override + { + mGame.load(esm); + mHasGame = true; + } + + int validateWeatherID(int weatherID) + { + if (weatherID >= -1 && weatherID < 10) + { + return weatherID; + } + else + { + throw std::runtime_error("Invalid weather ID: " + std::to_string(weatherID)); + } + } + + void write(ESM::ESMWriter& esm) override + { + if (!mHasGame) + return; + esm.startRecord(ESM::REC_WTHR); + ESM::WeatherState weather; + weather.mTimePassed = 0.0f; + weather.mFastForward = false; + weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour; + weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f); + weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather); + weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather); + weather.mQueuedWeather = -1; + // TODO: Determine how ModRegion modifiers are saved in Morrowind. + weather.save(esm); + esm.endRecord(ESM::REC_WTHR); + } + + private: + bool mHasGame; + GAME mGame; + }; + + /// Running global script + class ConvertSCPT : public Converter + { + public: + void read(ESM::ESMReader& esm) override + { + SCPT script; + script.load(esm); + ESM::GlobalScript out; + convertSCPT(script, out); + mScripts.push_back(out); + } + void write(ESM::ESMWriter& esm) override + { + for (const auto& script : mScripts) + { + esm.startRecord(ESM::REC_GSCR); + script.save(esm); + esm.endRecord(ESM::REC_GSCR); + } + } + + private: + std::vector mScripts; + }; + + /// Projectile converter + class ConvertPROJ : public Converter + { + public: + int getStage() override { return 2; } + void read(ESM::ESMReader& esm) override; + void write(ESM::ESMWriter& esm) override; + + private: + void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam); + PROJ mProj; + }; + + class ConvertSPLM : public Converter + { + public: + void read(ESM::ESMReader& esm) override; + void write(ESM::ESMWriter& esm) override; + + private: + SPLM mSPLM; + }; } diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index 79e09488c..2f03cfaf4 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -1,22 +1,23 @@ #include "convertinventory.hpp" -#include +#include + #include namespace ESSImport { - void convertInventory(const Inventory &inventory, ESM::InventoryState &state) + void convertInventory(const Inventory& inventory, ESM::InventoryState& state) { int index = 0; - for (const auto & item : inventory.mItems) + for (const auto& item : inventory.mItems) { ESM::ObjectState objstate; objstate.blank(); objstate.mRef = item; - objstate.mRef.mRefID = Misc::StringUtils::lowerCase(item.mId); + objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId); objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile - // openmw handles them differently, so no need to set any flags + // openmw handles them differently, so no need to set any flags state.mItems.push_back(objstate); if (item.mRelativeEquipmentSlot != -1) // Note we should really write the absolute slot here, which we do not know about diff --git a/apps/essimporter/convertinventory.hpp b/apps/essimporter/convertinventory.hpp index 8abe85a44..baa10d441 100644 --- a/apps/essimporter/convertinventory.hpp +++ b/apps/essimporter/convertinventory.hpp @@ -3,12 +3,12 @@ #include "importinventory.hpp" -#include +#include namespace ESSImport { - void convertInventory (const Inventory& inventory, ESM::InventoryState& state); + void convertInventory(const Inventory& inventory, ESM::InventoryState& state); } diff --git a/apps/essimporter/convertnpcc.cpp b/apps/essimporter/convertnpcc.cpp index 48d3d9232..f049edd8d 100644 --- a/apps/essimporter/convertnpcc.cpp +++ b/apps/essimporter/convertnpcc.cpp @@ -5,7 +5,7 @@ namespace ESSImport { - void convertNPCC(const NPCC &npcc, ESM::NpcState &npcState) + void convertNPCC(const NPCC& npcc, ESM::NpcState& npcState) { npcState.mNpcStats.mDisposition = npcc.mNPDT.mDisposition; npcState.mNpcStats.mReputation = npcc.mNPDT.mReputation; diff --git a/apps/essimporter/convertnpcc.hpp b/apps/essimporter/convertnpcc.hpp index eb12d8f3b..f0c35b72a 100644 --- a/apps/essimporter/convertnpcc.hpp +++ b/apps/essimporter/convertnpcc.hpp @@ -3,12 +3,12 @@ #include "importnpcc.hpp" -#include +#include namespace ESSImport { - void convertNPCC (const NPCC& npcc, ESM::NpcState& npcState); + void convertNPCC(const NPCC& npcc, ESM::NpcState& npcState); } diff --git a/apps/essimporter/convertplayer.cpp b/apps/essimporter/convertplayer.cpp index b3ccbca35..29c49451f 100644 --- a/apps/essimporter/convertplayer.cpp +++ b/apps/essimporter/convertplayer.cpp @@ -1,28 +1,35 @@ #include "convertplayer.hpp" +#include + +#include #include -#include +#include namespace ESSImport { - void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls) + void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, + bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls) { - out.mBirthsign = pcdt.mBirthsign; + out.mObject.mPosition.rot[0] + = -atan2(pcdt.mPNAM.mVerticalRotation.mData[2][1], pcdt.mPNAM.mVerticalRotation.mData[2][2]); + + out.mBirthsign = ESM::RefId::stringRefId(pcdt.mBirthsign); out.mObject.mNpcStats.mBounty = pcdt.mBounty; - for (const auto & essFaction : pcdt.mFactions) + for (const auto& essFaction : pcdt.mFactions) { ESM::NpcStats::Faction faction; faction.mExpelled = (essFaction.mFlags & 0x2) != 0; faction.mRank = essFaction.mRank; faction.mReputation = essFaction.mReputation; - out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(essFaction.mFactionName.toString())] = faction; + out.mObject.mNpcStats.mFactions[ESM::RefId::stringRefId(essFaction.mFactionName.toString())] = faction; } - for (int i=0; i<3; ++i) + for (size_t i = 0; i < out.mObject.mNpcStats.mSpecIncreases.size(); ++i) out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i]; - for (int i=0; i<8; ++i) + for (size_t i = 0; i < out.mObject.mNpcStats.mSkillIncrease.size(); ++i) out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i]; - for (int i=0; i<27; ++i) + for (size_t i = 0; i < out.mObject.mNpcStats.mSkills.size(); ++i) out.mObject.mNpcStats.mSkills[i].mProgress = pcdt.mPNAM.mSkillProgress[i]; out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress; @@ -35,9 +42,9 @@ namespace ESSImport teleportingEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_TeleportingDisabled); levitationEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LevitationDisabled); - for (const auto & knownDialogueTopic : pcdt.mKnownDialogueTopics) + for (const auto& knownDialogueTopic : pcdt.mKnownDialogueTopics) { - outDialogueTopics.push_back(Misc::StringUtils::lowerCase(knownDialogueTopic)); + outDialogueTopics.push_back(ESM::RefId::stringRefId(knownDialogueTopic)); } controls.mViewSwitchDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ViewSwitchDisabled; @@ -54,19 +61,9 @@ namespace ESSImport const PCDT::PNAM::MarkLocation& mark = pcdt.mPNAM.mMarkLocation; - ESM::CellId cell; - cell.mWorldspace = ESM::CellId::sDefaultWorldspace; - cell.mPaged = true; - - cell.mIndex.mX = mark.mCellX; - cell.mIndex.mY = mark.mCellY; - // TODO: Figure out a better way to detect interiors. (0, 0) is a valid exterior cell. - if (mark.mCellX == 0 && mark.mCellY == 0) - { - cell.mWorldspace = pcdt.mMNAM; - cell.mPaged = false; - } + bool interior = mark.mCellX == 0 && mark.mCellY == 0; + ESM::RefId cell = ESM::Cell::generateIdForCell(!interior, pcdt.mMNAM, mark.mCellX, mark.mCellY); out.mMarkedCell = cell; out.mMarkedPosition.pos[0] = mark.mX; diff --git a/apps/essimporter/convertplayer.hpp b/apps/essimporter/convertplayer.hpp index 1d2fdc87a..ccc0f7d64 100644 --- a/apps/essimporter/convertplayer.hpp +++ b/apps/essimporter/convertplayer.hpp @@ -3,13 +3,14 @@ #include "importplayer.hpp" -#include -#include +#include +#include namespace ESSImport { - void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls); + void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, + bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls); } diff --git a/apps/essimporter/convertscpt.cpp b/apps/essimporter/convertscpt.cpp index cb7947e40..dad3ed7b0 100644 --- a/apps/essimporter/convertscpt.cpp +++ b/apps/essimporter/convertscpt.cpp @@ -1,17 +1,17 @@ #include "convertscpt.hpp" -#include - #include "convertscri.hpp" +#include + namespace ESSImport { - void convertSCPT(const SCPT &scpt, ESM::GlobalScript &out) + void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out) { - out.mId = Misc::StringUtils::lowerCase(scpt.mSCHD.mName.toString()); + out.mId = ESM::RefId::stringRefId(scpt.mSCHD.mName.toString()); out.mRunning = scpt.mRunning; - out.mTargetRef.unset(); // TODO: convert target reference of global script + out.mTargetRef = ESM::RefNum{}; // TODO: convert target reference of global script convertSCRI(scpt.mSCRI, out.mLocals); } diff --git a/apps/essimporter/convertscpt.hpp b/apps/essimporter/convertscpt.hpp index 3390bd607..854cf7c9a 100644 --- a/apps/essimporter/convertscpt.hpp +++ b/apps/essimporter/convertscpt.hpp @@ -1,14 +1,14 @@ #ifndef OPENMW_ESSIMPORT_CONVERTSCPT_H #define OPENMW_ESSIMPORT_CONVERTSCPT_H -#include +#include #include "importscpt.hpp" namespace ESSImport { -void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out); + void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out); } diff --git a/apps/essimporter/convertscri.cpp b/apps/essimporter/convertscri.cpp index eba48df77..1dbe476e2 100644 --- a/apps/essimporter/convertscri.cpp +++ b/apps/essimporter/convertscri.cpp @@ -19,12 +19,12 @@ namespace namespace ESSImport { - void convertSCRI(const SCRI &scri, ESM::Locals &locals) + void convertSCRI(const SCRI& scri, ESM::Locals& locals) { // order *is* important, as we do not have variable names available in this format - storeVariables (scri.mShorts, locals, scri.mScript); - storeVariables (scri.mLongs, locals, scri.mScript); - storeVariables (scri.mFloats, locals, scri.mScript); + storeVariables(scri.mShorts, locals, scri.mScript); + storeVariables(scri.mLongs, locals, scri.mScript); + storeVariables(scri.mFloats, locals, scri.mScript); } } diff --git a/apps/essimporter/convertscri.hpp b/apps/essimporter/convertscri.hpp index 2d8945666..4a1026c39 100644 --- a/apps/essimporter/convertscri.hpp +++ b/apps/essimporter/convertscri.hpp @@ -3,13 +3,13 @@ #include "importscri.hpp" -#include +#include namespace ESSImport { /// Convert script variable assignments - void convertSCRI (const SCRI& scri, ESM::Locals& locals); + void convertSCRI(const SCRI& scri, ESM::Locals& locals); } diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp deleted file mode 100644 index 0ddd2eb64..000000000 --- a/apps/essimporter/importacdt.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include "importacdt.hpp" - -#include - -#include - -namespace ESSImport -{ - - void ActorData::load(ESM::ESMReader &esm) - { - if (esm.isNextSub("ACTN")) - { - /* - Activation flags: - ActivationFlag_UseEnabled = 1 - ActivationFlag_OnActivate = 2 - ActivationFlag_OnDeath = 10h - ActivationFlag_OnKnockout = 20h - ActivationFlag_OnMurder = 40h - ActivationFlag_DoorOpening = 100h - ActivationFlag_DoorClosing = 200h - ActivationFlag_DoorJammedOpening = 400h - ActivationFlag_DoorJammedClosing = 800h - */ - esm.skipHSub(); - } - - if (esm.isNextSub("STPR")) - esm.skipHSub(); - - if (esm.isNextSub("MNAM")) - esm.skipHSub(); - - bool isDeleted = false; - ESM::CellRef::loadData(esm, isDeleted); - - mHasACDT = false; - if (esm.isNextSub("ACDT")) - { - mHasACDT = true; - esm.getHT(mACDT); - } - - mHasACSC = false; - if (esm.isNextSub("ACSC")) - { - mHasACSC = true; - esm.getHT(mACSC); - } - - if (esm.isNextSub("ACSL")) - esm.skipHSubSize(112); - - if (esm.isNextSub("CSTN")) - esm.skipHSub(); // "PlayerSaveGame", link to some object? - - if (esm.isNextSub("LSTN")) - esm.skipHSub(); // "PlayerSaveGame", link to some object? - - // unsure at which point between LSTN and TGTN - if (esm.isNextSub("CSHN")) - esm.skipHSub(); // "PlayerSaveGame", link to some object? - - // unsure if before or after CSTN/LSTN - if (esm.isNextSub("LSHN")) - esm.skipHSub(); // "PlayerSaveGame", link to some object? - - while (esm.isNextSub("TGTN")) - esm.skipHSub(); // "PlayerSaveGame", link to some object? - - while (esm.isNextSub("FGTN")) - esm.getHString(); // fight target? - - // unsure at which point between TGTN and CRED - if (esm.isNextSub("AADT")) - { - // occurred when a creature was in the middle of its attack, 44 bytes - esm.skipHSub(); - } - - // unsure at which point between FGTN and CHRD - if (esm.isNextSub("PWPC")) - esm.skipHSub(); - if (esm.isNextSub("PWPS")) - esm.skipHSub(); - - if (esm.isNextSub("WNAM")) - { - std::string id = esm.getHString(); - - if (esm.isNextSub("XNAM")) - mSelectedEnchantItem = esm.getHString(); - else - mSelectedSpell = id; - - if (esm.isNextSub("YNAM")) - esm.skipHSub(); // 4 byte, 0 - } - - while (esm.isNextSub("APUD")) - { - // used power - esm.getSubHeader(); - std::string id = esm.getString(32); - (void)id; - // timestamp can't be used: this is the total hours passed, calculated by - // timestamp = 24 * (365 * year + cumulativeDays[month] + day) - // unfortunately cumulativeDays[month] is not clearly defined, - // in the (non-MCP) vanilla version the first month was missing, but MCP added it. - double timestamp; - esm.getT(timestamp); - } - - // FIXME: not all actors have this, add flag - if (esm.isNextSub("CHRD")) // npc only - esm.getHExact(mSkills, 27*2*sizeof(int)); - - if (esm.isNextSub("CRED")) // creature only - esm.getHExact(mCombatStats, 3*2*sizeof(int)); - - mSCRI.load(esm); - - if (esm.isNextSub("ND3D")) - esm.skipHSub(); - - mHasANIS = false; - if (esm.isNextSub("ANIS")) - { - mHasANIS = true; - esm.getHT(mANIS); - } - } - -} diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index 354eca32d..785e98820 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -3,8 +3,6 @@ #include -#include - #include "importscri.hpp" namespace ESM @@ -40,7 +38,8 @@ namespace ESSImport float mDynamic[3][2]; unsigned char mUnknown3[16]; float mAttributes[8][2]; - float mMagicEffects[27]; // Effect attributes: https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes + float mMagicEffects[27]; // Effect attributes: + // https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes unsigned char mUnknown4[4]; unsigned int mGoldPool; unsigned char mCountDown; // seen the same value as in ACSC.mCorpseClearCountdown, maybe @@ -63,7 +62,7 @@ namespace ESSImport }; #pragma pack(pop) - struct ActorData : public ESM::CellRef + struct ActorData { bool mHasACDT; ACDT mACDT; @@ -85,10 +84,6 @@ namespace ESSImport bool mHasANIS; ANIS mANIS; // scripted animation state - - virtual void load(ESM::ESMReader& esm); - - virtual ~ActorData() = default; }; } diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index 442a7781c..8470fd7cf 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -1,32 +1,152 @@ #include "importcellref.hpp" -#include +#include namespace ESSImport { - void CellRef::load(ESM::ESMReader &esm) + void CellRef::load(ESM::ESMReader& esm) { blank(); - // (FRMR subrecord name is already read by the loop in ConvertCell) - esm.getHT(mRefNum.mIndex); // FRMR + esm.getHNT(mRefNum.mIndex, "FRMR"); // this is required since openmw supports more than 255 content files int pluginIndex = (mRefNum.mIndex & 0xff000000) >> 24; - mRefNum.mContentFile = pluginIndex-1; + mRefNum.mContentFile = pluginIndex - 1; mRefNum.mIndex &= 0x00ffffff; mIndexedRefId = esm.getHNString("NAME"); - ActorData::load(esm); + if (esm.isNextSub("ACTN")) + { + /* + Activation flags: + ActivationFlag_UseEnabled = 1 + ActivationFlag_OnActivate = 2 + ActivationFlag_OnDeath = 10h + ActivationFlag_OnKnockout = 20h + ActivationFlag_OnMurder = 40h + ActivationFlag_DoorOpening = 100h + ActivationFlag_DoorClosing = 200h + ActivationFlag_DoorJammedOpening = 400h + ActivationFlag_DoorJammedClosing = 800h + */ + esm.skipHSub(); + } + + if (esm.isNextSub("STPR")) + esm.skipHSub(); + + if (esm.isNextSub("MNAM")) + esm.skipHSub(); + + bool isDeleted = false; + ESM::CellRef::loadData(esm, isDeleted); + + mActorData.mHasACDT = false; + if (esm.isNextSub("ACDT")) + { + mActorData.mHasACDT = true; + esm.getHT(mActorData.mACDT); + } + + mActorData.mHasACSC = false; + if (esm.isNextSub("ACSC")) + { + mActorData.mHasACSC = true; + esm.getHT(mActorData.mACSC); + } + + if (esm.isNextSub("ACSL")) + esm.skipHSubSize(112); + + if (esm.isNextSub("CSTN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + if (esm.isNextSub("LSTN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + // unsure at which point between LSTN and TGTN + if (esm.isNextSub("CSHN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + // unsure if before or after CSTN/LSTN + if (esm.isNextSub("LSHN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + while (esm.isNextSub("TGTN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + while (esm.isNextSub("FGTN")) + esm.getHString(); // fight target? + + // unsure at which point between TGTN and CRED + if (esm.isNextSub("AADT")) + { + // occurred when a creature was in the middle of its attack, 44 bytes + esm.skipHSub(); + } + + // unsure at which point between FGTN and CHRD + if (esm.isNextSub("PWPC")) + esm.skipHSub(); + if (esm.isNextSub("PWPS")) + esm.skipHSub(); + + if (esm.isNextSub("WNAM")) + { + std::string id = esm.getHString(); + + if (esm.isNextSub("XNAM")) + mActorData.mSelectedEnchantItem = esm.getHString(); + else + mActorData.mSelectedSpell = id; + + if (esm.isNextSub("YNAM")) + esm.skipHSub(); // 4 byte, 0 + } + + while (esm.isNextSub("APUD")) + { + // used power + esm.getSubHeader(); + std::string id = esm.getMaybeFixedStringSize(32); + (void)id; + // timestamp can't be used: this is the total hours passed, calculated by + // timestamp = 24 * (365 * year + cumulativeDays[month] + day) + // unfortunately cumulativeDays[month] is not clearly defined, + // in the (non-MCP) vanilla version the first month was missing, but MCP added it. + double timestamp; + esm.getT(timestamp); + } + + // FIXME: not all actors have this, add flag + if (esm.isNextSub("CHRD")) // npc only + esm.getHExact(mActorData.mSkills, 27 * 2 * sizeof(int)); + + if (esm.isNextSub("CRED")) // creature only + esm.getHExact(mActorData.mCombatStats, 3 * 2 * sizeof(int)); + + mActorData.mSCRI.load(esm); + + if (esm.isNextSub("ND3D")) + esm.skipHSub(); + + mActorData.mHasANIS = false; + if (esm.isNextSub("ANIS")) + { + mActorData.mHasANIS = true; + esm.getHT(mActorData.mANIS); + } + if (esm.isNextSub("LVCR")) { // occurs on levelled creature spawner references // probably some identifier for the creature that has been spawned? unsigned char lvcr; esm.getHT(lvcr); - //std::cout << "LVCR: " << (int)lvcr << std::endl; + // std::cout << "LVCR: " << (int)lvcr << std::endl; } mEnabled = true; @@ -35,8 +155,8 @@ namespace ESSImport // DATA should occur for all references, except levelled creature spawners // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess - esm.getHNOT(mPos, "DATA", 24); - esm.getHNOT(mPos, "DATA", 24); + esm.getHNOTSized<24>(mPos, "DATA"); + esm.getHNOTSized<24>(mPos, "DATA"); mDeleted = 0; if (esm.isNextSub("DELE")) diff --git a/apps/essimporter/importcellref.hpp b/apps/essimporter/importcellref.hpp index b115628d5..dfc44711e 100644 --- a/apps/essimporter/importcellref.hpp +++ b/apps/essimporter/importcellref.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "importacdt.hpp" @@ -15,7 +15,7 @@ namespace ESM namespace ESSImport { - struct CellRef : public ActorData + struct CellRef : public ESM::CellRef { std::string mIndexedRefId; @@ -25,9 +25,11 @@ namespace ESSImport bool mDeleted; - void load(ESM::ESMReader& esm) override; + ActorData mActorData; - virtual ~CellRef() = default; + void load(ESM::ESMReader& esm); + + ~CellRef() = default; }; } diff --git a/apps/essimporter/importcntc.cpp b/apps/essimporter/importcntc.cpp index a492aef5a..41f4e5010 100644 --- a/apps/essimporter/importcntc.cpp +++ b/apps/essimporter/importcntc.cpp @@ -1,11 +1,11 @@ #include "importcntc.hpp" -#include +#include namespace ESSImport { - void CNTC::load(ESM::ESMReader &esm) + void CNTC::load(ESM::ESMReader& esm) { mIndex = 0; esm.getHNT(mIndex, "INDX"); diff --git a/apps/essimporter/importcrec.cpp b/apps/essimporter/importcrec.cpp index 64879f2af..38ebd724f 100644 --- a/apps/essimporter/importcrec.cpp +++ b/apps/essimporter/importcrec.cpp @@ -1,11 +1,11 @@ #include "importcrec.hpp" -#include +#include namespace ESSImport { - void CREC::load(ESM::ESMReader &esm) + void CREC::load(ESM::ESMReader& esm) { esm.getHNT(mIndex, "INDX"); @@ -14,9 +14,8 @@ namespace ESSImport float scale; esm.getHNOT(scale, "XSCL"); - while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") - || esm.isNextSub("AI_A")) + || esm.isNextSub("AI_A")) mAiPackages.add(esm); mInventory.load(esm); diff --git a/apps/essimporter/importcrec.hpp b/apps/essimporter/importcrec.hpp index 5110fbc68..77933eafe 100644 --- a/apps/essimporter/importcrec.hpp +++ b/apps/essimporter/importcrec.hpp @@ -2,7 +2,7 @@ #define OPENMW_ESSIMPORT_CREC_H #include "importinventory.hpp" -#include +#include namespace ESM { diff --git a/apps/essimporter/importdial.cpp b/apps/essimporter/importdial.cpp index 5797a708a..6c45f9d05 100644 --- a/apps/essimporter/importdial.cpp +++ b/apps/essimporter/importdial.cpp @@ -1,11 +1,11 @@ #include "importdial.hpp" -#include +#include namespace ESSImport { - void DIAL::load(ESM::ESMReader &esm) + void DIAL::load(ESM::ESMReader& esm) { // See ESM::Dialogue::Type enum, not sure why we would need this here though int type = 0; diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 706512263..76b685c8a 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -1,27 +1,27 @@ #include "importer.hpp" +#include +#include #include -#include -#include - -#include #include +#include -#include -#include #include +#include +#include -#include -#include +#include +#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -36,25 +36,25 @@ namespace void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out) { - if (fileHeader.mSCRS.size() != 128*128*4) + if (fileHeader.mSCRS.size() != 128 * 128 * 4) { std::cerr << "Error: unexpected screenshot size " << std::endl; return; } - osg::ref_ptr image (new osg::Image); + osg::ref_ptr image(new osg::Image); image->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE); // need to convert pixel format from BGRA to RGB as the jpg readerwriter doesn't support it otherwise auto it = fileHeader.mSCRS.begin(); - for (int y=0; y<128; ++y) + for (int y = 0; y < 128; ++y) { - for (int x=0; x<128; ++x) + for (int x = 0; x < 128; ++x) { - assert(image->data(x,y)); - *(image->data(x,y)+2) = *it++; - *(image->data(x,y)+1) = *it++; - *image->data(x,y) = *it++; + assert(image->data(x, y)); + *(image->data(x, y) + 2) = *it++; + *(image->data(x, y) + 1) = *it++; + *image->data(x, y) = *it++; ++it; // skip alpha } } @@ -73,7 +73,8 @@ namespace osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image, ostream); if (!result.success()) { - std::cerr << "Error: can't write screenshot: " << result.message() << " code " << result.status() << std::endl; + std::cerr << "Error: can't write screenshot: " << result.message() << " code " << result.status() + << std::endl; return; } @@ -86,12 +87,12 @@ namespace namespace ESSImport { - Importer::Importer(const std::string &essfile, const std::string &outfile, const std::string &encoding) + Importer::Importer( + const std::filesystem::path& essfile, const std::filesystem::path& outfile, const std::string& encoding) : mEssFile(essfile) , mOutFile(outfile) , mEncoding(encoding) { - } struct File @@ -113,7 +114,7 @@ namespace ESSImport std::vector mRecords; }; - void read(const std::string& filename, File& file) + void read(const std::filesystem::path& filename, File& file) { ESM::ESMReader esm; esm.open(filename); @@ -144,14 +145,14 @@ namespace ESSImport void Importer::compare() { // data that always changes (and/or is already fully decoded) should be blacklisted - std::set > blacklist; + std::set> blacklist; blacklist.insert(std::make_pair("GLOB", "FLTV")); // gamehour blacklist.insert(std::make_pair("REFR", "DATA")); // player position blacklist.insert(std::make_pair("CELL", "NAM8")); // fog of war blacklist.insert(std::make_pair("GAME", "GMDT")); // weather data, current time always changes blacklist.insert(std::make_pair("CELL", "DELE")); // first 3 bytes are uninitialized - // this changes way too often, name suggests some renderer internal data? + // this changes way too often, name suggests some renderer internal data? blacklist.insert(std::make_pair("CELL", "ND3D")); blacklist.insert(std::make_pair("REFR", "ND3D")); @@ -161,7 +162,7 @@ namespace ESSImport read(mOutFile, file2); // todo rename variable // FIXME: use max(size1, size2) - for (unsigned int i=0; i= rec2.mSubrecords.size()) { std::ios::fmtflags f(std::cout.flags()); - std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl; + std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset + << std::endl; std::cout.flags(f); return; } @@ -201,8 +203,9 @@ namespace ESSImport if (sub.mName != sub2.mName) { std::ios::fmtflags f(std::cout.flags()); - std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset - << " (2) 0x" << sub2.mFileOffset << std::endl; + std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName + << ") at (1) 0x" << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset + << std::endl; std::cout.flags(f); break; // TODO: try to recover } @@ -214,11 +217,11 @@ namespace ESSImport std::ios::fmtflags f(std::cout.flags()); - std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset - << " (2) 0x" << sub2.mFileOffset << std::endl; + std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" + << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset << std::endl; std::cout << "Data 1:" << std::endl; - for (unsigned int k=0; k= sub2.mData.size() || sub2.mData[k] != sub.mData[k]) @@ -233,7 +236,7 @@ namespace ESSImport std::cout << std::endl; std::cout << "Data 2:" << std::endl; - for (unsigned int k=0; k= sub.mData.size() || sub.mData[k] != sub2.mData[k]) @@ -264,48 +267,48 @@ namespace ESSImport const ESM::Header& header = esm.getHeader(); context.mPlayerCellName = header.mGameData.mCurrentCell.toString(); - const unsigned int recREFR = ESM::FourCC<'R','E','F','R'>::value; - const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value; - const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value; - const unsigned int recKLST = ESM::FourCC<'K','L','S','T'>::value; - const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value; - const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value; - const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value; - const unsigned int recSPLM = ESM::FourCC<'S','P','L','M'>::value; + const unsigned int recREFR = ESM::fourCC("REFR"); + const unsigned int recPCDT = ESM::fourCC("PCDT"); + const unsigned int recFMAP = ESM::fourCC("FMAP"); + const unsigned int recKLST = ESM::fourCC("KLST"); + const unsigned int recSTLN = ESM::fourCC("STLN"); + const unsigned int recGAME = ESM::fourCC("GAME"); + const unsigned int recJOUR = ESM::fourCC("JOUR"); + const unsigned int recSPLM = ESM::fourCC("SPLM"); - std::map > converters; - converters[ESM::REC_GLOB] = std::shared_ptr(new ConvertGlobal()); - converters[ESM::REC_BOOK] = std::shared_ptr(new ConvertBook()); - converters[ESM::REC_NPC_] = std::shared_ptr(new ConvertNPC()); - converters[ESM::REC_CREA] = std::shared_ptr(new ConvertCREA()); - converters[ESM::REC_NPCC] = std::shared_ptr(new ConvertNPCC()); - converters[ESM::REC_CREC] = std::shared_ptr(new ConvertCREC()); - converters[recREFR ] = std::shared_ptr(new ConvertREFR()); - converters[recPCDT ] = std::shared_ptr(new ConvertPCDT()); - converters[recFMAP ] = std::shared_ptr(new ConvertFMAP()); - converters[recKLST ] = std::shared_ptr(new ConvertKLST()); - converters[recSTLN ] = std::shared_ptr(new ConvertSTLN()); - converters[recGAME ] = std::shared_ptr(new ConvertGAME()); - converters[ESM::REC_CELL] = std::shared_ptr(new ConvertCell()); - converters[ESM::REC_ALCH] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_CLAS] = std::shared_ptr(new ConvertClass()); - converters[ESM::REC_SPEL] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_ARMO] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_WEAP] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_CLOT] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_ENCH] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_WEAP] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_LEVC] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_LEVI] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_CNTC] = std::shared_ptr(new ConvertCNTC()); - converters[ESM::REC_FACT] = std::shared_ptr(new ConvertFACT()); - converters[ESM::REC_INFO] = std::shared_ptr(new ConvertINFO()); - converters[ESM::REC_DIAL] = std::shared_ptr(new ConvertDIAL()); - converters[ESM::REC_QUES] = std::shared_ptr(new ConvertQUES()); - converters[recJOUR ] = std::shared_ptr(new ConvertJOUR()); - converters[ESM::REC_SCPT] = std::shared_ptr(new ConvertSCPT()); - converters[ESM::REC_PROJ] = std::shared_ptr(new ConvertPROJ()); - converters[recSPLM] = std::shared_ptr(new ConvertSPLM()); + std::map> converters; + converters[ESM::REC_GLOB] = std::make_unique(); + converters[ESM::REC_BOOK] = std::make_unique(); + converters[ESM::REC_NPC_] = std::make_unique(); + converters[ESM::REC_CREA] = std::make_unique(); + converters[ESM::REC_NPCC] = std::make_unique(); + converters[ESM::REC_CREC] = std::make_unique(); + converters[recREFR] = std::make_unique(); + converters[recPCDT] = std::make_unique(); + converters[recFMAP] = std::make_unique(); + converters[recKLST] = std::make_unique(); + converters[recSTLN] = std::make_unique(); + converters[recGAME] = std::make_unique(); + converters[ESM::REC_CELL] = std::make_unique(); + converters[ESM::REC_ALCH] = std::make_unique>(); + converters[ESM::REC_CLAS] = std::make_unique(); + converters[ESM::REC_SPEL] = std::make_unique>(); + converters[ESM::REC_ARMO] = std::make_unique>(); + converters[ESM::REC_WEAP] = std::make_unique>(); + converters[ESM::REC_CLOT] = std::make_unique>(); + converters[ESM::REC_ENCH] = std::make_unique>(); + converters[ESM::REC_WEAP] = std::make_unique>(); + converters[ESM::REC_LEVC] = std::make_unique>(); + converters[ESM::REC_LEVI] = std::make_unique>(); + converters[ESM::REC_CNTC] = std::make_unique(); + converters[ESM::REC_FACT] = std::make_unique(); + converters[ESM::REC_INFO] = std::make_unique(); + converters[ESM::REC_DIAL] = std::make_unique(); + converters[ESM::REC_QUES] = std::make_unique(); + converters[recJOUR] = std::make_unique(); + converters[ESM::REC_SCPT] = std::make_unique(); + converters[ESM::REC_PROJ] = std::make_unique(); + converters[recSPLM] = std::make_unique(); // TODO: // - REGN (weather in certain regions?) @@ -314,7 +317,7 @@ namespace ESSImport std::set unknownRecords; - for (const auto & converter : converters) + for (const auto& converter : converters) { converter.second->setContext(context); } @@ -324,17 +327,18 @@ namespace ESSImport ESM::NAME n = esm.getRecName(); esm.getRecHeader(); - auto it = converters.find(n.intval); + auto it = converters.find(n.toInt()); if (it != converters.end()) { it->second->read(esm); } else { - if (unknownRecords.insert(n.intval).second) + if (unknownRecords.insert(n.toInt()).second) { std::ios::fmtflags f(std::cerr.flags()); - std::cerr << "Error: unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; + std::cerr << "Error: unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() + << ")" << std::endl; std::cerr.flags(f); } @@ -344,23 +348,23 @@ namespace ESSImport ESM::ESMWriter writer; - writer.setFormat (ESM::SavedGame::sCurrentFormat); + writer.setFormatVersion(ESM::CurrentSaveGameFormatVersion); - boost::filesystem::ofstream stream(boost::filesystem::path(mOutFile), std::ios::out | std::ios::binary); + std::ofstream stream(mOutFile, std::ios::out | std::ios::binary); // all unused writer.setVersion(0); writer.setType(0); writer.setAuthor(""); writer.setDescription(""); - writer.setRecordCount (0); + writer.setRecordCount(0); - for (const auto & master : header.mMaster) + for (const auto& master : header.mMaster) writer.addMaster(master.name, 0); // not using the size information anyway -> use value of 0 - writer.save (stream); + writer.save(stream); ESM::SavedGame profile; - for (const auto & master : header.mMaster) + for (const auto& master : header.mMaster) { profile.mContentFiles.push_back(master.name); } @@ -369,7 +373,8 @@ namespace ESSImport profile.mInGameTime.mGameHour = context.mHour; profile.mInGameTime.mMonth = context.mMonth; profile.mInGameTime.mYear = context.mYear; - profile.mPlayerCell = header.mGameData.mCurrentCell.toString(); + profile.mTimePlayed = 0; + profile.mPlayerCellName = context.mPlayerCellName; if (context.mPlayerBase.mClass == "NEWCLASSID_CHARGEN") profile.mPlayerClassName = context.mCustomPlayerClassName; else @@ -379,14 +384,13 @@ namespace ESSImport writeScreenshot(header, profile); - writer.startRecord (ESM::REC_SAVE); - profile.save (writer); - writer.endRecord (ESM::REC_SAVE); + writer.startRecord(ESM::REC_SAVE); + profile.save(writer); + writer.endRecord(ESM::REC_SAVE); // Writing order should be Dynamic Store -> Cells -> Player, // so that references to dynamic records can be recognized when loading - for (std::map >::const_iterator it = converters.begin(); - it != converters.end(); ++it) + for (auto it = converters.begin(); it != converters.end(); ++it) { if (it->second->getStage() != 0) continue; @@ -394,12 +398,11 @@ namespace ESSImport } writer.startRecord(ESM::REC_NPC_); - context.mPlayerBase.mId = "player"; + context.mPlayerBase.mId = ESM::RefId::stringRefId("Player"); context.mPlayerBase.save(writer); writer.endRecord(ESM::REC_NPC_); - for (std::map >::const_iterator it = converters.begin(); - it != converters.end(); ++it) + for (auto it = converters.begin(); it != converters.end(); ++it) { if (it->second->getStage() != 1) continue; @@ -407,14 +410,11 @@ namespace ESSImport } writer.startRecord(ESM::REC_PLAY); - if (context.mPlayer.mCellId.mPaged) - { - // exterior cell -> determine cell coordinates based on position - int cellX = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[0] / Constants::CellSizeInUnits)); - int cellY = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[1] / Constants::CellSizeInUnits)); - context.mPlayer.mCellId.mIndex.mX = cellX; - context.mPlayer.mCellId.mIndex.mY = cellY; - } + ESM::CellId cellId = ESM::CellId::extractFromRefId(context.mPlayer.mCellId); + int cellX = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[0] / Constants::CellSizeInUnits)); + int cellY = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[1] / Constants::CellSizeInUnits)); + + context.mPlayer.mCellId = ESM::Cell::generateIdForCell(cellId.mPaged, cellId.mWorldspace, cellX, cellY); context.mPlayer.save(writer); writer.endRecord(ESM::REC_PLAY); @@ -423,15 +423,14 @@ namespace ESSImport writer.endRecord(ESM::REC_ACTC); // Stage 2 requires cell references to be written / actors IDs assigned - for (std::map >::const_iterator it = converters.begin(); - it != converters.end(); ++it) + for (auto it = converters.begin(); it != converters.end(); ++it) { if (it->second->getStage() != 2) continue; it->second->write(writer); } - writer.startRecord (ESM::REC_DIAS); + writer.startRecord(ESM::REC_DIAS); context.mDialogueState.save(writer); writer.endRecord(ESM::REC_DIAS); @@ -440,5 +439,4 @@ namespace ESSImport writer.endRecord(ESM::REC_INPU); } - } diff --git a/apps/essimporter/importer.hpp b/apps/essimporter/importer.hpp index ccacd7972..fba199280 100644 --- a/apps/essimporter/importer.hpp +++ b/apps/essimporter/importer.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESSIMPORTER_IMPORTER_H #define OPENMW_ESSIMPORTER_IMPORTER_H -#include +#include namespace ESSImport { @@ -9,15 +9,16 @@ namespace ESSImport class Importer { public: - Importer(const std::string& essfile, const std::string& outfile, const std::string& encoding); + Importer( + const std::filesystem::path& essfile, const std::filesystem::path& outfile, const std::string& encoding); void run(); void compare(); private: - std::string mEssFile; - std::string mOutFile; + std::filesystem::path mEssFile; + std::filesystem::path mOutFile; std::string mEncoding; }; diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 179e00f08..03ea9d094 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -3,22 +3,19 @@ #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include "importnpcc.hpp" -#include "importcrec.hpp" #include "importcntc.hpp" -#include "importplayer.hpp" +#include "importcrec.hpp" +#include "importnpcc.hpp" #include "importsplm.h" - - namespace ESSImport { @@ -36,7 +33,7 @@ namespace ESSImport ESM::ControlsState mControlsState; // cells which should show an explored overlay on the global map - std::set > mExploredCells; + std::set> mExploredCells; ESM::GlobalMap mGlobalMapState; @@ -44,15 +41,15 @@ namespace ESSImport float mHour; // key - std::map, CREC> mCreatureChanges; - std::map, NPCC> mNpcChanges; - std::map, CNTC> mContainerChanges; + std::map, CREC> mCreatureChanges; + std::map, NPCC> mNpcChanges; + std::map, CNTC> mContainerChanges; - std::map, int> mActorIdMap; + std::map, int> mActorIdMap; int mNextActorId; - std::map mCreatures; - std::map mNpcs; + std::map mCreatures; + std::map mNpcs; std::vector mActiveSpells; @@ -63,20 +60,15 @@ namespace ESSImport , mHour(0.f) , mNextActorId(0) { - ESM::CellId playerCellId; - playerCellId.mPaged = true; - playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0; - mPlayer.mCellId = playerCellId; - mPlayer.mLastKnownExteriorPosition[0] - = mPlayer.mLastKnownExteriorPosition[1] - = mPlayer.mLastKnownExteriorPosition[2] - = 0.0f; + mPlayer.mCellId = ESM::RefId::esm3ExteriorCell(0, 0); + mPlayer.mLastKnownExteriorPosition[0] = mPlayer.mLastKnownExteriorPosition[1] + = mPlayer.mLastKnownExteriorPosition[2] = 0.0f; mPlayer.mHasMark = 0; mPlayer.mCurrentCrimeId = -1; // TODO mPlayer.mPaidCrimeId = -1; mPlayer.mObject.blank(); mPlayer.mObject.mEnabled = true; - mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame + mPlayer.mObject.mRef.mRefID = ESM::RefId::stringRefId("player"); // REFR.mRefID would be PlayerSaveGame mPlayer.mObject.mCreatureStats.mActorId = generateActorId(); mGlobalMapState.mBounds.mMinX = 0; @@ -87,10 +79,7 @@ namespace ESSImport mPlayerBase.blank(); } - int generateActorId() - { - return mNextActorId++; - } + int generateActorId() { return mNextActorId++; } }; } diff --git a/apps/essimporter/importgame.cpp b/apps/essimporter/importgame.cpp index 1012541b4..2a2572aae 100644 --- a/apps/essimporter/importgame.cpp +++ b/apps/essimporter/importgame.cpp @@ -1,29 +1,29 @@ #include "importgame.hpp" -#include +#include namespace ESSImport { -void GAME::load(ESM::ESMReader &esm) -{ - esm.getSubNameIs("GMDT"); - esm.getSubHeader(); - if (esm.getSubSize() == 92) + void GAME::load(ESM::ESMReader& esm) { - esm.getExact(&mGMDT, 92); - mGMDT.mSecundaPhase = 0; - } - else if (esm.getSubSize() == 96) - { - esm.getT(mGMDT); - } - else - esm.fail("unexpected subrecord size for GAME.GMDT"); + esm.getSubNameIs("GMDT"); + esm.getSubHeader(); + if (esm.getSubSize() == 92) + { + esm.getExact(&mGMDT, 92); + mGMDT.mSecundaPhase = 0; + } + else if (esm.getSubSize() == 96) + { + esm.getT(mGMDT); + } + else + esm.fail("unexpected subrecord size for GAME.GMDT"); - mGMDT.mWeatherTransition &= (0x000000ff); - mGMDT.mSecundaPhase &= (0x000000ff); - mGMDT.mMasserPhase &= (0x000000ff); -} + mGMDT.mWeatherTransition &= (0x000000ff); + mGMDT.mSecundaPhase &= (0x000000ff); + mGMDT.mMasserPhase &= (0x000000ff); + } } diff --git a/apps/essimporter/importgame.hpp b/apps/essimporter/importgame.hpp index d8051a527..8b26b9d8b 100644 --- a/apps/essimporter/importgame.hpp +++ b/apps/essimporter/importgame.hpp @@ -14,13 +14,13 @@ namespace ESSImport { struct GMDT { - char mCellName[64] {}; - int mFogColour {0}; - float mFogDensity {0.f}; - int mCurrentWeather {0}, mNextWeather {0}; - int mWeatherTransition {0}; // 0-100 transition between weathers, top 3 bytes may be garbage - float mTimeOfNextTransition {0.f}; // weather changes when gamehour == timeOfNextTransition - int mMasserPhase {0}, mSecundaPhase {0}; // top 3 bytes may be garbage + char mCellName[64]{}; + int mFogColour{ 0 }; + float mFogDensity{ 0.f }; + int mCurrentWeather{ 0 }, mNextWeather{ 0 }; + int mWeatherTransition{ 0 }; // 0-100 transition between weathers, top 3 bytes may be garbage + float mTimeOfNextTransition{ 0.f }; // weather changes when gamehour == timeOfNextTransition + int mMasserPhase{ 0 }, mSecundaPhase{ 0 }; // top 3 bytes may be garbage }; GMDT mGMDT; diff --git a/apps/essimporter/importinfo.cpp b/apps/essimporter/importinfo.cpp index 113155370..66902f6ff 100644 --- a/apps/essimporter/importinfo.cpp +++ b/apps/essimporter/importinfo.cpp @@ -1,11 +1,11 @@ #include "importinfo.hpp" -#include +#include namespace ESSImport { - void INFO::load(ESM::ESMReader &esm) + void INFO::load(ESM::ESMReader& esm) { mInfo = esm.getHNString("INAM"); mActorRefId = esm.getHNString("ACDT"); diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index e91c39452..707a80483 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -2,12 +2,12 @@ #include -#include +#include namespace ESSImport { - void Inventory::load(ESM::ESMReader &esm) + void Inventory::load(ESM::ESMReader& esm) { while (esm.isNextSub("NPCO")) { @@ -19,11 +19,10 @@ namespace ESSImport item.mCount = contItem.mCount; item.mRelativeEquipmentSlot = -1; item.mLockLevel = 0; - item.mRefNum.unset(); unsigned int itemCount = std::abs(item.mCount); bool separateStacks = false; - for (unsigned int i=0;i #include +#include -#include #include +#include #include "importscri.hpp" diff --git a/apps/essimporter/importjour.cpp b/apps/essimporter/importjour.cpp index e5d24e113..19a2d601c 100644 --- a/apps/essimporter/importjour.cpp +++ b/apps/essimporter/importjour.cpp @@ -1,11 +1,11 @@ #include "importjour.hpp" -#include +#include namespace ESSImport { - void JOUR::load(ESM::ESMReader &esm) + void JOUR::load(ESM::ESMReader& esm) { mText = esm.getHNString("NAME"); } diff --git a/apps/essimporter/importklst.cpp b/apps/essimporter/importklst.cpp index daa1ab077..d4cfc7f76 100644 --- a/apps/essimporter/importklst.cpp +++ b/apps/essimporter/importklst.cpp @@ -1,11 +1,11 @@ #include "importklst.hpp" -#include +#include namespace ESSImport { - void KLST::load(ESM::ESMReader &esm) + void KLST::load(ESM::ESMReader& esm) { while (esm.isNextSub("KNAM")) { diff --git a/apps/essimporter/importklst.hpp b/apps/essimporter/importklst.hpp index d07332600..7c1ff03bb 100644 --- a/apps/essimporter/importklst.hpp +++ b/apps/essimporter/importklst.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_ESSIMPORT_KLST_H #define OPENMW_ESSIMPORT_KLST_H -#include #include +#include namespace ESM { diff --git a/apps/essimporter/importnpcc.cpp b/apps/essimporter/importnpcc.cpp index 3cbd749ce..1b1a87ab8 100644 --- a/apps/essimporter/importnpcc.cpp +++ b/apps/essimporter/importnpcc.cpp @@ -1,16 +1,16 @@ #include "importnpcc.hpp" -#include +#include namespace ESSImport { - void NPCC::load(ESM::ESMReader &esm) + void NPCC::load(ESM::ESMReader& esm) { esm.getHNT(mNPDT, "NPDT"); while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") - || esm.isNextSub("AI_A")) + || esm.isNextSub("AI_A")) mAiPackages.add(esm); mInventory.load(esm); diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp index a23ab1e50..762add190 100644 --- a/apps/essimporter/importnpcc.hpp +++ b/apps/essimporter/importnpcc.hpp @@ -1,9 +1,7 @@ #ifndef OPENMW_ESSIMPORT_NPCC_H #define OPENMW_ESSIMPORT_NPCC_H -#include - -#include +#include #include "importinventory.hpp" @@ -29,7 +27,7 @@ namespace ESSImport Inventory mInventory; ESM::AIPackageList mAiPackages; - void load(ESM::ESMReader &esm); + void load(ESM::ESMReader& esm); }; } diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index 8c275a286..aa00e45b3 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -1,22 +1,11 @@ #include "importplayer.hpp" -#include +#include namespace ESSImport { - void REFR::load(ESM::ESMReader &esm) - { - esm.getHNT(mRefNum.mIndex, "FRMR"); - - mRefID = esm.getHNString("NAME"); - - mActorData.load(esm); - - esm.getHNOT(mPos, "DATA", 24); - } - - void PCDT::load(ESM::ESMReader &esm) + void PCDT::load(ESM::ESMReader& esm) { while (esm.isNextSub("DNAM")) { diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp index 924522383..0fb820cb6 100644 --- a/apps/essimporter/importplayer.hpp +++ b/apps/essimporter/importplayer.hpp @@ -1,15 +1,11 @@ #ifndef OPENMW_ESSIMPORT_PLAYER_H #define OPENMW_ESSIMPORT_PLAYER_H -#include #include +#include -#include -#include #include -#include "importacdt.hpp" - namespace ESM { class ESMReader; @@ -18,108 +14,102 @@ namespace ESM namespace ESSImport { -/// Player-agnostic player data -struct REFR -{ - ActorData mActorData; - - std::string mRefID; - ESM::Position mPos; - ESM::RefNum mRefNum; - - void load(ESM::ESMReader& esm); -}; - -/// Other player data -struct PCDT -{ - int mBounty; - std::string mBirthsign; - - std::vector mKnownDialogueTopics; - - enum PlayerFlags + /// Other player data + struct PCDT { - PlayerFlags_ViewSwitchDisabled = 0x1, - PlayerFlags_ControlsDisabled = 0x4, - PlayerFlags_Sleeping = 0x10, - PlayerFlags_Waiting = 0x40, - PlayerFlags_WeaponDrawn = 0x80, - PlayerFlags_SpellDrawn = 0x100, - PlayerFlags_InJail = 0x200, - PlayerFlags_JumpingDisabled = 0x1000, - PlayerFlags_LookingDisabled = 0x2000, - PlayerFlags_VanityModeDisabled = 0x4000, - PlayerFlags_WeaponDrawingDisabled = 0x8000, - PlayerFlags_SpellDrawingDisabled = 0x10000, - PlayerFlags_ThirdPerson = 0x20000, - PlayerFlags_TeleportingDisabled = 0x40000, - PlayerFlags_LevitationDisabled = 0x80000 - }; + int mBounty; + std::string mBirthsign; + + std::vector mKnownDialogueTopics; + + enum PlayerFlags + { + PlayerFlags_ViewSwitchDisabled = 0x1, + PlayerFlags_ControlsDisabled = 0x4, + PlayerFlags_Sleeping = 0x10, + PlayerFlags_Waiting = 0x40, + PlayerFlags_WeaponDrawn = 0x80, + PlayerFlags_SpellDrawn = 0x100, + PlayerFlags_InJail = 0x200, + PlayerFlags_JumpingDisabled = 0x1000, + PlayerFlags_LookingDisabled = 0x2000, + PlayerFlags_VanityModeDisabled = 0x4000, + PlayerFlags_WeaponDrawingDisabled = 0x8000, + PlayerFlags_SpellDrawingDisabled = 0x10000, + PlayerFlags_ThirdPerson = 0x20000, + PlayerFlags_TeleportingDisabled = 0x40000, + PlayerFlags_LevitationDisabled = 0x80000 + }; #pragma pack(push) #pragma pack(1) - struct FNAM - { - unsigned char mRank; - unsigned char mUnknown1[3]; - int mReputation; - unsigned char mFlags; // 0x1: unknown, 0x2: expelled - unsigned char mUnknown2[3]; - ESM::NAME32 mFactionName; - }; - - struct PNAM - { - struct MarkLocation + struct FNAM { - float mX, mY, mZ; // worldspace position - float mRotZ; // Z angle in radians - int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0) + unsigned char mRank; + unsigned char mUnknown1[3]; + int mReputation; + unsigned char mFlags; // 0x1: unknown, 0x2: expelled + unsigned char mUnknown2[3]; + ESM::NAME32 mFactionName; }; - int mPlayerFlags; // controls, camera and draw state - unsigned int mLevelProgress; - float mSkillProgress[27]; // skill progress, non-uniform scaled - unsigned char mSkillIncreases[8]; // number of skill increases for each attribute - int mTelekinesisRangeBonus; // in units; seems redundant - float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus - int mDetectKeyMagnitude; // seems redundant - int mDetectEnchantmentMagnitude; // seems redundant - int mDetectAnimalMagnitude; // seems redundant - MarkLocation mMarkLocation; - unsigned char mUnknown3[40]; - unsigned char mSpecIncreases[3]; // number of skill increases for each specialization - unsigned char mUnknown4; - }; + struct PNAM + { + struct MarkLocation + { + float mX, mY, mZ; // worldspace position + float mRotZ; // Z angle in radians + int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0) + }; - struct ENAM - { - int mCellX; - int mCellY; - }; + struct Rotation + { + float mData[3][3]; + }; - struct AADT // 44 bytes - { - int animGroupIndex; // See convertANIS() for the mapping. - unsigned char mUnknown5[40]; - }; + int mPlayerFlags; // controls, camera and draw state + unsigned int mLevelProgress; + float mSkillProgress[27]; // skill progress, non-uniform scaled + unsigned char mSkillIncreases[8]; // number of skill increases for each attribute + int mTelekinesisRangeBonus; // in units; seems redundant + float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus + int mDetectKeyMagnitude; // seems redundant + int mDetectEnchantmentMagnitude; // seems redundant + int mDetectAnimalMagnitude; // seems redundant + MarkLocation mMarkLocation; + unsigned char mUnknown3[4]; + Rotation mVerticalRotation; + unsigned char mSpecIncreases[3]; // number of skill increases for each specialization + unsigned char mUnknown4; + }; + + struct ENAM + { + int mCellX; + int mCellY; + }; + + struct AADT // 44 bytes + { + int animGroupIndex; // See convertANIS() for the mapping. + unsigned char mUnknown5[40]; + }; #pragma pack(pop) - std::vector mFactions; - PNAM mPNAM; + std::vector mFactions; + PNAM mPNAM; - bool mHasMark; - std::string mMNAM; // mark cell name; can also be sDefaultCellname or region name + bool mHasMark; + std::string mMNAM; // mark cell name; can also be sDefaultCellname or region name - bool mHasENAM; - ENAM mENAM; // last exterior cell + bool mHasENAM; + ENAM mENAM; // last exterior cell - bool mHasAADT; - AADT mAADT; + bool mHasAADT; + AADT mAADT; - void load(ESM::ESMReader& esm); -}; + void load(ESM::ESMReader& esm); + }; } diff --git a/apps/essimporter/importproj.cpp b/apps/essimporter/importproj.cpp index b2dcf4e7d..909b22841 100644 --- a/apps/essimporter/importproj.cpp +++ b/apps/essimporter/importproj.cpp @@ -1,18 +1,18 @@ #include "importproj.h" -#include +#include namespace ESSImport { -void ESSImport::PROJ::load(ESM::ESMReader& esm) -{ - while (esm.isNextSub("PNAM")) + void ESSImport::PROJ::load(ESM::ESMReader& esm) { - PNAM pnam; - esm.getHT(pnam); - mProjectiles.push_back(pnam); + while (esm.isNextSub("PNAM")) + { + PNAM pnam; + esm.getHT(pnam); + mProjectiles.push_back(pnam); + } } -} } diff --git a/apps/essimporter/importproj.h b/apps/essimporter/importproj.h index b8abab5fa..d1c544f66 100644 --- a/apps/essimporter/importproj.h +++ b/apps/essimporter/importproj.h @@ -1,9 +1,9 @@ #ifndef OPENMW_ESSIMPORT_IMPORTPROJ_H #define OPENMW_ESSIMPORT_IMPORTPROJ_H -#include #include #include +#include namespace ESM { @@ -13,34 +13,34 @@ namespace ESM namespace ESSImport { -struct PROJ -{ + struct PROJ + { #pragma pack(push) #pragma pack(1) - struct PNAM // 184 bytes - { - float mAttackStrength; - float mSpeed; - unsigned char mUnknown[4*2]; - float mFlightTime; - int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles) - unsigned char mUnknown2[4]; - ESM::Vector3 mVelocity; - ESM::Vector3 mPosition; - unsigned char mUnknown3[4*9]; - ESM::NAME32 mActorId; // indexed refID (with the exception of "PlayerSaveGame") - ESM::NAME32 mArrowId; - ESM::NAME32 mBowId; + struct PNAM // 184 bytes + { + float mAttackStrength; + float mSpeed; + unsigned char mUnknown[4 * 2]; + float mFlightTime; + int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles) + unsigned char mUnknown2[4]; + ESM::Vector3 mVelocity; + ESM::Vector3 mPosition; + unsigned char mUnknown3[4 * 9]; + ESM::NAME32 mActorId; // indexed refID (with the exception of "PlayerSaveGame") + ESM::NAME32 mArrowId; + ESM::NAME32 mBowId; - bool isMagic() const { return mSplmIndex != 0; } - }; + bool isMagic() const { return mSplmIndex != 0; } + }; #pragma pack(pop) - std::vector mProjectiles; + std::vector mProjectiles; - void load(ESM::ESMReader& esm); -}; + void load(ESM::ESMReader& esm); + }; } diff --git a/apps/essimporter/importques.cpp b/apps/essimporter/importques.cpp index 78b779e43..46e08cdd9 100644 --- a/apps/essimporter/importques.cpp +++ b/apps/essimporter/importques.cpp @@ -1,11 +1,11 @@ #include "importques.hpp" -#include +#include namespace ESSImport { - void QUES::load(ESM::ESMReader &esm) + void QUES::load(ESM::ESMReader& esm) { while (esm.isNextSub("DATA")) mInfo.push_back(esm.getHString()); diff --git a/apps/essimporter/importscpt.cpp b/apps/essimporter/importscpt.cpp index 652383cda..8768c2701 100644 --- a/apps/essimporter/importscpt.cpp +++ b/apps/essimporter/importscpt.cpp @@ -1,13 +1,11 @@ #include "importscpt.hpp" -#include - - +#include namespace ESSImport { - void SCPT::load(ESM::ESMReader &esm) + void SCPT::load(ESM::ESMReader& esm) { esm.getHNT(mSCHD, "SCHD"); diff --git a/apps/essimporter/importscpt.hpp b/apps/essimporter/importscpt.hpp index 6bfd2603a..8f6053244 100644 --- a/apps/essimporter/importscpt.hpp +++ b/apps/essimporter/importscpt.hpp @@ -3,7 +3,8 @@ #include "importscri.hpp" -#include +#include +#include namespace ESM { @@ -15,8 +16,8 @@ namespace ESSImport struct SCHD { - ESM::NAME32 mName; - ESM::Script::SCHDstruct mData; + ESM::NAME32 mName; + ESM::Script::SCHDstruct mData; }; // A running global script diff --git a/apps/essimporter/importscri.cpp b/apps/essimporter/importscri.cpp index de0b35c86..b6c1d4094 100644 --- a/apps/essimporter/importscri.cpp +++ b/apps/essimporter/importscri.cpp @@ -1,11 +1,11 @@ #include "importscri.hpp" -#include +#include namespace ESSImport { - void SCRI::load(ESM::ESMReader &esm) + void SCRI::load(ESM::ESMReader& esm) { mScript = esm.getHNOString("SCRI"); @@ -21,7 +21,7 @@ namespace ESSImport if (esm.isNextSub("SLSD")) { esm.getSubHeader(); - for (int i=0; i +#include #include diff --git a/apps/essimporter/importsplm.cpp b/apps/essimporter/importsplm.cpp index 9fdba4ddb..b49a07cde 100644 --- a/apps/essimporter/importsplm.cpp +++ b/apps/essimporter/importsplm.cpp @@ -1,43 +1,43 @@ #include "importsplm.h" -#include +#include namespace ESSImport { -void SPLM::load(ESM::ESMReader& esm) -{ - while (esm.isNextSub("NAME")) + void SPLM::load(ESM::ESMReader& esm) { - ActiveSpell spell; - esm.getHT(spell.mIndex); - esm.getHNT(spell.mSPDT, "SPDT"); - spell.mTarget = esm.getHNOString("TNAM"); - - while (esm.isNextSub("NPDT")) + while (esm.isNextSub("NAME")) { - ActiveEffect effect; - esm.getHT(effect.mNPDT); + ActiveSpell spell; + esm.getHT(spell.mIndex); + esm.getHNT(spell.mSPDT, "SPDT"); + spell.mTarget = esm.getHNOString("TNAM"); - // Effect-specific subrecords can follow: - // - INAM for disintegration and bound effects - // - CNAM for summoning and command effects - // - VNAM for vampirism - // NOTE: There can be multiple INAMs per effect. - // TODO: Needs more research. + while (esm.isNextSub("NPDT")) + { + ActiveEffect effect; + esm.getHT(effect.mNPDT); - esm.skipHSubUntil("NAM0"); // sentinel - esm.getSubName(); - esm.skipHSub(); + // Effect-specific subrecords can follow: + // - INAM for disintegration and bound effects + // - CNAM for summoning and command effects + // - VNAM for vampirism + // NOTE: There can be multiple INAMs per effect. + // TODO: Needs more research. - spell.mActiveEffects.push_back(effect); + esm.skipHSubUntil("NAM0"); // sentinel + esm.getSubName(); + esm.skipHSub(); + + spell.mActiveEffects.push_back(effect); + } + + unsigned char xnam; // sentinel + esm.getHNT(xnam, "XNAM"); + + mActiveSpells.push_back(spell); } - - unsigned char xnam; // sentinel - esm.getHNT(xnam, "XNAM"); - - mActiveSpells.push_back(spell); } -} } diff --git a/apps/essimporter/importsplm.h b/apps/essimporter/importsplm.h index 8fd5c2bb5..8187afb13 100644 --- a/apps/essimporter/importsplm.h +++ b/apps/essimporter/importsplm.h @@ -1,9 +1,8 @@ #ifndef OPENMW_ESSIMPORT_IMPORTSPLM_H #define OPENMW_ESSIMPORT_IMPORTSPLM_H -#include #include -#include +#include namespace ESM { @@ -13,69 +12,68 @@ namespace ESM namespace ESSImport { -struct SPLM -{ + struct SPLM + { #pragma pack(push) #pragma pack(1) - struct SPDT // 160 bytes - { - int mType; // 1 = spell, 2 = enchantment, 3 = potion - ESM::NAME32 mId; // base ID of a spell/enchantment/potion - unsigned char mUnknown[4*4]; - ESM::NAME32 mCasterId; - ESM::NAME32 mSourceId; // empty for spells - unsigned char mUnknown2[4*11]; - }; + struct SPDT // 160 bytes + { + int mType; // 1 = spell, 2 = enchantment, 3 = potion + ESM::NAME32 mId; // base ID of a spell/enchantment/potion + unsigned char mUnknown[4 * 4]; + ESM::NAME32 mCasterId; + ESM::NAME32 mSourceId; // empty for spells + unsigned char mUnknown2[4 * 11]; + }; - struct NPDT // 56 bytes - { - ESM::NAME32 mAffectedActorId; - unsigned char mUnknown[4*2]; - int mMagnitude; - float mSecondsActive; - unsigned char mUnknown2[4*2]; - }; + struct NPDT // 56 bytes + { + ESM::NAME32 mAffectedActorId; + unsigned char mUnknown[4 * 2]; + int mMagnitude; + float mSecondsActive; + unsigned char mUnknown2[4 * 2]; + }; - struct INAM // 40 bytes - { - int mUnknown; - unsigned char mUnknown2; - ESM::FIXED_STRING<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration - }; + struct INAM // 40 bytes + { + int mUnknown; + unsigned char mUnknown2; + ESM::FixedString<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration + }; - struct CNAM // 36 bytes - { - int mUnknown; // seems to always be 0 - ESM::NAME32 mSummonedOrCommandedActor[32]; - }; - - struct VNAM // 4 bytes - { - int mUnknown; - }; + struct CNAM // 36 bytes + { + int mUnknown; // seems to always be 0 + ESM::NAME32 mSummonedOrCommandedActor[32]; + }; + struct VNAM // 4 bytes + { + int mUnknown; + }; #pragma pack(pop) - struct ActiveEffect - { - NPDT mNPDT; + struct ActiveEffect + { + NPDT mNPDT; + }; + + struct ActiveSpell + { + int mIndex; + SPDT mSPDT; + std::string mTarget; + std::vector mActiveEffects; + }; + + std::vector mActiveSpells; + + void load(ESM::ESMReader& esm); }; - struct ActiveSpell - { - int mIndex; - SPDT mSPDT; - std::string mTarget; - std::vector mActiveEffects; - }; - - std::vector mActiveSpells; - - void load(ESM::ESMReader& esm); -}; - } #endif diff --git a/apps/essimporter/main.cpp b/apps/essimporter/main.cpp index 9b969e35a..7d3ad10bb 100644 --- a/apps/essimporter/main.cpp +++ b/apps/essimporter/main.cpp @@ -1,42 +1,38 @@ +#include #include #include -#include #include #include "importer.hpp" namespace bpo = boost::program_options; -namespace bfs = boost::filesystem; - - int main(int argc, char** argv) { try { - bpo::options_description desc("Syntax: openmw-essimporter infile.ess outfile.omwsave\nAllowed options"); + bpo::options_description desc(R"(Syntax: openmw-essimporter infile.ess outfile.omwsave +Allowed options)"); bpo::positional_options_description p_desc; - desc.add_options() - ("help,h", "produce help message") - ("mwsave,m", bpo::value(), "morrowind .ess save file") - ("output,o", bpo::value(), "output file (.omwsave)") - ("compare,c", "compare two .ess files") - ("encoding", boost::program_options::value()->default_value("win1252"), "encoding of the save file") - ; + auto addOption = desc.add_options(); + addOption("help,h", "produce help message"); + addOption("mwsave,m", bpo::value(), "morrowind .ess save file"); + addOption("output,o", bpo::value(), "output file (.omwsave)"); + addOption("compare,c", "compare two .ess files"); + addOption("encoding", boost::program_options::value()->default_value("win1252"), + "encoding of the save file"); p_desc.add("mwsave", 1).add("output", 1); + Files::ConfigurationManager::addCommonOptions(desc); bpo::variables_map variables; - bpo::parsed_options parsed = bpo::command_line_parser(argc, argv) - .options(desc) - .positional(p_desc) - .run(); - + bpo::parsed_options parsed = bpo::command_line_parser(argc, argv).options(desc).positional(p_desc).run(); bpo::store(parsed, variables); - if(variables.count("help") || !variables.count("mwsave") || !variables.count("output")) { + if (variables.count("help") || !variables.count("mwsave") || !variables.count("output")) + { std::cout << desc; return 0; } @@ -46,8 +42,8 @@ int main(int argc, char** argv) Files::ConfigurationManager cfgManager(true); cfgManager.readConfiguration(variables, desc); - std::string essFile = variables["mwsave"].as(); - std::string outputFile = variables["output"].as(); + const auto essFile = variables["mwsave"].as(); + const auto outputFile = variables["output"].as(); std::string encoding = variables["encoding"].as(); ESSImport::Importer importer(essFile, outputFile, encoding); @@ -56,11 +52,13 @@ int main(int argc, char** argv) importer.compare(); else { - const std::string& ext = ".omwsave"; - if (bfs::exists(bfs::path(outputFile)) - && (outputFile.size() < ext.size() || outputFile.substr(outputFile.size()-ext.size()) != ext)) + static constexpr std::u8string_view ext{ u8".omwsave" }; + const auto length = outputFile.native().size(); + if (std::filesystem::exists(outputFile) + && (length < ext.size() || outputFile.u8string().substr(length - ext.size()) != ext)) { - throw std::runtime_error("Output file already exists and does not end in .omwsave. Did you mean to use --compare?"); + throw std::runtime_error( + "Output file already exists and does not end in .omwsave. Did you mean to use --compare?"); } importer.run(); } diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 301823770..fe27dfc49 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -4,10 +4,9 @@ set(LAUNCHER sdlinit.cpp main.cpp maindialog.cpp - playpage.cpp textslotmsgbox.cpp + importpage.cpp settingspage.cpp - advancedpage.cpp utils/cellnameloader.cpp utils/profilescombobox.cpp @@ -23,10 +22,9 @@ set(LAUNCHER_HEADER graphicspage.hpp sdlinit.hpp maindialog.hpp - playpage.hpp textslotmsgbox.hpp + importpage.hpp settingspage.hpp - advancedpage.hpp utils/cellnameloader.hpp utils/profilescombobox.hpp @@ -36,31 +34,14 @@ set(LAUNCHER_HEADER ) # Headers that must be pre-processed -set(LAUNCHER_HEADER_MOC - datafilespage.hpp - graphicspage.hpp - maindialog.hpp - playpage.hpp - textslotmsgbox.hpp - settingspage.hpp - advancedpage.hpp - - utils/cellnameloader.hpp - utils/textinputdialog.hpp - utils/profilescombobox.hpp - utils/lineedit.hpp - utils/openalutil.hpp - -) - set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui - ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui + ${CMAKE_SOURCE_DIR}/files/ui/importpage.ui ${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui - ${CMAKE_SOURCE_DIR}/files/ui/advancedpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/directorypicker.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) @@ -73,9 +54,8 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) -QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) -QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) +QT_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) +QT_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(NOT WIN32) @@ -99,14 +79,26 @@ endif (WIN32) target_link_libraries(openmw-launcher ${SDL2_LIBRARY_ONLY} ${OPENAL_LIBRARY} - components + components_qt ) -target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core) +target_link_libraries(openmw-launcher Qt::Widgets Qt::Core) if (BUILD_WITH_CODE_COVERAGE) - add_definitions (--coverage) - target_link_libraries(openmw-launcher gcov) + target_compile_options(openmw-launcher PRIVATE --coverage) + target_link_libraries(openmw-launcher gcov) endif() +if(USE_QT) + set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) +endif(USE_QT) +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw-launcher PRIVATE + + + + + + ) +endif() diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp deleted file mode 100644 index bddb70aa0..000000000 --- a/apps/launcher/advancedpage.cpp +++ /dev/null @@ -1,432 +0,0 @@ -#include "advancedpage.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include "utils/openalutil.hpp" - -Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, QWidget *parent) - : QWidget(parent) - , mGameSettings(gameSettings) -{ - setObjectName ("AdvancedPage"); - setupUi(this); - - for(const char * name : Launcher::enumerateOpenALDevices()) - { - audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); - } - for(const char * name : Launcher::enumerateOpenALDevicesHrtf()) - { - hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); - } - - loadSettings(); - - mCellNameCompleter.setModel(&mCellNameCompleterModel); - startDefaultCharacterAtField->setCompleter(&mCellNameCompleter); -} - -void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) { - // Update the list of suggestions for the "Start default character at" field - mCellNameCompleterModel.setStringList(cellNames); - mCellNameCompleter.setCompletionMode(QCompleter::PopupCompletion); - mCellNameCompleter.setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); -} - -void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) { - startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked); - startDefaultCharacterAtField->setEnabled(state == Qt::Checked); -} - -void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked() -{ - QString scriptFile = QFileDialog::getOpenFileName( - this, - QObject::tr("Select script file"), - QDir::currentPath(), - QString(tr("Text file (*.txt)"))); - - if (scriptFile.isEmpty()) - return; - - QFileInfo info(scriptFile); - - if (!info.exists() || !info.isReadable()) - return; - - const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); - runScriptAfterStartupField->setText(path); -} - -namespace -{ - constexpr double CellSizeInUnits = 8192; - - double convertToCells(double unitRadius) - { - return std::round((unitRadius + 1024) / CellSizeInUnits); - } - - double convertToUnits(double CellGridRadius) - { - return CellSizeInUnits * CellGridRadius - 1024; - } -} - -bool Launcher::AdvancedPage::loadSettings() -{ - // Game mechanics - { - loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); - loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); - loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); - loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); - loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); - loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); - loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); - loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); - loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); - loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); - loadSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game"); - loadSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game"); - int unarmedFactorsStrengthIndex = Settings::Manager::getInt("strength influences hand to hand", "Game"); - if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) - unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); - loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); - loadSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); - int numPhysicsThreads = Settings::Manager::getInt("async num threads", "Physics"); - if (numPhysicsThreads >= 0) - physicsThreadsSpinBox->setValue(numPhysicsThreads); - loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game"); - } - - // Visuals - { - loadSettingBool(autoUseObjectNormalMapsCheckBox, "auto use object normal maps", "Shaders"); - loadSettingBool(autoUseObjectSpecularMapsCheckBox, "auto use object specular maps", "Shaders"); - loadSettingBool(autoUseTerrainNormalMapsCheckBox, "auto use terrain normal maps", "Shaders"); - loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); - loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); - loadSettingBool(radialFogCheckBox, "radial fog", "Shaders"); - loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); - connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); - loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); - if (animSourcesCheckBox->checkState() != Qt::Unchecked) - { - loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); - loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); - } - loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game"); - loadSettingBool(smoothMovementCheckBox, "smooth movement", "Game"); - - const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); - const bool objectPaging = Settings::Manager::getBool("object paging", "Terrain"); - if (distantTerrain && objectPaging) { - distantLandCheckBox->setCheckState(Qt::Checked); - } - - loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); - viewingDistanceComboBox->setValue(convertToCells(Settings::Manager::getInt("viewing distance", "Camera"))); - objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain")); - } - - // Audio - { - std::string selectedAudioDevice = Settings::Manager::getString("device", "Sound"); - if (selectedAudioDevice.empty() == false) - { - int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice)); - if (audioDeviceIndex != -1) - { - audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex); - } - } - int hrtfEnabledIndex = Settings::Manager::getInt("hrtf enable", "Sound"); - if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1) - { - enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1); - } - std::string selectedHRTFProfile = Settings::Manager::getString("hrtf", "Sound"); - if (selectedHRTFProfile.empty() == false) - { - int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile)); - if (hrtfProfileIndex != -1) - { - hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex); - } - } - } - - - // Camera - { - loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); - connect(viewOverShoulderCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotViewOverShoulderToggled(bool))); - viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState()); - loadSettingBool(autoSwitchShoulderCheckBox, "auto switch shoulder", "Camera"); - loadSettingBool(previewIfStandStillCheckBox, "preview if stand still", "Camera"); - loadSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera"); - loadSettingBool(headBobbingCheckBox, "head bobbing", "Camera"); - defaultShoulderComboBox->setCurrentIndex( - Settings::Manager::getVector2("view over shoulder offset", "Camera").x() >= 0 ? 0 : 1); - } - - // Interface Changes - { - loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); - loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); - loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); - loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); - loadSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); - int showOwnedIndex = Settings::Manager::getInt("show owned", "Game"); - // Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. - if (showOwnedIndex >= 0 && showOwnedIndex <= 3) - showOwnedComboBox->setCurrentIndex(showOwnedIndex); - loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); - loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); - scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "GUI")); - } - - // Bug fixes - { - loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); - loadSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); - } - - // Miscellaneous - { - // Saves - loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); - maximumQuicksavesComboBox->setValue(Settings::Manager::getInt("max quicksaves", "Saves")); - - // Other Settings - QString screenshotFormatString = QString::fromStdString(Settings::Manager::getString("screenshot format", "General")).toUpper(); - if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) - screenshotFormatComboBox->addItem(screenshotFormatString); - screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); - } - - // Testing - { - loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); - - bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; - if (skipMenu) - { - skipMenuCheckBox->setCheckState(Qt::Checked); - } - startDefaultCharacterAtLabel->setEnabled(skipMenu); - startDefaultCharacterAtField->setEnabled(skipMenu); - - startDefaultCharacterAtField->setText(mGameSettings.value("start")); - runScriptAfterStartupField->setText(mGameSettings.value("script-run")); - } - return true; -} - -void Launcher::AdvancedPage::saveSettings() -{ - // Game mechanics - { - saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); - saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); - saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); - saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); - saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); - saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); - saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); - saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); - saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game"); - saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); - saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game"); - saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game"); - int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); - if (unarmedFactorsStrengthIndex != Settings::Manager::getInt("strength influences hand to hand", "Game")) - Settings::Manager::setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); - saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); - saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); - int numPhysicsThreads = physicsThreadsSpinBox->value(); - if (numPhysicsThreads != Settings::Manager::getInt("async num threads", "Physics")) - Settings::Manager::setInt("async num threads", "Physics", numPhysicsThreads); - } - - // Visuals - { - saveSettingBool(autoUseObjectNormalMapsCheckBox, "auto use object normal maps", "Shaders"); - saveSettingBool(autoUseObjectSpecularMapsCheckBox, "auto use object specular maps", "Shaders"); - saveSettingBool(autoUseTerrainNormalMapsCheckBox, "auto use terrain normal maps", "Shaders"); - saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); - saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); - saveSettingBool(radialFogCheckBox, "radial fog", "Shaders"); - saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); - saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); - saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); - saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); - saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game"); - saveSettingBool(smoothMovementCheckBox, "smooth movement", "Game"); - - const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); - const bool objectPaging = Settings::Manager::getBool("object paging", "Terrain"); - const bool wantDistantLand = distantLandCheckBox->checkState(); - if (wantDistantLand != (distantTerrain && objectPaging)) { - Settings::Manager::setBool("distant terrain", "Terrain", wantDistantLand); - Settings::Manager::setBool("object paging", "Terrain", wantDistantLand); - } - - saveSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); - double viewingDistance = viewingDistanceComboBox->value(); - if (viewingDistance != convertToCells(Settings::Manager::getInt("viewing distance", "Camera"))) - { - Settings::Manager::setInt("viewing distance", "Camera", convertToUnits(viewingDistance)); - } - double objectPagingMinSize = objectPagingMinSizeComboBox->value(); - if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain")) - Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); - } - - // Audio - { - int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex(); - if (audioDeviceIndex != 0) - { - Settings::Manager::setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData()); - } - else - { - Settings::Manager::setString("device", "Sound", ""); - } - int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1; - if (hrtfEnabledIndex != Settings::Manager::getInt("hrtf enable", "Sound")) - { - Settings::Manager::setInt("hrtf enable", "Sound", hrtfEnabledIndex); - } - int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex(); - if (selectedHRTFProfileIndex != 0) - { - Settings::Manager::setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData()); - } - else - { - Settings::Manager::setString("hrtf", "Sound", ""); - } - } - - // Camera - { - saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera"); - saveSettingBool(autoSwitchShoulderCheckBox, "auto switch shoulder", "Camera"); - saveSettingBool(previewIfStandStillCheckBox, "preview if stand still", "Camera"); - saveSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera"); - saveSettingBool(headBobbingCheckBox, "head bobbing", "Camera"); - - osg::Vec2f shoulderOffset = Settings::Manager::getVector2("view over shoulder offset", "Camera"); - if (defaultShoulderComboBox->currentIndex() != (shoulderOffset.x() >= 0 ? 0 : 1)) - { - if (defaultShoulderComboBox->currentIndex() == 0) - shoulderOffset.x() = std::abs(shoulderOffset.x()); - else - shoulderOffset.x() = -std::abs(shoulderOffset.x()); - Settings::Manager::setVector2("view over shoulder offset", "Camera", shoulderOffset); - } - } - - // Interface Changes - { - saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); - saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); - saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); - saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); - saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); - int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); - if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game")) - Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex); - saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); - saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); - float uiScalingFactor = scalingSpinBox->value(); - if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI")) - Settings::Manager::setFloat("scaling factor", "GUI", uiScalingFactor); - } - - // Bug fixes - { - saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); - saveSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game"); - } - - // Miscellaneous - { - // Saves Settings - saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); - int maximumQuicksaves = maximumQuicksavesComboBox->value(); - if (maximumQuicksaves != Settings::Manager::getInt("max quicksaves", "Saves")) - { - Settings::Manager::setInt("max quicksaves", "Saves", maximumQuicksaves); - } - - // Other Settings - std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); - if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General")) - Settings::Manager::setString("screenshot format", "General", screenshotFormatString); - } - - // Testing - { - saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); - - int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; - if (skipMenu != mGameSettings.value("skip-menu").toInt()) - mGameSettings.setValue("skip-menu", QString::number(skipMenu)); - - QString startCell = startDefaultCharacterAtField->text(); - if (startCell != mGameSettings.value("start")) - { - mGameSettings.setValue("start", startCell); - } - QString scriptRun = runScriptAfterStartupField->text(); - if (scriptRun != mGameSettings.value("script-run")) - mGameSettings.setValue("script-run", scriptRun); - } -} - -void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) -{ - if (Settings::Manager::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 != Settings::Manager::getBool(setting, group)) - Settings::Manager::setBool(setting, group, cValue); -} - -void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) -{ - loadCellsForAutocomplete(cellNames); -} - -void Launcher::AdvancedPage::slotAnimSourcesToggled(bool checked) -{ - weaponSheathingCheckBox->setEnabled(checked); - shieldSheathingCheckBox->setEnabled(checked); - if (!checked) - { - weaponSheathingCheckBox->setCheckState(Qt::Unchecked); - shieldSheathingCheckBox->setCheckState(Qt::Unchecked); - } -} - -void Launcher::AdvancedPage::slotViewOverShoulderToggled(bool checked) -{ - viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState()); -} diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp deleted file mode 100644 index 9685dcefe..000000000 --- a/apps/launcher/advancedpage.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ADVANCEDPAGE_H -#define ADVANCEDPAGE_H - -#include -#include - -#include "ui_advancedpage.h" - -#include - -namespace Config { class GameSettings; } - -namespace Launcher -{ - class AdvancedPage : public QWidget, private Ui::AdvancedPage - { - Q_OBJECT - - public: - explicit AdvancedPage(Config::GameSettings &gameSettings, QWidget *parent = nullptr); - - bool loadSettings(); - void saveSettings(); - - public slots: - void slotLoadedCellsChanged(QStringList cellNames); - - private slots: - void on_skipMenuCheckBox_stateChanged(int state); - void on_runScriptAfterStartupBrowseButton_clicked(); - void slotAnimSourcesToggled(bool checked); - void slotViewOverShoulderToggled(bool checked); - - private: - Config::GameSettings &mGameSettings; - QCompleter mCellNameCompleter; - QStringListModel mCellNameCompleterModel; - - /** - * Load the cells associated with the given content files for use in autocomplete - * @param filePaths the file paths of the content files to be examined - */ - void loadCellsForAutocomplete(QStringList filePaths); - 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/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 956483a3f..d81f6dda5 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1,15 +1,18 @@ #include "datafilespage.hpp" +#include "maindialog.hpp" #include - -#include +#include #include -#include -#include -#include +#include + +#include #include +#include +#include #include + #include #include @@ -17,182 +20,403 @@ #include #include -#include -#include "utils/textinputdialog.hpp" +#include +#include +#include +#include +#include +#include +#include + #include "utils/profilescombobox.hpp" +#include "utils/textinputdialog.hpp" +#include "ui_directorypicker.h" -const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; +const char* Launcher::DataFilesPage::mDefaultContentListName = "Default"; -Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent) +namespace +{ + void contentSubdirs(const QString& path, QStringList& dirs) + { + QStringList fileFilter{ "*.esm", "*.esp", "*.omwaddon", "*.bsa", "*.omwscripts" }; + QStringList dirFilter{ "bookart", "icons", "meshes", "music", "sound", "textures" }; + + QDir currentDir(path); + if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty() + || !currentDir.entryInfoList(dirFilter, QDir::Dirs | QDir::NoDotAndDotDot).empty()) + dirs.push_back(currentDir.canonicalPath()); + + for (const auto& subdir : currentDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + contentSubdirs(subdir.canonicalFilePath(), dirs); + } +} + +namespace Launcher +{ + namespace + { + struct HandleNavMeshToolMessage + { + int mCellsCount; + int mExpectedMaxProgress; + int mMaxProgress; + int mProgress; + + HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedCells&& message) const + { + return HandleNavMeshToolMessage{ static_cast(message.mCount), mExpectedMaxProgress, + static_cast(message.mCount) * 100, mProgress }; + } + + HandleNavMeshToolMessage operator()(NavMeshTool::ProcessedCells&& message) const + { + return HandleNavMeshToolMessage{ mCellsCount, mExpectedMaxProgress, mMaxProgress, + std::max(mProgress, static_cast(message.mCount)) }; + } + + HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedTiles&& message) const + { + const int expectedMaxProgress = mCellsCount + static_cast(message.mCount); + return HandleNavMeshToolMessage{ mCellsCount, expectedMaxProgress, + std::max(mMaxProgress, expectedMaxProgress), mProgress }; + } + + HandleNavMeshToolMessage operator()(NavMeshTool::GeneratedTiles&& message) const + { + int progress = mCellsCount + static_cast(message.mCount); + if (mExpectedMaxProgress < mMaxProgress) + progress += static_cast(std::round((mMaxProgress - mExpectedMaxProgress) + * (static_cast(progress) / static_cast(mExpectedMaxProgress)))); + return HandleNavMeshToolMessage{ mCellsCount, mExpectedMaxProgress, mMaxProgress, + std::max(mProgress, progress) }; + } + }; + + int getMaxNavMeshDbFileSizeMiB() + { + return Settings::Manager::getUInt64("max navmeshdb file size", "Navigator") / (1024 * 1024); + } + + std::optional findFirstPath(const QStringList& directories, const QString& fileName) + { + for (const QString& directoryPath : directories) + { + const QString filePath = QDir(directoryPath).absoluteFilePath(fileName); + if (QFile::exists(filePath)) + return filePath; + } + return std::nullopt; + } + + QStringList findAllFilePaths(const QStringList& directories, const QStringList& fileNames) + { + QStringList result; + result.reserve(fileNames.size()); + for (const QString& fileName : fileNames) + if (const auto filepath = findFirstPath(directories, fileName)) + result.append(*filepath); + return result; + } + } +} + +Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings, + Config::LauncherSettings& launcherSettings, MainDialog* parent) : QWidget(parent) + , mMainDialog(parent) , mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) + , mNavMeshToolInvoker(new Process::ProcessInvoker(this)) { - ui.setupUi (this); - setObjectName ("DataFilesPage"); - mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); + ui.setupUi(this); + setObjectName("DataFilesPage"); + mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true); const QString encoding = mGameSettings.value("encoding", "win1252"); mSelector->setEncoding(encoding); + QStringList languages; + languages << tr("English") << tr("French") << tr("German") << tr("Italian") << tr("Polish") << tr("Russian") + << tr("Spanish"); + + mSelector->languageBox()->addItems(languages); + mNewProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); mCloneProfileDialog = new TextInputDialog(tr("Clone Content List"), tr("Content List name:"), this); - connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), - this, SLOT(updateNewProfileOkButton(QString))); - connect(mCloneProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), - this, SLOT(updateCloneProfileOkButton(QString))); + connect(mNewProfileDialog->lineEdit(), &LineEdit::textChanged, this, &DataFilesPage::updateNewProfileOkButton); + connect(mCloneProfileDialog->lineEdit(), &LineEdit::textChanged, this, &DataFilesPage::updateCloneProfileOkButton); + connect(ui.directoryAddSubdirsButton, &QPushButton::released, this, [this]() { this->addSubdirectories(true); }); + connect(ui.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); }); + connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); + connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); + connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); + connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchive(-1); }); + connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchive(1); }); + connect( + ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); buildView(); loadSettings(); // Connect signal and slot after the settings have been loaded. We only care about the user changing // the addons and don't want to get signals of the system doing it during startup. - connect(mSelector, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex)), - this, SLOT(slotAddonDataChanged())); + connect(mSelector, &ContentSelectorView::ContentSelector::signalAddonDataChanged, this, + &DataFilesPage::slotAddonDataChanged); // Call manually to indicate all changes to addon data during startup. slotAddonDataChanged(); } void Launcher::DataFilesPage::buildView() { - ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); + QToolButton* refreshButton = mSelector->refreshButton(); - QToolButton * refreshButton = mSelector->refreshButton(); + // tool buttons + ui.newProfileButton->setToolTip("Create a new Content List"); + ui.cloneProfileButton->setToolTip("Clone the current Content List"); + ui.deleteProfileButton->setToolTip("Delete an existing Content List"); - //tool buttons - ui.newProfileButton->setToolTip ("Create a new Content List"); - ui.cloneProfileButton->setToolTip ("Clone the current Content List"); - ui.deleteProfileButton->setToolTip ("Delete an existing Content List"); - refreshButton->setToolTip("Refresh Data Files"); - - //combo box + // combo box ui.profilesComboBox->addItem(mDefaultContentListName); - ui.profilesComboBox->setPlaceholderText (QString("Select a Content List...")); + ui.profilesComboBox->setPlaceholderText(QString("Select a Content List...")); ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String(mDefaultContentListName))); // Add the actions to the toolbuttons - ui.newProfileButton->setDefaultAction (ui.newProfileAction); - ui.cloneProfileButton->setDefaultAction (ui.cloneProfileAction); - ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); + ui.newProfileButton->setDefaultAction(ui.newProfileAction); + ui.cloneProfileButton->setDefaultAction(ui.cloneProfileAction); + ui.deleteProfileButton->setDefaultAction(ui.deleteProfileAction); refreshButton->setDefaultAction(ui.refreshDataFilesAction); - //establish connections - connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), - this, SLOT (slotProfileChanged(int))); + // establish connections + connect(ui.profilesComboBox, qOverload(&::ProfilesComboBox::currentIndexChanged), this, + &DataFilesPage::slotProfileChanged); - connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)), - this, SLOT (slotProfileRenamed(QString, QString))); + connect(ui.profilesComboBox, &::ProfilesComboBox::profileRenamed, this, &DataFilesPage::slotProfileRenamed); - connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), - this, SLOT (slotProfileChangedByUser(QString, QString))); + connect(ui.profilesComboBox, qOverload(&::ProfilesComboBox::signalProfileChanged), + this, &DataFilesPage::slotProfileChangedByUser); - connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked())); + connect(ui.refreshDataFilesAction, &QAction::triggered, this, &DataFilesPage::slotRefreshButtonClicked); + + connect(ui.updateNavMeshButton, &QPushButton::clicked, this, &DataFilesPage::startNavMeshTool); + connect(ui.cancelNavMeshButton, &QPushButton::clicked, this, &DataFilesPage::killNavMeshTool); + + connect(mNavMeshToolInvoker->getProcess(), &QProcess::readyReadStandardOutput, this, + &DataFilesPage::readNavMeshToolStdout); + connect(mNavMeshToolInvoker->getProcess(), &QProcess::readyReadStandardError, this, + &DataFilesPage::readNavMeshToolStderr); + connect(mNavMeshToolInvoker->getProcess(), qOverload(&QProcess::finished), this, + &DataFilesPage::navMeshToolFinished); } bool Launcher::DataFilesPage::loadSettings() { + ui.navMeshMaxSizeSpinBox->setValue(getMaxNavMeshDbFileSizeMiB()); + QStringList profiles = mLauncherSettings.getContentLists(); QString currentProfile = mLauncherSettings.getCurrentContentListName(); qDebug() << "The current profile is: " << currentProfile; - for (const QString &item : profiles) - addProfile (item, false); + for (const QString& item : profiles) + addProfile(item, false); // Hack: also add the current profile if (!currentProfile.isEmpty()) addProfile(currentProfile, true); + const int index = mSelector->languageBox()->findText(mLauncherSettings.getLanguage()); + if (index != -1) + mSelector->languageBox()->setCurrentIndex(index); + return true; } void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) { - QStringList paths = mGameSettings.getDataDirs(); + mSelector->clearFiles(); + ui.archiveListWidget->clear(); + ui.directoryListWidget->clear(); + + QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName); + if (directories.isEmpty()) + directories = mGameSettings.getDataDirs(); mDataLocal = mGameSettings.getDataLocal(); - if (!mDataLocal.isEmpty()) - paths.insert(0, mDataLocal); + directories.insert(0, mDataLocal); - mSelector->clearFiles(); + const auto& globalDataDir = mGameSettings.getGlobalDataDir(); + if (!globalDataDir.empty()) + directories.insert(0, Files::pathToQString(globalDataDir)); - for (const QString &path : paths) - mSelector->addFiles(path); - - PathIterator pathIterator(paths); - - mSelector->setProfileContent(filesInProfile(contentModelName, pathIterator)); -} - -QStringList Launcher::DataFilesPage::filesInProfile(const QString& profileName, PathIterator& pathIterator) -{ - QStringList files = mLauncherSettings.getContentListFiles(profileName); - QStringList filepaths; - - for (const QString& file : files) + std::unordered_set visitedDirectories; + for (const QString& currentDir : directories) { - QString filepath = pathIterator.findFirstPath(file); + // normalize user supplied directories: resolve symlink, convert to native separator, make absolute + const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath(); - if (!filepath.isEmpty()) - filepaths << filepath; - } + if (!visitedDirectories.insert(canonicalDirPath).second) + continue; - return filepaths; -} + // add new achives files presents in current directory + addArchivesFromDir(currentDir); -void Launcher::DataFilesPage::saveSettings(const QString &profile) -{ - QString profileName = profile; + QString tooltip; - if (profileName.isEmpty()) - profileName = ui.profilesComboBox->currentText(); + // add content files presents in current directory + mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath)); - //retrieve the files selected for the profile - ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); + // add current directory to list + ui.directoryListWidget->addItem(currentDir); + auto row = ui.directoryListWidget->count() - 1; + auto* item = ui.directoryListWidget->item(row); - //set the value of the current profile (not necessarily the profile being saved!) - mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText()); - - QStringList fileNames; - for (const ContentSelectorModel::EsmFile *item : items) - { - fileNames.append(item->fileName()); - } - mLauncherSettings.setContentList(profileName, fileNames); - mGameSettings.setContentList(fileNames); -} - -QStringList Launcher::DataFilesPage::selectedFilePaths() -{ - //retrieve the files selected for the profile - ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); - QStringList filePaths; - for (const ContentSelectorModel::EsmFile *item : items) - { - QFile file(item->filePath()); - - if(file.exists()) + // Display new content with green background + if (mNewDataDirs.contains(canonicalDirPath)) { - filePaths.append(item->filePath()); + tooltip += "Will be added to the current profile\n"; + item->setBackground(Qt::green); + item->setForeground(Qt::black); + } + + // deactivate data-local and global data directory: they are always included + if (currentDir == mDataLocal || Files::pathFromQString(currentDir) == globalDataDir) + { + auto flags = item->flags(); + item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); + } + + // Add a "data file" icon if the directory contains a content file + if (mSelector->containsDataFiles(currentDir)) + { + item->setIcon(QIcon(":/images/openmw-plugin.png")); + tooltip += "Contains content file(s)"; } else { - slotRefreshButtonClicked(); + // Pad to correct vertical alignment + QPixmap pixmap(QSize(200, 200)); // Arbitrary big number, will be scaled down to widget size + pixmap.fill(ui.directoryListWidget->palette().base().color()); + auto emptyIcon = QIcon(pixmap); + item->setIcon(emptyIcon); } + item->setToolTip(tooltip); } + mSelector->sortFiles(); + + QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName); + if (selectedArchives.isEmpty()) + selectedArchives = mGameSettings.getArchiveList(); + + // sort and tick BSA according to profile + int row = 0; + for (const auto& archive : selectedArchives) + { + const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly); + if (match.isEmpty()) + continue; + const auto name = match[0]->text(); + const auto oldrow = ui.archiveListWidget->row(match[0]); + ui.archiveListWidget->takeItem(oldrow); + ui.archiveListWidget->insertItem(row, name); + ui.archiveListWidget->item(row)->setCheckState(Qt::Checked); + row++; + } + + mSelector->setProfileContent( + findAllFilePaths(directories, mLauncherSettings.getContentListFiles(contentModelName))); +} + +void Launcher::DataFilesPage::saveSettings(const QString& profile) +{ + if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB()) + Settings::Manager::setUInt64( + "max navmeshdb file size", "Navigator", static_cast(std::max(0, value)) * 1024 * 1024); + + QString profileName = profile; + + if (profileName.isEmpty()) + profileName = ui.profilesComboBox->currentText(); + + // retrieve the data paths + auto dirList = selectedDirectoriesPaths(); + + // retrieve the files selected for the profile + ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); + + // set the value of the current profile (not necessarily the profile being saved!) + mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText()); + + QStringList fileNames; + for (const ContentSelectorModel::EsmFile* item : items) + { + fileNames.append(item->fileName()); + } + mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames); + mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames); + + QString language(mSelector->languageBox()->currentText()); + + mLauncherSettings.setLanguage(language); + + if (language == QLatin1String("Polish")) + { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + } + else if (language == QLatin1String("Russian")) + { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + } + else + { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + } +} + +QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const +{ + QStringList dirList; + for (int i = 0; i < ui.directoryListWidget->count(); ++i) + { + const QListWidgetItem* item = ui.directoryListWidget->item(i); + if (item->flags() & Qt::ItemIsEnabled) + dirList.append(item->text()); + } + return dirList; +} + +QStringList Launcher::DataFilesPage::selectedArchivePaths() const +{ + QStringList archiveList; + for (int i = 0; i < ui.archiveListWidget->count(); ++i) + { + const QListWidgetItem* item = ui.archiveListWidget->item(i); + if (item->checkState() == Qt::Checked) + archiveList.append(item->text()); + } + return archiveList; +} + +QStringList Launcher::DataFilesPage::selectedFilePaths() const +{ + // retrieve the files selected for the profile + ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); + QStringList filePaths; + for (const ContentSelectorModel::EsmFile* item : items) + if (QFile::exists(item->filePath())) + filePaths.append(item->filePath()); return filePaths; } -void Launcher::DataFilesPage::removeProfile(const QString &profile) +void Launcher::DataFilesPage::removeProfile(const QString& profile) { mLauncherSettings.removeContentList(profile); } -QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const +QAbstractItemModel* Launcher::DataFilesPage::profilesModel() const { return ui.profilesComboBox->model(); } @@ -211,50 +435,59 @@ void Launcher::DataFilesPage::setProfile(int index, bool savePrevious) mPreviousProfile = current; - setProfile (previous, current, savePrevious); + setProfile(previous, current, savePrevious); } } -void Launcher::DataFilesPage::setProfile (const QString &previous, const QString ¤t, bool savePrevious) +void Launcher::DataFilesPage::setProfile(const QString& previous, const QString& current, bool savePrevious) { - //abort if no change (poss. duplicate signal) + // abort if no change (poss. duplicate signal) if (previous == current) - return; + return; if (!previous.isEmpty() && savePrevious) - saveSettings (previous); + saveSettings(previous); - ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current)); + ui.profilesComboBox->setCurrentProfile(ui.profilesComboBox->findText(current)); + mNewDataDirs.clear(); + mKnownArchives.clear(); populateFileViews(current); + // save list of "old" bsa to be able to display "new" bsa in a different colour + for (int i = 0; i < ui.archiveListWidget->count(); ++i) + { + auto* item = ui.archiveListWidget->item(i); + mKnownArchives.push_back(item->text()); + } + checkForDefaultProfile(); } -void Launcher::DataFilesPage::slotProfileDeleted (const QString &item) +void Launcher::DataFilesPage::slotProfileDeleted(const QString& item) { - removeProfile (item); + removeProfile(item); } -void Launcher::DataFilesPage:: refreshDataFilesView () +void Launcher::DataFilesPage::refreshDataFilesView() { QString currentProfile = ui.profilesComboBox->currentText(); saveSettings(currentProfile); populateFileViews(currentProfile); } -void Launcher::DataFilesPage::slotRefreshButtonClicked () +void Launcher::DataFilesPage::slotRefreshButtonClicked() { refreshDataFilesView(); } -void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) +void Launcher::DataFilesPage::slotProfileChangedByUser(const QString& previous, const QString& current) { setProfile(previous, current, true); - emit signalProfileChanged (ui.profilesComboBox->findText(current)); + emit signalProfileChanged(ui.profilesComboBox->findText(current)); } -void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) +void Launcher::DataFilesPage::slotProfileRenamed(const QString& previous, const QString& current) { if (previous.isEmpty()) return; @@ -263,7 +496,7 @@ void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const saveSettings(); // Remove the old one - removeProfile (previous); + removeProfile(previous); loadSettings(); } @@ -274,7 +507,7 @@ void Launcher::DataFilesPage::slotProfileChanged(int index) if (ui.profilesComboBox->currentIndex() != index) ui.profilesComboBox->setCurrentIndex(index); - setProfile (index, true); + setProfile(index, true); } void Launcher::DataFilesPage::on_newProfileAction_triggered() @@ -294,16 +527,16 @@ void Launcher::DataFilesPage::on_newProfileAction_triggered() addProfile(profile, true); } -void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurrent) +void Launcher::DataFilesPage::addProfile(const QString& profile, bool setAsCurrent) { if (profile.isEmpty()) return; - if (ui.profilesComboBox->findText (profile) == -1) - ui.profilesComboBox->addItem (profile); + if (ui.profilesComboBox->findText(profile) == -1) + ui.profilesComboBox->addItem(profile); if (setAsCurrent) - setProfile (ui.profilesComboBox->findText (profile), false); + setProfile(ui.profilesComboBox->findText(profile), false); } void Launcher::DataFilesPage::on_cloneProfileAction_triggered() @@ -316,7 +549,7 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered() if (profile.isEmpty()) return; - mLauncherSettings.setContentList(profile, selectedFilePaths()); + mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths()); addProfile(profile, true); } @@ -327,11 +560,11 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() if (profile.isEmpty()) return; - if (!showDeleteMessageBox (profile)) + if (!showDeleteMessageBox(profile)) return; // this should work since the Default profile can't be deleted and is always index 0 - int next = ui.profilesComboBox->currentIndex()-1; + int next = ui.profilesComboBox->currentIndex() - 1; // changing the profile forces a reload of plugin file views. ui.profilesComboBox->setCurrentIndex(next); @@ -342,28 +575,182 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() checkForDefaultProfile(); } -void Launcher::DataFilesPage::updateNewProfileOkButton(const QString &text) +void Launcher::DataFilesPage::updateNewProfileOkButton(const QString& text) { // We do this here because we need the profiles combobox text mNewProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } -void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString &text) +void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString& text) { // We do this here because we need the profiles combobox text mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } -void Launcher::DataFilesPage::checkForDefaultProfile() +QString Launcher::DataFilesPage::selectDirectory() { - //don't allow deleting "Default" profile - bool success = (ui.profilesComboBox->currentText() != mDefaultContentListName); + QFileDialog fileDialog(this); + fileDialog.setFileMode(QFileDialog::Directory); + fileDialog.setOptions(QFileDialog::Option::ShowDirsOnly | QFileDialog::Option::ReadOnly); - ui.deleteProfileAction->setEnabled (success); - ui.profilesComboBox->setEditEnabled (success); + if (fileDialog.exec() == QDialog::Rejected) + return {}; + + return QDir(fileDialog.selectedFiles()[0]).canonicalPath(); } -bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) +void Launcher::DataFilesPage::addSubdirectories(bool append) +{ + int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow(); + + if (selectedRow == -1) + return; + + const auto rootDir = selectDirectory(); + if (rootDir.isEmpty()) + return; + + QStringList subdirs; + contentSubdirs(rootDir, subdirs); + + if (subdirs.empty()) + { + // we didn't find anything that looks like a content directory, add directory selected by user + if (ui.directoryListWidget->findItems(rootDir, Qt::MatchFixedString).isEmpty()) + { + ui.directoryListWidget->addItem(rootDir); + mNewDataDirs.push_back(rootDir); + refreshDataFilesView(); + } + return; + } + + QDialog dialog; + Ui::SelectSubdirs select; + + select.setupUi(&dialog); + + for (const auto& dir : subdirs) + { + if (!ui.directoryListWidget->findItems(dir, Qt::MatchFixedString).isEmpty()) + continue; + const auto lastRow = select.dirListWidget->count(); + select.dirListWidget->addItem(dir); + select.dirListWidget->item(lastRow)->setCheckState(Qt::Unchecked); + } + + dialog.show(); + + if (dialog.exec() == QDialog::Rejected) + return; + + for (int i = 0; i < select.dirListWidget->count(); ++i) + { + const auto* dir = select.dirListWidget->item(i); + if (dir->checkState() == Qt::Checked) + { + ui.directoryListWidget->insertItem(selectedRow++, dir->text()); + mNewDataDirs.push_back(dir->text()); + } + } + + refreshDataFilesView(); +} + +void Launcher::DataFilesPage::sortDirectories() +{ + // Ensure disabled entries (aka default directories) are always at the top. + for (auto i = 1; i < ui.directoryListWidget->count(); ++i) + { + if (!(ui.directoryListWidget->item(i)->flags() & Qt::ItemIsEnabled) + && (ui.directoryListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled)) + { + const auto item = ui.directoryListWidget->takeItem(i); + ui.directoryListWidget->insertItem(i - 1, item); + ui.directoryListWidget->setCurrentRow(i); + } + } +} + +void Launcher::DataFilesPage::moveDirectory(int step) +{ + int selectedRow = ui.directoryListWidget->currentRow(); + int newRow = selectedRow + step; + if (selectedRow == -1 || newRow < 0 || newRow > ui.directoryListWidget->count() - 1) + return; + + if (!(ui.directoryListWidget->item(newRow)->flags() & Qt::ItemIsEnabled)) + return; + + const auto item = ui.directoryListWidget->takeItem(selectedRow); + ui.directoryListWidget->insertItem(newRow, item); + ui.directoryListWidget->setCurrentRow(newRow); +} + +void Launcher::DataFilesPage::removeDirectory() +{ + for (const auto& path : ui.directoryListWidget->selectedItems()) + ui.directoryListWidget->takeItem(ui.directoryListWidget->row(path)); + refreshDataFilesView(); +} + +void Launcher::DataFilesPage::moveArchive(int step) +{ + int selectedRow = ui.archiveListWidget->currentRow(); + int newRow = selectedRow + step; + if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) + return; + + const auto* item = ui.archiveListWidget->takeItem(selectedRow); + + addArchive(item->text(), item->checkState(), newRow); + ui.archiveListWidget->setCurrentRow(newRow); +} + +void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row) +{ + if (row == -1) + row = ui.archiveListWidget->count(); + ui.archiveListWidget->insertItem(row, name); + ui.archiveListWidget->item(row)->setCheckState(selected); + if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? + { + ui.archiveListWidget->item(row)->setBackground(Qt::green); + ui.archiveListWidget->item(row)->setForeground(Qt::black); + } +} + +void Launcher::DataFilesPage::addArchivesFromDir(const QString& path) +{ + QDir dir(path, "*.bsa"); + + std::unordered_set archives; + for (int i = 0; i < ui.archiveListWidget->count(); ++i) + archives.insert(ui.archiveListWidget->item(i)->text()); + + for (const auto& fileinfo : dir.entryInfoList()) + { + const auto absPath = fileinfo.absoluteFilePath(); + if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BSAVER_UNKNOWN) + continue; + + const auto fileName = fileinfo.fileName(); + + if (archives.insert(fileName).second) + addArchive(fileName, Qt::Unchecked); + } +} + +void Launcher::DataFilesPage::checkForDefaultProfile() +{ + // don't allow deleting "Default" profile + bool success = (ui.profilesComboBox->currentText() != mDefaultContentListName); + + ui.deleteProfileAction->setEnabled(success); + ui.profilesComboBox->setEditEnabled(success); +} + +bool Launcher::DataFilesPage::showDeleteMessageBox(const QString& text) { QMessageBox msgBox(this); msgBox.setWindowTitle(tr("Delete Content List")); @@ -371,8 +758,7 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText(tr("Are you sure you want to delete %1?").arg(text)); - QAbstractButton *deleteButton = - msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); + QAbstractButton* deleteButton = msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); msgBox.exec(); @@ -382,7 +768,8 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) void Launcher::DataFilesPage::slotAddonDataChanged() { QStringList selectedFiles = selectedFilePaths(); - if (previousSelectedFiles != selectedFiles) { + if (previousSelectedFiles != selectedFiles) + { previousSelectedFiles = selectedFiles; // Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a // barely perceptible UI lag. Splitting into its own thread to alleviate that. @@ -392,22 +779,120 @@ void Launcher::DataFilesPage::slotAddonDataChanged() } // Mutex lock to run reloadCells synchronously. -std::mutex _reloadCellsMutex; +static std::mutex reloadCellsMutex; void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles) { // Use a mutex lock so that we can prevent two threads from executing the rest of this code at the same time // Based on https://stackoverflow.com/a/5429695/531762 - std::unique_lock lock(_reloadCellsMutex); + std::unique_lock lock(reloadCellsMutex); // The following code will run only if there is not another thread currently running it CellNameLoader cellNameLoader; -#if QT_VERSION >= QT_VERSION_CHECK(5,14,0) QSet set = cellNameLoader.getCellNames(selectedFiles); QStringList cellNamesList(set.begin(), set.end()); -#else - QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles)); -#endif std::sort(cellNamesList.begin(), cellNamesList.end()); emit signalLoadedCellsChanged(cellNamesList); } + +void Launcher::DataFilesPage::startNavMeshTool() +{ + mMainDialog->writeSettings(); + + ui.navMeshLogPlainTextEdit->clear(); + ui.navMeshProgressBar->setValue(0); + ui.navMeshProgressBar->setMaximum(1); + ui.navMeshProgressBar->resetFormat(); + + mNavMeshToolProgress = NavMeshToolProgress{}; + + QStringList arguments({ "--write-binary-log" }); + if (ui.navMeshRemoveUnusedTilesCheckBox->checkState() == Qt::Checked) + arguments.append("--remove-unused-tiles"); + + if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"), arguments)) + return; + + ui.cancelNavMeshButton->setEnabled(true); + ui.navMeshProgressBar->setEnabled(true); +} + +void Launcher::DataFilesPage::killNavMeshTool() +{ + mNavMeshToolInvoker->killProcess(); +} + +void Launcher::DataFilesPage::readNavMeshToolStderr() +{ + updateNavMeshProgress(4096); +} + +void Launcher::DataFilesPage::updateNavMeshProgress(int minDataSize) +{ + if (!mNavMeshToolProgress.mEnabled) + return; + QProcess& process = *mNavMeshToolInvoker->getProcess(); + mNavMeshToolProgress.mMessagesData.append(process.readAllStandardError()); + if (mNavMeshToolProgress.mMessagesData.size() < minDataSize) + return; + const std::byte* const begin = reinterpret_cast(mNavMeshToolProgress.mMessagesData.constData()); + const std::byte* const end = begin + mNavMeshToolProgress.mMessagesData.size(); + const std::byte* position = begin; + HandleNavMeshToolMessage handle{ + mNavMeshToolProgress.mCellsCount, + mNavMeshToolProgress.mExpectedMaxProgress, + ui.navMeshProgressBar->maximum(), + ui.navMeshProgressBar->value(), + }; + try + { + while (true) + { + NavMeshTool::Message message; + const std::byte* const nextPosition = NavMeshTool::deserialize(position, end, message); + if (nextPosition == position) + break; + position = nextPosition; + handle = std::visit(handle, NavMeshTool::decode(message)); + } + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Failed to deserialize navmeshtool message: " << e.what(); + mNavMeshToolProgress.mEnabled = false; + ui.navMeshProgressBar->setFormat("Failed to update progress: " + QString(e.what())); + } + if (position != begin) + mNavMeshToolProgress.mMessagesData = mNavMeshToolProgress.mMessagesData.mid(position - begin); + mNavMeshToolProgress.mCellsCount = handle.mCellsCount; + mNavMeshToolProgress.mExpectedMaxProgress = handle.mExpectedMaxProgress; + ui.navMeshProgressBar->setMaximum(handle.mMaxProgress); + ui.navMeshProgressBar->setValue(handle.mProgress); +} + +void Launcher::DataFilesPage::readNavMeshToolStdout() +{ + QProcess& process = *mNavMeshToolInvoker->getProcess(); + QByteArray& logData = mNavMeshToolProgress.mLogData; + logData.append(process.readAllStandardOutput()); + const int lineEnd = logData.lastIndexOf('\n'); + if (lineEnd == -1) + return; + const int size = logData.size() >= lineEnd && logData[lineEnd - 1] == '\r' ? lineEnd - 1 : lineEnd; + ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(logData.data(), size)); + logData = logData.mid(lineEnd + 1); +} + +void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + updateNavMeshProgress(0); + ui.navMeshLogPlainTextEdit->appendPlainText( + QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAllStandardOutput())); + if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit) + { + ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum()); + ui.navMeshProgressBar->resetFormat(); + } + ui.cancelNavMeshButton->setEnabled(false); + ui.navMeshProgressBar->setEnabled(false); +} diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 5a7a6dc6e..033c91f9c 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -2,23 +2,34 @@ #define DATAFILESPAGE_H #include "ui_datafilespage.h" -#include +#include #include #include +#include class QSortFilterProxyModel; class QAbstractItemModel; class QMenu; -namespace Files { struct ConfigurationManager; } -namespace ContentSelectorView { class ContentSelector; } -namespace Config { class GameSettings; - class LauncherSettings; } +namespace Files +{ + struct ConfigurationManager; +} +namespace ContentSelectorView +{ + class ContentSelector; +} +namespace Config +{ + class GameSettings; + class LauncherSettings; +} namespace Launcher { + class MainDialog; class TextInputDialog; class ProfilesComboBox; @@ -26,131 +37,109 @@ namespace Launcher { Q_OBJECT - ContentSelectorView::ContentSelector *mSelector; + ContentSelectorView::ContentSelector* mSelector; Ui::DataFilesPage ui; public: - explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, - Config::LauncherSettings &launcherSettings, QWidget *parent = nullptr); + explicit DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings, + Config::LauncherSettings& launcherSettings, MainDialog* parent = nullptr); QAbstractItemModel* profilesModel() const; int profilesIndex() const; - //void writeConfig(QString profile = QString()); - void saveSettings(const QString &profile = ""); + // void writeConfig(QString profile = QString()); + void saveSettings(const QString& profile = ""); bool loadSettings(); - /** - * Returns the file paths of all selected content files - * @return the file paths of all selected content files - */ - QStringList selectedFilePaths(); - signals: - void signalProfileChanged (int index); + void signalProfileChanged(int index); void signalLoadedCellsChanged(QStringList selectedFiles); public slots: - void slotProfileChanged (int index); + void slotProfileChanged(int index); private slots: - void slotProfileChangedByUser(const QString &previous, const QString ¤t); - void slotProfileRenamed(const QString &previous, const QString ¤t); - void slotProfileDeleted(const QString &item); - void slotAddonDataChanged (); - void slotRefreshButtonClicked (); + void slotProfileChangedByUser(const QString& previous, const QString& current); + void slotProfileRenamed(const QString& previous, const QString& current); + void slotProfileDeleted(const QString& item); + void slotAddonDataChanged(); + void slotRefreshButtonClicked(); - void updateNewProfileOkButton(const QString &text); - void updateCloneProfileOkButton(const QString &text); + void updateNewProfileOkButton(const QString& text); + void updateCloneProfileOkButton(const QString& text); + void addSubdirectories(bool append); + void sortDirectories(); + void removeDirectory(); + void moveArchive(int step); + void moveDirectory(int step); void on_newProfileAction_triggered(); void on_cloneProfileAction_triggered(); void on_deleteProfileAction_triggered(); + void startNavMeshTool(); + void killNavMeshTool(); + void readNavMeshToolStdout(); + void readNavMeshToolStderr(); + void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus); + public: /// Content List that is always present - const static char *mDefaultContentListName; + const static char* mDefaultContentListName; private: + struct NavMeshToolProgress + { + bool mEnabled = true; + QByteArray mLogData; + QByteArray mMessagesData; + std::map mWorldspaces; + int mCellsCount = 0; + int mExpectedMaxProgress = 0; + }; - TextInputDialog *mNewProfileDialog; - TextInputDialog *mCloneProfileDialog; + MainDialog* mMainDialog; + TextInputDialog* mNewProfileDialog; + TextInputDialog* mCloneProfileDialog; - Files::ConfigurationManager &mCfgMgr; + const Files::ConfigurationManager& mCfgMgr; - Config::GameSettings &mGameSettings; - Config::LauncherSettings &mLauncherSettings; + Config::GameSettings& mGameSettings; + Config::LauncherSettings& mLauncherSettings; QString mPreviousProfile; QStringList previousSelectedFiles; QString mDataLocal; + QStringList mKnownArchives; + QStringList mNewDataDirs; + Process::ProcessInvoker* mNavMeshToolInvoker; + NavMeshToolProgress mNavMeshToolProgress; + + void addArchive(const QString& name, Qt::CheckState selected, int row = -1); + void addArchivesFromDir(const QString& dir); void buildView(); - void setProfile (int index, bool savePrevious); - void setProfile (const QString &previous, const QString ¤t, bool savePrevious); - void removeProfile (const QString &profile); - bool showDeleteMessageBox (const QString &text); - void addProfile (const QString &profile, bool setAsCurrent); + void setProfile(int index, bool savePrevious); + void setProfile(const QString& previous, const QString& current, bool savePrevious); + void removeProfile(const QString& profile); + bool showDeleteMessageBox(const QString& text); + void addProfile(const QString& profile, bool setAsCurrent); void checkForDefaultProfile(); void populateFileViews(const QString& contentModelName); void reloadCells(QStringList selectedFiles); - void refreshDataFilesView (); + void refreshDataFilesView(); + void updateNavMeshProgress(int minDataSize); + QString selectDirectory(); - class PathIterator - { - QStringList::ConstIterator mCitEnd; - QStringList::ConstIterator mCitCurrent; - QStringList::ConstIterator mCitBegin; - QString mFile; - QString mFilePath; - - public: - PathIterator (const QStringList &list) - { - mCitBegin = list.constBegin(); - mCitCurrent = mCitBegin; - mCitEnd = list.constEnd(); - } - - QString findFirstPath (const QString &file) - { - mCitCurrent = mCitBegin; - mFile = file; - return path(); - } - - QString findNextPath () { return path(); } - - private: - - QString path () - { - bool success = false; - QDir dir; - QFileInfo file; - - while (!success) - { - if (mCitCurrent == mCitEnd) - break; - - dir.setPath (*(mCitCurrent++)); - file.setFile (dir.absoluteFilePath (mFile)); - - success = file.exists(); - } - - if (success) - return file.absoluteFilePath(); - - return ""; - } - - }; - - QStringList filesInProfile(const QString& profileName, PathIterator& pathIterator); + /** + * Returns the file paths of all selected content files + * @return the file paths of all selected content files + */ + QStringList selectedFilePaths() const; + QStringList selectedArchivePaths() const; + QStringList selectedDirectoriesPaths() const; }; } #endif diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index ebb031e9e..5f37c0def 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -1,8 +1,8 @@ #include "graphicspage.hpp" -#include +#include "sdlinit.hpp" + #include -#include #include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED @@ -13,13 +13,12 @@ #include +#include #include -#include - QString getAspect(int x, int y) { - int gcd = std::gcd (x, y); + int gcd = std::gcd(x, y); if (gcd == 0) return QString(); @@ -32,10 +31,10 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -Launcher::GraphicsPage::GraphicsPage(QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(QWidget* parent) : QWidget(parent) { - setObjectName ("GraphicsPage"); + setObjectName("GraphicsPage"); setupUi(this); // Set the maximum res we can set in windowed mode @@ -43,12 +42,12 @@ Launcher::GraphicsPage::GraphicsPage(QWidget *parent) customWidthSpinBox->setMaximum(res.width()); customHeightSpinBox->setMaximum(res.height()); - connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int))); - connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool))); - connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int))); - connect(framerateLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotFramerateLimitToggled(bool))); - connect(shadowDistanceCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotShadowDistLimitToggled(bool))); - + connect(windowModeComboBox, qOverload(&QComboBox::currentIndexChanged), this, + &GraphicsPage::slotFullScreenChanged); + connect(standardRadioButton, &QRadioButton::toggled, this, &GraphicsPage::slotStandardToggled); + connect(screenComboBox, qOverload(&QComboBox::currentIndexChanged), this, &GraphicsPage::screenChanged); + connect(framerateLimitCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotFramerateLimitToggled); + connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotShadowDistLimitToggled); } bool Launcher::GraphicsPage::setupSDL() @@ -67,7 +66,8 @@ bool Launcher::GraphicsPage::setupSDL() msgBox.setWindowTitle(tr("Error receiving number of screens")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
SDL_GetNumVideoDisplays failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); + msgBox.setText( + tr("
SDL_GetNumVideoDisplays failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return false; } @@ -93,11 +93,18 @@ bool Launcher::GraphicsPage::loadSettings() return false; // Visuals - if (Settings::Manager::getBool("vsync", "Video")) - vSyncCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("fullscreen", "Video")) - fullScreenCheckBox->setCheckState(Qt::Checked); + int vsync = Settings::Manager::getInt("vsync mode", "Video"); + if (vsync < 0 || vsync > 2) + vsync = 0; + + vSyncComboBox->setCurrentIndex(vsync); + + size_t windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); + if (windowMode > static_cast(Settings::WindowMode::Windowed)) + windowMode = 0; + windowModeComboBox->setCurrentIndex(windowMode); + slotFullScreenChanged(windowMode); if (Settings::Manager::getBool("window border", "Video")) windowBorderCheckBox->setCheckState(Qt::Checked); @@ -116,10 +123,13 @@ bool Launcher::GraphicsPage::loadSettings() int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); - if (resIndex != -1) { + if (resIndex != -1) + { standardRadioButton->toggle(); resolutionComboBox->setCurrentIndex(resIndex); - } else { + } + else + { customRadioButton->toggle(); customWidthSpinBox->setValue(width); customHeightSpinBox->setValue(height); @@ -152,9 +162,8 @@ bool Launcher::GraphicsPage::loadSettings() if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) indoorShadowsCheckBox->setCheckState(Qt::Checked); - shadowComputeSceneBoundsComboBox->setCurrentIndex( - shadowComputeSceneBoundsComboBox->findText( - QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str())))); + shadowComputeSceneBoundsComboBox->setCurrentIndex(shadowComputeSceneBoundsComboBox->findText( + QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str())))); int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows"); if (shadowDistLimit > 0) @@ -181,13 +190,13 @@ void Launcher::GraphicsPage::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) - bool cVSync = vSyncCheckBox->checkState(); - if (cVSync != Settings::Manager::getBool("vsync", "Video")) - Settings::Manager::setBool("vsync", "Video", cVSync); + int cVSync = vSyncComboBox->currentIndex(); + if (cVSync != Settings::Manager::getInt("vsync mode", "Video")) + Settings::Manager::setInt("vsync mode", "Video", cVSync); - bool cFullScreen = fullScreenCheckBox->checkState(); - if (cFullScreen != Settings::Manager::getBool("fullscreen", "Video")) - Settings::Manager::setBool("fullscreen", "Video", cFullScreen); + int cWindowMode = windowModeComboBox->currentIndex(); + if (cWindowMode != Settings::Manager::getInt("window mode", "Video")) + Settings::Manager::setInt("window mode", "Video", cWindowMode); bool cWindowBorder = windowBorderCheckBox->checkState(); if (cWindowBorder != Settings::Manager::getBool("window border", "Video")) @@ -199,13 +208,18 @@ void Launcher::GraphicsPage::saveSettings() int cWidth = 0; int cHeight = 0; - if (standardRadioButton->isChecked()) { - QRegExp resolutionRe(QString("(\\d+) x (\\d+).*")); - if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) { - cWidth = resolutionRe.cap(1).toInt(); - cHeight = resolutionRe.cap(2).toInt(); + if (standardRadioButton->isChecked()) + { + QRegularExpression resolutionRe("^(\\d+) x (\\d+)"); + QRegularExpressionMatch match = resolutionRe.match(resolutionComboBox->currentText().simplified()); + if (match.hasMatch()) + { + cWidth = match.captured(1).toInt(); + cHeight = match.captured(2).toInt(); } - } else { + } + else + { cWidth = customWidthSpinBox->value(); cHeight = customHeightSpinBox->value(); } @@ -232,8 +246,10 @@ void Launcher::GraphicsPage::saveSettings() } // Lighting - static std::array lightingMethodMap = {"legacy", "shaders compatibility", "shaders"}; - Settings::Manager::setString("lighting method", "Shaders", lightingMethodMap[lightingMethodComboBox->currentIndex()]); + static std::array lightingMethodMap = { "legacy", "shaders compatibility", "shaders" }; + const std::string& cLightingMethod = lightingMethodMap[lightingMethodComboBox->currentIndex()]; + if (cLightingMethod != Settings::Manager::getString("lighting method", "Shaders")) + Settings::Manager::setString("lighting method", "Shaders", cLightingMethod); // Shadows int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; @@ -299,7 +315,8 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) msgBox.setWindowTitle(tr("Error receiving resolutions")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
SDL_GetNumDisplayModes failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); + msgBox.setText( + tr("
SDL_GetNumDisplayModes failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return result; } @@ -312,7 +329,8 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) msgBox.setWindowTitle(tr("Error receiving resolutions")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
SDL_GetDisplayMode failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); + msgBox.setText( + tr("
SDL_GetDisplayMode failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return result; } @@ -320,10 +338,12 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h); QString aspect = getAspect(mode.w, mode.h); - if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { + if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) + { resolution.append(tr("\t(Wide ") + aspect + ")"); - - } else if (aspect == QLatin1String("4:3")) { + } + else if (aspect == QLatin1String("4:3")) + { resolution.append(tr("\t(Standard 4:3)")); } @@ -351,21 +371,26 @@ QRect Launcher::GraphicsPage::getMaximumResolution() void Launcher::GraphicsPage::screenChanged(int screen) { - if (screen >= 0) { + if (screen >= 0) + { resolutionComboBox->clear(); resolutionComboBox->addItems(mResolutionsPerScreen[screen]); } } -void Launcher::GraphicsPage::slotFullScreenChanged(int state) +void Launcher::GraphicsPage::slotFullScreenChanged(int mode) { - if (state == Qt::Checked) { + if (mode == static_cast(Settings::WindowMode::Fullscreen) + || mode == static_cast(Settings::WindowMode::WindowedFullscreen)) + { standardRadioButton->toggle(); customRadioButton->setEnabled(false); customWidthSpinBox->setEnabled(false); customHeightSpinBox->setEnabled(false); windowBorderCheckBox->setEnabled(false); - } else { + } + else + { customRadioButton->setEnabled(true); customWidthSpinBox->setEnabled(true); customHeightSpinBox->setEnabled(true); @@ -375,11 +400,14 @@ void Launcher::GraphicsPage::slotFullScreenChanged(int state) void Launcher::GraphicsPage::slotStandardToggled(bool checked) { - if (checked) { + if (checked) + { resolutionComboBox->setEnabled(true); customWidthSpinBox->setEnabled(false); customHeightSpinBox->setEnabled(false); - } else { + } + else + { resolutionComboBox->setEnabled(false); customWidthSpinBox->setEnabled(true); customHeightSpinBox->setEnabled(true); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index a6754ccb0..92bdf35ac 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -5,9 +5,10 @@ #include -#include "sdlinit.hpp" - -namespace Files { struct ConfigurationManager; } +namespace Files +{ + struct ConfigurationManager; +} namespace Launcher { @@ -18,7 +19,7 @@ namespace Launcher Q_OBJECT public: - explicit GraphicsPage(QWidget *parent = nullptr); + explicit GraphicsPage(QWidget* parent = nullptr); void saveSettings(); bool loadSettings(); diff --git a/apps/launcher/importpage.cpp b/apps/launcher/importpage.cpp new file mode 100644 index 000000000..af7993875 --- /dev/null +++ b/apps/launcher/importpage.cpp @@ -0,0 +1,230 @@ +#include "importpage.hpp" + +#include +#include +#include +#include + +#include +#include + +#include "utils/textinputdialog.hpp" + +using namespace Process; + +Launcher::ImportPage::ImportPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings, + Config::LauncherSettings& launcherSettings, MainDialog* parent) + : QWidget(parent) + , mCfgMgr(cfg) + , mGameSettings(gameSettings) + , mLauncherSettings(launcherSettings) + , mMain(parent) +{ + setupUi(this); + + mWizardInvoker = new ProcessInvoker(); + mImporterInvoker = new ProcessInvoker(); + resetProgressBar(); + + connect(mWizardInvoker->getProcess(), &QProcess::started, this, &ImportPage::wizardStarted); + + connect(mWizardInvoker->getProcess(), qOverload(&QProcess::finished), this, + &ImportPage::wizardFinished); + + connect(mImporterInvoker->getProcess(), &QProcess::started, this, &ImportPage::importerStarted); + + connect(mImporterInvoker->getProcess(), qOverload(&QProcess::finished), this, + &ImportPage::importerFinished); + + // Detect Morrowind configuration files + QStringList iniPaths; + + for (const QString& path : mGameSettings.getDataDirs()) + { + QDir dir(path); + dir.setPath(dir.canonicalPath()); // Resolve symlinks + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + else + { + if (!dir.cdUp()) + continue; // Cannot move from Data Files + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + } + } + + if (!iniPaths.isEmpty()) + { + settingsComboBox->addItems(iniPaths); + importerButton->setEnabled(true); + } + else + { + importerButton->setEnabled(false); + } + + loadSettings(); +} + +Launcher::ImportPage::~ImportPage() +{ + delete mWizardInvoker; + delete mImporterInvoker; +} + +void Launcher::ImportPage::on_wizardButton_clicked() +{ + mMain->writeSettings(); + + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) + return; +} + +void Launcher::ImportPage::on_importerButton_clicked() +{ + mMain->writeSettings(); + + // Create the file if it doesn't already exist, else the importer will fail + auto path = mCfgMgr.getUserConfigPath(); + path /= "openmw.cfg"; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QFile file(path); +#else + QFile file(Files::pathToQString(path)); +#endif + + if (!file.exists()) + { + if (!file.open(QIODevice::ReadWrite)) + { + // File cannot be created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText( + tr("

Could not open or create %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

") + .arg(file.fileName())); + msgBox.exec(); + return; + } + + file.close(); + } + + // Construct the arguments to run the importer + QStringList arguments; + + if (addonsCheckBox->isChecked()) + arguments.append(QString("--game-files")); + + if (fontsCheckBox->isChecked()) + arguments.append(QString("--fonts")); + + arguments.append(QString("--encoding")); + arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); + arguments.append(QString("--ini")); + arguments.append(settingsComboBox->currentText()); + arguments.append(QString("--cfg")); + arguments.append(Files::pathToQString(path)); + + qDebug() << "arguments " << arguments; + + // start the progress bar as a "bouncing ball" + progressBar->setMaximum(0); + progressBar->setValue(0); + if (!mImporterInvoker->startProcess(QLatin1String("openmw-iniimporter"), arguments, false)) + { + resetProgressBar(); + } +} + +void Launcher::ImportPage::on_browseButton_clicked() +{ + QString iniFile = QFileDialog::getOpenFileName(this, QObject::tr("Select configuration file"), QDir::currentPath(), + QString(tr("Morrowind configuration file (*.ini)"))); + + if (iniFile.isEmpty()) + return; + + QFileInfo info(iniFile); + + if (!info.exists() || !info.isReadable()) + return; + + const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); + + if (settingsComboBox->findText(path) == -1) + { + settingsComboBox->addItem(path); + settingsComboBox->setCurrentIndex(settingsComboBox->findText(path)); + importerButton->setEnabled(true); + } +} + +void Launcher::ImportPage::wizardStarted() +{ + mMain->hide(); // Hide the launcher + + wizardButton->setEnabled(false); +} + +void Launcher::ImportPage::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return qApp->quit(); + + mMain->reloadSettings(); + wizardButton->setEnabled(true); + + mMain->show(); // Show the launcher again +} + +void Launcher::ImportPage::importerStarted() +{ + importerButton->setEnabled(false); +} + +void Launcher::ImportPage::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + { + resetProgressBar(); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Importer finished")); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setText(tr("Failed to import settings from INI file.")); + msgBox.exec(); + } + else + { + // indicate progress finished + progressBar->setMaximum(1); + progressBar->setValue(1); + + // Importer may have changed settings, so refresh + mMain->reloadSettings(); + } + + importerButton->setEnabled(true); +} + +void Launcher::ImportPage::resetProgressBar() +{ + // set progress bar to 0 % + progressBar->reset(); +} + +void Launcher::ImportPage::saveSettings() {} + +bool Launcher::ImportPage::loadSettings() +{ + return true; +} diff --git a/apps/launcher/importpage.hpp b/apps/launcher/importpage.hpp new file mode 100644 index 000000000..f3e16e42b --- /dev/null +++ b/apps/launcher/importpage.hpp @@ -0,0 +1,64 @@ +#ifndef IMPORTSPAGE_HPP +#define IMPORTSPAGE_HPP + +#include + +#include "ui_importpage.h" + +#include "maindialog.hpp" + +namespace Files +{ + struct ConfigurationManager; +} +namespace Config +{ + class GameSettings; + class LauncherSettings; +} + +namespace Launcher +{ + class TextInputDialog; + + class ImportPage : public QWidget, private Ui::ImportPage + { + Q_OBJECT + + public: + explicit ImportPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings, + Config::LauncherSettings& launcherSettings, MainDialog* parent = nullptr); + ~ImportPage() override; + + void saveSettings(); + bool loadSettings(); + + /// set progress bar on page to 0% + void resetProgressBar(); + + private slots: + + void on_wizardButton_clicked(); + void on_importerButton_clicked(); + void on_browseButton_clicked(); + + void wizardStarted(); + void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); + + void importerStarted(); + void importerFinished(int exitCode, QProcess::ExitStatus exitStatus); + + private: + Process::ProcessInvoker* mWizardInvoker; + Process::ProcessInvoker* mImporterInvoker; + + const Files::ConfigurationManager& mCfgMgr; + + Config::GameSettings& mGameSettings; + Config::LauncherSettings& mLauncherSettings; + + MainDialog* mMain; + }; +} + +#endif // IMPORTSPAGE_HPP diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 9c9acb4a1..4aac90fb6 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,8 +1,14 @@ #include -#include -#include #include +#include + +#include +#include + +#include +#include +#include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED #undef MAC_OS_X_VERSION_MIN_REQUIRED @@ -12,13 +18,23 @@ #include "maindialog.hpp" -int main(int argc, char *argv[]) +int runLauncher(int argc, char* argv[]) { + Platform::init(); + + boost::program_options::variables_map variables; + boost::program_options::options_description description; + Files::ConfigurationManager configurationManager; + configurationManager.addCommonOptions(description); + configurationManager.readConfiguration(variables, description, true); + + setupLogging(configurationManager.getLogPath(), "Launcher"); + try { QApplication app(argc, argv); - // Internationalization + // Internationalization QString locale = QLocale::system().name().section('_', 0, 0); QTranslator appTranslator; @@ -30,7 +46,7 @@ int main(int argc, char *argv[]) QDir::setCurrent(dir.absolutePath()); - Launcher::MainDialog mainWin; + Launcher::MainDialog mainWin(configurationManager); Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog(); if (result == Launcher::FirstRunDialogResultFailure) @@ -43,9 +59,14 @@ int main(int argc, char *argv[]) return exitCode; } - catch (std::exception& e) + catch (const std::exception& e) { - std::cerr << "ERROR: " << e.what() << std::endl; + Log(Debug::Error) << "Unexpected exception: " << e.what(); return 0; } } + +int main(int argc, char* argv[]) +{ + return wrapApplication(runLauncher, argc, argv, "Launcher"); +} diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 47dbbab8e..3a29f90b3 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,25 +1,34 @@ #include "maindialog.hpp" -#include +#include +#include +#include +#include #include +#include +#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include -#include "playpage.hpp" -#include "graphicspage.hpp" #include "datafilespage.hpp" +#include "graphicspage.hpp" +#include "importpage.hpp" #include "settingspage.hpp" -#include "advancedpage.hpp" using namespace Process; -void cfgError(const QString& title, const QString& msg) { +void cfgError(const QString& title, const QString& msg) +{ QMessageBox msgBox; msgBox.setWindowTitle(title); msgBox.setIcon(QMessageBox::Critical); @@ -28,39 +37,32 @@ void cfgError(const QString& title, const QString& msg) { msgBox.exec(); } -Launcher::MainDialog::MainDialog(QWidget *parent) - : QMainWindow(parent), mGameSettings (mCfgMgr) +Launcher::MainDialog::MainDialog(const Files::ConfigurationManager& configurationManager, QWidget* parent) + : QMainWindow(parent) + , mCfgMgr(configurationManager) + , mGameSettings(mCfgMgr) { setupUi(this); mGameInvoker = new ProcessInvoker(); mWizardInvoker = new ProcessInvoker(); - connect(mWizardInvoker->getProcess(), SIGNAL(started()), - this, SLOT(wizardStarted())); + connect(mWizardInvoker->getProcess(), &QProcess::started, this, &MainDialog::wizardStarted); - connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(wizardFinished(int,QProcess::ExitStatus))); + connect(mWizardInvoker->getProcess(), qOverload(&QProcess::finished), this, + &MainDialog::wizardFinished); - iconWidget->setViewMode(QListView::IconMode); - iconWidget->setWrapping(false); - iconWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Just to be sure - iconWidget->setIconSize(QSize(48, 48)); - iconWidget->setMovement(QListView::Static); - - iconWidget->setSpacing(4); - iconWidget->setCurrentRow(0); - iconWidget->setFlow(QListView::LeftToRight); - - QPushButton *helpButton = new QPushButton(tr("Help")); - QPushButton *playButton = new QPushButton(tr("Play")); buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close")); - buttonBox->addButton(helpButton, QDialogButtonBox::HelpRole); - buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole); + buttonBox->button(QDialogButtonBox::Ok)->setText(tr(" Launch OpenMW ")); + buttonBox->button(QDialogButtonBox::Help)->setText(tr("Help")); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(play())); - connect(buttonBox, SIGNAL(helpRequested()), this, SLOT(help())); + // Order of buttons can be different on different setups, + // so make sure that the Play button has a focus by default. + buttonBox->button(QDialogButtonBox::Ok)->setFocus(); + + connect(buttonBox, &QDialogButtonBox::rejected, this, &MainDialog::close); + connect(buttonBox, &QDialogButtonBox::accepted, this, &MainDialog::play); + connect(buttonBox, &QDialogButtonBox::helpRequested, this, &MainDialog::help); // Remove what's this? button setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -79,40 +81,10 @@ void Launcher::MainDialog::createIcons() if (!QIcon::hasThemeIcon("document-new")) QIcon::setThemeName("tango"); - QListWidgetItem *playButton = new QListWidgetItem(iconWidget); - playButton->setIcon(QIcon(":/images/openmw.png")); - playButton->setText(tr("Play")); - playButton->setTextAlignment(Qt::AlignCenter); - playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - - QListWidgetItem *dataFilesButton = new QListWidgetItem(iconWidget); - dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png")); - dataFilesButton->setText(tr("Data Files")); - dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); - dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - - QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); - graphicsButton->setIcon(QIcon(":/images/preferences-video.png")); - graphicsButton->setText(tr("Graphics")); - graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); - graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - - QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget); - settingsButton->setIcon(QIcon(":/images/preferences.png")); - settingsButton->setText(tr("Settings")); - settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); - settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - - QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget); - advancedButton->setIcon(QIcon(":/images/preferences-advanced.png")); - 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*))); - + connect(dataAction, &QAction::triggered, this, &MainDialog::enableDataPage); + connect(graphicsAction, &QAction::triggered, this, &MainDialog::enableGraphicsPage); + connect(settingsAction, &QAction::triggered, this, &MainDialog::enableSettingsPage); + connect(importAction, &QAction::triggered, this, &MainDialog::enableImportPage); } void Launcher::MainDialog::createPages() @@ -121,33 +93,23 @@ void Launcher::MainDialog::createPages() if (pagesWidget->count() != 0) return; - mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mGraphicsPage = new GraphicsPage(this); - mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mAdvancedPage = new AdvancedPage(mGameSettings, this); - - // Set the combobox of the play page to imitate the combobox on the datafilespage - mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); - mPlayPage->setProfilesIndex(mDataFilesPage->profilesIndex()); + mImportPage = new ImportPage(mCfgMgr, mGameSettings, mLauncherSettings, this); + mSettingsPage = new SettingsPage(mGameSettings, this); // Add the pages to the stacked widget - pagesWidget->addWidget(mPlayPage); pagesWidget->addWidget(mDataFilesPage); pagesWidget->addWidget(mGraphicsPage); pagesWidget->addWidget(mSettingsPage); - pagesWidget->addWidget(mAdvancedPage); + pagesWidget->addWidget(mImportPage); // Select the first page - iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select); + dataAction->setChecked(true); - connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play())); - - connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); - connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); // Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread - connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection); - + connect(mDataFilesPage, &DataFilesPage::signalLoadedCellsChanged, mSettingsPage, + &SettingsPage::slotLoadedCellsChanged, Qt::QueuedConnection); } Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() @@ -155,21 +117,36 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() if (!setupLauncherSettings()) return FirstRunDialogResultFailure; - if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) + // Dialog wizard and setup will fail if the config directory does not already exist + const auto& userConfigDir = mCfgMgr.getUserConfigPath(); + if (!exists(userConfigDir)) + { + if (!create_directories(userConfigDir)) + { + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not create directory %0

\ + Please make sure you have the right permissions \ + and try again.
") + .arg(Files::pathToQString(canonical(userConfigDir)))); + return FirstRunDialogResultFailure; + } + } + + if (mLauncherSettings.isFirstRun()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("First run")); msgBox.setIcon(QMessageBox::Question); msgBox.setStandardButtons(QMessageBox::NoButton); - msgBox.setText(tr("

Welcome to OpenMW!

\ + msgBox.setText( + tr("

Welcome to OpenMW!

\

It is recommended to run the Installation Wizard.

\

The Wizard will let you select an existing Morrowind installation, \ or install Morrowind for OpenMW to use.

")); - QAbstractButton *wizardButton = - msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! - QAbstractButton *skipButton = - msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); + QAbstractButton* wizardButton + = msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! + QAbstractButton* skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); msgBox.exec(); @@ -187,7 +164,8 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() return FirstRunDialogResultFailure; } - if (!setup() || !setupGameData()) { + if (!setup() || !setupGameData()) + { return FirstRunDialogResultFailure; } return FirstRunDialogResultContinue; @@ -210,8 +188,9 @@ void Launcher::MainDialog::setVersionLabel() // Add the compile date and time auto compileDate = QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), QLatin1String("MMM d yyyy")); auto compileTime = QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), QLatin1String("hh:mm:ss")); - versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale::system().toString(compileDate, QLocale::LongFormat), - QLocale::system().toString(compileTime, QLocale::ShortFormat))); + versionLabel->setToolTip(tr("Compiled on %1 %2") + .arg(QLocale::system().toString(compileDate, QLocale::LongFormat), + QLocale::system().toString(compileTime, QLocale::ShortFormat))); } bool Launcher::MainDialog::setup() @@ -251,7 +230,7 @@ bool Launcher::MainDialog::reloadSettings() if (!setupGraphicsSettings()) return false; - if (!mSettingsPage->loadSettings()) + if (!mImportPage->loadSettings()) return false; if (!mDataFilesPage->loadSettings()) @@ -260,54 +239,81 @@ bool Launcher::MainDialog::reloadSettings() if (!mGraphicsPage->loadSettings()) return false; - if (!mAdvancedPage->loadSettings()) + if (!mSettingsPage->loadSettings()) return false; return true; } -void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) +void Launcher::MainDialog::enableDataPage() { - if (!current) - current = previous; + pagesWidget->setCurrentIndex(0); + mImportPage->resetProgressBar(); + dataAction->setChecked(true); + graphicsAction->setChecked(false); + importAction->setChecked(false); + settingsAction->setChecked(false); +} - int currentIndex = iconWidget->row(current); - pagesWidget->setCurrentIndex(currentIndex); - mSettingsPage->resetProgressBar(); +void Launcher::MainDialog::enableGraphicsPage() +{ + pagesWidget->setCurrentIndex(1); + mImportPage->resetProgressBar(); + dataAction->setChecked(false); + graphicsAction->setChecked(true); + settingsAction->setChecked(false); + importAction->setChecked(false); +} + +void Launcher::MainDialog::enableSettingsPage() +{ + pagesWidget->setCurrentIndex(2); + mImportPage->resetProgressBar(); + dataAction->setChecked(false); + graphicsAction->setChecked(false); + settingsAction->setChecked(true); + importAction->setChecked(false); +} + +void Launcher::MainDialog::enableImportPage() +{ + pagesWidget->setCurrentIndex(3); + mImportPage->resetProgressBar(); + dataAction->setChecked(false); + graphicsAction->setChecked(false); + settingsAction->setChecked(false); + importAction->setChecked(true); } bool Launcher::MainDialog::setupLauncherSettings() { mLauncherSettings.clear(); - mLauncherSettings.setMultiValueEnabled(true); + const QString path + = Files::pathToQString(mCfgMgr.getUserConfigPath() / Config::LauncherSettings::sLauncherConfigFileName); - QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); + if (!QFile::exists(path)) + return true; - QStringList paths; - paths.append(QString(Config::LauncherSettings::sLauncherConfigFileName)); - paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); + Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); - for (const QString &path : paths) + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qDebug() << "Loading config file:" << path.toUtf8().constData(); - QFile file(path); - if (file.exists()) { - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - return false; - } - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - mLauncherSettings.readFile(stream); - } - file.close(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading:

%1

\ + Please make sure you have the right permissions \ + and try again.
") + .arg(file.fileName()) + .arg(file.errorString())); + return false; } + QTextStream stream(&file); + Misc::ensureUtf8Encoding(stream); + + mLauncherSettings.readFile(stream); + return true; } @@ -315,58 +321,48 @@ bool Launcher::MainDialog::setupGameSettings() { mGameSettings.clear(); - QString localPath = QString::fromUtf8(mCfgMgr.getLocalPath().string().c_str()); - QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); - QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); + QFile file; + + auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, bool), + bool ignoreContent = false) -> std::optional { + file.setFileName(path); + if (file.exists()) + { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
") + .arg(file.fileName())); + return {}; + } + QTextStream stream(&file); + Misc::ensureUtf8Encoding(stream); + + (mGameSettings.*reader)(stream, ignoreContent); + file.close(); + return true; + } + return false; + }; // Load the user config file first, separately // So we can write it properly, uncontaminated - QString path = userPath + QLatin1String("openmw.cfg"); - QFile file(path); - - qDebug() << "Loading config file:" << path.toUtf8().constData(); - - if (file.exists()) { - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - return false; - } - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - mGameSettings.readUserFile(stream); - file.close(); - } + if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile)) + return false; // Now the rest - priority: user > local > global - QStringList paths; - paths.append(globalPath + QString("openmw.cfg")); - paths.append(localPath + QString("openmw.cfg")); - paths.append(userPath + QString("openmw.cfg")); - - for (const QString &path2 : paths) + if (auto result = loadFile(Files::getLocalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) { - qDebug() << "Loading config file:" << path2.toUtf8().constData(); - - file.setFileName(path2); - if (file.exists()) { - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - return false; - } - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - mGameSettings.readFile(stream); - file.close(); - } + // Load global if local wasn't found + if (!*result && !loadFile(Files::getGlobalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) + return false; } + else + return false; + if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readFile)) + return false; return true; } @@ -380,7 +376,10 @@ bool Launcher::MainDialog::setupGameData() { QDir dir(path3); QStringList filters; - filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; + filters << "*.esp" + << "*.esm" + << "*.omwgame" + << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) dataDirs.append(path3); @@ -392,13 +391,12 @@ bool Launcher::MainDialog::setupGameData() msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::NoButton); - msgBox.setText(tr("
Could not find the Data Files location

\ + msgBox.setText( + tr("
Could not find the Data Files location

\ The directory containing the data files was not found.")); - QAbstractButton *wizardButton = - msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole); - QAbstractButton *skipButton = - msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); + QAbstractButton* wizardButton = msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole); + QAbstractButton* skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); Q_UNUSED(skipButton); // Suppress compiler unused warning @@ -416,87 +414,38 @@ bool Launcher::MainDialog::setupGameData() bool Launcher::MainDialog::setupGraphicsSettings() { - // This method is almost a copy of OMW::Engine::loadSettings(). They should definitely - // remain consistent, and possibly be merged into a shared component. At the very least - // the filenames should be in the CfgMgr component. - - // Ensure to clear previous settings in case we had already loaded settings. - mEngineSettings.clear(); - - // Create the settings manager and load default settings file - const std::string localDefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); - const std::string globalDefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); - std::string defaultPath; - - // Prefer the defaults.bin in the current directory. - if (boost::filesystem::exists(localDefault)) - defaultPath = localDefault; - else if (boost::filesystem::exists(globalDefault)) - defaultPath = globalDefault; - // Something's very wrong if we can't find the file at all. - else { - cfgError(tr("Error reading OpenMW configuration file"), - tr("
Could not find defaults.bin

\ - The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.")); + Settings::Manager::clear(); // Ensure to clear previous settings in case we had already loaded settings. + try + { + Settings::Manager::load(mCfgMgr); + return true; + } + catch (std::exception& e) + { + cfgError(tr("Error reading OpenMW configuration files"), + tr("
The problem may be due to an incomplete installation of OpenMW.
\ + Reinstalling OpenMW may resolve the problem.
") + + e.what()); return false; } - - // Load the default settings, report any parsing errors. - try { - mEngineSettings.loadDefault(defaultPath); - } - catch (std::exception& e) { - std::string msg = std::string("
Error reading defaults.bin

") + e.what(); - cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); - return false; - } - - // Load user settings if they exist - const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); - // User settings are not required to exist, so if they don't we're done. - if (!boost::filesystem::exists(userPath)) return true; - - try { - mEngineSettings.loadUser(userPath); - } - catch (std::exception& e) { - std::string msg = std::string("
Error reading settings.cfg

") + e.what(); - cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); - return false; - } - - return true; } void Launcher::MainDialog::loadSettings() { - int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt(); - int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt(); - - int posX = mLauncherSettings.value(QString("General/MainWindow/posx")).toInt(); - int posY = mLauncherSettings.value(QString("General/MainWindow/posy")).toInt(); - - resize(width, height); - move(posX, posY); + const auto& mainWindow = mLauncherSettings.getMainWindow(); + resize(mainWindow.mWidth, mainWindow.mHeight); + move(mainWindow.mPosX, mainWindow.mPosY); } void Launcher::MainDialog::saveSettings() { - QString width = QString::number(this->width()); - QString height = QString::number(this->height()); - - mLauncherSettings.setValue(QString("General/MainWindow/width"), width); - mLauncherSettings.setValue(QString("General/MainWindow/height"), height); - - QString posX = QString::number(this->pos().x()); - QString posY = QString::number(this->pos().y()); - - mLauncherSettings.setValue(QString("General/MainWindow/posx"), posX); - mLauncherSettings.setValue(QString("General/MainWindow/posy"), posY); - - mLauncherSettings.setValue(QString("General/firstrun"), QString("false")); - + mLauncherSettings.setMainWindow(Config::LauncherSettings::MainWindow{ + .mWidth = width(), + .mHeight = height(), + .mPosX = pos().x(), + .mPosY = pos().y(), + }); + mLauncherSettings.resetFirstRun(); } bool Launcher::MainDialog::writeSettings() @@ -505,65 +454,76 @@ bool Launcher::MainDialog::writeSettings() saveSettings(); mDataFilesPage->saveSettings(); mGraphicsPage->saveSettings(); + mImportPage->saveSettings(); mSettingsPage->saveSettings(); - mAdvancedPage->saveSettings(); - QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); - QDir dir(userPath); + const auto& userPath = mCfgMgr.getUserConfigPath(); - if (!dir.exists()) { - if (!dir.mkpath(userPath)) { + if (!exists(userPath)) + { + if (!create_directories(userPath)) + { cfgError(tr("Error creating OpenMW configuration directory"), - tr("
Could not create %0

\ + tr("
Could not create %0

\ Please make sure you have the right permissions \ - and try again.
").arg(userPath)); + and try again.
") + .arg(Files::pathToQString(userPath))); return false; } } // Game settings - QFile file(userPath + QString("openmw.cfg")); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QFile file(userPath / Files::openmwCfgFile); +#else + QFile file(Files::getUserConfigPathQString(mCfgMgr)); +#endif - if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) + { // File cannot be opened or created cfgError(tr("Error writing OpenMW configuration file"), - tr("
Could not open or create %0 for writing

\ + tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); + and try again.
") + .arg(file.fileName())); return false; } - mGameSettings.writeFileWithComments(file); file.close(); // Graphics settings - const std::string settingsPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); - try { - mEngineSettings.saveUser(settingsPath); + const auto settingsPath = mCfgMgr.getUserConfigPath() / "settings.cfg"; + try + { + Settings::Manager::saveUser(settingsPath); } - catch (std::exception& e) { - std::string msg = "
Error writing settings.cfg

" + - settingsPath + "

" + e.what(); + catch (std::exception& e) + { + std::string msg = "
Error writing settings.cfg

" + Files::pathToUnicodeString(settingsPath) + + "

" + e.what(); cfgError(tr("Error writing user settings file"), tr(msg.c_str())); return false; } // Launcher settings - file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); + file.setFileName(Files::pathToQString(userPath / Config::LauncherSettings::sLauncherConfigFileName)); - if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) + { // File cannot be opened or created cfgError(tr("Error writing Launcher configuration file"), - tr("
Could not open or create %0 for writing

\ + tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); + and try again.
") + .arg(file.fileName())); return false; } QTextStream stream(&file); stream.setDevice(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); + Misc::ensureUtf8Encoding(stream); mLauncherSettings.writeFile(stream); file.close(); @@ -571,7 +531,7 @@ bool Launcher::MainDialog::writeSettings() return true; } -void Launcher::MainDialog::closeEvent(QCloseEvent *event) +void Launcher::MainDialog::closeEvent(QCloseEvent* event) { writeSettings(); event->accept(); @@ -599,14 +559,16 @@ void Launcher::MainDialog::play() if (!writeSettings()) return qApp->quit(); - if (!mGameSettings.hasMaster()) { + if (!mGameSettings.hasMaster()) + { QMessageBox msgBox; msgBox.setWindowTitle(tr("No game file selected")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
You do not have a game file selected.

\ + msgBox.setText( + tr("
You do not have a game file selected.

\ OpenMW will not start without a game file selected.
")); - msgBox.exec(); + msgBox.exec(); return; } diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 80e014e28..cc68d52c7 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -1,34 +1,32 @@ #ifndef MAINDIALOG_H #define MAINDIALOG_H - #ifndef Q_MOC_RUN -#include - - #include #include #include -#include #endif #include "ui_mainwindow.h" class QListWidgetItem; class QStackedWidget; -class QStringList; class QStringListModel; class QString; +namespace Files +{ + struct ConfigurationManager; +} + namespace Launcher { - class PlayPage; class GraphicsPage; class DataFilesPage; class UnshieldThread; + class ImportPage; class SettingsPage; - class AdvancedPage; enum FirstRunDialogResult { @@ -46,8 +44,8 @@ namespace Launcher Q_OBJECT public: - explicit MainDialog(QWidget *parent = nullptr); - ~MainDialog(); + explicit MainDialog(const Files::ConfigurationManager& configurationManager, QWidget* parent = nullptr); + ~MainDialog() override; FirstRunDialogResult showFirstRunDialog(); @@ -55,7 +53,10 @@ namespace Launcher bool writeSettings(); public slots: - void changePage(QListWidgetItem *current, QListWidgetItem *previous); + void enableDataPage(); + void enableGraphicsPage(); + void enableSettingsPage(); + void enableImportPage(); void play(); void help(); @@ -79,26 +80,26 @@ namespace Launcher void loadSettings(); void saveSettings(); - inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); } - bool startProgram(const QString &name, const QStringList &arguments, bool detached = false); + inline bool startProgram(const QString& name, bool detached = false) + { + return startProgram(name, QStringList(), detached); + } + bool startProgram(const QString& name, const QStringList& arguments, bool detached = false); - void closeEvent(QCloseEvent *event) override; + void closeEvent(QCloseEvent* event) override; - PlayPage *mPlayPage; - GraphicsPage *mGraphicsPage; - DataFilesPage *mDataFilesPage; - SettingsPage *mSettingsPage; - AdvancedPage *mAdvancedPage; + GraphicsPage* mGraphicsPage; + DataFilesPage* mDataFilesPage; + ImportPage* mImportPage; + SettingsPage* mSettingsPage; - Process::ProcessInvoker *mGameInvoker; - Process::ProcessInvoker *mWizardInvoker; + Process::ProcessInvoker* mGameInvoker; + Process::ProcessInvoker* mWizardInvoker; - Files::ConfigurationManager mCfgMgr; + const Files::ConfigurationManager& mCfgMgr; Config::GameSettings mGameSettings; - Settings::Manager mEngineSettings; Config::LauncherSettings mLauncherSettings; - }; } #endif diff --git a/apps/launcher/playpage.cpp b/apps/launcher/playpage.cpp deleted file mode 100644 index 99b74fdd3..000000000 --- a/apps/launcher/playpage.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "playpage.hpp" - -#include - -Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent) -{ - setObjectName ("PlayPage"); - setupUi(this); - - profilesComboBox->setView(new QListView()); - - connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int))); - connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked())); - -} - -void Launcher::PlayPage::setProfilesModel(QAbstractItemModel *model) -{ - profilesComboBox->setModel(model); -} - -void Launcher::PlayPage::setProfilesIndex(int index) -{ - profilesComboBox->setCurrentIndex(index); -} - -void Launcher::PlayPage::slotPlayClicked() -{ - emit playButtonClicked(); -} diff --git a/apps/launcher/playpage.hpp b/apps/launcher/playpage.hpp deleted file mode 100644 index 8f414dc6a..000000000 --- a/apps/launcher/playpage.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef PLAYPAGE_H -#define PLAYPAGE_H - -#include "ui_playpage.h" - -class QComboBox; -class QPushButton; -class QAbstractItemModel; - -namespace Launcher -{ - class PlayPage : public QWidget, private Ui::PlayPage - { - Q_OBJECT - - public: - PlayPage(QWidget *parent = nullptr); - void setProfilesModel(QAbstractItemModel *model); - - signals: - void signalProfileChanged(int index); - void playButtonClicked(); - - public slots: - void setProfilesIndex(int index); - - private slots: - void slotPlayClicked(); - - - - }; -} -#endif diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index ca7fd028a..cf7b208d5 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -1,275 +1,446 @@ #include "settingspage.hpp" -#include -#include -#include +#include +#include +#include -#include +#include +#include +#include #include -#include -#include "utils/textinputdialog.hpp" -#include "datafilespage.hpp" +#include -using namespace Process; +#include "utils/openalutil.hpp" -Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, - Config::GameSettings &gameSettings, - Config::LauncherSettings &launcherSettings, MainDialog *parent) - : QWidget(parent) - , mCfgMgr(cfg) - , mGameSettings(gameSettings) - , mLauncherSettings(launcherSettings) - , mMain(parent) +namespace { - setupUi(this); - - QStringList languages; - languages << tr("English") - << tr("French") - << tr("German") - << tr("Italian") - << tr("Polish") - << tr("Russian") - << tr("Spanish"); - - languageComboBox->addItems(languages); - - mWizardInvoker = new ProcessInvoker(); - mImporterInvoker = new ProcessInvoker(); - resetProgressBar(); - - connect(mWizardInvoker->getProcess(), SIGNAL(started()), - this, SLOT(wizardStarted())); - - connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(wizardFinished(int,QProcess::ExitStatus))); - - connect(mImporterInvoker->getProcess(), SIGNAL(started()), - this, SLOT(importerStarted())); - - connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(importerFinished(int,QProcess::ExitStatus))); - - mProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); - - connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), - this, SLOT(updateOkButton(QString))); - - // Detect Morrowind configuration files - QStringList iniPaths; - - for (const QString &path : mGameSettings.getDataDirs()) + void loadSettingBool(const Settings::SettingValue& value, QCheckBox& checkbox) { - QDir dir(path); - dir.setPath(dir.canonicalPath()); // Resolve symlinks - - if (dir.exists(QString("Morrowind.ini"))) - iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); - else - { - if (!dir.cdUp()) - continue; // Cannot move from Data Files - - if (dir.exists(QString("Morrowind.ini"))) - iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); - } + checkbox.setCheckState(value ? Qt::Checked : Qt::Unchecked); } - if (!iniPaths.isEmpty()) { - settingsComboBox->addItems(iniPaths); - importerButton->setEnabled(true); - } else { - importerButton->setEnabled(false); + void saveSettingBool(const QCheckBox& checkbox, Settings::SettingValue& value) + { + value.set(checkbox.checkState() == Qt::Checked); + } + + void loadSettingInt(const Settings::SettingValue& value, QComboBox& comboBox) + { + comboBox.setCurrentIndex(value); + } + + void loadSettingInt(const Settings::SettingValue& value, QComboBox& comboBox) + { + comboBox.setCurrentIndex(static_cast(value.get())); + } + + void saveSettingInt(const QComboBox& comboBox, Settings::SettingValue& value) + { + value.set(comboBox.currentIndex()); + } + + void saveSettingInt(const QComboBox& comboBox, Settings::SettingValue& value) + { + value.set(static_cast(comboBox.currentIndex())); + } + + void loadSettingInt(const Settings::SettingValue& value, QSpinBox& spinBox) + { + spinBox.setValue(value); + } + + void saveSettingInt(const QSpinBox& spinBox, Settings::SettingValue& value) + { + value.set(spinBox.value()); + } +} + +Launcher::SettingsPage::SettingsPage(Config::GameSettings& gameSettings, QWidget* parent) + : QWidget(parent) + , mGameSettings(gameSettings) +{ + setObjectName("SettingsPage"); + setupUi(this); + + for (const std::string& name : Launcher::enumerateOpenALDevices()) + { + audioDeviceSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name)); + } + for (const std::string& name : Launcher::enumerateOpenALDevicesHrtf()) + { + hrtfProfileSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name)); } loadSettings(); + + mCellNameCompleter.setModel(&mCellNameCompleterModel); + startDefaultCharacterAtField->setCompleter(&mCellNameCompleter); } -Launcher::SettingsPage::~SettingsPage() +void Launcher::SettingsPage::loadCellsForAutocomplete(QStringList cellNames) { - delete mWizardInvoker; - delete mImporterInvoker; + // Update the list of suggestions for the "Start default character at" field + mCellNameCompleterModel.setStringList(cellNames); + mCellNameCompleter.setCompletionMode(QCompleter::PopupCompletion); + mCellNameCompleter.setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); } -void Launcher::SettingsPage::on_wizardButton_clicked() +void Launcher::SettingsPage::on_skipMenuCheckBox_stateChanged(int state) { - mMain->writeSettings(); - - if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) - return; + startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked); + startDefaultCharacterAtField->setEnabled(state == Qt::Checked); } -void Launcher::SettingsPage::on_importerButton_clicked() +void Launcher::SettingsPage::on_runScriptAfterStartupBrowseButton_clicked() { - mMain->writeSettings(); + QString scriptFile = QFileDialog::getOpenFileName( + this, QObject::tr("Select script file"), QDir::currentPath(), QString(tr("Text file (*.txt)"))); - // Create the file if it doesn't already exist, else the importer will fail - QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); - path.append(QLatin1String("openmw.cfg")); - QFile file(path); - - if (!file.exists()) { - if (!file.open(QIODevice::ReadWrite)) { - // File cannot be created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("

Could not open or create %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

").arg(file.fileName())); - msgBox.exec(); - return; - } - - file.close(); - } - - // Construct the arguments to run the importer - QStringList arguments; - - if (addonsCheckBox->isChecked()) - arguments.append(QString("--game-files")); - - arguments.append(QString("--encoding")); - arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); - arguments.append(QString("--ini")); - arguments.append(settingsComboBox->currentText()); - arguments.append(QString("--cfg")); - arguments.append(path); - - qDebug() << "arguments " << arguments; - - // start the progress bar as a "bouncing ball" - progressBar->setMaximum(0); - progressBar->setValue(0); - if (!mImporterInvoker->startProcess(QLatin1String("openmw-iniimporter"), arguments, false)) - { - resetProgressBar(); - } -} - -void Launcher::SettingsPage::on_browseButton_clicked() -{ - QString iniFile = QFileDialog::getOpenFileName( - this, - QObject::tr("Select configuration file"), - QDir::currentPath(), - QString(tr("Morrowind configuration file (*.ini)"))); - - - if (iniFile.isEmpty()) + if (scriptFile.isEmpty()) return; - QFileInfo info(iniFile); + QFileInfo info(scriptFile); if (!info.exists() || !info.isReadable()) return; const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); - - if (settingsComboBox->findText(path) == -1) { - settingsComboBox->addItem(path); - settingsComboBox->setCurrentIndex(settingsComboBox->findText(path)); - importerButton->setEnabled(true); - } + runScriptAfterStartupField->setText(path); } -void Launcher::SettingsPage::wizardStarted() +namespace { - mMain->hide(); // Hide the launcher + constexpr double CellSizeInUnits = 8192; - wizardButton->setEnabled(false); -} - -void Launcher::SettingsPage::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - if (exitCode != 0 || exitStatus == QProcess::CrashExit) - return qApp->quit(); - - mMain->reloadSettings(); - wizardButton->setEnabled(true); - - mMain->show(); // Show the launcher again -} - -void Launcher::SettingsPage::importerStarted() -{ - importerButton->setEnabled(false); -} - -void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - if (exitCode != 0 || exitStatus == QProcess::CrashExit) + double convertToCells(double unitRadius) { - resetProgressBar(); - - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Importer finished")); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setText(tr("Failed to import settings from INI file.")); - msgBox.exec(); + return unitRadius / CellSizeInUnits; } - else + + int convertToUnits(double CellGridRadius) { - // indicate progress finished - progressBar->setMaximum(1); - progressBar->setValue(1); - - // Importer may have changed settings, so refresh - mMain->reloadSettings(); - } - - importerButton->setEnabled(true); -} - -void Launcher::SettingsPage::resetProgressBar() -{ - // set progress bar to 0 % - progressBar->reset(); -} - -void Launcher::SettingsPage::updateOkButton(const QString &text) -{ - // We do this here because we need to access the profiles - if (text.isEmpty()) { - mProfileDialog->setOkButtonEnabled(false); - return; - } - - const QStringList profiles(mLauncherSettings.getContentLists()); - - (profiles.contains(text)) - ? mProfileDialog->setOkButtonEnabled(false) - : mProfileDialog->setOkButtonEnabled(true); -} - -void Launcher::SettingsPage::saveSettings() -{ - QString language(languageComboBox->currentText()); - - mLauncherSettings.setValue(QLatin1String("Settings/language"), language); - - if (language == QLatin1String("Polish")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); - } else if (language == QLatin1String("Russian")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); - } else { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + return static_cast(CellSizeInUnits * CellGridRadius); } } bool Launcher::SettingsPage::loadSettings() { - QString language(mLauncherSettings.value(QLatin1String("Settings/language"))); + // Game mechanics + { + loadSettingBool(Settings::game().mCanLootDuringDeathAnimation, *canLootDuringDeathAnimationCheckBox); + loadSettingBool(Settings::game().mFollowersAttackOnSight, *followersAttackOnSightCheckBox); + loadSettingBool(Settings::game().mRebalanceSoulGemValues, *rebalanceSoulGemValuesCheckBox); + loadSettingBool(Settings::game().mEnchantedWeaponsAreMagical, *enchantedWeaponsMagicalCheckBox); + loadSettingBool( + Settings::game().mBarterDispositionChangeIsPermanent, *permanentBarterDispositionChangeCheckBox); + loadSettingBool(Settings::game().mClassicReflectedAbsorbSpellsBehavior, *classicReflectedAbsorbSpellsCheckBox); + loadSettingBool(Settings::game().mClassicCalmSpellsBehavior, *classicCalmSpellsCheckBox); + loadSettingBool( + Settings::game().mOnlyAppropriateAmmunitionBypassesResistance, *requireAppropriateAmmunitionCheckBox); + loadSettingBool(Settings::game().mUncappedDamageFatigue, *uncappedDamageFatigueCheckBox); + loadSettingBool(Settings::game().mNormaliseRaceSpeed, *normaliseRaceSpeedCheckBox); + loadSettingBool(Settings::game().mSwimUpwardCorrection, *swimUpwardCorrectionCheckBox); + loadSettingBool(Settings::game().mNPCsAvoidCollisions, *avoidCollisionsCheckBox); + loadSettingInt(Settings::game().mStrengthInfluencesHandToHand, *unarmedFactorsStrengthComboBox); + loadSettingBool(Settings::game().mAlwaysAllowStealingFromKnockedOutActors, *stealingFromKnockedOutCheckBox); + loadSettingBool(Settings::navigator().mEnable, *enableNavigatorCheckBox); + loadSettingInt(Settings::physics().mAsyncNumThreads, *physicsThreadsSpinBox); + loadSettingBool( + Settings::game().mAllowActorsToFollowOverWaterSurface, *allowNPCToFollowOverWaterSurfaceCheckBox); + loadSettingBool( + Settings::game().mUnarmedCreatureAttacksDamageArmor, *unarmedCreatureAttacksDamageArmorCheckBox); + loadSettingInt(Settings::game().mActorCollisionShapeType, *actorCollisonShapeTypeComboBox); + } - int index = languageComboBox->findText(language); + // Visuals + { + loadSettingBool(Settings::shaders().mAutoUseObjectNormalMaps, *autoUseObjectNormalMapsCheckBox); + loadSettingBool(Settings::shaders().mAutoUseObjectSpecularMaps, *autoUseObjectSpecularMapsCheckBox); + loadSettingBool(Settings::shaders().mAutoUseTerrainNormalMaps, *autoUseTerrainNormalMapsCheckBox); + loadSettingBool(Settings::shaders().mAutoUseTerrainSpecularMaps, *autoUseTerrainSpecularMapsCheckBox); + loadSettingBool(Settings::shaders().mApplyLightingToEnvironmentMaps, *bumpMapLocalLightingCheckBox); + loadSettingBool(Settings::shaders().mSoftParticles, *softParticlesCheckBox); + loadSettingBool(Settings::shaders().mAntialiasAlphaTest, *antialiasAlphaTestCheckBox); + if (Settings::shaders().mAntialiasAlphaTest == 0) + antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked); + loadSettingBool(Settings::shaders().mAdjustCoverageForAlphaTest, *adjustCoverageForAlphaTestCheckBox); + loadSettingBool(Settings::shaders().mWeatherParticleOcclusion, *weatherParticleOcclusionCheckBox); + loadSettingBool(Settings::game().mUseMagicItemAnimations, *magicItemAnimationsCheckBox); + connect(animSourcesCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotAnimSourcesToggled); + loadSettingBool(Settings::game().mUseAdditionalAnimSources, *animSourcesCheckBox); + if (animSourcesCheckBox->checkState() != Qt::Unchecked) + { + loadSettingBool(Settings::game().mWeaponSheathing, *weaponSheathingCheckBox); + loadSettingBool(Settings::game().mShieldSheathing, *shieldSheathingCheckBox); + } + loadSettingBool(Settings::game().mTurnToMovementDirection, *turnToMovementDirectionCheckBox); + loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); - if (index != -1) - languageComboBox->setCurrentIndex(index); + distantLandCheckBox->setCheckState( + Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked); + loadSettingBool(Settings::terrain().mObjectPagingActiveGrid, *activeGridObjectPagingCheckBox); + viewingDistanceComboBox->setValue(convertToCells(Settings::camera().mViewingDistance)); + objectPagingMinSizeComboBox->setValue(Settings::terrain().mObjectPagingMinSize); + + loadSettingBool(Settings::game().mDayNightSwitches, *nightDaySwitchesCheckBox); + + connect(postprocessEnabledCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotPostProcessToggled); + loadSettingBool(Settings::postProcessing().mEnabled, *postprocessEnabledCheckBox); + loadSettingBool(Settings::postProcessing().mTransparentPostpass, *postprocessTransparentPostpassCheckBox); + postprocessHDRTimeComboBox->setValue(Settings::postProcessing().mAutoExposureSpeed); + + connect(skyBlendingCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotSkyBlendingToggled); + loadSettingBool(Settings::fog().mRadialFog, *radialFogCheckBox); + loadSettingBool(Settings::fog().mExponentialFog, *exponentialFogCheckBox); + loadSettingBool(Settings::fog().mSkyBlending, *skyBlendingCheckBox); + skyBlendingStartComboBox->setValue(Settings::fog().mSkyBlendingStart); + } + + // Audio + { + const std::string& selectedAudioDevice = Settings::sound().mDevice; + if (selectedAudioDevice.empty() == false) + { + int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice)); + if (audioDeviceIndex != -1) + { + audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex); + } + } + const int hrtfEnabledIndex = Settings::sound().mHrtfEnable; + if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1) + { + enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1); + } + const std::string& selectedHRTFProfile = Settings::sound().mHrtf; + if (selectedHRTFProfile.empty() == false) + { + int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile)); + if (hrtfProfileIndex != -1) + { + hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex); + } + } + } + + // Interface Changes + { + loadSettingBool(Settings::game().mShowEffectDuration, *showEffectDurationCheckBox); + loadSettingBool(Settings::game().mShowEnchantChance, *showEnchantChanceCheckBox); + loadSettingBool(Settings::game().mShowMeleeInfo, *showMeleeInfoCheckBox); + loadSettingBool(Settings::game().mShowProjectileDamage, *showProjectileDamageCheckBox); + loadSettingBool(Settings::gui().mColorTopicEnable, *changeDialogTopicsCheckBox); + showOwnedComboBox->setCurrentIndex(Settings::game().mShowOwned); + loadSettingBool(Settings::gui().mStretchMenuBackground, *stretchBackgroundCheckBox); + loadSettingBool(Settings::map().mAllowZooming, *useZoomOnMapCheckBox); + loadSettingBool(Settings::game().mGraphicHerbalism, *graphicHerbalismCheckBox); + scalingSpinBox->setValue(Settings::gui().mScalingFactor); + fontSizeSpinBox->setValue(Settings::gui().mFontSize); + } + + // Bug fixes + { + loadSettingBool(Settings::game().mPreventMerchantEquipping, *preventMerchantEquippingCheckBox); + loadSettingBool( + Settings::game().mTrainersTrainingSkillsBasedOnBaseSkill, *trainersTrainingSkillsBasedOnBaseSkillCheckBox); + } + + // Miscellaneous + { + // Saves + loadSettingBool(Settings::saves().mTimeplayed, *timePlayedCheckbox); + loadSettingInt(Settings::saves().mMaxQuicksaves, *maximumQuicksavesComboBox); + + // Other Settings + QString screenshotFormatString = QString::fromStdString(Settings::general().mScreenshotFormat).toUpper(); + if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) + screenshotFormatComboBox->addItem(screenshotFormatString); + screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); + + loadSettingBool(Settings::general().mNotifyOnSavedScreenshot, *notifyOnSavedScreenshotCheckBox); + } + + // Testing + { + loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox); + + bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; + if (skipMenu) + { + skipMenuCheckBox->setCheckState(Qt::Checked); + } + startDefaultCharacterAtLabel->setEnabled(skipMenu); + startDefaultCharacterAtField->setEnabled(skipMenu); + + startDefaultCharacterAtField->setText(mGameSettings.value("start")); + runScriptAfterStartupField->setText(mGameSettings.value("script-run")); + } return true; } + +void Launcher::SettingsPage::saveSettings() +{ + // Game mechanics + { + saveSettingBool(*canLootDuringDeathAnimationCheckBox, Settings::game().mCanLootDuringDeathAnimation); + saveSettingBool(*followersAttackOnSightCheckBox, Settings::game().mFollowersAttackOnSight); + saveSettingBool(*rebalanceSoulGemValuesCheckBox, Settings::game().mRebalanceSoulGemValues); + saveSettingBool(*enchantedWeaponsMagicalCheckBox, Settings::game().mEnchantedWeaponsAreMagical); + saveSettingBool( + *permanentBarterDispositionChangeCheckBox, Settings::game().mBarterDispositionChangeIsPermanent); + saveSettingBool(*classicReflectedAbsorbSpellsCheckBox, Settings::game().mClassicReflectedAbsorbSpellsBehavior); + saveSettingBool(*classicCalmSpellsCheckBox, Settings::game().mClassicCalmSpellsBehavior); + saveSettingBool( + *requireAppropriateAmmunitionCheckBox, Settings::game().mOnlyAppropriateAmmunitionBypassesResistance); + saveSettingBool(*uncappedDamageFatigueCheckBox, Settings::game().mUncappedDamageFatigue); + saveSettingBool(*normaliseRaceSpeedCheckBox, Settings::game().mNormaliseRaceSpeed); + saveSettingBool(*swimUpwardCorrectionCheckBox, Settings::game().mSwimUpwardCorrection); + saveSettingBool(*avoidCollisionsCheckBox, Settings::game().mNPCsAvoidCollisions); + saveSettingInt(*unarmedFactorsStrengthComboBox, Settings::game().mStrengthInfluencesHandToHand); + saveSettingBool(*stealingFromKnockedOutCheckBox, Settings::game().mAlwaysAllowStealingFromKnockedOutActors); + saveSettingBool(*enableNavigatorCheckBox, Settings::navigator().mEnable); + saveSettingInt(*physicsThreadsSpinBox, Settings::physics().mAsyncNumThreads); + saveSettingBool( + *allowNPCToFollowOverWaterSurfaceCheckBox, Settings::game().mAllowActorsToFollowOverWaterSurface); + saveSettingBool( + *unarmedCreatureAttacksDamageArmorCheckBox, Settings::game().mUnarmedCreatureAttacksDamageArmor); + saveSettingInt(*actorCollisonShapeTypeComboBox, Settings::game().mActorCollisionShapeType); + } + + // Visuals + { + saveSettingBool(*autoUseObjectNormalMapsCheckBox, Settings::shaders().mAutoUseObjectNormalMaps); + saveSettingBool(*autoUseObjectSpecularMapsCheckBox, Settings::shaders().mAutoUseObjectSpecularMaps); + saveSettingBool(*autoUseTerrainNormalMapsCheckBox, Settings::shaders().mAutoUseTerrainNormalMaps); + saveSettingBool(*autoUseTerrainSpecularMapsCheckBox, Settings::shaders().mAutoUseTerrainSpecularMaps); + saveSettingBool(*bumpMapLocalLightingCheckBox, Settings::shaders().mApplyLightingToEnvironmentMaps); + saveSettingBool(*radialFogCheckBox, Settings::fog().mRadialFog); + saveSettingBool(*softParticlesCheckBox, Settings::shaders().mSoftParticles); + saveSettingBool(*antialiasAlphaTestCheckBox, Settings::shaders().mAntialiasAlphaTest); + saveSettingBool(*adjustCoverageForAlphaTestCheckBox, Settings::shaders().mAdjustCoverageForAlphaTest); + saveSettingBool(*weatherParticleOcclusionCheckBox, Settings::shaders().mWeatherParticleOcclusion); + saveSettingBool(*magicItemAnimationsCheckBox, Settings::game().mUseMagicItemAnimations); + saveSettingBool(*animSourcesCheckBox, Settings::game().mUseAdditionalAnimSources); + saveSettingBool(*weaponSheathingCheckBox, Settings::game().mWeaponSheathing); + saveSettingBool(*shieldSheathingCheckBox, Settings::game().mShieldSheathing); + saveSettingBool(*turnToMovementDirectionCheckBox, Settings::game().mTurnToMovementDirection); + saveSettingBool(*smoothMovementCheckBox, Settings::game().mSmoothMovement); + + const bool wantDistantLand = distantLandCheckBox->checkState() == Qt::Checked; + if (wantDistantLand != (Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging)) + { + Settings::terrain().mDistantTerrain.set(wantDistantLand); + Settings::terrain().mObjectPaging.set(wantDistantLand); + } + + saveSettingBool(*activeGridObjectPagingCheckBox, Settings::terrain().mObjectPagingActiveGrid); + Settings::camera().mViewingDistance.set(convertToUnits(viewingDistanceComboBox->value())); + Settings::terrain().mObjectPagingMinSize.set(objectPagingMinSizeComboBox->value()); + saveSettingBool(*nightDaySwitchesCheckBox, Settings::game().mDayNightSwitches); + saveSettingBool(*postprocessEnabledCheckBox, Settings::postProcessing().mEnabled); + saveSettingBool(*postprocessTransparentPostpassCheckBox, Settings::postProcessing().mTransparentPostpass); + Settings::postProcessing().mAutoExposureSpeed.set(postprocessHDRTimeComboBox->value()); + saveSettingBool(*radialFogCheckBox, Settings::fog().mRadialFog); + saveSettingBool(*exponentialFogCheckBox, Settings::fog().mExponentialFog); + saveSettingBool(*skyBlendingCheckBox, Settings::fog().mSkyBlending); + Settings::fog().mSkyBlendingStart.set(skyBlendingStartComboBox->value()); + } + + // Audio + { + if (audioDeviceSelectorComboBox->currentIndex() != 0) + Settings::sound().mDevice.set(audioDeviceSelectorComboBox->currentText().toStdString()); + else + Settings::sound().mDevice.set({}); + + Settings::sound().mHrtfEnable.set(enableHRTFComboBox->currentIndex() - 1); + + if (hrtfProfileSelectorComboBox->currentIndex() != 0) + Settings::sound().mHrtf.set(hrtfProfileSelectorComboBox->currentText().toStdString()); + else + Settings::sound().mHrtf.set({}); + } + + // Interface Changes + { + saveSettingBool(*showEffectDurationCheckBox, Settings::game().mShowEffectDuration); + saveSettingBool(*showEnchantChanceCheckBox, Settings::game().mShowEnchantChance); + saveSettingBool(*showMeleeInfoCheckBox, Settings::game().mShowMeleeInfo); + saveSettingBool(*showProjectileDamageCheckBox, Settings::game().mShowProjectileDamage); + saveSettingBool(*changeDialogTopicsCheckBox, Settings::gui().mColorTopicEnable); + saveSettingInt(*showOwnedComboBox, Settings::game().mShowOwned); + saveSettingBool(*stretchBackgroundCheckBox, Settings::gui().mStretchMenuBackground); + saveSettingBool(*useZoomOnMapCheckBox, Settings::map().mAllowZooming); + saveSettingBool(*graphicHerbalismCheckBox, Settings::game().mGraphicHerbalism); + Settings::gui().mScalingFactor.set(scalingSpinBox->value()); + Settings::gui().mFontSize.set(fontSizeSpinBox->value()); + } + + // Bug fixes + { + saveSettingBool(*preventMerchantEquippingCheckBox, Settings::game().mPreventMerchantEquipping); + saveSettingBool( + *trainersTrainingSkillsBasedOnBaseSkillCheckBox, Settings::game().mTrainersTrainingSkillsBasedOnBaseSkill); + } + + // Miscellaneous + { + // Saves Settings + saveSettingBool(*timePlayedCheckbox, Settings::saves().mTimeplayed); + saveSettingInt(*maximumQuicksavesComboBox, Settings::saves().mMaxQuicksaves); + + // Other Settings + Settings::general().mScreenshotFormat.set(screenshotFormatComboBox->currentText().toLower().toStdString()); + saveSettingBool(*notifyOnSavedScreenshotCheckBox, Settings::general().mNotifyOnSavedScreenshot); + } + + // Testing + { + saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor); + + int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; + if (skipMenu != mGameSettings.value("skip-menu").toInt()) + mGameSettings.setValue("skip-menu", QString::number(skipMenu)); + + QString startCell = startDefaultCharacterAtField->text(); + if (startCell != mGameSettings.value("start")) + { + mGameSettings.setValue("start", startCell); + } + QString scriptRun = runScriptAfterStartupField->text(); + if (scriptRun != mGameSettings.value("script-run")) + mGameSettings.setValue("script-run", scriptRun); + } +} + +void Launcher::SettingsPage::slotLoadedCellsChanged(QStringList cellNames) +{ + loadCellsForAutocomplete(cellNames); +} + +void Launcher::SettingsPage::slotAnimSourcesToggled(bool checked) +{ + weaponSheathingCheckBox->setEnabled(checked); + shieldSheathingCheckBox->setEnabled(checked); + if (!checked) + { + weaponSheathingCheckBox->setCheckState(Qt::Unchecked); + shieldSheathingCheckBox->setCheckState(Qt::Unchecked); + } +} + +void Launcher::SettingsPage::slotPostProcessToggled(bool checked) +{ + postprocessTransparentPostpassCheckBox->setEnabled(checked); + postprocessHDRTimeComboBox->setEnabled(checked); + postprocessHDRTimeLabel->setEnabled(checked); +} + +void Launcher::SettingsPage::slotSkyBlendingToggled(bool checked) +{ + skyBlendingStartComboBox->setEnabled(checked); + skyBlendingStartLabel->setEnabled(checked); +} diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index df7c0e8eb..9f7d6b1f4 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -1,63 +1,48 @@ -#ifndef SETTINGSPAGE_HPP -#define SETTINGSPAGE_HPP +#ifndef SETTINGSPAGE_H +#define SETTINGSPAGE_H -#include +#include +#include #include "ui_settingspage.h" -#include "maindialog.hpp" - -namespace Files { struct ConfigurationManager; } -namespace Config { class GameSettings; - class LauncherSettings; } +namespace Config +{ + class GameSettings; +} namespace Launcher { - class TextInputDialog; - class SettingsPage : public QWidget, private Ui::SettingsPage { Q_OBJECT public: - SettingsPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, - Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr); - ~SettingsPage(); + explicit SettingsPage(Config::GameSettings& gameSettings, QWidget* parent = nullptr); - void saveSettings(); bool loadSettings(); - - /// set progress bar on page to 0% - void resetProgressBar(); + void saveSettings(); + + public slots: + void slotLoadedCellsChanged(QStringList cellNames); private slots: - - void on_wizardButton_clicked(); - void on_importerButton_clicked(); - void on_browseButton_clicked(); - - void wizardStarted(); - void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); - - void importerStarted(); - void importerFinished(int exitCode, QProcess::ExitStatus exitStatus); - - void updateOkButton(const QString &text); + void on_skipMenuCheckBox_stateChanged(int state); + void on_runScriptAfterStartupBrowseButton_clicked(); + void slotAnimSourcesToggled(bool checked); + void slotPostProcessToggled(bool checked); + void slotSkyBlendingToggled(bool checked); private: + Config::GameSettings& mGameSettings; + QCompleter mCellNameCompleter; + QStringListModel mCellNameCompleterModel; - Process::ProcessInvoker *mWizardInvoker; - Process::ProcessInvoker *mImporterInvoker; - - Files::ConfigurationManager &mCfgMgr; - - Config::GameSettings &mGameSettings; - Config::LauncherSettings &mLauncherSettings; - - MainDialog *mMain; - TextInputDialog *mProfileDialog; - + /** + * Load the cells associated with the given content files for use in autocomplete + * @param filePaths the file paths of the content files to be examined + */ + void loadCellsForAutocomplete(QStringList filePaths); }; } - -#endif // SETTINGSPAGE_HPP +#endif diff --git a/apps/launcher/textslotmsgbox.hpp b/apps/launcher/textslotmsgbox.hpp index a0fefaa25..1eac6cf91 100644 --- a/apps/launcher/textslotmsgbox.hpp +++ b/apps/launcher/textslotmsgbox.hpp @@ -7,9 +7,9 @@ namespace Launcher { class TextSlotMsgBox : public QMessageBox { - Q_OBJECT - public slots: - void setTextSlot(const QString& string); + Q_OBJECT + public slots: + void setTextSlot(const QString& string); }; } #endif diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp index e7f6e83e7..d47e923eb 100644 --- a/apps/launcher/utils/cellnameloader.cpp +++ b/apps/launcher/utils/cellnameloader.cpp @@ -1,44 +1,57 @@ #include "cellnameloader.hpp" -#include -#include +#include +#include +#include -QSet CellNameLoader::getCellNames(QStringList &contentPaths) +QSet CellNameLoader::getCellNames(const QStringList& contentPaths) { QSet cellNames; ESM::ESMReader esmReader; // Loop through all content files - for (auto &contentPath : contentPaths) { - esmReader.open(contentPath.toStdString()); - - // Loop through all records - while(esmReader.hasMoreRecs()) + for (const QString& contentPath : contentPaths) + { + if (contentPath.endsWith(".omwscripts", Qt::CaseInsensitive)) + continue; + try { - ESM::NAME recordName = esmReader.getRecName(); - esmReader.getRecHeader(); + esmReader.open(Files::pathFromQString(contentPath)); - if (isCellRecord(recordName)) { - QString cellName = getCellName(esmReader); - if (!cellName.isEmpty()) { - cellNames.insert(cellName); + // Loop through all records + while (esmReader.hasMoreRecs()) + { + ESM::NAME recordName = esmReader.getRecName(); + esmReader.getRecHeader(); + + if (isCellRecord(recordName)) + { + QString cellName = getCellName(esmReader); + if (!cellName.isEmpty()) + { + cellNames.insert(cellName); + } } - } - // Stop loading content for this record and continue to the next - esmReader.skipRecord(); + // Stop loading content for this record and continue to the next + esmReader.skipRecord(); + } + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Failed to get cell names from " << contentPath.toStdString() << ": " << e.what(); } } return cellNames; } -bool CellNameLoader::isCellRecord(ESM::NAME &recordName) +bool CellNameLoader::isCellRecord(ESM::NAME& recordName) { - return recordName.intval == ESM::REC_CELL; + return recordName.toInt() == ESM::REC_CELL; } -QString CellNameLoader::getCellName(ESM::ESMReader &esmReader) +QString CellNameLoader::getCellName(ESM::ESMReader& esmReader) { ESM::Cell cell; bool isDeleted = false; @@ -46,4 +59,3 @@ QString CellNameLoader::getCellName(ESM::ESMReader &esmReader) return QString::fromStdString(cell.mName); } - diff --git a/apps/launcher/utils/cellnameloader.hpp b/apps/launcher/utils/cellnameloader.hpp index 899ff75ad..6c7993dc5 100644 --- a/apps/launcher/utils/cellnameloader.hpp +++ b/apps/launcher/utils/cellnameloader.hpp @@ -4,21 +4,28 @@ #include #include -#include +#include -namespace ESM {class ESMReader; struct Cell;} -namespace ContentSelectorView {class ContentSelector;} +namespace ESM +{ + class ESMReader; + struct Cell; +} +namespace ContentSelectorView +{ + class ContentSelector; +} -class CellNameLoader { +class CellNameLoader +{ public: - /** * Returns the names of all cells contained within the given content files * @param contentPaths the file paths of each content file to be examined * @return the names of all cells */ - QSet getCellNames(QStringList &contentPaths); + QSet getCellNames(const QStringList& contentPaths); private: /** @@ -26,15 +33,14 @@ private: * @param name The name associated with the record * @return whether or not the given record is of type "Cell" */ - bool isCellRecord(ESM::NAME &name); + bool isCellRecord(ESM::NAME& name); /** * Returns the name of the cell * @param esmReader the reader currently pointed to a loaded cell * @return the name of the cell */ - QString getCellName(ESM::ESMReader &esmReader); + QString getCellName(ESM::ESMReader& esmReader); }; - -#endif //OPENMW_CELLNAMELOADER_H +#endif // OPENMW_CELLNAMELOADER_H diff --git a/apps/launcher/utils/lineedit.cpp b/apps/launcher/utils/lineedit.cpp index 348707580..0741e84c1 100644 --- a/apps/launcher/utils/lineedit.cpp +++ b/apps/launcher/utils/lineedit.cpp @@ -1,6 +1,6 @@ #include "lineedit.hpp" -LineEdit::LineEdit(QWidget *parent) +LineEdit::LineEdit(QWidget* parent) : QLineEdit(parent) { setupClearButton(); @@ -9,22 +9,19 @@ LineEdit::LineEdit(QWidget *parent) void LineEdit::setupClearButton() { mClearButton = new QToolButton(this); - QPixmap pixmap(":images/clear.png"); - mClearButton->setIcon(QIcon(pixmap)); - mClearButton->setIconSize(pixmap.size()); + mClearButton->setIcon(QIcon::fromTheme("edit-clear")); mClearButton->setCursor(Qt::ArrowCursor); mClearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); mClearButton->hide(); - connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear())); - connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&))); + connect(mClearButton, &QToolButton::clicked, this, &LineEdit::clear); + connect(this, &LineEdit::textChanged, this, &LineEdit::updateClearButton); } -void LineEdit::resizeEvent(QResizeEvent *) +void LineEdit::resizeEvent(QResizeEvent*) { QSize sz = mClearButton->sizeHint(); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - mClearButton->move(rect().right() - frameWidth - sz.width(), - (rect().bottom() + 1 - sz.height())/2); + mClearButton->move(rect().right() - frameWidth - sz.width(), (rect().bottom() + 1 - sz.height()) / 2); } void LineEdit::updateClearButton(const QString& text) diff --git a/apps/launcher/utils/lineedit.hpp b/apps/launcher/utils/lineedit.hpp index 89de39588..1a06c3ee0 100644 --- a/apps/launcher/utils/lineedit.hpp +++ b/apps/launcher/utils/lineedit.hpp @@ -23,19 +23,18 @@ class LineEdit : public QLineEdit QString mPlaceholderText; public: - LineEdit(QWidget *parent = nullptr); + LineEdit(QWidget* parent = nullptr); protected: - void resizeEvent(QResizeEvent *) override; + void resizeEvent(QResizeEvent*) override; private slots: - void updateClearButton(const QString &text); + void updateClearButton(const QString& text); protected: - QToolButton *mClearButton; + QToolButton* mClearButton; void setupClearButton(); }; #endif // LIENEDIT_H - diff --git a/apps/launcher/utils/openalutil.cpp b/apps/launcher/utils/openalutil.cpp index 52ad20894..1a332e978 100644 --- a/apps/launcher/utils/openalutil.cpp +++ b/apps/launcher/utils/openalutil.cpp @@ -1,6 +1,5 @@ #include #include -#include #include @@ -10,12 +9,12 @@ #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif -std::vector Launcher::enumerateOpenALDevices() +std::vector Launcher::enumerateOpenALDevices() { - std::vector devlist; - const ALCchar *devnames; + std::vector devlist; + const ALCchar* devnames; - if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) + if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) { devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); } @@ -23,23 +22,23 @@ std::vector Launcher::enumerateOpenALDevices() { devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); } - - while(devnames && *devnames) + + while (devnames && *devnames) { devlist.emplace_back(devnames); - devnames += strlen(devnames)+1; + devnames += strlen(devnames) + 1; } return devlist; } -std::vector Launcher::enumerateOpenALDevicesHrtf() +std::vector Launcher::enumerateOpenALDevicesHrtf() { - std::vector ret; + std::vector ret; - ALCdevice *device = alcOpenDevice(nullptr); - if(device) + ALCdevice* device = alcOpenDevice(nullptr); + if (device) { - if(alcIsExtensionPresent(device, "ALC_SOFT_HRTF")) + if (alcIsExtensionPresent(device, "ALC_SOFT_HRTF")) { LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; void* funcPtr = alcGetProcAddress(device, "alcGetStringiSOFT"); @@ -47,10 +46,10 @@ std::vector Launcher::enumerateOpenALDevicesHrtf() ALCint num_hrtf; alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); ret.reserve(num_hrtf); - for(ALCint i = 0;i < num_hrtf;++i) + for (ALCint i = 0; i < num_hrtf; ++i) { - const ALCchar *entry = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i); - if(strcmp(entry, "") == 0) + const ALCchar* entry = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i); + if (strcmp(entry, "") == 0) break; ret.emplace_back(entry); } diff --git a/apps/launcher/utils/openalutil.hpp b/apps/launcher/utils/openalutil.hpp index 4a84fbae7..f0add7539 100644 --- a/apps/launcher/utils/openalutil.hpp +++ b/apps/launcher/utils/openalutil.hpp @@ -1,7 +1,8 @@ +#include #include namespace Launcher { - std::vector enumerateOpenALDevices(); - std::vector enumerateOpenALDevicesHrtf(); -} \ No newline at end of file + std::vector enumerateOpenALDevices(); + std::vector enumerateOpenALDevicesHrtf(); +} diff --git a/apps/launcher/utils/profilescombobox.cpp b/apps/launcher/utils/profilescombobox.cpp index af349ddff..193dd8e4c 100644 --- a/apps/launcher/utils/profilescombobox.cpp +++ b/apps/launcher/utils/profilescombobox.cpp @@ -1,15 +1,12 @@ -#include -#include #include -#include +#include #include "profilescombobox.hpp" -ProfilesComboBox::ProfilesComboBox(QWidget *parent) : - ContentSelectorView::ComboBox(parent) +ProfilesComboBox::ProfilesComboBox(QWidget* parent) + : ContentSelectorView::ComboBox(parent) { - connect(this, SIGNAL(activated(int)), this, - SLOT(slotIndexChangedByUser(int))); + connect(this, qOverload(&ProfilesComboBox::activated), this, &ProfilesComboBox::slotIndexChangedByUser); setInsertPolicy(QComboBox::NoInsert); } @@ -19,9 +16,10 @@ void ProfilesComboBox::setEditEnabled(bool editable) if (isEditable() == editable) return; - if (!editable) { - disconnect(lineEdit(), SIGNAL(editingFinished()), this, SLOT(slotEditingFinished())); - disconnect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); + if (!editable) + { + disconnect(lineEdit(), &QLineEdit::editingFinished, this, &ProfilesComboBox::slotEditingFinished); + disconnect(lineEdit(), &QLineEdit::textChanged, this, &ProfilesComboBox::slotTextChanged); return setEditable(false); } @@ -29,31 +27,31 @@ void ProfilesComboBox::setEditEnabled(bool editable) setEditable(true); setValidator(mValidator); - ComboBoxLineEdit *edit = new ComboBoxLineEdit(this); + auto* edit = new ComboBoxLineEdit(this); setLineEdit(edit); setCompleter(nullptr); - connect(lineEdit(), SIGNAL(editingFinished()), this, - SLOT(slotEditingFinished())); + connect(lineEdit(), &QLineEdit::editingFinished, this, &ProfilesComboBox::slotEditingFinished); - connect(lineEdit(), SIGNAL(textChanged(QString)), this, - SLOT(slotTextChanged(QString))); + connect(lineEdit(), &QLineEdit::textChanged, this, &ProfilesComboBox::slotTextChanged); - connect (lineEdit(), SIGNAL(textChanged(QString)), this, - SIGNAL (signalProfileTextChanged (QString))); + connect(lineEdit(), &QLineEdit::textChanged, this, &ProfilesComboBox::signalProfileTextChanged); } -void ProfilesComboBox::slotTextChanged(const QString &text) +void ProfilesComboBox::slotTextChanged(const QString& text) { QPalette palette; - palette.setColor(QPalette::Text,Qt::red); + palette.setColor(QPalette::Text, Qt::red); int index = findText(text); - if (text.isEmpty() || (index != -1 && index != currentIndex())) { + if (text.isEmpty() || (index != -1 && index != currentIndex())) + { lineEdit()->setPalette(palette); - } else { + } + else + { lineEdit()->setPalette(QApplication::palette()); } } @@ -76,7 +74,7 @@ void ProfilesComboBox::slotEditingFinished() return; setItemText(currentIndex(), current); - emit(profileRenamed(previous, current)); + emit profileRenamed(previous, current); } void ProfilesComboBox::slotIndexChangedByUser(int index) @@ -84,15 +82,16 @@ void ProfilesComboBox::slotIndexChangedByUser(int index) if (index == -1) return; - emit (signalProfileChanged(mOldProfile, currentText())); + emit signalProfileChanged(mOldProfile, currentText()); mOldProfile = currentText(); } -ProfilesComboBox::ComboBoxLineEdit::ComboBoxLineEdit (QWidget *parent) - : LineEdit (parent) +ProfilesComboBox::ComboBoxLineEdit::ComboBoxLineEdit(QWidget* parent) + : LineEdit(parent) { int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); setObjectName(QString("ComboBoxLineEdit")); - setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); + setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ") + .arg(mClearButton->sizeHint().width() + frameWidth + 1)); } diff --git a/apps/launcher/utils/profilescombobox.hpp b/apps/launcher/utils/profilescombobox.hpp index 065682f3d..91f4f9ef9 100644 --- a/apps/launcher/utils/profilescombobox.hpp +++ b/apps/launcher/utils/profilescombobox.hpp @@ -4,7 +4,7 @@ #include "components/contentselector/view/combobox.hpp" #include "lineedit.hpp" -#include +#include class QString; @@ -16,12 +16,11 @@ public: class ComboBoxLineEdit : public LineEdit { public: - explicit ComboBoxLineEdit (QWidget *parent = nullptr); + explicit ComboBoxLineEdit(QWidget* parent = nullptr); }; public: - - explicit ProfilesComboBox(QWidget *parent = nullptr); + explicit ProfilesComboBox(QWidget* parent = nullptr); void setEditEnabled(bool editable); void setCurrentProfile(int index) { @@ -30,16 +29,16 @@ public: } signals: - void signalProfileTextChanged(const QString &item); - void signalProfileChanged(const QString &previous, const QString ¤t); + void signalProfileTextChanged(const QString& item); + void signalProfileChanged(const QString& previous, const QString& current); void signalProfileChanged(int index); - void profileRenamed(const QString &oldName, const QString &newName); + void profileRenamed(const QString& oldName, const QString& newName); private slots: void slotEditingFinished(); void slotIndexChangedByUser(int index); - void slotTextChanged(const QString &text); + void slotTextChanged(const QString& text); private: QString mOldProfile; diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 70b827596..9e06e6a6c 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -1,31 +1,31 @@ #include "textinputdialog.hpp" -#include #include +#include +#include #include #include #include -#include -Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : - QDialog(parent) +Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString& text, QWidget* parent) + : QDialog(parent) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); mButtonBox = new QDialogButtonBox(this); mButtonBox->addButton(QDialogButtonBox::Ok); mButtonBox->addButton(QDialogButtonBox::Cancel); - mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); + mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - QLabel *label = new QLabel(this); + auto* label = new QLabel(this); label->setText(text); // Line edit - QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore + QValidator* validator = new QRegularExpressionValidator(QRegularExpression("^[a-zA-Z0-9_]*$"), this); mLineEdit = new LineEdit(this); mLineEdit->setValidator(validator); mLineEdit->setCompleter(nullptr); - QVBoxLayout *dialogLayout = new QVBoxLayout(this); + auto* dialogLayout = new QVBoxLayout(this); dialogLayout->addWidget(label); dialogLayout->addWidget(mLineEdit); dialogLayout->addWidget(mButtonBox); @@ -39,12 +39,8 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString & setModal(true); - connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); -} - -Launcher::TextInputDialog::~TextInputDialog() -{ + connect(mButtonBox, &QDialogButtonBox::accepted, this, &TextInputDialog::accept); + connect(mButtonBox, &QDialogButtonBox::rejected, this, &TextInputDialog::reject); } int Launcher::TextInputDialog::exec() @@ -56,15 +52,18 @@ int Launcher::TextInputDialog::exec() void Launcher::TextInputDialog::setOkButtonEnabled(bool enabled) { - QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); + QPushButton* okButton = mButtonBox->button(QDialogButtonBox::Ok); okButton->setEnabled(enabled); QPalette palette; palette.setColor(QPalette::Text, Qt::red); - if (enabled) { + if (enabled) + { mLineEdit->setPalette(QApplication::palette()); - } else { + } + else + { // Existing profile name, make the text red mLineEdit->setPalette(palette); } diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp index de0659963..333f4b657 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -14,20 +14,17 @@ namespace Launcher Q_OBJECT public: + explicit TextInputDialog(const QString& title, const QString& text, QWidget* parent = nullptr); + ~TextInputDialog() override = default; - explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = nullptr); - ~TextInputDialog (); - - inline LineEdit *lineEdit() { return mLineEdit; } + inline LineEdit* lineEdit() { return mLineEdit; } void setOkButtonEnabled(bool enabled); int exec() override; private: - - QDialogButtonBox *mButtonBox; - LineEdit *mLineEdit; - + QDialogButtonBox* mButtonBox; + LineEdit* mLineEdit; }; } diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index e83656708..c2329941c 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -15,7 +15,6 @@ openmw_add_executable(openmw-iniimporter target_link_libraries(openmw-iniimporter ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} components ) @@ -26,10 +25,17 @@ if (WIN32) endif(WIN32) if (MINGW) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") endif() if (BUILD_WITH_CODE_COVERAGE) - add_definitions (--coverage) - target_link_libraries(openmw-iniimporter gcov) + target_compile_options(openmw-iniimporter PRIVATE --coverage) + target_link_libraries(openmw-iniimporter gcov) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw-iniimporter PRIVATE + + + ) endif() diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 2763d8ad9..ddc571d5c 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -1,638 +1,254 @@ #include "importer.hpp" +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include - -namespace bfs = boost::filesystem; +namespace sfs = std::filesystem; MwIniImporter::MwIniImporter() : mVerbose(false) , mEncoding(ToUTF8::WINDOWS_1250) { - const char *map[][2] = - { - { "no-sound", "General:Disable Audio" }, - { 0, 0 } - }; - const char *fallback[] = { + const char* map[][2] = { { "no-sound", "General:Disable Audio" }, { 0, 0 } }; + const char* fallback[] = { // light - "LightAttenuation:UseConstant", - "LightAttenuation:ConstantValue", - "LightAttenuation:UseLinear", - "LightAttenuation:LinearMethod", - "LightAttenuation:LinearValue", - "LightAttenuation:LinearRadiusMult", - "LightAttenuation:UseQuadratic", - "LightAttenuation:QuadraticMethod", - "LightAttenuation:QuadraticValue", - "LightAttenuation:QuadraticRadiusMult", - "LightAttenuation:OutQuadInLin", + "LightAttenuation:UseConstant", "LightAttenuation:ConstantValue", "LightAttenuation:UseLinear", + "LightAttenuation:LinearMethod", "LightAttenuation:LinearValue", "LightAttenuation:LinearRadiusMult", + "LightAttenuation:UseQuadratic", "LightAttenuation:QuadraticMethod", "LightAttenuation:QuadraticValue", + "LightAttenuation:QuadraticRadiusMult", "LightAttenuation:OutQuadInLin", // inventory - "Inventory:DirectionalDiffuseR", - "Inventory:DirectionalDiffuseG", - "Inventory:DirectionalDiffuseB", - "Inventory:DirectionalAmbientR", - "Inventory:DirectionalAmbientG", - "Inventory:DirectionalAmbientB", - "Inventory:DirectionalRotationX", - "Inventory:DirectionalRotationY", - "Inventory:UniformScaling", + "Inventory:DirectionalDiffuseR", "Inventory:DirectionalDiffuseG", "Inventory:DirectionalDiffuseB", + "Inventory:DirectionalAmbientR", "Inventory:DirectionalAmbientG", "Inventory:DirectionalAmbientB", + "Inventory:DirectionalRotationX", "Inventory:DirectionalRotationY", "Inventory:UniformScaling", // map - "Map:Travel Siltstrider Red", - "Map:Travel Siltstrider Green", - "Map:Travel Siltstrider Blue", - "Map:Travel Boat Red", - "Map:Travel Boat Green", - "Map:Travel Boat Blue", - "Map:Travel Magic Red", - "Map:Travel Magic Green", - "Map:Travel Magic Blue", - "Map:Show Travel Lines", + "Map:Travel Siltstrider Red", "Map:Travel Siltstrider Green", "Map:Travel Siltstrider Blue", + "Map:Travel Boat Red", "Map:Travel Boat Green", "Map:Travel Boat Blue", "Map:Travel Magic Red", + "Map:Travel Magic Green", "Map:Travel Magic Blue", "Map:Show Travel Lines", // water - "Water:Map Alpha", - "Water:World Alpha", - "Water:SurfaceTextureSize", - "Water:SurfaceTileCount", - "Water:SurfaceFPS", - "Water:SurfaceTexture", - "Water:SurfaceFrameCount", - "Water:TileTextureDivisor", - "Water:RippleTexture", - "Water:RippleFrameCount", - "Water:RippleLifetime", - "Water:MaxNumberRipples", - "Water:RippleScale", - "Water:RippleRotSpeed", - "Water:RippleAlphas", - "Water:PSWaterReflectTerrain", - "Water:PSWaterReflectUpdate", - "Water:NearWaterRadius", - "Water:NearWaterPoints", - "Water:NearWaterUnderwaterFreq", - "Water:NearWaterUnderwaterVolume", - "Water:NearWaterIndoorTolerance", - "Water:NearWaterOutdoorTolerance", - "Water:NearWaterIndoorID", - "Water:NearWaterOutdoorID", - "Water:UnderwaterSunriseFog", - "Water:UnderwaterDayFog", - "Water:UnderwaterSunsetFog", - "Water:UnderwaterNightFog", - "Water:UnderwaterIndoorFog", - "Water:UnderwaterColor", + "Water:Map Alpha", "Water:World Alpha", "Water:SurfaceTextureSize", "Water:SurfaceTileCount", + "Water:SurfaceFPS", "Water:SurfaceTexture", "Water:SurfaceFrameCount", "Water:TileTextureDivisor", + "Water:RippleTexture", "Water:RippleFrameCount", "Water:RippleLifetime", "Water:MaxNumberRipples", + "Water:RippleScale", "Water:RippleRotSpeed", "Water:RippleAlphas", "Water:PSWaterReflectTerrain", + "Water:PSWaterReflectUpdate", "Water:NearWaterRadius", "Water:NearWaterPoints", "Water:NearWaterUnderwaterFreq", + "Water:NearWaterUnderwaterVolume", "Water:NearWaterIndoorTolerance", "Water:NearWaterOutdoorTolerance", + "Water:NearWaterIndoorID", "Water:NearWaterOutdoorID", "Water:UnderwaterSunriseFog", "Water:UnderwaterDayFog", + "Water:UnderwaterSunsetFog", "Water:UnderwaterNightFog", "Water:UnderwaterIndoorFog", "Water:UnderwaterColor", "Water:UnderwaterColorWeight", // pixelwater - "PixelWater:SurfaceFPS", - "PixelWater:TileCount", - "PixelWater:Resolution", + "PixelWater:SurfaceFPS", "PixelWater:TileCount", "PixelWater:Resolution", // fonts - "Fonts:Font 0", - "Fonts:Font 1", - "Fonts:Font 2", + "Fonts:Font 0", "Fonts:Font 1", "Fonts:Font 2", // UI colors - "FontColor:color_normal", - "FontColor:color_normal_over", - "FontColor:color_normal_pressed", - "FontColor:color_active", - "FontColor:color_active_over", - "FontColor:color_active_pressed", - "FontColor:color_disabled", - "FontColor:color_disabled_over", - "FontColor:color_disabled_pressed", - "FontColor:color_link", - "FontColor:color_link_over", - "FontColor:color_link_pressed", - "FontColor:color_journal_link", - "FontColor:color_journal_link_over", - "FontColor:color_journal_link_pressed", - "FontColor:color_journal_topic", - "FontColor:color_journal_topic_over", - "FontColor:color_journal_topic_pressed", - "FontColor:color_answer", - "FontColor:color_answer_over", - "FontColor:color_answer_pressed", - "FontColor:color_header", - "FontColor:color_notify", - "FontColor:color_big_normal", - "FontColor:color_big_normal_over", - "FontColor:color_big_normal_pressed", - "FontColor:color_big_link", - "FontColor:color_big_link_over", - "FontColor:color_big_link_pressed", - "FontColor:color_big_answer", - "FontColor:color_big_answer_over", - "FontColor:color_big_answer_pressed", - "FontColor:color_big_header", - "FontColor:color_big_notify", - "FontColor:color_background", - "FontColor:color_focus", - "FontColor:color_health", - "FontColor:color_magic", - "FontColor:color_fatigue", - "FontColor:color_misc", - "FontColor:color_weapon_fill", - "FontColor:color_magic_fill", - "FontColor:color_positive", - "FontColor:color_negative", - "FontColor:color_count", + "FontColor:color_normal", "FontColor:color_normal_over", "FontColor:color_normal_pressed", + "FontColor:color_active", "FontColor:color_active_over", "FontColor:color_active_pressed", + "FontColor:color_disabled", "FontColor:color_disabled_over", "FontColor:color_disabled_pressed", + "FontColor:color_link", "FontColor:color_link_over", "FontColor:color_link_pressed", + "FontColor:color_journal_link", "FontColor:color_journal_link_over", "FontColor:color_journal_link_pressed", + "FontColor:color_journal_topic", "FontColor:color_journal_topic_over", "FontColor:color_journal_topic_pressed", + "FontColor:color_answer", "FontColor:color_answer_over", "FontColor:color_answer_pressed", + "FontColor:color_header", "FontColor:color_notify", "FontColor:color_big_normal", + "FontColor:color_big_normal_over", "FontColor:color_big_normal_pressed", "FontColor:color_big_link", + "FontColor:color_big_link_over", "FontColor:color_big_link_pressed", "FontColor:color_big_answer", + "FontColor:color_big_answer_over", "FontColor:color_big_answer_pressed", "FontColor:color_big_header", + "FontColor:color_big_notify", "FontColor:color_background", "FontColor:color_focus", "FontColor:color_health", + "FontColor:color_magic", "FontColor:color_fatigue", "FontColor:color_misc", "FontColor:color_weapon_fill", + "FontColor:color_magic_fill", "FontColor:color_positive", "FontColor:color_negative", "FontColor:color_count", // level up messages - "Level Up:Level2", - "Level Up:Level3", - "Level Up:Level4", - "Level Up:Level5", - "Level Up:Level6", - "Level Up:Level7", - "Level Up:Level8", - "Level Up:Level9", - "Level Up:Level10", - "Level Up:Level11", - "Level Up:Level12", - "Level Up:Level13", - "Level Up:Level14", - "Level Up:Level15", - "Level Up:Level16", - "Level Up:Level17", - "Level Up:Level18", - "Level Up:Level19", - "Level Up:Level20", - "Level Up:Default", + "Level Up:Level2", "Level Up:Level3", "Level Up:Level4", "Level Up:Level5", "Level Up:Level6", + "Level Up:Level7", "Level Up:Level8", "Level Up:Level9", "Level Up:Level10", "Level Up:Level11", + "Level Up:Level12", "Level Up:Level13", "Level Up:Level14", "Level Up:Level15", "Level Up:Level16", + "Level Up:Level17", "Level Up:Level18", "Level Up:Level19", "Level Up:Level20", "Level Up:Default", // character creation multiple choice test - "Question 1:Question", - "Question 1:AnswerOne", - "Question 1:AnswerTwo", - "Question 1:AnswerThree", - "Question 1:Sound", - "Question 2:Question", - "Question 2:AnswerOne", - "Question 2:AnswerTwo", - "Question 2:AnswerThree", - "Question 2:Sound", - "Question 3:Question", - "Question 3:AnswerOne", - "Question 3:AnswerTwo", - "Question 3:AnswerThree", - "Question 3:Sound", - "Question 4:Question", - "Question 4:AnswerOne", - "Question 4:AnswerTwo", - "Question 4:AnswerThree", - "Question 4:Sound", - "Question 5:Question", - "Question 5:AnswerOne", - "Question 5:AnswerTwo", - "Question 5:AnswerThree", - "Question 5:Sound", - "Question 6:Question", - "Question 6:AnswerOne", - "Question 6:AnswerTwo", - "Question 6:AnswerThree", - "Question 6:Sound", - "Question 7:Question", - "Question 7:AnswerOne", - "Question 7:AnswerTwo", - "Question 7:AnswerThree", - "Question 7:Sound", - "Question 8:Question", - "Question 8:AnswerOne", - "Question 8:AnswerTwo", - "Question 8:AnswerThree", - "Question 8:Sound", - "Question 9:Question", - "Question 9:AnswerOne", - "Question 9:AnswerTwo", - "Question 9:AnswerThree", - "Question 9:Sound", - "Question 10:Question", - "Question 10:AnswerOne", - "Question 10:AnswerTwo", - "Question 10:AnswerThree", - "Question 10:Sound", + "Question 1:Question", "Question 1:AnswerOne", "Question 1:AnswerTwo", "Question 1:AnswerThree", + "Question 1:Sound", "Question 2:Question", "Question 2:AnswerOne", "Question 2:AnswerTwo", + "Question 2:AnswerThree", "Question 2:Sound", "Question 3:Question", "Question 3:AnswerOne", + "Question 3:AnswerTwo", "Question 3:AnswerThree", "Question 3:Sound", "Question 4:Question", + "Question 4:AnswerOne", "Question 4:AnswerTwo", "Question 4:AnswerThree", "Question 4:Sound", + "Question 5:Question", "Question 5:AnswerOne", "Question 5:AnswerTwo", "Question 5:AnswerThree", + "Question 5:Sound", "Question 6:Question", "Question 6:AnswerOne", "Question 6:AnswerTwo", + "Question 6:AnswerThree", "Question 6:Sound", "Question 7:Question", "Question 7:AnswerOne", + "Question 7:AnswerTwo", "Question 7:AnswerThree", "Question 7:Sound", "Question 8:Question", + "Question 8:AnswerOne", "Question 8:AnswerTwo", "Question 8:AnswerThree", "Question 8:Sound", + "Question 9:Question", "Question 9:AnswerOne", "Question 9:AnswerTwo", "Question 9:AnswerThree", + "Question 9:Sound", "Question 10:Question", "Question 10:AnswerOne", "Question 10:AnswerTwo", + "Question 10:AnswerThree", "Question 10:Sound", // blood textures and models - "Blood:Model 0", - "Blood:Model 1", - "Blood:Model 2", - "Blood:Texture 0", - "Blood:Texture 1", - "Blood:Texture 2", - "Blood:Texture 3", - "Blood:Texture 4", - "Blood:Texture 5", - "Blood:Texture 6", - "Blood:Texture 7", - "Blood:Texture Name 0", - "Blood:Texture Name 1", - "Blood:Texture Name 2", - "Blood:Texture Name 3", - "Blood:Texture Name 4", - "Blood:Texture Name 5", - "Blood:Texture Name 6", - "Blood:Texture Name 7", + "Blood:Model 0", "Blood:Model 1", "Blood:Model 2", "Blood:Texture 0", "Blood:Texture 1", "Blood:Texture 2", + "Blood:Texture 3", "Blood:Texture 4", "Blood:Texture 5", "Blood:Texture 6", "Blood:Texture 7", + "Blood:Texture Name 0", "Blood:Texture Name 1", "Blood:Texture Name 2", "Blood:Texture Name 3", + "Blood:Texture Name 4", "Blood:Texture Name 5", "Blood:Texture Name 6", "Blood:Texture Name 7", // movies - "Movies:Company Logo", - "Movies:Morrowind Logo", - "Movies:New Game", - "Movies:Loading", - "Movies:Options Menu", + "Movies:Company Logo", "Movies:Morrowind Logo", "Movies:New Game", "Movies:Loading", "Movies:Options Menu", // weather related values - "Weather Thunderstorm:Thunder Sound ID 0", - "Weather Thunderstorm:Thunder Sound ID 1", - "Weather Thunderstorm:Thunder Sound ID 2", - "Weather Thunderstorm:Thunder Sound ID 3", - "Weather:Sunrise Time", - "Weather:Sunset Time", - "Weather:Sunrise Duration", - "Weather:Sunset Duration", + "Weather Thunderstorm:Thunder Sound ID 0", "Weather Thunderstorm:Thunder Sound ID 1", + "Weather Thunderstorm:Thunder Sound ID 2", "Weather Thunderstorm:Thunder Sound ID 3", "Weather:Sunrise Time", + "Weather:Sunset Time", "Weather:Sunrise Duration", "Weather:Sunset Duration", "Weather:Hours Between Weather Changes", // AKA weather update time - "Weather Thunderstorm:Thunder Frequency", - "Weather Thunderstorm:Thunder Threshold", + "Weather Thunderstorm:Thunder Frequency", "Weather Thunderstorm:Thunder Threshold", - "Weather:EnvReduceColor", - "Weather:LerpCloseColor", - "Weather:BumpFadeColor", - "Weather:AlphaReduce", - "Weather:Minimum Time Between Environmental Sounds", - "Weather:Maximum Time Between Environmental Sounds", - "Weather:Sun Glare Fader Max", - "Weather:Sun Glare Fader Angle Max", - "Weather:Sun Glare Fader Color", - "Weather:Timescale Clouds", - "Weather:Precip Gravity", - "Weather:Rain Ripples", - "Weather:Rain Ripple Radius", - "Weather:Rain Ripples Per Drop", - "Weather:Rain Ripple Scale", - "Weather:Rain Ripple Speed", - "Weather:Fog Depth Change Speed", - "Weather:Sky Pre-Sunrise Time", - "Weather:Sky Post-Sunrise Time", - "Weather:Sky Pre-Sunset Time", - "Weather:Sky Post-Sunset Time", - "Weather:Ambient Pre-Sunrise Time", - "Weather:Ambient Post-Sunrise Time", - "Weather:Ambient Pre-Sunset Time", - "Weather:Ambient Post-Sunset Time", - "Weather:Fog Pre-Sunrise Time", - "Weather:Fog Post-Sunrise Time", - "Weather:Fog Pre-Sunset Time", - "Weather:Fog Post-Sunset Time", - "Weather:Sun Pre-Sunrise Time", - "Weather:Sun Post-Sunrise Time", - "Weather:Sun Pre-Sunset Time", - "Weather:Sun Post-Sunset Time", - "Weather:Stars Post-Sunset Start", - "Weather:Stars Pre-Sunrise Finish", - "Weather:Stars Fading Duration", - "Weather:Snow Ripples", - "Weather:Snow Ripple Radius", - "Weather:Snow Ripples Per Flake", - "Weather:Snow Ripple Scale", - "Weather:Snow Ripple Speed", - "Weather:Snow Gravity Scale", - "Weather:Snow High Kill", - "Weather:Snow Low Kill", + "Weather:EnvReduceColor", "Weather:LerpCloseColor", "Weather:BumpFadeColor", "Weather:AlphaReduce", + "Weather:Minimum Time Between Environmental Sounds", "Weather:Maximum Time Between Environmental Sounds", + "Weather:Sun Glare Fader Max", "Weather:Sun Glare Fader Angle Max", "Weather:Sun Glare Fader Color", + "Weather:Timescale Clouds", "Weather:Precip Gravity", "Weather:Rain Ripples", "Weather:Rain Ripple Radius", + "Weather:Rain Ripples Per Drop", "Weather:Rain Ripple Scale", "Weather:Rain Ripple Speed", + "Weather:Fog Depth Change Speed", "Weather:Sky Pre-Sunrise Time", "Weather:Sky Post-Sunrise Time", + "Weather:Sky Pre-Sunset Time", "Weather:Sky Post-Sunset Time", "Weather:Ambient Pre-Sunrise Time", + "Weather:Ambient Post-Sunrise Time", "Weather:Ambient Pre-Sunset Time", "Weather:Ambient Post-Sunset Time", + "Weather:Fog Pre-Sunrise Time", "Weather:Fog Post-Sunrise Time", "Weather:Fog Pre-Sunset Time", + "Weather:Fog Post-Sunset Time", "Weather:Sun Pre-Sunrise Time", "Weather:Sun Post-Sunrise Time", + "Weather:Sun Pre-Sunset Time", "Weather:Sun Post-Sunset Time", "Weather:Stars Post-Sunset Start", + "Weather:Stars Pre-Sunrise Finish", "Weather:Stars Fading Duration", "Weather:Snow Ripples", + "Weather:Snow Ripple Radius", "Weather:Snow Ripples Per Flake", "Weather:Snow Ripple Scale", + "Weather:Snow Ripple Speed", "Weather:Snow Gravity Scale", "Weather:Snow High Kill", "Weather:Snow Low Kill", - "Weather Clear:Cloud Texture", - "Weather Clear:Clouds Maximum Percent", - "Weather Clear:Transition Delta", - "Weather Clear:Sky Sunrise Color", - "Weather Clear:Sky Day Color", - "Weather Clear:Sky Sunset Color", - "Weather Clear:Sky Night Color", - "Weather Clear:Fog Sunrise Color", - "Weather Clear:Fog Day Color", - "Weather Clear:Fog Sunset Color", - "Weather Clear:Fog Night Color", - "Weather Clear:Ambient Sunrise Color", - "Weather Clear:Ambient Day Color", - "Weather Clear:Ambient Sunset Color", - "Weather Clear:Ambient Night Color", - "Weather Clear:Sun Sunrise Color", - "Weather Clear:Sun Day Color", - "Weather Clear:Sun Sunset Color", - "Weather Clear:Sun Night Color", - "Weather Clear:Sun Disc Sunset Color", - "Weather Clear:Land Fog Day Depth", - "Weather Clear:Land Fog Night Depth", - "Weather Clear:Wind Speed", - "Weather Clear:Cloud Speed", - "Weather Clear:Glare View", - "Weather Clear:Ambient Loop Sound ID", + "Weather Clear:Cloud Texture", "Weather Clear:Clouds Maximum Percent", "Weather Clear:Transition Delta", + "Weather Clear:Sky Sunrise Color", "Weather Clear:Sky Day Color", "Weather Clear:Sky Sunset Color", + "Weather Clear:Sky Night Color", "Weather Clear:Fog Sunrise Color", "Weather Clear:Fog Day Color", + "Weather Clear:Fog Sunset Color", "Weather Clear:Fog Night Color", "Weather Clear:Ambient Sunrise Color", + "Weather Clear:Ambient Day Color", "Weather Clear:Ambient Sunset Color", "Weather Clear:Ambient Night Color", + "Weather Clear:Sun Sunrise Color", "Weather Clear:Sun Day Color", "Weather Clear:Sun Sunset Color", + "Weather Clear:Sun Night Color", "Weather Clear:Sun Disc Sunset Color", "Weather Clear:Land Fog Day Depth", + "Weather Clear:Land Fog Night Depth", "Weather Clear:Wind Speed", "Weather Clear:Cloud Speed", + "Weather Clear:Glare View", "Weather Clear:Ambient Loop Sound ID", - "Weather Cloudy:Cloud Texture", - "Weather Cloudy:Clouds Maximum Percent", - "Weather Cloudy:Transition Delta", - "Weather Cloudy:Sky Sunrise Color", - "Weather Cloudy:Sky Day Color", - "Weather Cloudy:Sky Sunset Color", - "Weather Cloudy:Sky Night Color", - "Weather Cloudy:Fog Sunrise Color", - "Weather Cloudy:Fog Day Color", - "Weather Cloudy:Fog Sunset Color", - "Weather Cloudy:Fog Night Color", - "Weather Cloudy:Ambient Sunrise Color", - "Weather Cloudy:Ambient Day Color", - "Weather Cloudy:Ambient Sunset Color", - "Weather Cloudy:Ambient Night Color", - "Weather Cloudy:Sun Sunrise Color", - "Weather Cloudy:Sun Day Color", - "Weather Cloudy:Sun Sunset Color", - "Weather Cloudy:Sun Night Color", - "Weather Cloudy:Sun Disc Sunset Color", - "Weather Cloudy:Land Fog Day Depth", - "Weather Cloudy:Land Fog Night Depth", - "Weather Cloudy:Wind Speed", - "Weather Cloudy:Cloud Speed", - "Weather Cloudy:Glare View", - "Weather Cloudy:Ambient Loop Sound ID", + "Weather Cloudy:Cloud Texture", "Weather Cloudy:Clouds Maximum Percent", "Weather Cloudy:Transition Delta", + "Weather Cloudy:Sky Sunrise Color", "Weather Cloudy:Sky Day Color", "Weather Cloudy:Sky Sunset Color", + "Weather Cloudy:Sky Night Color", "Weather Cloudy:Fog Sunrise Color", "Weather Cloudy:Fog Day Color", + "Weather Cloudy:Fog Sunset Color", "Weather Cloudy:Fog Night Color", "Weather Cloudy:Ambient Sunrise Color", + "Weather Cloudy:Ambient Day Color", "Weather Cloudy:Ambient Sunset Color", "Weather Cloudy:Ambient Night Color", + "Weather Cloudy:Sun Sunrise Color", "Weather Cloudy:Sun Day Color", "Weather Cloudy:Sun Sunset Color", + "Weather Cloudy:Sun Night Color", "Weather Cloudy:Sun Disc Sunset Color", "Weather Cloudy:Land Fog Day Depth", + "Weather Cloudy:Land Fog Night Depth", "Weather Cloudy:Wind Speed", "Weather Cloudy:Cloud Speed", + "Weather Cloudy:Glare View", "Weather Cloudy:Ambient Loop Sound ID", - "Weather Foggy:Cloud Texture", - "Weather Foggy:Clouds Maximum Percent", - "Weather Foggy:Transition Delta", - "Weather Foggy:Sky Sunrise Color", - "Weather Foggy:Sky Day Color", - "Weather Foggy:Sky Sunset Color", - "Weather Foggy:Sky Night Color", - "Weather Foggy:Fog Sunrise Color", - "Weather Foggy:Fog Day Color", - "Weather Foggy:Fog Sunset Color", - "Weather Foggy:Fog Night Color", - "Weather Foggy:Ambient Sunrise Color", - "Weather Foggy:Ambient Day Color", - "Weather Foggy:Ambient Sunset Color", - "Weather Foggy:Ambient Night Color", - "Weather Foggy:Sun Sunrise Color", - "Weather Foggy:Sun Day Color", - "Weather Foggy:Sun Sunset Color", - "Weather Foggy:Sun Night Color", - "Weather Foggy:Sun Disc Sunset Color", - "Weather Foggy:Land Fog Day Depth", - "Weather Foggy:Land Fog Night Depth", - "Weather Foggy:Wind Speed", - "Weather Foggy:Cloud Speed", - "Weather Foggy:Glare View", - "Weather Foggy:Ambient Loop Sound ID", + "Weather Foggy:Cloud Texture", "Weather Foggy:Clouds Maximum Percent", "Weather Foggy:Transition Delta", + "Weather Foggy:Sky Sunrise Color", "Weather Foggy:Sky Day Color", "Weather Foggy:Sky Sunset Color", + "Weather Foggy:Sky Night Color", "Weather Foggy:Fog Sunrise Color", "Weather Foggy:Fog Day Color", + "Weather Foggy:Fog Sunset Color", "Weather Foggy:Fog Night Color", "Weather Foggy:Ambient Sunrise Color", + "Weather Foggy:Ambient Day Color", "Weather Foggy:Ambient Sunset Color", "Weather Foggy:Ambient Night Color", + "Weather Foggy:Sun Sunrise Color", "Weather Foggy:Sun Day Color", "Weather Foggy:Sun Sunset Color", + "Weather Foggy:Sun Night Color", "Weather Foggy:Sun Disc Sunset Color", "Weather Foggy:Land Fog Day Depth", + "Weather Foggy:Land Fog Night Depth", "Weather Foggy:Wind Speed", "Weather Foggy:Cloud Speed", + "Weather Foggy:Glare View", "Weather Foggy:Ambient Loop Sound ID", - "Weather Thunderstorm:Cloud Texture", - "Weather Thunderstorm:Clouds Maximum Percent", - "Weather Thunderstorm:Transition Delta", - "Weather Thunderstorm:Sky Sunrise Color", - "Weather Thunderstorm:Sky Day Color", - "Weather Thunderstorm:Sky Sunset Color", - "Weather Thunderstorm:Sky Night Color", - "Weather Thunderstorm:Fog Sunrise Color", - "Weather Thunderstorm:Fog Day Color", - "Weather Thunderstorm:Fog Sunset Color", - "Weather Thunderstorm:Fog Night Color", - "Weather Thunderstorm:Ambient Sunrise Color", - "Weather Thunderstorm:Ambient Day Color", - "Weather Thunderstorm:Ambient Sunset Color", - "Weather Thunderstorm:Ambient Night Color", - "Weather Thunderstorm:Sun Sunrise Color", - "Weather Thunderstorm:Sun Day Color", - "Weather Thunderstorm:Sun Sunset Color", - "Weather Thunderstorm:Sun Night Color", - "Weather Thunderstorm:Sun Disc Sunset Color", - "Weather Thunderstorm:Land Fog Day Depth", - "Weather Thunderstorm:Land Fog Night Depth", - "Weather Thunderstorm:Wind Speed", - "Weather Thunderstorm:Cloud Speed", - "Weather Thunderstorm:Glare View", - "Weather Thunderstorm:Rain Loop Sound ID", - "Weather Thunderstorm:Using Precip", - "Weather Thunderstorm:Rain Diameter", - "Weather Thunderstorm:Rain Height Min", - "Weather Thunderstorm:Rain Height Max", - "Weather Thunderstorm:Rain Threshold", - "Weather Thunderstorm:Max Raindrops", - "Weather Thunderstorm:Rain Entrance Speed", - "Weather Thunderstorm:Ambient Loop Sound ID", - "Weather Thunderstorm:Flash Decrement", + "Weather Thunderstorm:Cloud Texture", "Weather Thunderstorm:Clouds Maximum Percent", + "Weather Thunderstorm:Transition Delta", "Weather Thunderstorm:Sky Sunrise Color", + "Weather Thunderstorm:Sky Day Color", "Weather Thunderstorm:Sky Sunset Color", + "Weather Thunderstorm:Sky Night Color", "Weather Thunderstorm:Fog Sunrise Color", + "Weather Thunderstorm:Fog Day Color", "Weather Thunderstorm:Fog Sunset Color", + "Weather Thunderstorm:Fog Night Color", "Weather Thunderstorm:Ambient Sunrise Color", + "Weather Thunderstorm:Ambient Day Color", "Weather Thunderstorm:Ambient Sunset Color", + "Weather Thunderstorm:Ambient Night Color", "Weather Thunderstorm:Sun Sunrise Color", + "Weather Thunderstorm:Sun Day Color", "Weather Thunderstorm:Sun Sunset Color", + "Weather Thunderstorm:Sun Night Color", "Weather Thunderstorm:Sun Disc Sunset Color", + "Weather Thunderstorm:Land Fog Day Depth", "Weather Thunderstorm:Land Fog Night Depth", + "Weather Thunderstorm:Wind Speed", "Weather Thunderstorm:Cloud Speed", "Weather Thunderstorm:Glare View", + "Weather Thunderstorm:Rain Loop Sound ID", "Weather Thunderstorm:Using Precip", + "Weather Thunderstorm:Rain Diameter", "Weather Thunderstorm:Rain Height Min", + "Weather Thunderstorm:Rain Height Max", "Weather Thunderstorm:Rain Threshold", + "Weather Thunderstorm:Max Raindrops", "Weather Thunderstorm:Rain Entrance Speed", + "Weather Thunderstorm:Ambient Loop Sound ID", "Weather Thunderstorm:Flash Decrement", - "Weather Rain:Cloud Texture", - "Weather Rain:Clouds Maximum Percent", - "Weather Rain:Transition Delta", - "Weather Rain:Sky Sunrise Color", - "Weather Rain:Sky Day Color", - "Weather Rain:Sky Sunset Color", - "Weather Rain:Sky Night Color", - "Weather Rain:Fog Sunrise Color", - "Weather Rain:Fog Day Color", - "Weather Rain:Fog Sunset Color", - "Weather Rain:Fog Night Color", - "Weather Rain:Ambient Sunrise Color", - "Weather Rain:Ambient Day Color", - "Weather Rain:Ambient Sunset Color", - "Weather Rain:Ambient Night Color", - "Weather Rain:Sun Sunrise Color", - "Weather Rain:Sun Day Color", - "Weather Rain:Sun Sunset Color", - "Weather Rain:Sun Night Color", - "Weather Rain:Sun Disc Sunset Color", - "Weather Rain:Land Fog Day Depth", - "Weather Rain:Land Fog Night Depth", - "Weather Rain:Wind Speed", - "Weather Rain:Cloud Speed", - "Weather Rain:Glare View", - "Weather Rain:Rain Loop Sound ID", - "Weather Rain:Using Precip", - "Weather Rain:Rain Diameter", - "Weather Rain:Rain Height Min", - "Weather Rain:Rain Height Max", - "Weather Rain:Rain Threshold", - "Weather Rain:Rain Entrance Speed", - "Weather Rain:Ambient Loop Sound ID", + "Weather Rain:Cloud Texture", "Weather Rain:Clouds Maximum Percent", "Weather Rain:Transition Delta", + "Weather Rain:Sky Sunrise Color", "Weather Rain:Sky Day Color", "Weather Rain:Sky Sunset Color", + "Weather Rain:Sky Night Color", "Weather Rain:Fog Sunrise Color", "Weather Rain:Fog Day Color", + "Weather Rain:Fog Sunset Color", "Weather Rain:Fog Night Color", "Weather Rain:Ambient Sunrise Color", + "Weather Rain:Ambient Day Color", "Weather Rain:Ambient Sunset Color", "Weather Rain:Ambient Night Color", + "Weather Rain:Sun Sunrise Color", "Weather Rain:Sun Day Color", "Weather Rain:Sun Sunset Color", + "Weather Rain:Sun Night Color", "Weather Rain:Sun Disc Sunset Color", "Weather Rain:Land Fog Day Depth", + "Weather Rain:Land Fog Night Depth", "Weather Rain:Wind Speed", "Weather Rain:Cloud Speed", + "Weather Rain:Glare View", "Weather Rain:Rain Loop Sound ID", "Weather Rain:Using Precip", + "Weather Rain:Rain Diameter", "Weather Rain:Rain Height Min", "Weather Rain:Rain Height Max", + "Weather Rain:Rain Threshold", "Weather Rain:Rain Entrance Speed", "Weather Rain:Ambient Loop Sound ID", "Weather Rain:Max Raindrops", - "Weather Overcast:Cloud Texture", - "Weather Overcast:Clouds Maximum Percent", - "Weather Overcast:Transition Delta", - "Weather Overcast:Sky Sunrise Color", - "Weather Overcast:Sky Day Color", - "Weather Overcast:Sky Sunset Color", - "Weather Overcast:Sky Night Color", - "Weather Overcast:Fog Sunrise Color", - "Weather Overcast:Fog Day Color", - "Weather Overcast:Fog Sunset Color", - "Weather Overcast:Fog Night Color", - "Weather Overcast:Ambient Sunrise Color", - "Weather Overcast:Ambient Day Color", - "Weather Overcast:Ambient Sunset Color", - "Weather Overcast:Ambient Night Color", - "Weather Overcast:Sun Sunrise Color", - "Weather Overcast:Sun Day Color", - "Weather Overcast:Sun Sunset Color", - "Weather Overcast:Sun Night Color", - "Weather Overcast:Sun Disc Sunset Color", - "Weather Overcast:Land Fog Day Depth", - "Weather Overcast:Land Fog Night Depth", - "Weather Overcast:Wind Speed", - "Weather Overcast:Cloud Speed", - "Weather Overcast:Glare View", - "Weather Overcast:Ambient Loop Sound ID", + "Weather Overcast:Cloud Texture", "Weather Overcast:Clouds Maximum Percent", + "Weather Overcast:Transition Delta", "Weather Overcast:Sky Sunrise Color", "Weather Overcast:Sky Day Color", + "Weather Overcast:Sky Sunset Color", "Weather Overcast:Sky Night Color", "Weather Overcast:Fog Sunrise Color", + "Weather Overcast:Fog Day Color", "Weather Overcast:Fog Sunset Color", "Weather Overcast:Fog Night Color", + "Weather Overcast:Ambient Sunrise Color", "Weather Overcast:Ambient Day Color", + "Weather Overcast:Ambient Sunset Color", "Weather Overcast:Ambient Night Color", + "Weather Overcast:Sun Sunrise Color", "Weather Overcast:Sun Day Color", "Weather Overcast:Sun Sunset Color", + "Weather Overcast:Sun Night Color", "Weather Overcast:Sun Disc Sunset Color", + "Weather Overcast:Land Fog Day Depth", "Weather Overcast:Land Fog Night Depth", "Weather Overcast:Wind Speed", + "Weather Overcast:Cloud Speed", "Weather Overcast:Glare View", "Weather Overcast:Ambient Loop Sound ID", - "Weather Ashstorm:Cloud Texture", - "Weather Ashstorm:Clouds Maximum Percent", - "Weather Ashstorm:Transition Delta", - "Weather Ashstorm:Sky Sunrise Color", - "Weather Ashstorm:Sky Day Color", - "Weather Ashstorm:Sky Sunset Color", - "Weather Ashstorm:Sky Night Color", - "Weather Ashstorm:Fog Sunrise Color", - "Weather Ashstorm:Fog Day Color", - "Weather Ashstorm:Fog Sunset Color", - "Weather Ashstorm:Fog Night Color", - "Weather Ashstorm:Ambient Sunrise Color", - "Weather Ashstorm:Ambient Day Color", - "Weather Ashstorm:Ambient Sunset Color", - "Weather Ashstorm:Ambient Night Color", - "Weather Ashstorm:Sun Sunrise Color", - "Weather Ashstorm:Sun Day Color", - "Weather Ashstorm:Sun Sunset Color", - "Weather Ashstorm:Sun Night Color", - "Weather Ashstorm:Sun Disc Sunset Color", - "Weather Ashstorm:Land Fog Day Depth", - "Weather Ashstorm:Land Fog Night Depth", - "Weather Ashstorm:Wind Speed", - "Weather Ashstorm:Cloud Speed", - "Weather Ashstorm:Glare View", - "Weather Ashstorm:Ambient Loop Sound ID", + "Weather Ashstorm:Cloud Texture", "Weather Ashstorm:Clouds Maximum Percent", + "Weather Ashstorm:Transition Delta", "Weather Ashstorm:Sky Sunrise Color", "Weather Ashstorm:Sky Day Color", + "Weather Ashstorm:Sky Sunset Color", "Weather Ashstorm:Sky Night Color", "Weather Ashstorm:Fog Sunrise Color", + "Weather Ashstorm:Fog Day Color", "Weather Ashstorm:Fog Sunset Color", "Weather Ashstorm:Fog Night Color", + "Weather Ashstorm:Ambient Sunrise Color", "Weather Ashstorm:Ambient Day Color", + "Weather Ashstorm:Ambient Sunset Color", "Weather Ashstorm:Ambient Night Color", + "Weather Ashstorm:Sun Sunrise Color", "Weather Ashstorm:Sun Day Color", "Weather Ashstorm:Sun Sunset Color", + "Weather Ashstorm:Sun Night Color", "Weather Ashstorm:Sun Disc Sunset Color", + "Weather Ashstorm:Land Fog Day Depth", "Weather Ashstorm:Land Fog Night Depth", "Weather Ashstorm:Wind Speed", + "Weather Ashstorm:Cloud Speed", "Weather Ashstorm:Glare View", "Weather Ashstorm:Ambient Loop Sound ID", "Weather Ashstorm:Storm Threshold", - "Weather Blight:Cloud Texture", - "Weather Blight:Clouds Maximum Percent", - "Weather Blight:Transition Delta", - "Weather Blight:Sky Sunrise Color", - "Weather Blight:Sky Day Color", - "Weather Blight:Sky Sunset Color", - "Weather Blight:Sky Night Color", - "Weather Blight:Fog Sunrise Color", - "Weather Blight:Fog Day Color", - "Weather Blight:Fog Sunset Color", - "Weather Blight:Fog Night Color", - "Weather Blight:Ambient Sunrise Color", - "Weather Blight:Ambient Day Color", - "Weather Blight:Ambient Sunset Color", - "Weather Blight:Ambient Night Color", - "Weather Blight:Sun Sunrise Color", - "Weather Blight:Sun Day Color", - "Weather Blight:Sun Sunset Color", - "Weather Blight:Sun Night Color", - "Weather Blight:Sun Disc Sunset Color", - "Weather Blight:Land Fog Day Depth", - "Weather Blight:Land Fog Night Depth", - "Weather Blight:Wind Speed", - "Weather Blight:Cloud Speed", - "Weather Blight:Glare View", - "Weather Blight:Ambient Loop Sound ID", - "Weather Blight:Storm Threshold", + "Weather Blight:Cloud Texture", "Weather Blight:Clouds Maximum Percent", "Weather Blight:Transition Delta", + "Weather Blight:Sky Sunrise Color", "Weather Blight:Sky Day Color", "Weather Blight:Sky Sunset Color", + "Weather Blight:Sky Night Color", "Weather Blight:Fog Sunrise Color", "Weather Blight:Fog Day Color", + "Weather Blight:Fog Sunset Color", "Weather Blight:Fog Night Color", "Weather Blight:Ambient Sunrise Color", + "Weather Blight:Ambient Day Color", "Weather Blight:Ambient Sunset Color", "Weather Blight:Ambient Night Color", + "Weather Blight:Sun Sunrise Color", "Weather Blight:Sun Day Color", "Weather Blight:Sun Sunset Color", + "Weather Blight:Sun Night Color", "Weather Blight:Sun Disc Sunset Color", "Weather Blight:Land Fog Day Depth", + "Weather Blight:Land Fog Night Depth", "Weather Blight:Wind Speed", "Weather Blight:Cloud Speed", + "Weather Blight:Glare View", "Weather Blight:Ambient Loop Sound ID", "Weather Blight:Storm Threshold", "Weather Blight:Disease Chance", // for Bloodmoon - "Weather Snow:Cloud Texture", - "Weather Snow:Clouds Maximum Percent", - "Weather Snow:Transition Delta", - "Weather Snow:Sky Sunrise Color", - "Weather Snow:Sky Day Color", - "Weather Snow:Sky Sunset Color", - "Weather Snow:Sky Night Color", - "Weather Snow:Fog Sunrise Color", - "Weather Snow:Fog Day Color", - "Weather Snow:Fog Sunset Color", - "Weather Snow:Fog Night Color", - "Weather Snow:Ambient Sunrise Color", - "Weather Snow:Ambient Day Color", - "Weather Snow:Ambient Sunset Color", - "Weather Snow:Ambient Night Color", - "Weather Snow:Sun Sunrise Color", - "Weather Snow:Sun Day Color", - "Weather Snow:Sun Sunset Color", - "Weather Snow:Sun Night Color", - "Weather Snow:Sun Disc Sunset Color", - "Weather Snow:Land Fog Day Depth", - "Weather Snow:Land Fog Night Depth", - "Weather Snow:Wind Speed", - "Weather Snow:Cloud Speed", - "Weather Snow:Glare View", - "Weather Snow:Snow Diameter", - "Weather Snow:Snow Height Min", - "Weather Snow:Snow Height Max", - "Weather Snow:Snow Entrance Speed", - "Weather Snow:Max Snowflakes", - "Weather Snow:Ambient Loop Sound ID", - "Weather Snow:Snow Threshold", + "Weather Snow:Cloud Texture", "Weather Snow:Clouds Maximum Percent", "Weather Snow:Transition Delta", + "Weather Snow:Sky Sunrise Color", "Weather Snow:Sky Day Color", "Weather Snow:Sky Sunset Color", + "Weather Snow:Sky Night Color", "Weather Snow:Fog Sunrise Color", "Weather Snow:Fog Day Color", + "Weather Snow:Fog Sunset Color", "Weather Snow:Fog Night Color", "Weather Snow:Ambient Sunrise Color", + "Weather Snow:Ambient Day Color", "Weather Snow:Ambient Sunset Color", "Weather Snow:Ambient Night Color", + "Weather Snow:Sun Sunrise Color", "Weather Snow:Sun Day Color", "Weather Snow:Sun Sunset Color", + "Weather Snow:Sun Night Color", "Weather Snow:Sun Disc Sunset Color", "Weather Snow:Land Fog Day Depth", + "Weather Snow:Land Fog Night Depth", "Weather Snow:Wind Speed", "Weather Snow:Cloud Speed", + "Weather Snow:Glare View", "Weather Snow:Snow Diameter", "Weather Snow:Snow Height Min", + "Weather Snow:Snow Height Max", "Weather Snow:Snow Entrance Speed", "Weather Snow:Max Snowflakes", + "Weather Snow:Ambient Loop Sound ID", "Weather Snow:Snow Threshold", // for Bloodmoon - "Weather Blizzard:Cloud Texture", - "Weather Blizzard:Clouds Maximum Percent", - "Weather Blizzard:Transition Delta", - "Weather Blizzard:Sky Sunrise Color", - "Weather Blizzard:Sky Day Color", - "Weather Blizzard:Sky Sunset Color", - "Weather Blizzard:Sky Night Color", - "Weather Blizzard:Fog Sunrise Color", - "Weather Blizzard:Fog Day Color", - "Weather Blizzard:Fog Sunset Color", - "Weather Blizzard:Fog Night Color", - "Weather Blizzard:Ambient Sunrise Color", - "Weather Blizzard:Ambient Day Color", - "Weather Blizzard:Ambient Sunset Color", - "Weather Blizzard:Ambient Night Color", - "Weather Blizzard:Sun Sunrise Color", - "Weather Blizzard:Sun Day Color", - "Weather Blizzard:Sun Sunset Color", - "Weather Blizzard:Sun Night Color", - "Weather Blizzard:Sun Disc Sunset Color", - "Weather Blizzard:Land Fog Day Depth", - "Weather Blizzard:Land Fog Night Depth", - "Weather Blizzard:Wind Speed", - "Weather Blizzard:Cloud Speed", - "Weather Blizzard:Glare View", - "Weather Blizzard:Ambient Loop Sound ID", + "Weather Blizzard:Cloud Texture", "Weather Blizzard:Clouds Maximum Percent", + "Weather Blizzard:Transition Delta", "Weather Blizzard:Sky Sunrise Color", "Weather Blizzard:Sky Day Color", + "Weather Blizzard:Sky Sunset Color", "Weather Blizzard:Sky Night Color", "Weather Blizzard:Fog Sunrise Color", + "Weather Blizzard:Fog Day Color", "Weather Blizzard:Fog Sunset Color", "Weather Blizzard:Fog Night Color", + "Weather Blizzard:Ambient Sunrise Color", "Weather Blizzard:Ambient Day Color", + "Weather Blizzard:Ambient Sunset Color", "Weather Blizzard:Ambient Night Color", + "Weather Blizzard:Sun Sunrise Color", "Weather Blizzard:Sun Day Color", "Weather Blizzard:Sun Sunset Color", + "Weather Blizzard:Sun Night Color", "Weather Blizzard:Sun Disc Sunset Color", + "Weather Blizzard:Land Fog Day Depth", "Weather Blizzard:Land Fog Night Depth", "Weather Blizzard:Wind Speed", + "Weather Blizzard:Cloud Speed", "Weather Blizzard:Glare View", "Weather Blizzard:Ambient Loop Sound ID", "Weather Blizzard:Storm Threshold", // moons - "Moons:Secunda Size", - "Moons:Secunda Axis Offset", - "Moons:Secunda Speed", - "Moons:Secunda Daily Increment", - "Moons:Secunda Moon Shadow Early Fade Angle", - "Moons:Secunda Fade Start Angle", - "Moons:Secunda Fade End Angle", - "Moons:Secunda Fade In Start", - "Moons:Secunda Fade In Finish", - "Moons:Secunda Fade Out Start", - "Moons:Secunda Fade Out Finish", - "Moons:Masser Size", - "Moons:Masser Axis Offset", - "Moons:Masser Speed", - "Moons:Masser Daily Increment", - "Moons:Masser Moon Shadow Early Fade Angle", - "Moons:Masser Fade Start Angle", - "Moons:Masser Fade End Angle", - "Moons:Masser Fade In Start", - "Moons:Masser Fade In Finish", - "Moons:Masser Fade Out Start", - "Moons:Masser Fade Out Finish", - "Moons:Script Color", + "Moons:Secunda Size", "Moons:Secunda Axis Offset", "Moons:Secunda Speed", "Moons:Secunda Daily Increment", + "Moons:Secunda Moon Shadow Early Fade Angle", "Moons:Secunda Fade Start Angle", "Moons:Secunda Fade End Angle", + "Moons:Secunda Fade In Start", "Moons:Secunda Fade In Finish", "Moons:Secunda Fade Out Start", + "Moons:Secunda Fade Out Finish", "Moons:Masser Size", "Moons:Masser Axis Offset", "Moons:Masser Speed", + "Moons:Masser Daily Increment", "Moons:Masser Moon Shadow Early Fade Angle", "Moons:Masser Fade Start Angle", + "Moons:Masser Fade End Angle", "Moons:Masser Fade In Start", "Moons:Masser Fade In Finish", + "Moons:Masser Fade Out Start", "Moons:Masser Fade Out Finish", "Moons:Script Color", // werewolf (Bloodmoon) "General:Werewolf FOV", @@ -640,107 +256,127 @@ MwIniImporter::MwIniImporter() 0 }; - for(int i=0; map[i][0]; i++) { + for (int i = 0; map[i][0]; i++) + { mMergeMap.insert(std::make_pair(map[i][0], map[i][1])); } - for(int i=0; fallback[i]; i++) { + for (int i = 0; fallback[i]; i++) + { mMergeFallback.emplace_back(fallback[i]); } } -void MwIniImporter::setVerbose(bool verbose) { +void MwIniImporter::setVerbose(bool verbose) +{ mVerbose = verbose; } -MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const { - std::cout << "load ini file: " << filename << std::endl; +MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::filesystem::path& filename) const +{ + std::cout << "load ini file: " << Files::pathToUnicodeString(filename) << std::endl; std::string section(""); MwIniImporter::multistrmap map; - bfs::ifstream file((bfs::path(filename))); + std::ifstream file(filename); ToUTF8::Utf8Encoder encoder(mEncoding); std::string line; - while (std::getline(file, line)) { + while (std::getline(file, line)) + { - line = encoder.getUtf8(line); + std::string_view utf8 = encoder.getUtf8(line); // unify Unix-style and Windows file ending - if (!(line.empty()) && (line[line.length()-1]) == '\r') { - line = line.substr(0, line.length()-1); + if (!(utf8.empty()) && (utf8[utf8.length() - 1]) == '\r') + { + utf8 = utf8.substr(0, utf8.length() - 1); } - if(line.empty()) { + if (utf8.empty()) + { continue; } - if(line[0] == '[') { - int pos = static_cast(line.find(']')); - if(pos < 2) { - std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl; + if (utf8[0] == '[') + { + int pos = static_cast(utf8.find(']')); + if (pos < 2) + { + std::cout << "Warning: ini file wrongly formatted (" << utf8 << "). Line ignored." << std::endl; continue; } - section = line.substr(1, line.find(']')-1); + section = utf8.substr(1, utf8.find(']') - 1); continue; } - int comment_pos = static_cast(line.find(';')); - if(comment_pos > 0) { - line = line.substr(0,comment_pos); + int comment_pos = static_cast(utf8.find(';')); + if (comment_pos > 0) + { + utf8 = utf8.substr(0, comment_pos); } - int pos = static_cast(line.find('=')); - if(pos < 1) { + int pos = static_cast(utf8.find('=')); + if (pos < 1) + { continue; } - std::string key(section + ":" + line.substr(0,pos)); - std::string value(line.substr(pos+1)); - if(value.empty()) { + std::string key(section + ":" + std::string(utf8.substr(0, pos))); + const std::string_view value(utf8.substr(pos + 1)); + if (value.empty()) + { std::cout << "Warning: ignored empty value for key '" << key << "'." << std::endl; continue; } - if(map.find(key) == map.end()) { - map.insert( std::make_pair (key, std::vector() ) ); - } - map[key].push_back(value); + auto it = map.find(key); + + if (it == map.end()) + it = map.emplace_hint(it, std::move(key), std::vector()); + + it->second.push_back(std::string(value)); } return map; } -MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const boost::filesystem::path& filename) { - std::cout << "load cfg file: " << filename << std::endl; +MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::filesystem::path& filename) +{ + std::cout << "load cfg file: " << Files::pathToUnicodeString(filename) << std::endl; MwIniImporter::multistrmap map; - bfs::ifstream file((bfs::path(filename))); + std::ifstream file(filename); std::string line; - while (std::getline(file, line)) { + while (std::getline(file, line)) + { // we cant say comment by only looking at first char anymore int comment_pos = static_cast(line.find('#')); - if(comment_pos > 0) { - line = line.substr(0,comment_pos); + if (comment_pos > 0) + { + line = line.substr(0, comment_pos); } - if(line.empty()) { + if (line.empty()) + { continue; } int pos = static_cast(line.find('=')); - if(pos < 1) { + if (pos < 1) + { continue; } - std::string key(line.substr(0,pos)); - std::string value(line.substr(pos+1)); + std::string key(line.substr(0, pos)); + std::string value(line.substr(pos + 1)); - if(map.find(key) == map.end()) { - map.insert( std::make_pair (key, std::vector() ) ); + if (map.find(key) == map.end()) + { + map.insert(std::make_pair(key, std::vector())); } map[key].push_back(value); } @@ -748,11 +384,15 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const boost::filesystem::p return map; } -void MwIniImporter::merge(multistrmap &cfg, const multistrmap &ini) const { +void MwIniImporter::merge(multistrmap& cfg, const multistrmap& ini) const +{ multistrmap::const_iterator iniIt; - for(strmap::const_iterator it=mMergeMap.begin(); it!=mMergeMap.end(); ++it) { - if((iniIt = ini.find(it->second)) != ini.end()) { - for(std::vector::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) { + for (strmap::const_iterator it = mMergeMap.begin(); it != mMergeMap.end(); ++it) + { + if ((iniIt = ini.find(it->second)) != ini.end()) + { + for (std::vector::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) + { cfg.erase(it->first); insertMultistrmap(cfg, it->first, *vc); } @@ -760,74 +400,82 @@ void MwIniImporter::merge(multistrmap &cfg, const multistrmap &ini) const { } } -void MwIniImporter::mergeFallback(multistrmap &cfg, const multistrmap &ini) const { +void MwIniImporter::mergeFallback(multistrmap& cfg, const multistrmap& ini) const +{ cfg.erase("fallback"); multistrmap::const_iterator iniIt; - for(std::vector::const_iterator it=mMergeFallback.begin(); it!=mMergeFallback.end(); ++it) { - if((iniIt = ini.find(*it)) != ini.end()) { - for(std::vector::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) { + for (std::vector::const_iterator it = mMergeFallback.begin(); it != mMergeFallback.end(); ++it) + { + if ((iniIt = ini.find(*it)) != ini.end()) + { + for (std::vector::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) + { std::string value(*it); - std::replace( value.begin(), value.end(), ' ', '_' ); - std::replace( value.begin(), value.end(), ':', '_' ); - value.append(",").append(vc->substr(0,vc->length())); + std::replace(value.begin(), value.end(), ' ', '_'); + std::replace(value.begin(), value.end(), ':', '_'); + value.append(",").append(vc->substr(0, vc->length())); insertMultistrmap(cfg, "fallback", value); } } } } -void MwIniImporter::insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value) { - const multistrmap::const_iterator it = cfg.find(key); - if(it == cfg.end()) { - cfg.insert(std::make_pair (key, std::vector() )); +void MwIniImporter::insertMultistrmap(multistrmap& cfg, const std::string& key, const std::string& value) +{ + const auto it = cfg.find(key); + if (it == cfg.end()) + { + cfg.insert(std::make_pair(key, std::vector())); } cfg[key].push_back(value); } -void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) const { +void MwIniImporter::importArchives(multistrmap& cfg, const multistrmap& ini) const +{ std::vector archives; std::string baseArchive("Archives:Archive "); std::string archive; // Search archives listed in ini file - multistrmap::const_iterator it = ini.begin(); - for(int i=0; it != ini.end(); i++) { + auto it = ini.begin(); + for (int i = 0; it != ini.end(); i++) + { archive = baseArchive; archive.append(std::to_string(i)); it = ini.find(archive); - if(it == ini.end()) { + if (it == ini.end()) + { break; } - for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { + for (std::vector::const_iterator entry = it->second.begin(); entry != it->second.end(); ++entry) + { archives.push_back(*entry); } } cfg.erase("fallback-archive"); - cfg.insert( std::make_pair > ("fallback-archive", std::vector())); + cfg.insert(std::make_pair>("fallback-archive", std::vector())); // Add Morrowind.bsa by default, since Vanilla loads this archive even if it // does not appears in the ini file cfg["fallback-archive"].push_back("Morrowind.bsa"); - for(std::vector::const_iterator iter=archives.begin(); iter!=archives.end(); ++iter) { + for (auto iter = archives.begin(); iter != archives.end(); ++iter) + { cfg["fallback-archive"].push_back(*iter); } } -void MwIniImporter::dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector& result) +void MwIniImporter::dependencySortStep( + std::string& element, MwIniImporter::dependencyList& source, std::vector& result) { auto iter = std::find_if( - source.begin(), - source.end(), - [&element](std::pair< std::string, std::vector >& sourceElement) - { + source.begin(), source.end(), [&element](std::pair>& sourceElement) { return sourceElement.first == element; - } - ); + }); if (iter != source.end()) { auto foundElement = std::move(*iter); @@ -850,15 +498,15 @@ std::vector MwIniImporter::dependencySort(MwIniImporter::dependency return result; } -std::vector::iterator MwIniImporter::findString(std::vector& source, const std::string& string) +std::vector::iterator MwIniImporter::findString( + std::vector& source, const std::string& string) { - return std::find_if(source.begin(), source.end(), [&string](const std::string& sourceString) - { - return Misc::StringUtils::ciEqual(sourceString, string); - }); + return std::find_if(source.begin(), source.end(), + [&string](const std::string& sourceString) { return Misc::StringUtils::ciEqual(sourceString, string); }); } -void MwIniImporter::addPaths(std::vector& output, std::vector input) { +void MwIniImporter::addPaths(std::vector& output, std::vector input) +{ for (auto& path : input) { if (path.front() == '"') @@ -866,18 +514,19 @@ void MwIniImporter::addPaths(std::vector& output, std:: // Drop first and last characters - quotation marks path = path.substr(1, path.size() - 2); } - output.emplace_back(path); + output.emplace_back(Files::pathFromUnicodeString(path)); } } -void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const +void MwIniImporter::importGameFiles( + multistrmap& cfg, const multistrmap& ini, const std::filesystem::path& iniFilename) const { - std::vector> contentFiles; + std::vector> contentFiles; std::string baseGameFile("Game Files:GameFile"); std::time_t defaultTime = 0; ToUTF8::Utf8Encoder encoder(mEncoding); - std::vector dataPaths; + std::vector dataPaths; if (cfg.count("data")) addPaths(dataPaths, cfg["data"]); @@ -886,27 +535,24 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co dataPaths.push_back(iniFilename.parent_path() /= "Data Files"); - multistrmap::const_iterator it = ini.begin(); - for (int i=0; it != ini.end(); i++) + auto it = ini.begin(); + for (int i = 0; it != ini.end(); i++) { std::string gameFile = baseGameFile; gameFile.append(std::to_string(i)); it = ini.find(gameFile); - if(it == ini.end()) + if (it == ini.end()) break; - for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) + for (const std::string& file : it->second) { - std::string filetype(entry->substr(entry->length()-3)); - Misc::StringUtils::lowerCaseInPlace(filetype); - - if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) + if (Misc::StringUtils::ciEndsWith(file, "esm") || Misc::StringUtils::ciEndsWith(file, "esp")) { bool found = false; - for (auto & dataPath : dataPaths) + for (auto& dataPath : dataPaths) { - boost::filesystem::path path = dataPath / *entry; + std::filesystem::path path = dataPath / file; std::time_t time = lastWriteTime(path, defaultTime); if (time != defaultTime) { @@ -916,13 +562,13 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co } } if (!found) - std::cout << "Warning: " << *entry << " not found, ignoring" << std::endl; + std::cout << "Warning: " << file << " not found, ignoring" << std::endl; } } } cfg.erase("content"); - cfg.insert( std::make_pair("content", std::vector() ) ); + cfg.insert(std::make_pair("content", std::vector())); // sort by timestamp sort(contentFiles.begin(), contentFiles.end()); @@ -933,28 +579,28 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co reader.setEncoder(&encoder); for (auto& file : contentFiles) { - reader.open(file.second.string()); + reader.open(file.second); std::vector dependencies; for (auto& gameFile : reader.getGameFiles()) { dependencies.push_back(gameFile.name); } - unsortedFiles.emplace_back(boost::filesystem::path(reader.getName()).filename().string(), dependencies); + unsortedFiles.emplace_back(Files::pathToUnicodeString(reader.getName().filename()), dependencies); reader.close(); } auto sortedFiles = dependencySort(unsortedFiles); // hard-coded dependency Morrowind - Tribunal - Bloodmoon - if(findString(sortedFiles, "Morrowind.esm") != sortedFiles.end()) + if (findString(sortedFiles, "Morrowind.esm") != sortedFiles.end()) { - auto tribunalIter = findString(sortedFiles, "Tribunal.esm"); + auto tribunalIter = findString(sortedFiles, "Tribunal.esm"); auto bloodmoonIter = findString(sortedFiles, "Bloodmoon.esm"); if (bloodmoonIter != sortedFiles.end() && tribunalIter != sortedFiles.end()) { size_t bloodmoonIndex = std::distance(sortedFiles.begin(), bloodmoonIter); - size_t tribunalIndex = std::distance(sortedFiles.begin(), tribunalIter); + size_t tribunalIndex = std::distance(sortedFiles.begin(), tribunalIter); if (bloodmoonIndex < tribunalIndex) tribunalIndex++; sortedFiles.insert(bloodmoonIter, *tribunalIter); @@ -966,34 +612,36 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co cfg["content"].push_back(file); } -void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) { +void MwIniImporter::writeToFile(std::ostream& out, const multistrmap& cfg) +{ - for(multistrmap::const_iterator it=cfg.begin(); it != cfg.end(); ++it) { - for(std::vector::const_iterator entry=it->second.begin(); entry != it->second.end(); ++entry) { + for (multistrmap::const_iterator it = cfg.begin(); it != cfg.end(); ++it) + { + for (auto entry = it->second.begin(); entry != it->second.end(); ++entry) + { out << (it->first) << "=" << (*entry) << std::endl; } } } -void MwIniImporter::setInputEncoding(const ToUTF8::FromType &encoding) +void MwIniImporter::setInputEncoding(const ToUTF8::FromType& encoding) { - mEncoding = encoding; + mEncoding = encoding; } -std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime) +std::time_t MwIniImporter::lastWriteTime(const std::filesystem::path& filename, std::time_t defaultTime) { std::time_t writeTime(defaultTime); - if (boost::filesystem::exists(filename)) + if (std::filesystem::exists(filename)) { - boost::filesystem::path resolved = boost::filesystem::canonical(filename); - writeTime = boost::filesystem::last_write_time(resolved); + std::filesystem::path resolved = std::filesystem::canonical(filename); + const auto time = std::filesystem::last_write_time(resolved); + writeTime = Misc::toTimeT(time); // print timestamp - const int size=1024; - char timeStrBuffer[size]; - if (std::strftime(timeStrBuffer, size, "%x %X", localtime(&writeTime)) > 0) - std::cout << "content file: " << resolved << " timestamp = (" << writeTime << - ") " << timeStrBuffer << std::endl; + const auto str = Misc::fileTimeToString(time, "%x %X"); + if (!str.empty()) + std::cout << "content file: " << resolved << " timestamp = (" << writeTime << ") " << str << std::endl; } return writeTime; } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 7b710a4a4..4c42caf5a 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -1,44 +1,45 @@ #ifndef MWINIIMPORTER_IMPORTER #define MWINIIMPORTER_IMPORTER 1 -#include -#include -#include #include +#include #include -#include +#include +#include +#include #include -class MwIniImporter { - public: +class MwIniImporter +{ +public: typedef std::map strmap; - typedef std::map > multistrmap; - typedef std::vector< std::pair< std::string, std::vector > > dependencyList; + typedef std::map> multistrmap; + typedef std::vector>> dependencyList; MwIniImporter(); - void setInputEncoding(const ToUTF8::FromType& encoding); - void setVerbose(bool verbose); - multistrmap loadIniFile(const boost::filesystem::path& filename) const; - static multistrmap loadCfgFile(const boost::filesystem::path& filename); - void merge(multistrmap &cfg, const multistrmap &ini) const; - void mergeFallback(multistrmap &cfg, const multistrmap &ini) const; - void importGameFiles(multistrmap &cfg, const multistrmap &ini, - const boost::filesystem::path& iniFilename) const; - void importArchives(multistrmap &cfg, const multistrmap &ini) const; - static void writeToFile(std::ostream &out, const multistrmap &cfg); + void setInputEncoding(const ToUTF8::FromType& encoding); + void setVerbose(bool verbose); + multistrmap loadIniFile(const std::filesystem::path& filename) const; + static multistrmap loadCfgFile(const std::filesystem::path& filename); + void merge(multistrmap& cfg, const multistrmap& ini) const; + void mergeFallback(multistrmap& cfg, const multistrmap& ini) const; + void importGameFiles(multistrmap& cfg, const multistrmap& ini, const std::filesystem::path& iniFilename) const; + void importArchives(multistrmap& cfg, const multistrmap& ini) const; + static void writeToFile(std::ostream& out, const multistrmap& cfg); static std::vector dependencySort(MwIniImporter::dependencyList source); - private: - static void dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector& result); +private: + static void dependencySortStep( + std::string& element, MwIniImporter::dependencyList& source, std::vector& result); static std::vector::iterator findString(std::vector& source, const std::string& string); - static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); - static void addPaths(std::vector& output, std::vector input); + static void insertMultistrmap(multistrmap& cfg, const std::string& key, const std::string& value); + static void addPaths(std::vector& output, std::vector input); /// \return file's "last modified time", used in original MW to determine plug-in load order - static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime); + static std::time_t lastWriteTime(const std::filesystem::path& filename, std::time_t defaultTime); bool mVerbose; strmap mMergeMap; diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 8a2aeb603..0beb2b38c 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -1,44 +1,51 @@ #include "importer.hpp" +#include +#include #include #include -#include -#include + +#include +#include namespace bpo = boost::program_options; -namespace bfs = boost::filesystem; +namespace sfs = std::filesystem; #ifndef _WIN32 -int main(int argc, char *argv[]) { +int main(int argc, char* argv[]) +{ #else // Include on Windows only -#include +#include +#include class utf8argv { public: - - utf8argv(int argc, wchar_t *wargv[]) + utf8argv(int argc, wchar_t* wargv[]) { args.reserve(argc); - argv = new const char *[argc]; + argv = new const char*[argc]; - for (int i = 0; i < argc; ++i) { - args.push_back(boost::locale::conv::utf_to_utf(wargv[i])); + std::wstring_convert> converter; + + for (int i = 0; i < argc; ++i) + { + args.push_back(converter.to_bytes(wargv[i])); argv[i] = args.back().c_str(); } } ~utf8argv() { delete[] argv; } - char **get() const { return const_cast(argv); } + char** get() const { return const_cast(argv); } private: utf8argv(const utf8argv&); utf8argv& operator=(const utf8argv&); - const char **argv; + const char** argv; std::vector args; }; @@ -46,65 +53,74 @@ private: characters interface which presents UTF-16 encoding. The rest of OpenMW application stack assumes UTF-8 encoding, therefore this conversion. - - For boost::filesystem::path::imbue see components/files/windowspath.cpp */ -int wmain(int argc, wchar_t *wargv[]) { +int wmain(int argc, wchar_t* wargv[]) +{ utf8argv converter(argc, wargv); - char **argv = converter.get(); - boost::filesystem::path::imbue(boost::locale::generator().generate("")); + char** argv = converter.get(); #endif try { bpo::options_description desc("Syntax: openmw-iniimporter inifile configfile\nAllowed options"); bpo::positional_options_description p_desc; - desc.add_options() - ("help,h", "produce help message") - ("verbose,v", "verbose output") - ("ini,i", bpo::value(), "morrowind.ini file") - ("cfg,c", bpo::value(), "openmw.cfg file") - ("output,o", bpo::value()->default_value(""), "openmw.cfg file") - ("game-files,g", "import esm and esp files") - ("no-archives,A", "disable bsa archives import") - ("encoding,e", bpo::value()-> default_value("win1252"), - "Character encoding used in OpenMW game messages:\n" - "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" - "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" - "\n\twin1252 - Western European (Latin) alphabet, used by default") - ; + auto addOption = desc.add_options(); + addOption("help,h", "produce help message"); + addOption("verbose,v", "verbose output"); + addOption("ini,i", bpo::value(), "morrowind.ini file"); + addOption("cfg,c", bpo::value(), "openmw.cfg file"); + addOption("output,o", bpo::value()->default_value({}), "openmw.cfg file"); + addOption("game-files,g", "import esm and esp files"); + addOption("fonts,f", "import bitmap fonts"); + addOption("no-archives,A", "disable bsa archives import"); + addOption("encoding,e", bpo::value()->default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, " + "Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default"); + ; p_desc.add("ini", 1).add("cfg", 1); bpo::variables_map vm; - bpo::parsed_options parsed = bpo::command_line_parser(argc, argv) - .options(desc) - .positional(p_desc) - .run(); - + bpo::parsed_options parsed = bpo::command_line_parser(argc, argv).options(desc).positional(p_desc).run(); bpo::store(parsed, vm); - if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) { + if (vm.count("help") || !vm.count("ini") || !vm.count("cfg")) + { std::cout << desc; return 0; } bpo::notify(vm); - boost::filesystem::path iniFile(vm["ini"].as()); - boost::filesystem::path cfgFile(vm["cfg"].as()); + std::filesystem::path iniFile( + vm["ini"].as().u8string()); // This call to u8string is redundant, but required to + // build on MSVC 14.26 due to implementation bugs. + std::filesystem::path cfgFile( + vm["cfg"].as().u8string()); // This call to u8string is redundant, but required to + // build on MSVC 14.26 due to implementation bugs. // if no output is given, write back to cfg file - std::string outputFile(vm["output"].as()); - if(vm["output"].defaulted()) { - outputFile = vm["cfg"].as(); + std::filesystem::path outputFile = vm["output"] + .as() + .u8string(); // This call to u8string is redundant, but required to build + // on MSVC 14.26 due to implementation bugs. + if (vm["output"].defaulted()) + { + outputFile = vm["cfg"] + .as() + .u8string(); // This call to u8string is redundant, but required to build on MSVC 14.26 due + // to implementation bugs. } - if(!boost::filesystem::exists(iniFile)) { + if (!std::filesystem::exists(iniFile)) + { std::cerr << "ini file does not exist" << std::endl; return -3; } - if(!boost::filesystem::exists(cfgFile)) + if (!std::filesystem::exists(cfgFile)) std::cerr << "cfg file does not exist" << std::endl; MwIniImporter importer; @@ -117,19 +133,28 @@ int wmain(int argc, wchar_t *wargv[]) { MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); + if (!vm.count("fonts")) + { + ini.erase("Fonts:Font 0"); + ini.erase("Fonts:Font 1"); + ini.erase("Fonts:Font 2"); + } + importer.merge(cfg, ini); importer.mergeFallback(cfg, ini); - if(vm.count("game-files")) { + if (vm.count("game-files")) + { importer.importGameFiles(cfg, ini, iniFile); } - if(!vm.count("no-archives")) { + if (!vm.count("no-archives")) + { importer.importArchives(cfg, ini); } - std::cout << "write to: " << outputFile << std::endl; - bfs::ofstream file((bfs::path(outputFile))); + std::cout << "write to: " << Files::pathToUnicodeString(outputFile) << std::endl; + std::ofstream file(outputFile); importer.writeToFile(file, cfg); } catch (std::exception& e) diff --git a/apps/navmeshtool/CMakeLists.txt b/apps/navmeshtool/CMakeLists.txt new file mode 100644 index 000000000..09d7af3b2 --- /dev/null +++ b/apps/navmeshtool/CMakeLists.txt @@ -0,0 +1,31 @@ +set(NAVMESHTOOL + worldspacedata.cpp + navmesh.cpp + main.cpp +) +source_group(apps\\navmeshtool FILES ${NAVMESHTOOL}) + +openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL}) + +target_link_libraries(openmw-navmeshtool + ${Boost_PROGRAM_OPTIONS_LIBRARY} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + target_compile_options(openmw-navmeshtool PRIVATE --coverage) + target_link_libraries(openmw-navmeshtool gcov) +endif() + +if (WIN32) + install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".") +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw-navmeshtool PRIVATE + + + + + ) +endif() diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp new file mode 100644 index 000000000..e5126d920 --- /dev/null +++ b/apps/navmeshtool/main.cpp @@ -0,0 +1,263 @@ +#include "navmesh.hpp" +#include "worldspacedata.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#endif + +namespace NavMeshTool +{ + namespace + { + namespace bpo = boost::program_options; + + using StringsVector = std::vector; + + constexpr std::string_view applicationName = "NavMeshTool"; + + bpo::options_description makeOptionsDescription() + { + using Fallback::FallbackMap; + + bpo::options_description result; + auto addOption = result.add_options(); + addOption("help", "print help message"); + + addOption("version", "print version information and quit"); + + addOption("data", + bpo::value() + ->default_value(Files::MaybeQuotedPathContainer(), "data") + ->multitoken() + ->composing(), + "set data directories (later directories have higher priority)"); + + addOption("data-local", + bpo::value()->default_value( + Files::MaybeQuotedPathContainer::value_type(), ""), + "set local data directory (highest priority)"); + + addOption("fallback-archive", + bpo::value() + ->default_value(StringsVector(), "fallback-archive") + ->multitoken() + ->composing(), + "set fallback BSA archives (later archives have higher priority)"); + + addOption("resources", + bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), + "set resources directory"); + + addOption("content", + bpo::value()->default_value(StringsVector(), "")->multitoken()->composing(), + "content file(s): esm/esp, or omwgame/omwaddon/omwscripts"); + + addOption("encoding", bpo::value()->default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, " + "Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default"); + + addOption("fallback", + bpo::value() + ->default_value(Fallback::FallbackMap(), "") + ->multitoken() + ->composing(), + "fallback values"); + + addOption("threads", + bpo::value()->default_value( + std::max(std::thread::hardware_concurrency() - 1, 1)), + "number of threads for parallel processing"); + + addOption("process-interior-cells", bpo::value()->implicit_value(true)->default_value(false), + "build navmesh for interior cells"); + + addOption("remove-unused-tiles", bpo::value()->implicit_value(true)->default_value(false), + "remove tiles from cache that will not be used with current content profile"); + + addOption("write-binary-log", bpo::value()->implicit_value(true)->default_value(false), + "write progress in binary messages to be consumed by the launcher"); + + Files::ConfigurationManager::addCommonOptions(result); + + return result; + } + + int runNavMeshTool(int argc, char* argv[]) + { + Platform::init(); + + bpo::options_description desc = makeOptionsDescription(); + + bpo::parsed_options options = bpo::command_line_parser(argc, argv).options(desc).allow_unregistered().run(); + bpo::variables_map variables; + + bpo::store(options, variables); + bpo::notify(variables); + + if (variables.find("help") != variables.end()) + { + getRawStdout() << desc << std::endl; + return 0; + } + + Files::ConfigurationManager config; + config.readConfiguration(variables, desc); + + setupLogging(config.getLogPath(), applicationName); + + const std::string encoding(variables["encoding"].as()); + Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding)); + + Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); + + auto local = variables["data-local"].as(); + if (!local.empty()) + dataDirs.push_back(std::move(local)); + + config.filterOutNonExistingPaths(dataDirs); + + const auto resDir = variables["resources"].as(); + Version::Version v = Version::getOpenmwVersion(resDir); + Log(Debug::Info) << v.describe(); + dataDirs.insert(dataDirs.begin(), resDir / "vfs"); + const auto fileCollections = Files::Collections(dataDirs); + const auto archives = variables["fallback-archive"].as(); + const auto contentFiles = variables["content"].as(); + const std::size_t threadsNumber = variables["threads"].as(); + + if (threadsNumber < 1) + { + std::cerr << "Invalid threads number: " << threadsNumber << ", expected >= 1"; + return -1; + } + + const bool processInteriorCells = variables["process-interior-cells"].as(); + const bool removeUnusedTiles = variables["remove-unused-tiles"].as(); + const bool writeBinaryLog = variables["write-binary-log"].as(); + +#ifdef WIN32 + if (writeBinaryLog) + _setmode(_fileno(stderr), _O_BINARY); +#endif + + Fallback::Map::init(variables["fallback"].as().mMap); + + VFS::Manager vfs; + + VFS::registerArchives(&vfs, fileCollections, archives, true); + + Settings::Manager::load(config); + + const DetourNavigator::AgentBounds agentBounds{ + Settings::game().mActorCollisionShapeType, + Settings::game().mDefaultActorPathfindHalfExtents, + }; + const std::uint64_t maxDbFileSize = Settings::Manager::getUInt64("max navmeshdb file size", "Navigator"); + const auto dbPath = Files::pathToUnicodeString(config.getUserDataPath() / "navmesh.db"); + + Log(Debug::Info) << "Using navmeshdb at " << dbPath; + + DetourNavigator::NavMeshDb db(dbPath, maxDbFileSize); + + ESM::ReadersCache readers; + EsmLoader::Query query; + query.mLoadActivators = true; + query.mLoadCells = true; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = true; + query.mLoadLands = true; + query.mLoadStatics = true; + const EsmLoader::EsmData esmData + = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); + + Resource::ImageManager imageManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); + Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); + DetourNavigator::RecastGlobalAllocator::init(); + DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); + navigatorSettings.mRecast.mSwimHeightScale + = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat(); + + WorldspaceData cellsData = gatherWorldspaceData( + navigatorSettings, readers, vfs, bulletShapeManager, esmData, processInteriorCells, writeBinaryLog); + + const Status status = generateAllNavMeshTiles(agentBounds, navigatorSettings, threadsNumber, + removeUnusedTiles, writeBinaryLog, cellsData, std::move(db)); + + switch (status) + { + case Status::Ok: + Log(Debug::Info) << "Done"; + break; + case Status::Cancelled: + Log(Debug::Warning) << "Cancelled"; + break; + case Status::NotEnoughSpace: + Log(Debug::Warning) + << "Navmesh generation is cancelled due to running out of disk space or limits " + << "for navmesh db. Check disk space at the db location \"" << dbPath + << "\". If there is enough space, adjust \"max navmeshdb file size\" setting (see " + << "https://openmw.readthedocs.io/en/latest/reference/modding/settings/" + "navigator.html?highlight=navmesh#max-navmeshdb-file-size)."; + break; + } + + return 0; + } + } +} + +int main(int argc, char* argv[]) +{ + return wrapApplication(NavMeshTool::runNavMeshTool, argc, argv, NavMeshTool::applicationName); +} diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp new file mode 100644 index 000000000..384c96546 --- /dev/null +++ b/apps/navmeshtool/navmesh.cpp @@ -0,0 +1,315 @@ +#include "navmesh.hpp" + +#include "worldspacedata.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + namespace + { + using DetourNavigator::AgentBounds; + using DetourNavigator::GenerateNavMeshTile; + using DetourNavigator::MeshSource; + using DetourNavigator::NavMeshDb; + using DetourNavigator::NavMeshTileInfo; + using DetourNavigator::PreparedNavMeshData; + using DetourNavigator::RecastMeshProvider; + using DetourNavigator::Settings; + using DetourNavigator::ShapeId; + using DetourNavigator::TileId; + using DetourNavigator::TilePosition; + using DetourNavigator::TilesPositionsRange; + using DetourNavigator::TileVersion; + using Sqlite3::Transaction; + + void logGeneratedTiles(std::size_t provided, std::size_t expected) + { + Log(Debug::Info) << provided << "/" << expected << " (" + << (static_cast(provided) / static_cast(expected) * 100) + << "%) navmesh tiles are generated"; + } + + template + void serializeToStderr(const T& value) + { + const std::vector data = serialize(value); + getLockedRawStderr()->write( + reinterpret_cast(data.data()), static_cast(data.size())); + } + + void logGeneratedTilesMessage(std::size_t number) + { + serializeToStderr(GeneratedTiles{ static_cast(number) }); + } + + struct LogGeneratedTiles + { + void operator()(std::size_t provided, std::size_t expected) const { logGeneratedTiles(provided, expected); } + }; + + class NavMeshTileConsumer final : public DetourNavigator::NavMeshTileConsumer + { + public: + std::atomic_size_t mExpected{ 0 }; + + explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles, bool writeBinaryLog) + : mDb(std::move(db)) + , mRemoveUnusedTiles(removeUnusedTiles) + , mWriteBinaryLog(writeBinaryLog) + , mTransaction(mDb.startTransaction(Sqlite3::TransactionMode::Immediate)) + , mNextTileId(mDb.getMaxTileId() + 1) + , mNextShapeId(mDb.getMaxShapeId() + 1) + { + } + + std::size_t getProvided() const { return mProvided.load(); } + + std::size_t getInserted() const { return mInserted.load(); } + + std::size_t getUpdated() const { return mUpdated.load(); } + + std::size_t getDeleted() const + { + const std::lock_guard lock(mMutex); + return mDeleted; + } + + std::int64_t resolveMeshSource(const MeshSource& source) override + { + const std::lock_guard lock(mMutex); + return DetourNavigator::resolveMeshSource(mDb, source, mNextShapeId); + } + + std::optional find(std::string_view worldspace, const TilePosition& tilePosition, + const std::vector& input) override + { + std::optional result; + std::lock_guard lock(mMutex); + if (const auto tile = mDb.findTile(worldspace, tilePosition, input)) + { + NavMeshTileInfo info; + info.mTileId = tile->mTileId; + info.mVersion = tile->mVersion; + result.emplace(info); + } + return result; + } + + void ignore(std::string_view worldspace, const TilePosition& tilePosition) override + { + if (mRemoveUnusedTiles) + { + std::lock_guard lock(mMutex); + mDeleted += static_cast(mDb.deleteTilesAt(worldspace, tilePosition)); + } + report(); + } + + void identity(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId) override + { + if (mRemoveUnusedTiles) + { + std::lock_guard lock(mMutex); + mDeleted += static_cast( + mDb.deleteTilesAtExcept(worldspace, tilePosition, TileId{ tileId })); + } + report(); + } + + void insert(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t version, + const std::vector& input, PreparedNavMeshData& data) override + { + { + std::lock_guard lock(mMutex); + if (mRemoveUnusedTiles) + mDeleted += static_cast(mDb.deleteTilesAt(worldspace, tilePosition)); + data.mUserId = static_cast(mNextTileId); + mDb.insertTile( + mNextTileId, worldspace, tilePosition, TileVersion{ version }, input, serialize(data)); + ++mNextTileId; + } + ++mInserted; + report(); + } + + void update(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId, + std::int64_t version, PreparedNavMeshData& data) override + { + data.mUserId = static_cast(tileId); + { + std::lock_guard lock(mMutex); + if (mRemoveUnusedTiles) + mDeleted += static_cast( + mDb.deleteTilesAtExcept(worldspace, tilePosition, TileId{ tileId })); + mDb.updateTile(TileId{ tileId }, TileVersion{ version }, serialize(data)); + } + ++mUpdated; + report(); + } + + void cancel(std::string_view reason) override + { + std::unique_lock lock(mMutex); + if (reason.find("database or disk is full") != std::string_view::npos) + mStatus = Status::NotEnoughSpace; + else + mStatus = Status::Cancelled; + mHasTile.notify_one(); + } + + Status wait() + { + constexpr std::chrono::seconds transactionInterval(1); + std::unique_lock lock(mMutex); + auto start = std::chrono::steady_clock::now(); + while (mProvided < mExpected && mStatus == Status::Ok) + { + mHasTile.wait(lock); + const auto now = std::chrono::steady_clock::now(); + if (now - start > transactionInterval) + { + mTransaction.commit(); + mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate); + start = now; + } + } + logGeneratedTiles(mProvided, mExpected); + if (mWriteBinaryLog) + logGeneratedTilesMessage(mProvided); + return mStatus; + } + + void commit() + { + const std::lock_guard lock(mMutex); + mTransaction.commit(); + } + + void vacuum() + { + const std::lock_guard lock(mMutex); + mDb.vacuum(); + } + + void removeTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range) + { + const std::lock_guard lock(mMutex); + mTransaction.commit(); + Log(Debug::Info) << "Removing tiles outside processed range for worldspace \"" << worldspace << "\"..."; + mDeleted += static_cast(mDb.deleteTilesOutsideRange(worldspace, range)); + mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate); + } + + private: + std::atomic_size_t mProvided{ 0 }; + std::atomic_size_t mInserted{ 0 }; + std::atomic_size_t mUpdated{ 0 }; + std::size_t mDeleted = 0; + Status mStatus = Status::Ok; + mutable std::mutex mMutex; + NavMeshDb mDb; + const bool mRemoveUnusedTiles; + const bool mWriteBinaryLog; + Transaction mTransaction; + TileId mNextTileId; + std::condition_variable mHasTile; + Misc::ProgressReporter mReporter; + ShapeId mNextShapeId; + std::mutex mReportMutex; + + void report() + { + const std::size_t provided = mProvided.fetch_add(1, std::memory_order_relaxed) + 1; + mReporter(provided, mExpected); + mHasTile.notify_one(); + if (mWriteBinaryLog) + logGeneratedTilesMessage(provided); + } + }; + } + + Status generateAllNavMeshTiles(const AgentBounds& agentBounds, const Settings& settings, std::size_t threadsNumber, + bool removeUnusedTiles, bool writeBinaryLog, WorldspaceData& data, NavMeshDb&& db) + { + Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers..."; + + SceneUtil::WorkQueue workQueue(threadsNumber); + auto navMeshTileConsumer + = std::make_shared(std::move(db), removeUnusedTiles, writeBinaryLog); + std::size_t tiles = 0; + std::mt19937_64 random; + + for (const std::unique_ptr& input : data.mNavMeshInputs) + { + const auto range = DetourNavigator::makeTilesPositionsRange(Misc::Convert::toOsgXY(input->mAabb.m_min), + Misc::Convert::toOsgXY(input->mAabb.m_max), settings.mRecast); + + if (removeUnusedTiles) + navMeshTileConsumer->removeTilesOutsideRange(input->mWorldspace, range); + + std::vector worldspaceTiles; + + DetourNavigator::getTilesPositions( + range, [&](const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); }); + + tiles += worldspaceTiles.size(); + + if (writeBinaryLog) + serializeToStderr(ExpectedTiles{ static_cast(tiles) }); + + navMeshTileConsumer->mExpected = tiles; + + std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random); + + for (const TilePosition& tilePosition : worldspaceTiles) + workQueue.addWorkItem(new GenerateNavMeshTile(input->mWorldspace, tilePosition, + RecastMeshProvider(input->mTileCachedRecastMeshManager), agentBounds, settings, + navMeshTileConsumer)); + } + + const Status status = navMeshTileConsumer->wait(); + if (status == Status::Ok) + navMeshTileConsumer->commit(); + + const auto inserted = navMeshTileConsumer->getInserted(); + const auto updated = navMeshTileConsumer->getUpdated(); + const auto deleted = navMeshTileConsumer->getDeleted(); + + Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, " << inserted + << " are inserted, " << updated << " updated and " << deleted << " deleted"; + + if (inserted + updated + deleted > 0) + { + Log(Debug::Info) << "Vacuuming the database..."; + navMeshTileConsumer->vacuum(); + } + + return status; + } +} diff --git a/apps/navmeshtool/navmesh.hpp b/apps/navmeshtool/navmesh.hpp new file mode 100644 index 000000000..4e607f4f2 --- /dev/null +++ b/apps/navmeshtool/navmesh.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_NAVMESHTOOL_NAVMESH_H +#define OPENMW_NAVMESHTOOL_NAVMESH_H + +#include + +namespace DetourNavigator +{ + class NavMeshDb; + struct Settings; + struct AgentBounds; +} + +namespace NavMeshTool +{ + struct WorldspaceData; + + enum class Status + { + Ok, + Cancelled, + NotEnoughSpace, + }; + + Status generateAllNavMeshTiles(const DetourNavigator::AgentBounds& agentBounds, + const DetourNavigator::Settings& settings, std::size_t threadsNumber, bool removeUnusedTiles, + bool writeBinaryLog, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db); +} + +#endif diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp new file mode 100644 index 000000000..9712b80b6 --- /dev/null +++ b/apps/navmeshtool/worldspacedata.cpp @@ -0,0 +1,371 @@ +#include "worldspacedata.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + namespace + { + using DetourNavigator::CollisionShape; + using DetourNavigator::HeightfieldPlane; + using DetourNavigator::HeightfieldShape; + using DetourNavigator::HeightfieldSurface; + using DetourNavigator::ObjectId; + using DetourNavigator::ObjectTransform; + + struct CellRef + { + ESM::RecNameInts mType; + ESM::RefNum mRefNum; + ESM::RefId mRefId; + float mScale; + ESM::Position mPos; + + CellRef( + ESM::RecNameInts type, ESM::RefNum refNum, ESM::RefId&& refId, float scale, const ESM::Position& pos) + : mType(type) + , mRefNum(refNum) + , mRefId(std::move(refId)) + , mScale(scale) + , mPos(pos) + { + } + }; + + ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, const ESM::RefId& refId) + { + const auto it = std::lower_bound( + esmData.mRefIdTypes.begin(), esmData.mRefIdTypes.end(), refId, EsmLoader::LessById{}); + if (it == esmData.mRefIdTypes.end() || it->mId != refId) + return {}; + return it->mType; + } + + std::vector loadCellRefs( + const ESM::Cell& cell, const EsmLoader::EsmData& esmData, ESM::ReadersCache& readers) + { + std::vector> cellRefs; + + for (std::size_t i = 0; i < cell.mContextList.size(); i++) + { + ESM::ReadersCache::BusyItem reader = readers.get(static_cast(cell.mContextList[i].index)); + cell.restore(*reader, static_cast(i)); + ESM::CellRef cellRef; + bool deleted = false; + while (ESM::Cell::getNextRef(*reader, cellRef, deleted)) + { + const ESM::RecNameInts type = getType(esmData, cellRef.mRefID); + if (type == ESM::RecNameInts{}) + continue; + cellRefs.emplace_back( + deleted, type, cellRef.mRefNum, std::move(cellRef.mRefID), cellRef.mScale, cellRef.mPos); + } + } + + Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs"; + + const auto getKey + = [](const EsmLoader::Record& v) -> const ESM::RefNum& { return v.mValue.mRefNum; }; + std::vector result = prepareRecords(cellRefs, getKey); + + Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs"; + + return result; + } + + template + void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, + Resource::BulletShapeManager& bulletShapeManager, ESM::ReadersCache& readers, F&& f) + { + std::vector cellRefs = loadCellRefs(cell, esmData, readers); + + Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs"; + + for (CellRef& cellRef : cellRefs) + { + std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType)); + if (model.empty()) + continue; + + if (cellRef.mType != ESM::REC_STAT) + model = Misc::ResourceHelpers::correctActorModelPath(model, &vfs); + + osg::ref_ptr shape = [&] { + try + { + return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model, &vfs)); + } + catch (const std::exception& e) + { + Log(Debug::Warning) << "Failed to load cell ref \"" << cellRef.mRefId << "\" model \"" << model + << "\": " << e.what(); + return osg::ref_ptr(); + } + }(); + + if (shape == nullptr || shape->mCollisionShape == nullptr) + continue; + + osg::ref_ptr shapeInstance( + new Resource::BulletShapeInstance(std::move(shape))); + + switch (cellRef.mType) + { + case ESM::REC_ACTI: + case ESM::REC_CONT: + case ESM::REC_DOOR: + case ESM::REC_STAT: + f(BulletObject(std::move(shapeInstance), cellRef.mPos, cellRef.mScale)); + break; + default: + break; + } + } + } + + struct GetXY + { + osg::Vec2i operator()(const ESM::Land& value) const { return osg::Vec2i(value.mX, value.mY); } + }; + + struct LessByXY + { + bool operator()(const ESM::Land& lhs, const ESM::Land& rhs) const { return GetXY{}(lhs) < GetXY{}(rhs); } + + bool operator()(const ESM::Land& lhs, const osg::Vec2i& rhs) const { return GetXY{}(lhs) < rhs; } + + bool operator()(const osg::Vec2i& lhs, const ESM::Land& rhs) const { return lhs < GetXY{}(rhs); } + }; + + btAABB getAabb(const osg::Vec2i& cellPosition, btScalar minHeight, btScalar maxHeight) + { + btAABB aabb; + aabb.m_min = btVector3(static_cast(cellPosition.x() * ESM::Land::REAL_SIZE), + static_cast(cellPosition.y() * ESM::Land::REAL_SIZE), minHeight); + aabb.m_max = btVector3(static_cast((cellPosition.x() + 1) * ESM::Land::REAL_SIZE), + static_cast((cellPosition.y() + 1) * ESM::Land::REAL_SIZE), maxHeight); + return aabb; + } + + void mergeOrAssign(const btAABB& aabb, btAABB& target, bool& initialized) + { + if (initialized) + return target.merge(aabb); + + target.m_min = aabb.m_min; + target.m_max = aabb.m_max; + initialized = true; + } + + std::tuple makeHeightfieldShape(const std::optional& land, + const osg::Vec2i& cellPosition, std::vector>& heightfields, + std::vector>& landDatas) + { + if (!land.has_value() || osg::Vec2i(land->mX, land->mY) != cellPosition + || (land->mDataTypes & ESM::Land::DATA_VHGT) == 0) + return { HeightfieldPlane{ ESM::Land::DEFAULT_HEIGHT }, ESM::Land::DEFAULT_HEIGHT, + ESM::Land::DEFAULT_HEIGHT }; + + ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique()); + land->loadData(ESM::Land::DATA_VHGT, &landData); + heightfields.push_back(std::vector(std::begin(landData.mHeights), std::end(landData.mHeights))); + HeightfieldSurface surface; + surface.mHeights = heightfields.back().data(); + surface.mMinHeight = landData.mMinHeight; + surface.mMaxHeight = landData.mMaxHeight; + surface.mSize = static_cast(ESM::Land::LAND_SIZE); + return { surface, landData.mMinHeight, landData.mMaxHeight }; + } + + template + void serializeToStderr(const T& value) + { + const std::vector data = serialize(value); + getRawStderr().write(reinterpret_cast(data.data()), static_cast(data.size())); + } + + std::string makeAddObjectErrorMessage( + ObjectId objectId, DetourNavigator::AreaType areaType, const CollisionShape& shape) + { + std::ostringstream stream; + stream << "Failed to add object to recast mesh objectId=" << objectId.value() << " areaType=" << areaType + << " fileName=" << shape.getInstance()->mFileName + << " fileHash=" << Misc::StringUtils::toHex(shape.getInstance()->mFileHash); + return stream.str(); + } + } + + WorldspaceNavMeshInput::WorldspaceNavMeshInput( + std::string worldspace, const DetourNavigator::RecastSettings& settings) + : mWorldspace(std::move(worldspace)) + , mTileCachedRecastMeshManager(settings) + { + mAabb.m_min = btVector3(0, 0, 0); + mAabb.m_max = btVector3(0, 0, 0); + } + + WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers, + const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, + bool processInteriorCells, bool writeBinaryLog) + { + Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells..."; + + std::map> navMeshInputs; + WorldspaceData data; + + std::size_t objectsCounter = 0; + + if (writeBinaryLog) + serializeToStderr(ExpectedCells{ static_cast(esmData.mCells.size()) }); + + for (std::size_t i = 0; i < esmData.mCells.size(); ++i) + { + const ESM::Cell& cell = esmData.mCells[i]; + const bool exterior = cell.isExterior(); + + if (!exterior && !processInteriorCells) + { + if (writeBinaryLog) + serializeToStderr(ProcessedCells{ static_cast(i + 1) }); + Log(Debug::Info) << "Skipped interior" + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" + << cell.getDescription() << "\""; + continue; + } + + Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/" + << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; + + const osg::Vec2i cellPosition(cell.mData.mX, cell.mData.mY); + const std::size_t cellObjectsBegin = data.mObjects.size(); + const auto cellWorldspace = Misc::StringUtils::lowerCase( + (cell.isExterior() ? ESM::Cell::sDefaultWorldspaceId : cell.mId).serializeText()); + WorldspaceNavMeshInput& navMeshInput = [&]() -> WorldspaceNavMeshInput& { + auto it = navMeshInputs.find(cellWorldspace); + if (it == navMeshInputs.end()) + { + it = navMeshInputs + .emplace(cellWorldspace, + std::make_unique(cellWorldspace, settings.mRecast)) + .first; + it->second->mTileCachedRecastMeshManager.setWorldspace(cellWorldspace, nullptr); + } + return *it->second; + }(); + + const auto guard = navMeshInput.mTileCachedRecastMeshManager.makeUpdateGuard(); + + if (exterior) + { + const auto it + = std::lower_bound(esmData.mLands.begin(), esmData.mLands.end(), cellPosition, LessByXY{}); + const auto [heightfieldShape, minHeight, maxHeight] + = makeHeightfieldShape(it == esmData.mLands.end() ? std::optional() : *it, cellPosition, + data.mHeightfields, data.mLandData); + + mergeOrAssign( + getAabb(cellPosition, minHeight, maxHeight), navMeshInput.mAabb, navMeshInput.mAabbInitialized); + + navMeshInput.mTileCachedRecastMeshManager.addHeightfield( + cellPosition, ESM::Land::REAL_SIZE, heightfieldShape, guard.get()); + + navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1, guard.get()); + } + else + { + if ((cell.mData.mFlags & ESM::Cell::HasWater) != 0) + navMeshInput.mTileCachedRecastMeshManager.addWater( + cellPosition, std::numeric_limits::max(), cell.mWater, guard.get()); + } + + forEachObject(cell, esmData, vfs, bulletShapeManager, readers, [&](BulletObject object) { + if (object.getShapeInstance()->mVisualCollisionType != Resource::VisualCollisionType::None) + return; + + const btTransform& transform = object.getCollisionObject().getWorldTransform(); + const btAABB aabb = BulletHelpers::getAabb(*object.getCollisionObject().getCollisionShape(), transform); + mergeOrAssign(aabb, navMeshInput.mAabb, navMeshInput.mAabbInitialized); + if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) + navMeshInput.mAabb.merge(BulletHelpers::getAabb(*avoid, transform)); + + const ObjectId objectId(++objectsCounter); + const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), + object.getObjectTransform()); + + if (!navMeshInput.mTileCachedRecastMeshManager.addObject( + objectId, shape, transform, DetourNavigator::AreaType_ground, guard.get())) + throw std::logic_error( + makeAddObjectErrorMessage(objectId, DetourNavigator::AreaType_ground, shape)); + + if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) + { + const ObjectId avoidObjectId(++objectsCounter); + const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform()); + if (!navMeshInput.mTileCachedRecastMeshManager.addObject( + avoidObjectId, avoidShape, transform, DetourNavigator::AreaType_null, guard.get())) + throw std::logic_error( + makeAddObjectErrorMessage(avoidObjectId, DetourNavigator::AreaType_null, avoidShape)); + } + + data.mObjects.emplace_back(std::move(object)); + }); + + const auto cellDescription = cell.getDescription(); + + if (writeBinaryLog) + serializeToStderr(ProcessedCells{ static_cast(i + 1) }); + + Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/" + << esmData.mCells.size() << ") " << cellDescription << " with " + << (data.mObjects.size() - cellObjectsBegin) << " objects"; + } + + data.mNavMeshInputs.reserve(navMeshInputs.size()); + std::transform(navMeshInputs.begin(), navMeshInputs.end(), std::back_inserter(data.mNavMeshInputs), + [](auto& v) { return std::move(v.second); }); + + Log(Debug::Info) << "Processed " << esmData.mCells.size() << " cells, added " << data.mObjects.size() + << " objects and " << data.mHeightfields.size() << " height fields"; + + return data; + } +} diff --git a/apps/navmeshtool/worldspacedata.hpp b/apps/navmeshtool/worldspacedata.hpp new file mode 100644 index 000000000..b6ccce673 --- /dev/null +++ b/apps/navmeshtool/worldspacedata.hpp @@ -0,0 +1,96 @@ +#ifndef OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H +#define OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace ESM +{ + class ESMReader; + class ReadersCache; +} + +namespace VFS +{ + class Manager; +} + +namespace Resource +{ + class BulletShapeManager; +} + +namespace EsmLoader +{ + struct EsmData; +} + +namespace DetourNavigator +{ + struct Settings; +} + +namespace NavMeshTool +{ + using DetourNavigator::ObjectTransform; + using DetourNavigator::TileCachedRecastMeshManager; + + struct WorldspaceNavMeshInput + { + std::string mWorldspace; + TileCachedRecastMeshManager mTileCachedRecastMeshManager; + btAABB mAabb; + bool mAabbInitialized = false; + + explicit WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings); + }; + + class BulletObject + { + public: + BulletObject(osg::ref_ptr&& shapeInstance, const ESM::Position& position, + float localScaling) + : mShapeInstance(std::move(shapeInstance)) + , mObjectTransform{ position, localScaling } + , mCollisionObject(BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(), + Misc::Convert::toBullet(position.asVec3()), + Misc::Convert::toBullet(Misc::Convert::makeOsgQuat(position)))) + { + mShapeInstance->setLocalScaling(btVector3(localScaling, localScaling, localScaling)); + } + + const osg::ref_ptr& getShapeInstance() const noexcept { return mShapeInstance; } + const DetourNavigator::ObjectTransform& getObjectTransform() const noexcept { return mObjectTransform; } + btCollisionObject& getCollisionObject() const noexcept { return *mCollisionObject; } + + private: + osg::ref_ptr mShapeInstance; + DetourNavigator::ObjectTransform mObjectTransform; + std::unique_ptr mCollisionObject; + }; + + struct WorldspaceData + { + std::vector> mNavMeshInputs; + std::vector mObjects; + std::vector> mLandData; + std::vector> mHeightfields; + }; + + WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers, + const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, + bool processInteriorCells, bool writeBinaryLog); +} + +#endif diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt index 3cbee2b7e..5ca21c10c 100644 --- a/apps/niftest/CMakeLists.txt +++ b/apps/niftest/CMakeLists.txt @@ -9,11 +9,14 @@ openmw_add_executable(niftest ) target_link_libraries(niftest - ${Boost_FILESYSTEM_LIBRARY} - components + components ) if (BUILD_WITH_CODE_COVERAGE) - add_definitions (--coverage) - target_link_libraries(niftest gcov) + target_compile_options(niftest PRIVATE --coverage) + target_link_libraries(niftest gcov) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(niftest PRIVATE ) endif() diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index e403562d3..d08ccce4b 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -1,75 +1,106 @@ -///Program to test .nif files both on the FileSystem and in BSA archives. +/// Program to test .nif files both on the FileSystem and in BSA archives. +#include +#include #include -#include -#include +#include +#include +#include +#include -#include +#include #include -#include +#include +#include +#include +#include #include #include +#include #include -#include // Create local aliases for brevity namespace bpo = boost::program_options; -namespace bfs = boost::filesystem; -///See if the file has the named extension -bool hasExtension(std::string filename, std::string extensionToFind) +/// See if the file has the named extension +bool hasExtension(const std::filesystem::path& filename, const std::string& extensionToFind) { - std::string extension = filename.substr(filename.find_last_of('.')+1); - - //Convert strings to lower case for comparison - std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); - std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower); - - if(extension == extensionToFind) - return true; - else - return false; + const auto extension = Files::pathToUnicodeString(filename.extension()); + return Misc::StringUtils::ciEqual(extension, extensionToFind); } -///See if the file has the "nif" extension. -bool isNIF(const std::string & filename) +/// See if the file has the "nif" extension. +bool isNIF(const std::filesystem::path& filename) { - return hasExtension(filename,"nif"); + return hasExtension(filename, ".nif"); } -///See if the file has the "bsa" extension. -bool isBSA(const std::string & filename) +/// See if the file has the "bsa" extension. +bool isBSA(const std::filesystem::path& filename) { - return hasExtension(filename,"bsa"); + return hasExtension(filename, ".bsa"); +} + +std::unique_ptr makeBsaArchive(const std::filesystem::path& path) +{ + switch (Bsa::BSAFile::detectVersion(path)) + { + case Bsa::BSAVER_UNKNOWN: + std::cerr << '"' << path << "\" is unknown BSA archive" << std::endl; + return nullptr; + case Bsa::BSAVER_COMPRESSED: + return std::make_unique::type>(path); + case Bsa::BSAVER_BA2_GNRL: + return std::make_unique::type>(path); + case Bsa::BSAVER_BA2_DX10: + return std::make_unique::type>(path); + case Bsa::BSAVER_UNCOMPRESSED: + return std::make_unique::type>(path); + } + + std::cerr << '"' << path << "\" is unsupported BSA archive" << std::endl; + + return nullptr; +} + +std::unique_ptr makeArchive(const std::filesystem::path& path) +{ + if (isBSA(path)) + return makeBsaArchive(path); + if (std::filesystem::is_directory(path)) + return std::make_unique(path); + return nullptr; } /// Check all the nif files in a given VFS::Archive -/// \note Takes ownership! /// \note Can not read a bsa file inside of a bsa file. -void readVFS(VFS::Archive* anArchive,std::string archivePath = "") +void readVFS(std::unique_ptr&& anArchive, const std::filesystem::path& archivePath = {}) { - VFS::Manager myManager(true); - myManager.addArchive(anArchive); + if (anArchive == nullptr) + return; + + VFS::Manager myManager; + myManager.addArchive(std::move(anArchive)); myManager.buildIndex(); - std::map files=myManager.getIndex(); - for(std::map::const_iterator it=files.begin(); it!=files.end(); ++it) + for (const auto& name : myManager.getRecursiveDirectoryIterator("")) { - std::string name = it->first; - - try{ - if(isNIF(name)) + try + { + if (isNIF(name)) { - // std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile temp_nif(myManager.get(name),archivePath+name); + // std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile file(archivePath / name); + Nif::Reader reader(file); + reader.parse(myManager.get(name)); } - else if(isBSA(name)) + else if (isBSA(name)) { - if(!archivePath.empty() && !isBSA(archivePath)) + if (!archivePath.empty() && !isBSA(archivePath)) { -// std::cout << "Reading BSA File: " << name << std::endl; - readVFS(new VFS::BsaArchive(archivePath+name),archivePath+name+"/"); -// std::cout << "Done with BSA File: " << name << std::endl; + // std::cout << "Reading BSA File: " << name << std::endl; + readVFS(makeBsaArchive(archivePath / name), archivePath / name); + // std::cout << "Done with BSA File: " << name << std::endl; } } } @@ -80,44 +111,49 @@ void readVFS(VFS::Archive* anArchive,std::string archivePath = "") } } -bool parseOptions (int argc, char** argv, std::vector& files) +bool parseOptions(int argc, char** argv, std::vector& files, bool& writeDebugLog, + std::vector& archives) { - bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n" - "Usages:\n" - " niftool \n" - " Scan the file or directories for nif errors.\n\n" - "Allowed options"); - desc.add_options() - ("help,h", "print help message.") - ("input-file", bpo::value< std::vector >(), "input file") - ; + bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF and BSA files - //Default option if none provided +Usages: + niftool + Scan the file or directories for nif errors. + +Allowed options)"); + auto addOption = desc.add_options(); + addOption("help,h", "print help message."); + addOption("write-debug-log,v", "write debug log for unsupported nif files"); + addOption("archives", bpo::value(), "path to archive files to provide files"); + addOption("input-file", bpo::value(), "input file"); + + // Default option if none provided bpo::positional_options_description p; p.add("input-file", -1); bpo::variables_map variables; try { - bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv). - options(desc).positional(p).run(); + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).options(desc).positional(p).run(); bpo::store(valid_opts, variables); bpo::notify(variables); - if (variables.count ("help")) + if (variables.count("help")) { std::cout << desc << std::endl; return false; } + writeDebugLog = variables.count("write-debug-log") > 0; if (variables.count("input-file")) { - files = variables["input-file"].as< std::vector >(); + files = variables["input-file"].as(); + if (const auto it = variables.find("archives"); it != variables.end()) + archives = it->second.as(); return true; } } - catch(std::exception &e) + catch (std::exception& e) { - std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" - << desc << std::endl; + std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl; return false; } @@ -126,44 +162,58 @@ bool parseOptions (int argc, char** argv, std::vector& files) return false; } -int main(int argc, char **argv) +int main(int argc, char** argv) { - std::vector files; - if(!parseOptions (argc, argv, files)) + std::vector files; + bool writeDebugLog = false; + std::vector archives; + if (!parseOptions(argc, argv, files, writeDebugLog, archives)) return 1; - Nif::NIFFile::setLoadUnsupportedFiles(true); -// std::cout << "Reading Files" << std::endl; - for(std::vector::const_iterator it=files.begin(); it!=files.end(); ++it) - { - std::string name = *it; + Nif::Reader::setLoadUnsupportedFiles(true); + Nif::Reader::setWriteNifDebugLog(writeDebugLog); + std::unique_ptr vfs; + if (!archives.empty()) + { + vfs = std::make_unique(); + for (const std::filesystem::path& path : archives) + if (auto archive = makeArchive(path)) + vfs->addArchive(std::move(archive)); + else + std::cerr << '"' << path << "\" is unsupported archive" << std::endl; + vfs->buildIndex(); + } + + // std::cout << "Reading Files" << std::endl; + for (const auto& path : files) + { try { - if(isNIF(name)) + if (isNIF(path)) { - //std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name.c_str()),name); - } - else if(isBSA(name)) - { -// std::cout << "Reading BSA File: " << name << std::endl; - readVFS(new VFS::BsaArchive(name)); - } - else if(bfs::is_directory(bfs::path(name))) - { -// std::cout << "Reading All Files in: " << name << std::endl; - readVFS(new VFS::FileSystemArchive(name),name); - } - else - { - std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl; - } + // std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile file(path); + Nif::Reader reader(file); + if (vfs != nullptr) + reader.parse(vfs->get(Files::pathToUnicodeString(path))); + else + reader.parse(Files::openConstrainedFileStream(path)); + } + else if (auto archive = makeArchive(path)) + { + readVFS(std::move(archive), path); + } + else + { + std::cerr << "ERROR: \"" << Files::pathToUnicodeString(path) + << "\" is not a nif file, bsa file, or directory!" << std::endl; + } } catch (std::exception& e) { std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; } - } - return 0; + } + return 0; } diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 88c4233c9..83f8cea16 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -1,4 +1,4 @@ -set (OPENCS_SRC main.cpp +set (OPENCS_SRC ${CMAKE_SOURCE_DIR}/files/windows/opencs.rc ) @@ -8,29 +8,29 @@ opencs_units (model/doc document operation saving documentmanager loader runner operationholder ) -opencs_units_noqt (model/doc - stage savingstate savingstages blacklist messages +opencs_units (model/doc + savingstate savingstages blacklist messages ) -opencs_hdrs_noqt (model/doc +opencs_hdrs (model/doc state ) opencs_units (model/world idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel - actoradapter + actoradapter idcollection ) -opencs_units_noqt (model/world +opencs_units (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection - refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope + refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro ) -opencs_hdrs_noqt (model/world +opencs_hdrs (model/world columnimp idcollection collection info subcellcollection ) @@ -39,14 +39,14 @@ opencs_units (model/tools tools reportmodel mergeoperation ) -opencs_units_noqt (model/tools +opencs_units (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck ) -opencs_hdrs_noqt (model/tools +opencs_hdrs (model/tools mergestate ) @@ -57,11 +57,11 @@ opencs_units (view/doc ) -opencs_units_noqt (view/doc +opencs_units (view/doc subviewfactory ) -opencs_hdrs_noqt (view/doc +opencs_hdrs (view/doc subviewfactoryimp ) @@ -71,10 +71,10 @@ opencs_units (view/world cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator - bodypartcreator landtexturecreator landcreator + bodypartcreator landtexturecreator landcreator tableheadermouseeventhandler ) -opencs_units_noqt (view/world +opencs_units (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate scripthighlighter idvalidator dialoguecreator idcompletiondelegate colordelegate dragdroputils @@ -92,12 +92,12 @@ opencs_units (view/render cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands ) -opencs_units_noqt (view/render +opencs_units (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase cellarrow cellmarker cellborder pathgrid ) -opencs_hdrs_noqt (view/render +opencs_hdrs (view/render mask ) @@ -106,7 +106,7 @@ opencs_units (view/tools reportsubview reporttable searchsubview searchbox merge ) -opencs_units_noqt (view/tools +opencs_units (view/tools subviews ) @@ -119,12 +119,12 @@ opencs_units (model/prefs shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting ) -opencs_units_noqt (model/prefs +opencs_units (model/prefs category ) -opencs_units_noqt (model/filter - node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode +opencs_units (model/filter + unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode ) opencs_units (view/filter @@ -143,15 +143,18 @@ set (OPENCS_UI ${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui ) -source_group (openmw-cs FILES ${OPENCS_SRC} ${OPENCS_HDR}) +source_group (openmw-cs FILES main.cpp ${OPENCS_SRC} ${OPENCS_HDR}) if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) -qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) -qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) +if (QT_VERSION_MAJOR VERSION_EQUAL 5) + qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) +else () + qt6_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) +endif() +qt_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) # for compiled .ui files include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -168,24 +171,38 @@ else() set (OPENCS_OPENMW_CFG "") endif(APPLE) -openmw_add_executable(openmw-cs - MACOSX_BUNDLE +add_library(openmw-cs-lib ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} ${OPENCS_RES_SRC} - ${OPENCS_MAC_ICON} - ${OPENCS_CFG} - ${OPENCS_DEFAULT_FILTERS_FILE} - ${OPENCS_OPENMW_CFG} ) -if(APPLE) +set_target_properties(openmw-cs-lib PROPERTIES OUTPUT_NAME openmw-cs) + +if(BUILD_OPENCS) + openmw_add_executable(openmw-cs + MACOSX_BUNDLE + ${OPENCS_MAC_ICON} + ${OPENCS_CFG} + ${OPENCS_DEFAULT_FILTERS_FILE} + ${OPENCS_OPENMW_CFG} + main.cpp + ) + + target_link_libraries(openmw-cs openmw-cs-lib) + + if (BUILD_WITH_CODE_COVERAGE) + target_compile_options(openmw-cs-lib PRIVATE --coverage) + target_link_libraries(openmw-cs-lib gcov) + endif() +endif() + +if(APPLE AND BUILD_OPENCS) set(OPENCS_BUNDLE_NAME "OpenMW-CS") set(OPENCS_BUNDLE_RESOURCES_DIR "${OpenMW_BINARY_DIR}/${OPENCS_BUNDLE_NAME}.app/Contents/Resources") - set(OPENMW_MYGUI_FILES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) - set(OPENMW_SHADERS_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) + set(OPENMW_RESOURCES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) @@ -212,9 +229,9 @@ if(APPLE) add_custom_command(TARGET openmw-cs POST_BUILD COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${OPENCS_BUNDLE_RESOURCES_DIR}/resources") -endif(APPLE) +endif() -target_link_libraries(openmw-cs +target_link_libraries(openmw-cs-lib # CMake's built-in OSG finder does not use pkgconfig, so we have to # manually ensure the order is correct for inter-library dependencies. # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`. @@ -226,41 +243,21 @@ target_link_libraries(openmw-cs ${OSGTEXT_LIBRARIES} ${OSG_LIBRARIES} ${EXTERN_OSGQT_LIBRARY} - ${Boost_SYSTEM_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} - components + components_qt ) -if(OSG_STATIC) - unset(_osg_plugins_static_files) - add_library(openmw_cs_osg_plugins INTERFACE) - foreach(_plugin ${USED_OSG_PLUGINS}) - string(TOUPPER ${_plugin} _plugin_uc) - if(OPENMW_USE_SYSTEM_OSG) - list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) - else() - list(APPEND _osg_plugins_static_files $) - target_link_libraries(openmw_cs_osg_plugins INTERFACE $) - add_dependencies(openmw_cs_osg_plugins ${${_plugin_uc}_LIBRARY}) - endif() - endforeach() - # We use --whole-archive because OSG plugins use registration. - get_whole_archive_options(_opts ${_osg_plugins_static_files}) - target_link_options(openmw_cs_osg_plugins INTERFACE ${_opts}) - target_link_libraries(openmw-cs openmw_cs_osg_plugins) - - if(OPENMW_USE_SYSTEM_OSG) - # OSG plugin pkgconfig files are missing these dependencies. - # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 - target_link_libraries(openmw freetype jpeg png) - endif() -endif(OSG_STATIC) - -target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) +if (QT_VERSION_MAJOR VERSION_EQUAL 6) + target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets) +else() + target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL) +endif() if (WIN32) - target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) + target_link_libraries(openmw-cs-lib ${Boost_LOCALE_LIBRARY}) +endif() + +if (WIN32 AND BUILD_OPENCS) INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".") get_generator_is_multi_config(multi_config) @@ -280,7 +277,29 @@ if (MSVC) endif (CMAKE_CL_64) endif (MSVC) - -if(APPLE) +if(APPLE AND BUILD_OPENCS) INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle) endif() + +if(USE_QT) + set_property(TARGET openmw-cs-lib PROPERTY AUTOMOC ON) +endif(USE_QT) + +if (BUILD_WITH_CODE_COVERAGE) + target_compile_options(openmw-cs-lib PRIVATE --coverage) + target_link_libraries(openmw-cs-lib gcov) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw-cs-lib PRIVATE + + + + + + + + + + ) +endif() diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 3f53a523f..416d05973 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -1,30 +1,57 @@ #include "editor.hpp" #include +#include #include #include #include +#include -#include -#include -#include -#include +#include -#include "model/doc/document.hpp" -#include "model/world/data.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include #ifdef _WIN32 -#include +#include #endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "view/doc/viewmanager.hpp" + using namespace Fallback; -CS::Editor::Editor (int argc, char **argv) -: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), - mPid(""), mLock(), mMerge (mDocumentManager), - mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr) +CS::Editor::Editor(int argc, char** argv) + : mConfigVariables(readConfiguration()) + , mSettingsState(mCfgMgr) + , mDocumentManager(mCfgMgr) + , mPid(std::filesystem::temp_directory_path() / "openmw-cs.pid") + , mLockFile(QFileInfo(Files::pathToQString(mPid)).absoluteFilePath() + ".lock") + , mMerge(mDocumentManager) + , mIpcServerName("org.openmw.OpenCS") + , mServer(nullptr) + , mClientSocket(nullptr) { - std::pair > config = readConfig(); + std::pair> config = readConfig(); mViewManager = new CSVDoc::ViewManager(mDocumentManager); if (argc > 1) @@ -35,127 +62,148 @@ CS::Editor::Editor (int argc, char **argv) NifOsg::Loader::setShowMarkers(true); - mDocumentManager.setFileData(mFsStrict, config.first, config.second); + mDocumentManager.setFileData(config.first, config.second); - mNewGame.setLocalData (mLocal); - mFileDialog.setLocalData (mLocal); - mMerge.setLocalData (mLocal); + mNewGame.setLocalData(mLocal); + mFileDialog.setLocalData(mLocal); + mMerge.setLocalData(mLocal); - connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)), - this, SLOT (documentAdded (CSMDoc::Document *))); - connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)), - this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *))); - connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()), - this, SLOT (lastDocumentDeleted())); + connect(&mDocumentManager, &CSMDoc::DocumentManager::documentAdded, this, &Editor::documentAdded); + connect( + &mDocumentManager, &CSMDoc::DocumentManager::documentAboutToBeRemoved, this, &Editor::documentAboutToBeRemoved); + connect(&mDocumentManager, &CSMDoc::DocumentManager::lastDocumentDeleted, this, &Editor::lastDocumentDeleted); - connect (mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); - connect (mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); - connect (mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); - connect (mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ())); - connect (mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *))); + connect(mViewManager, &CSVDoc::ViewManager::newGameRequest, this, &Editor::createGame); + connect(mViewManager, &CSVDoc::ViewManager::newAddonRequest, this, &Editor::createAddon); + connect(mViewManager, &CSVDoc::ViewManager::loadDocumentRequest, this, &Editor::loadDocument); + connect(mViewManager, &CSVDoc::ViewManager::editSettingsRequest, this, &Editor::showSettings); + connect(mViewManager, &CSVDoc::ViewManager::mergeDocument, this, &Editor::mergeDocument); - connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ())); - connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ())); - connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ())); - connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ())); + connect(&mStartup, &CSVDoc::StartupDialogue::createGame, this, &Editor::createGame); + connect(&mStartup, &CSVDoc::StartupDialogue::createAddon, this, &Editor::createAddon); + connect(&mStartup, &CSVDoc::StartupDialogue::loadDocument, this, &Editor::loadDocument); + connect(&mStartup, &CSVDoc::StartupDialogue::editConfig, this, &Editor::showSettings); - connect (&mFileDialog, SIGNAL(signalOpenFiles (const boost::filesystem::path&)), - this, SLOT(openFiles (const boost::filesystem::path&))); + connect(&mFileDialog, &CSVDoc::FileDialog::signalOpenFiles, this, + [this](const std::filesystem::path& savePath) { this->openFiles(savePath); }); + connect(&mFileDialog, &CSVDoc::FileDialog::signalCreateNewFile, this, &Editor::createNewFile); + connect(&mFileDialog, &CSVDoc::FileDialog::rejected, this, &Editor::cancelFileDialog); - connect (&mFileDialog, SIGNAL(signalCreateNewFile (const boost::filesystem::path&)), - this, SLOT(createNewFile (const boost::filesystem::path&))); - connect (&mFileDialog, SIGNAL (rejected()), this, SLOT (cancelFileDialog ())); - - connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), - this, SLOT (createNewGame (const boost::filesystem::path&))); - connect (&mNewGame, SIGNAL (cancelCreateGame()), this, SLOT (cancelCreateGame ())); + connect(&mNewGame, &CSVDoc::NewGameDialogue::createRequest, this, &Editor::createNewGame); + connect(&mNewGame, &CSVDoc::NewGameDialogue::cancelCreateGame, this, &Editor::cancelCreateGame); } -CS::Editor::~Editor () +CS::Editor::~Editor() { delete mViewManager; + mLockFile.unlock(); mPidFile.close(); - if(mServer && boost::filesystem::exists(mPid)) - static_cast ( // silence coverity warning - remove(mPid.string().c_str())); // ignore any error + if (mServer && std::filesystem::exists(mPid)) + std::filesystem::remove(mPid); } -std::pair > CS::Editor::readConfig(bool quiet) +boost::program_options::variables_map CS::Editor::readConfiguration() { boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); - desc.add_options() - ("data", boost::program_options::value()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing()) - ("data-local", boost::program_options::value()->default_value(Files::EscapePath(), "")) - ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) - ("encoding", boost::program_options::value()->default_value("win1252")) - ("resources", boost::program_options::value()->default_value(Files::EscapePath(), "resources")) - ("fallback-archive", boost::program_options::value()-> - default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken()) - ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") - ->multitoken()->composing(), "fallback values") - ("script-blacklist", boost::program_options::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") - ("script-blacklist-use", boost::program_options::value()->implicit_value(true) - ->default_value(true), "enable script blacklisting"); + auto addOption = desc.add_options(); + addOption("data", + boost::program_options::value() + ->default_value(Files::MaybeQuotedPathContainer(), "data") + ->multitoken() + ->composing()); + addOption("data-local", + boost::program_options::value()->default_value( + Files::MaybeQuotedPathContainer::value_type(), "")); + addOption("encoding", boost::program_options::value()->default_value("win1252")); + addOption("resources", + boost::program_options::value()->default_value(Files::MaybeQuotedPath(), "resources")); + addOption("fallback-archive", + boost::program_options::value>() + ->default_value(std::vector(), "fallback-archive") + ->multitoken()); + addOption("fallback", + boost::program_options::value()->default_value(FallbackMap(), "")->multitoken()->composing(), + "fallback values"); + addOption("script-blacklist", + boost::program_options::value>() + ->default_value(std::vector(), "") + ->multitoken(), + "exclude specified script from the verifier (if the use of the blacklist is enabled)"); + addOption("script-blacklist-use", boost::program_options::value()->implicit_value(true)->default_value(true), + "enable script blacklisting"); + Files::ConfigurationManager::addCommonOptions(desc); boost::program_options::notify(variables); mCfgMgr.readConfiguration(variables, desc, false); + Settings::Manager::load(mCfgMgr, true); + setupLogging(mCfgMgr.getLogPath(), "OpenMW-CS"); + + return variables; +} + +std::pair> CS::Editor::readConfig(bool quiet) +{ + boost::program_options::variables_map& variables = mConfigVariables; Fallback::Map::init(variables["fallback"].as().mMap); - mEncodingName = variables["encoding"].as().toStdString(); + mEncodingName = variables["encoding"].as(); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); - mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str())); + mFileDialog.setEncoding(QString::fromUtf8(mEncodingName.c_str())); - mDocumentManager.setResourceDir (mResources = variables["resources"].as().mPath); + mDocumentManager.setResourceDir(mResources = variables["resources"] + .as() + .u8string()); // This call to u8string is redundant, but required + // to build on MSVC 14.26 due to implementation bugs. if (variables["script-blacklist-use"].as()) - mDocumentManager.setBlacklistedScripts ( - variables["script-blacklist"].as().toStdStringVector()); - - mFsStrict = variables["fs-strict"].as(); + mDocumentManager.setBlacklistedScripts(variables["script-blacklist"].as>()); Files::PathContainer dataDirs, dataLocal; - if (!variables["data"].empty()) { - dataDirs = Files::PathContainer(Files::EscapePath::toPathContainer(variables["data"].as())); + if (!variables["data"].empty()) + { + dataDirs = asPathContainer(variables["data"].as()); } - Files::PathContainer::value_type local(variables["data-local"].as().mPath); + Files::PathContainer::value_type local(variables["data-local"] + .as() + .u8string()); // This call to u8string is redundant, but required to + // build on MSVC 14.26 due to implementation bugs. if (!local.empty()) + { + std::filesystem::create_directories(local); dataLocal.push_back(local); - - mCfgMgr.processPaths (dataDirs); - mCfgMgr.processPaths (dataLocal, true); + } + mCfgMgr.filterOutNonExistingPaths(dataDirs); + mCfgMgr.filterOutNonExistingPaths(dataLocal); if (!dataLocal.empty()) mLocal = dataLocal[0]; else { QMessageBox messageBox; - messageBox.setWindowTitle (tr ("No local data path available")); - messageBox.setIcon (QMessageBox::Critical); - messageBox.setStandardButtons (QMessageBox::Ok); - messageBox.setText(tr("
OpenCS is unable to access the local data directory. This may indicate a faulty configuration or a broken install.")); + messageBox.setWindowTitle(tr("No local data path available")); + messageBox.setIcon(QMessageBox::Critical); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.setText( + tr("
OpenCS is unable to access the local data directory. This may indicate a faulty configuration " + "or a broken install.")); messageBox.exec(); - QApplication::exit (1); + QApplication::exit(1); } - dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + dataDirs.insert(dataDirs.end(), dataLocal.begin(), dataLocal.end()); - //iterate the data directories and add them to the file dialog for loading - for (Files::PathContainer::const_reverse_iterator iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) - { - QString path = QString::fromUtf8 (iter->string().c_str()); - mFileDialog.addFiles(path); - } + // iterate the data directories and add them to the file dialog for loading + mFileDialog.addFiles(dataDirs); - return std::make_pair (dataDirs, variables["fallback-archive"].as().toStdStringVector()); + return std::make_pair(dataDirs, variables["fallback-archive"].as>()); } void CS::Editor::createGame() @@ -188,9 +236,9 @@ void CS::Editor::createAddon() mStartup.hide(); mFileDialog.clearFiles(); - readConfig(/*quiet*/true); + readConfig(/*quiet*/ true); - mFileDialog.showDialog (CSVDoc::ContentAction_New); + mFileDialog.showDialog(CSVDoc::ContentAction_New); } void CS::Editor::cancelFileDialog() @@ -212,59 +260,63 @@ void CS::Editor::loadDocument() mStartup.hide(); mFileDialog.clearFiles(); - readConfig(/*quiet*/true); + readConfig(/*quiet*/ true); - mFileDialog.showDialog (CSVDoc::ContentAction_Edit); + mFileDialog.showDialog(CSVDoc::ContentAction_Edit); } -void CS::Editor::openFiles (const boost::filesystem::path &savePath, const std::vector &discoveredFiles) +void CS::Editor::openFiles( + const std::filesystem::path& savePath, const std::vector& discoveredFiles) { - std::vector files; + std::vector files; - if(discoveredFiles.empty()) + if (discoveredFiles.empty()) { - for (const QString &path : mFileDialog.selectedFilePaths()) - files.emplace_back(path.toUtf8().constData()); + for (const QString& path : mFileDialog.selectedFilePaths()) + { + files.emplace_back(Files::pathFromQString(path)); + } } else { files = discoveredFiles; } - mDocumentManager.addDocument (files, savePath, false); + mDocumentManager.addDocument(files, savePath, false); mFileDialog.hide(); } -void CS::Editor::createNewFile (const boost::filesystem::path &savePath) +void CS::Editor::createNewFile(const std::filesystem::path& savePath) { - std::vector files; + std::vector files; - for (const QString &path : mFileDialog.selectedFilePaths()) { - files.emplace_back(path.toUtf8().constData()); + for (const QString& path : mFileDialog.selectedFilePaths()) + { + files.emplace_back(Files::pathFromQString(path)); } - files.push_back (savePath); + files.push_back(savePath); - mDocumentManager.addDocument (files, savePath, true); + mDocumentManager.addDocument(files, savePath, true); mFileDialog.hide(); } -void CS::Editor::createNewGame (const boost::filesystem::path& file) +void CS::Editor::createNewGame(const std::filesystem::path& file) { - std::vector files; + std::vector files; - files.push_back (file); + files.push_back(file); - mDocumentManager.addDocument (files, file, true); + mDocumentManager.addDocument(files, file, true); mNewGame.hide(); } void CS::Editor::showStartup() { - if(mStartup.isHidden()) + if (mStartup.isHidden()) mStartup.show(); mStartup.raise(); mStartup.activateWindow(); @@ -275,7 +327,7 @@ void CS::Editor::showSettings() if (mSettings.isHidden()) mSettings.show(); - mSettings.move (QCursor::pos()); + mSettings.move(QCursor::pos()); mSettings.raise(); mSettings.activateWindow(); } @@ -284,14 +336,11 @@ bool CS::Editor::makeIPCServer() { try { - mPid = boost::filesystem::temp_directory_path(); - mPid /= "openmw-cs.pid"; - bool pidExists = boost::filesystem::exists(mPid); + bool pidExists = std::filesystem::exists(mPid); mPidFile.open(mPid); - mLock = boost::interprocess::file_lock(mPid.string().c_str()); - if(!mLock.try_lock()) + if (!mLockFile.tryLock()) { Log(Debug::Error) << "Error: OpenMW-CS is already running."; return false; @@ -305,34 +354,35 @@ bool CS::Editor::makeIPCServer() mServer = new QLocalServer(this); - if(pidExists) + if (pidExists) { // hack to get the temp directory path mServer->listen("dummy"); QString fullPath = mServer->fullServerName(); mServer->close(); - fullPath.remove(QRegExp("dummy$")); + fullPath.remove(QRegularExpression("dummy$")); fullPath += mIpcServerName; - if(boost::filesystem::exists(fullPath.toUtf8().constData())) + const auto path = Files::pathFromQString(fullPath); + if (exists(path)) { // TODO: compare pid of the current process with that in the file Log(Debug::Info) << "Detected unclean shutdown."; // delete the stale file - if(remove(fullPath.toUtf8().constData())) + if (remove(path)) Log(Debug::Error) << "Error: can not remove stale connection file."; } } } - catch(const std::exception& e) + catch (const std::exception& e) { Log(Debug::Error) << "Error: " << e.what(); return false; } - if(mServer->listen(mIpcServerName)) + if (mServer->listen(mIpcServerName)) { - connect(mServer, SIGNAL(newConnection()), this, SLOT(showStartup())); + connect(mServer, &QLocalServer::newConnection, this, &Editor::showStartup); return true; } @@ -364,29 +414,26 @@ int CS::Editor::run() else { ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder = ToUTF8::calculateEncoding(mEncodingName); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName)); fileReader.setEncoder(&encoder); - fileReader.open(mFileToLoad.string()); + fileReader.open(mFileToLoad); - std::vector discoveredFiles; + std::vector discoveredFiles; - for (std::vector::const_iterator itemIter = fileReader.getGameFiles().begin(); - itemIter != fileReader.getGameFiles().end(); ++itemIter) + for (const auto& item : fileReader.getGameFiles()) { - for (Files::PathContainer::const_iterator pathIter = mDataDirs.begin(); - pathIter != mDataDirs.end(); ++pathIter) + for (const auto& path : mDataDirs) { - const boost::filesystem::path masterPath = *pathIter / itemIter->name; - if (boost::filesystem::exists(masterPath)) + if (auto masterPath = path / item.name; std::filesystem::exists(masterPath)) { - discoveredFiles.push_back(masterPath); + discoveredFiles.emplace_back(std::move(masterPath)); break; } } } discoveredFiles.push_back(mFileToLoad); - QString extension = QString::fromStdString(mFileToLoad.extension().string()).toLower(); + const auto extension = Files::pathToQString(mFileToLoad.extension()).toLower(); if (extension == ".esm") { mFileToLoad.replace_extension(".omwgame"); @@ -406,14 +453,14 @@ int CS::Editor::run() return QApplication::exec(); } -void CS::Editor::documentAdded (CSMDoc::Document *document) +void CS::Editor::documentAdded(CSMDoc::Document* document) { - mViewManager->addView (document); + mViewManager->addView(document); } -void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document) +void CS::Editor::documentAboutToBeRemoved(CSMDoc::Document* document) { - if (mMerge.getDocument()==document) + if (mMerge.getDocument() == document) mMerge.cancel(); } @@ -422,9 +469,9 @@ void CS::Editor::lastDocumentDeleted() QApplication::quit(); } -void CS::Editor::mergeDocument (CSMDoc::Document *document) +void CS::Editor::mergeDocument(CSMDoc::Document* document) { - mMerge.configure (document); + mMerge.configure(document); mMerge.show(); mMerge.raise(); mMerge.activateWindow(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 1c9342761..79c658c04 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -1,13 +1,17 @@ #ifndef CS_EDITOR_H #define CS_EDITOR_H -#include -#include +#include +#include #include #include -#include -#include + +#include +#include +#include +#include +#include #ifndef Q_MOC_RUN #include @@ -16,95 +20,101 @@ #include #include "model/doc/documentmanager.hpp" - #include "model/prefs/state.hpp" -#include "view/doc/viewmanager.hpp" -#include "view/doc/startup.hpp" #include "view/doc/filedialog.hpp" #include "view/doc/newgame.hpp" - +#include "view/doc/startup.hpp" #include "view/prefs/dialogue.hpp" - #include "view/tools/merge.hpp" +class QLocalServer; +class QLocalSocket; + namespace CSMDoc { class Document; } +namespace CSVDoc +{ + class ViewManager; +} + namespace CS { class Editor : public QObject { - Q_OBJECT + Q_OBJECT - Files::ConfigurationManager mCfgMgr; - CSMPrefs::State mSettingsState; - CSMDoc::DocumentManager mDocumentManager; - CSVDoc::StartupDialogue mStartup; - CSVDoc::NewGameDialogue mNewGame; - CSVPrefs::Dialogue mSettings; - CSVDoc::FileDialog mFileDialog; - boost::filesystem::path mLocal; - boost::filesystem::path mResources; - boost::filesystem::path mPid; - boost::interprocess::file_lock mLock; - boost::filesystem::ofstream mPidFile; - bool mFsStrict; - CSVTools::Merge mMerge; - CSVDoc::ViewManager* mViewManager; - boost::filesystem::path mFileToLoad; - Files::PathContainer mDataDirs; - std::string mEncodingName; + Files::ConfigurationManager mCfgMgr; + boost::program_options::variables_map mConfigVariables; + CSMPrefs::State mSettingsState; + CSMDoc::DocumentManager mDocumentManager; + CSVDoc::StartupDialogue mStartup; + CSVDoc::NewGameDialogue mNewGame; + CSVPrefs::Dialogue mSettings; + CSVDoc::FileDialog mFileDialog; + std::filesystem::path mLocal; + std::filesystem::path mResources; + std::filesystem::path mPid; + QLockFile mLockFile; + std::ofstream mPidFile; + CSVTools::Merge mMerge; + CSVDoc::ViewManager* mViewManager; + std::filesystem::path mFileToLoad; + Files::PathContainer mDataDirs; + std::string mEncodingName; - std::pair > readConfig(bool quiet=false); - ///< \return data paths + boost::program_options::variables_map readConfiguration(); + ///< Calls mCfgMgr.readConfiguration; should be used before initialization of mSettingsState as it depends on + ///< the configuration. + std::pair> readConfig(bool quiet = false); + ///< \return data paths - // not implemented - Editor (const Editor&); - Editor& operator= (const Editor&); + // not implemented + Editor(const Editor&); + Editor& operator=(const Editor&); - public: + public: + Editor(int argc, char** argv); + ~Editor(); - Editor (int argc, char **argv); - ~Editor (); + bool makeIPCServer(); + void connectToIPCServer(); - bool makeIPCServer(); - void connectToIPCServer(); + int run(); + ///< \return error status - int run(); - ///< \return error status + private slots: - private slots: + void createGame(); + void createAddon(); + void cancelCreateGame(); + void cancelFileDialog(); - void createGame(); - void createAddon(); - void cancelCreateGame(); - void cancelFileDialog(); + void loadDocument(); + void openFiles( + const std::filesystem::path& path, const std::vector& discoveredFiles = {}); + void createNewFile(const std::filesystem::path& path); + void createNewGame(const std::filesystem::path& file); - void loadDocument(); - void openFiles (const boost::filesystem::path &path, const std::vector &discoveredFiles = std::vector()); - void createNewFile (const boost::filesystem::path& path); - void createNewGame (const boost::filesystem::path& file); + void showStartup(); - void showStartup(); + void showSettings(); - void showSettings(); + void documentAdded(CSMDoc::Document* document); - void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved(CSMDoc::Document* document); - void documentAboutToBeRemoved (CSMDoc::Document *document); + void lastDocumentDeleted(); - void lastDocumentDeleted(); + void mergeDocument(CSMDoc::Document* document); - void mergeDocument (CSMDoc::Document *document); - - private: - - QString mIpcServerName; - QLocalServer *mServer; - QLocalSocket *mClientSocket; + private: + QString mIpcServerName; + QLocalServer* mServer; + QLocalSocket* mClientSocket; }; } diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index c7d57a256..ecab9614a 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -5,9 +5,13 @@ #include #include -#include +#include + +#include #include +#include +#include #include "model/doc/messages.hpp" #include "model/world/universalid.hpp" @@ -16,58 +20,80 @@ #include #endif -Q_DECLARE_METATYPE (std::string) +Q_DECLARE_METATYPE(std::string) + +class QEvent; +class QObject; class Application : public QApplication { - private: - - bool notify (QObject *receiver, QEvent *event) override +private: + bool notify(QObject* receiver, QEvent* event) override + { + try { - try - { - return QApplication::notify (receiver, event); - } - catch (const std::exception& exception) - { - Log(Debug::Error) << "An exception has been caught: " << exception.what(); - } - - return false; + return QApplication::notify(receiver, event); + } + catch (const std::exception& exception) + { + Log(Debug::Error) << "An exception has been caught: " << exception.what(); } - public: + return false; + } - Application (int& argc, char *argv[]) : QApplication (argc, argv) {} +public: + Application(int& argc, char* argv[]) + : QApplication(argc, argv) + { + } }; -int runApplication(int argc, char *argv[]) +void setQSurfaceFormat() { + osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); + QSurfaceFormat format = QSurfaceFormat::defaultFormat(); + format.setVersion(2, 1); + format.setRenderableType(QSurfaceFormat::OpenGL); + format.setDepthBufferSize(24); + format.setSamples(ds->getMultiSamples()); + format.setStencilBufferSize(ds->getMinimumNumStencilBits()); + format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + QSurfaceFormat::setDefaultFormat(format); +} + +int runApplication(int argc, char* argv[]) +{ + Platform::init(); + #ifdef Q_OS_MAC setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); #endif - Q_INIT_RESOURCE (resources); + Q_INIT_RESOURCE(resources); - qRegisterMetaType ("std::string"); - qRegisterMetaType ("CSMWorld::UniversalId"); - qRegisterMetaType ("CSMDoc::Message"); + qRegisterMetaType("std::string"); + qRegisterMetaType("CSMWorld::UniversalId"); + qRegisterMetaType("CSMDoc::Message"); - Application application (argc, argv); + setQSurfaceFormat(); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + + Application application(argc, argv); #ifdef Q_OS_MAC QDir dir(QCoreApplication::applicationDirPath()); QDir::setCurrent(dir.absolutePath()); #endif - application.setWindowIcon (QIcon (":./openmw-cs.png")); + application.setWindowIcon(QIcon(":./openmw-cs.png")); CS::Editor editor(argc, argv); #ifdef __linux__ - setlocale(LC_NUMERIC,"C"); + setlocale(LC_NUMERIC, "C"); #endif - if(!editor.makeIPCServer()) + if (!editor.makeIPCServer()) { editor.connectToIPCServer(); return 0; @@ -76,8 +102,7 @@ int runApplication(int argc, char *argv[]) return editor.run(); } - -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { return wrapApplication(&runApplication, argc, argv, "OpenMW-CS"); } diff --git a/apps/opencs/model/doc/blacklist.cpp b/apps/opencs/model/doc/blacklist.cpp index 690d79983..9b422cb75 100644 --- a/apps/opencs/model/doc/blacklist.cpp +++ b/apps/opencs/model/doc/blacklist.cpp @@ -1,30 +1,32 @@ #include "blacklist.hpp" #include +#include +#include -#include +#include -bool CSMDoc::Blacklist::isBlacklisted (const CSMWorld::UniversalId& id) const +#include + +bool CSMDoc::Blacklist::isBlacklisted(const CSMWorld::UniversalId& id) const { - std::map >::const_iterator iter = - mIds.find (id.getType()); + std::map>::const_iterator iter = mIds.find(id.getType()); - if (iter==mIds.end()) + if (iter == mIds.end()) return false; - return std::binary_search (iter->second.begin(), iter->second.end(), - Misc::StringUtils::lowerCase (id.getId())); + return std::binary_search(iter->second.begin(), iter->second.end(), Misc::StringUtils::lowerCase(id.getId())); } -void CSMDoc::Blacklist::add (CSMWorld::UniversalId::Type type, - const std::vector& ids) +void CSMDoc::Blacklist::add(CSMWorld::UniversalId::Type type, const std::vector& ids) { std::vector& list = mIds[type]; size_t size = list.size(); - list.resize (size+ids.size()); + list.resize(size + ids.size()); - std::transform (ids.begin(), ids.end(), list.begin()+size, Misc::StringUtils::lowerCase); - std::sort (list.begin(), list.end()); + std::transform(ids.begin(), ids.end(), list.begin() + size, + [](const std::string& s) { return Misc::StringUtils::lowerCase(s); }); + std::sort(list.begin(), list.end()); } diff --git a/apps/opencs/model/doc/blacklist.hpp b/apps/opencs/model/doc/blacklist.hpp index 9bf7f1d86..e565aa252 100644 --- a/apps/opencs/model/doc/blacklist.hpp +++ b/apps/opencs/model/doc/blacklist.hpp @@ -2,8 +2,8 @@ #define CSM_DOC_BLACKLIST_H #include -#include #include +#include #include "../world/universalid.hpp" @@ -12,13 +12,12 @@ namespace CSMDoc /// \brief ID blacklist sorted by UniversalId type class Blacklist { - std::map > mIds; + std::map> mIds; - public: + public: + bool isBlacklisted(const CSMWorld::UniversalId& id) const; - bool isBlacklisted (const CSMWorld::UniversalId& id) const; - - void add (CSMWorld::UniversalId::Type type, const std::vector& ids); + void add(CSMWorld::UniversalId::Type type, const std::vector& ids); }; } diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 3a20555d1..9ee2a8bc4 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -1,9 +1,34 @@ #include "document.hpp" -#include +#include "state.hpp" -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include "../world/defaultgmsts.hpp" @@ -11,143 +36,146 @@ #include #endif -#include +namespace CSMWorld +{ + class IdCompletionManager; +} void CSMDoc::Document::addGmsts() { - for (size_t i=0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGmsts::Floats[i]; - gmst.mValue.setType (ESM::VT_Float); - gmst.mValue.setFloat (CSMWorld::DefaultGmsts::FloatsDefaultValues[i]); - getData().getGmsts().add (gmst); + gmst.mId = ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::Floats[i]); + gmst.mValue.setType(ESM::VT_Float); + gmst.mRecordFlags = 0; + gmst.mValue.setFloat(CSMWorld::DefaultGmsts::FloatsDefaultValues[i]); + getData().getGmsts().add(gmst); } - for (size_t i=0; i < CSMWorld::DefaultGmsts::IntCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::IntCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGmsts::Ints[i]; - gmst.mValue.setType (ESM::VT_Int); - gmst.mValue.setInteger (CSMWorld::DefaultGmsts::IntsDefaultValues[i]); - getData().getGmsts().add (gmst); + gmst.mId = ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::Ints[i]); + gmst.mValue.setType(ESM::VT_Int); + gmst.mRecordFlags = 0; + gmst.mValue.setInteger(CSMWorld::DefaultGmsts::IntsDefaultValues[i]); + getData().getGmsts().add(gmst); } - for (size_t i=0; i < CSMWorld::DefaultGmsts::StringCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::StringCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGmsts::Strings[i]; - gmst.mValue.setType (ESM::VT_String); - gmst.mValue.setString (""); - getData().getGmsts().add (gmst); + gmst.mId = ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::Strings[i]); + gmst.mValue.setType(ESM::VT_String); + gmst.mRecordFlags = 0; + gmst.mValue.setString(""); + getData().getGmsts().add(gmst); } } void CSMDoc::Document::addOptionalGmsts() { - for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalFloatCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::OptionalFloatCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGmsts::OptionalFloats[i]; + gmst.mId = ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::OptionalFloats[i]); gmst.blank(); - gmst.mValue.setType (ESM::VT_Float); - addOptionalGmst (gmst); + gmst.mValue.setType(ESM::VT_Float); + addOptionalGmst(gmst); } - for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalIntCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::OptionalIntCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGmsts::OptionalInts[i]; + gmst.mId = ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::OptionalInts[i]); gmst.blank(); - gmst.mValue.setType (ESM::VT_Int); - addOptionalGmst (gmst); + gmst.mValue.setType(ESM::VT_Int); + addOptionalGmst(gmst); } - for (size_t i=0; i < CSMWorld::DefaultGmsts::OptionalStringCount; ++i) + for (size_t i = 0; i < CSMWorld::DefaultGmsts::OptionalStringCount; ++i) { ESM::GameSetting gmst; - gmst.mId = CSMWorld::DefaultGmsts::OptionalStrings[i]; + gmst.mId = ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::OptionalStrings[i]); gmst.blank(); - gmst.mValue.setType (ESM::VT_String); - gmst.mValue.setString (""); - addOptionalGmst (gmst); + gmst.mValue.setType(ESM::VT_String); + gmst.mValue.setString(""); + addOptionalGmst(gmst); } } void CSMDoc::Document::addOptionalGlobals() { - static const char *sGlobals[] = - { + static constexpr std::string_view globals[] = { "DaysPassed", "PCWerewolf", "PCYear", - 0 }; - for (int i=0; sGlobals[i]; ++i) + for (std::size_t i = 0; i < std::size(globals); ++i) { ESM::Global global; - global.mId = sGlobals[i]; + global.mId = ESM::RefId::stringRefId(globals[i]); global.blank(); - global.mValue.setType (ESM::VT_Long); + global.mValue.setType(ESM::VT_Long); - if (i==0) - global.mValue.setInteger (1); // dayspassed starts counting at 1 + if (i == 0) + global.mValue.setInteger(1); // dayspassed starts counting at 1 - addOptionalGlobal (global); + addOptionalGlobal(global); } } void CSMDoc::Document::addOptionalMagicEffects() { - for (int i=ESM::MagicEffect::SummonFabricant; i<=ESM::MagicEffect::SummonCreature05; ++i) + for (int i = ESM::MagicEffect::SummonFabricant; i <= ESM::MagicEffect::SummonCreature05; ++i) { ESM::MagicEffect effect; effect.mIndex = i; - effect.mId = ESM::MagicEffect::indexToId (i); + effect.mId = ESM::MagicEffect::indexToRefId(i); effect.blank(); - addOptionalMagicEffect (effect); + addOptionalMagicEffect(effect); } } -void CSMDoc::Document::addOptionalGmst (const ESM::GameSetting& gmst) +void CSMDoc::Document::addOptionalGmst(const ESM::GameSetting& gmst) { - if (getData().getGmsts().searchId (gmst.mId)==-1) + if (getData().getGmsts().searchId(gmst.mId) == -1) { - CSMWorld::Record record; - record.mBase = gmst; - record.mState = CSMWorld::RecordBase::State_BaseOnly; - getData().getGmsts().appendRecord (record); + auto record = std::make_unique>(); + record->mBase = gmst; + record->mState = CSMWorld::RecordBase::State_BaseOnly; + getData().getGmsts().appendRecord(std::move(record)); } } -void CSMDoc::Document::addOptionalGlobal (const ESM::Global& global) +void CSMDoc::Document::addOptionalGlobal(const ESM::Global& global) { - if (getData().getGlobals().searchId (global.mId)==-1) + if (getData().getGlobals().searchId(global.mId) == -1) { - CSMWorld::Record record; - record.mBase = global; - record.mState = CSMWorld::RecordBase::State_BaseOnly; - getData().getGlobals().appendRecord (record); + auto record = std::make_unique>(); + record->mBase = global; + record->mState = CSMWorld::RecordBase::State_BaseOnly; + getData().getGlobals().appendRecord(std::move(record)); } } -void CSMDoc::Document::addOptionalMagicEffect (const ESM::MagicEffect& magicEffect) +void CSMDoc::Document::addOptionalMagicEffect(const ESM::MagicEffect& magicEffect) { - if (getData().getMagicEffects().searchId (magicEffect.mId)==-1) + if (getData().getMagicEffects().searchId(magicEffect.mId) == -1) { - CSMWorld::Record record; - record.mBase = magicEffect; - record.mState = CSMWorld::RecordBase::State_BaseOnly; - getData().getMagicEffects().appendRecord (record); + auto record = std::make_unique>(); + record->mBase = magicEffect; + record->mState = CSMWorld::RecordBase::State_BaseOnly; + getData().getMagicEffects().appendRecord(std::move(record)); } } void CSMDoc::Document::createBase() { - static const char *sGlobals[] = - { + static constexpr std::string_view globals[] = { "Day", "DaysPassed", "GameHour", @@ -156,35 +184,34 @@ void CSMDoc::Document::createBase() "PCVampire", "PCWerewolf", "PCYear", - 0 }; - for (int i=0; sGlobals[i]; ++i) + for (std::size_t i = 0; i < std::size(globals); ++i) { ESM::Global record; - record.mId = sGlobals[i]; - record.mValue.setType (i==2 ? ESM::VT_Float : ESM::VT_Long); + record.mId = ESM::RefId::stringRefId(globals[i]); + record.mRecordFlags = 0; + record.mValue.setType(i == 2 ? ESM::VT_Float : ESM::VT_Long); - if (i==0 || i==1) - record.mValue.setInteger (1); + if (i == 0 || i == 1) + record.mValue.setInteger(1); - getData().getGlobals().add (record); + getData().getGlobals().add(record); } addGmsts(); - for (int i=0; i<27; ++i) + for (int i = 0; i < 27; ++i) { ESM::Skill record; record.mIndex = i; - record.mId = ESM::Skill::indexToId (record.mIndex); + record.mId = ESM::Skill::indexToRefId(record.mIndex); record.blank(); - getData().getSkills().add (record); + getData().getSkills().add(record); } - static const char *sVoice[] = - { + static constexpr std::string_view voices[] = { "Intruder", "Attack", "Hello", @@ -193,21 +220,20 @@ void CSMDoc::Document::createBase() "Idle", "Flee", "Hit", - 0 }; - for (int i=0; sVoice[i]; ++i) + for (const std::string_view voice : voices) { ESM::Dialogue record; - record.mId = sVoice[i]; + record.mId = ESM::RefId::stringRefId(voice); + record.mStringId = voice; record.mType = ESM::Dialogue::Voice; record.blank(); - getData().getTopics().add (record); + getData().getTopics().add(record); } - static const char *sGreetings[] = - { + static constexpr std::string_view greetings[] = { "Greeting 0", "Greeting 1", "Greeting 2", @@ -218,21 +244,20 @@ void CSMDoc::Document::createBase() "Greeting 7", "Greeting 8", "Greeting 9", - 0 }; - for (int i=0; sGreetings[i]; ++i) + for (const std::string_view greeting : greetings) { ESM::Dialogue record; - record.mId = sGreetings[i]; + record.mId = ESM::RefId::stringRefId(greeting); + record.mStringId = greeting; record.mType = ESM::Dialogue::Greeting; record.blank(); - getData().getTopics().add (record); + getData().getTopics().add(record); } - static const char *sPersuasion[] = - { + static constexpr std::string_view persuasions[] = { "Intimidate Success", "Intimidate Fail", "Service Refusal", @@ -243,64 +268,67 @@ void CSMDoc::Document::createBase() "Admire Fail", "Taunt Fail", "Bribe Fail", - 0 }; - for (int i=0; sPersuasion[i]; ++i) + for (const std::string_view persuasion : persuasions) { ESM::Dialogue record; - record.mId = sPersuasion[i]; + record.mId = ESM::RefId::stringRefId(persuasion); + record.mStringId = persuasion; record.mType = ESM::Dialogue::Persuasion; record.blank(); - getData().getTopics().add (record); + getData().getTopics().add(record); } - for (int i=0; i& files,bool new_, - const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, - ToUTF8::FromType encoding, const std::vector& blacklistedScripts, - bool fsStrict, const Files::PathContainer& dataPaths, const std::vector& archives) -: mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, fsStrict, dataPaths, archives, resDir), - mTools (*this, encoding), - mProjectPath ((configuration.getUserDataPath() / "projects") / - (savePath.filename().string() + ".project")), - mSavingOperation (*this, mProjectPath, encoding), - mSaving (&mSavingOperation), - mResDir(resDir), mRunner (mProjectPath), - mDirty (false), mIdCompletionManager(mData) +CSMDoc::Document::Document(const Files::ConfigurationManager& configuration, std::vector files, + bool new_, const std::filesystem::path& savePath, const std::filesystem::path& resDir, ToUTF8::FromType encoding, + const std::vector& blacklistedScripts, const Files::PathContainer& dataPaths, + const std::vector& archives) + : mSavePath(savePath) + , mContentFiles(std::move(files)) + , mNew(new_) + , mData(encoding, dataPaths, archives, resDir) + , mTools(*this, encoding) + , mProjectPath((configuration.getUserDataPath() / "projects") / (savePath.filename().u8string() + u8".project")) + , mSavingOperation(*this, mProjectPath, encoding) + , mSaving(&mSavingOperation) + , mResDir(resDir) + , mRunner(mProjectPath) + , mDirty(false) + , mIdCompletionManager(mData) { if (mContentFiles.empty()) - throw std::runtime_error ("Empty content file sequence"); + throw std::runtime_error("Empty content file sequence"); - if (mNew || !boost::filesystem::exists (mProjectPath)) + if (mNew || !std::filesystem::exists(mProjectPath)) { - boost::filesystem::path filtersPath (configuration.getUserDataPath() / "defaultfilters"); + auto filtersPath = configuration.getUserDataPath() / "defaultfilters"; - boost::filesystem::ofstream destination(mProjectPath, std::ios::out | std::ios::binary); + std::ofstream destination(mProjectPath, std::ios::out | std::ios::binary); if (!destination.is_open()) - throw std::runtime_error("Can not create project file: " + mProjectPath.string()); + throw std::runtime_error("Can not create project file: " + Files::pathToUnicodeString(mProjectPath)); destination.exceptions(std::ios::failbit | std::ios::badbit); - if (!boost::filesystem::exists (filtersPath)) + if (!std::filesystem::exists(filtersPath)) filtersPath = mResDir / "defaultfilters"; - boost::filesystem::ifstream source(filtersPath, std::ios::in | std::ios::binary); + std::ifstream source(filtersPath, std::ios::in | std::ios::binary); if (!source.is_open()) - throw std::runtime_error("Can not read filters file: " + filtersPath.string()); + throw std::runtime_error("Can not read filters file: " + Files::pathToUnicodeString(filtersPath)); source.exceptions(std::ios::failbit | std::ios::badbit); destination << source.rdbuf(); @@ -308,36 +336,29 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, if (mNew) { - if (mContentFiles.size()==1) + if (mContentFiles.size() == 1) createBase(); } - mBlacklist.add (CSMWorld::UniversalId::Type_Script, blacklistedScripts); + mBlacklist.add(CSMWorld::UniversalId::Type_Script, blacklistedScripts); addOptionalGmsts(); addOptionalGlobals(); addOptionalMagicEffects(); - connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); + connect(&mUndoStack, &QUndoStack::cleanChanged, this, &Document::modificationStateChanged); - connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); - connect (&mTools, SIGNAL (done (int, bool)), this, SIGNAL (operationDone (int, bool))); - connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); - connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), - this, SIGNAL (mergeDone (CSMDoc::Document*))); + connect(&mTools, &CSMTools::Tools::progress, this, qOverload(&Document::progress)); + connect(&mTools, &CSMTools::Tools::done, this, &Document::operationDone); + connect(&mTools, &CSMTools::Tools::done, this, &Document::operationDone2); + connect(&mTools, &CSMTools::Tools::mergeDone, this, &Document::mergeDone); - connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); - connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); + connect(&mSaving, &OperationHolder::progress, this, qOverload(&Document::progress)); + connect(&mSaving, &OperationHolder::done, this, &Document::operationDone2); - connect ( - &mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)), - this, SLOT (reportMessage (const CSMDoc::Message&, int))); + connect(&mSaving, &OperationHolder::reportMessage, this, &Document::reportMessage); - connect (&mRunner, SIGNAL (runStateChanged()), this, SLOT (runStateChanged())); -} - -CSMDoc::Document::~Document() -{ + connect(&mRunner, &Runner::runStateChanged, this, &Document::runStateChanged); } QUndoStack& CSMDoc::Document::getUndoStack() @@ -364,22 +385,22 @@ int CSMDoc::Document::getState() const return state; } -const boost::filesystem::path& CSMDoc::Document::getResourceDir() const +const std::filesystem::path& CSMDoc::Document::getResourceDir() const { return mResDir; } -const boost::filesystem::path& CSMDoc::Document::getSavePath() const +const std::filesystem::path& CSMDoc::Document::getSavePath() const { return mSavePath; } -const boost::filesystem::path& CSMDoc::Document::getProjectPath() const +const std::filesystem::path& CSMDoc::Document::getProjectPath() const { return mProjectPath; } -const std::vector& CSMDoc::Document::getContentFiles() const +const std::vector& CSMDoc::Document::getContentFiles() const { return mContentFiles; } @@ -392,64 +413,62 @@ bool CSMDoc::Document::isNew() const void CSMDoc::Document::save() { if (mSaving.isRunning()) - throw std::logic_error ( - "Failed to initiate save, because a save operation is already running."); + throw std::logic_error("Failed to initiate save, because a save operation is already running."); mSaving.start(); - emit stateChanged (getState(), this); + emit stateChanged(getState(), this); } -CSMWorld::UniversalId CSMDoc::Document::verify (const CSMWorld::UniversalId& reportId) +CSMWorld::UniversalId CSMDoc::Document::verify(const CSMWorld::UniversalId& reportId) { - CSMWorld::UniversalId id = mTools.runVerifier (reportId); - emit stateChanged (getState(), this); + CSMWorld::UniversalId id = mTools.runVerifier(reportId); + emit stateChanged(getState(), this); return id; } - CSMWorld::UniversalId CSMDoc::Document::newSearch() { return mTools.newSearch(); } -void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search) +void CSMDoc::Document::runSearch(const CSMWorld::UniversalId& searchId, const CSMTools::Search& search) { - mTools.runSearch (searchId, search); - emit stateChanged (getState(), this); + mTools.runSearch(searchId, search); + emit stateChanged(getState(), this); } -void CSMDoc::Document::runMerge (std::unique_ptr target) +void CSMDoc::Document::runMerge(std::unique_ptr target) { - mTools.runMerge (std::move(target)); - emit stateChanged (getState(), this); + mTools.runMerge(std::move(target)); + emit stateChanged(getState(), this); } -void CSMDoc::Document::abortOperation (int type) +void CSMDoc::Document::abortOperation(int type) { - if (type==State_Saving) + if (type == State_Saving) mSaving.abort(); else - mTools.abortOperation (type); + mTools.abortOperation(type); } -void CSMDoc::Document::modificationStateChanged (bool clean) +void CSMDoc::Document::modificationStateChanged(bool clean) { - emit stateChanged (getState(), this); + emit stateChanged(getState(), this); } -void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) +void CSMDoc::Document::reportMessage(const CSMDoc::Message& message, int type) { /// \todo find a better way to get these messages to the user. Log(Debug::Info) << message.mMessage; } -void CSMDoc::Document::operationDone2 (int type, bool failed) +void CSMDoc::Document::operationDone2(int type, bool failed) { - if (type==CSMDoc::State_Saving && !failed) + if (type == CSMDoc::State_Saving && !failed) mDirty = false; - emit stateChanged (getState(), this); + emit stateChanged(getState(), this); } const CSMWorld::Data& CSMDoc::Document::getData() const @@ -462,27 +481,26 @@ CSMWorld::Data& CSMDoc::Document::getData() return mData; } -CSMTools::ReportModel *CSMDoc::Document::getReport (const CSMWorld::UniversalId& id) +CSMTools::ReportModel* CSMDoc::Document::getReport(const CSMWorld::UniversalId& id) { - return mTools.getReport (id); + return mTools.getReport(id); } -bool CSMDoc::Document::isBlacklisted (const CSMWorld::UniversalId& id) - const +bool CSMDoc::Document::isBlacklisted(const CSMWorld::UniversalId& id) const { - return mBlacklist.isBlacklisted (id); + return mBlacklist.isBlacklisted(id); } -void CSMDoc::Document::startRunning (const std::string& profile, - const std::string& startupInstruction) +void CSMDoc::Document::startRunning(const std::string& profile, const std::string& startupInstruction) { - std::vector contentFiles; + std::vector contentFiles; - for (std::vector::const_iterator iter (mContentFiles.begin()); - iter!=mContentFiles.end(); ++iter) - contentFiles.push_back (iter->filename().string()); + for (const auto& mContentFile : mContentFiles) + { + contentFiles.emplace_back(mContentFile.filename()); + } - mRunner.configure (getData().getDebugProfiles().getRecord (profile).get(), contentFiles, + mRunner.configure(getData().getDebugProfiles().getRecord(ESM::RefId::stringRefId(profile)).get(), contentFiles, startupInstruction); int state = getState(); @@ -490,9 +508,9 @@ void CSMDoc::Document::startRunning (const std::string& profile, if (state & State_Modified) { // need to save first - mRunner.start (true); + mRunner.start(true); - new SaveWatcher (&mRunner, &mSaving); // no, that is not a memory leak. Qt is weird. + new SaveWatcher(&mRunner, &mSaving); // no, that is not a memory leak. Qt is weird. if (!(state & State_Saving)) save(); @@ -506,22 +524,22 @@ void CSMDoc::Document::stopRunning() mRunner.stop(); } -QTextDocument *CSMDoc::Document::getRunLog() +QTextDocument* CSMDoc::Document::getRunLog() { return mRunner.getLog(); } void CSMDoc::Document::runStateChanged() { - emit stateChanged (getState(), this); + emit stateChanged(getState(), this); } -void CSMDoc::Document::progress (int current, int max, int type) +void CSMDoc::Document::progress(int current, int max, int type) { - emit progress (current, max, type, 1, this); + emit progress(current, max, type, 1, this); } -CSMWorld::IdCompletionManager &CSMDoc::Document::getIdCompletionManager() +CSMWorld::IdCompletionManager& CSMDoc::Document::getIdCompletionManager() { return mIdCompletionManager; } diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 0332cb43a..4acdfafa4 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -1,13 +1,15 @@ #ifndef CSM_DOC_DOCUMENT_H #define CSM_DOC_DOCUMENT_H -#include - -#include - -#include #include -#include +#include + +#include +#include +#include +#include + +#include #include #include @@ -17,22 +19,17 @@ #include "../tools/tools.hpp" -#include "state.hpp" -#include "saving.hpp" #include "blacklist.hpp" -#include "runner.hpp" #include "operationholder.hpp" +#include "runner.hpp" +#include "saving.hpp" -class QAbstractItemModel; +class QTextDocument; -namespace Fallback +namespace CSMTools { - class Map; -} - -namespace VFS -{ - class Manager; + class ReportModel; + class Search; } namespace ESM @@ -47,142 +44,135 @@ namespace Files struct ConfigurationManager; } -namespace CSMWorld -{ - class ResourcesManager; -} - namespace CSMDoc { + struct Message; class Document : public QObject { - Q_OBJECT + Q_OBJECT - private: + private: + std::filesystem::path mSavePath; + std::vector mContentFiles; + bool mNew; + CSMWorld::Data mData; + CSMTools::Tools mTools; + std::filesystem::path mProjectPath; + Saving mSavingOperation; + OperationHolder mSaving; + std::filesystem::path mResDir; + Blacklist mBlacklist; + Runner mRunner; + bool mDirty; - boost::filesystem::path mSavePath; - std::vector mContentFiles; - bool mNew; - CSMWorld::Data mData; - CSMTools::Tools mTools; - boost::filesystem::path mProjectPath; - Saving mSavingOperation; - OperationHolder mSaving; - boost::filesystem::path mResDir; - Blacklist mBlacklist; - Runner mRunner; - bool mDirty; + CSMWorld::IdCompletionManager mIdCompletionManager; - CSMWorld::IdCompletionManager mIdCompletionManager; + // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is + // connected to a slot, that is using other member variables. Unfortunately this connection is cut only in the + // QObject destructor, which is way too late. + QUndoStack mUndoStack; - // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is - // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. - QUndoStack mUndoStack; + // not implemented + Document(const Document&); + Document& operator=(const Document&); - // not implemented - Document (const Document&); - Document& operator= (const Document&); + void createBase(); - void createBase(); + void addGmsts(); - void addGmsts(); + void addOptionalGmsts(); - void addOptionalGmsts(); + void addOptionalGlobals(); - void addOptionalGlobals(); + void addOptionalMagicEffects(); - void addOptionalMagicEffects(); + void addOptionalGmst(const ESM::GameSetting& gmst); - void addOptionalGmst (const ESM::GameSetting& gmst); + void addOptionalGlobal(const ESM::Global& global); - void addOptionalGlobal (const ESM::Global& global); + void addOptionalMagicEffect(const ESM::MagicEffect& effect); - void addOptionalMagicEffect (const ESM::MagicEffect& effect); + public: + Document(const Files::ConfigurationManager& configuration, std::vector files, bool new_, + const std::filesystem::path& savePath, const std::filesystem::path& resDir, ToUTF8::FromType encoding, + const std::vector& blacklistedScripts, const Files::PathContainer& dataPaths, + const std::vector& archives); - public: + ~Document() override = default; - Document (const Files::ConfigurationManager& configuration, - const std::vector< boost::filesystem::path >& files, bool new_, - const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, - ToUTF8::FromType encoding, const std::vector& blacklistedScripts, - bool fsStrict, const Files::PathContainer& dataPaths, const std::vector& archives); + QUndoStack& getUndoStack(); - ~Document(); + int getState() const; - QUndoStack& getUndoStack(); + const std::filesystem::path& getResourceDir() const; - int getState() const; + const std::filesystem::path& getSavePath() const; - const boost::filesystem::path& getResourceDir() const; + const std::filesystem::path& getProjectPath() const; - const boost::filesystem::path& getSavePath() const; + const std::vector& getContentFiles() const; + ///< \attention The last element in this collection is the file that is being edited, + /// but with its original path instead of the save path. - const boost::filesystem::path& getProjectPath() const; + bool isNew() const; + ///< Is this a newly created content file? - const std::vector& getContentFiles() const; - ///< \attention The last element in this collection is the file that is being edited, - /// but with its original path instead of the save path. + void save(); - bool isNew() const; - ///< Is this a newly created content file? + CSMWorld::UniversalId verify(const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); - void save(); + CSMWorld::UniversalId newSearch(); - CSMWorld::UniversalId verify (const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); + void runSearch(const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); - CSMWorld::UniversalId newSearch(); + void runMerge(std::unique_ptr target); - void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); + void abortOperation(int type); - void runMerge (std::unique_ptr target); + const CSMWorld::Data& getData() const; - void abortOperation (int type); + CSMWorld::Data& getData(); - const CSMWorld::Data& getData() const; + CSMTools::ReportModel* getReport(const CSMWorld::UniversalId& id); + ///< The ownership of the returned report is not transferred. - CSMWorld::Data& getData(); + bool isBlacklisted(const CSMWorld::UniversalId& id) const; - CSMTools::ReportModel *getReport (const CSMWorld::UniversalId& id); - ///< The ownership of the returned report is not transferred. + void startRunning(const std::string& profile, const std::string& startupInstruction = ""); - bool isBlacklisted (const CSMWorld::UniversalId& id) const; + void stopRunning(); - void startRunning (const std::string& profile, - const std::string& startupInstruction = ""); + QTextDocument* getRunLog(); - void stopRunning(); + CSMWorld::IdCompletionManager& getIdCompletionManager(); - QTextDocument *getRunLog(); + void flagAsDirty(); - CSMWorld::IdCompletionManager &getIdCompletionManager(); + signals: - void flagAsDirty(); + void stateChanged(int state, CSMDoc::Document* document); - signals: + void progress(int current, int max, int type, int threads, CSMDoc::Document* document); - void stateChanged (int state, CSMDoc::Document *document); + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone(CSMDoc::Document* document); - void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + void operationDone(int type, bool failed); - /// \attention When this signal is emitted, *this hands over the ownership of the - /// document. This signal must be handled to avoid a leak. - void mergeDone (CSMDoc::Document *document); + private slots: - void operationDone (int type, bool failed); + void modificationStateChanged(bool clean); - private slots: + void reportMessage(const CSMDoc::Message& message, int type); - void modificationStateChanged (bool clean); + void operationDone2(int type, bool failed); - void reportMessage (const CSMDoc::Message& message, int type); + void runStateChanged(); - void operationDone2 (int type, bool failed); + public slots: - void runStateChanged(); - - public slots: - - void progress (int current, int max, int type); + void progress(int current, int max, int type); }; } diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index d70301ac5..4052c8a78 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -1,6 +1,12 @@ #include "documentmanager.hpp" -#include +#include + +#include +#include +#include + +#include #ifndef Q_MOC_RUN #include @@ -8,31 +14,25 @@ #include "document.hpp" -CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration) -: mConfiguration (configuration), mEncoding (ToUTF8::WINDOWS_1252), mFsStrict(false) +CSMDoc::DocumentManager::DocumentManager(const Files::ConfigurationManager& configuration) + : mConfiguration(configuration) + , mEncoding(ToUTF8::WINDOWS_1252) { - boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects"; + std::filesystem::path projectPath = configuration.getUserDataPath() / "projects"; - if (!boost::filesystem::is_directory (projectPath)) - boost::filesystem::create_directories (projectPath); + if (!std::filesystem::is_directory(projectPath)) + std::filesystem::create_directories(projectPath); - mLoader.moveToThread (&mLoaderThread); + mLoader.moveToThread(&mLoaderThread); mLoaderThread.start(); - connect (&mLoader, SIGNAL (documentLoaded (Document *)), - this, SLOT (documentLoaded (Document *))); - connect (&mLoader, SIGNAL (documentNotLoaded (Document *, const std::string&)), - this, SLOT (documentNotLoaded (Document *, const std::string&))); - connect (this, SIGNAL (loadRequest (CSMDoc::Document *)), - &mLoader, SLOT (loadDocument (CSMDoc::Document *))); - connect (&mLoader, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int)), - this, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int))); - connect (&mLoader, SIGNAL (nextRecord (CSMDoc::Document *, int)), - this, SIGNAL (nextRecord (CSMDoc::Document *, int))); - connect (this, SIGNAL (cancelLoading (CSMDoc::Document *)), - &mLoader, SLOT (abortLoading (CSMDoc::Document *))); - connect (&mLoader, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), - this, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&))); + connect(&mLoader, &Loader::documentLoaded, this, &DocumentManager::documentLoaded); + connect(&mLoader, &Loader::documentNotLoaded, this, &DocumentManager::documentNotLoaded); + connect(this, &DocumentManager::loadRequest, &mLoader, &Loader::loadDocument); + connect(&mLoader, &Loader::nextStage, this, &DocumentManager::nextStage); + connect(&mLoader, &Loader::nextRecord, this, &DocumentManager::nextRecord); + connect(this, &DocumentManager::cancelLoading, &mLoader, &Loader::abortLoading); + connect(&mLoader, &Loader::loadMessage, this, &DocumentManager::loadMessage); } CSMDoc::DocumentManager::~DocumentManager() @@ -42,7 +42,7 @@ CSMDoc::DocumentManager::~DocumentManager() mLoader.hasThingsToDo().wakeAll(); mLoaderThread.wait(); - for (std::vector::iterator iter (mDocuments.begin()); iter!=mDocuments.end(); ++iter) + for (std::vector::iterator iter(mDocuments.begin()); iter != mDocuments.end(); ++iter) delete *iter; } @@ -51,80 +51,79 @@ bool CSMDoc::DocumentManager::isEmpty() return mDocuments.empty(); } -void CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, - bool new_) +void CSMDoc::DocumentManager::addDocument( + const std::vector& files, const std::filesystem::path& savePath, bool new_) { - Document *document = makeDocument (files, savePath, new_); - insertDocument (document); + Document* document = makeDocument(files, savePath, new_); + insertDocument(document); } -CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( - const std::vector< boost::filesystem::path >& files, - const boost::filesystem::path& savePath, bool new_) +CSMDoc::Document* CSMDoc::DocumentManager::makeDocument( + const std::vector& files, const std::filesystem::path& savePath, bool new_) { - return new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mBlacklistedScripts, mFsStrict, mDataPaths, mArchives); + return new Document( + mConfiguration, files, new_, savePath, mResDir, mEncoding, mBlacklistedScripts, mDataPaths, mArchives); } -void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) +void CSMDoc::DocumentManager::insertDocument(CSMDoc::Document* document) { - mDocuments.push_back (document); + mDocuments.push_back(document); - connect (document, SIGNAL (mergeDone (CSMDoc::Document*)), - this, SLOT (insertDocument (CSMDoc::Document*))); + connect(document, SIGNAL(mergeDone(CSMDoc::Document*)), this, SLOT(insertDocument(CSMDoc::Document*))); - emit loadRequest (document); + emit loadRequest(document); mLoader.hasThingsToDo().wakeAll(); } -void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document) +void CSMDoc::DocumentManager::removeDocument(CSMDoc::Document* document) { - std::vector::iterator iter = std::find (mDocuments.begin(), mDocuments.end(), document); + std::vector::iterator iter = std::find(mDocuments.begin(), mDocuments.end(), document); - if (iter==mDocuments.end()) - throw std::runtime_error ("removing invalid document"); + if (iter == mDocuments.end()) + throw std::runtime_error("removing invalid document"); - emit documentAboutToBeRemoved (document); + emit documentAboutToBeRemoved(document); - mDocuments.erase (iter); + mDocuments.erase(iter); document->deleteLater(); if (mDocuments.empty()) emit lastDocumentDeleted(); } -void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& parResDir) +void CSMDoc::DocumentManager::setResourceDir(const std::filesystem::path& parResDir) { - mResDir = boost::filesystem::system_complete(parResDir); + mResDir = std::filesystem::absolute(parResDir); } -void CSMDoc::DocumentManager::setEncoding (ToUTF8::FromType encoding) +void CSMDoc::DocumentManager::setEncoding(ToUTF8::FromType encoding) { mEncoding = encoding; } -void CSMDoc::DocumentManager::setBlacklistedScripts (const std::vector& scriptIds) +void CSMDoc::DocumentManager::setBlacklistedScripts(const std::vector& scriptIds) { mBlacklistedScripts = scriptIds; } -void CSMDoc::DocumentManager::documentLoaded (Document *document) +void CSMDoc::DocumentManager::documentLoaded(Document* document) { - emit documentAdded (document); - emit loadingStopped (document, true, ""); + emit documentAdded(document); + emit loadingStopped(document, true, ""); } -void CSMDoc::DocumentManager::documentNotLoaded (Document *document, const std::string& error) +void CSMDoc::DocumentManager::documentNotLoaded(Document* document, const std::string& error) { - emit loadingStopped (document, false, error); + emit loadingStopped(document, false, error); if (error.empty()) // do not remove the document yet, if we have an error - removeDocument (document); + removeDocument(document); } -void CSMDoc::DocumentManager::setFileData(bool strict, const Files::PathContainer& dataPaths, const std::vector& archives) +void CSMDoc::DocumentManager::setFileData( + const Files::PathContainer& dataPaths, const std::vector& archives) { - mFsStrict = strict; mDataPaths = dataPaths; mArchives = archives; } diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index ecb2a1103..25f7d1d4f 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -1,25 +1,18 @@ #ifndef CSM_DOC_DOCUMENTMGR_H #define CSM_DOC_DOCUMENTMGR_H -#include -#include - -#include - #include #include -#include -#include +#include +#include +#include + #include +#include #include "loader.hpp" -namespace VFS -{ - class Manager; -} - namespace Files { struct ConfigurationManager; @@ -31,94 +24,90 @@ namespace CSMDoc class DocumentManager : public QObject { - Q_OBJECT + Q_OBJECT - std::vector mDocuments; - const Files::ConfigurationManager& mConfiguration; - QThread mLoaderThread; - Loader mLoader; - ToUTF8::FromType mEncoding; - std::vector mBlacklistedScripts; + std::vector mDocuments; + const Files::ConfigurationManager& mConfiguration; + QThread mLoaderThread; + Loader mLoader; + ToUTF8::FromType mEncoding; + std::vector mBlacklistedScripts; - boost::filesystem::path mResDir; + std::filesystem::path mResDir; - bool mFsStrict; - Files::PathContainer mDataPaths; - std::vector mArchives; + Files::PathContainer mDataPaths; + std::vector mArchives; - DocumentManager (const DocumentManager&); - DocumentManager& operator= (const DocumentManager&); + DocumentManager(const DocumentManager&); + DocumentManager& operator=(const DocumentManager&); - public: + public: + DocumentManager(const Files::ConfigurationManager& configuration); - DocumentManager (const Files::ConfigurationManager& configuration); + ~DocumentManager(); - ~DocumentManager(); + void addDocument( + const std::vector& files, const std::filesystem::path& savePath, bool new_); + ///< \param new_ Do not load the last content file in \a files and instead create in an + /// appropriate way. - void addDocument (const std::vector< boost::filesystem::path >& files, - const boost::filesystem::path& savePath, bool new_); - ///< \param new_ Do not load the last content file in \a files and instead create in an - /// appropriate way. + /// Create a new document. The ownership of the created document is transferred to + /// the calling function. The DocumentManager does not manage it. Loading has not + /// taken place at the point when the document is returned. + /// + /// \param new_ Do not load the last content file in \a files and instead create in an + /// appropriate way. + Document* makeDocument( + const std::vector& files, const std::filesystem::path& savePath, bool new_); - /// Create a new document. The ownership of the created document is transferred to - /// the calling function. The DocumentManager does not manage it. Loading has not - /// taken place at the point when the document is returned. - /// - /// \param new_ Do not load the last content file in \a files and instead create in an - /// appropriate way. - Document *makeDocument (const std::vector< boost::filesystem::path >& files, - const boost::filesystem::path& savePath, bool new_); + void setResourceDir(const std::filesystem::path& parResDir); - void setResourceDir (const boost::filesystem::path& parResDir); + void setEncoding(ToUTF8::FromType encoding); - void setEncoding (ToUTF8::FromType encoding); + void setBlacklistedScripts(const std::vector& scriptIds); - void setBlacklistedScripts (const std::vector& scriptIds); + /// Sets the file data that gets passed to newly created documents. + void setFileData(const Files::PathContainer& dataPaths, const std::vector& archives); - /// Sets the file data that gets passed to newly created documents. - void setFileData(bool strict, const Files::PathContainer& dataPaths, const std::vector& archives); + bool isEmpty(); - bool isEmpty(); + private slots: - private slots: + void documentLoaded(Document* document); + ///< The ownership of \a document is not transferred. - void documentLoaded (Document *document); - ///< The ownership of \a document is not transferred. + void documentNotLoaded(Document* document, const std::string& error); + ///< Document load has been interrupted either because of a call to abortLoading + /// or a problem during loading). In the former case error will be an empty string. - void documentNotLoaded (Document *document, const std::string& error); - ///< Document load has been interrupted either because of a call to abortLoading - /// or a problem during loading). In the former case error will be an empty string. + public slots: - public slots: + void removeDocument(CSMDoc::Document* document); + ///< Emits the lastDocumentDeleted signal, if applicable. - void removeDocument (CSMDoc::Document *document); - ///< Emits the lastDocumentDeleted signal, if applicable. + /// Hand over document to *this. The ownership is transferred. The DocumentManager + /// will initiate the load procedure, if necessary + void insertDocument(CSMDoc::Document* document); - /// Hand over document to *this. The ownership is transferred. The DocumentManager - /// will initiate the load procedure, if necessary - void insertDocument (CSMDoc::Document *document); + signals: - signals: + void documentAdded(CSMDoc::Document* document); - void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved(CSMDoc::Document* document); - void documentAboutToBeRemoved (CSMDoc::Document *document); + void loadRequest(CSMDoc::Document* document); - void loadRequest (CSMDoc::Document *document); + void lastDocumentDeleted(); - void lastDocumentDeleted(); + void loadingStopped(CSMDoc::Document* document, bool completed, const std::string& error); - void loadingStopped (CSMDoc::Document *document, bool completed, - const std::string& error); + void nextStage(CSMDoc::Document* document, const std::string& name, int totalRecords); - void nextStage (CSMDoc::Document *document, const std::string& name, - int totalRecords); + void nextRecord(CSMDoc::Document* document, int records); - void nextRecord (CSMDoc::Document *document, int records); + void cancelLoading(CSMDoc::Document* document); - void cancelLoading (CSMDoc::Document *document); - - void loadMessage (CSMDoc::Document *document, const std::string& message); + void loadMessage(CSMDoc::Document* document, const std::string& message); }; } diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 1c5a7348c..46dea447f 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -1,20 +1,35 @@ #include "loader.hpp" -#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include #include "../tools/reportmodel.hpp" #include "document.hpp" -CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLoaded (0), mRecordsLeft (false) {} - +CSMDoc::Loader::Stage::Stage() + : mFile(0) + , mRecordsLoaded(0) + , mRecordsLeft(false) +{ +} CSMDoc::Loader::Loader() : mShouldStop(false) { - mTimer = new QTimer (this); + mTimer = new QTimer(this); - connect (mTimer, SIGNAL (timeout()), this, SLOT (load())); + connect(mTimer, &QTimer::timeout, this, &Loader::load); mTimer->start(); } @@ -33,7 +48,7 @@ void CSMDoc::Loader::load() if (mDocuments.empty()) { mMutex.lock(); - mThingsToDo.wait (&mMutex); + mThingsToDo.wait(&mMutex); mMutex.unlock(); if (mShouldStop) @@ -42,12 +57,15 @@ void CSMDoc::Loader::load() return; } - std::vector >::iterator iter = mDocuments.begin(); + if (!mStart.has_value()) + mStart = std::chrono::steady_clock::now(); - Document *document = iter->first; + std::vector>::iterator iter = mDocuments.begin(); - int size = static_cast (document->getContentFiles().size()); - int editedIndex = size-1; // index of the file to be edited/created + Document* document = iter->first; + + int size = static_cast(document->getContentFiles().size()); + int editedIndex = size - 1; // index of the file to be edited/created if (document->isNew()) --size; @@ -58,10 +76,10 @@ void CSMDoc::Loader::load() { if (iter->second.mRecordsLeft) { - Messages messages (Message::Severity_Error); + Messages messages(Message::Severity_Error); const int batchingSize = 50; - for (int i=0; igetData().continueLoading (messages)) + for (int i = 0; i < batchingSize; ++i) // do not flood the system with update signals + if (document->getData().continueLoading(messages)) { iter->second.mRecordsLeft = false; break; @@ -69,42 +87,43 @@ void CSMDoc::Loader::load() else ++(iter->second.mRecordsLoaded); - CSMWorld::UniversalId log (CSMWorld::UniversalId::Type_LoadErrorLog, 0); + CSMWorld::UniversalId log(CSMWorld::UniversalId::Type_LoadErrorLog, 0); { // silence a g++ warning - for (CSMDoc::Messages::Iterator messageIter (messages.begin()); - messageIter!=messages.end(); ++messageIter) - { - document->getReport (log)->add (*messageIter); - emit loadMessage (document, messageIter->mMessage); - } + for (CSMDoc::Messages::Iterator messageIter(messages.begin()); messageIter != messages.end(); + ++messageIter) + { + document->getReport(log)->add(*messageIter); + emit loadMessage(document, messageIter->mMessage); + } } - emit nextRecord (document, iter->second.mRecordsLoaded); + emit nextRecord(document, iter->second.mRecordsLoaded); return; } - if (iter->second.mFilesecond.mFile < size) // start loading the files { - boost::filesystem::path path = document->getContentFiles()[iter->second.mFile]; + std::filesystem::path path = document->getContentFiles()[iter->second.mFile]; - int steps = document->getData().startLoading (path, iter->second.mFile!=editedIndex, false); + int steps = document->getData().startLoading(path, iter->second.mFile != editedIndex, /*project*/ false); iter->second.mRecordsLeft = true; iter->second.mRecordsLoaded = 0; - emit nextStage (document, path.filename().string(), steps); + emit nextStage(document, Files::pathToUnicodeString(path.filename()), steps); } - else if (iter->second.mFile==size) + else if (iter->second.mFile == size) // start loading the last (project) file { - int steps = document->getData().startLoading (document->getProjectPath(), false, true); + int steps = document->getData().startLoading(document->getProjectPath(), /*base*/ false, true); iter->second.mRecordsLeft = true; iter->second.mRecordsLoaded = 0; - emit nextStage (document, "Project File", steps); + emit nextStage(document, "Project File", steps); } else { + document->getData().finishLoading(); done = true; } @@ -112,32 +131,39 @@ void CSMDoc::Loader::load() } catch (const std::exception& e) { - mDocuments.erase (iter); - emit documentNotLoaded (document, e.what()); + mDocuments.erase(iter); + emit documentNotLoaded(document, e.what()); return; } if (done) { - mDocuments.erase (iter); - emit documentLoaded (document); + if (mStart.has_value()) + { + const auto duration = std::chrono::steady_clock::now() - *mStart; + Log(Debug::Verbose) << "Loaded content files in " + << std::chrono::duration_cast>(duration).count() << 's'; + mStart.reset(); + } + + mDocuments.erase(iter); + emit documentLoaded(document); } } -void CSMDoc::Loader::loadDocument (CSMDoc::Document *document) +void CSMDoc::Loader::loadDocument(CSMDoc::Document* document) { - mDocuments.emplace_back (document, Stage()); + mDocuments.emplace_back(document, Stage()); } -void CSMDoc::Loader::abortLoading (CSMDoc::Document *document) +void CSMDoc::Loader::abortLoading(CSMDoc::Document* document) { - for (std::vector >::iterator iter = mDocuments.begin(); - iter!=mDocuments.end(); ++iter) + for (std::vector>::iterator iter = mDocuments.begin(); iter != mDocuments.end(); ++iter) { - if (iter->first==document) + if (iter->first == document) { - mDocuments.erase (iter); - emit documentNotLoaded (document, ""); + mDocuments.erase(iter); + emit documentNotLoaded(document, ""); break; } } diff --git a/apps/opencs/model/doc/loader.hpp b/apps/opencs/model/doc/loader.hpp index ce5bc5848..ccf493d19 100644 --- a/apps/opencs/model/doc/loader.hpp +++ b/apps/opencs/model/doc/loader.hpp @@ -1,78 +1,83 @@ #ifndef CSM_DOC_LOADER_H #define CSM_DOC_LOADER_H +#include +#include +#include +#include #include -#include #include -#include +#include #include +class QTimer; + namespace CSMDoc { class Document; class Loader : public QObject { - Q_OBJECT + Q_OBJECT - struct Stage - { - int mFile; - int mRecordsLoaded; - bool mRecordsLeft; + struct Stage + { + int mFile; + int mRecordsLoaded; + bool mRecordsLeft; - Stage(); - }; + Stage(); + }; - QMutex mMutex; - QWaitCondition mThingsToDo; - std::vector > mDocuments; + QMutex mMutex; + QWaitCondition mThingsToDo; + std::vector> mDocuments; - QTimer* mTimer; - bool mShouldStop; + QTimer* mTimer; + bool mShouldStop; - public: + std::optional mStart; - Loader(); + public: + Loader(); - QWaitCondition& hasThingsToDo(); + QWaitCondition& hasThingsToDo(); - void stop(); + void stop(); - private slots: + private slots: - void load(); + void load(); - public slots: + public slots: - void loadDocument (CSMDoc::Document *document); - ///< The ownership of \a document is not transferred. + void loadDocument(CSMDoc::Document* document); + ///< The ownership of \a document is not transferred. - void abortLoading (CSMDoc::Document *document); - ///< Abort loading \a docuemnt (ignored if \a document has already finished being - /// loaded). Will result in a documentNotLoaded signal, once the Loader has finished - /// cleaning up. + void abortLoading(CSMDoc::Document* document); + ///< Abort loading \a docuemnt (ignored if \a document has already finished being + /// loaded). Will result in a documentNotLoaded signal, once the Loader has finished + /// cleaning up. - signals: + signals: - void documentLoaded (Document *document); - ///< The ownership of \a document is not transferred. + void documentLoaded(Document* document); + ///< The ownership of \a document is not transferred. - void documentNotLoaded (Document *document, const std::string& error); - ///< Document load has been interrupted either because of a call to abortLoading - /// or a problem during loading). In the former case error will be an empty string. + void documentNotLoaded(Document* document, const std::string& error); + ///< Document load has been interrupted either because of a call to abortLoading + /// or a problem during loading). In the former case error will be an empty string. - void nextStage (CSMDoc::Document *document, const std::string& name, - int totalRecords); + void nextStage(CSMDoc::Document* document, const std::string& name, int totalRecords); - void nextRecord (CSMDoc::Document *document, int records); - ///< \note This signal is only given once per group of records. The group size is - /// approximately the total number of records divided by the steps value of the - /// previous nextStage signal. + void nextRecord(CSMDoc::Document* document, int records); + ///< \note This signal is only given once per group of records. The group size is + /// approximately the total number of records divided by the steps value of the + /// previous nextStage signal. - void loadMessage (CSMDoc::Document *document, const std::string& message); - ///< Non-critical load error or warning + void loadMessage(CSMDoc::Document* document, const std::string& message); + ///< Non-critical load error or warning }; } diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index b70d44eda..bf28ac1c9 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -1,38 +1,52 @@ #include "messages.hpp" -CSMDoc::Message::Message() : mSeverity(Severity_Default){} +#include -CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, Severity severity) -: mId (id), mMessage (message), mHint (hint), mSeverity (severity) -{} +CSMDoc::Message::Message() + : mSeverity(Severity_Default) +{ +} -std::string CSMDoc::Message::toString (Severity severity) +CSMDoc::Message::Message( + const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity) + : mId(id) + , mMessage(message) + , mHint(hint) + , mSeverity(severity) +{ +} + +std::string CSMDoc::Message::toString(Severity severity) { switch (severity) { - case CSMDoc::Message::Severity_Info: return "Information"; - case CSMDoc::Message::Severity_Warning: return "Warning"; - case CSMDoc::Message::Severity_Error: return "Error"; - case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; - case CSMDoc::Message::Severity_Default: break; + case CSMDoc::Message::Severity_Info: + return "Information"; + case CSMDoc::Message::Severity_Warning: + return "Warning"; + case CSMDoc::Message::Severity_Error: + return "Error"; + case CSMDoc::Message::Severity_SeriousError: + return "Serious Error"; + case CSMDoc::Message::Severity_Default: + break; } return ""; } - -CSMDoc::Messages::Messages (Message::Severity default_) -: mDefault (default_) -{} - -void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, Message::Severity severity) +CSMDoc::Messages::Messages(Message::Severity default_) + : mDefault(default_) { - if (severity==Message::Severity_Default) +} + +void CSMDoc::Messages::add( + const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Message::Severity severity) +{ + if (severity == Message::Severity_Default) severity = mDefault; - mMessages.push_back (Message (id, message, hint, severity)); + mMessages.push_back(Message(id, message, hint, severity)); } CSMDoc::Messages::Iterator CSMDoc::Messages::begin() const diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp index 671ded82a..7715a89b3 100644 --- a/apps/opencs/model/doc/messages.hpp +++ b/apps/opencs/model/doc/messages.hpp @@ -1,11 +1,12 @@ #ifndef CSM_DOC_MESSAGES_H #define CSM_DOC_MESSAGES_H +#include + +#include #include #include -#include - #include "../world/universalid.hpp" namespace CSMDoc @@ -14,9 +15,9 @@ namespace CSMDoc { enum Severity { - Severity_Info = 0, // no problem - Severity_Warning = 1, // a potential problem, but we are probably fine - Severity_Error = 2, // an error; we are not fine + Severity_Info = 0, // no problem + Severity_Warning = 1, // a potential problem, but we are probably fine + Severity_Error = 2, // an error; we are not fine Severity_SeriousError = 3, // an error so bad we can't even be sure if we are // reporting it correctly Severity_Default = 4 @@ -29,39 +30,35 @@ namespace CSMDoc Message(); - Message (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, Severity severity); + Message( + const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity); - static std::string toString (Severity severity); + static std::string toString(Severity severity); }; class Messages { - public: + public: + typedef std::vector Collection; - typedef std::vector Collection; + typedef Collection::const_iterator Iterator; - typedef Collection::const_iterator Iterator; + private: + Collection mMessages; + Message::Severity mDefault; - private: + public: + Messages(Message::Severity default_); - Collection mMessages; - Message::Severity mDefault; + void add(const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint = "", + Message::Severity severity = Message::Severity_Default); - public: + Iterator begin() const; - Messages (Message::Severity default_); - - void add (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint = "", - Message::Severity severity = Message::Severity_Default); - - Iterator begin() const; - - Iterator end() const; + Iterator end() const; }; } -Q_DECLARE_METATYPE (CSMDoc::Message) +Q_DECLARE_METATYPE(CSMDoc::Message) #endif diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 369c6bb10..4ba9f8ef8 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -1,14 +1,45 @@ #include "operation.hpp" -#include +#include +#include #include #include +#include + +#include + #include "../world/universalid.hpp" #include "stage.hpp" +namespace CSMDoc +{ + namespace + { + std::string_view operationToString(State value) + { + switch (value) + { + case State_Saving: + return "Saving"; + case State_Merging: + return "Merging"; + case State_Verifying: + return "Verifying"; + case State_Searching: + return "Searching"; + case State_Loading: + return "Loading"; + default: + break; + } + return "Unknown"; + } + } +} + void CSMDoc::Operation::prepareStages() { mCurrentStage = mStages.begin(); @@ -17,25 +48,33 @@ void CSMDoc::Operation::prepareStages() mTotalSteps = 0; mError = false; - for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) + for (std::vector>::iterator iter(mStages.begin()); iter != mStages.end(); ++iter) { iter->second = iter->first->setup(); mTotalSteps += iter->second; } } -CSMDoc::Operation::Operation (int type, bool ordered, bool finalAlways) -: mType (type), mStages(std::vector >()), mCurrentStage(mStages.begin()), - mCurrentStep(0), mCurrentStepTotal(0), mTotalSteps(0), mOrdered (ordered), - mFinalAlways (finalAlways), mError(false), mConnected (false), mPrepared (false), - mDefaultSeverity (Message::Severity_Error) +CSMDoc::Operation::Operation(State type, bool ordered, bool finalAlways) + : mType(type) + , mStages(std::vector>()) + , mCurrentStage(mStages.begin()) + , mCurrentStep(0) + , mCurrentStepTotal(0) + , mTotalSteps(0) + , mOrdered(ordered) + , mFinalAlways(finalAlways) + , mError(false) + , mConnected(false) + , mPrepared(false) + , mDefaultSeverity(Message::Severity_Error) { - mTimer = new QTimer (this); + mTimer = new QTimer(this); } CSMDoc::Operation::~Operation() { - for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) + for (std::vector>::iterator iter(mStages.begin()); iter != mStages.end(); ++iter) delete iter->first; } @@ -45,21 +84,22 @@ void CSMDoc::Operation::run() if (!mConnected) { - connect (mTimer, SIGNAL (timeout()), this, SLOT (executeStage())); + connect(mTimer, &QTimer::timeout, this, &Operation::executeStage); mConnected = true; } mPrepared = false; + mStart = std::chrono::steady_clock::now(); - mTimer->start (0); + mTimer->start(0); } -void CSMDoc::Operation::appendStage (Stage *stage) +void CSMDoc::Operation::appendStage(Stage* stage) { - mStages.emplace_back (stage, 0); + mStages.emplace_back(stage, 0); } -void CSMDoc::Operation::setDefaultSeverity (Message::Severity severity) +void CSMDoc::Operation::setDefaultSeverity(Message::Severity severity) { mDefaultSeverity = severity; } @@ -78,7 +118,7 @@ void CSMDoc::Operation::abort() if (mFinalAlways) { - if (mStages.begin()!=mStages.end() && mCurrentStage!=--mStages.end()) + if (mStages.begin() != mStages.end() && mCurrentStage != --mStages.end()) { mCurrentStep = 0; mCurrentStage = --mStages.end(); @@ -96,11 +136,11 @@ void CSMDoc::Operation::executeStage() mPrepared = true; } - Messages messages (mDefaultSeverity); + Messages messages(mDefaultSeverity); - while (mCurrentStage!=mStages.end()) + while (mCurrentStage != mStages.end()) { - if (mCurrentStep>=mCurrentStage->second) + if (mCurrentStep >= mCurrentStage->second) { mCurrentStep = 0; ++mCurrentStage; @@ -109,11 +149,12 @@ void CSMDoc::Operation::executeStage() { try { - mCurrentStage->first->perform (mCurrentStep++, messages); + mCurrentStage->first->perform(mCurrentStep++, messages); } catch (const std::exception& e) { - emit reportMessage (Message (CSMWorld::UniversalId(), e.what(), "", Message::Severity_SeriousError), mType); + emit reportMessage( + Message(CSMWorld::UniversalId(), e.what(), "", Message::Severity_SeriousError), mType); abort(); } @@ -122,17 +163,27 @@ void CSMDoc::Operation::executeStage() } } - emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); + emit progress(mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); - for (Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) - emit reportMessage (*iter, mType); + for (Messages::Iterator iter(messages.begin()); iter != messages.end(); ++iter) + emit reportMessage(*iter, mType); + + if (mCurrentStage == mStages.end()) + { + if (mStart.has_value()) + { + const auto duration = std::chrono::steady_clock::now() - *mStart; + Log(Debug::Verbose) << operationToString(mType) << " operation is completed in " + << std::chrono::duration_cast>(duration).count() << 's'; + mStart.reset(); + } - if (mCurrentStage==mStages.end()) operationDone(); + } } void CSMDoc::Operation::operationDone() { mTimer->stop(); - emit done (mType, mError); + emit done(mType, mError); } diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index ff396fa3c..f569ffa25 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -1,19 +1,17 @@ #ifndef CSM_DOC_OPERATION_H #define CSM_DOC_OPERATION_H +#include +#include +#include #include -#include #include -#include -#include #include "messages.hpp" +#include "state.hpp" -namespace CSMWorld -{ - class UniversalId; -} +class QTimer; namespace CSMDoc { @@ -21,63 +19,63 @@ namespace CSMDoc class Operation : public QObject { - Q_OBJECT + Q_OBJECT - int mType; - std::vector > mStages; // stage, number of steps - std::vector >::iterator mCurrentStage; - int mCurrentStep; - int mCurrentStepTotal; - int mTotalSteps; - int mOrdered; - bool mFinalAlways; - bool mError; - bool mConnected; - QTimer *mTimer; - bool mPrepared; - Message::Severity mDefaultSeverity; + State mType; + std::vector> mStages; // stage, number of steps + std::vector>::iterator mCurrentStage; + int mCurrentStep; + int mCurrentStepTotal; + int mTotalSteps; + int mOrdered; + bool mFinalAlways; + bool mError; + bool mConnected; + QTimer* mTimer; + bool mPrepared; + Message::Severity mDefaultSeverity; + std::optional mStart; - void prepareStages(); + void prepareStages(); - public: + public: + Operation(State type, bool ordered, bool finalAlways = false); + ///< \param ordered Stages must be executed in the given order. + /// \param finalAlways Execute last stage even if an error occurred during earlier stages. - Operation (int type, bool ordered, bool finalAlways = false); - ///< \param ordered Stages must be executed in the given order. - /// \param finalAlways Execute last stage even if an error occurred during earlier stages. + virtual ~Operation(); - virtual ~Operation(); + void appendStage(Stage* stage); + ///< The ownership of \a stage is transferred to *this. + /// + /// \attention Do no call this function while this Operation is running. - void appendStage (Stage *stage); - ///< The ownership of \a stage is transferred to *this. - /// - /// \attention Do no call this function while this Operation is running. + /// \attention Do no call this function while this Operation is running. + void setDefaultSeverity(Message::Severity severity); - /// \attention Do no call this function while this Operation is running. - void setDefaultSeverity (Message::Severity severity); + bool hasError() const; - bool hasError() const; + signals: - signals: + void progress(int current, int max, int type); - void progress (int current, int max, int type); + void reportMessage(const CSMDoc::Message& message, int type); - void reportMessage (const CSMDoc::Message& message, int type); + void done(int type, bool failed); - void done (int type, bool failed); + public slots: - public slots: + void abort(); - void abort(); + void run(); - void run(); + private slots: - private slots: + void executeStage(); - void executeStage(); + protected slots: - protected slots: - - virtual void operationDone(); + virtual void operationDone(); }; } diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index 0fd2bef95..6beab1fd9 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -2,34 +2,28 @@ #include "operation.hpp" -CSMDoc::OperationHolder::OperationHolder (Operation *operation) +CSMDoc::OperationHolder::OperationHolder(Operation* operation) : mOperation(nullptr) - , mRunning (false) + , mRunning(false) { if (operation) - setOperation (operation); + setOperation(operation); } -void CSMDoc::OperationHolder::setOperation (Operation *operation) +void CSMDoc::OperationHolder::setOperation(Operation* operation) { mOperation = operation; - mOperation->moveToThread (&mThread); + mOperation->moveToThread(&mThread); - connect ( - mOperation, SIGNAL (progress (int, int, int)), - this, SIGNAL (progress (int, int, int))); + connect(mOperation, &Operation::progress, this, &OperationHolder::progress); - connect ( - mOperation, SIGNAL (reportMessage (const CSMDoc::Message&, int)), - this, SIGNAL (reportMessage (const CSMDoc::Message&, int))); + connect(mOperation, &Operation::reportMessage, this, &OperationHolder::reportMessage); - connect ( - mOperation, SIGNAL (done (int, bool)), - this, SLOT (doneSlot (int, bool))); + connect(mOperation, &Operation::done, this, &OperationHolder::doneSlot); - connect (this, SIGNAL (abortSignal()), mOperation, SLOT (abort())); + connect(this, &OperationHolder::abortSignal, mOperation, &Operation::abort); - connect (&mThread, SIGNAL (started()), mOperation, SLOT (run())); + connect(&mThread, &QThread::started, mOperation, &Operation::run); } bool CSMDoc::OperationHolder::isRunning() const @@ -58,9 +52,9 @@ void CSMDoc::OperationHolder::abortAndWait() } } -void CSMDoc::OperationHolder::doneSlot (int type, bool failed) +void CSMDoc::OperationHolder::doneSlot(int type, bool failed) { mRunning = false; mThread.quit(); - emit done (type, failed); + emit done(type, failed); } diff --git a/apps/opencs/model/doc/operationholder.hpp b/apps/opencs/model/doc/operationholder.hpp index 69af6ed66..8056850b9 100644 --- a/apps/opencs/model/doc/operationholder.hpp +++ b/apps/opencs/model/doc/operationholder.hpp @@ -4,53 +4,46 @@ #include #include -#include "messages.hpp" - -namespace CSMWorld -{ - class UniversalId; -} - namespace CSMDoc { class Operation; + struct Message; class OperationHolder : public QObject { - Q_OBJECT - - QThread mThread; - Operation *mOperation; - bool mRunning; + Q_OBJECT - public: + QThread mThread; + Operation* mOperation; + bool mRunning; - OperationHolder (Operation *operation = nullptr); + public: + OperationHolder(Operation* operation = nullptr); - void setOperation (Operation *operation); + void setOperation(Operation* operation); - bool isRunning() const; + bool isRunning() const; - void start(); + void start(); - void abort(); + void abort(); - // Abort and wait until thread has finished. - void abortAndWait(); + // Abort and wait until thread has finished. + void abortAndWait(); - private slots: + private slots: - void doneSlot (int type, bool failed); - - signals: + void doneSlot(int type, bool failed); - void progress (int current, int max, int type); + signals: - void reportMessage (const CSMDoc::Message& message, int type); + void progress(int current, int max, int type); - void done (int type, bool failed); + void reportMessage(const CSMDoc::Message& message, int type); - void abortSignal(); + void done(int type, bool failed); + + void abortSignal(); }; } diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 927bda080..0099cb2f9 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -1,22 +1,32 @@ #include "runner.hpp" -#include +#include + +#if defined(Q_OS_MAC) +#include #include +#endif + +#include +#include +#include #include #include +#include + #include "operationholder.hpp" -CSMDoc::Runner::Runner (const boost::filesystem::path& projectPath) -: mRunning (false), mStartup (nullptr), mProjectPath (projectPath) +CSMDoc::Runner::Runner(std::filesystem::path projectPath) + : mRunning(false) + , mStartup(nullptr) + , mProjectPath(std::move(projectPath)) { - connect (&mProcess, SIGNAL (finished (int, QProcess::ExitStatus)), - this, SLOT (finished (int, QProcess::ExitStatus))); + connect(&mProcess, qOverload(&QProcess::finished), this, &Runner::finished); - connect (&mProcess, SIGNAL (readyReadStandardOutput()), - this, SLOT (readyReadStandardOutput())); + connect(&mProcess, &QProcess::readyReadStandardOutput, this, &Runner::readyReadStandardOutput); - mProcess.setProcessChannelMode (QProcess::MergedChannels); + mProcess.setProcessChannelMode(QProcess::MergedChannels); mProfile.blank(); } @@ -25,13 +35,13 @@ CSMDoc::Runner::~Runner() { if (mRunning) { - disconnect (&mProcess, nullptr, this, nullptr); + disconnect(&mProcess, nullptr, this, nullptr); mProcess.kill(); mProcess.waitForFinished(); } } -void CSMDoc::Runner::start (bool delayed) +void CSMDoc::Runner::start(bool delayed) { if (mStartup) { @@ -56,16 +66,16 @@ void CSMDoc::Runner::start (bool delayed) path.prepend(QString("./")); #endif - mStartup = new QTemporaryFile (this); + mStartup = new QTemporaryFile(this); mStartup->open(); { - QTextStream stream (mStartup); + QTextStream stream(mStartup); if (!mStartupInstruction.empty()) - stream << QString::fromUtf8 (mStartupInstruction.c_str()) << '\n'; + stream << QString::fromUtf8(mStartupInstruction.c_str()) << '\n'; - stream << QString::fromUtf8 (mProfile.mScriptText.c_str()); + stream << QString::fromUtf8(mProfile.mScriptText.c_str()); } mStartup->close(); @@ -78,23 +88,21 @@ void CSMDoc::Runner::start (bool delayed) else arguments << "--new-game=1"; - arguments << ("--script-run="+mStartup->fileName());; + arguments << ("--script-run=" + mStartup->fileName()); - arguments << - QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str()); + arguments << "--data=\"" + Files::pathToQString(mProjectPath.parent_path()) + "\""; arguments << "--replace=content"; + arguments << "--content=builtin.omwscripts"; - for (std::vector::const_iterator iter (mContentFiles.begin()); - iter!=mContentFiles.end(); ++iter) + for (const auto& mContentFile : mContentFiles) { - arguments << QString::fromUtf8 (("--content="+*iter).c_str()); + arguments << "--content=" + Files::pathToQString(mContentFile); } - arguments - << QString::fromUtf8 (("--content="+mProjectPath.filename().string()).c_str()); + arguments << "--content=" + Files::pathToQString(mProjectPath.filename()); - mProcess.start (path, arguments); + mProcess.start(path, arguments); } mRunning = true; @@ -106,7 +114,7 @@ void CSMDoc::Runner::stop() delete mStartup; mStartup = nullptr; - if (mProcess.state()==QProcess::NotRunning) + if (mProcess.state() == QProcess::NotRunning) { mRunning = false; emit runStateChanged(); @@ -120,39 +128,38 @@ bool CSMDoc::Runner::isRunning() const return mRunning; } -void CSMDoc::Runner::configure (const ESM::DebugProfile& profile, - const std::vector& contentFiles, const std::string& startupInstruction) +void CSMDoc::Runner::configure(const ESM::DebugProfile& profile, const std::vector& contentFiles, + const std::string& startupInstruction) { mProfile = profile; mContentFiles = contentFiles; mStartupInstruction = startupInstruction; } -void CSMDoc::Runner::finished (int exitCode, QProcess::ExitStatus exitStatus) +void CSMDoc::Runner::finished(int exitCode, QProcess::ExitStatus exitStatus) { mRunning = false; emit runStateChanged(); } -QTextDocument *CSMDoc::Runner::getLog() +QTextDocument* CSMDoc::Runner::getLog() { return &mLog; } void CSMDoc::Runner::readyReadStandardOutput() { - mLog.setPlainText ( - mLog.toPlainText() + QString::fromUtf8 (mProcess.readAllStandardOutput())); + mLog.setPlainText(mLog.toPlainText() + QString::fromUtf8(mProcess.readAllStandardOutput())); } - -CSMDoc::SaveWatcher::SaveWatcher (Runner *runner, OperationHolder *operation) -: QObject (runner), mRunner (runner) +CSMDoc::SaveWatcher::SaveWatcher(Runner* runner, OperationHolder* operation) + : QObject(runner) + , mRunner(runner) { - connect (operation, SIGNAL (done (int, bool)), this, SLOT (saveDone (int, bool))); + connect(operation, &OperationHolder::done, this, &SaveWatcher::saveDone); } -void CSMDoc::SaveWatcher::saveDone (int type, bool failed) +void CSMDoc::SaveWatcher::saveDone(int type, bool failed) { if (failed) mRunner->stop(); diff --git a/apps/opencs/model/doc/runner.hpp b/apps/opencs/model/doc/runner.hpp index 517122492..b94caca38 100644 --- a/apps/opencs/model/doc/runner.hpp +++ b/apps/opencs/model/doc/runner.hpp @@ -1,86 +1,81 @@ #ifndef CSM_DOC_RUNNER_H #define CSM_DOC_RUNNER_H -#include #include - -#include +#include #include #include #include -#include +#include + +#include class QTemporaryFile; namespace CSMDoc { class OperationHolder; - + class Runner : public QObject { - Q_OBJECT + Q_OBJECT - QProcess mProcess; - bool mRunning; - ESM::DebugProfile mProfile; - std::vector mContentFiles; - std::string mStartupInstruction; - QTemporaryFile *mStartup; - QTextDocument mLog; - boost::filesystem::path mProjectPath; + QProcess mProcess; + bool mRunning; + ESM::DebugProfile mProfile; + std::vector mContentFiles; + std::string mStartupInstruction; + QTemporaryFile* mStartup; + QTextDocument mLog; + std::filesystem::path mProjectPath; - public: + public: + Runner(std::filesystem::path projectPath); - Runner (const boost::filesystem::path& projectPath); + ~Runner(); - ~Runner(); + /// \param delayed Flag as running but do not start the OpenMW process yet (the + /// process must be started by another call of start with delayed==false) + void start(bool delayed = false); - /// \param delayed Flag as running but do not start the OpenMW process yet (the - /// process must be started by another call of start with delayed==false) - void start (bool delayed = false); + void stop(); - void stop(); + /// \note Running state is entered when the start function is called. This + /// is not necessarily identical to the moment the child process is started. + bool isRunning() const; - /// \note Running state is entered when the start function is called. This - /// is not necessarily identical to the moment the child process is started. - bool isRunning() const; + void configure(const ESM::DebugProfile& profile, const std::vector& contentFiles, + const std::string& startupInstruction); - void configure (const ESM::DebugProfile& profile, - const std::vector& contentFiles, - const std::string& startupInstruction); + QTextDocument* getLog(); - QTextDocument *getLog(); + signals: - signals: + void runStateChanged(); - void runStateChanged(); + private slots: - private slots: + void finished(int exitCode, QProcess::ExitStatus exitStatus); - void finished (int exitCode, QProcess::ExitStatus exitStatus); - - void readyReadStandardOutput(); + void readyReadStandardOutput(); }; - class Operation; - /// \brief Watch for end of save operation and restart or stop runner class SaveWatcher : public QObject { - Q_OBJECT + Q_OBJECT - Runner *mRunner; + Runner* mRunner; - public: + public: + /// *this attaches itself to runner + SaveWatcher(Runner* runner, OperationHolder* operation); - /// *this attaches itself to runner - SaveWatcher (Runner *runner, OperationHolder *operation); + private slots: - private slots: - - void saveDone (int type, bool failed); + void saveDone(int type, bool failed); }; } diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 95a2feaf2..b2e4d4649 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -1,106 +1,128 @@ #include "saving.hpp" +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "../world/data.hpp" #include "../world/idcollection.hpp" -#include "state.hpp" -#include "savingstages.hpp" #include "document.hpp" +#include "savingstages.hpp" +#include "state.hpp" -CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& projectPath, - ToUTF8::FromType encoding) -: Operation (State_Saving, true, true), mDocument (document), mState (*this, projectPath, encoding) +CSMDoc::Saving::Saving(Document& document, const std::filesystem::path& projectPath, ToUTF8::FromType encoding) + : Operation(State_Saving, true, true) + , mDocument(document) + , mState(*this, projectPath, encoding) { // save project file - appendStage (new OpenSaveStage (mDocument, mState, true)); + appendStage(new OpenSaveStage(mDocument, mState, true)); - appendStage (new WriteHeaderStage (mDocument, mState, true)); + appendStage(new WriteHeaderStage(mDocument, mState, true)); - appendStage (new WriteCollectionStage > ( + appendStage(new WriteCollectionStage>( mDocument.getData().getFilters(), mState, CSMWorld::Scope_Project)); - appendStage (new WriteCollectionStage > ( + appendStage(new WriteCollectionStage>( mDocument.getData().getDebugProfiles(), mState, CSMWorld::Scope_Project)); - appendStage (new WriteCollectionStage > ( + appendStage(new WriteCollectionStage>( mDocument.getData().getScripts(), mState, CSMWorld::Scope_Project)); - appendStage (new CloseSaveStage (mState)); + appendStage(new CloseSaveStage(mState)); // save content file - appendStage (new OpenSaveStage (mDocument, mState, false)); + appendStage(new OpenSaveStage(mDocument, mState, false)); - appendStage (new WriteHeaderStage (mDocument, mState, false)); + appendStage(new WriteHeaderStage(mDocument, mState, false)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getGlobals(), mState)); + appendStage( + new WriteCollectionStage>(mDocument.getData().getGlobals(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getGmsts(), mState)); + appendStage( + new WriteCollectionStage>(mDocument.getData().getGmsts(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getSkills(), mState)); + appendStage(new WriteCollectionStage>(mDocument.getData().getSkills(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getClasses(), mState)); + appendStage(new WriteCollectionStage>(mDocument.getData().getClasses(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getFactions(), mState)); + appendStage( + new WriteCollectionStage>(mDocument.getData().getFactions(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getRaces(), mState)); + appendStage(new WriteCollectionStage>(mDocument.getData().getRaces(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getSounds(), mState)); + appendStage(new WriteCollectionStage>(mDocument.getData().getSounds(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getScripts(), mState)); + appendStage( + new WriteCollectionStage>(mDocument.getData().getScripts(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getRegions(), mState)); + appendStage( + new WriteCollectionStage>(mDocument.getData().getRegions(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getBirthsigns(), mState)); + appendStage( + new WriteCollectionStage>(mDocument.getData().getBirthsigns(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getSpells(), mState)); + appendStage(new WriteCollectionStage>(mDocument.getData().getSpells(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getEnchantments(), mState)); + appendStage(new WriteCollectionStage>( + mDocument.getData().getEnchantments(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getBodyParts(), mState)); + appendStage( + new WriteCollectionStage>(mDocument.getData().getBodyParts(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getSoundGens(), mState)); + appendStage(new WriteCollectionStage>( + mDocument.getData().getSoundGens(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getMagicEffects(), mState)); + appendStage(new WriteCollectionStage>( + mDocument.getData().getMagicEffects(), mState)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getStartScripts(), mState)); + appendStage(new WriteCollectionStage>( + mDocument.getData().getStartScripts(), mState)); - appendStage (new WriteRefIdCollectionStage (mDocument, mState)); + appendStage(new WriteRefIdCollectionStage(mDocument, mState)); - appendStage (new CollectionReferencesStage (mDocument, mState)); + appendStage(new CollectionReferencesStage(mDocument, mState)); - appendStage (new WriteCellCollectionStage (mDocument, mState)); + appendStage(new WriteCellCollectionStage(mDocument, mState)); // Dialogue can reference objects and cells so must be written after these records for vanilla-compatible files - appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); + appendStage(new WriteDialogueCollectionStage(mDocument, mState, false)); - appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); + appendStage(new WriteDialogueCollectionStage(mDocument, mState, true)); - appendStage (new WritePathgridCollectionStage (mDocument, mState)); + appendStage(new WritePathgridCollectionStage(mDocument, mState)); - appendStage (new WriteLandTextureCollectionStage (mDocument, mState)); + appendStage(new WriteLandTextureCollectionStage(mDocument, mState)); // references Land Textures - appendStage (new WriteLandCollectionStage (mDocument, mState)); + appendStage(new WriteLandCollectionStage(mDocument, mState)); // close file and clean up - appendStage (new CloseSaveStage (mState)); + appendStage(new CloseSaveStage(mState)); - appendStage (new FinalSavingStage (mDocument, mState)); + appendStage(new FinalSavingStage(mDocument, mState)); } diff --git a/apps/opencs/model/doc/saving.hpp b/apps/opencs/model/doc/saving.hpp index 44239b21b..5dcdbb680 100644 --- a/apps/opencs/model/doc/saving.hpp +++ b/apps/opencs/model/doc/saving.hpp @@ -1,29 +1,28 @@ #ifndef CSM_DOC_SAVING_H #define CSM_DOC_SAVING_H -#include +#include #include #include "operation.hpp" #include "savingstate.hpp" +#include + namespace CSMDoc { class Document; class Saving : public Operation { - Q_OBJECT + Q_OBJECT - Document& mDocument; - SavingState mState; - - public: - - Saving (Document& document, const boost::filesystem::path& projectPath, - ToUTF8::FromType encoding); + Document& mDocument; + SavingState mState; + public: + Saving(Document& document, const std::filesystem::path& projectPath, ToUTF8::FromType encoding); }; } diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 44698cd2e..82135e004 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -1,48 +1,81 @@ #include "savingstages.hpp" -#include - #include -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "../world/infocollection.hpp" #include "../world/cellcoordinates.hpp" #include "document.hpp" -CSMDoc::OpenSaveStage::OpenSaveStage (Document& document, SavingState& state, bool projectFile) -: mDocument (document), mState (state), mProjectFile (projectFile) -{} +CSMDoc::OpenSaveStage::OpenSaveStage(Document& document, SavingState& state, bool projectFile) + : mDocument(document) + , mState(state) + , mProjectFile(projectFile) +{ +} int CSMDoc::OpenSaveStage::setup() { return 1; } -void CSMDoc::OpenSaveStage::perform (int stage, Messages& messages) +void CSMDoc::OpenSaveStage::perform(int stage, Messages& messages) { - mState.start (mDocument, mProjectFile); + mState.start(mDocument, mProjectFile); - mState.getStream().open ( - mProjectFile ? mState.getPath() : mState.getTmpPath(), - std::ios::binary); + mState.getStream().open(mProjectFile ? mState.getPath() : mState.getTmpPath(), std::ios::binary); if (!mState.getStream().is_open()) - throw std::runtime_error ("failed to open stream for saving"); + throw std::runtime_error("failed to open stream for saving"); } - -CSMDoc::WriteHeaderStage::WriteHeaderStage (Document& document, SavingState& state, bool simple) -: mDocument (document), mState (state), mSimple (simple) -{} +CSMDoc::WriteHeaderStage::WriteHeaderStage(Document& document, SavingState& state, bool simple) + : mDocument(document) + , mState(state) + , mSimple(simple) +{ +} int CSMDoc::WriteHeaderStage::setup() { return 1; } -void CSMDoc::WriteHeaderStage::perform (int stage, Messages& messages) +void CSMDoc::WriteHeaderStage::perform(int stage, Messages& messages) { mState.getWriter().setVersion(); @@ -50,53 +83,54 @@ void CSMDoc::WriteHeaderStage::perform (int stage, Messages& messages) if (mSimple) { - mState.getWriter().setAuthor (""); - mState.getWriter().setDescription (""); - mState.getWriter().setRecordCount (0); - mState.getWriter().setFormat (ESM::Header::CurrentFormat); + mState.getWriter().setAuthor(""); + mState.getWriter().setDescription(""); + mState.getWriter().setRecordCount(0); + + // ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs + // we use the format `0` for compatibility with old versions. + mState.getWriter().setFormatVersion(ESM::DefaultFormatVersion); } else { - mDocument.getData().getMetaData().save (mState.getWriter()); - mState.getWriter().setRecordCount ( - mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + - mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + - mDocument.getData().count (CSMWorld::RecordBase::State_Deleted)); + mDocument.getData().getMetaData().save(mState.getWriter()); + mState.getWriter().setRecordCount(mDocument.getData().count(CSMWorld::RecordBase::State_Modified) + + mDocument.getData().count(CSMWorld::RecordBase::State_ModifiedOnly) + + mDocument.getData().count(CSMWorld::RecordBase::State_Deleted)); /// \todo refine dependency list (at least remove redundant dependencies) - std::vector dependencies = mDocument.getContentFiles(); - std::vector::const_iterator end (--dependencies.end()); + std::vector dependencies = mDocument.getContentFiles(); + std::vector::const_iterator end(--dependencies.end()); - for (std::vector::const_iterator iter (dependencies.begin()); - iter!=end; ++iter) + for (std::vector::const_iterator iter(dependencies.begin()); iter != end; ++iter) { - std::string name = iter->filename().string(); - uint64_t size = boost::filesystem::file_size (*iter); + auto name = Files::pathToUnicodeString(iter->filename()); + auto size = std::filesystem::file_size(*iter); - mState.getWriter().addMaster (name, size); + mState.getWriter().addMaster(name, size); } } - mState.getWriter().save (mState.getStream()); + mState.getWriter().save(mState.getStream()); } - -CSMDoc::WriteDialogueCollectionStage::WriteDialogueCollectionStage (Document& document, - SavingState& state, bool journal) -: mState (state), - mTopics (journal ? document.getData().getJournals() : document.getData().getTopics()), - mInfos (journal ? document.getData().getJournalInfos() : document.getData().getTopicInfos()) -{} +CSMDoc::WriteDialogueCollectionStage::WriteDialogueCollectionStage(Document& document, SavingState& state, bool journal) + : mState(state) + , mTopics(journal ? document.getData().getJournals() : document.getData().getTopics()) + , mInfos(journal ? document.getData().getJournalInfos() : document.getData().getTopicInfos()) +{ +} int CSMDoc::WriteDialogueCollectionStage::setup() { + mInfosByTopic = mInfos.getInfosByTopic(); return mTopics.getSize(); } -void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& messages) +void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& topic = mTopics.getRecord (stage); + const CSMWorld::Record& topic = mTopics.getRecord(stage); if (topic.mState == CSMWorld::RecordBase::State_Deleted) { @@ -110,191 +144,261 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message // Test, if we need to save anything associated info records. bool infoModified = false; - CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId); + const auto topicInfos = mInfosByTopic.find(topic.get().mId); - for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) + if (topicInfos != mInfosByTopic.end()) { - if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) + for (const auto& record : topicInfos->second) { - infoModified = true; - break; + if (record->isModified() || record->mState == CSMWorld::RecordBase::State_Deleted) + { + infoModified = true; + break; + } } } if (topic.isModified() || infoModified) { if (infoModified && topic.mState != CSMWorld::RecordBase::State_Modified - && topic.mState != CSMWorld::RecordBase::State_ModifiedOnly) + && topic.mState != CSMWorld::RecordBase::State_ModifiedOnly) { - mState.getWriter().startRecord (topic.mBase.sRecordId); - topic.mBase.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); - mState.getWriter().endRecord (topic.mBase.sRecordId); + mState.getWriter().startRecord(topic.mBase.sRecordId); + topic.mBase.save(mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); + mState.getWriter().endRecord(topic.mBase.sRecordId); } else { - mState.getWriter().startRecord (topic.mModified.sRecordId); - topic.mModified.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); - mState.getWriter().endRecord (topic.mModified.sRecordId); + mState.getWriter().startRecord(topic.mModified.sRecordId); + topic.mModified.save(mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); + mState.getWriter().endRecord(topic.mModified.sRecordId); } // write modified selected info records - for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) + if (topicInfos != mInfosByTopic.end()) { - if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) + const std::vector*>& infos = topicInfos->second; + + for (auto iter = infos.begin(); iter != infos.end(); ++iter) { - ESM::DialInfo info = iter->get(); - info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); + const CSMWorld::Record& record = **iter; - info.mPrev = ""; - if (iter!=range.first) + if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted) { - CSMWorld::InfoCollection::RecordConstIterator prev = iter; - --prev; + ESM::DialInfo info = record.get(); + info.mId = record.get().mOriginalId; - info.mPrev = prev->get().mId.substr (prev->get().mId.find_last_of ('#')+1); + if (iter == infos.begin()) + info.mPrev = ESM::RefId(); + else + info.mPrev = (*std::prev(iter))->get().mOriginalId; + + const auto next = std::next(iter); + + if (next == infos.end()) + info.mNext = ESM::RefId(); + else + info.mNext = (*next)->get().mOriginalId; + + writer.startRecord(info.sRecordId); + info.save(writer, record.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord(info.sRecordId); } - - CSMWorld::InfoCollection::RecordConstIterator next = iter; - ++next; - - info.mNext = ""; - if (next!=range.second) - { - info.mNext = next->get().mId.substr (next->get().mId.find_last_of ('#')+1); - } - - writer.startRecord (info.sRecordId); - info.save (writer, iter->mState == CSMWorld::RecordBase::State_Deleted); - writer.endRecord (info.sRecordId); } } } } - -CSMDoc::WriteRefIdCollectionStage::WriteRefIdCollectionStage (Document& document, SavingState& state) -: mDocument (document), mState (state) -{} +CSMDoc::WriteRefIdCollectionStage::WriteRefIdCollectionStage(Document& document, SavingState& state) + : mDocument(document) + , mState(state) +{ +} int CSMDoc::WriteRefIdCollectionStage::setup() { return mDocument.getData().getReferenceables().getSize(); } -void CSMDoc::WriteRefIdCollectionStage::perform (int stage, Messages& messages) +void CSMDoc::WriteRefIdCollectionStage::perform(int stage, Messages& messages) { - mDocument.getData().getReferenceables().save (stage, mState.getWriter()); + mDocument.getData().getReferenceables().save(stage, mState.getWriter()); } - -CSMDoc::CollectionReferencesStage::CollectionReferencesStage (Document& document, - SavingState& state) -: mDocument (document), mState (state) -{} +CSMDoc::CollectionReferencesStage::CollectionReferencesStage(Document& document, SavingState& state) + : mDocument(document) + , mState(state) +{ +} int CSMDoc::CollectionReferencesStage::setup() { - mState.getSubRecords().clear(); + mState.clearSubRecords(); int size = mDocument.getData().getReferences().getSize(); - int steps = size/100; - if (size%100) ++steps; + int steps = size / 100; + if (size % 100) + ++steps; return steps; } -void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) +void CSMDoc::CollectionReferencesStage::perform(int stage, Messages& messages) { int size = mDocument.getData().getReferences().getSize(); - for (int i=stage*100; i& record = - mDocument.getData().getReferences().getRecord (i); + const CSMWorld::Record& record = mDocument.getData().getReferences().getRecord(i); if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted) { - std::string cellId = record.get().mOriginalCell.empty() ? - record.get().mCell : record.get().mOriginalCell; + ESM::RefId cellId = record.get().mOriginalCell.empty() ? record.get().mCell : record.get().mOriginalCell; - std::deque& indices = - mState.getSubRecords()[Misc::StringUtils::lowerCase (cellId)]; + std::deque& indices = mState.getOrInsertSubRecord(cellId); // collect moved references at the end of the container - bool interior = cellId.substr (0, 1)!="#"; + + const bool interior = !cellId.startsWith("#"); std::ostringstream stream; if (!interior) { // recalculate the ref's cell location std::pair index = record.get().getCellIndex(); - stream << "#" << index.first << " " << index.second; + cellId = ESM::RefId::stringRefId(ESM::RefId::esm3ExteriorCell(index.first, index.second).toString()); } // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. - if ((record.get().mOriginalCell.empty() ? - record.get().mCell : record.get().mOriginalCell) != stream.str() && !interior && record.mState!=CSMWorld::RecordBase::State_ModifiedOnly && !record.get().mNew) - indices.push_back (i); + if ((record.get().mOriginalCell.empty() ? record.get().mCell : record.get().mOriginalCell) != cellId + && !interior && record.mState != CSMWorld::RecordBase::State_ModifiedOnly && !record.get().mNew) + indices.push_back(i); else - indices.push_front (i); + indices.push_front(i); } } } - -CSMDoc::WriteCellCollectionStage::WriteCellCollectionStage (Document& document, - SavingState& state) -: mDocument (document), mState (state) -{} +CSMDoc::WriteCellCollectionStage::WriteCellCollectionStage(Document& document, SavingState& state) + : mDocument(document) + , mState(state) +{ +} int CSMDoc::WriteCellCollectionStage::setup() { return mDocument.getData().getCells().getSize(); } -void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) +void CSMDoc::WriteCellCollectionStage::writeReferences( + const std::deque& references, bool interior, unsigned int& newRefNum) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& cell = mDocument.getData().getCells().getRecord (stage); - std::map >::const_iterator references = - mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); + for (std::deque::const_iterator iter(references.begin()); iter != references.end(); ++iter) + { + const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord(*iter); - if (cell.isModified() || - cell.mState == CSMWorld::RecordBase::State_Deleted || - references!=mState.getSubRecords().end()) + if (ref.isModified() || ref.mState == CSMWorld::RecordBase::State_Deleted) + { + CSMWorld::CellRef refRecord = ref.get(); + + // Check for uninitialized content file + if (!refRecord.mRefNum.hasContentFile()) + refRecord.mRefNum.mContentFile = 0; + + // recalculate the ref's cell location + std::ostringstream stream; + if (!interior) + { + std::pair index = refRecord.getCellIndex(); + stream << "#" << index.first << " " << index.second; + } + + ESM::RefId streamId = ESM::RefId::stringRefId(stream.str()); + if (refRecord.mNew || refRecord.mRefNum.mIndex == 0 + || (!interior && ref.mState == CSMWorld::RecordBase::State_ModifiedOnly && refRecord.mCell != streamId)) + { + refRecord.mRefNum.mIndex = newRefNum++; + } + else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != streamId + && !interior) + { + // An empty mOriginalCell is meant to indicate that it is the same as + // the current cell. It is possible that a moved ref is moved again. + + ESM::MovedCellRef moved; + moved.mRefNum = refRecord.mRefNum; + + // Need to fill mTarget with the ref's new position. + std::istringstream istream(stream.str().c_str()); + + char ignore; + istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1]; + + writer.writeFormId(refRecord.mRefNum, false, "MVRF"); + writer.writeHNT("CNDT", moved.mTarget); + } + + refRecord.save(writer, false, false, ref.mState == CSMWorld::RecordBase::State_Deleted); + } + } +} + +void CSMDoc::WriteCellCollectionStage::perform(int stage, Messages& messages) +{ + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& cell = mDocument.getData().getCells().getRecord(stage); + const CSMWorld::RefIdCollection& referenceables = mDocument.getData().getReferenceables(); + const CSMWorld::RefIdData& refIdData = referenceables.getDataSet(); + + std::deque tempRefs; + std::deque persistentRefs; + + const std::deque* references = mState.findSubRecord(cell.get().mId); + + if (cell.isModified() || cell.mState == CSMWorld::RecordBase::State_Deleted || references != nullptr) { CSMWorld::Cell cellRecord = cell.get(); - bool interior = cellRecord.mId.substr (0, 1)!="#"; + const bool interior = !cellRecord.mId.startsWith("#"); // count new references and adjust RefNumCount accordingsly unsigned int newRefNum = cellRecord.mRefNumCounter; - if (references!=mState.getSubRecords().end()) + if (references != nullptr) { - for (std::deque::const_iterator iter (references->second.begin()); - iter!=references->second.end(); ++iter) + for (std::deque::const_iterator iter(references->begin()); iter != references->end(); ++iter) { - const CSMWorld::Record& ref = - mDocument.getData().getReferences().getRecord (*iter); + const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord(*iter); CSMWorld::CellRef refRecord = ref.get(); - if (refRecord.mNew || - (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && - /// \todo consider worldspace - CSMWorld::CellCoordinates (refRecord.getCellIndex()).getId("") != refRecord.mCell)) + CSMWorld::RefIdData::LocalIndex localIndex = refIdData.searchId(refRecord.mRefID); + unsigned int recordFlags = refIdData.getRecordFlags(refRecord.mRefID); + bool isPersistent = ((recordFlags & ESM::FLAG_Persistent) != 0) || refRecord.mTeleport + || localIndex.second == CSMWorld::UniversalId::Type_Creature + || localIndex.second == CSMWorld::UniversalId::Type_Npc; + + if (isPersistent) + persistentRefs.push_back(*iter); + else + tempRefs.push_back(*iter); + + if (refRecord.mNew + || (!interior && ref.mState == CSMWorld::RecordBase::State_ModifiedOnly && + /// \todo consider worldspace + ESM::RefId::stringRefId(CSMWorld::CellCoordinates(refRecord.getCellIndex()).getId("")) + != refRecord.mCell)) ++cellRecord.mRefNumCounter; if (refRecord.mRefNum.mIndex >= newRefNum) newRefNum = refRecord.mRefNum.mIndex + 1; - } } // write cell data - writer.startRecord (cellRecord.sRecordId); + writer.startRecord(cellRecord.sRecordId); if (interior) cellRecord.mData.mFlags |= ESM::Cell::Interior; @@ -302,204 +406,154 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { cellRecord.mData.mFlags &= ~ESM::Cell::Interior; - std::istringstream stream (cellRecord.mId.c_str()); + std::istringstream stream(cellRecord.mId.getRefIdString().c_str()); char ignore; stream >> ignore >> cellRecord.mData.mX >> cellRecord.mData.mY; } - cellRecord.save (writer, cell.mState == CSMWorld::RecordBase::State_Deleted); + cellRecord.save(writer, cell.mState == CSMWorld::RecordBase::State_Deleted); // write references - if (references!=mState.getSubRecords().end()) + if (references != nullptr) { - for (std::deque::const_iterator iter (references->second.begin()); - iter!=references->second.end(); ++iter) - { - const CSMWorld::Record& ref = - mDocument.getData().getReferences().getRecord (*iter); - - if (ref.isModified() || ref.mState == CSMWorld::RecordBase::State_Deleted) - { - CSMWorld::CellRef refRecord = ref.get(); - - // Check for uninitialized content file - if (!refRecord.mRefNum.hasContentFile()) - refRecord.mRefNum.mContentFile = 0; - - // recalculate the ref's cell location - std::ostringstream stream; - if (!interior) - { - std::pair index = refRecord.getCellIndex(); - stream << "#" << index.first << " " << index.second; - } - - if (refRecord.mNew || refRecord.mRefNum.mIndex == 0 || - (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && - refRecord.mCell!=stream.str())) - { - refRecord.mRefNum.mIndex = newRefNum++; - } - else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) - != stream.str() && !interior) - { - // An empty mOriginalCell is meant to indicate that it is the same as - // the current cell. It is possible that a moved ref is moved again. - - ESM::MovedCellRef moved; - moved.mRefNum = refRecord.mRefNum; - - // Need to fill mTarget with the ref's new position. - std::istringstream istream (stream.str().c_str()); - - char ignore; - istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1]; - - refRecord.mRefNum.save (writer, false, "MVRF"); - writer.writeHNT ("CNDT", moved.mTarget); - } - - refRecord.save (writer, false, false, ref.mState == CSMWorld::RecordBase::State_Deleted); - } - } + writeReferences(persistentRefs, interior, newRefNum); + cellRecord.saveTempMarker(writer, static_cast(references->size()) - persistentRefs.size()); + writeReferences(tempRefs, interior, newRefNum); } - writer.endRecord (cellRecord.sRecordId); + writer.endRecord(cellRecord.sRecordId); } } - -CSMDoc::WritePathgridCollectionStage::WritePathgridCollectionStage (Document& document, - SavingState& state) -: mDocument (document), mState (state) -{} +CSMDoc::WritePathgridCollectionStage::WritePathgridCollectionStage(Document& document, SavingState& state) + : mDocument(document) + , mState(state) +{ +} int CSMDoc::WritePathgridCollectionStage::setup() { return mDocument.getData().getPathgrids().getSize(); } -void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages) +void CSMDoc::WritePathgridCollectionStage::perform(int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& pathgrid = - mDocument.getData().getPathgrids().getRecord (stage); + const CSMWorld::Record& pathgrid = mDocument.getData().getPathgrids().getRecord(stage); if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Pathgrid record = pathgrid.get(); - - if (record.mId.substr (0, 1)=="#") + if (record.mId.startsWith("#")) { - std::istringstream stream (record.mId.c_str()); + std::istringstream stream(record.mId.getRefIdString()); char ignore; stream >> ignore >> record.mData.mX >> record.mData.mY; } else record.mCell = record.mId; - writer.startRecord (record.sRecordId); - record.save (writer, pathgrid.mState == CSMWorld::RecordBase::State_Deleted); - writer.endRecord (record.sRecordId); + writer.startRecord(record.sRecordId); + record.save(writer, pathgrid.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord(record.sRecordId); } } - -CSMDoc::WriteLandCollectionStage::WriteLandCollectionStage (Document& document, - SavingState& state) -: mDocument (document), mState (state) -{} +CSMDoc::WriteLandCollectionStage::WriteLandCollectionStage(Document& document, SavingState& state) + : mDocument(document) + , mState(state) +{ +} int CSMDoc::WriteLandCollectionStage::setup() { return mDocument.getData().getLand().getSize(); } -void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) +void CSMDoc::WriteLandCollectionStage::perform(int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& land = - mDocument.getData().getLand().getRecord (stage); + const CSMWorld::Record& land = mDocument.getData().getLand().getRecord(stage); if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Land record = land.get(); - writer.startRecord (record.sRecordId); - record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); - writer.endRecord (record.sRecordId); + writer.startRecord(record.sRecordId); + record.save(writer, land.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord(record.sRecordId); } } - -CSMDoc::WriteLandTextureCollectionStage::WriteLandTextureCollectionStage (Document& document, - SavingState& state) -: mDocument (document), mState (state) -{} +CSMDoc::WriteLandTextureCollectionStage::WriteLandTextureCollectionStage(Document& document, SavingState& state) + : mDocument(document) + , mState(state) +{ +} int CSMDoc::WriteLandTextureCollectionStage::setup() { return mDocument.getData().getLandTextures().getSize(); } -void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages) +void CSMDoc::WriteLandTextureCollectionStage::perform(int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& landTexture = - mDocument.getData().getLandTextures().getRecord (stage); + const CSMWorld::Record& landTexture = mDocument.getData().getLandTextures().getRecord(stage); if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::LandTexture record = landTexture.get(); - writer.startRecord (record.sRecordId); - record.save (writer, landTexture.mState == CSMWorld::RecordBase::State_Deleted); - writer.endRecord (record.sRecordId); + writer.startRecord(record.sRecordId); + record.save(writer, landTexture.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord(record.sRecordId); } } - -CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state) -: mState (state) -{} +CSMDoc::CloseSaveStage::CloseSaveStage(SavingState& state) + : mState(state) +{ +} int CSMDoc::CloseSaveStage::setup() { return 1; } -void CSMDoc::CloseSaveStage::perform (int stage, Messages& messages) +void CSMDoc::CloseSaveStage::perform(int stage, Messages& messages) { mState.getStream().close(); if (!mState.getStream()) - throw std::runtime_error ("saving failed"); + throw std::runtime_error("saving failed"); } - -CSMDoc::FinalSavingStage::FinalSavingStage (Document& document, SavingState& state) -: mDocument (document), mState (state) -{} +CSMDoc::FinalSavingStage::FinalSavingStage(Document& document, SavingState& state) + : mDocument(document) + , mState(state) +{ +} int CSMDoc::FinalSavingStage::setup() { return 1; } -void CSMDoc::FinalSavingStage::perform (int stage, Messages& messages) +void CSMDoc::FinalSavingStage::perform(int stage, Messages& messages) { if (mState.hasError()) { mState.getWriter().close(); mState.getStream().close(); - if (boost::filesystem::exists (mState.getTmpPath())) - boost::filesystem::remove (mState.getTmpPath()); + if (std::filesystem::exists(mState.getTmpPath())) + std::filesystem::remove(mState.getTmpPath()); } else if (!mState.isProjectFile()) { - if (boost::filesystem::exists (mState.getPath())) - boost::filesystem::remove (mState.getPath()); + if (std::filesystem::exists(mState.getPath())) + std::filesystem::remove(mState.getPath()); - boost::filesystem::rename (mState.getTmpPath(), mState.getPath()); + std::filesystem::rename(mState.getTmpPath(), mState.getPath()); mDocument.getUndoStack().setClean(); } diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index d3a0cc9b3..5423b8f50 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -3,17 +3,20 @@ #include "stage.hpp" -#include "../world/record.hpp" -#include "../world/idcollection.hpp" -#include "../world/scope.hpp" +#include +#include -#include +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" +#include "../world/record.hpp" +#include "../world/scope.hpp" #include "savingstate.hpp" namespace ESM { struct Dialogue; + class ESMWriter; } namespace CSMWorld @@ -24,244 +27,230 @@ namespace CSMWorld namespace CSMDoc { class Document; - class SavingState; + class Messages; class OpenSaveStage : public Stage { - Document& mDocument; - SavingState& mState; - bool mProjectFile; + Document& mDocument; + SavingState& mState; + bool mProjectFile; - public: + public: + OpenSaveStage(Document& document, SavingState& state, bool projectFile); + ///< \param projectFile Saving the project file instead of the content file. - OpenSaveStage (Document& document, SavingState& state, bool projectFile); - ///< \param projectFile Saving the project file instead of the content file. + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; class WriteHeaderStage : public Stage { - Document& mDocument; - SavingState& mState; - bool mSimple; + Document& mDocument; + SavingState& mState; + bool mSimple; - public: + public: + WriteHeaderStage(Document& document, SavingState& state, bool simple); + ///< \param simple Simplified header (used for project files). - WriteHeaderStage (Document& document, SavingState& state, bool simple); - ///< \param simple Simplified header (used for project files). + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - - template + template class WriteCollectionStage : public Stage { - const CollectionT& mCollection; - SavingState& mState; - CSMWorld::Scope mScope; + const CollectionT& mCollection; + SavingState& mState; + CSMWorld::Scope mScope; - public: + public: + WriteCollectionStage( + const CollectionT& collection, SavingState& state, CSMWorld::Scope scope = CSMWorld::Scope_Content); - WriteCollectionStage (const CollectionT& collection, SavingState& state, - CSMWorld::Scope scope = CSMWorld::Scope_Content); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - template - WriteCollectionStage::WriteCollectionStage (const CollectionT& collection, - SavingState& state, CSMWorld::Scope scope) - : mCollection (collection), mState (state), mScope (scope) - {} + template + WriteCollectionStage::WriteCollectionStage( + const CollectionT& collection, SavingState& state, CSMWorld::Scope scope) + : mCollection(collection) + , mState(state) + , mScope(scope) + { + } - template + template int WriteCollectionStage::setup() { return mCollection.getSize(); } - template - void WriteCollectionStage::perform (int stage, Messages& messages) + template + void WriteCollectionStage::perform(int stage, Messages& messages) { - if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope) + if (CSMWorld::getScopeFromId(mCollection.getRecord(stage).get().mId) != mScope) return; ESM::ESMWriter& writer = mState.getWriter(); - CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; - typename CollectionT::ESXRecord record = mCollection.getRecord (stage).get(); + CSMWorld::RecordBase::State state = mCollection.getRecord(stage).mState; + typename CollectionT::ESXRecord record = mCollection.getRecord(stage).get(); - if (state == CSMWorld::RecordBase::State_Modified || - state == CSMWorld::RecordBase::State_ModifiedOnly || - state == CSMWorld::RecordBase::State_Deleted) + if (state == CSMWorld::RecordBase::State_Modified || state == CSMWorld::RecordBase::State_ModifiedOnly + || state == CSMWorld::RecordBase::State_Deleted) { - writer.startRecord (record.sRecordId); - record.save (writer, state == CSMWorld::RecordBase::State_Deleted); - writer.endRecord (record.sRecordId); + writer.startRecord(record.sRecordId, record.mRecordFlags); + record.save(writer, state == CSMWorld::RecordBase::State_Deleted); + writer.endRecord(record.sRecordId); } } - class WriteDialogueCollectionStage : public Stage { - SavingState& mState; - const CSMWorld::IdCollection& mTopics; - CSMWorld::InfoCollection& mInfos; + SavingState& mState; + const CSMWorld::IdCollection& mTopics; + CSMWorld::InfoCollection& mInfos; + CSMWorld::InfosRecordPtrByTopic mInfosByTopic; - public: + public: + WriteDialogueCollectionStage(Document& document, SavingState& state, bool journal); - WriteDialogueCollectionStage (Document& document, SavingState& state, bool journal); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - class WriteRefIdCollectionStage : public Stage { - Document& mDocument; - SavingState& mState; + Document& mDocument; + SavingState& mState; - public: + public: + WriteRefIdCollectionStage(Document& document, SavingState& state); - WriteRefIdCollectionStage (Document& document, SavingState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - class CollectionReferencesStage : public Stage { - Document& mDocument; - SavingState& mState; + Document& mDocument; + SavingState& mState; - public: + public: + CollectionReferencesStage(Document& document, SavingState& state); - CollectionReferencesStage (Document& document, SavingState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; class WriteCellCollectionStage : public Stage { - Document& mDocument; - SavingState& mState; + Document& mDocument; + SavingState& mState; - public: + void writeReferences(const std::deque& references, bool interior, unsigned int& newRefNum); - WriteCellCollectionStage (Document& document, SavingState& state); + public: + WriteCellCollectionStage(Document& document, SavingState& state); - int setup() override; - ///< \return number of steps + int setup() override; + ///< \return number of steps - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - class WritePathgridCollectionStage : public Stage { - Document& mDocument; - SavingState& mState; + Document& mDocument; + SavingState& mState; - public: + public: + WritePathgridCollectionStage(Document& document, SavingState& state); - WritePathgridCollectionStage (Document& document, SavingState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - class WriteLandCollectionStage : public Stage { - Document& mDocument; - SavingState& mState; + Document& mDocument; + SavingState& mState; - public: + public: + WriteLandCollectionStage(Document& document, SavingState& state); - WriteLandCollectionStage (Document& document, SavingState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - class WriteLandTextureCollectionStage : public Stage { - Document& mDocument; - SavingState& mState; + Document& mDocument; + SavingState& mState; - public: + public: + WriteLandTextureCollectionStage(Document& document, SavingState& state); - WriteLandTextureCollectionStage (Document& document, SavingState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; class CloseSaveStage : public Stage { - SavingState& mState; + SavingState& mState; - public: + public: + CloseSaveStage(SavingState& state); - CloseSaveStage (SavingState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; class FinalSavingStage : public Stage { - Document& mDocument; - SavingState& mState; + Document& mDocument; + SavingState& mState; - public: + public: + FinalSavingStage(Document& document, SavingState& state); - FinalSavingStage (Document& document, SavingState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; } diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp index 7214735f0..aec2d0fd9 100644 --- a/apps/opencs/model/doc/savingstate.cpp +++ b/apps/opencs/model/doc/savingstate.cpp @@ -1,15 +1,18 @@ #include "savingstate.hpp" -#include +#include +#include -#include "operation.hpp" #include "document.hpp" +#include "operation.hpp" -CSMDoc::SavingState::SavingState (Operation& operation, const boost::filesystem::path& projectPath, - ToUTF8::FromType encoding) -: mOperation (operation), mEncoder (encoding), mProjectPath (projectPath), mProjectFile (false) +CSMDoc::SavingState::SavingState(Operation& operation, std::filesystem::path projectPath, ToUTF8::FromType encoding) + : mOperation(operation) + , mEncoder(encoding) + , mProjectPath(std::move(projectPath)) + , mProjectFile(false) { - mWriter.setEncoder (&mEncoder); + mWriter.setEncoder(&mEncoder); } bool CSMDoc::SavingState::hasError() const @@ -17,7 +20,7 @@ bool CSMDoc::SavingState::hasError() const return mOperation.hasError(); } -void CSMDoc::SavingState::start (Document& document, bool project) +void CSMDoc::SavingState::start(Document& document, bool project) { mProjectFile = project; @@ -33,24 +36,24 @@ void CSMDoc::SavingState::start (Document& document, bool project) else mPath = document.getSavePath(); - boost::filesystem::path file (mPath.filename().string() + ".tmp"); + std::filesystem::path file(mPath.filename().u8string() + u8".tmp"); mTmpPath = mPath.parent_path(); mTmpPath /= file; } -const boost::filesystem::path& CSMDoc::SavingState::getPath() const +const std::filesystem::path& CSMDoc::SavingState::getPath() const { return mPath; } -const boost::filesystem::path& CSMDoc::SavingState::getTmpPath() const +const std::filesystem::path& CSMDoc::SavingState::getTmpPath() const { return mTmpPath; } -boost::filesystem::ofstream& CSMDoc::SavingState::getStream() +std::ofstream& CSMDoc::SavingState::getStream() { return mStream; } @@ -65,7 +68,20 @@ bool CSMDoc::SavingState::isProjectFile() const return mProjectFile; } -std::map >& CSMDoc::SavingState::getSubRecords() +const std::deque* CSMDoc::SavingState::findSubRecord(const ESM::RefId& refId) const { - return mSubRecords; + const auto it = mSubRecords.find(refId); + if (it == mSubRecords.end()) + return nullptr; + return &it->second; +} + +std::deque& CSMDoc::SavingState::getOrInsertSubRecord(const ESM::RefId& refId) +{ + return mSubRecords[refId]; +} + +void CSMDoc::SavingState::clearSubRecords() +{ + mSubRecords.clear(); } diff --git a/apps/opencs/model/doc/savingstate.hpp b/apps/opencs/model/doc/savingstate.hpp index e6c8c545a..c42dff036 100644 --- a/apps/opencs/model/doc/savingstate.hpp +++ b/apps/opencs/model/doc/savingstate.hpp @@ -1,15 +1,14 @@ #ifndef CSM_DOC_SAVINGSTATE_H #define CSM_DOC_SAVINGSTATE_H +#include +#include #include #include -#include - -#include -#include - -#include +#include +#include +#include #include namespace CSMDoc @@ -19,41 +18,42 @@ namespace CSMDoc class SavingState { - Operation& mOperation; - boost::filesystem::path mPath; - boost::filesystem::path mTmpPath; - ToUTF8::Utf8Encoder mEncoder; - boost::filesystem::ofstream mStream; - ESM::ESMWriter mWriter; - boost::filesystem::path mProjectPath; - bool mProjectFile; - std::map > mSubRecords; // record ID, list of subrecords + Operation& mOperation; + std::filesystem::path mPath; + std::filesystem::path mTmpPath; + ToUTF8::Utf8Encoder mEncoder; + std::ofstream mStream; + ESM::ESMWriter mWriter; + std::filesystem::path mProjectPath; + bool mProjectFile; + std::map> mSubRecords; // record ID, list of subrecords - public: + public: + SavingState(Operation& operation, std::filesystem::path projectPath, ToUTF8::FromType encoding); - SavingState (Operation& operation, const boost::filesystem::path& projectPath, - ToUTF8::FromType encoding); + bool hasError() const; - bool hasError() const; + void start(Document& document, bool project); + ///< \param project Save project file instead of content file. - void start (Document& document, bool project); - ///< \param project Save project file instead of content file. + const std::filesystem::path& getPath() const; - const boost::filesystem::path& getPath() const; + const std::filesystem::path& getTmpPath() const; - const boost::filesystem::path& getTmpPath() const; + std::ofstream& getStream(); - boost::filesystem::ofstream& getStream(); + ESM::ESMWriter& getWriter(); - ESM::ESMWriter& getWriter(); + bool isProjectFile() const; + ///< Currently saving project file? (instead of content file) - bool isProjectFile() const; - ///< Currently saving project file? (instead of content file) + const std::deque* findSubRecord(const ESM::RefId& refId) const; - std::map >& getSubRecords(); + std::deque& getOrInsertSubRecord(const ESM::RefId& refId); + + void clearSubRecords(); }; - } #endif diff --git a/apps/opencs/model/doc/stage.cpp b/apps/opencs/model/doc/stage.cpp deleted file mode 100644 index 3c8c107ba..000000000 --- a/apps/opencs/model/doc/stage.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "stage.hpp" - -CSMDoc::Stage::~Stage() {} diff --git a/apps/opencs/model/doc/stage.hpp b/apps/opencs/model/doc/stage.hpp index 1eae27a98..2f1d0fdbd 100644 --- a/apps/opencs/model/doc/stage.hpp +++ b/apps/opencs/model/doc/stage.hpp @@ -1,28 +1,19 @@ #ifndef CSM_DOC_STAGE_H #define CSM_DOC_STAGE_H -#include -#include - -#include "../world/universalid.hpp" - -#include "messages.hpp" - -class QString; - namespace CSMDoc { + class Messages; class Stage { - public: + public: + virtual ~Stage() = default; - virtual ~Stage(); + virtual int setup() = 0; + ///< \return number of steps - virtual int setup() = 0; - ///< \return number of steps - - virtual void perform (int stage, Messages& messages) = 0; - ///< Messages resulting from this stage will be appended to \a messages. + virtual void perform(int stage, Messages& messages) = 0; + ///< Messages resulting from this stage will be appended to \a messages. }; } diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp index 7352b4b99..9d53fa816 100644 --- a/apps/opencs/model/doc/state.hpp +++ b/apps/opencs/model/doc/state.hpp @@ -14,7 +14,7 @@ namespace CSMDoc State_Verifying = 32, State_Merging = 64, State_Searching = 128, - State_Loading = 256 // pseudo-state; can not be encountered in a loaded document + State_Loading = 256 // pseudo-state; can not be encountered in a loaded document }; } diff --git a/apps/opencs/model/filter/andnode.cpp b/apps/opencs/model/filter/andnode.cpp index 757865717..1fbf17435 100644 --- a/apps/opencs/model/filter/andnode.cpp +++ b/apps/opencs/model/filter/andnode.cpp @@ -1,18 +1,19 @@ #include "andnode.hpp" -#include +#include +#include -CSMFilter::AndNode::AndNode (const std::vector >& nodes) -: NAryNode (nodes, "and") -{} +CSMFilter::AndNode::AndNode(const std::vector>& nodes) + : NAryNode(nodes, "and") +{ +} -bool CSMFilter::AndNode::test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const +bool CSMFilter::AndNode::test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { int size = getSize(); - for (int i=0; i +#include +#include + +namespace CSMWorld +{ + class IdTableBase; +} + namespace CSMFilter { + class Node; class AndNode : public NAryNode { - public: + public: + AndNode(const std::vector>& nodes); - AndNode (const std::vector >& nodes); - - bool test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const override; - ///< \return Can the specified table row pass through to filter? - /// \param columns column ID to column index mapping + bool test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping }; } diff --git a/apps/opencs/model/filter/booleannode.cpp b/apps/opencs/model/filter/booleannode.cpp index ee7ddc1c0..793709b8f 100644 --- a/apps/opencs/model/filter/booleannode.cpp +++ b/apps/opencs/model/filter/booleannode.cpp @@ -1,14 +1,21 @@ #include "booleannode.hpp" -CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {} +namespace CSMWorld +{ + class IdTableBase; +} -bool CSMFilter::BooleanNode::test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const +CSMFilter::BooleanNode::BooleanNode(bool true_) + : mTrue(true_) +{ +} + +bool CSMFilter::BooleanNode::test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { return mTrue; } -std::string CSMFilter::BooleanNode::toString (bool numericColumns) const +std::string CSMFilter::BooleanNode::toString(bool numericColumns) const { return mTrue ? "true" : "false"; } diff --git a/apps/opencs/model/filter/booleannode.hpp b/apps/opencs/model/filter/booleannode.hpp index bed6cbeb0..331899553 100644 --- a/apps/opencs/model/filter/booleannode.hpp +++ b/apps/opencs/model/filter/booleannode.hpp @@ -1,28 +1,33 @@ #ifndef CSM_FILTER_BOOLEANNODE_H #define CSM_FILTER_BOOLEANNODE_H +#include +#include + #include "leafnode.hpp" +namespace CSMWorld +{ + class IdTableBase; +} + namespace CSMFilter { class BooleanNode : public LeafNode { - bool mTrue; + bool mTrue; - public: + public: + BooleanNode(bool true_); - BooleanNode (bool true_); - - bool test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const override; - ///< \return Can the specified table row pass through to filter? - /// \param columns column ID to column index mapping - - std::string toString (bool numericColumns) const override; - ///< Return a string that represents this node. - /// - /// \param numericColumns Use numeric IDs instead of string to represent columns. + bool test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping + std::string toString(bool numericColumns) const override; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } diff --git a/apps/opencs/model/filter/leafnode.cpp b/apps/opencs/model/filter/leafnode.cpp index 6745e165e..fd37c6cb6 100644 --- a/apps/opencs/model/filter/leafnode.cpp +++ b/apps/opencs/model/filter/leafnode.cpp @@ -4,4 +4,3 @@ std::vector CSMFilter::LeafNode::getReferencedColumns() const { return std::vector(); } - diff --git a/apps/opencs/model/filter/leafnode.hpp b/apps/opencs/model/filter/leafnode.hpp index d27bdcfe8..bf90da18b 100644 --- a/apps/opencs/model/filter/leafnode.hpp +++ b/apps/opencs/model/filter/leafnode.hpp @@ -1,7 +1,7 @@ #ifndef CSM_FILTER_LEAFNODE_H #define CSM_FILTER_LEAFNODE_H -#include +#include #include "node.hpp" @@ -9,11 +9,10 @@ namespace CSMFilter { class LeafNode : public Node { - public: - - std::vector getReferencedColumns() const override; - ///< Return a list of the IDs of the columns referenced by this node. The column mapping - /// passed into test as columns must contain all columns listed here. + public: + std::vector getReferencedColumns() const override; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. }; } diff --git a/apps/opencs/model/filter/narynode.cpp b/apps/opencs/model/filter/narynode.cpp index 9415f1daf..baa5eb788 100644 --- a/apps/opencs/model/filter/narynode.cpp +++ b/apps/opencs/model/filter/narynode.cpp @@ -1,38 +1,41 @@ #include "narynode.hpp" +#include #include -CSMFilter::NAryNode::NAryNode (const std::vector >& nodes, - const std::string& name) -: mNodes (nodes), mName (name) -{} +#include + +CSMFilter::NAryNode::NAryNode(const std::vector>& nodes, const std::string& name) + : mNodes(nodes) + , mName(name) +{ +} int CSMFilter::NAryNode::getSize() const { return static_cast(mNodes.size()); } -const CSMFilter::Node& CSMFilter::NAryNode::operator[] (int index) const +const CSMFilter::Node& CSMFilter::NAryNode::operator[](int index) const { - return *mNodes.at (index); + return *mNodes.at(index); } std::vector CSMFilter::NAryNode::getReferencedColumns() const { std::vector columns; - for (std::vector >::const_iterator iter (mNodes.begin()); - iter!=mNodes.end(); ++iter) + for (std::vector>::const_iterator iter(mNodes.begin()); iter != mNodes.end(); ++iter) { std::vector columns2 = (*iter)->getReferencedColumns(); - columns.insert (columns.end(), columns2.begin(), columns2.end()); + columns.insert(columns.end(), columns2.begin(), columns2.end()); } return columns; } -std::string CSMFilter::NAryNode::toString (bool numericColumns) const +std::string CSMFilter::NAryNode::toString(bool numericColumns) const { std::ostringstream stream; @@ -41,19 +44,17 @@ std::string CSMFilter::NAryNode::toString (bool numericColumns) const bool first = true; int size = getSize(); - for (int i=0; i +#include #include +#include #include "node.hpp" @@ -10,25 +11,24 @@ namespace CSMFilter { class NAryNode : public Node { - std::vector > mNodes; - std::string mName; + std::vector> mNodes; + std::string mName; - public: + public: + NAryNode(const std::vector>& nodes, const std::string& name); - NAryNode (const std::vector >& nodes, const std::string& name); + int getSize() const; - int getSize() const; + const Node& operator[](int index) const; - const Node& operator[] (int index) const; + std::vector getReferencedColumns() const override; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. - std::vector getReferencedColumns() const override; - ///< Return a list of the IDs of the columns referenced by this node. The column mapping - /// passed into test as columns must contain all columns listed here. - - std::string toString (bool numericColumns) const override; - ///< Return a string that represents this node. - /// - /// \param numericColumns Use numeric IDs instead of string to represent columns. + std::string toString(bool numericColumns) const override; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } diff --git a/apps/opencs/model/filter/node.cpp b/apps/opencs/model/filter/node.cpp deleted file mode 100644 index e25610675..000000000 --- a/apps/opencs/model/filter/node.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "node.hpp" - -CSMFilter::Node::Node() {} - -CSMFilter::Node::~Node() {} diff --git a/apps/opencs/model/filter/node.hpp b/apps/opencs/model/filter/node.hpp index 7295b9018..b143d238d 100644 --- a/apps/opencs/model/filter/node.hpp +++ b/apps/opencs/model/filter/node.hpp @@ -1,9 +1,9 @@ #ifndef CSM_FILTER_NODE_H #define CSM_FILTER_NODE_H -#include #include #include +#include #include #include @@ -21,32 +21,27 @@ namespace CSMFilter /// interpreted as "the node and all its children". class Node { - // not implemented - Node (const Node&); - Node& operator= (const Node&); + public: + Node() = default; + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + virtual ~Node() = default; - public: + virtual bool test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const = 0; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping - Node(); + virtual std::vector getReferencedColumns() const = 0; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. - virtual ~Node(); - - virtual bool test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const = 0; - ///< \return Can the specified table row pass through to filter? - /// \param columns column ID to column index mapping - - virtual std::vector getReferencedColumns() const = 0; - ///< Return a list of the IDs of the columns referenced by this node. The column mapping - /// passed into test as columns must contain all columns listed here. - - virtual std::string toString (bool numericColumns) const = 0; - ///< Return a string that represents this node. - /// - /// \param numericColumns Use numeric IDs instead of string to represent columns. + virtual std::string toString(bool numericColumns) const = 0; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } -Q_DECLARE_METATYPE (std::shared_ptr) +Q_DECLARE_METATYPE(std::shared_ptr) #endif diff --git a/apps/opencs/model/filter/notnode.cpp b/apps/opencs/model/filter/notnode.cpp index 81588c754..80162dc9e 100644 --- a/apps/opencs/model/filter/notnode.cpp +++ b/apps/opencs/model/filter/notnode.cpp @@ -1,9 +1,19 @@ #include "notnode.hpp" -CSMFilter::NotNode::NotNode (std::shared_ptr child) : UnaryNode (child, "not") {} +#include +#include -bool CSMFilter::NotNode::test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const +namespace CSMWorld { - return !getChild().test (table, row, columns); + class IdTableBase; +} + +CSMFilter::NotNode::NotNode(std::shared_ptr child) + : UnaryNode(child, "not") +{ +} + +bool CSMFilter::NotNode::test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const +{ + return !getChild().test(table, row, columns); } diff --git a/apps/opencs/model/filter/notnode.hpp b/apps/opencs/model/filter/notnode.hpp index 5b15163d9..955a19f7b 100644 --- a/apps/opencs/model/filter/notnode.hpp +++ b/apps/opencs/model/filter/notnode.hpp @@ -1,20 +1,27 @@ #ifndef CSM_FILTER_NOTNODE_H #define CSM_FILTER_NOTNODE_H +#include +#include + #include "unarynode.hpp" +namespace CSMWorld +{ + class IdTableBase; +} + namespace CSMFilter { + class Node; class NotNode : public UnaryNode { - public: + public: + NotNode(std::shared_ptr child); - NotNode (std::shared_ptr child); - - bool test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const override; - ///< \return Can the specified table row pass through to filter? - /// \param columns column ID to column index mapping + bool test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping }; } diff --git a/apps/opencs/model/filter/ornode.cpp b/apps/opencs/model/filter/ornode.cpp index 9e6d8b2c4..ed17b88a5 100644 --- a/apps/opencs/model/filter/ornode.cpp +++ b/apps/opencs/model/filter/ornode.cpp @@ -1,18 +1,24 @@ #include "ornode.hpp" -#include +#include +#include -CSMFilter::OrNode::OrNode (const std::vector >& nodes) -: NAryNode (nodes, "or") -{} +namespace CSMWorld +{ + class IdTableBase; +} -bool CSMFilter::OrNode::test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const +CSMFilter::OrNode::OrNode(const std::vector>& nodes) + : NAryNode(nodes, "or") +{ +} + +bool CSMFilter::OrNode::test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const { int size = getSize(); - for (int i=0; i +#include +#include + +namespace CSMWorld +{ + class IdTableBase; +} + namespace CSMFilter { + class Node; class OrNode : public NAryNode { - public: + public: + OrNode(const std::vector>& nodes); - OrNode (const std::vector >& nodes); - - bool test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const override; - ///< \return Can the specified table row pass through to filter? - /// \param columns column ID to column index mapping + bool test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping }; } diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index d363b4849..5443db285 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -1,22 +1,40 @@ #include "parser.hpp" +#include #include -#include #include +#include +#include -#include +#include +#include + +#include +#include #include "../world/columns.hpp" #include "../world/data.hpp" -#include "../world/idcollection.hpp" -#include "booleannode.hpp" -#include "ornode.hpp" #include "andnode.hpp" +#include "booleannode.hpp" #include "notnode.hpp" +#include "ornode.hpp" #include "textnode.hpp" #include "valuenode.hpp" +namespace +{ + bool isAlpha(char c) + { + return std::isalpha(static_cast(c)); + } + + bool isDigit(char c) + { + return std::isdigit(static_cast(c)); + } +} + namespace CSMFilter { struct Token @@ -46,49 +64,70 @@ namespace CSMFilter std::string mString; double mNumber; - Token (Type type = Type_None); + Token(Type type = Type_None); - Token (Type type, const std::string& string); + Token(Type type, const std::string& string); ///< Non-string type that can also be interpreted as a string. - Token (const std::string& string); + Token(const std::string& string); - Token (double number); + Token(double number); operator bool() const; bool isString() const; }; - Token::Token (Type type) : mType (type), mNumber(0.0) {} + Token::Token(Type type) + : mType(type) + , mNumber(0.0) + { + } - Token::Token (Type type, const std::string& string) : mType (type), mString (string), mNumber(0.0) {} + Token::Token(Type type, const std::string& string) + : mType(type) + , mString(string) + , mNumber(0.0) + { + } - Token::Token (const std::string& string) : mType (Type_String), mString (string), mNumber(0.0) {} + Token::Token(const std::string& string) + : mType(Type_String) + , mString(string) + , mNumber(0.0) + { + } - Token::Token (double number) : mType (Type_Number), mNumber (number) {} + Token::Token(double number) + : mType(Type_Number) + , mNumber(number) + { + } bool Token::isString() const { - return mType==Type_String || mType>=Type_Keyword_True; + return mType == Type_String || mType >= Type_Keyword_True; } Token::operator bool() const { - return mType!=Type_None; + return mType != Type_None; } - bool operator== (const Token& left, const Token& right) + bool operator==(const Token& left, const Token& right) { - if (left.mType!=right.mType) + if (left.mType != right.mType) return false; switch (left.mType) { - case Token::Type_String: return left.mString==right.mString; - case Token::Type_Number: return left.mNumber==right.mNumber; + case Token::Type_String: + return left.mString == right.mString; + case Token::Type_Number: + return left.mNumber == right.mNumber; - default: return true; + default: + return true; } } } @@ -97,19 +136,19 @@ CSMFilter::Token CSMFilter::Parser::getStringToken() { std::string string; - int size = static_cast (mInput.size()); + int size = static_cast(mInput.size()); - for (; mIndex1) + if (c == '"' && string.size() > 1) { ++mIndex; break; @@ -118,49 +157,49 @@ CSMFilter::Token CSMFilter::Parser::getStringToken() if (!string.empty()) { - if (string[0]=='"' && (string[string.size()-1]!='"' || string.size()<2) ) + if (string[0] == '"' && (string[string.size() - 1] != '"' || string.size() < 2)) { error(); - return Token (Token::Type_None); + return Token(Token::Type_None); } - if (string[0]!='"' && string[string.size()-1]=='"') + if (string[0] != '"' && string[string.size() - 1] == '"') { error(); - return Token (Token::Type_None); + return Token(Token::Type_None); } - if (string[0]=='"') - return string.substr (1, string.size()-2); + if (string[0] == '"') + return string.substr(1, string.size() - 2); } - return checkKeywords (string); + return checkKeywords(string); } CSMFilter::Token CSMFilter::Parser::getNumberToken() { std::string string; - int size = static_cast (mInput.size()); + int size = static_cast(mInput.size()); bool hasDecimalPoint = false; bool hasDigit = false; - for (; mIndex> value; return value; } -CSMFilter::Token CSMFilter::Parser::checkKeywords (const Token& token) +CSMFilter::Token CSMFilter::Parser::checkKeywords(const Token& token) { - static const char *sKeywords[] = - { - "true", "false", - "and", "or", "not", - "string", "value", - 0 + static const char* sKeywords[] = { + "true", + "false", + "and", + "or", + "not", + "string", + "value", + nullptr, }; - std::string string = Misc::StringUtils::lowerCase (token.mString); + std::string string = Misc::StringUtils::lowerCase(token.mString); - for (int i=0; sKeywords[i]; ++i) - if (sKeywords[i]==string || (string.size()==1 && sKeywords[i][0]==string[0])) - return Token (static_cast (i+Token::Type_Keyword_True), token.mString); + for (int i = 0; sKeywords[i]; ++i) + if (sKeywords[i] == string || (string.size() == 1 && sKeywords[i][0] == string[0])) + return Token(static_cast(i + Token::Type_Keyword_True), token.mString); return token; } CSMFilter::Token CSMFilter::Parser::getNextToken() { - int size = static_cast (mInput.size()); + int size = static_cast(mInput.size()); char c = 0; - for (; mIndex=size) - return Token (Token::Type_EOS); + if (mIndex >= size) + return Token(Token::Type_EOS); switch (c) { - case '(': ++mIndex; return Token (Token::Type_Open); - case ')': ++mIndex; return Token (Token::Type_Close); - case '[': ++mIndex; return Token (Token::Type_OpenSquare); - case ']': ++mIndex; return Token (Token::Type_CloseSquare); - case ',': ++mIndex; return Token (Token::Type_Comma); - case '!': ++mIndex; return Token (Token::Type_OneShot); + case '(': + ++mIndex; + return Token(Token::Type_Open); + case ')': + ++mIndex; + return Token(Token::Type_Close); + case '[': + ++mIndex; + return Token(Token::Type_OpenSquare); + case ']': + ++mIndex; + return Token(Token::Type_CloseSquare); + case ',': + ++mIndex; + return Token(Token::Type_Comma); + case '!': + ++mIndex; + return Token(Token::Type_OneShot); } - if (c=='"' || c=='_' || std::isalpha (c) || c==':') + if (c == '"' || c == '_' || isAlpha(c) || c == ':') return getStringToken(); - if (c=='-' || c=='.' || std::isdigit (c)) + if (c == '-' || c == '.' || isDigit(c)) return getNumberToken(); error(); - return Token (Token::Type_None); + return Token(Token::Type_None); } -std::shared_ptr CSMFilter::Parser::parseImp (bool allowEmpty, bool ignoreOneShot) +std::shared_ptr CSMFilter::Parser::parseImp(bool allowEmpty, bool ignoreOneShot) { if (Token token = getNextToken()) { - if (token==Token (Token::Type_OneShot)) + if (token == Token(Token::Type_OneShot)) token = getNextToken(); if (token) @@ -247,16 +301,16 @@ std::shared_ptr CSMFilter::Parser::parseImp (bool allowEmpty, b { case Token::Type_Keyword_True: - return std::shared_ptr (new BooleanNode (true)); + return std::make_shared(true); case Token::Type_Keyword_False: - return std::shared_ptr (new BooleanNode (false)); + return std::make_shared(false); case Token::Type_Keyword_And: case Token::Type_Keyword_Or: - return parseNAry (token); + return parseNAry(token); case Token::Type_Keyword_Not: { @@ -265,7 +319,7 @@ std::shared_ptr CSMFilter::Parser::parseImp (bool allowEmpty, b if (mError) return std::shared_ptr(); - return std::shared_ptr (new NotNode (node)); + return std::make_shared(node); } case Token::Type_Keyword_Text: @@ -292,13 +346,13 @@ std::shared_ptr CSMFilter::Parser::parseImp (bool allowEmpty, b return std::shared_ptr(); } -std::shared_ptr CSMFilter::Parser::parseNAry (const Token& keyword) +std::shared_ptr CSMFilter::Parser::parseNAry(const Token& keyword) { - std::vector > nodes; + std::vector> nodes; Token token = getNextToken(); - if (token.mType!=Token::Type_Open) + if (token.mType != Token::Type_Open) { error(); return std::shared_ptr(); @@ -311,25 +365,29 @@ std::shared_ptr CSMFilter::Parser::parseNAry (const Token& keyw if (mError) return std::shared_ptr(); - nodes.push_back (node); + nodes.push_back(node); token = getNextToken(); - if (!token || (token.mType!=Token::Type_Close && token.mType!=Token::Type_Comma)) + if (!token || (token.mType != Token::Type_Close && token.mType != Token::Type_Comma)) { error(); return std::shared_ptr(); } - if (token.mType==Token::Type_Close) + if (token.mType == Token::Type_Close) break; } switch (keyword.mType) { - case Token::Type_Keyword_And: return std::shared_ptr (new AndNode (nodes)); - case Token::Type_Keyword_Or: return std::shared_ptr (new OrNode (nodes)); - default: error(); return std::shared_ptr(); + case Token::Type_Keyword_And: + return std::make_shared(nodes); + case Token::Type_Keyword_Or: + return std::make_shared(nodes); + default: + error(); + return std::shared_ptr(); } } @@ -337,7 +395,7 @@ std::shared_ptr CSMFilter::Parser::parseText() { Token token = getNextToken(); - if (token.mType!=Token::Type_Open) + if (token.mType != Token::Type_Open) { error(); return std::shared_ptr(); @@ -351,17 +409,17 @@ std::shared_ptr CSMFilter::Parser::parseText() // parse column ID int columnId = -1; - if (token.mType==Token::Type_Number) + if (token.mType == Token::Type_Number) { - if (static_cast (token.mNumber)==token.mNumber) - columnId = static_cast (token.mNumber); + if (static_cast(token.mNumber) == token.mNumber) + columnId = static_cast(token.mNumber); } else if (token.isString()) { - columnId = CSMWorld::Columns::getId (token.mString); + columnId = CSMWorld::Columns::getId(token.mString); } - if (columnId<0) + if (columnId < 0) { error(); return std::shared_ptr(); @@ -369,7 +427,7 @@ std::shared_ptr CSMFilter::Parser::parseText() token = getNextToken(); - if (token.mType!=Token::Type_Comma) + if (token.mType != Token::Type_Comma) { error(); return std::shared_ptr(); @@ -388,20 +446,20 @@ std::shared_ptr CSMFilter::Parser::parseText() token = getNextToken(); - if (token.mType!=Token::Type_Close) + if (token.mType != Token::Type_Close) { error(); return std::shared_ptr(); } - return std::shared_ptr (new TextNode (columnId, text)); + return std::make_shared(columnId, text); } std::shared_ptr CSMFilter::Parser::parseValue() { Token token = getNextToken(); - if (token.mType!=Token::Type_Open) + if (token.mType != Token::Type_Open) { error(); return std::shared_ptr(); @@ -415,17 +473,17 @@ std::shared_ptr CSMFilter::Parser::parseValue() // parse column ID int columnId = -1; - if (token.mType==Token::Type_Number) + if (token.mType == Token::Type_Number) { - if (static_cast (token.mNumber)==token.mNumber) - columnId = static_cast (token.mNumber); + if (static_cast(token.mNumber) == token.mNumber) + columnId = static_cast(token.mNumber); } else if (token.isString()) { - columnId = CSMWorld::Columns::getId (token.mString); + columnId = CSMWorld::Columns::getId(token.mString); } - if (columnId<0) + if (columnId < 0) { error(); return std::shared_ptr(); @@ -433,7 +491,7 @@ std::shared_ptr CSMFilter::Parser::parseValue() token = getNextToken(); - if (token.mType!=Token::Type_Comma) + if (token.mType != Token::Type_Comma) { error(); return std::shared_ptr(); @@ -447,7 +505,7 @@ std::shared_ptr CSMFilter::Parser::parseValue() token = getNextToken(); - if (token.mType==Token::Type_Number) + if (token.mType == Token::Type_Number) { // single value lower = upper = token.mNumber; @@ -456,9 +514,9 @@ std::shared_ptr CSMFilter::Parser::parseValue() else { // interval - if (token.mType==Token::Type_OpenSquare) + if (token.mType == Token::Type_OpenSquare) lowerType = ValueNode::Type_Closed; - else if (token.mType!=Token::Type_CloseSquare && token.mType!=Token::Type_Open) + else if (token.mType != Token::Type_CloseSquare && token.mType != Token::Type_Open) { error(); return std::shared_ptr(); @@ -466,19 +524,19 @@ std::shared_ptr CSMFilter::Parser::parseValue() token = getNextToken(); - if (token.mType==Token::Type_Number) + if (token.mType == Token::Type_Number) { lower = token.mNumber; token = getNextToken(); - if (token.mType!=Token::Type_Comma) + if (token.mType != Token::Type_Comma) { error(); return std::shared_ptr(); } } - else if (token.mType==Token::Type_Comma) + else if (token.mType == Token::Type_Comma) { lowerType = ValueNode::Type_Infinite; } @@ -490,7 +548,7 @@ std::shared_ptr CSMFilter::Parser::parseValue() token = getNextToken(); - if (token.mType==Token::Type_Number) + if (token.mType == Token::Type_Number) { upper = token.mNumber; @@ -499,12 +557,12 @@ std::shared_ptr CSMFilter::Parser::parseValue() else upperType = ValueNode::Type_Infinite; - if (token.mType==Token::Type_CloseSquare) + if (token.mType == Token::Type_CloseSquare) { - if (upperType!=ValueNode::Type_Infinite) + if (upperType != ValueNode::Type_Infinite) upperType = ValueNode::Type_Closed; } - else if (token.mType!=Token::Type_OpenSquare && token.mType!=Token::Type_Close) + else if (token.mType != Token::Type_OpenSquare && token.mType != Token::Type_Close) { error(); return std::shared_ptr(); @@ -513,13 +571,13 @@ std::shared_ptr CSMFilter::Parser::parseValue() token = getNextToken(); - if (token.mType!=Token::Type_Close) + if (token.mType != Token::Type_Close) { error(); return std::shared_ptr(); } - return std::shared_ptr (new ValueNode (columnId, lowerType, upperType, lower, upper)); + return std::make_shared(columnId, lowerType, upperType, lower, upper); } void CSMFilter::Parser::error() @@ -527,10 +585,14 @@ void CSMFilter::Parser::error() mError = true; } -CSMFilter::Parser::Parser (const CSMWorld::Data& data) -: mIndex (0), mError (false), mData (data) {} +CSMFilter::Parser::Parser(const CSMWorld::Data& data) + : mIndex(0) + , mError(false) + , mData(data) +{ +} -bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) +bool CSMFilter::Parser::parse(const std::string& filter, bool allowPredefined) { // reset mFilter.reset(); @@ -543,19 +605,19 @@ bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) if (allowPredefined) token = getNextToken(); - if (allowPredefined && token==Token (Token::Type_EOS)) + if (allowPredefined && token == Token(Token::Type_EOS)) { mFilter.reset(); return true; } - else if (!allowPredefined || token==Token (Token::Type_OneShot)) + else if (!allowPredefined || token == Token(Token::Type_OneShot)) { - std::shared_ptr node = parseImp (true, token!=Token (Token::Type_OneShot)); + std::shared_ptr node = parseImp(true, token != Token(Token::Type_OneShot)); if (mError) return false; - if (getNextToken()!=Token (Token::Type_EOS)) + if (getNextToken() != Token(Token::Type_EOS)) { error(); return false; @@ -566,30 +628,30 @@ bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) else { // Empty filter string equals to filter "true". - mFilter.reset (new BooleanNode (true)); + mFilter = std::make_shared(true); } return true; } // We do not use isString() here, because there could be a pre-defined filter with an ID that is // equal a filter keyword. - else if (token.mType==Token::Type_String) + else if (token.mType == Token::Type_String) { - if (getNextToken()!=Token (Token::Type_EOS)) + if (getNextToken() != Token(Token::Type_EOS)) { error(); return false; } - int index = mData.getFilters().searchId (token.mString); + const int index = mData.getFilters().searchId(ESM::RefId::stringRefId(token.mString)); - if (index==-1) + if (index == -1) { error(); return false; } - const CSMWorld::Record& record = mData.getFilters().getRecord (index); + const CSMWorld::Record& record = mData.getFilters().getRecord(index); if (record.isDeleted()) { @@ -597,7 +659,7 @@ bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) return false; } - return parse (record.get().mFilter, false); + return parse(record.get().mFilter, false); } else { @@ -609,7 +671,7 @@ bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined) std::shared_ptr CSMFilter::Parser::getFilter() const { if (mError) - throw std::logic_error ("No filter available"); + throw std::logic_error("No filter available"); return mFilter; } diff --git a/apps/opencs/model/filter/parser.hpp b/apps/opencs/model/filter/parser.hpp index 344c552ef..d5e3f3aed 100644 --- a/apps/opencs/model/filter/parser.hpp +++ b/apps/opencs/model/filter/parser.hpp @@ -1,7 +1,13 @@ #ifndef CSM_FILTER_PARSER_H #define CSM_FILTER_PARSER_H -#include "node.hpp" +#include +#include + +namespace CSMFilter +{ + class Node; +} namespace CSMWorld { @@ -14,43 +20,42 @@ namespace CSMFilter class Parser { - std::shared_ptr mFilter; - std::string mInput; - int mIndex; - bool mError; - const CSMWorld::Data& mData; + std::shared_ptr mFilter; + std::string mInput; + int mIndex; + bool mError; + const CSMWorld::Data& mData; - Token getStringToken(); + Token getStringToken(); - Token getNumberToken(); + Token getNumberToken(); - Token getNextToken(); + Token getNextToken(); - Token checkKeywords (const Token& token); - ///< Turn string token into keyword token, if possible. + Token checkKeywords(const Token& token); + ///< Turn string token into keyword token, if possible. - std::shared_ptr parseImp (bool allowEmpty = false, bool ignoreOneShot = false); - ///< Will return a null-pointer, if there is nothing more to parse. + std::shared_ptr parseImp(bool allowEmpty = false, bool ignoreOneShot = false); + ///< Will return a null-pointer, if there is nothing more to parse. - std::shared_ptr parseNAry (const Token& keyword); + std::shared_ptr parseNAry(const Token& keyword); - std::shared_ptr parseText(); + std::shared_ptr parseText(); - std::shared_ptr parseValue(); + std::shared_ptr parseValue(); - void error(); + void error(); - public: + public: + Parser(const CSMWorld::Data& data); - Parser (const CSMWorld::Data& data); + bool parse(const std::string& filter, bool allowPredefined = true); + ///< Discards any previous calls to parse + /// + /// \return Success? - bool parse (const std::string& filter, bool allowPredefined = true); - ///< Discards any previous calls to parse - /// - /// \return Success? - - std::shared_ptr getFilter() const; - ///< Throws an exception if the last call to parse did not return true. + std::shared_ptr getFilter() const; + ///< Throws an exception if the last call to parse did not return true. }; } diff --git a/apps/opencs/model/filter/textnode.cpp b/apps/opencs/model/filter/textnode.cpp index 808090dc3..7d837f9e5 100644 --- a/apps/opencs/model/filter/textnode.cpp +++ b/apps/opencs/model/filter/textnode.cpp @@ -1,50 +1,55 @@ #include "textnode.hpp" +#include #include #include +#include -#include +#include #include "../world/columns.hpp" #include "../world/idtablebase.hpp" -CSMFilter::TextNode::TextNode (int columnId, const std::string& text) -: mColumnId (columnId), mText (text) -{} - -bool CSMFilter::TextNode::test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const +CSMFilter::TextNode::TextNode(int columnId, const std::string& text) + : mColumnId(columnId) + , mText(text) + , mRegExp(QRegularExpression::anchoredPattern(QString::fromUtf8(mText.c_str())), + QRegularExpression::CaseInsensitiveOption) { - const std::map::const_iterator iter = columns.find (mColumnId); +} - if (iter==columns.end()) - throw std::logic_error ("invalid column in text node test"); +bool CSMFilter::TextNode::test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const +{ + const std::map::const_iterator iter = columns.find(mColumnId); - if (iter->second==-1) + if (iter == columns.end()) + throw std::logic_error("invalid column in text node test"); + + if (iter->second == -1) return true; - QModelIndex index = table.index (row, iter->second); + QModelIndex index = table.index(row, iter->second); - QVariant data = table.data (index); + QVariant data = table.data(index); QString string; - if (data.type()==QVariant::String) + if (data.type() == QVariant::String) { string = data.toString(); } - else if ((data.type()==QVariant::Int || data.type()==QVariant::UInt) && - CSMWorld::Columns::hasEnums (static_cast (mColumnId))) + else if ((data.type() == QVariant::Int || data.type() == QVariant::UInt) + && CSMWorld::Columns::hasEnums(static_cast(mColumnId))) { int value = data.toInt(); - std::vector> enums = - CSMWorld::Columns::getEnums (static_cast (mColumnId)); + std::vector> enums + = CSMWorld::Columns::getEnums(static_cast(mColumnId)); - if (value>=0 && value (enums.size())) - string = QString::fromUtf8 (enums[value].second.c_str()); + if (value >= 0 && value < static_cast(enums.size())) + string = QString::fromUtf8(enums[value].second.c_str()); } - else if (data.type()==QVariant::Bool) + else if (data.type() == QVariant::Bool) { string = data.toBool() ? "true" : "false"; } @@ -54,17 +59,17 @@ bool CSMFilter::TextNode::test (const CSMWorld::IdTableBase& table, int row, return false; /// \todo make pattern syntax configurable - QRegExp regExp (QString::fromUtf8 (mText.c_str()), Qt::CaseInsensitive); + QRegularExpressionMatch match = mRegExp.match(string); - return regExp.exactMatch (string); + return match.hasMatch(); } std::vector CSMFilter::TextNode::getReferencedColumns() const { - return std::vector (1, mColumnId); + return std::vector(1, mColumnId); } -std::string CSMFilter::TextNode::toString (bool numericColumns) const +std::string CSMFilter::TextNode::toString(bool numericColumns) const { std::ostringstream stream; @@ -73,10 +78,7 @@ std::string CSMFilter::TextNode::toString (bool numericColumns) const if (numericColumns) stream << mColumnId; else - stream - << "\"" - << CSMWorld::Columns::getName (static_cast (mColumnId)) - << "\""; + stream << "\"" << CSMWorld::Columns::getName(static_cast(mColumnId)) << "\""; stream << ", \"" << mText << "\")"; diff --git a/apps/opencs/model/filter/textnode.hpp b/apps/opencs/model/filter/textnode.hpp index 0e7a0e4f5..14efa0a3a 100644 --- a/apps/opencs/model/filter/textnode.hpp +++ b/apps/opencs/model/filter/textnode.hpp @@ -1,32 +1,39 @@ #ifndef CSM_FILTER_TEXTNODE_H #define CSM_FILTER_TEXTNODE_H +#include +#include +#include + +#include + +#include + #include "leafnode.hpp" namespace CSMFilter { class TextNode : public LeafNode { - int mColumnId; - std::string mText; + int mColumnId; + std::string mText; + QRegularExpression mRegExp; - public: + public: + TextNode(int columnId, const std::string& text); - TextNode (int columnId, const std::string& text); + bool test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping - bool test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const override; - ///< \return Can the specified table row pass through to filter? - /// \param columns column ID to column index mapping + std::vector getReferencedColumns() const override; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. - std::vector getReferencedColumns() const override; - ///< Return a list of the IDs of the columns referenced by this node. The column mapping - /// passed into test as columns must contain all columns listed here. - - std::string toString (bool numericColumns) const override; - ///< Return a string that represents this node. - /// - /// \param numericColumns Use numeric IDs instead of string to represent columns. + std::string toString(bool numericColumns) const override; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } diff --git a/apps/opencs/model/filter/unarynode.cpp b/apps/opencs/model/filter/unarynode.cpp index 7211f78b0..277ef406c 100644 --- a/apps/opencs/model/filter/unarynode.cpp +++ b/apps/opencs/model/filter/unarynode.cpp @@ -1,8 +1,12 @@ #include "unarynode.hpp" -CSMFilter::UnaryNode::UnaryNode (std::shared_ptr child, const std::string& name) -: mChild (child), mName (name) -{} +#include + +CSMFilter::UnaryNode::UnaryNode(std::shared_ptr child, const std::string& name) + : mChild(child) + , mName(name) +{ +} const CSMFilter::Node& CSMFilter::UnaryNode::getChild() const { @@ -19,7 +23,7 @@ std::vector CSMFilter::UnaryNode::getReferencedColumns() const return mChild->getReferencedColumns(); } -std::string CSMFilter::UnaryNode::toString (bool numericColumns) const +std::string CSMFilter::UnaryNode::toString(bool numericColumns) const { - return mName + " " + mChild->toString (numericColumns); + return mName + " " + mChild->toString(numericColumns); } diff --git a/apps/opencs/model/filter/unarynode.hpp b/apps/opencs/model/filter/unarynode.hpp index 39326d167..04422d3c0 100644 --- a/apps/opencs/model/filter/unarynode.hpp +++ b/apps/opencs/model/filter/unarynode.hpp @@ -1,31 +1,34 @@ #ifndef CSM_FILTER_UNARYNODE_H #define CSM_FILTER_UNARYNODE_H +#include +#include +#include + #include "node.hpp" namespace CSMFilter { class UnaryNode : public Node { - std::shared_ptr mChild; - std::string mName; + std::shared_ptr mChild; + std::string mName; - public: + public: + UnaryNode(std::shared_ptr child, const std::string& name); - UnaryNode (std::shared_ptr child, const std::string& name); + const Node& getChild() const; - const Node& getChild() const; + Node& getChild(); - Node& getChild(); + std::vector getReferencedColumns() const override; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. - std::vector getReferencedColumns() const override; - ///< Return a list of the IDs of the columns referenced by this node. The column mapping - /// passed into test as columns must contain all columns listed here. - - std::string toString (bool numericColumns) const override; - ///< Return a string that represents this node. - /// - /// \param numericColumns Use numeric IDs instead of string to represent columns. + std::string toString(bool numericColumns) const override; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } diff --git a/apps/opencs/model/filter/valuenode.cpp b/apps/opencs/model/filter/valuenode.cpp index 85c5a8e27..264967f24 100644 --- a/apps/opencs/model/filter/valuenode.cpp +++ b/apps/opencs/model/filter/valuenode.cpp @@ -2,47 +2,65 @@ #include #include +#include -#include "../world/columns.hpp" #include "../world/idtablebase.hpp" -CSMFilter::ValueNode::ValueNode (int columnId, Type lowerType, Type upperType, - double lower, double upper) -: mColumnId (columnId), mLower (lower), mUpper (upper), mLowerType (lowerType), mUpperType (upperType){} - -bool CSMFilter::ValueNode::test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const +CSMFilter::ValueNode::ValueNode(int columnId, Type lowerType, Type upperType, double lower, double upper) + : mColumnId(columnId) + , mLower(lower) + , mUpper(upper) + , mLowerType(lowerType) + , mUpperType(upperType) { - const std::map::const_iterator iter = columns.find (mColumnId); +} - if (iter==columns.end()) - throw std::logic_error ("invalid column in value node test"); +bool CSMFilter::ValueNode::test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const +{ + const std::map::const_iterator iter = columns.find(mColumnId); - if (iter->second==-1) + if (iter == columns.end()) + throw std::logic_error("invalid column in value node test"); + + if (iter->second == -1) return true; - QModelIndex index = table.index (row, iter->second); + QModelIndex index = table.index(row, iter->second); - QVariant data = table.data (index); + QVariant data = table.data(index); - if (data.type()!=QVariant::Double && data.type()!=QVariant::Bool && data.type()!=QVariant::Int && - data.type()!=QVariant::UInt && data.type()!=static_cast (QMetaType::Float)) + if (data.type() != QVariant::Double && data.type() != QVariant::Bool && data.type() != QVariant::Int + && data.type() != QVariant::UInt && data.type() != static_cast(QMetaType::Float)) return false; double value = data.toDouble(); switch (mLowerType) { - case Type_Closed: if (valuemUpper) return false; break; - case Type_Open: if (value>=mUpper) return false; break; - case Type_Infinite: break; + case Type_Closed: + if (value > mUpper) + return false; + break; + case Type_Open: + if (value >= mUpper) + return false; + break; + case Type_Infinite: + break; } return true; @@ -50,10 +68,10 @@ bool CSMFilter::ValueNode::test (const CSMWorld::IdTableBase& table, int row, std::vector CSMFilter::ValueNode::getReferencedColumns() const { - return std::vector (1, mColumnId); + return std::vector(1, mColumnId); } -std::string CSMFilter::ValueNode::toString (bool numericColumns) const +std::string CSMFilter::ValueNode::toString(bool numericColumns) const { std::ostringstream stream; @@ -62,31 +80,40 @@ std::string CSMFilter::ValueNode::toString (bool numericColumns) const if (numericColumns) stream << mColumnId; else - stream - << "\"" - << CSMWorld::Columns::getName (static_cast (mColumnId)) - << "\""; + stream << "\"" << CSMWorld::Columns::getName(static_cast(mColumnId)) << "\""; stream << ", "; - if (mLower==mUpper && mLowerType!=Type_Infinite && mUpperType!=Type_Infinite) + if (mLower == mUpper && mLowerType != Type_Infinite && mUpperType != Type_Infinite) stream << mLower; else { switch (mLowerType) { - case Type_Closed: stream << "[" << mLower; break; - case Type_Open: stream << "(" << mLower; break; - case Type_Infinite: stream << "("; break; + case Type_Closed: + stream << "[" << mLower; + break; + case Type_Open: + stream << "(" << mLower; + break; + case Type_Infinite: + stream << "("; + break; } stream << ", "; switch (mUpperType) { - case Type_Closed: stream << mUpper << "]"; break; - case Type_Open: stream << mUpper << ")"; break; - case Type_Infinite: stream << ")"; break; + case Type_Closed: + stream << mUpper << "]"; + break; + case Type_Open: + stream << mUpper << ")"; + break; + case Type_Infinite: + stream << ")"; + break; } } diff --git a/apps/opencs/model/filter/valuenode.hpp b/apps/opencs/model/filter/valuenode.hpp index 339e4948a..caf4f1862 100644 --- a/apps/opencs/model/filter/valuenode.hpp +++ b/apps/opencs/model/filter/valuenode.hpp @@ -3,43 +3,47 @@ #include "leafnode.hpp" +#include + +#include +#include +#include + namespace CSMFilter { class ValueNode : public LeafNode { - public: + public: + enum Type + { + Type_Closed, + Type_Open, + Type_Infinite + }; - enum Type - { - Type_Closed, Type_Open, Type_Infinite - }; + private: + int mColumnId; + std::string mText; + double mLower; + double mUpper; + Type mLowerType; + Type mUpperType; - private: + public: + ValueNode(int columnId, Type lowerType, Type upperType, double lower, double upper); - int mColumnId; - std::string mText; - double mLower; - double mUpper; - Type mLowerType; - Type mUpperType; + bool test(const CSMWorld::IdTableBase& table, int row, const std::map& columns) const override; + ///< \return Can the specified table row pass through to filter? + /// \param columns column ID to column index mapping - public: + std::vector getReferencedColumns() const override; + ///< Return a list of the IDs of the columns referenced by this node. The column mapping + /// passed into test as columns must contain all columns listed here. - ValueNode (int columnId, Type lowerType, Type upperType, double lower, double upper); - - bool test (const CSMWorld::IdTableBase& table, int row, - const std::map& columns) const override; - ///< \return Can the specified table row pass through to filter? - /// \param columns column ID to column index mapping - - std::vector getReferencedColumns() const override; - ///< Return a list of the IDs of the columns referenced by this node. The column mapping - /// passed into test as columns must contain all columns listed here. - - std::string toString (bool numericColumns) const override; - ///< Return a string that represents this node. - /// - /// \param numericColumns Use numeric IDs instead of string to represent columns. + std::string toString(bool numericColumns) const override; + ///< Return a string that represents this node. + /// + /// \param numericColumns Use numeric IDs instead of string to represent columns. }; } diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index 005875349..c668bc0af 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -1,4 +1,3 @@ - #include "boolsetting.hpp" #include @@ -6,52 +5,56 @@ #include +#include + #include "category.hpp" #include "state.hpp" -CSMPrefs::BoolSetting::BoolSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, bool default_) -: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(nullptr) -{} +CSMPrefs::BoolSetting::BoolSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, bool default_) + : Setting(parent, mutex, key, label) + , mDefault(default_) + , mWidget(nullptr) +{ +} -CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip (const std::string& tooltip) +CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip(const std::string& tooltip) { mTooltip = tooltip; return *this; } -std::pair CSMPrefs::BoolSetting::makeWidgets (QWidget *parent) +std::pair CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) { - mWidget = new QCheckBox (QString::fromUtf8 (getLabel().c_str()), parent); - mWidget->setCheckState (mDefault ? Qt::Checked : Qt::Unchecked); + mWidget = new QCheckBox(QString::fromUtf8(getLabel().c_str()), parent); + mWidget->setCheckState(mDefault ? Qt::Checked : Qt::Unchecked); if (!mTooltip.empty()) { - QString tooltip = QString::fromUtf8 (mTooltip.c_str()); - mWidget->setToolTip (tooltip); + QString tooltip = QString::fromUtf8(mTooltip.c_str()); + mWidget->setToolTip(tooltip); } - connect (mWidget, SIGNAL (stateChanged (int)), this, SLOT (valueChanged (int))); + connect(mWidget, &QCheckBox::stateChanged, this, &BoolSetting::valueChanged); - return std::make_pair (static_cast (nullptr), mWidget); + return std::make_pair(static_cast(nullptr), mWidget); } void CSMPrefs::BoolSetting::updateWidget() { if (mWidget) { - mWidget->setCheckState(getValues().getBool(getKey(), getParent()->getKey()) - ? Qt::Checked - : Qt::Unchecked); + mWidget->setCheckState( + Settings::Manager::getBool(getKey(), getParent()->getKey()) ? Qt::Checked : Qt::Unchecked); } } -void CSMPrefs::BoolSetting::valueChanged (int value) +void CSMPrefs::BoolSetting::valueChanged(int value) { { - QMutexLocker lock (getMutex()); - getValues().setBool (getKey(), getParent()->getKey(), value); + QMutexLocker lock(getMutex()); + Settings::Manager::setBool(getKey(), getParent()->getKey(), value); } - getParent()->getState()->update (*this); + getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index 941cb5037..e75ea1a34 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -3,33 +3,36 @@ #include "setting.hpp" +#include +#include + class QCheckBox; namespace CSMPrefs { + class Category; + class BoolSetting : public Setting { - Q_OBJECT + Q_OBJECT - std::string mTooltip; - bool mDefault; - QCheckBox* mWidget; + std::string mTooltip; + bool mDefault; + QCheckBox* mWidget; - public: + public: + BoolSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, bool default_); - BoolSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, bool default_); + BoolSetting& setTooltip(const std::string& tooltip); - BoolSetting& setTooltip (const std::string& tooltip); + /// Return label, input widget. + std::pair makeWidgets(QWidget* parent) override; - /// Return label, input widget. - std::pair makeWidgets (QWidget *parent) override; + void updateWidget() override; - void updateWidget() override; + private slots: - private slots: - - void valueChanged (int value); + void valueChanged(int value); }; } diff --git a/apps/opencs/model/prefs/category.cpp b/apps/opencs/model/prefs/category.cpp index 6af0ac645..5a82be08f 100644 --- a/apps/opencs/model/prefs/category.cpp +++ b/apps/opencs/model/prefs/category.cpp @@ -6,23 +6,25 @@ #include "setting.hpp" #include "state.hpp" -CSMPrefs::Category::Category (State *parent, const std::string& key) -: mParent (parent), mKey (key) -{} +CSMPrefs::Category::Category(State* parent, const std::string& key) + : mParent(parent) + , mKey(key) +{ +} const std::string& CSMPrefs::Category::getKey() const { return mKey; } -CSMPrefs::State *CSMPrefs::Category::getState() const +CSMPrefs::State* CSMPrefs::Category::getState() const { return mParent; } -void CSMPrefs::Category::addSetting (Setting *setting) +void CSMPrefs::Category::addSetting(Setting* setting) { - mSettings.push_back (setting); + mSettings.push_back(setting); } CSMPrefs::Category::Iterator CSMPrefs::Category::begin() @@ -35,17 +37,17 @@ CSMPrefs::Category::Iterator CSMPrefs::Category::end() return mSettings.end(); } -CSMPrefs::Setting& CSMPrefs::Category::operator[] (const std::string& key) +CSMPrefs::Setting& CSMPrefs::Category::operator[](const std::string& key) { - for (Iterator iter = mSettings.begin(); iter!=mSettings.end(); ++iter) - if ((*iter)->getKey()==key) + for (Iterator iter = mSettings.begin(); iter != mSettings.end(); ++iter) + if ((*iter)->getKey() == key) return **iter; - throw std::logic_error ("Invalid user setting: " + key); + throw std::logic_error("Invalid user setting: " + key); } void CSMPrefs::Category::update() { - for (Iterator iter = mSettings.begin(); iter!=mSettings.end(); ++iter) - mParent->update (**iter); + for (Iterator iter = mSettings.begin(); iter != mSettings.end(); ++iter) + mParent->update(**iter); } diff --git a/apps/opencs/model/prefs/category.hpp b/apps/opencs/model/prefs/category.hpp index b70716aa0..5c75f9906 100644 --- a/apps/opencs/model/prefs/category.hpp +++ b/apps/opencs/model/prefs/category.hpp @@ -1,6 +1,7 @@ #ifndef CSM_PREFS_CATEGORY_H #define CSM_PREFS_CATEGORY_H +#include #include #include @@ -11,34 +12,31 @@ namespace CSMPrefs class Category { - public: + public: + typedef std::vector Container; + typedef Container::iterator Iterator; - typedef std::vector Container; - typedef Container::iterator Iterator; + private: + State* mParent; + std::string mKey; + Container mSettings; - private: + public: + Category(State* parent, const std::string& key); - State *mParent; - std::string mKey; - Container mSettings; + const std::string& getKey() const; - public: + State* getState() const; - Category (State *parent, const std::string& key); + void addSetting(Setting* setting); - const std::string& getKey() const; + Iterator begin(); - State *getState() const; + Iterator end(); - void addSetting (Setting *setting); + Setting& operator[](const std::string& key); - Iterator begin(); - - Iterator end(); - - Setting& operator[] (const std::string& key); - - void update(); + void update(); }; } diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index e676ad91c..e33ae78a1 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -1,4 +1,3 @@ - #include "coloursetting.hpp" #include @@ -7,56 +6,60 @@ #include +#include + #include "../../view/widget/coloreditor.hpp" #include "category.hpp" #include "state.hpp" -CSMPrefs::ColourSetting::ColourSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, QColor default_) -: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(nullptr) -{} +CSMPrefs::ColourSetting::ColourSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, QColor default_) + : Setting(parent, mutex, key, label) + , mDefault(default_) + , mWidget(nullptr) +{ +} -CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip (const std::string& tooltip) +CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip(const std::string& tooltip) { mTooltip = tooltip; return *this; } -std::pair CSMPrefs::ColourSetting::makeWidgets (QWidget *parent) +std::pair CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) { - QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); - mWidget = new CSVWidget::ColorEditor (mDefault, parent); + mWidget = new CSVWidget::ColorEditor(mDefault, parent); if (!mTooltip.empty()) { - QString tooltip = QString::fromUtf8 (mTooltip.c_str()); - label->setToolTip (tooltip); - mWidget->setToolTip (tooltip); + QString tooltip = QString::fromUtf8(mTooltip.c_str()); + label->setToolTip(tooltip); + mWidget->setToolTip(tooltip); } - connect (mWidget, SIGNAL (pickingFinished()), this, SLOT (valueChanged())); + connect(mWidget, &CSVWidget::ColorEditor::pickingFinished, this, &ColourSetting::valueChanged); - return std::make_pair (label, mWidget); + return std::make_pair(label, mWidget); } void CSMPrefs::ColourSetting::updateWidget() { if (mWidget) { - mWidget->setColor(QString::fromStdString - (getValues().getString(getKey(), getParent()->getKey()))); + mWidget->setColor(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); } } void CSMPrefs::ColourSetting::valueChanged() { - CSVWidget::ColorEditor& widget = dynamic_cast (*sender()); + CSVWidget::ColorEditor& widget = dynamic_cast(*sender()); { - QMutexLocker lock (getMutex()); - getValues().setString (getKey(), getParent()->getKey(), widget.color().name().toUtf8().data()); + QMutexLocker lock(getMutex()); + Settings::Manager::setString(getKey(), getParent()->getKey(), widget.color().name().toUtf8().data()); } - getParent()->getState()->update (*this); + getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index 4a814c0e2..0c22d9cc5 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -5,6 +5,13 @@ #include +#include +#include + +class QMutex; +class QObject; +class QWidget; + namespace CSVWidget { class ColorEditor; @@ -12,30 +19,29 @@ namespace CSVWidget namespace CSMPrefs { + class Category; class ColourSetting : public Setting { - Q_OBJECT + Q_OBJECT - std::string mTooltip; - QColor mDefault; - CSVWidget::ColorEditor* mWidget; + std::string mTooltip; + QColor mDefault; + CSVWidget::ColorEditor* mWidget; - public: + public: + ColourSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, QColor default_); - ColourSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, - QColor default_); + ColourSetting& setTooltip(const std::string& tooltip); - ColourSetting& setTooltip (const std::string& tooltip); + /// Return label, input widget. + std::pair makeWidgets(QWidget* parent) override; - /// Return label, input widget. - std::pair makeWidgets (QWidget *parent) override; + void updateWidget() override; - void updateWidget() override; + private slots: - private slots: - - void valueChanged(); + void valueChanged(); }; } diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 757b67389..7e3aadb0c 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -3,21 +3,27 @@ #include -#include #include +#include #include #include +#include + #include "category.hpp" #include "state.hpp" -CSMPrefs::DoubleSetting::DoubleSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, double default_) -: Setting (parent, values, mutex, key, label), - mPrecision(2), mMin (0), mMax (std::numeric_limits::max()), - mDefault (default_), mWidget(nullptr) -{} +CSMPrefs::DoubleSetting::DoubleSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, double default_) + : Setting(parent, mutex, key, label) + , mPrecision(2) + , mMin(0) + , mMax(std::numeric_limits::max()) + , mDefault(default_) + , mWidget(nullptr) +{ +} CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setPrecision(int precision) { @@ -25,66 +31,66 @@ CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setPrecision(int precision) return *this; } -CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setRange (double min, double max) +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setRange(double min, double max) { mMin = min; mMax = max; return *this; } -CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMin (double min) +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMin(double min) { mMin = min; return *this; } -CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMax (double max) +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMax(double max) { mMax = max; return *this; } -CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip (const std::string& tooltip) +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip(const std::string& tooltip) { mTooltip = tooltip; return *this; } -std::pair CSMPrefs::DoubleSetting::makeWidgets (QWidget *parent) +std::pair CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) { - QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); - mWidget = new QDoubleSpinBox (parent); + mWidget = new QDoubleSpinBox(parent); mWidget->setDecimals(mPrecision); - mWidget->setRange (mMin, mMax); - mWidget->setValue (mDefault); + mWidget->setRange(mMin, mMax); + mWidget->setValue(mDefault); if (!mTooltip.empty()) { - QString tooltip = QString::fromUtf8 (mTooltip.c_str()); - label->setToolTip (tooltip); - mWidget->setToolTip (tooltip); + QString tooltip = QString::fromUtf8(mTooltip.c_str()); + label->setToolTip(tooltip); + mWidget->setToolTip(tooltip); } - connect (mWidget, SIGNAL (valueChanged (double)), this, SLOT (valueChanged (double))); + connect(mWidget, qOverload(&QDoubleSpinBox::valueChanged), this, &DoubleSetting::valueChanged); - return std::make_pair (label, mWidget); + return std::make_pair(label, mWidget); } void CSMPrefs::DoubleSetting::updateWidget() { if (mWidget) { - mWidget->setValue(getValues().getFloat(getKey(), getParent()->getKey())); + mWidget->setValue(Settings::Manager::getFloat(getKey(), getParent()->getKey())); } } -void CSMPrefs::DoubleSetting::valueChanged (double value) +void CSMPrefs::DoubleSetting::valueChanged(double value) { { - QMutexLocker lock (getMutex()); - getValues().setFloat (getKey(), getParent()->getKey(), value); + QMutexLocker lock(getMutex()); + Settings::Manager::setFloat(getKey(), getParent()->getKey(), value); } - getParent()->getState()->update (*this); + getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index 47886e446..c951d2a88 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -7,42 +7,42 @@ class QDoubleSpinBox; namespace CSMPrefs { + class Category; + class DoubleSetting : public Setting { - Q_OBJECT + Q_OBJECT - int mPrecision; - double mMin; - double mMax; - std::string mTooltip; - double mDefault; - QDoubleSpinBox* mWidget; + int mPrecision; + double mMin; + double mMax; + std::string mTooltip; + double mDefault; + QDoubleSpinBox* mWidget; - public: + public: + DoubleSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, double default_); - DoubleSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, - double default_); + DoubleSetting& setPrecision(int precision); - DoubleSetting& setPrecision (int precision); + // defaults to [0, std::numeric_limits::max()] + DoubleSetting& setRange(double min, double max); - // defaults to [0, std::numeric_limits::max()] - DoubleSetting& setRange (double min, double max); + DoubleSetting& setMin(double min); - DoubleSetting& setMin (double min); + DoubleSetting& setMax(double max); - DoubleSetting& setMax (double max); + DoubleSetting& setTooltip(const std::string& tooltip); - DoubleSetting& setTooltip (const std::string& tooltip); + /// Return label, input widget. + std::pair makeWidgets(QWidget* parent) override; - /// Return label, input widget. - std::pair makeWidgets (QWidget *parent) override; + void updateWidget() override; - void updateWidget() override; + private slots: - private slots: - - void valueChanged (double value); + void valueChanged(double value); }; } diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index ec3fca328..a3ac9bce2 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -1,124 +1,130 @@ - #include "enumsetting.hpp" -#include #include +#include #include #include +#include +#include + +#include + #include #include "category.hpp" #include "state.hpp" - -CSMPrefs::EnumValue::EnumValue (const std::string& value, const std::string& tooltip) -: mValue (value), mTooltip (tooltip) -{} - -CSMPrefs::EnumValue::EnumValue (const char *value) -: mValue (value) -{} - - -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const EnumValues& values) +CSMPrefs::EnumValue::EnumValue(const std::string& value, const std::string& tooltip) + : mValue(value) + , mTooltip(tooltip) { - mValues.insert (mValues.end(), values.mValues.begin(), values.mValues.end()); +} + +CSMPrefs::EnumValue::EnumValue(const char* value) + : mValue(value) +{ +} + +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const EnumValues& values) +{ + mValues.insert(mValues.end(), values.mValues.begin(), values.mValues.end()); return *this; } -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const EnumValue& value) +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const EnumValue& value) { - mValues.push_back (value); + mValues.push_back(value); return *this; } -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const std::string& value, const std::string& tooltip) +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const std::string& value, const std::string& tooltip) { mValues.emplace_back(value, tooltip); return *this; } +CSMPrefs::EnumSetting::EnumSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, const EnumValue& default_) + : Setting(parent, mutex, key, label) + , mDefault(default_) + , mWidget(nullptr) +{ +} -CSMPrefs::EnumSetting::EnumSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, const EnumValue& default_) -: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(nullptr) -{} - -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip (const std::string& tooltip) +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip(const std::string& tooltip) { mTooltip = tooltip; return *this; } -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValues (const EnumValues& values) +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValues(const EnumValues& values) { - mValues.add (values); + mValues.add(values); return *this; } -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue (const EnumValue& value) +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const EnumValue& value) { - mValues.add (value); + mValues.add(value); return *this; } -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue (const std::string& value, const std::string& tooltip) +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const std::string& value, const std::string& tooltip) { - mValues.add (value, tooltip); + mValues.add(value, tooltip); return *this; } -std::pair CSMPrefs::EnumSetting::makeWidgets (QWidget *parent) +std::pair CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) { - QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); - mWidget = new QComboBox (parent); + mWidget = new QComboBox(parent); - int index = 0; + size_t index = 0; - for (int i=0; i (mValues.mValues.size()); ++i) + for (size_t i = 0; i < mValues.mValues.size(); ++i) { - if (mDefault.mValue==mValues.mValues[i].mValue) + if (mDefault.mValue == mValues.mValues[i].mValue) index = i; - mWidget->addItem (QString::fromUtf8 (mValues.mValues[i].mValue.c_str())); + mWidget->addItem(QString::fromUtf8(mValues.mValues[i].mValue.c_str())); if (!mValues.mValues[i].mTooltip.empty()) - mWidget->setItemData (i, QString::fromUtf8 (mValues.mValues[i].mTooltip.c_str()), - Qt::ToolTipRole); + mWidget->setItemData(i, QString::fromUtf8(mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole); } - mWidget->setCurrentIndex (index); + mWidget->setCurrentIndex(static_cast(index)); if (!mTooltip.empty()) { - QString tooltip = QString::fromUtf8 (mTooltip.c_str()); - label->setToolTip (tooltip); + QString tooltip = QString::fromUtf8(mTooltip.c_str()); + label->setToolTip(tooltip); } - connect (mWidget, SIGNAL (currentIndexChanged (int)), this, SLOT (valueChanged (int))); + connect(mWidget, qOverload(&QComboBox::currentIndexChanged), this, &EnumSetting::valueChanged); - return std::make_pair (label, mWidget); + return std::make_pair(label, mWidget); } void CSMPrefs::EnumSetting::updateWidget() { if (mWidget) { - int index = mWidget->findText(QString::fromStdString - (getValues().getString(getKey(), getParent()->getKey()))); + int index + = mWidget->findText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); mWidget->setCurrentIndex(index); } } -void CSMPrefs::EnumSetting::valueChanged (int value) +void CSMPrefs::EnumSetting::valueChanged(int value) { { - QMutexLocker lock (getMutex()); - getValues().setString (getKey(), getParent()->getKey(), mValues.mValues.at (value).mValue); + QMutexLocker lock(getMutex()); + Settings::Manager::setString(getKey(), getParent()->getKey(), mValues.mValues.at(value).mValue); } - getParent()->getState()->update (*this); + getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index 235f6adc3..57bd2115c 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -1,6 +1,8 @@ #ifndef CSM_PREFS_ENUMSETTING_H #define CSM_PREFS_ENUMSETTING_H +#include +#include #include #include "setting.hpp" @@ -9,58 +11,58 @@ class QComboBox; namespace CSMPrefs { + class Category; + struct EnumValue { std::string mValue; std::string mTooltip; - EnumValue (const std::string& value, const std::string& tooltip = ""); + EnumValue(const std::string& value, const std::string& tooltip = ""); - EnumValue (const char *value); + EnumValue(const char* value); }; struct EnumValues { std::vector mValues; - EnumValues& add (const EnumValues& values); + EnumValues& add(const EnumValues& values); - EnumValues& add (const EnumValue& value); + EnumValues& add(const EnumValue& value); - EnumValues& add (const std::string& value, const std::string& tooltip); + EnumValues& add(const std::string& value, const std::string& tooltip); }; class EnumSetting : public Setting { - Q_OBJECT + Q_OBJECT - std::string mTooltip; - EnumValue mDefault; - EnumValues mValues; - QComboBox* mWidget; + std::string mTooltip; + EnumValue mDefault; + EnumValues mValues; + QComboBox* mWidget; - public: + public: + EnumSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, + const EnumValue& default_); - EnumSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, - const EnumValue& default_); + EnumSetting& setTooltip(const std::string& tooltip); - EnumSetting& setTooltip (const std::string& tooltip); + EnumSetting& addValues(const EnumValues& values); - EnumSetting& addValues (const EnumValues& values); + EnumSetting& addValue(const EnumValue& value); - EnumSetting& addValue (const EnumValue& value); + EnumSetting& addValue(const std::string& value, const std::string& tooltip); - EnumSetting& addValue (const std::string& value, const std::string& tooltip); + /// Return label, input widget. + std::pair makeWidgets(QWidget* parent) override; - /// Return label, input widget. - std::pair makeWidgets (QWidget *parent) override; + void updateWidget() override; - void updateWidget() override; + private slots: - private slots: - - void valueChanged (int value); + void valueChanged(int value); }; } diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 407ed11f0..90cc77c78 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -4,79 +4,85 @@ #include #include -#include #include +#include #include +#include + #include "category.hpp" #include "state.hpp" -CSMPrefs::IntSetting::IntSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, int default_) -: Setting (parent, values, mutex, key, label), mMin (0), mMax (std::numeric_limits::max()), - mDefault (default_), mWidget(nullptr) -{} +CSMPrefs::IntSetting::IntSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, int default_) + : Setting(parent, mutex, key, label) + , mMin(0) + , mMax(std::numeric_limits::max()) + , mDefault(default_) + , mWidget(nullptr) +{ +} -CSMPrefs::IntSetting& CSMPrefs::IntSetting::setRange (int min, int max) +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setRange(int min, int max) { mMin = min; mMax = max; return *this; } -CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMin (int min) +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMin(int min) { mMin = min; return *this; } -CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMax (int max) +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMax(int max) { mMax = max; return *this; } -CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip (const std::string& tooltip) +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip(const std::string& tooltip) { mTooltip = tooltip; return *this; } -std::pair CSMPrefs::IntSetting::makeWidgets (QWidget *parent) +std::pair CSMPrefs::IntSetting::makeWidgets(QWidget* parent) { - QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); - mWidget = new QSpinBox (parent); - mWidget->setRange (mMin, mMax); - mWidget->setValue (mDefault); + mWidget = new QSpinBox(parent); + mWidget->setRange(mMin, mMax); + mWidget->setValue(mDefault); if (!mTooltip.empty()) { - QString tooltip = QString::fromUtf8 (mTooltip.c_str()); - label->setToolTip (tooltip); - mWidget->setToolTip (tooltip); + QString tooltip = QString::fromUtf8(mTooltip.c_str()); + label->setToolTip(tooltip); + mWidget->setToolTip(tooltip); } - connect (mWidget, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); + connect(mWidget, qOverload(&QSpinBox::valueChanged), this, &IntSetting::valueChanged); - return std::make_pair (label, mWidget); + return std::make_pair(label, mWidget); } void CSMPrefs::IntSetting::updateWidget() { if (mWidget) { - mWidget->setValue(getValues().getInt(getKey(), getParent()->getKey())); + mWidget->setValue(Settings::Manager::getInt(getKey(), getParent()->getKey())); } } -void CSMPrefs::IntSetting::valueChanged (int value) +void CSMPrefs::IntSetting::valueChanged(int value) { { - QMutexLocker lock (getMutex()); - getValues().setInt (getKey(), getParent()->getKey(), value); + QMutexLocker lock(getMutex()); + Settings::Manager::setInt(getKey(), getParent()->getKey(), value); } - getParent()->getState()->update (*this); + getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index f18213b77..8a655178a 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -4,41 +4,44 @@ #include "setting.hpp" class QSpinBox; +class QMutex; +class QObject; +class QWidget; namespace CSMPrefs { + class Category; + class IntSetting : public Setting { - Q_OBJECT + Q_OBJECT - int mMin; - int mMax; - std::string mTooltip; - int mDefault; - QSpinBox* mWidget; + int mMin; + int mMax; + std::string mTooltip; + int mDefault; + QSpinBox* mWidget; - public: + public: + IntSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, int default_); - IntSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, int default_); + // defaults to [0, std::numeric_limits::max()] + IntSetting& setRange(int min, int max); - // defaults to [0, std::numeric_limits::max()] - IntSetting& setRange (int min, int max); + IntSetting& setMin(int min); - IntSetting& setMin (int min); + IntSetting& setMax(int max); - IntSetting& setMax (int max); + IntSetting& setTooltip(const std::string& tooltip); - IntSetting& setTooltip (const std::string& tooltip); + /// Return label, input widget. + std::pair makeWidgets(QWidget* parent) override; - /// Return label, input widget. - std::pair makeWidgets (QWidget *parent) override; + void updateWidget() override; - void updateWidget() override; + private slots: - private slots: - - void valueChanged (int value); + void valueChanged(int value); }; } diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 288926d00..8752a4d51 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -5,16 +5,22 @@ #include #include #include -#include -#include "state.hpp" +#include + +#include +#include + #include "shortcutmanager.hpp" +#include "state.hpp" + +class QObject; +class QWidget; namespace CSMPrefs { - ModifierSetting::ModifierSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, - const std::string& label) - : Setting(parent, values, mutex, key, label) + ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) + : Setting(parent, mutex, key, label) , mButton(nullptr) , mEditorActive(false) { @@ -38,7 +44,7 @@ namespace CSMPrefs mButton = widget; - connect(widget, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool))); + connect(widget, &QPushButton::toggled, this, &ModifierSetting::buttonToggled); return std::make_pair(label, widget); } @@ -47,7 +53,7 @@ namespace CSMPrefs { if (mButton) { - std::string shortcut = getValues().getString(getKey(), getParent()->getKey()); + const std::string& shortcut = Settings::Manager::getString(getKey(), getParent()->getKey()); int modifier; State::get().getShortcutManager().convertFromString(shortcut, modifier); @@ -88,10 +94,7 @@ namespace CSMPrefs bool ModifierSetting::handleEvent(QObject* target, int mod, int value) { // For potential future exceptions - const int Blacklist[] = - { - 0 - }; + const int Blacklist[] = { 0 }; const size_t BlacklistSize = sizeof(Blacklist) / sizeof(int); @@ -116,7 +119,6 @@ namespace CSMPrefs return true; } - // Update modifier int modifier = value; storeValue(modifier); @@ -135,7 +137,7 @@ namespace CSMPrefs { QMutexLocker lock(getMutex()); - getValues().setString(getKey(), getParent()->getKey(), value); + Settings::Manager::setString(getKey(), getParent()->getKey(), value); } getParent()->getState()->update(*this); diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index 977badb8d..ae984243a 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -1,45 +1,46 @@ #ifndef CSM_PREFS_MODIFIERSETTING_H #define CSM_PREFS_MODIFIERSETTING_H -#include - #include "setting.hpp" +#include +#include + +class QMutex; +class QObject; +class QWidget; class QEvent; class QPushButton; namespace CSMPrefs { + class Category; class ModifierSetting : public Setting { - Q_OBJECT + Q_OBJECT - public: + public: + ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); - ModifierSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, - const std::string& label); + std::pair makeWidgets(QWidget* parent) override; - std::pair makeWidgets(QWidget* parent) override; + void updateWidget() override; - void updateWidget() override; + protected: + bool eventFilter(QObject* target, QEvent* event) override; - protected: + private: + bool handleEvent(QObject* target, int mod, int value); - bool eventFilter(QObject* target, QEvent* event) override; + void storeValue(int modifier); + void resetState(); - private: + QPushButton* mButton; + bool mEditorActive; - bool handleEvent(QObject* target, int mod, int value); + private slots: - void storeValue(int modifier); - void resetState(); - - QPushButton* mButton; - bool mEditorActive; - - private slots: - - void buttonToggled(bool checked); + void buttonToggled(bool checked); }; } diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index 165062232..efe360d1e 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -4,37 +4,33 @@ #include #include +#include + #include "category.hpp" #include "state.hpp" -Settings::Manager& CSMPrefs::Setting::getValues() -{ - return *mValues; -} - -QMutex *CSMPrefs::Setting::getMutex() +QMutex* CSMPrefs::Setting::getMutex() { return mMutex; } -CSMPrefs::Setting::Setting (Category *parent, Settings::Manager *values, QMutex *mutex, - const std::string& key, const std::string& label) -: QObject (parent->getState()), mParent (parent), mValues (values), mMutex (mutex), mKey (key), - mLabel (label) -{} - -CSMPrefs::Setting:: ~Setting() {} - -std::pair CSMPrefs::Setting::makeWidgets (QWidget *parent) -{ - return std::pair (0, 0); -} - -void CSMPrefs::Setting::updateWidget() +CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) + : QObject(parent->getState()) + , mParent(parent) + , mMutex(mutex) + , mKey(key) + , mLabel(label) { } -const CSMPrefs::Category *CSMPrefs::Setting::getParent() const +std::pair CSMPrefs::Setting::makeWidgets(QWidget* parent) +{ + return std::pair(0, 0); +} + +void CSMPrefs::Setting::updateWidget() {} + +const CSMPrefs::Category* CSMPrefs::Setting::getParent() const { return mParent; } @@ -51,51 +47,51 @@ const std::string& CSMPrefs::Setting::getLabel() const int CSMPrefs::Setting::toInt() const { - QMutexLocker lock (mMutex); - return mValues->getInt (mKey, mParent->getKey()); + QMutexLocker lock(mMutex); + return Settings::Manager::getInt(mKey, mParent->getKey()); } double CSMPrefs::Setting::toDouble() const { - QMutexLocker lock (mMutex); - return mValues->getFloat (mKey, mParent->getKey()); + QMutexLocker lock(mMutex); + return Settings::Manager::getFloat(mKey, mParent->getKey()); } std::string CSMPrefs::Setting::toString() const { - QMutexLocker lock (mMutex); - return mValues->getString (mKey, mParent->getKey()); + QMutexLocker lock(mMutex); + return Settings::Manager::getString(mKey, mParent->getKey()); } bool CSMPrefs::Setting::isTrue() const { - QMutexLocker lock (mMutex); - return mValues->getBool (mKey, mParent->getKey()); + QMutexLocker lock(mMutex); + return Settings::Manager::getBool(mKey, mParent->getKey()); } QColor CSMPrefs::Setting::toColor() const { // toString() handles lock - return QColor (QString::fromUtf8 (toString().c_str())); + return QColor(QString::fromUtf8(toString().c_str())); } -bool CSMPrefs::operator== (const Setting& setting, const std::string& key) +bool CSMPrefs::operator==(const Setting& setting, const std::string& key) { std::string fullKey = setting.getParent()->getKey() + "/" + setting.getKey(); - return fullKey==key; + return fullKey == key; } -bool CSMPrefs::operator== (const std::string& key, const Setting& setting) +bool CSMPrefs::operator==(const std::string& key, const Setting& setting) { - return setting==key; + return setting == key; } -bool CSMPrefs::operator!= (const Setting& setting, const std::string& key) +bool CSMPrefs::operator!=(const Setting& setting, const std::string& key) { - return !(setting==key); + return !(setting == key); } -bool CSMPrefs::operator!= (const std::string& key, const Setting& setting) +bool CSMPrefs::operator!=(const std::string& key, const Setting& setting) { - return !(key==setting); + return !(key == setting); } diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index 7cb2d7acf..f63271b3f 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -10,70 +10,60 @@ class QWidget; class QColor; class QMutex; -namespace Settings -{ - class Manager; -} - namespace CSMPrefs { class Category; class Setting : public QObject { - Q_OBJECT + Q_OBJECT - Category *mParent; - Settings::Manager *mValues; - QMutex *mMutex; - std::string mKey; - std::string mLabel; + Category* mParent; + QMutex* mMutex; + std::string mKey; + std::string mLabel; - protected: + protected: + QMutex* getMutex(); - Settings::Manager& getValues(); + public: + Setting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); - QMutex *getMutex(); + ~Setting() override = default; - public: + /// Return label, input widget. + /// + /// \note first can be a 0-pointer, which means that the label is part of the input + /// widget. + virtual std::pair makeWidgets(QWidget* parent); - Setting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label); + /// Updates the widget returned by makeWidgets() to the current setting. + /// + /// \note If make_widgets() has not been called yet then nothing happens. + virtual void updateWidget(); - virtual ~Setting(); + const Category* getParent() const; - /// Return label, input widget. - /// - /// \note first can be a 0-pointer, which means that the label is part of the input - /// widget. - virtual std::pair makeWidgets (QWidget *parent); + const std::string& getKey() const; - /// Updates the widget returned by makeWidgets() to the current setting. - /// - /// \note If make_widgets() has not been called yet then nothing happens. - virtual void updateWidget(); + const std::string& getLabel() const; - const Category *getParent() const; + int toInt() const; - const std::string& getKey() const; + double toDouble() const; - const std::string& getLabel() const; + std::string toString() const; - int toInt() const; + bool isTrue() const; - double toDouble() const; - - std::string toString() const; - - bool isTrue() const; - - QColor toColor() const; + QColor toColor() const; }; // note: fullKeys have the format categoryKey/settingKey - bool operator== (const Setting& setting, const std::string& fullKey); - bool operator== (const std::string& fullKey, const Setting& setting); - bool operator!= (const Setting& setting, const std::string& fullKey); - bool operator!= (const std::string& fullKey, const Setting& setting); + bool operator==(const Setting& setting, const std::string& fullKey); + bool operator==(const std::string& fullKey, const Setting& setting); + bool operator!=(const Setting& setting, const std::string& fullKey); + bool operator!=(const std::string& fullKey, const Setting& setting); } #endif diff --git a/apps/opencs/model/prefs/shortcut.cpp b/apps/opencs/model/prefs/shortcut.cpp index ff7b949a4..673627e67 100644 --- a/apps/opencs/model/prefs/shortcut.cpp +++ b/apps/opencs/model/prefs/shortcut.cpp @@ -1,14 +1,15 @@ #include "shortcut.hpp" #include +#include #include #include #include -#include "state.hpp" #include "shortcutmanager.hpp" +#include "state.hpp" namespace CSMPrefs { @@ -25,7 +26,7 @@ namespace CSMPrefs , mModifierStatus(false) , mAction(nullptr) { - assert (parent); + assert(parent); State::get().getShortcutManager().addShortcut(this); State::get().getShortcutManager().getSequence(name, mSequence); @@ -44,7 +45,7 @@ namespace CSMPrefs , mModifierStatus(false) , mAction(nullptr) { - assert (parent); + assert(parent); State::get().getShortcutManager().addShortcut(this); State::get().getShortcutManager().getSequence(name, mSequence); @@ -64,7 +65,7 @@ namespace CSMPrefs , mModifierStatus(false) , mAction(nullptr) { - assert (parent); + assert(parent); State::get().getShortcutManager().addShortcut(this); State::get().getShortcutManager().getSequence(name, mSequence); @@ -77,7 +78,7 @@ namespace CSMPrefs { State::get().getShortcutManager().removeShortcut(this); } - catch(const std::exception& e) + catch (const std::exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } @@ -176,8 +177,8 @@ namespace CSMPrefs { mAction->setText(mActionText); - disconnect(this, SIGNAL(activated()), mAction, SLOT(trigger())); - disconnect(mAction, SIGNAL(destroyed()), this, SLOT(actionDeleted())); + disconnect(this, qOverload<>(&Shortcut::activated), mAction, &QAction::trigger); + disconnect(mAction, &QAction::destroyed, this, &Shortcut::actionDeleted); } mAction = action; @@ -187,8 +188,8 @@ namespace CSMPrefs mActionText = mAction->text(); mAction->setText(mActionText + "\t" + State::get().getShortcutManager().convertToString(mSequence).data()); - connect(this, SIGNAL(activated()), mAction, SLOT(trigger())); - connect(mAction, SIGNAL(destroyed()), this, SLOT(actionDeleted())); + connect(this, qOverload<>(&Shortcut::activated), mAction, &QAction::trigger); + connect(mAction, &QAction::destroyed, this, &Shortcut::actionDeleted); } } diff --git a/apps/opencs/model/prefs/shortcut.hpp b/apps/opencs/model/prefs/shortcut.hpp index 4fa6f8a1a..b2a17b8cb 100644 --- a/apps/opencs/model/prefs/shortcut.hpp +++ b/apps/opencs/model/prefs/shortcut.hpp @@ -15,107 +15,105 @@ namespace CSMPrefs /// A class similar in purpose to QShortcut, but with the ability to use mouse buttons class Shortcut : public QObject { - Q_OBJECT + Q_OBJECT - public: + public: + enum ActivationStatus + { + AS_Regular, + AS_Secondary, + AS_Inactive + }; - enum ActivationStatus - { - AS_Regular, - AS_Secondary, - AS_Inactive - }; + enum SecondaryMode + { + SM_Replace, ///< The secondary signal replaces the regular signal when the modifier is active + SM_Detach, ///< The secondary signal is emitted independent of the regular signal, even if not active + SM_Ignore ///< The secondary signal will not ever be emitted + }; - enum SecondaryMode - { - SM_Replace, ///< The secondary signal replaces the regular signal when the modifier is active - SM_Detach, ///< The secondary signal is emitted independent of the regular signal, even if not active - SM_Ignore ///< The secondary signal will not ever be emitted - }; + Shortcut(const std::string& name, QWidget* parent); + Shortcut(const std::string& name, const std::string& modName, QWidget* parent); + Shortcut(const std::string& name, const std::string& modName, SecondaryMode secMode, QWidget* parent); - Shortcut(const std::string& name, QWidget* parent); - Shortcut(const std::string& name, const std::string& modName, QWidget* parent); - Shortcut(const std::string& name, const std::string& modName, SecondaryMode secMode, QWidget* parent); + ~Shortcut(); - ~Shortcut(); + bool isEnabled() const; - bool isEnabled() const; + const std::string& getName() const; + const std::string& getModifierName() const; - const std::string& getName() const; - const std::string& getModifierName() const; + SecondaryMode getSecondaryMode() const; - SecondaryMode getSecondaryMode() const; + const QKeySequence& getSequence() const; + int getModifier() const; - const QKeySequence& getSequence() const; - int getModifier() const; + /// The position in the sequence + int getPosition() const; + /// The position in the sequence + int getLastPosition() const; - /// The position in the sequence - int getPosition() const; - /// The position in the sequence - int getLastPosition() const; + ActivationStatus getActivationStatus() const; + bool getModifierStatus() const; - ActivationStatus getActivationStatus() const; - bool getModifierStatus() const; + void enable(bool state); - void enable(bool state); + void setSequence(const QKeySequence& sequence); + void setModifier(int modifier); - void setSequence(const QKeySequence& sequence); - void setModifier(int modifier); + /// The position in the sequence + void setPosition(int pos); - /// The position in the sequence - void setPosition(int pos); + void setActivationStatus(ActivationStatus status); + void setModifierStatus(bool status); - void setActivationStatus(ActivationStatus status); - void setModifierStatus(bool status); + /// Appends the sequence to the QAction text, also keeps it up to date + void associateAction(QAction* action); - /// Appends the sequence to the QAction text, also keeps it up to date - void associateAction(QAction* action); + // Workaround for Qt4 signals being "protected" + void signalActivated(bool state); + void signalActivated(); - // Workaround for Qt4 signals being "protected" - void signalActivated(bool state); - void signalActivated(); + void signalSecondary(bool state); + void signalSecondary(); - void signalSecondary(bool state); - void signalSecondary(); + QString toString() const; - QString toString() const; + private: + bool mEnabled; - private: + std::string mName; + std::string mModName; + SecondaryMode mSecondaryMode; + QKeySequence mSequence; + int mModifier; - bool mEnabled; + int mCurrentPos; + int mLastPos; - std::string mName; - std::string mModName; - SecondaryMode mSecondaryMode; - QKeySequence mSequence; - int mModifier; + ActivationStatus mActivationStatus; + bool mModifierStatus; - int mCurrentPos; - int mLastPos; + QAction* mAction; + QString mActionText; - ActivationStatus mActivationStatus; - bool mModifierStatus; + private slots: - QAction* mAction; - QString mActionText; + void actionDeleted(); - private slots: + signals: - void actionDeleted(); + /// Triggered when the shortcut is activated or deactivated; can be determined from \p state + void activated(bool state); - signals: + /// Convenience signal. + void activated(); - /// Triggered when the shortcut is activated or deactivated; can be determined from \p state - void activated(bool state); + /// Triggered depending on SecondaryMode + void secondary(bool state); - /// Convenience signal. - void activated(); - - /// Triggered depending on SecondaryMode - void secondary(bool state); - - /// Convenience signal. - void secondary(); + /// Convenience signal. + void secondary(); }; } diff --git a/apps/opencs/model/prefs/shortcuteventhandler.cpp b/apps/opencs/model/prefs/shortcuteventhandler.cpp index a4102e1db..aedd9b52e 100644 --- a/apps/opencs/model/prefs/shortcuteventhandler.cpp +++ b/apps/opencs/model/prefs/shortcuteventhandler.cpp @@ -34,7 +34,7 @@ namespace CSMPrefs // Intercept widget events widget->installEventFilter(this); - connect(widget, SIGNAL(destroyed()), this, SLOT(widgetDestroyed())); + connect(widget, &QWidget::destroyed, this, &ShortcutEventHandler::widgetDestroyed); } // Add to list @@ -49,7 +49,9 @@ namespace CSMPrefs ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); if (shortcutListIt != mWidgetShortcuts.end()) { - shortcutListIt->second.erase(std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut), shortcutListIt->second.end()); + shortcutListIt->second.erase( + std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut), + shortcutListIt->second.end()); } } @@ -60,8 +62,8 @@ namespace CSMPrefs { QWidget* widget = static_cast(watched); QKeyEvent* keyEvent = static_cast(event); - unsigned int mod = (unsigned int) keyEvent->modifiers(); - unsigned int key = (unsigned int) keyEvent->key(); + unsigned int mod = (unsigned int)keyEvent->modifiers(); + unsigned int key = (unsigned int)keyEvent->key(); if (!keyEvent->isAutoRepeat()) return activate(widget, mod, key); @@ -70,8 +72,8 @@ namespace CSMPrefs { QWidget* widget = static_cast(watched); QKeyEvent* keyEvent = static_cast(event); - unsigned int mod = (unsigned int) keyEvent->modifiers(); - unsigned int key = (unsigned int) keyEvent->key(); + unsigned int mod = (unsigned int)keyEvent->modifiers(); + unsigned int key = (unsigned int)keyEvent->key(); if (!keyEvent->isAutoRepeat()) return deactivate(widget, mod, key); @@ -80,8 +82,8 @@ namespace CSMPrefs { QWidget* widget = static_cast(watched); QMouseEvent* mouseEvent = static_cast(event); - unsigned int mod = (unsigned int) mouseEvent->modifiers(); - unsigned int button = (unsigned int) mouseEvent->button(); + unsigned int mod = (unsigned int)mouseEvent->modifiers(); + unsigned int button = (unsigned int)mouseEvent->button(); return activate(widget, mod, button); } @@ -89,8 +91,8 @@ namespace CSMPrefs { QWidget* widget = static_cast(watched); QMouseEvent* mouseEvent = static_cast(event); - unsigned int mod = (unsigned int) mouseEvent->modifiers(); - unsigned int button = (unsigned int) mouseEvent->button(); + unsigned int mod = (unsigned int)mouseEvent->modifiers(); + unsigned int button = (unsigned int)mouseEvent->button(); return deactivate(widget, mod, button); } @@ -148,7 +150,7 @@ namespace CSMPrefs bool ShortcutEventHandler::activate(QWidget* widget, unsigned int mod, unsigned int button) { - std::vector > potentials; + std::vector> potentials; bool used = false; while (widget) @@ -178,7 +180,7 @@ namespace CSMPrefs { if (pos < lastPos && (result == Matches_WithMod || pos > 0)) { - shortcut->setPosition(pos+1); + shortcut->setPosition(pos + 1); } else if (pos == lastPos) { @@ -267,8 +269,8 @@ namespace CSMPrefs bool ShortcutEventHandler::checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate) { - if (!shortcut->isEnabled() || !shortcut->getModifier() || shortcut->getSecondaryMode() == Shortcut::SM_Ignore || - shortcut->getModifierStatus() == activate) + if (!shortcut->isEnabled() || !shortcut->getModifier() || shortcut->getSecondaryMode() == Shortcut::SM_Ignore + || shortcut->getModifierStatus() == activate) return false; MatchResult result = match(mod, button, shortcut->getModifier()); @@ -302,8 +304,8 @@ namespace CSMPrefs return used; } - ShortcutEventHandler::MatchResult ShortcutEventHandler::match(unsigned int mod, unsigned int button, - unsigned int value) + ShortcutEventHandler::MatchResult ShortcutEventHandler::match( + unsigned int mod, unsigned int button, unsigned int value) { if ((mod | button) == value) { @@ -319,8 +321,8 @@ namespace CSMPrefs } } - bool ShortcutEventHandler::sort(const std::pair& left, - const std::pair& right) + bool ShortcutEventHandler::sort( + const std::pair& left, const std::pair& right) { if (left.first == Matches_WithMod && right.first == Matches_NoMod) return true; diff --git a/apps/opencs/model/prefs/shortcuteventhandler.hpp b/apps/opencs/model/prefs/shortcuteventhandler.hpp index 700b977fd..2093e259e 100644 --- a/apps/opencs/model/prefs/shortcuteventhandler.hpp +++ b/apps/opencs/model/prefs/shortcuteventhandler.hpp @@ -16,53 +16,49 @@ namespace CSMPrefs /// Users of this class should install it as an event handler class ShortcutEventHandler : public QObject { - Q_OBJECT + Q_OBJECT - public: + public: + ShortcutEventHandler(QObject* parent); - ShortcutEventHandler(QObject* parent); + void addShortcut(Shortcut* shortcut); + void removeShortcut(Shortcut* shortcut); - void addShortcut(Shortcut* shortcut); - void removeShortcut(Shortcut* shortcut); + protected: + bool eventFilter(QObject* watched, QEvent* event) override; - protected: + private: + typedef std::vector ShortcutList; + // Child, Parent + typedef std::map WidgetMap; + typedef std::map ShortcutMap; - bool eventFilter(QObject* watched, QEvent* event) override; + enum MatchResult + { + Matches_WithMod, + Matches_NoMod, + Matches_Not + }; - private: + void updateParent(QWidget* widget); - typedef std::vector ShortcutList; - // Child, Parent - typedef std::map WidgetMap; - typedef std::map ShortcutMap; + bool activate(QWidget* widget, unsigned int mod, unsigned int button); - enum MatchResult - { - Matches_WithMod, - Matches_NoMod, - Matches_Not - }; + bool deactivate(QWidget* widget, unsigned int mod, unsigned int button); - void updateParent(QWidget* widget); + bool checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate); - bool activate(QWidget* widget, unsigned int mod, unsigned int button); + MatchResult match(unsigned int mod, unsigned int button, unsigned int value); - bool deactivate(QWidget* widget, unsigned int mod, unsigned int button); + // Prefers Matches_WithMod and a larger number of buttons + static bool sort(const std::pair& left, const std::pair& right); - bool checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate); + WidgetMap mChildParentRelations; + ShortcutMap mWidgetShortcuts; - MatchResult match(unsigned int mod, unsigned int button, unsigned int value); + private slots: - // Prefers Matches_WithMod and a larger number of buttons - static bool sort(const std::pair& left, - const std::pair& right); - - WidgetMap mChildParentRelations; - ShortcutMap mWidgetShortcuts; - - private slots: - - void widgetDestroyed(); + void widgetDestroyed(); }; } diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index 4c5f77900..a6f1da4f8 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -1,8 +1,9 @@ #include "shortcutmanager.hpp" #include +#include +#include -#include #include #include "shortcut.hpp" @@ -342,446 +343,445 @@ namespace CSMPrefs return substrings.join(""); } - const std::pair ShortcutManager::QtKeys[] = - { - std::make_pair((int)Qt::Key_Space , "Space"), - std::make_pair((int)Qt::Key_Exclam , "Exclam"), - std::make_pair((int)Qt::Key_QuoteDbl , "QuoteDbl"), - std::make_pair((int)Qt::Key_NumberSign , "NumberSign"), - std::make_pair((int)Qt::Key_Dollar , "Dollar"), - std::make_pair((int)Qt::Key_Percent , "Percent"), - std::make_pair((int)Qt::Key_Ampersand , "Ampersand"), - std::make_pair((int)Qt::Key_Apostrophe , "Apostrophe"), - std::make_pair((int)Qt::Key_ParenLeft , "ParenLeft"), - std::make_pair((int)Qt::Key_ParenRight , "ParenRight"), - std::make_pair((int)Qt::Key_Asterisk , "Asterisk"), - std::make_pair((int)Qt::Key_Plus , "Plus"), - std::make_pair((int)Qt::Key_Comma , "Comma"), - std::make_pair((int)Qt::Key_Minus , "Minus"), - std::make_pair((int)Qt::Key_Period , "Period"), - std::make_pair((int)Qt::Key_Slash , "Slash"), - std::make_pair((int)Qt::Key_0 , "0"), - std::make_pair((int)Qt::Key_1 , "1"), - std::make_pair((int)Qt::Key_2 , "2"), - std::make_pair((int)Qt::Key_3 , "3"), - std::make_pair((int)Qt::Key_4 , "4"), - std::make_pair((int)Qt::Key_5 , "5"), - std::make_pair((int)Qt::Key_6 , "6"), - std::make_pair((int)Qt::Key_7 , "7"), - std::make_pair((int)Qt::Key_8 , "8"), - std::make_pair((int)Qt::Key_9 , "9"), - std::make_pair((int)Qt::Key_Colon , "Colon"), - std::make_pair((int)Qt::Key_Semicolon , "Semicolon"), - std::make_pair((int)Qt::Key_Less , "Less"), - std::make_pair((int)Qt::Key_Equal , "Equal"), - std::make_pair((int)Qt::Key_Greater , "Greater"), - std::make_pair((int)Qt::Key_Question , "Question"), - std::make_pair((int)Qt::Key_At , "At"), - std::make_pair((int)Qt::Key_A , "A"), - std::make_pair((int)Qt::Key_B , "B"), - std::make_pair((int)Qt::Key_C , "C"), - std::make_pair((int)Qt::Key_D , "D"), - std::make_pair((int)Qt::Key_E , "E"), - std::make_pair((int)Qt::Key_F , "F"), - std::make_pair((int)Qt::Key_G , "G"), - std::make_pair((int)Qt::Key_H , "H"), - std::make_pair((int)Qt::Key_I , "I"), - std::make_pair((int)Qt::Key_J , "J"), - std::make_pair((int)Qt::Key_K , "K"), - std::make_pair((int)Qt::Key_L , "L"), - std::make_pair((int)Qt::Key_M , "M"), - std::make_pair((int)Qt::Key_N , "N"), - std::make_pair((int)Qt::Key_O , "O"), - std::make_pair((int)Qt::Key_P , "P"), - std::make_pair((int)Qt::Key_Q , "Q"), - std::make_pair((int)Qt::Key_R , "R"), - std::make_pair((int)Qt::Key_S , "S"), - std::make_pair((int)Qt::Key_T , "T"), - std::make_pair((int)Qt::Key_U , "U"), - std::make_pair((int)Qt::Key_V , "V"), - std::make_pair((int)Qt::Key_W , "W"), - std::make_pair((int)Qt::Key_X , "X"), - std::make_pair((int)Qt::Key_Y , "Y"), - std::make_pair((int)Qt::Key_Z , "Z"), - std::make_pair((int)Qt::Key_BracketLeft , "BracketLeft"), - std::make_pair((int)Qt::Key_Backslash , "Backslash"), - std::make_pair((int)Qt::Key_BracketRight , "BracketRight"), - std::make_pair((int)Qt::Key_AsciiCircum , "AsciiCircum"), - std::make_pair((int)Qt::Key_Underscore , "Underscore"), - std::make_pair((int)Qt::Key_QuoteLeft , "QuoteLeft"), - std::make_pair((int)Qt::Key_BraceLeft , "BraceLeft"), - std::make_pair((int)Qt::Key_Bar , "Bar"), - std::make_pair((int)Qt::Key_BraceRight , "BraceRight"), - std::make_pair((int)Qt::Key_AsciiTilde , "AsciiTilde"), - std::make_pair((int)Qt::Key_nobreakspace , "nobreakspace"), - std::make_pair((int)Qt::Key_exclamdown , "exclamdown"), - std::make_pair((int)Qt::Key_cent , "cent"), - std::make_pair((int)Qt::Key_sterling , "sterling"), - std::make_pair((int)Qt::Key_currency , "currency"), - std::make_pair((int)Qt::Key_yen , "yen"), - std::make_pair((int)Qt::Key_brokenbar , "brokenbar"), - std::make_pair((int)Qt::Key_section , "section"), - std::make_pair((int)Qt::Key_diaeresis , "diaeresis"), - std::make_pair((int)Qt::Key_copyright , "copyright"), - std::make_pair((int)Qt::Key_ordfeminine , "ordfeminine"), - std::make_pair((int)Qt::Key_guillemotleft , "guillemotleft"), - std::make_pair((int)Qt::Key_notsign , "notsign"), - std::make_pair((int)Qt::Key_hyphen , "hyphen"), - std::make_pair((int)Qt::Key_registered , "registered"), - std::make_pair((int)Qt::Key_macron , "macron"), - std::make_pair((int)Qt::Key_degree , "degree"), - std::make_pair((int)Qt::Key_plusminus , "plusminus"), - std::make_pair((int)Qt::Key_twosuperior , "twosuperior"), - std::make_pair((int)Qt::Key_threesuperior , "threesuperior"), - std::make_pair((int)Qt::Key_acute , "acute"), - std::make_pair((int)Qt::Key_mu , "mu"), - std::make_pair((int)Qt::Key_paragraph , "paragraph"), - std::make_pair((int)Qt::Key_periodcentered , "periodcentered"), - std::make_pair((int)Qt::Key_cedilla , "cedilla"), - std::make_pair((int)Qt::Key_onesuperior , "onesuperior"), - std::make_pair((int)Qt::Key_masculine , "masculine"), - std::make_pair((int)Qt::Key_guillemotright , "guillemotright"), - std::make_pair((int)Qt::Key_onequarter , "onequarter"), - std::make_pair((int)Qt::Key_onehalf , "onehalf"), - std::make_pair((int)Qt::Key_threequarters , "threequarters"), - std::make_pair((int)Qt::Key_questiondown , "questiondown"), - std::make_pair((int)Qt::Key_Agrave , "Agrave"), - std::make_pair((int)Qt::Key_Aacute , "Aacute"), - std::make_pair((int)Qt::Key_Acircumflex , "Acircumflex"), - std::make_pair((int)Qt::Key_Atilde , "Atilde"), - std::make_pair((int)Qt::Key_Adiaeresis , "Adiaeresis"), - std::make_pair((int)Qt::Key_Aring , "Aring"), - std::make_pair((int)Qt::Key_AE , "AE"), - std::make_pair((int)Qt::Key_Ccedilla , "Ccedilla"), - std::make_pair((int)Qt::Key_Egrave , "Egrave"), - std::make_pair((int)Qt::Key_Eacute , "Eacute"), - std::make_pair((int)Qt::Key_Ecircumflex , "Ecircumflex"), - std::make_pair((int)Qt::Key_Ediaeresis , "Ediaeresis"), - std::make_pair((int)Qt::Key_Igrave , "Igrave"), - std::make_pair((int)Qt::Key_Iacute , "Iacute"), - std::make_pair((int)Qt::Key_Icircumflex , "Icircumflex"), - std::make_pair((int)Qt::Key_Idiaeresis , "Idiaeresis"), - std::make_pair((int)Qt::Key_ETH , "ETH"), - std::make_pair((int)Qt::Key_Ntilde , "Ntilde"), - std::make_pair((int)Qt::Key_Ograve , "Ograve"), - std::make_pair((int)Qt::Key_Oacute , "Oacute"), - std::make_pair((int)Qt::Key_Ocircumflex , "Ocircumflex"), - std::make_pair((int)Qt::Key_Otilde , "Otilde"), - std::make_pair((int)Qt::Key_Odiaeresis , "Odiaeresis"), - std::make_pair((int)Qt::Key_multiply , "multiply"), - std::make_pair((int)Qt::Key_Ooblique , "Ooblique"), - std::make_pair((int)Qt::Key_Ugrave , "Ugrave"), - std::make_pair((int)Qt::Key_Uacute , "Uacute"), - std::make_pair((int)Qt::Key_Ucircumflex , "Ucircumflex"), - std::make_pair((int)Qt::Key_Udiaeresis , "Udiaeresis"), - std::make_pair((int)Qt::Key_Yacute , "Yacute"), - std::make_pair((int)Qt::Key_THORN , "THORN"), - std::make_pair((int)Qt::Key_ssharp , "ssharp"), - std::make_pair((int)Qt::Key_division , "division"), - std::make_pair((int)Qt::Key_ydiaeresis , "ydiaeresis"), - std::make_pair((int)Qt::Key_Escape , "Escape"), - std::make_pair((int)Qt::Key_Tab , "Tab"), - std::make_pair((int)Qt::Key_Backtab , "Backtab"), - std::make_pair((int)Qt::Key_Backspace , "Backspace"), - std::make_pair((int)Qt::Key_Return , "Return"), - std::make_pair((int)Qt::Key_Enter , "Enter"), - std::make_pair((int)Qt::Key_Insert , "Insert"), - std::make_pair((int)Qt::Key_Delete , "Delete"), - std::make_pair((int)Qt::Key_Pause , "Pause"), - std::make_pair((int)Qt::Key_Print , "Print"), - std::make_pair((int)Qt::Key_SysReq , "SysReq"), - std::make_pair((int)Qt::Key_Clear , "Clear"), - std::make_pair((int)Qt::Key_Home , "Home"), - std::make_pair((int)Qt::Key_End , "End"), - std::make_pair((int)Qt::Key_Left , "Left"), - std::make_pair((int)Qt::Key_Up , "Up"), - std::make_pair((int)Qt::Key_Right , "Right"), - std::make_pair((int)Qt::Key_Down , "Down"), - std::make_pair((int)Qt::Key_PageUp , "PageUp"), - std::make_pair((int)Qt::Key_PageDown , "PageDown"), - std::make_pair((int)Qt::Key_Shift , "Shift"), - std::make_pair((int)Qt::Key_Control , "Control"), - std::make_pair((int)Qt::Key_Meta , "Meta"), - std::make_pair((int)Qt::Key_Alt , "Alt"), - std::make_pair((int)Qt::Key_CapsLock , "CapsLock"), - std::make_pair((int)Qt::Key_NumLock , "NumLock"), - std::make_pair((int)Qt::Key_ScrollLock , "ScrollLock"), - std::make_pair((int)Qt::Key_F1 , "F1"), - std::make_pair((int)Qt::Key_F2 , "F2"), - std::make_pair((int)Qt::Key_F3 , "F3"), - std::make_pair((int)Qt::Key_F4 , "F4"), - std::make_pair((int)Qt::Key_F5 , "F5"), - std::make_pair((int)Qt::Key_F6 , "F6"), - std::make_pair((int)Qt::Key_F7 , "F7"), - std::make_pair((int)Qt::Key_F8 , "F8"), - std::make_pair((int)Qt::Key_F9 , "F9"), - std::make_pair((int)Qt::Key_F10 , "F10"), - std::make_pair((int)Qt::Key_F11 , "F11"), - std::make_pair((int)Qt::Key_F12 , "F12"), - std::make_pair((int)Qt::Key_F13 , "F13"), - std::make_pair((int)Qt::Key_F14 , "F14"), - std::make_pair((int)Qt::Key_F15 , "F15"), - std::make_pair((int)Qt::Key_F16 , "F16"), - std::make_pair((int)Qt::Key_F17 , "F17"), - std::make_pair((int)Qt::Key_F18 , "F18"), - std::make_pair((int)Qt::Key_F19 , "F19"), - std::make_pair((int)Qt::Key_F20 , "F20"), - std::make_pair((int)Qt::Key_F21 , "F21"), - std::make_pair((int)Qt::Key_F22 , "F22"), - std::make_pair((int)Qt::Key_F23 , "F23"), - std::make_pair((int)Qt::Key_F24 , "F24"), - std::make_pair((int)Qt::Key_F25 , "F25"), - std::make_pair((int)Qt::Key_F26 , "F26"), - std::make_pair((int)Qt::Key_F27 , "F27"), - std::make_pair((int)Qt::Key_F28 , "F28"), - std::make_pair((int)Qt::Key_F29 , "F29"), - std::make_pair((int)Qt::Key_F30 , "F30"), - std::make_pair((int)Qt::Key_F31 , "F31"), - std::make_pair((int)Qt::Key_F32 , "F32"), - std::make_pair((int)Qt::Key_F33 , "F33"), - std::make_pair((int)Qt::Key_F34 , "F34"), - std::make_pair((int)Qt::Key_F35 , "F35"), - std::make_pair((int)Qt::Key_Super_L , "Super_L"), - std::make_pair((int)Qt::Key_Super_R , "Super_R"), - std::make_pair((int)Qt::Key_Menu , "Menu"), - std::make_pair((int)Qt::Key_Hyper_L , "Hyper_L"), - std::make_pair((int)Qt::Key_Hyper_R , "Hyper_R"), - std::make_pair((int)Qt::Key_Help , "Help"), - std::make_pair((int)Qt::Key_Direction_L , "Direction_L"), - std::make_pair((int)Qt::Key_Direction_R , "Direction_R"), - std::make_pair((int)Qt::Key_Back , "Back"), - std::make_pair((int)Qt::Key_Forward , "Forward"), - std::make_pair((int)Qt::Key_Stop , "Stop"), - std::make_pair((int)Qt::Key_Refresh , "Refresh"), - std::make_pair((int)Qt::Key_VolumeDown , "VolumeDown"), - std::make_pair((int)Qt::Key_VolumeMute , "VolumeMute"), - std::make_pair((int)Qt::Key_VolumeUp , "VolumeUp"), - std::make_pair((int)Qt::Key_BassBoost , "BassBoost"), - std::make_pair((int)Qt::Key_BassUp , "BassUp"), - std::make_pair((int)Qt::Key_BassDown , "BassDown"), - std::make_pair((int)Qt::Key_TrebleUp , "TrebleUp"), - std::make_pair((int)Qt::Key_TrebleDown , "TrebleDown"), - std::make_pair((int)Qt::Key_MediaPlay , "MediaPlay"), - std::make_pair((int)Qt::Key_MediaStop , "MediaStop"), - std::make_pair((int)Qt::Key_MediaPrevious , "MediaPrevious"), - std::make_pair((int)Qt::Key_MediaNext , "MediaNext"), - std::make_pair((int)Qt::Key_MediaRecord , "MediaRecord"), - std::make_pair((int)Qt::Key_MediaPause , "MediaPause"), - std::make_pair((int)Qt::Key_MediaTogglePlayPause , "MediaTogglePlayPause"), - std::make_pair((int)Qt::Key_HomePage , "HomePage"), - std::make_pair((int)Qt::Key_Favorites , "Favorites"), - std::make_pair((int)Qt::Key_Search , "Search"), - std::make_pair((int)Qt::Key_Standby , "Standby"), - std::make_pair((int)Qt::Key_OpenUrl , "OpenUrl"), - std::make_pair((int)Qt::Key_LaunchMail , "LaunchMail"), - std::make_pair((int)Qt::Key_LaunchMedia , "LaunchMedia"), - std::make_pair((int)Qt::Key_Launch0 , "Launch0"), - std::make_pair((int)Qt::Key_Launch1 , "Launch1"), - std::make_pair((int)Qt::Key_Launch2 , "Launch2"), - std::make_pair((int)Qt::Key_Launch3 , "Launch3"), - std::make_pair((int)Qt::Key_Launch4 , "Launch4"), - std::make_pair((int)Qt::Key_Launch5 , "Launch5"), - std::make_pair((int)Qt::Key_Launch6 , "Launch6"), - std::make_pair((int)Qt::Key_Launch7 , "Launch7"), - std::make_pair((int)Qt::Key_Launch8 , "Launch8"), - std::make_pair((int)Qt::Key_Launch9 , "Launch9"), - std::make_pair((int)Qt::Key_LaunchA , "LaunchA"), - std::make_pair((int)Qt::Key_LaunchB , "LaunchB"), - std::make_pair((int)Qt::Key_LaunchC , "LaunchC"), - std::make_pair((int)Qt::Key_LaunchD , "LaunchD"), - std::make_pair((int)Qt::Key_LaunchE , "LaunchE"), - std::make_pair((int)Qt::Key_LaunchF , "LaunchF"), - std::make_pair((int)Qt::Key_MonBrightnessUp , "MonBrightnessUp"), - std::make_pair((int)Qt::Key_MonBrightnessDown , "MonBrightnessDown"), - std::make_pair((int)Qt::Key_KeyboardLightOnOff , "KeyboardLightOnOff"), - std::make_pair((int)Qt::Key_KeyboardBrightnessUp , "KeyboardBrightnessUp"), - std::make_pair((int)Qt::Key_KeyboardBrightnessDown , "KeyboardBrightnessDown"), - std::make_pair((int)Qt::Key_PowerOff , "PowerOff"), - std::make_pair((int)Qt::Key_WakeUp , "WakeUp"), - std::make_pair((int)Qt::Key_Eject , "Eject"), - std::make_pair((int)Qt::Key_ScreenSaver , "ScreenSaver"), - std::make_pair((int)Qt::Key_WWW , "WWW"), - std::make_pair((int)Qt::Key_Memo , "Memo"), - std::make_pair((int)Qt::Key_LightBulb , "LightBulb"), - std::make_pair((int)Qt::Key_Shop , "Shop"), - std::make_pair((int)Qt::Key_History , "History"), - std::make_pair((int)Qt::Key_AddFavorite , "AddFavorite"), - std::make_pair((int)Qt::Key_HotLinks , "HotLinks"), - std::make_pair((int)Qt::Key_BrightnessAdjust , "BrightnessAdjust"), - std::make_pair((int)Qt::Key_Finance , "Finance"), - std::make_pair((int)Qt::Key_Community , "Community"), - std::make_pair((int)Qt::Key_AudioRewind , "AudioRewind"), - std::make_pair((int)Qt::Key_BackForward , "BackForward"), - std::make_pair((int)Qt::Key_ApplicationLeft , "ApplicationLeft"), - std::make_pair((int)Qt::Key_ApplicationRight , "ApplicationRight"), - std::make_pair((int)Qt::Key_Book , "Book"), - std::make_pair((int)Qt::Key_CD , "CD"), - std::make_pair((int)Qt::Key_Calculator , "Calculator"), - std::make_pair((int)Qt::Key_ToDoList , "ToDoList"), - std::make_pair((int)Qt::Key_ClearGrab , "ClearGrab"), - std::make_pair((int)Qt::Key_Close , "Close"), - std::make_pair((int)Qt::Key_Copy , "Copy"), - std::make_pair((int)Qt::Key_Cut , "Cut"), - std::make_pair((int)Qt::Key_Display , "Display"), - std::make_pair((int)Qt::Key_DOS , "DOS"), - std::make_pair((int)Qt::Key_Documents , "Documents"), - std::make_pair((int)Qt::Key_Excel , "Excel"), - std::make_pair((int)Qt::Key_Explorer , "Explorer"), - std::make_pair((int)Qt::Key_Game , "Game"), - std::make_pair((int)Qt::Key_Go , "Go"), - std::make_pair((int)Qt::Key_iTouch , "iTouch"), - std::make_pair((int)Qt::Key_LogOff , "LogOff"), - std::make_pair((int)Qt::Key_Market , "Market"), - std::make_pair((int)Qt::Key_Meeting , "Meeting"), - std::make_pair((int)Qt::Key_MenuKB , "MenuKB"), - std::make_pair((int)Qt::Key_MenuPB , "MenuPB"), - std::make_pair((int)Qt::Key_MySites , "MySites"), - std::make_pair((int)Qt::Key_News , "News"), - std::make_pair((int)Qt::Key_OfficeHome , "OfficeHome"), - std::make_pair((int)Qt::Key_Option , "Option"), - std::make_pair((int)Qt::Key_Paste , "Paste"), - std::make_pair((int)Qt::Key_Phone , "Phone"), - std::make_pair((int)Qt::Key_Calendar , "Calendar"), - std::make_pair((int)Qt::Key_Reply , "Reply"), - std::make_pair((int)Qt::Key_Reload , "Reload"), - std::make_pair((int)Qt::Key_RotateWindows , "RotateWindows"), - std::make_pair((int)Qt::Key_RotationPB , "RotationPB"), - std::make_pair((int)Qt::Key_RotationKB , "RotationKB"), - std::make_pair((int)Qt::Key_Save , "Save"), - std::make_pair((int)Qt::Key_Send , "Send"), - std::make_pair((int)Qt::Key_Spell , "Spell"), - std::make_pair((int)Qt::Key_SplitScreen , "SplitScreen"), - std::make_pair((int)Qt::Key_Support , "Support"), - std::make_pair((int)Qt::Key_TaskPane , "TaskPane"), - std::make_pair((int)Qt::Key_Terminal , "Terminal"), - std::make_pair((int)Qt::Key_Tools , "Tools"), - std::make_pair((int)Qt::Key_Travel , "Travel"), - std::make_pair((int)Qt::Key_Video , "Video"), - std::make_pair((int)Qt::Key_Word , "Word"), - std::make_pair((int)Qt::Key_Xfer , "Xfer"), - std::make_pair((int)Qt::Key_ZoomIn , "ZoomIn"), - std::make_pair((int)Qt::Key_ZoomOut , "ZoomOut"), - std::make_pair((int)Qt::Key_Away , "Away"), - std::make_pair((int)Qt::Key_Messenger , "Messenger"), - std::make_pair((int)Qt::Key_WebCam , "WebCam"), - std::make_pair((int)Qt::Key_MailForward , "MailForward"), - std::make_pair((int)Qt::Key_Pictures , "Pictures"), - std::make_pair((int)Qt::Key_Music , "Music"), - std::make_pair((int)Qt::Key_Battery , "Battery"), - std::make_pair((int)Qt::Key_Bluetooth , "Bluetooth"), - std::make_pair((int)Qt::Key_WLAN , "WLAN"), - std::make_pair((int)Qt::Key_UWB , "UWB"), - std::make_pair((int)Qt::Key_AudioForward , "AudioForward"), - std::make_pair((int)Qt::Key_AudioRepeat , "AudioRepeat"), - std::make_pair((int)Qt::Key_AudioRandomPlay , "AudioRandomPlay"), - std::make_pair((int)Qt::Key_Subtitle , "Subtitle"), - std::make_pair((int)Qt::Key_AudioCycleTrack , "AudioCycleTrack"), - std::make_pair((int)Qt::Key_Time , "Time"), - std::make_pair((int)Qt::Key_Hibernate , "Hibernate"), - std::make_pair((int)Qt::Key_View , "View"), - std::make_pair((int)Qt::Key_TopMenu , "TopMenu"), - std::make_pair((int)Qt::Key_PowerDown , "PowerDown"), - std::make_pair((int)Qt::Key_Suspend , "Suspend"), - std::make_pair((int)Qt::Key_ContrastAdjust , "ContrastAdjust"), - std::make_pair((int)Qt::Key_LaunchG , "LaunchG"), - std::make_pair((int)Qt::Key_LaunchH , "LaunchH"), - std::make_pair((int)Qt::Key_TouchpadToggle , "TouchpadToggle"), - std::make_pair((int)Qt::Key_TouchpadOn , "TouchpadOn"), - std::make_pair((int)Qt::Key_TouchpadOff , "TouchpadOff"), - std::make_pair((int)Qt::Key_MicMute , "MicMute"), - std::make_pair((int)Qt::Key_Red , "Red"), - std::make_pair((int)Qt::Key_Green , "Green"), - std::make_pair((int)Qt::Key_Yellow , "Yellow"), - std::make_pair((int)Qt::Key_Blue , "Blue"), - std::make_pair((int)Qt::Key_ChannelUp , "ChannelUp"), - std::make_pair((int)Qt::Key_ChannelDown , "ChannelDown"), - std::make_pair((int)Qt::Key_Guide , "Guide"), - std::make_pair((int)Qt::Key_Info , "Info"), - std::make_pair((int)Qt::Key_Settings , "Settings"), - std::make_pair((int)Qt::Key_MicVolumeUp , "MicVolumeUp"), - std::make_pair((int)Qt::Key_MicVolumeDown , "MicVolumeDown"), - std::make_pair((int)Qt::Key_New , "New"), - std::make_pair((int)Qt::Key_Open , "Open"), - std::make_pair((int)Qt::Key_Find , "Find"), - std::make_pair((int)Qt::Key_Undo , "Undo"), - std::make_pair((int)Qt::Key_Redo , "Redo"), - std::make_pair((int)Qt::Key_AltGr , "AltGr"), - std::make_pair((int)Qt::Key_Multi_key , "Multi_key"), - std::make_pair((int)Qt::Key_Kanji , "Kanji"), - std::make_pair((int)Qt::Key_Muhenkan , "Muhenkan"), - std::make_pair((int)Qt::Key_Henkan , "Henkan"), - std::make_pair((int)Qt::Key_Romaji , "Romaji"), - std::make_pair((int)Qt::Key_Hiragana , "Hiragana"), - std::make_pair((int)Qt::Key_Katakana , "Katakana"), - std::make_pair((int)Qt::Key_Hiragana_Katakana , "Hiragana_Katakana"), - std::make_pair((int)Qt::Key_Zenkaku , "Zenkaku"), - std::make_pair((int)Qt::Key_Hankaku , "Hankaku"), - std::make_pair((int)Qt::Key_Zenkaku_Hankaku , "Zenkaku_Hankaku"), - std::make_pair((int)Qt::Key_Touroku , "Touroku"), - std::make_pair((int)Qt::Key_Massyo , "Massyo"), - std::make_pair((int)Qt::Key_Kana_Lock , "Kana_Lock"), - std::make_pair((int)Qt::Key_Kana_Shift , "Kana_Shift"), - std::make_pair((int)Qt::Key_Eisu_Shift , "Eisu_Shift"), - std::make_pair((int)Qt::Key_Eisu_toggle , "Eisu_toggle"), - std::make_pair((int)Qt::Key_Hangul , "Hangul"), - std::make_pair((int)Qt::Key_Hangul_Start , "Hangul_Start"), - std::make_pair((int)Qt::Key_Hangul_End , "Hangul_End"), - std::make_pair((int)Qt::Key_Hangul_Hanja , "Hangul_Hanja"), - std::make_pair((int)Qt::Key_Hangul_Jamo , "Hangul_Jamo"), - std::make_pair((int)Qt::Key_Hangul_Romaja , "Hangul_Romaja"), - std::make_pair((int)Qt::Key_Codeinput , "Codeinput"), - std::make_pair((int)Qt::Key_Hangul_Jeonja , "Hangul_Jeonja"), - std::make_pair((int)Qt::Key_Hangul_Banja , "Hangul_Banja"), - std::make_pair((int)Qt::Key_Hangul_PreHanja , "Hangul_PreHanja"), - std::make_pair((int)Qt::Key_Hangul_PostHanja , "Hangul_PostHanja"), - std::make_pair((int)Qt::Key_SingleCandidate , "SingleCandidate"), - std::make_pair((int)Qt::Key_MultipleCandidate , "MultipleCandidate"), - std::make_pair((int)Qt::Key_PreviousCandidate , "PreviousCandidate"), - std::make_pair((int)Qt::Key_Hangul_Special , "Hangul_Special"), - std::make_pair((int)Qt::Key_Mode_switch , "Mode_switch"), - std::make_pair((int)Qt::Key_Dead_Grave , "Dead_Grave"), - std::make_pair((int)Qt::Key_Dead_Acute , "Dead_Acute"), - std::make_pair((int)Qt::Key_Dead_Circumflex , "Dead_Circumflex"), - std::make_pair((int)Qt::Key_Dead_Tilde , "Dead_Tilde"), - std::make_pair((int)Qt::Key_Dead_Macron , "Dead_Macron"), - std::make_pair((int)Qt::Key_Dead_Breve , "Dead_Breve"), - std::make_pair((int)Qt::Key_Dead_Abovedot , "Dead_Abovedot"), - std::make_pair((int)Qt::Key_Dead_Diaeresis , "Dead_Diaeresis"), - std::make_pair((int)Qt::Key_Dead_Abovering , "Dead_Abovering"), - std::make_pair((int)Qt::Key_Dead_Doubleacute , "Dead_Doubleacute"), - std::make_pair((int)Qt::Key_Dead_Caron , "Dead_Caron"), - std::make_pair((int)Qt::Key_Dead_Cedilla , "Dead_Cedilla"), - std::make_pair((int)Qt::Key_Dead_Ogonek , "Dead_Ogonek"), - std::make_pair((int)Qt::Key_Dead_Iota , "Dead_Iota"), - std::make_pair((int)Qt::Key_Dead_Voiced_Sound , "Dead_Voiced_Sound"), - std::make_pair((int)Qt::Key_Dead_Semivoiced_Sound , "Dead_Semivoiced_Sound"), - std::make_pair((int)Qt::Key_Dead_Belowdot , "Dead_Belowdot"), - std::make_pair((int)Qt::Key_Dead_Hook , "Dead_Hook"), - std::make_pair((int)Qt::Key_Dead_Horn , "Dead_Horn"), - std::make_pair((int)Qt::Key_MediaLast , "MediaLast"), - std::make_pair((int)Qt::Key_Select , "Select"), - std::make_pair((int)Qt::Key_Yes , "Yes"), - std::make_pair((int)Qt::Key_No , "No"), - std::make_pair((int)Qt::Key_Cancel , "Cancel"), - std::make_pair((int)Qt::Key_Printer , "Printer"), - std::make_pair((int)Qt::Key_Execute , "Execute"), - std::make_pair((int)Qt::Key_Sleep , "Sleep"), - std::make_pair((int)Qt::Key_Play , "Play"), - std::make_pair((int)Qt::Key_Zoom , "Zoom"), - std::make_pair((int)Qt::Key_Exit , "Exit"), - std::make_pair((int)Qt::Key_Context1 , "Context1"), - std::make_pair((int)Qt::Key_Context2 , "Context2"), - std::make_pair((int)Qt::Key_Context3 , "Context3"), - std::make_pair((int)Qt::Key_Context4 , "Context4"), - std::make_pair((int)Qt::Key_Call , "Call"), - std::make_pair((int)Qt::Key_Hangup , "Hangup"), - std::make_pair((int)Qt::Key_Flip , "Flip"), - std::make_pair((int)Qt::Key_ToggleCallHangup , "ToggleCallHangup"), - std::make_pair((int)Qt::Key_VoiceDial , "VoiceDial"), - std::make_pair((int)Qt::Key_LastNumberRedial , "LastNumberRedial"), - std::make_pair((int)Qt::Key_Camera , "Camera"), - std::make_pair((int)Qt::Key_CameraFocus , "CameraFocus"), - std::make_pair(0 , (const char*) nullptr) + const std::pair ShortcutManager::QtKeys[] = { + std::make_pair((int)Qt::Key_Space, "Space"), + std::make_pair((int)Qt::Key_Exclam, "Exclam"), + std::make_pair((int)Qt::Key_QuoteDbl, "QuoteDbl"), + std::make_pair((int)Qt::Key_NumberSign, "NumberSign"), + std::make_pair((int)Qt::Key_Dollar, "Dollar"), + std::make_pair((int)Qt::Key_Percent, "Percent"), + std::make_pair((int)Qt::Key_Ampersand, "Ampersand"), + std::make_pair((int)Qt::Key_Apostrophe, "Apostrophe"), + std::make_pair((int)Qt::Key_ParenLeft, "ParenLeft"), + std::make_pair((int)Qt::Key_ParenRight, "ParenRight"), + std::make_pair((int)Qt::Key_Asterisk, "Asterisk"), + std::make_pair((int)Qt::Key_Plus, "Plus"), + std::make_pair((int)Qt::Key_Comma, "Comma"), + std::make_pair((int)Qt::Key_Minus, "Minus"), + std::make_pair((int)Qt::Key_Period, "Period"), + std::make_pair((int)Qt::Key_Slash, "Slash"), + std::make_pair((int)Qt::Key_0, "0"), + std::make_pair((int)Qt::Key_1, "1"), + std::make_pair((int)Qt::Key_2, "2"), + std::make_pair((int)Qt::Key_3, "3"), + std::make_pair((int)Qt::Key_4, "4"), + std::make_pair((int)Qt::Key_5, "5"), + std::make_pair((int)Qt::Key_6, "6"), + std::make_pair((int)Qt::Key_7, "7"), + std::make_pair((int)Qt::Key_8, "8"), + std::make_pair((int)Qt::Key_9, "9"), + std::make_pair((int)Qt::Key_Colon, "Colon"), + std::make_pair((int)Qt::Key_Semicolon, "Semicolon"), + std::make_pair((int)Qt::Key_Less, "Less"), + std::make_pair((int)Qt::Key_Equal, "Equal"), + std::make_pair((int)Qt::Key_Greater, "Greater"), + std::make_pair((int)Qt::Key_Question, "Question"), + std::make_pair((int)Qt::Key_At, "At"), + std::make_pair((int)Qt::Key_A, "A"), + std::make_pair((int)Qt::Key_B, "B"), + std::make_pair((int)Qt::Key_C, "C"), + std::make_pair((int)Qt::Key_D, "D"), + std::make_pair((int)Qt::Key_E, "E"), + std::make_pair((int)Qt::Key_F, "F"), + std::make_pair((int)Qt::Key_G, "G"), + std::make_pair((int)Qt::Key_H, "H"), + std::make_pair((int)Qt::Key_I, "I"), + std::make_pair((int)Qt::Key_J, "J"), + std::make_pair((int)Qt::Key_K, "K"), + std::make_pair((int)Qt::Key_L, "L"), + std::make_pair((int)Qt::Key_M, "M"), + std::make_pair((int)Qt::Key_N, "N"), + std::make_pair((int)Qt::Key_O, "O"), + std::make_pair((int)Qt::Key_P, "P"), + std::make_pair((int)Qt::Key_Q, "Q"), + std::make_pair((int)Qt::Key_R, "R"), + std::make_pair((int)Qt::Key_S, "S"), + std::make_pair((int)Qt::Key_T, "T"), + std::make_pair((int)Qt::Key_U, "U"), + std::make_pair((int)Qt::Key_V, "V"), + std::make_pair((int)Qt::Key_W, "W"), + std::make_pair((int)Qt::Key_X, "X"), + std::make_pair((int)Qt::Key_Y, "Y"), + std::make_pair((int)Qt::Key_Z, "Z"), + std::make_pair((int)Qt::Key_BracketLeft, "BracketLeft"), + std::make_pair((int)Qt::Key_Backslash, "Backslash"), + std::make_pair((int)Qt::Key_BracketRight, "BracketRight"), + std::make_pair((int)Qt::Key_AsciiCircum, "AsciiCircum"), + std::make_pair((int)Qt::Key_Underscore, "Underscore"), + std::make_pair((int)Qt::Key_QuoteLeft, "QuoteLeft"), + std::make_pair((int)Qt::Key_BraceLeft, "BraceLeft"), + std::make_pair((int)Qt::Key_Bar, "Bar"), + std::make_pair((int)Qt::Key_BraceRight, "BraceRight"), + std::make_pair((int)Qt::Key_AsciiTilde, "AsciiTilde"), + std::make_pair((int)Qt::Key_nobreakspace, "nobreakspace"), + std::make_pair((int)Qt::Key_exclamdown, "exclamdown"), + std::make_pair((int)Qt::Key_cent, "cent"), + std::make_pair((int)Qt::Key_sterling, "sterling"), + std::make_pair((int)Qt::Key_currency, "currency"), + std::make_pair((int)Qt::Key_yen, "yen"), + std::make_pair((int)Qt::Key_brokenbar, "brokenbar"), + std::make_pair((int)Qt::Key_section, "section"), + std::make_pair((int)Qt::Key_diaeresis, "diaeresis"), + std::make_pair((int)Qt::Key_copyright, "copyright"), + std::make_pair((int)Qt::Key_ordfeminine, "ordfeminine"), + std::make_pair((int)Qt::Key_guillemotleft, "guillemotleft"), + std::make_pair((int)Qt::Key_notsign, "notsign"), + std::make_pair((int)Qt::Key_hyphen, "hyphen"), + std::make_pair((int)Qt::Key_registered, "registered"), + std::make_pair((int)Qt::Key_macron, "macron"), + std::make_pair((int)Qt::Key_degree, "degree"), + std::make_pair((int)Qt::Key_plusminus, "plusminus"), + std::make_pair((int)Qt::Key_twosuperior, "twosuperior"), + std::make_pair((int)Qt::Key_threesuperior, "threesuperior"), + std::make_pair((int)Qt::Key_acute, "acute"), + std::make_pair((int)Qt::Key_mu, "mu"), + std::make_pair((int)Qt::Key_paragraph, "paragraph"), + std::make_pair((int)Qt::Key_periodcentered, "periodcentered"), + std::make_pair((int)Qt::Key_cedilla, "cedilla"), + std::make_pair((int)Qt::Key_onesuperior, "onesuperior"), + std::make_pair((int)Qt::Key_masculine, "masculine"), + std::make_pair((int)Qt::Key_guillemotright, "guillemotright"), + std::make_pair((int)Qt::Key_onequarter, "onequarter"), + std::make_pair((int)Qt::Key_onehalf, "onehalf"), + std::make_pair((int)Qt::Key_threequarters, "threequarters"), + std::make_pair((int)Qt::Key_questiondown, "questiondown"), + std::make_pair((int)Qt::Key_Agrave, "Agrave"), + std::make_pair((int)Qt::Key_Aacute, "Aacute"), + std::make_pair((int)Qt::Key_Acircumflex, "Acircumflex"), + std::make_pair((int)Qt::Key_Atilde, "Atilde"), + std::make_pair((int)Qt::Key_Adiaeresis, "Adiaeresis"), + std::make_pair((int)Qt::Key_Aring, "Aring"), + std::make_pair((int)Qt::Key_AE, "AE"), + std::make_pair((int)Qt::Key_Ccedilla, "Ccedilla"), + std::make_pair((int)Qt::Key_Egrave, "Egrave"), + std::make_pair((int)Qt::Key_Eacute, "Eacute"), + std::make_pair((int)Qt::Key_Ecircumflex, "Ecircumflex"), + std::make_pair((int)Qt::Key_Ediaeresis, "Ediaeresis"), + std::make_pair((int)Qt::Key_Igrave, "Igrave"), + std::make_pair((int)Qt::Key_Iacute, "Iacute"), + std::make_pair((int)Qt::Key_Icircumflex, "Icircumflex"), + std::make_pair((int)Qt::Key_Idiaeresis, "Idiaeresis"), + std::make_pair((int)Qt::Key_ETH, "ETH"), + std::make_pair((int)Qt::Key_Ntilde, "Ntilde"), + std::make_pair((int)Qt::Key_Ograve, "Ograve"), + std::make_pair((int)Qt::Key_Oacute, "Oacute"), + std::make_pair((int)Qt::Key_Ocircumflex, "Ocircumflex"), + std::make_pair((int)Qt::Key_Otilde, "Otilde"), + std::make_pair((int)Qt::Key_Odiaeresis, "Odiaeresis"), + std::make_pair((int)Qt::Key_multiply, "multiply"), + std::make_pair((int)Qt::Key_Ooblique, "Ooblique"), + std::make_pair((int)Qt::Key_Ugrave, "Ugrave"), + std::make_pair((int)Qt::Key_Uacute, "Uacute"), + std::make_pair((int)Qt::Key_Ucircumflex, "Ucircumflex"), + std::make_pair((int)Qt::Key_Udiaeresis, "Udiaeresis"), + std::make_pair((int)Qt::Key_Yacute, "Yacute"), + std::make_pair((int)Qt::Key_THORN, "THORN"), + std::make_pair((int)Qt::Key_ssharp, "ssharp"), + std::make_pair((int)Qt::Key_division, "division"), + std::make_pair((int)Qt::Key_ydiaeresis, "ydiaeresis"), + std::make_pair((int)Qt::Key_Escape, "Escape"), + std::make_pair((int)Qt::Key_Tab, "Tab"), + std::make_pair((int)Qt::Key_Backtab, "Backtab"), + std::make_pair((int)Qt::Key_Backspace, "Backspace"), + std::make_pair((int)Qt::Key_Return, "Return"), + std::make_pair((int)Qt::Key_Enter, "Enter"), + std::make_pair((int)Qt::Key_Insert, "Insert"), + std::make_pair((int)Qt::Key_Delete, "Delete"), + std::make_pair((int)Qt::Key_Pause, "Pause"), + std::make_pair((int)Qt::Key_Print, "Print"), + std::make_pair((int)Qt::Key_SysReq, "SysReq"), + std::make_pair((int)Qt::Key_Clear, "Clear"), + std::make_pair((int)Qt::Key_Home, "Home"), + std::make_pair((int)Qt::Key_End, "End"), + std::make_pair((int)Qt::Key_Left, "Left"), + std::make_pair((int)Qt::Key_Up, "Up"), + std::make_pair((int)Qt::Key_Right, "Right"), + std::make_pair((int)Qt::Key_Down, "Down"), + std::make_pair((int)Qt::Key_PageUp, "PageUp"), + std::make_pair((int)Qt::Key_PageDown, "PageDown"), + std::make_pair((int)Qt::Key_Shift, "Shift"), + std::make_pair((int)Qt::Key_Control, "Control"), + std::make_pair((int)Qt::Key_Meta, "Meta"), + std::make_pair((int)Qt::Key_Alt, "Alt"), + std::make_pair((int)Qt::Key_CapsLock, "CapsLock"), + std::make_pair((int)Qt::Key_NumLock, "NumLock"), + std::make_pair((int)Qt::Key_ScrollLock, "ScrollLock"), + std::make_pair((int)Qt::Key_F1, "F1"), + std::make_pair((int)Qt::Key_F2, "F2"), + std::make_pair((int)Qt::Key_F3, "F3"), + std::make_pair((int)Qt::Key_F4, "F4"), + std::make_pair((int)Qt::Key_F5, "F5"), + std::make_pair((int)Qt::Key_F6, "F6"), + std::make_pair((int)Qt::Key_F7, "F7"), + std::make_pair((int)Qt::Key_F8, "F8"), + std::make_pair((int)Qt::Key_F9, "F9"), + std::make_pair((int)Qt::Key_F10, "F10"), + std::make_pair((int)Qt::Key_F11, "F11"), + std::make_pair((int)Qt::Key_F12, "F12"), + std::make_pair((int)Qt::Key_F13, "F13"), + std::make_pair((int)Qt::Key_F14, "F14"), + std::make_pair((int)Qt::Key_F15, "F15"), + std::make_pair((int)Qt::Key_F16, "F16"), + std::make_pair((int)Qt::Key_F17, "F17"), + std::make_pair((int)Qt::Key_F18, "F18"), + std::make_pair((int)Qt::Key_F19, "F19"), + std::make_pair((int)Qt::Key_F20, "F20"), + std::make_pair((int)Qt::Key_F21, "F21"), + std::make_pair((int)Qt::Key_F22, "F22"), + std::make_pair((int)Qt::Key_F23, "F23"), + std::make_pair((int)Qt::Key_F24, "F24"), + std::make_pair((int)Qt::Key_F25, "F25"), + std::make_pair((int)Qt::Key_F26, "F26"), + std::make_pair((int)Qt::Key_F27, "F27"), + std::make_pair((int)Qt::Key_F28, "F28"), + std::make_pair((int)Qt::Key_F29, "F29"), + std::make_pair((int)Qt::Key_F30, "F30"), + std::make_pair((int)Qt::Key_F31, "F31"), + std::make_pair((int)Qt::Key_F32, "F32"), + std::make_pair((int)Qt::Key_F33, "F33"), + std::make_pair((int)Qt::Key_F34, "F34"), + std::make_pair((int)Qt::Key_F35, "F35"), + std::make_pair((int)Qt::Key_Super_L, "Super_L"), + std::make_pair((int)Qt::Key_Super_R, "Super_R"), + std::make_pair((int)Qt::Key_Menu, "Menu"), + std::make_pair((int)Qt::Key_Hyper_L, "Hyper_L"), + std::make_pair((int)Qt::Key_Hyper_R, "Hyper_R"), + std::make_pair((int)Qt::Key_Help, "Help"), + std::make_pair((int)Qt::Key_Direction_L, "Direction_L"), + std::make_pair((int)Qt::Key_Direction_R, "Direction_R"), + std::make_pair((int)Qt::Key_Back, "Back"), + std::make_pair((int)Qt::Key_Forward, "Forward"), + std::make_pair((int)Qt::Key_Stop, "Stop"), + std::make_pair((int)Qt::Key_Refresh, "Refresh"), + std::make_pair((int)Qt::Key_VolumeDown, "VolumeDown"), + std::make_pair((int)Qt::Key_VolumeMute, "VolumeMute"), + std::make_pair((int)Qt::Key_VolumeUp, "VolumeUp"), + std::make_pair((int)Qt::Key_BassBoost, "BassBoost"), + std::make_pair((int)Qt::Key_BassUp, "BassUp"), + std::make_pair((int)Qt::Key_BassDown, "BassDown"), + std::make_pair((int)Qt::Key_TrebleUp, "TrebleUp"), + std::make_pair((int)Qt::Key_TrebleDown, "TrebleDown"), + std::make_pair((int)Qt::Key_MediaPlay, "MediaPlay"), + std::make_pair((int)Qt::Key_MediaStop, "MediaStop"), + std::make_pair((int)Qt::Key_MediaPrevious, "MediaPrevious"), + std::make_pair((int)Qt::Key_MediaNext, "MediaNext"), + std::make_pair((int)Qt::Key_MediaRecord, "MediaRecord"), + std::make_pair((int)Qt::Key_MediaPause, "MediaPause"), + std::make_pair((int)Qt::Key_MediaTogglePlayPause, "MediaTogglePlayPause"), + std::make_pair((int)Qt::Key_HomePage, "HomePage"), + std::make_pair((int)Qt::Key_Favorites, "Favorites"), + std::make_pair((int)Qt::Key_Search, "Search"), + std::make_pair((int)Qt::Key_Standby, "Standby"), + std::make_pair((int)Qt::Key_OpenUrl, "OpenUrl"), + std::make_pair((int)Qt::Key_LaunchMail, "LaunchMail"), + std::make_pair((int)Qt::Key_LaunchMedia, "LaunchMedia"), + std::make_pair((int)Qt::Key_Launch0, "Launch0"), + std::make_pair((int)Qt::Key_Launch1, "Launch1"), + std::make_pair((int)Qt::Key_Launch2, "Launch2"), + std::make_pair((int)Qt::Key_Launch3, "Launch3"), + std::make_pair((int)Qt::Key_Launch4, "Launch4"), + std::make_pair((int)Qt::Key_Launch5, "Launch5"), + std::make_pair((int)Qt::Key_Launch6, "Launch6"), + std::make_pair((int)Qt::Key_Launch7, "Launch7"), + std::make_pair((int)Qt::Key_Launch8, "Launch8"), + std::make_pair((int)Qt::Key_Launch9, "Launch9"), + std::make_pair((int)Qt::Key_LaunchA, "LaunchA"), + std::make_pair((int)Qt::Key_LaunchB, "LaunchB"), + std::make_pair((int)Qt::Key_LaunchC, "LaunchC"), + std::make_pair((int)Qt::Key_LaunchD, "LaunchD"), + std::make_pair((int)Qt::Key_LaunchE, "LaunchE"), + std::make_pair((int)Qt::Key_LaunchF, "LaunchF"), + std::make_pair((int)Qt::Key_MonBrightnessUp, "MonBrightnessUp"), + std::make_pair((int)Qt::Key_MonBrightnessDown, "MonBrightnessDown"), + std::make_pair((int)Qt::Key_KeyboardLightOnOff, "KeyboardLightOnOff"), + std::make_pair((int)Qt::Key_KeyboardBrightnessUp, "KeyboardBrightnessUp"), + std::make_pair((int)Qt::Key_KeyboardBrightnessDown, "KeyboardBrightnessDown"), + std::make_pair((int)Qt::Key_PowerOff, "PowerOff"), + std::make_pair((int)Qt::Key_WakeUp, "WakeUp"), + std::make_pair((int)Qt::Key_Eject, "Eject"), + std::make_pair((int)Qt::Key_ScreenSaver, "ScreenSaver"), + std::make_pair((int)Qt::Key_WWW, "WWW"), + std::make_pair((int)Qt::Key_Memo, "Memo"), + std::make_pair((int)Qt::Key_LightBulb, "LightBulb"), + std::make_pair((int)Qt::Key_Shop, "Shop"), + std::make_pair((int)Qt::Key_History, "History"), + std::make_pair((int)Qt::Key_AddFavorite, "AddFavorite"), + std::make_pair((int)Qt::Key_HotLinks, "HotLinks"), + std::make_pair((int)Qt::Key_BrightnessAdjust, "BrightnessAdjust"), + std::make_pair((int)Qt::Key_Finance, "Finance"), + std::make_pair((int)Qt::Key_Community, "Community"), + std::make_pair((int)Qt::Key_AudioRewind, "AudioRewind"), + std::make_pair((int)Qt::Key_BackForward, "BackForward"), + std::make_pair((int)Qt::Key_ApplicationLeft, "ApplicationLeft"), + std::make_pair((int)Qt::Key_ApplicationRight, "ApplicationRight"), + std::make_pair((int)Qt::Key_Book, "Book"), + std::make_pair((int)Qt::Key_CD, "CD"), + std::make_pair((int)Qt::Key_Calculator, "Calculator"), + std::make_pair((int)Qt::Key_ToDoList, "ToDoList"), + std::make_pair((int)Qt::Key_ClearGrab, "ClearGrab"), + std::make_pair((int)Qt::Key_Close, "Close"), + std::make_pair((int)Qt::Key_Copy, "Copy"), + std::make_pair((int)Qt::Key_Cut, "Cut"), + std::make_pair((int)Qt::Key_Display, "Display"), + std::make_pair((int)Qt::Key_DOS, "DOS"), + std::make_pair((int)Qt::Key_Documents, "Documents"), + std::make_pair((int)Qt::Key_Excel, "Excel"), + std::make_pair((int)Qt::Key_Explorer, "Explorer"), + std::make_pair((int)Qt::Key_Game, "Game"), + std::make_pair((int)Qt::Key_Go, "Go"), + std::make_pair((int)Qt::Key_iTouch, "iTouch"), + std::make_pair((int)Qt::Key_LogOff, "LogOff"), + std::make_pair((int)Qt::Key_Market, "Market"), + std::make_pair((int)Qt::Key_Meeting, "Meeting"), + std::make_pair((int)Qt::Key_MenuKB, "MenuKB"), + std::make_pair((int)Qt::Key_MenuPB, "MenuPB"), + std::make_pair((int)Qt::Key_MySites, "MySites"), + std::make_pair((int)Qt::Key_News, "News"), + std::make_pair((int)Qt::Key_OfficeHome, "OfficeHome"), + std::make_pair((int)Qt::Key_Option, "Option"), + std::make_pair((int)Qt::Key_Paste, "Paste"), + std::make_pair((int)Qt::Key_Phone, "Phone"), + std::make_pair((int)Qt::Key_Calendar, "Calendar"), + std::make_pair((int)Qt::Key_Reply, "Reply"), + std::make_pair((int)Qt::Key_Reload, "Reload"), + std::make_pair((int)Qt::Key_RotateWindows, "RotateWindows"), + std::make_pair((int)Qt::Key_RotationPB, "RotationPB"), + std::make_pair((int)Qt::Key_RotationKB, "RotationKB"), + std::make_pair((int)Qt::Key_Save, "Save"), + std::make_pair((int)Qt::Key_Send, "Send"), + std::make_pair((int)Qt::Key_Spell, "Spell"), + std::make_pair((int)Qt::Key_SplitScreen, "SplitScreen"), + std::make_pair((int)Qt::Key_Support, "Support"), + std::make_pair((int)Qt::Key_TaskPane, "TaskPane"), + std::make_pair((int)Qt::Key_Terminal, "Terminal"), + std::make_pair((int)Qt::Key_Tools, "Tools"), + std::make_pair((int)Qt::Key_Travel, "Travel"), + std::make_pair((int)Qt::Key_Video, "Video"), + std::make_pair((int)Qt::Key_Word, "Word"), + std::make_pair((int)Qt::Key_Xfer, "Xfer"), + std::make_pair((int)Qt::Key_ZoomIn, "ZoomIn"), + std::make_pair((int)Qt::Key_ZoomOut, "ZoomOut"), + std::make_pair((int)Qt::Key_Away, "Away"), + std::make_pair((int)Qt::Key_Messenger, "Messenger"), + std::make_pair((int)Qt::Key_WebCam, "WebCam"), + std::make_pair((int)Qt::Key_MailForward, "MailForward"), + std::make_pair((int)Qt::Key_Pictures, "Pictures"), + std::make_pair((int)Qt::Key_Music, "Music"), + std::make_pair((int)Qt::Key_Battery, "Battery"), + std::make_pair((int)Qt::Key_Bluetooth, "Bluetooth"), + std::make_pair((int)Qt::Key_WLAN, "WLAN"), + std::make_pair((int)Qt::Key_UWB, "UWB"), + std::make_pair((int)Qt::Key_AudioForward, "AudioForward"), + std::make_pair((int)Qt::Key_AudioRepeat, "AudioRepeat"), + std::make_pair((int)Qt::Key_AudioRandomPlay, "AudioRandomPlay"), + std::make_pair((int)Qt::Key_Subtitle, "Subtitle"), + std::make_pair((int)Qt::Key_AudioCycleTrack, "AudioCycleTrack"), + std::make_pair((int)Qt::Key_Time, "Time"), + std::make_pair((int)Qt::Key_Hibernate, "Hibernate"), + std::make_pair((int)Qt::Key_View, "View"), + std::make_pair((int)Qt::Key_TopMenu, "TopMenu"), + std::make_pair((int)Qt::Key_PowerDown, "PowerDown"), + std::make_pair((int)Qt::Key_Suspend, "Suspend"), + std::make_pair((int)Qt::Key_ContrastAdjust, "ContrastAdjust"), + std::make_pair((int)Qt::Key_LaunchG, "LaunchG"), + std::make_pair((int)Qt::Key_LaunchH, "LaunchH"), + std::make_pair((int)Qt::Key_TouchpadToggle, "TouchpadToggle"), + std::make_pair((int)Qt::Key_TouchpadOn, "TouchpadOn"), + std::make_pair((int)Qt::Key_TouchpadOff, "TouchpadOff"), + std::make_pair((int)Qt::Key_MicMute, "MicMute"), + std::make_pair((int)Qt::Key_Red, "Red"), + std::make_pair((int)Qt::Key_Green, "Green"), + std::make_pair((int)Qt::Key_Yellow, "Yellow"), + std::make_pair((int)Qt::Key_Blue, "Blue"), + std::make_pair((int)Qt::Key_ChannelUp, "ChannelUp"), + std::make_pair((int)Qt::Key_ChannelDown, "ChannelDown"), + std::make_pair((int)Qt::Key_Guide, "Guide"), + std::make_pair((int)Qt::Key_Info, "Info"), + std::make_pair((int)Qt::Key_Settings, "Settings"), + std::make_pair((int)Qt::Key_MicVolumeUp, "MicVolumeUp"), + std::make_pair((int)Qt::Key_MicVolumeDown, "MicVolumeDown"), + std::make_pair((int)Qt::Key_New, "New"), + std::make_pair((int)Qt::Key_Open, "Open"), + std::make_pair((int)Qt::Key_Find, "Find"), + std::make_pair((int)Qt::Key_Undo, "Undo"), + std::make_pair((int)Qt::Key_Redo, "Redo"), + std::make_pair((int)Qt::Key_AltGr, "AltGr"), + std::make_pair((int)Qt::Key_Multi_key, "Multi_key"), + std::make_pair((int)Qt::Key_Kanji, "Kanji"), + std::make_pair((int)Qt::Key_Muhenkan, "Muhenkan"), + std::make_pair((int)Qt::Key_Henkan, "Henkan"), + std::make_pair((int)Qt::Key_Romaji, "Romaji"), + std::make_pair((int)Qt::Key_Hiragana, "Hiragana"), + std::make_pair((int)Qt::Key_Katakana, "Katakana"), + std::make_pair((int)Qt::Key_Hiragana_Katakana, "Hiragana_Katakana"), + std::make_pair((int)Qt::Key_Zenkaku, "Zenkaku"), + std::make_pair((int)Qt::Key_Hankaku, "Hankaku"), + std::make_pair((int)Qt::Key_Zenkaku_Hankaku, "Zenkaku_Hankaku"), + std::make_pair((int)Qt::Key_Touroku, "Touroku"), + std::make_pair((int)Qt::Key_Massyo, "Massyo"), + std::make_pair((int)Qt::Key_Kana_Lock, "Kana_Lock"), + std::make_pair((int)Qt::Key_Kana_Shift, "Kana_Shift"), + std::make_pair((int)Qt::Key_Eisu_Shift, "Eisu_Shift"), + std::make_pair((int)Qt::Key_Eisu_toggle, "Eisu_toggle"), + std::make_pair((int)Qt::Key_Hangul, "Hangul"), + std::make_pair((int)Qt::Key_Hangul_Start, "Hangul_Start"), + std::make_pair((int)Qt::Key_Hangul_End, "Hangul_End"), + std::make_pair((int)Qt::Key_Hangul_Hanja, "Hangul_Hanja"), + std::make_pair((int)Qt::Key_Hangul_Jamo, "Hangul_Jamo"), + std::make_pair((int)Qt::Key_Hangul_Romaja, "Hangul_Romaja"), + std::make_pair((int)Qt::Key_Codeinput, "Codeinput"), + std::make_pair((int)Qt::Key_Hangul_Jeonja, "Hangul_Jeonja"), + std::make_pair((int)Qt::Key_Hangul_Banja, "Hangul_Banja"), + std::make_pair((int)Qt::Key_Hangul_PreHanja, "Hangul_PreHanja"), + std::make_pair((int)Qt::Key_Hangul_PostHanja, "Hangul_PostHanja"), + std::make_pair((int)Qt::Key_SingleCandidate, "SingleCandidate"), + std::make_pair((int)Qt::Key_MultipleCandidate, "MultipleCandidate"), + std::make_pair((int)Qt::Key_PreviousCandidate, "PreviousCandidate"), + std::make_pair((int)Qt::Key_Hangul_Special, "Hangul_Special"), + std::make_pair((int)Qt::Key_Mode_switch, "Mode_switch"), + std::make_pair((int)Qt::Key_Dead_Grave, "Dead_Grave"), + std::make_pair((int)Qt::Key_Dead_Acute, "Dead_Acute"), + std::make_pair((int)Qt::Key_Dead_Circumflex, "Dead_Circumflex"), + std::make_pair((int)Qt::Key_Dead_Tilde, "Dead_Tilde"), + std::make_pair((int)Qt::Key_Dead_Macron, "Dead_Macron"), + std::make_pair((int)Qt::Key_Dead_Breve, "Dead_Breve"), + std::make_pair((int)Qt::Key_Dead_Abovedot, "Dead_Abovedot"), + std::make_pair((int)Qt::Key_Dead_Diaeresis, "Dead_Diaeresis"), + std::make_pair((int)Qt::Key_Dead_Abovering, "Dead_Abovering"), + std::make_pair((int)Qt::Key_Dead_Doubleacute, "Dead_Doubleacute"), + std::make_pair((int)Qt::Key_Dead_Caron, "Dead_Caron"), + std::make_pair((int)Qt::Key_Dead_Cedilla, "Dead_Cedilla"), + std::make_pair((int)Qt::Key_Dead_Ogonek, "Dead_Ogonek"), + std::make_pair((int)Qt::Key_Dead_Iota, "Dead_Iota"), + std::make_pair((int)Qt::Key_Dead_Voiced_Sound, "Dead_Voiced_Sound"), + std::make_pair((int)Qt::Key_Dead_Semivoiced_Sound, "Dead_Semivoiced_Sound"), + std::make_pair((int)Qt::Key_Dead_Belowdot, "Dead_Belowdot"), + std::make_pair((int)Qt::Key_Dead_Hook, "Dead_Hook"), + std::make_pair((int)Qt::Key_Dead_Horn, "Dead_Horn"), + std::make_pair((int)Qt::Key_MediaLast, "MediaLast"), + std::make_pair((int)Qt::Key_Select, "Select"), + std::make_pair((int)Qt::Key_Yes, "Yes"), + std::make_pair((int)Qt::Key_No, "No"), + std::make_pair((int)Qt::Key_Cancel, "Cancel"), + std::make_pair((int)Qt::Key_Printer, "Printer"), + std::make_pair((int)Qt::Key_Execute, "Execute"), + std::make_pair((int)Qt::Key_Sleep, "Sleep"), + std::make_pair((int)Qt::Key_Play, "Play"), + std::make_pair((int)Qt::Key_Zoom, "Zoom"), + std::make_pair((int)Qt::Key_Exit, "Exit"), + std::make_pair((int)Qt::Key_Context1, "Context1"), + std::make_pair((int)Qt::Key_Context2, "Context2"), + std::make_pair((int)Qt::Key_Context3, "Context3"), + std::make_pair((int)Qt::Key_Context4, "Context4"), + std::make_pair((int)Qt::Key_Call, "Call"), + std::make_pair((int)Qt::Key_Hangup, "Hangup"), + std::make_pair((int)Qt::Key_Flip, "Flip"), + std::make_pair((int)Qt::Key_ToggleCallHangup, "ToggleCallHangup"), + std::make_pair((int)Qt::Key_VoiceDial, "VoiceDial"), + std::make_pair((int)Qt::Key_LastNumberRedial, "LastNumberRedial"), + std::make_pair((int)Qt::Key_Camera, "Camera"), + std::make_pair((int)Qt::Key_CameraFocus, "CameraFocus"), + std::make_pair(0, static_cast(nullptr)), }; } diff --git a/apps/opencs/model/prefs/shortcutmanager.hpp b/apps/opencs/model/prefs/shortcutmanager.hpp index 99f01a5df..fc8db3f2b 100644 --- a/apps/opencs/model/prefs/shortcutmanager.hpp +++ b/apps/opencs/model/prefs/shortcutmanager.hpp @@ -2,6 +2,8 @@ #define CSM_PREFS_SHORTCUTMANAGER_H #include +#include +#include #include #include @@ -15,58 +17,56 @@ namespace CSMPrefs /// Class used to track and update shortcuts/sequences class ShortcutManager : public QObject { - Q_OBJECT + Q_OBJECT - public: + public: + ShortcutManager(); - ShortcutManager(); + /// The shortcut class will do this automatically + void addShortcut(Shortcut* shortcut); - /// The shortcut class will do this automatically - void addShortcut(Shortcut* shortcut); + /// The shortcut class will do this automatically + void removeShortcut(Shortcut* shortcut); - /// The shortcut class will do this automatically - void removeShortcut(Shortcut* shortcut); + bool getSequence(const std::string& name, QKeySequence& sequence) const; + void setSequence(const std::string& name, const QKeySequence& sequence); - bool getSequence(const std::string& name, QKeySequence& sequence) const; - void setSequence(const std::string& name, const QKeySequence& sequence); + bool getModifier(const std::string& name, int& modifier) const; + void setModifier(const std::string& name, int modifier); - bool getModifier(const std::string& name, int& modifier) const; - void setModifier(const std::string& name, int modifier); + std::string convertToString(const QKeySequence& sequence) const; + std::string convertToString(int modifier) const; - std::string convertToString(const QKeySequence& sequence) const; - std::string convertToString(int modifier) const; + std::string convertToString(const QKeySequence& sequence, int modifier) const; - std::string convertToString(const QKeySequence& sequence, int modifier) const; + void convertFromString(const std::string& data, QKeySequence& sequence) const; + void convertFromString(const std::string& data, int& modifier) const; - void convertFromString(const std::string& data, QKeySequence& sequence) const; - void convertFromString(const std::string& data, int& modifier) const; + void convertFromString(const std::string& data, QKeySequence& sequence, int& modifier) const; - void convertFromString(const std::string& data, QKeySequence& sequence, int& modifier) const; + /// Replaces "{sequence-name}" or "{modifier-name}" with the appropriate text + QString processToolTip(const QString& toolTip) const; - /// Replaces "{sequence-name}" or "{modifier-name}" with the appropriate text - QString processToolTip(const QString& toolTip) const; + private: + // Need a multimap in case multiple shortcuts share the same name + typedef std::multimap ShortcutMap; + typedef std::map SequenceMap; + typedef std::map ModifierMap; + typedef std::map NameMap; + typedef std::map KeyMap; - private: + ShortcutMap mShortcuts; + SequenceMap mSequences; + ModifierMap mModifiers; - // Need a multimap in case multiple shortcuts share the same name - typedef std::multimap ShortcutMap; - typedef std::map SequenceMap; - typedef std::map ModifierMap; - typedef std::map NameMap; - typedef std::map KeyMap; + NameMap mNames; + KeyMap mKeys; - ShortcutMap mShortcuts; - SequenceMap mSequences; - ModifierMap mModifiers; + ShortcutEventHandler* mEventHandler; - NameMap mNames; - KeyMap mKeys; + void createLookupTables(); - ShortcutEventHandler* mEventHandler; - - void createLookupTables(); - - static const std::pair QtKeys[]; + static const std::pair QtKeys[]; }; } diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index e0cd5bb07..d8c71d700 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -5,17 +5,21 @@ #include #include #include -#include #include +#include + +#include + +#include +#include -#include "state.hpp" #include "shortcutmanager.hpp" +#include "state.hpp" namespace CSMPrefs { - ShortcutSetting::ShortcutSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, - const std::string& label) - : Setting(parent, values, mutex, key, label) + ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) + : Setting(parent, mutex, key, label) , mButton(nullptr) , mEditorActive(false) , mEditorPos(0) @@ -44,7 +48,7 @@ namespace CSMPrefs mButton = widget; - connect(widget, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool))); + connect(widget, &QPushButton::toggled, this, &ShortcutSetting::buttonToggled); return std::make_pair(label, widget); } @@ -53,7 +57,7 @@ namespace CSMPrefs { if (mButton) { - std::string shortcut = getValues().getString(getKey(), getParent()->getKey()); + const std::string& shortcut = Settings::Manager::getString(getKey(), getParent()->getKey()); QKeySequence sequence; State::get().getShortcutManager().convertFromString(shortcut, sequence); @@ -113,14 +117,7 @@ namespace CSMPrefs bool ShortcutSetting::handleEvent(QObject* target, int mod, int value, bool active) { // Modifiers are handled differently - const int Blacklist[] = - { - Qt::Key_Shift, - Qt::Key_Control, - Qt::Key_Meta, - Qt::Key_Alt, - Qt::Key_AltGr - }; + const int Blacklist[] = { Qt::Key_Shift, Qt::Key_Control, Qt::Key_Meta, Qt::Key_Alt, Qt::Key_AltGr }; const size_t BlacklistSize = sizeof(Blacklist) / sizeof(int); @@ -179,7 +176,7 @@ namespace CSMPrefs { QMutexLocker lock(getMutex()); - getValues().setString(getKey(), getParent()->getKey(), value); + Settings::Manager::setString(getKey(), getParent()->getKey(), value); } getParent()->getState()->update(*this); diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index 52298232e..bef140dad 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -1,50 +1,53 @@ #ifndef CSM_PREFS_SHORTCUTSETTING_H #define CSM_PREFS_SHORTCUTSETTING_H +#include +#include + #include #include "setting.hpp" class QEvent; +class QMutex; +class QObject; class QPushButton; +class QWidget; namespace CSMPrefs { + class Category; class ShortcutSetting : public Setting { - Q_OBJECT + Q_OBJECT - public: + public: + ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); - ShortcutSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, - const std::string& label); + std::pair makeWidgets(QWidget* parent) override; - std::pair makeWidgets(QWidget* parent) override; + void updateWidget() override; - void updateWidget() override; + protected: + bool eventFilter(QObject* target, QEvent* event) override; - protected: + private: + bool handleEvent(QObject* target, int mod, int value, bool active); - bool eventFilter(QObject* target, QEvent* event) override; + void storeValue(const QKeySequence& sequence); + void resetState(); - private: + static constexpr int MaxKeys = 4; - bool handleEvent(QObject* target, int mod, int value, bool active); + QPushButton* mButton; - void storeValue(const QKeySequence& sequence); - void resetState(); + bool mEditorActive; + int mEditorPos; + int mEditorKeys[MaxKeys]; - static constexpr int MaxKeys = 4; + private slots: - QPushButton* mButton; - - bool mEditorActive; - int mEditorPos; - int mEditorKeys[MaxKeys]; - - private slots: - - void buttonToggled(bool checked); + void buttonToggled(bool checked); }; } diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 58a0f296e..97f29bc8b 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -1,655 +1,661 @@ - #include "state.hpp" -#include -#include -#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include -#include "intsetting.hpp" -#include "doublesetting.hpp" #include "boolsetting.hpp" #include "coloursetting.hpp" -#include "shortcutsetting.hpp" +#include "doublesetting.hpp" +#include "intsetting.hpp" #include "modifiersetting.hpp" +#include "shortcutsetting.hpp" +#include "stringsetting.hpp" -CSMPrefs::State *CSMPrefs::State::sThis = nullptr; - -void CSMPrefs::State::load() -{ - // default settings file - boost::filesystem::path local = mConfigurationManager.getLocalPath() / mDefaultConfigFile; - boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mDefaultConfigFile; - - if (boost::filesystem::exists (local)) - mSettings.loadDefault (local.string()); - else if (boost::filesystem::exists (global)) - mSettings.loadDefault (global.string()); - else - throw std::runtime_error ("No default settings file found! Make sure the file \"" + mDefaultConfigFile + "\" was properly installed."); - - // user settings file - boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; - - if (boost::filesystem::exists (user)) - mSettings.loadUser (user.string()); -} +CSMPrefs::State* CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::declare() { - declareCategory ("Windows"); - declareInt ("default-width", "Default window width", 800). - setTooltip ("Newly opened top-level windows will open with this width."). - setMin (80); - declareInt ("default-height", "Default window height", 600). - setTooltip ("Newly opened top-level windows will open with this height."). - setMin (80); - declareBool ("show-statusbar", "Show Status Bar", true). - setTooltip ("If a newly open top level window is showing status bars or not. " - " Note that this does not affect existing windows."); + declareCategory("Windows"); + declareInt("default-width", "Default window width", 800) + .setTooltip("Newly opened top-level windows will open with this width.") + .setMin(80); + declareInt("default-height", "Default window height", 600) + .setTooltip("Newly opened top-level windows will open with this height.") + .setMin(80); + declareBool("show-statusbar", "Show Status Bar", true) + .setTooltip( + "If a newly open top level window is showing status bars or not. " + " Note that this does not affect existing windows."); declareSeparator(); - declareBool ("reuse", "Reuse Subviews", true). - setTooltip ("When a new subview is requested and a matching subview already " - " exist, do not open a new subview and use the existing one instead."); - declareInt ("max-subviews", "Maximum number of subviews per top-level window", 256). - setTooltip ("If the maximum number is reached and a new subview is opened " - "it will be placed into a new top-level window."). - setRange (1, 256); - declareBool ("hide-subview", "Hide single subview", false). - setTooltip ("When a view contains only a single subview, hide the subview title " - "bar and if this subview is closed also close the view (unless it is the last " - "view for this document)"); - declareInt ("minimum-width", "Minimum subview width", 325). - setTooltip ("Minimum width of subviews."). - setRange (50, 10000); + declareBool("reuse", "Reuse Subviews", true) + .setTooltip( + "When a new subview is requested and a matching subview already " + " exist, do not open a new subview and use the existing one instead."); + declareInt("max-subviews", "Maximum number of subviews per top-level window", 256) + .setTooltip( + "If the maximum number is reached and a new subview is opened " + "it will be placed into a new top-level window.") + .setRange(1, 256); + declareBool("hide-subview", "Hide single subview", false) + .setTooltip( + "When a view contains only a single subview, hide the subview title " + "bar and if this subview is closed also close the view (unless it is the last " + "view for this document)"); + declareInt("minimum-width", "Minimum subview width", 325) + .setTooltip("Minimum width of subviews.") + .setRange(50, 10000); declareSeparator(); - EnumValue scrollbarOnly ("Scrollbar Only", "Simple addition of scrollbars, the view window " + EnumValue scrollbarOnly("Scrollbar Only", + "Simple addition of scrollbars, the view window " "does not grow automatically."); - declareEnum ("mainwindow-scrollbar", "Horizontal scrollbar mode for main window.", scrollbarOnly). - addValue (scrollbarOnly). - addValue ("Grow Only", "The view window grows as subviews are added. No scrollbars."). - addValue ("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); - declareBool ("grow-limit", "Grow Limit Screen", false). - setTooltip ("When \"Grow then Scroll\" option is selected, the window size grows to" - " the width of the virtual desktop. \nIf this option is selected the the window growth" - "is limited to the current screen."); + declareEnum("mainwindow-scrollbar", "Horizontal scrollbar mode for main window.", scrollbarOnly) + .addValue(scrollbarOnly) + .addValue("Grow Only", "The view window grows as subviews are added. No scrollbars.") + .addValue("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + declareBool("grow-limit", "Grow Limit Screen", false) + .setTooltip( + "When \"Grow then Scroll\" option is selected, the window size grows to" + " the width of the virtual desktop. \nIf this option is selected the the window growth" + "is limited to the current screen."); +#endif - declareCategory ("Records"); - EnumValue iconAndText ("Icon and Text"); + declareCategory("Records"); + EnumValue iconAndText("Icon and Text"); EnumValues recordValues; - recordValues.add (iconAndText).add ("Icon Only").add ("Text Only"); - declareEnum ("status-format", "Modification status display format", iconAndText). - addValues (recordValues); - declareEnum ("type-format", "ID type display format", iconAndText). - addValues (recordValues); + recordValues.add(iconAndText).add("Icon Only").add("Text Only"); + declareEnum("status-format", "Modification status display format", iconAndText).addValues(recordValues); + declareEnum("type-format", "ID type display format", iconAndText).addValues(recordValues); - declareCategory ("ID Tables"); - EnumValue inPlaceEdit ("Edit in Place", "Edit the clicked cell"); - EnumValue editRecord ("Edit Record", "Open a dialogue subview for the clicked record"); - EnumValue view ("View", "Open a scene subview for the clicked record (not available everywhere)"); - EnumValue editRecordAndClose ("Edit Record and Close"); + declareCategory("ID Tables"); + EnumValue inPlaceEdit("Edit in Place", "Edit the clicked cell"); + EnumValue editRecord("Edit Record", "Open a dialogue subview for the clicked record"); + EnumValue view("View", "Open a scene subview for the clicked record (not available everywhere)"); + EnumValue editRecordAndClose("Edit Record and Close"); EnumValues doubleClickValues; - doubleClickValues.add (inPlaceEdit).add (editRecord).add (view).add ("Revert"). - add ("Delete").add (editRecordAndClose). - add ("View and Close", "Open a scene subview for the clicked record and close the table subview"); - declareEnum ("double", "Double Click", inPlaceEdit).addValues (doubleClickValues); - declareEnum ("double-s", "Shift Double Click", editRecord).addValues (doubleClickValues); - declareEnum ("double-c", "Control Double Click", view).addValues (doubleClickValues); - declareEnum ("double-sc", "Shift Control Double Click", editRecordAndClose).addValues (doubleClickValues); + doubleClickValues.add(inPlaceEdit) + .add(editRecord) + .add(view) + .add("Revert") + .add("Delete") + .add(editRecordAndClose) + .add("View and Close", "Open a scene subview for the clicked record and close the table subview"); + declareEnum("double", "Double Click", inPlaceEdit).addValues(doubleClickValues); + declareEnum("double-s", "Shift Double Click", editRecord).addValues(doubleClickValues); + declareEnum("double-c", "Control Double Click", view).addValues(doubleClickValues); + declareEnum("double-sc", "Shift Control Double Click", editRecordAndClose).addValues(doubleClickValues); declareSeparator(); - EnumValue jumpAndSelect ("Jump and Select", "Scroll new record into view and make it the selection"); - declareEnum ("jump-to-added", "Action on adding or cloning a record", jumpAndSelect). - addValue (jumpAndSelect). - addValue ("Jump Only", "Scroll new record into view"). - addValue ("No Jump", "No special action"); - declareBool ("extended-config", - "Manually specify affected record types for an extended delete/revert", false). - setTooltip ("Delete and revert commands have an extended form that also affects " - "associated records.\n\n" - "If this option is enabled, types of affected records are selected " - "manually before a command execution.\nOtherwise, all associated " - "records are deleted/reverted immediately."); + EnumValue jumpAndSelect("Jump and Select", "Scroll new record into view and make it the selection"); + declareEnum("jump-to-added", "Action on adding or cloning a record", jumpAndSelect) + .addValue(jumpAndSelect) + .addValue("Jump Only", "Scroll new record into view") + .addValue("No Jump", "No special action"); + declareBool("extended-config", "Manually specify affected record types for an extended delete/revert", false) + .setTooltip( + "Delete and revert commands have an extended form that also affects " + "associated records.\n\n" + "If this option is enabled, types of affected records are selected " + "manually before a command execution.\nOtherwise, all associated " + "records are deleted/reverted immediately."); + declareBool("subview-new-window", "Open Record in new window", false) + .setTooltip( + "When editing a record, open the view in a new window," + " rather than docked in the main view."); - declareCategory ("ID Dialogues"); - declareBool ("toolbar", "Show toolbar", true); + declareCategory("ID Dialogues"); + declareBool("toolbar", "Show toolbar", true); - declareCategory ("Reports"); - EnumValue actionNone ("None"); - EnumValue actionEdit ("Edit", "Open a table or dialogue suitable for addressing the listed report"); - EnumValue actionRemove ("Remove", "Remove the report from the report table"); - EnumValue actionEditAndRemove ("Edit And Remove", "Open a table or dialogue suitable for addressing the listed report, then remove the report from the report table"); + declareCategory("Reports"); + EnumValue actionNone("None"); + EnumValue actionEdit("Edit", "Open a table or dialogue suitable for addressing the listed report"); + EnumValue actionRemove("Remove", "Remove the report from the report table"); + EnumValue actionEditAndRemove("Edit And Remove", + "Open a table or dialogue suitable for addressing the listed report, then remove the report from the report " + "table"); EnumValues reportValues; - reportValues.add (actionNone).add (actionEdit).add (actionRemove).add (actionEditAndRemove); - declareEnum ("double", "Double Click", actionEdit).addValues (reportValues); - declareEnum ("double-s", "Shift Double Click", actionRemove).addValues (reportValues); - declareEnum ("double-c", "Control Double Click", actionEditAndRemove).addValues (reportValues); - declareEnum ("double-sc", "Shift Control Double Click", actionNone).addValues (reportValues); + reportValues.add(actionNone).add(actionEdit).add(actionRemove).add(actionEditAndRemove); + declareEnum("double", "Double Click", actionEdit).addValues(reportValues); + declareEnum("double-s", "Shift Double Click", actionRemove).addValues(reportValues); + declareEnum("double-c", "Control Double Click", actionEditAndRemove).addValues(reportValues); + declareEnum("double-sc", "Shift Control Double Click", actionNone).addValues(reportValues); declareBool("ignore-base-records", "Ignore base records in verifier", false); - declareCategory ("Search & Replace"); - declareInt ("char-before", "Characters before search string", 10). - setTooltip ("Maximum number of character to display in search result before the searched text"); - declareInt ("char-after", "Characters after search string", 10). - setTooltip ("Maximum number of character to display in search result after the searched text"); - declareBool ("auto-delete", "Delete row from result table after a successful replace", true); + declareCategory("Search & Replace"); + declareInt("char-before", "Characters before search string", 10) + .setTooltip("Maximum number of character to display in search result before the searched text"); + declareInt("char-after", "Characters after search string", 10) + .setTooltip("Maximum number of character to display in search result after the searched text"); + declareBool("auto-delete", "Delete row from result table after a successful replace", true); - declareCategory ("Scripts"); - declareBool ("show-linenum", "Show Line Numbers", true). - setTooltip ("Show line numbers to the left of the script editor window." - "The current row and column numbers of the text cursor are shown at the bottom."); - declareBool ("wrap-lines", "Wrap Lines", false). - setTooltip ("Wrap lines longer than width of script editor."); - declareBool ("mono-font", "Use monospace font", true); - declareInt ("tab-width", "Tab Width", 4). - setTooltip ("Number of characters for tab width"). - setRange (1, 10); - EnumValue warningsNormal ("Normal", "Report warnings as warning"); - declareEnum ("warnings", "Warning Mode", warningsNormal). - addValue ("Ignore", "Do not report warning"). - addValue (warningsNormal). - addValue ("Strict", "Promote warning to an error"); - declareBool ("toolbar", "Show toolbar", true); - declareInt ("compile-delay", "Delay between updating of source errors", 100). - setTooltip ("Delay in milliseconds"). - setRange (0, 10000); - declareInt ("error-height", "Initial height of the error panel", 100). - setRange (100, 10000); - declareBool ("highlight-occurrences", "Highlight other occurrences of selected names", true); - declareColour ("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); + declareCategory("Scripts"); + declareBool("show-linenum", "Show Line Numbers", true) + .setTooltip( + "Show line numbers to the left of the script editor window." + "The current row and column numbers of the text cursor are shown at the bottom."); + declareBool("wrap-lines", "Wrap Lines", false).setTooltip("Wrap lines longer than width of script editor."); + declareBool("mono-font", "Use monospace font", true); + declareInt("tab-width", "Tab Width", 4).setTooltip("Number of characters for tab width").setRange(1, 10); + EnumValue warningsNormal("Normal", "Report warnings as warning"); + declareEnum("warnings", "Warning Mode", warningsNormal) + .addValue("Ignore", "Do not report warning") + .addValue(warningsNormal) + .addValue("Strict", "Promote warning to an error"); + declareBool("toolbar", "Show toolbar", true); + declareInt("compile-delay", "Delay between updating of source errors", 100) + .setTooltip("Delay in milliseconds") + .setRange(0, 10000); + declareInt("error-height", "Initial height of the error panel", 100).setRange(100, 10000); + declareBool("highlight-occurrences", "Highlight other occurrences of selected names", true); + declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); declareSeparator(); - declareColour ("colour-int", "Highlight Colour: Integer Literals", QColor ("darkmagenta")); - declareColour ("colour-float", "Highlight Colour: Float Literals", QColor ("magenta")); - declareColour ("colour-name", "Highlight Colour: Names", QColor ("grey")); - declareColour ("colour-keyword", "Highlight Colour: Keywords", QColor ("red")); - declareColour ("colour-special", "Highlight Colour: Special Characters", QColor ("darkorange")); - declareColour ("colour-comment", "Highlight Colour: Comments", QColor ("green")); - declareColour ("colour-id", "Highlight Colour: IDs", QColor ("blue")); + declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); + declareColour("colour-float", "Highlight Colour: Float Literals", QColor("magenta")); + declareColour("colour-name", "Highlight Colour: Names", QColor("grey")); + declareColour("colour-keyword", "Highlight Colour: Keywords", QColor("red")); + declareColour("colour-special", "Highlight Colour: Special Characters", QColor("darkorange")); + declareColour("colour-comment", "Highlight Colour: Comments", QColor("green")); + declareColour("colour-id", "Highlight Colour: IDs", QColor("blue")); - declareCategory ("General Input"); - declareBool ("cycle", "Cyclic next/previous", false). - setTooltip ("When using next/previous functions at the last/first item of a " - "list go to the first/last item"); + declareCategory("General Input"); + declareBool("cycle", "Cyclic next/previous", false) + .setTooltip( + "When using next/previous functions at the last/first item of a " + "list go to the first/last item"); - declareCategory ("3D Scene Input"); + 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); + 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 ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); - declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); - declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); + declareDouble("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("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); + declareDouble("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); + declareDouble("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); 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); + 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); - declareDouble ("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0). - setRange (0.001, 100.0); - declareDouble ("drag-shift-factor", - "Shift-acceleration factor during drag operations", 4.0). - setTooltip ("Acceleration factor during drag operations while holding down shift"). - setRange (0.001, 100.0); - declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); + declareBool("context-select", "Context Sensitive Selection", false); + declareDouble("drag-factor", "Mouse sensitivity during drag operations", 1.0).setRange(0.001, 100.0); + declareDouble("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0).setRange(0.001, 100.0); + declareDouble("drag-shift-factor", "Shift-acceleration factor during drag operations", 4.0) + .setTooltip("Acceleration factor during drag operations while holding down shift") + .setRange(0.001, 100.0); + declareDouble("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); - declareCategory ("Rendering"); - declareInt ("framerate-limit", "FPS limit", 60). - setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\"."). - setRange(0, 10000); - 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("Rendering"); + declareInt("framerate-limit", "FPS limit", 60) + .setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\".") + .setRange(0, 10000); + 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); declareBool("scene-use-gradient", "Use Gradient Background", true); - declareColour ("scene-day-background-colour", "Day Background Colour", QColor (110, 120, 128, 255)); - declareColour ("scene-day-gradient-colour", "Day Gradient Colour", QColor (47, 51, 51, 255)). - setTooltip("Sets the gradient color to use in conjunction with the day background color. Ignored if " - "the gradient option is disabled."); - declareColour ("scene-bright-background-colour", "Scene Bright Background Colour", QColor (79, 87, 92, 255)); - declareColour ("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor (47, 51, 51, 255)). - setTooltip("Sets the gradient color to use in conjunction with the bright background color. Ignored if " + declareColour("scene-day-background-colour", "Day Background Colour", QColor(110, 120, 128, 255)); + declareColour("scene-day-gradient-colour", "Day Gradient Colour", QColor(47, 51, 51, 255)) + .setTooltip( + "Sets the gradient color to use in conjunction with the day background color. Ignored if " "the gradient option is disabled."); - declareColour ("scene-night-background-colour", "Scene Night Background Colour", QColor (64, 77, 79, 255)); - declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)). - setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if " + declareColour("scene-bright-background-colour", "Scene Bright Background Colour", QColor(79, 87, 92, 255)); + declareColour("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor(47, 51, 51, 255)) + .setTooltip( + "Sets the gradient color to use in conjunction with the bright background color. Ignored if " "the gradient option is disabled."); + declareColour("scene-night-background-colour", "Scene Night Background Colour", QColor(64, 77, 79, 255)); + declareColour("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor(47, 51, 51, 255)) + .setTooltip( + "Sets the gradient color to use in conjunction with the night background color. Ignored if " + "the gradient option is disabled."); + declareBool("scene-day-night-switch-nodes", "Use Day/Night Switch Nodes", true); - declareCategory ("Tooltips"); - declareBool ("scene", "Show Tooltips in 3D scenes", true); - declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false); - declareInt ("scene-delay", "Tooltip delay in milliseconds", 500). - setMin (1); + declareCategory("Tooltips"); + declareBool("scene", "Show Tooltips in 3D scenes", true); + declareBool("scene-hide-basic", "Hide basic 3D scenes tooltips", false); + declareInt("scene-delay", "Tooltip delay in milliseconds", 500).setMin(1); - EnumValue createAndInsert ("Create cell and insert"); - EnumValue showAndInsert ("Show cell and insert"); - EnumValue dontInsert ("Discard"); - EnumValue insertAnyway ("Insert anyway"); + EnumValue createAndInsert("Create cell and insert"); + EnumValue showAndInsert("Show cell and insert"); + EnumValue dontInsert("Discard"); + EnumValue insertAnyway("Insert anyway"); EnumValues insertOutsideCell; - insertOutsideCell.add (createAndInsert).add (dontInsert).add (insertAnyway); + insertOutsideCell.add(createAndInsert).add(dontInsert).add(insertAnyway); EnumValues insertOutsideVisibleCell; - insertOutsideVisibleCell.add (showAndInsert).add (dontInsert).add (insertAnyway); + insertOutsideVisibleCell.add(showAndInsert).add(dontInsert).add(insertAnyway); - EnumValue createAndLandEdit ("Create cell and land, then edit"); - EnumValue showAndLandEdit ("Show cell and edit"); - EnumValue dontLandEdit ("Discard"); + EnumValue createAndLandEdit("Create cell and land, then edit"); + EnumValue showAndLandEdit("Show cell and edit"); + EnumValue dontLandEdit("Discard"); EnumValues landeditOutsideCell; - landeditOutsideCell.add (createAndLandEdit).add (dontLandEdit); + landeditOutsideCell.add(createAndLandEdit).add(dontLandEdit); EnumValues landeditOutsideVisibleCell; - landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit); + landeditOutsideVisibleCell.add(showAndLandEdit).add(dontLandEdit); - EnumValue SelectOnly ("Select only"); - EnumValue SelectAdd ("Add to selection"); - EnumValue SelectRemove ("Remove from selection"); - EnumValue selectInvert ("Invert selection"); + EnumValue SelectOnly("Select only"); + EnumValue SelectAdd("Add to selection"); + EnumValue SelectRemove("Remove from selection"); + EnumValue selectInvert("Invert selection"); EnumValues primarySelectAction; - primarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); + primarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert); EnumValues secondarySelectAction; - secondarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); + secondarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert); - declareCategory ("3D Scene Editing"); - declareInt ("distance", "Drop Distance", 50). - setTooltip ("If an instance drop can not be placed against another object at the " + declareCategory("3D Scene Editing"); + declareDouble("gridsnap-movement", "Grid snap size", 16); + declareDouble("gridsnap-rotation", "Angle snap size", 15); + declareDouble("gridsnap-scale", "Scale snap size", 0.25); + declareInt("distance", "Drop Distance", 50) + .setTooltip( + "If an instance drop can not be placed against another object at the " "insert point, it will be placed by this distance from the insert point instead"); - declareEnum ("outside-drop", "Handling drops outside of cells", createAndInsert). - addValues (insertOutsideCell); - declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). - addValues (insertOutsideVisibleCell); - declareEnum ("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit). - setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record."). - addValues (landeditOutsideCell); - declareEnum ("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit). - setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible."). - addValues (landeditOutsideVisibleCell); - declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50). - setMin (1); - declareInt ("shapebrush-maximumsize", "Maximum height edit brush size", 100). - setTooltip("Setting for the slider range of brush size in terrain height editing."). - setMin (1); - declareBool ("landedit-post-smoothpainting", "Smooth land after painting height", false). - setTooltip("Raise and lower tools will leave bumpy finish without this option"); - declareDouble ("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25). - setTooltip("If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. " - "Negative values may be used to roughen instead of smooth."). - setMin (-1). - setMax (1); - declareBool ("open-list-view", "Open displays list view", false). - setTooltip ("When opening a reference from the scene view, it will open the" - " instance list view instead of the individual instance record view."); - declareEnum ("primary-select-action", "Action for primary select", SelectOnly). - setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). - addValues (primarySelectAction); - declareEnum ("secondary-select-action", "Action for secondary select", SelectAdd). - setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). - addValues (secondarySelectAction); + declareEnum("outside-drop", "Handling drops outside of cells", createAndInsert).addValues(insertOutsideCell); + declareEnum("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert) + .addValues(insertOutsideVisibleCell); + declareEnum("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit) + .setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record.") + .addValues(landeditOutsideCell); + declareEnum("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit) + .setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible.") + .addValues(landeditOutsideVisibleCell); + declareInt("texturebrush-maximumsize", "Maximum texture brush size", 50).setMin(1); + declareInt("shapebrush-maximumsize", "Maximum height edit brush size", 100) + .setTooltip("Setting for the slider range of brush size in terrain height editing.") + .setMin(1); + declareBool("landedit-post-smoothpainting", "Smooth land after painting height", false) + .setTooltip("Raise and lower tools will leave bumpy finish without this option"); + declareDouble("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25) + .setTooltip( + "If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. " + "Negative values may be used to roughen instead of smooth.") + .setMin(-1) + .setMax(1); + declareBool("open-list-view", "Open displays list view", false) + .setTooltip( + "When opening a reference from the scene view, it will open the" + " instance list view instead of the individual instance record view."); + declareEnum("primary-select-action", "Action for primary select", SelectOnly) + .setTooltip( + "Selection can be chosen between select only, add to selection, remove from selection and invert " + "selection.") + .addValues(primarySelectAction); + declareEnum("secondary-select-action", "Action for secondary select", SelectAdd) + .setTooltip( + "Selection can be chosen between select only, add to selection, remove from selection and invert " + "selection.") + .addValues(secondarySelectAction); - declareCategory ("Key Bindings"); + declareCategory("Key Bindings"); - declareSubcategory ("Document"); - declareShortcut ("document-file-newgame", "New Game", QKeySequence(Qt::ControlModifier | Qt::Key_N)); - declareShortcut ("document-file-newaddon", "New Addon", QKeySequence()); - declareShortcut ("document-file-open", "Open", QKeySequence(Qt::ControlModifier | Qt::Key_O)); - declareShortcut ("document-file-save", "Save", QKeySequence(Qt::ControlModifier | Qt::Key_S)); - declareShortcut ("document-help-help", "Help", QKeySequence(Qt::Key_F1)); - declareShortcut ("document-help-tutorial", "Tutorial", QKeySequence()); - declareShortcut ("document-file-verify", "Verify", QKeySequence()); - declareShortcut ("document-file-merge", "Merge", QKeySequence()); - declareShortcut ("document-file-errorlog", "Open Load Error Log", QKeySequence()); - declareShortcut ("document-file-metadata", "Meta Data", QKeySequence()); - declareShortcut ("document-file-close", "Close Document", QKeySequence(Qt::ControlModifier | Qt::Key_W)); - declareShortcut ("document-file-exit", "Exit Application", QKeySequence(Qt::ControlModifier | Qt::Key_Q)); - declareShortcut ("document-edit-undo", "Undo", QKeySequence(Qt::ControlModifier | Qt::Key_Z)); - declareShortcut ("document-edit-redo", "Redo", QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Z)); - declareShortcut ("document-edit-preferences", "Open Preferences", QKeySequence()); - declareShortcut ("document-edit-search", "Search", QKeySequence(Qt::ControlModifier | Qt::Key_F)); - declareShortcut ("document-view-newview", "New View", QKeySequence()); - declareShortcut ("document-view-statusbar", "Toggle Status Bar", QKeySequence()); - declareShortcut ("document-view-filters", "Open Filter List", QKeySequence()); - declareShortcut ("document-world-regions", "Open Region List", QKeySequence()); - declareShortcut ("document-world-cells", "Open Cell List", QKeySequence()); - declareShortcut ("document-world-referencables", "Open Object List", QKeySequence()); - declareShortcut ("document-world-references", "Open Instance List", QKeySequence()); - declareShortcut ("document-world-lands", "Open Lands List", QKeySequence()); - declareShortcut ("document-world-landtextures", "Open Land Textures List", QKeySequence()); - declareShortcut ("document-world-pathgrid", "Open Pathgrid List", QKeySequence()); - declareShortcut ("document-world-regionmap", "Open Region Map", QKeySequence()); - declareShortcut ("document-mechanics-globals", "Open Global List", QKeySequence()); - declareShortcut ("document-mechanics-gamesettings", "Open Game Settings", QKeySequence()); - declareShortcut ("document-mechanics-scripts", "Open Script List", QKeySequence()); - declareShortcut ("document-mechanics-spells", "Open Spell List", QKeySequence()); - declareShortcut ("document-mechanics-enchantments", "Open Enchantment List", QKeySequence()); - declareShortcut ("document-mechanics-magiceffects", "Open Magic Effect List", QKeySequence()); - declareShortcut ("document-mechanics-startscripts", "Open Start Script List", QKeySequence()); - declareShortcut ("document-character-skills", "Open Skill List", QKeySequence()); - declareShortcut ("document-character-classes", "Open Class List", QKeySequence()); - declareShortcut ("document-character-factions", "Open Faction List", QKeySequence()); - declareShortcut ("document-character-races", "Open Race List", QKeySequence()); - declareShortcut ("document-character-birthsigns", "Open Birthsign List", QKeySequence()); - declareShortcut ("document-character-topics", "Open Topic List", QKeySequence()); - declareShortcut ("document-character-journals", "Open Journal List", QKeySequence()); - declareShortcut ("document-character-topicinfos", "Open Topic Info List", QKeySequence()); - declareShortcut ("document-character-journalinfos", "Open Journal Info List", QKeySequence()); - declareShortcut ("document-character-bodyparts", "Open Body Part List", QKeySequence()); - declareShortcut ("document-assets-reload", "Reload Assets", QKeySequence(Qt::Key_F5)); - declareShortcut ("document-assets-sounds", "Open Sound Asset List", QKeySequence()); - declareShortcut ("document-assets-soundgens", "Open Sound Generator List", QKeySequence()); - declareShortcut ("document-assets-meshes", "Open Mesh Asset List", QKeySequence()); - declareShortcut ("document-assets-icons", "Open Icon Asset List", QKeySequence()); - declareShortcut ("document-assets-music", "Open Music Asset List", QKeySequence()); - declareShortcut ("document-assets-soundres", "Open Sound File List", QKeySequence()); - declareShortcut ("document-assets-textures", "Open Texture Asset List", QKeySequence()); - declareShortcut ("document-assets-videos", "Open Video Asset List", QKeySequence()); - declareShortcut ("document-debug-run", "Run Debug", QKeySequence()); - declareShortcut ("document-debug-shutdown", "Stop Debug", QKeySequence()); - declareShortcut ("document-debug-profiles", "Debug Profiles", QKeySequence()); - declareShortcut ("document-debug-runlog", "Open Run Log", QKeySequence()); + declareSubcategory("Document"); + declareShortcut("document-file-newgame", "New Game", QKeySequence(Qt::ControlModifier | Qt::Key_N)); + declareShortcut("document-file-newaddon", "New Addon", QKeySequence()); + declareShortcut("document-file-open", "Open", QKeySequence(Qt::ControlModifier | Qt::Key_O)); + declareShortcut("document-file-save", "Save", QKeySequence(Qt::ControlModifier | Qt::Key_S)); + declareShortcut("document-help-help", "Help", QKeySequence(Qt::Key_F1)); + declareShortcut("document-help-tutorial", "Tutorial", QKeySequence()); + declareShortcut("document-file-verify", "Verify", QKeySequence()); + declareShortcut("document-file-merge", "Merge", QKeySequence()); + declareShortcut("document-file-errorlog", "Open Load Error Log", QKeySequence()); + declareShortcut("document-file-metadata", "Meta Data", QKeySequence()); + declareShortcut("document-file-close", "Close Document", QKeySequence(Qt::ControlModifier | Qt::Key_W)); + declareShortcut("document-file-exit", "Exit Application", QKeySequence(Qt::ControlModifier | Qt::Key_Q)); + declareShortcut("document-edit-undo", "Undo", QKeySequence(Qt::ControlModifier | Qt::Key_Z)); + declareShortcut("document-edit-redo", "Redo", QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Z)); + declareShortcut("document-edit-preferences", "Open Preferences", QKeySequence()); + declareShortcut("document-edit-search", "Search", QKeySequence(Qt::ControlModifier | Qt::Key_F)); + declareShortcut("document-view-newview", "New View", QKeySequence()); + declareShortcut("document-view-statusbar", "Toggle Status Bar", QKeySequence()); + declareShortcut("document-view-filters", "Open Filter List", QKeySequence()); + declareShortcut("document-world-regions", "Open Region List", QKeySequence()); + declareShortcut("document-world-cells", "Open Cell List", QKeySequence()); + declareShortcut("document-world-referencables", "Open Object List", QKeySequence()); + declareShortcut("document-world-references", "Open Instance List", QKeySequence()); + declareShortcut("document-world-lands", "Open Lands List", QKeySequence()); + declareShortcut("document-world-landtextures", "Open Land Textures List", QKeySequence()); + declareShortcut("document-world-pathgrid", "Open Pathgrid List", QKeySequence()); + declareShortcut("document-world-regionmap", "Open Region Map", QKeySequence()); + declareShortcut("document-mechanics-globals", "Open Global List", QKeySequence()); + declareShortcut("document-mechanics-gamesettings", "Open Game Settings", QKeySequence()); + declareShortcut("document-mechanics-scripts", "Open Script List", QKeySequence()); + declareShortcut("document-mechanics-spells", "Open Spell List", QKeySequence()); + declareShortcut("document-mechanics-enchantments", "Open Enchantment List", QKeySequence()); + declareShortcut("document-mechanics-magiceffects", "Open Magic Effect List", QKeySequence()); + declareShortcut("document-mechanics-startscripts", "Open Start Script List", QKeySequence()); + declareShortcut("document-character-skills", "Open Skill List", QKeySequence()); + declareShortcut("document-character-classes", "Open Class List", QKeySequence()); + declareShortcut("document-character-factions", "Open Faction List", QKeySequence()); + declareShortcut("document-character-races", "Open Race List", QKeySequence()); + declareShortcut("document-character-birthsigns", "Open Birthsign List", QKeySequence()); + declareShortcut("document-character-topics", "Open Topic List", QKeySequence()); + declareShortcut("document-character-journals", "Open Journal List", QKeySequence()); + declareShortcut("document-character-topicinfos", "Open Topic Info List", QKeySequence()); + declareShortcut("document-character-journalinfos", "Open Journal Info List", QKeySequence()); + declareShortcut("document-character-bodyparts", "Open Body Part List", QKeySequence()); + declareShortcut("document-assets-reload", "Reload Assets", QKeySequence(Qt::Key_F5)); + declareShortcut("document-assets-sounds", "Open Sound Asset List", QKeySequence()); + declareShortcut("document-assets-soundgens", "Open Sound Generator List", QKeySequence()); + declareShortcut("document-assets-meshes", "Open Mesh Asset List", QKeySequence()); + declareShortcut("document-assets-icons", "Open Icon Asset List", QKeySequence()); + declareShortcut("document-assets-music", "Open Music Asset List", QKeySequence()); + declareShortcut("document-assets-soundres", "Open Sound File List", QKeySequence()); + declareShortcut("document-assets-textures", "Open Texture Asset List", QKeySequence()); + declareShortcut("document-assets-videos", "Open Video Asset List", QKeySequence()); + declareShortcut("document-debug-run", "Run Debug", QKeySequence()); + declareShortcut("document-debug-shutdown", "Stop Debug", QKeySequence()); + declareShortcut("document-debug-profiles", "Debug Profiles", QKeySequence()); + declareShortcut("document-debug-runlog", "Open Run Log", QKeySequence()); - declareSubcategory ("Table"); - declareShortcut ("table-edit", "Edit Record", QKeySequence()); - declareShortcut ("table-add", "Add Row/Record", QKeySequence(Qt::ShiftModifier | Qt::Key_A)); - declareShortcut ("table-clone", "Clone Record", QKeySequence(Qt::ShiftModifier | Qt::Key_D)); - declareShortcut ("touch-record", "Touch Record", QKeySequence()); - declareShortcut ("table-revert", "Revert Record", QKeySequence()); - declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete)); - declareShortcut ("table-moveup", "Move Record Up", QKeySequence()); - declareShortcut ("table-movedown", "Move Record Down", QKeySequence()); - declareShortcut ("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); - declareShortcut ("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V)); - declareShortcut ("table-extendeddelete", "Extended Record Deletion", QKeySequence()); - declareShortcut ("table-extendedrevert", "Extended Record Revertion", QKeySequence()); + declareSubcategory("Table"); + declareShortcut("table-edit", "Edit Record", QKeySequence()); + declareShortcut("table-add", "Add Row/Record", QKeySequence(Qt::ShiftModifier | Qt::Key_A)); + declareShortcut("table-clone", "Clone Record", QKeySequence(Qt::ShiftModifier | Qt::Key_D)); + declareShortcut("touch-record", "Touch Record", QKeySequence()); + declareShortcut("table-revert", "Revert Record", QKeySequence()); + declareShortcut("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete)); + declareShortcut("table-moveup", "Move Record Up", QKeySequence()); + declareShortcut("table-movedown", "Move Record Down", QKeySequence()); + declareShortcut("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); + declareShortcut("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V)); + declareShortcut("table-extendeddelete", "Extended Record Deletion", QKeySequence()); + declareShortcut("table-extendedrevert", "Extended Record Revertion", QKeySequence()); - declareSubcategory ("Report Table"); - declareShortcut ("reporttable-show", "Show Report", QKeySequence()); - declareShortcut ("reporttable-remove", "Remove Report", QKeySequence(Qt::Key_Delete)); - declareShortcut ("reporttable-replace", "Replace Report", QKeySequence()); - declareShortcut ("reporttable-refresh", "Refresh Report", QKeySequence()); + declareSubcategory("Report Table"); + declareShortcut("reporttable-show", "Show Report", QKeySequence()); + declareShortcut("reporttable-remove", "Remove Report", QKeySequence(Qt::Key_Delete)); + declareShortcut("reporttable-replace", "Replace Report", QKeySequence()); + declareShortcut("reporttable-refresh", "Refresh Report", QKeySequence()); - declareSubcategory ("Scene"); - declareShortcut ("scene-navi-primary", "Camera Rotation From Mouse Movement", QKeySequence(Qt::LeftButton)); - declareShortcut ("scene-navi-secondary", "Camera Translation From Mouse Movement", + declareSubcategory("Scene"); + declareShortcut("scene-navi-primary", "Camera Rotation From Mouse Movement", QKeySequence(Qt::LeftButton)); + declareShortcut("scene-navi-secondary", "Camera Translation From Mouse Movement", QKeySequence(Qt::ControlModifier | (int)Qt::LeftButton)); - declareShortcut ("scene-open-primary", "Primary Open", QKeySequence(Qt::ShiftModifier | (int)Qt::LeftButton)); - declareShortcut ("scene-edit-primary", "Primary Edit", QKeySequence(Qt::RightButton)); - declareShortcut ("scene-edit-secondary", "Secondary Edit", - QKeySequence(Qt::ControlModifier | (int)Qt::RightButton)); - declareShortcut ("scene-select-primary", "Primary Select", QKeySequence(Qt::MiddleButton)); - declareShortcut ("scene-select-secondary", "Secondary Select", - QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); - declareModifier ("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift); - declareShortcut ("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete)); - declareShortcut ("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G)); - declareShortcut ("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H)); - declareShortcut ("scene-instance-drop-terrain-separately", "Drop to terrain level separately", QKeySequence()); - declareShortcut ("scene-instance-drop-collision-separately", "Drop to collision separately", QKeySequence()); - declareShortcut ("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5)); - declareShortcut ("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6)); - declareShortcut ("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8)); - declareShortcut ("scene-load-cam-westcell", "Load West Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_4)); - declareShortcut ("scene-load-cam-southcell", "Load South Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_2)); - declareShortcut ("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape)); - declareShortcut ("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); - declareShortcut ("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); + declareShortcut("scene-open-primary", "Primary Open", QKeySequence(Qt::ShiftModifier | (int)Qt::LeftButton)); + declareShortcut("scene-edit-primary", "Primary Edit", QKeySequence(Qt::RightButton)); + declareShortcut("scene-edit-secondary", "Secondary Edit", QKeySequence(Qt::ControlModifier | (int)Qt::RightButton)); + declareShortcut("scene-select-primary", "Primary Select", QKeySequence(Qt::MiddleButton)); + declareShortcut( + "scene-select-secondary", "Secondary Select", QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); + declareShortcut( + "scene-select-tertiary", "Tertiary Select", QKeySequence(Qt::ShiftModifier | (int)Qt::MiddleButton)); + declareModifier("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift); + declareShortcut("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete)); + declareShortcut("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G)); + declareShortcut("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H)); + declareShortcut("scene-instance-drop-terrain-separately", "Drop to terrain level separately", QKeySequence()); + declareShortcut("scene-instance-drop-collision-separately", "Drop to collision separately", QKeySequence()); + declareShortcut("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5)); + declareShortcut("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6)); + declareShortcut("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8)); + declareShortcut("scene-load-cam-westcell", "Load West Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_4)); + declareShortcut("scene-load-cam-southcell", "Load South Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_2)); + declareShortcut("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape)); + declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); + declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); - declareSubcategory ("1st/Free Camera"); - declareShortcut ("free-forward", "Forward", QKeySequence(Qt::Key_W)); - declareShortcut ("free-backward", "Backward", QKeySequence(Qt::Key_S)); - declareShortcut ("free-left", "Left", QKeySequence(Qt::Key_A)); - declareShortcut ("free-right", "Right", QKeySequence(Qt::Key_D)); - declareShortcut ("free-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); - declareShortcut ("free-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); - declareShortcut ("free-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); + declareSubcategory("1st/Free Camera"); + declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W)); + declareShortcut("free-backward", "Backward", QKeySequence(Qt::Key_S)); + declareShortcut("free-left", "Left", QKeySequence(Qt::Key_A)); + declareShortcut("free-right", "Right", QKeySequence(Qt::Key_D)); + declareShortcut("free-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); + declareShortcut("free-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); + declareShortcut("free-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); - declareSubcategory ("Orbit Camera"); - declareShortcut ("orbit-up", "Up", QKeySequence(Qt::Key_W)); - declareShortcut ("orbit-down", "Down", QKeySequence(Qt::Key_S)); - declareShortcut ("orbit-left", "Left", QKeySequence(Qt::Key_A)); - declareShortcut ("orbit-right", "Right", QKeySequence(Qt::Key_D)); - declareShortcut ("orbit-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); - declareShortcut ("orbit-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); - declareShortcut ("orbit-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); - declareShortcut ("orbit-center-selection", "Center On Selected", QKeySequence(Qt::Key_C)); + declareSubcategory("Orbit Camera"); + declareShortcut("orbit-up", "Up", QKeySequence(Qt::Key_W)); + declareShortcut("orbit-down", "Down", QKeySequence(Qt::Key_S)); + declareShortcut("orbit-left", "Left", QKeySequence(Qt::Key_A)); + declareShortcut("orbit-right", "Right", QKeySequence(Qt::Key_D)); + declareShortcut("orbit-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); + declareShortcut("orbit-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); + declareShortcut("orbit-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); + declareShortcut("orbit-center-selection", "Center On Selected", QKeySequence(Qt::Key_C)); - declareSubcategory ("Script Editor"); - declareShortcut ("script-editor-comment", "Comment Selection", QKeySequence()); - declareShortcut ("script-editor-uncomment", "Uncomment Selection", QKeySequence()); + declareSubcategory("Script Editor"); + declareShortcut("script-editor-comment", "Comment Selection", QKeySequence()); + declareShortcut("script-editor-uncomment", "Uncomment Selection", QKeySequence()); - declareCategory ("Models"); - declareString ("baseanim", "base animations", "meshes/base_anim.nif"). - setTooltip("3rd person base model with textkeys-data"); - declareString ("baseanimkna", "base animations, kna", "meshes/base_animkna.nif"). - setTooltip("3rd person beast race base model with textkeys-data"); - declareString ("baseanimfemale", "base animations, female", "meshes/base_anim_female.nif"). - setTooltip("3rd person female base model with textkeys-data"); - declareString ("wolfskin", "base animations, wolf", "meshes/wolf/skin.nif"). - setTooltip("3rd person werewolf skin"); + declareCategory("Models"); + declareString("baseanim", "base animations", "meshes/base_anim.nif") + .setTooltip("3rd person base model with textkeys-data"); + declareString("baseanimkna", "base animations, kna", "meshes/base_animkna.nif") + .setTooltip("3rd person beast race base model with textkeys-data"); + declareString("baseanimfemale", "base animations, female", "meshes/base_anim_female.nif") + .setTooltip("3rd person female base model with textkeys-data"); + declareString("wolfskin", "base animations, wolf", "meshes/wolf/skin.nif").setTooltip("3rd person werewolf skin"); } -void CSMPrefs::State::declareCategory (const std::string& key) +void CSMPrefs::State::declareCategory(const std::string& key) { - std::map::iterator iter = mCategories.find (key); + std::map::iterator iter = mCategories.find(key); - if (iter!=mCategories.end()) + if (iter != mCategories.end()) { mCurrentCategory = iter; } else { - mCurrentCategory = - mCategories.insert (std::make_pair (key, Category (this, key))).first; + mCurrentCategory = mCategories.insert(std::make_pair(key, Category(this, key))).first; } } -CSMPrefs::IntSetting& CSMPrefs::State::declareInt (const std::string& key, - const std::string& label, int default_) +CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const std::string& label, int default_) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); setDefault(key, std::to_string(default_)); - default_ = mSettings.getInt (key, mCurrentCategory->second.getKey()); + default_ = Settings::Manager::getInt(key, mCurrentCategory->second.getKey()); - CSMPrefs::IntSetting *setting = - new CSMPrefs::IntSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, - default_); + CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, default_); - mCurrentCategory->second.addSetting (setting); + mCurrentCategory->second.addSetting(setting); return *setting; } -CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble (const std::string& key, - const std::string& label, double default_) +CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble( + const std::string& key, const std::string& label, double default_) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); std::ostringstream stream; stream << default_; setDefault(key, stream.str()); - default_ = mSettings.getFloat (key, mCurrentCategory->second.getKey()); + default_ = Settings::Manager::getFloat(key, mCurrentCategory->second.getKey()); - CSMPrefs::DoubleSetting *setting = - new CSMPrefs::DoubleSetting (&mCurrentCategory->second, &mSettings, &mMutex, - key, label, default_); + CSMPrefs::DoubleSetting* setting + = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, default_); - mCurrentCategory->second.addSetting (setting); + mCurrentCategory->second.addSetting(setting); return *setting; } -CSMPrefs::BoolSetting& CSMPrefs::State::declareBool (const std::string& key, - const std::string& label, bool default_) +CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, const std::string& label, bool default_) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); - setDefault (key, default_ ? "true" : "false"); + setDefault(key, default_ ? "true" : "false"); - default_ = mSettings.getBool (key, mCurrentCategory->second.getKey()); + default_ = Settings::Manager::getBool(key, mCurrentCategory->second.getKey()); - CSMPrefs::BoolSetting *setting = - new CSMPrefs::BoolSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, - default_); + CSMPrefs::BoolSetting* setting + = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, default_); - mCurrentCategory->second.addSetting (setting); + mCurrentCategory->second.addSetting(setting); return *setting; } -CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum (const std::string& key, - const std::string& label, EnumValue default_) +CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum( + const std::string& key, const std::string& label, EnumValue default_) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); - setDefault (key, default_.mValue); + setDefault(key, default_.mValue); - default_.mValue = mSettings.getString (key, mCurrentCategory->second.getKey()); + default_.mValue = Settings::Manager::getString(key, mCurrentCategory->second.getKey()); - CSMPrefs::EnumSetting *setting = - new CSMPrefs::EnumSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, - default_); + CSMPrefs::EnumSetting* setting + = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, default_); - mCurrentCategory->second.addSetting (setting); + mCurrentCategory->second.addSetting(setting); return *setting; } -CSMPrefs::ColourSetting& CSMPrefs::State::declareColour (const std::string& key, - const std::string& label, QColor default_) +CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( + const std::string& key, const std::string& label, QColor default_) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); - setDefault (key, default_.name().toUtf8().data()); + setDefault(key, default_.name().toUtf8().data()); - default_.setNamedColor (QString::fromUtf8 (mSettings.getString (key, mCurrentCategory->second.getKey()).c_str())); + default_.setNamedColor( + QString::fromUtf8(Settings::Manager::getString(key, mCurrentCategory->second.getKey()).c_str())); - CSMPrefs::ColourSetting *setting = - new CSMPrefs::ColourSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, - default_); + CSMPrefs::ColourSetting* setting + = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, default_); - mCurrentCategory->second.addSetting (setting); + mCurrentCategory->second.addSetting(setting); return *setting; } -CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut (const std::string& key, const std::string& label, - const QKeySequence& default_) +CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( + const std::string& key, const std::string& label, const QKeySequence& default_) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); std::string seqStr = getShortcutManager().convertToString(default_); - setDefault (key, seqStr); + setDefault(key, seqStr); // Setup with actual data QKeySequence sequence; - getShortcutManager().convertFromString(mSettings.getString(key, mCurrentCategory->second.getKey()), sequence); + getShortcutManager().convertFromString( + Settings::Manager::getString(key, mCurrentCategory->second.getKey()), sequence); getShortcutManager().setSequence(key, sequence); - CSMPrefs::ShortcutSetting *setting = new CSMPrefs::ShortcutSetting (&mCurrentCategory->second, &mSettings, &mMutex, - key, label); - mCurrentCategory->second.addSetting (setting); + CSMPrefs::ShortcutSetting* setting = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label); + mCurrentCategory->second.addSetting(setting); return *setting; } -CSMPrefs::StringSetting& CSMPrefs::State::declareString (const std::string& key, const std::string& label, std::string default_) +CSMPrefs::StringSetting& CSMPrefs::State::declareString( + const std::string& key, const std::string& label, std::string default_) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); - setDefault (key, default_); + setDefault(key, default_); - default_ = mSettings.getString (key, mCurrentCategory->second.getKey()); + default_ = Settings::Manager::getString(key, mCurrentCategory->second.getKey()); - CSMPrefs::StringSetting *setting = - new CSMPrefs::StringSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, - default_); + CSMPrefs::StringSetting* setting + = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, default_); - mCurrentCategory->second.addSetting (setting); + mCurrentCategory->second.addSetting(setting); return *setting; } -CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const std::string& label, - int default_) +CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier( + const std::string& key, const std::string& label, int default_) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); std::string modStr = getShortcutManager().convertToString(default_); - setDefault (key, modStr); + setDefault(key, modStr); // Setup with actual data int modifier; - getShortcutManager().convertFromString(mSettings.getString(key, mCurrentCategory->second.getKey()), modifier); + getShortcutManager().convertFromString( + Settings::Manager::getString(key, mCurrentCategory->second.getKey()), modifier); getShortcutManager().setModifier(key, modifier); - CSMPrefs::ModifierSetting *setting = new CSMPrefs::ModifierSetting (&mCurrentCategory->second, &mSettings, &mMutex, - key, label); - mCurrentCategory->second.addSetting (setting); + CSMPrefs::ModifierSetting* setting = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, key, label); + mCurrentCategory->second.addSetting(setting); return *setting; } void CSMPrefs::State::declareSeparator() { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); - CSMPrefs::Setting *setting = - new CSMPrefs::Setting (&mCurrentCategory->second, &mSettings, &mMutex, "", ""); + CSMPrefs::Setting* setting = new CSMPrefs::Setting(&mCurrentCategory->second, &mMutex, "", ""); - mCurrentCategory->second.addSetting (setting); + mCurrentCategory->second.addSetting(setting); } void CSMPrefs::State::declareSubcategory(const std::string& label) { - if (mCurrentCategory==mCategories.end()) - throw std::logic_error ("no category for setting"); + if (mCurrentCategory == mCategories.end()) + throw std::logic_error("no category for setting"); - CSMPrefs::Setting *setting = - new CSMPrefs::Setting (&mCurrentCategory->second, &mSettings, &mMutex, "", label); + CSMPrefs::Setting* setting = new CSMPrefs::Setting(&mCurrentCategory->second, &mMutex, "", label); - mCurrentCategory->second.addSetting (setting); + mCurrentCategory->second.addSetting(setting); } -void CSMPrefs::State::setDefault (const std::string& key, const std::string& default_) +void CSMPrefs::State::setDefault(const std::string& key, const std::string& default_) { - Settings::CategorySetting fullKey (mCurrentCategory->second.getKey(), key); + Settings::CategorySetting fullKey(mCurrentCategory->second.getKey(), key); - Settings::CategorySettingValueMap::iterator iter = - mSettings.mDefaultSettings.find (fullKey); + Settings::CategorySettingValueMap::iterator iter = Settings::Manager::mDefaultSettings.find(fullKey); - if (iter==mSettings.mDefaultSettings.end()) - mSettings.mDefaultSettings.insert (std::make_pair (fullKey, default_)); + if (iter == Settings::Manager::mDefaultSettings.end()) + Settings::Manager::mDefaultSettings.insert(std::make_pair(fullKey, default_)); } -CSMPrefs::State::State (const Files::ConfigurationManager& configurationManager) -: mConfigFile ("openmw-cs.cfg"), mDefaultConfigFile("defaults-cs.bin"), mConfigurationManager (configurationManager), - mCurrentCategory (mCategories.end()) +CSMPrefs::State::State(const Files::ConfigurationManager& configurationManager) + : mConfigFile("openmw-cs.cfg") + , mDefaultConfigFile("defaults-cs.bin") + , mConfigurationManager(configurationManager) + , mCurrentCategory(mCategories.end()) { if (sThis) - throw std::logic_error ("An instance of CSMPRefs::State already exists"); + throw std::logic_error("An instance of CSMPRefs::State already exists"); sThis = this; - load(); declare(); } @@ -660,8 +666,7 @@ CSMPrefs::State::~State() void CSMPrefs::State::save() { - boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; - mSettings.saveUser (user.string()); + Settings::Manager::saveUser(mConfigurationManager.getUserConfigPath() / mConfigFile); } CSMPrefs::State::Iterator CSMPrefs::State::begin() @@ -679,41 +684,41 @@ CSMPrefs::ShortcutManager& CSMPrefs::State::getShortcutManager() return mShortcutManager; } -CSMPrefs::Category& CSMPrefs::State::operator[] (const std::string& key) +CSMPrefs::Category& CSMPrefs::State::operator[](const std::string& key) { - Iterator iter = mCategories.find (key); + Iterator iter = mCategories.find(key); - if (iter==mCategories.end()) - throw std::logic_error ("Invalid user settings category: " + key); + if (iter == mCategories.end()) + throw std::logic_error("Invalid user settings category: " + key); return iter->second; } -void CSMPrefs::State::update (const Setting& setting) +void CSMPrefs::State::update(const Setting& setting) { - emit (settingChanged (&setting)); + emit settingChanged(&setting); } CSMPrefs::State& CSMPrefs::State::get() { if (!sThis) - throw std::logic_error ("No instance of CSMPrefs::State"); + throw std::logic_error("No instance of CSMPrefs::State"); return *sThis; } void CSMPrefs::State::resetCategory(const std::string& category) { - for (Settings::CategorySettingValueMap::iterator i = mSettings.mUserSettings.begin(); - i != mSettings.mUserSettings.end(); ++i) + for (Settings::CategorySettingValueMap::iterator i = Settings::Manager::mUserSettings.begin(); + i != Settings::Manager::mUserSettings.end(); ++i) { // if the category matches if (i->first.first == category) { // mark the setting as changed - mSettings.mChangedSettings.insert(std::make_pair(i->first.first, i->first.second)); + Settings::Manager::mChangedSettings.insert(std::make_pair(i->first.first, i->first.second)); // reset the value to the default - i->second = mSettings.mDefaultSettings[i->first]; + i->second = Settings::Manager::mDefaultSettings[i->first]; } } @@ -737,7 +742,6 @@ void CSMPrefs::State::resetAll() } } - CSMPrefs::State& CSMPrefs::get() { return State::get(); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 7c9fcbecd..354f4552e 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -4,19 +4,15 @@ #include #include -#include #include +#include #ifndef Q_MOC_RUN #include #endif -#include - #include "category.hpp" -#include "setting.hpp" #include "enumsetting.hpp" -#include "stringsetting.hpp" #include "shortcutmanager.hpp" class QColor; @@ -29,6 +25,8 @@ namespace CSMPrefs class ColourSetting; class ShortcutSetting; class ModifierSetting; + class Setting; + class StringSetting; /// \brief User settings state /// @@ -36,87 +34,80 @@ namespace CSMPrefs /// been completed. class State : public QObject { - Q_OBJECT + Q_OBJECT - static State *sThis; + static State* sThis; - public: + public: + typedef std::map Collection; + typedef Collection::iterator Iterator; - typedef std::map Collection; - typedef Collection::iterator Iterator; + private: + const std::string mConfigFile; + const std::string mDefaultConfigFile; + const Files::ConfigurationManager& mConfigurationManager; + ShortcutManager mShortcutManager; + Collection mCategories; + Iterator mCurrentCategory; + QMutex mMutex; - private: + // not implemented + State(const State&); + State& operator=(const State&); - const std::string mConfigFile; - const std::string mDefaultConfigFile; - const Files::ConfigurationManager& mConfigurationManager; - ShortcutManager mShortcutManager; - Settings::Manager mSettings; - Collection mCategories; - Iterator mCurrentCategory; - QMutex mMutex; + private: + void declare(); - // not implemented - State (const State&); - State& operator= (const State&); + void declareCategory(const std::string& key); - private: + IntSetting& declareInt(const std::string& key, const std::string& label, int default_); + DoubleSetting& declareDouble(const std::string& key, const std::string& label, double default_); - void load(); + BoolSetting& declareBool(const std::string& key, const std::string& label, bool default_); - void declare(); + EnumSetting& declareEnum(const std::string& key, const std::string& label, EnumValue default_); - void declareCategory (const std::string& key); + ColourSetting& declareColour(const std::string& key, const std::string& label, QColor default_); - IntSetting& declareInt (const std::string& key, const std::string& label, int default_); - DoubleSetting& declareDouble (const std::string& key, const std::string& label, double default_); + ShortcutSetting& declareShortcut( + const std::string& key, const std::string& label, const QKeySequence& default_); - BoolSetting& declareBool (const std::string& key, const std::string& label, bool default_); + StringSetting& declareString(const std::string& key, const std::string& label, std::string default_); - EnumSetting& declareEnum (const std::string& key, const std::string& label, EnumValue default_); + ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_); - ColourSetting& declareColour (const std::string& key, const std::string& label, QColor default_); + void declareSeparator(); - ShortcutSetting& declareShortcut (const std::string& key, const std::string& label, - const QKeySequence& default_); + void declareSubcategory(const std::string& label); - StringSetting& declareString (const std::string& key, const std::string& label, std::string default_); + void setDefault(const std::string& key, const std::string& default_); - ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_); + public: + State(const Files::ConfigurationManager& configurationManager); - void declareSeparator(); + ~State(); - void declareSubcategory(const std::string& label); + void save(); - void setDefault (const std::string& key, const std::string& default_); + Iterator begin(); - public: + Iterator end(); - State (const Files::ConfigurationManager& configurationManager); + ShortcutManager& getShortcutManager(); - ~State(); + Category& operator[](const std::string& key); - void save(); + void update(const Setting& setting); - Iterator begin(); + static State& get(); - Iterator end(); + void resetCategory(const std::string& category); - ShortcutManager& getShortcutManager(); + void resetAll(); - Category& operator[](const std::string& key); + signals: - void update (const Setting& setting); - - static State& get(); - - void resetCategory(const std::string& category); - - void resetAll(); - - signals: - - void settingChanged (const CSMPrefs::Setting *setting); + void settingChanged(const CSMPrefs::Setting* setting); }; // convenience function diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index 27290b6c7..9016e2c8c 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -6,49 +6,54 @@ #include +#include + #include "category.hpp" #include "state.hpp" -CSMPrefs::StringSetting::StringSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, std::string default_) -: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(nullptr) -{} +CSMPrefs::StringSetting::StringSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, std::string default_) + : Setting(parent, mutex, key, label) + , mDefault(default_) + , mWidget(nullptr) +{ +} -CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip (const std::string& tooltip) +CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip(const std::string& tooltip) { mTooltip = tooltip; return *this; } -std::pair CSMPrefs::StringSetting::makeWidgets (QWidget *parent) +std::pair CSMPrefs::StringSetting::makeWidgets(QWidget* parent) { - mWidget = new QLineEdit (QString::fromUtf8 (mDefault.c_str()), parent); + mWidget = new QLineEdit(QString::fromUtf8(mDefault.c_str()), parent); if (!mTooltip.empty()) { - QString tooltip = QString::fromUtf8 (mTooltip.c_str()); - mWidget->setToolTip (tooltip); + QString tooltip = QString::fromUtf8(mTooltip.c_str()); + mWidget->setToolTip(tooltip); } - connect (mWidget, SIGNAL (textChanged (QString)), this, SLOT (textChanged (QString))); + connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged); - return std::make_pair (static_cast (nullptr), mWidget); + return std::make_pair(static_cast(nullptr), mWidget); } void CSMPrefs::StringSetting::updateWidget() { if (mWidget) { - mWidget->setText(QString::fromStdString(getValues().getString(getKey(), getParent()->getKey()))); + mWidget->setText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); } } -void CSMPrefs::StringSetting::textChanged (const QString& text) +void CSMPrefs::StringSetting::textChanged(const QString& text) { { - QMutexLocker lock (getMutex()); - getValues().setString (getKey(), getParent()->getKey(), text.toStdString()); + QMutexLocker lock(getMutex()); + Settings::Manager::setString(getKey(), getParent()->getKey(), text.toStdString()); } - getParent()->getState()->update (*this); + getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 36d020f29..4494b1a95 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -3,33 +3,39 @@ #include "setting.hpp" +#include +#include + class QLineEdit; +class QMutex; +class QObject; +class QWidget; namespace CSMPrefs { + class Category; class StringSetting : public Setting { - Q_OBJECT + Q_OBJECT - std::string mTooltip; - std::string mDefault; - QLineEdit* mWidget; + std::string mTooltip; + std::string mDefault; + QLineEdit* mWidget; - public: + public: + StringSetting( + Category* parent, QMutex* mutex, const std::string& key, const std::string& label, std::string default_); - StringSetting (Category *parent, Settings::Manager *values, - QMutex *mutex, const std::string& key, const std::string& label, std::string default_); + StringSetting& setTooltip(const std::string& tooltip); - StringSetting& setTooltip (const std::string& tooltip); + /// Return label, input widget. + std::pair makeWidgets(QWidget* parent) override; - /// Return label, input widget. - std::pair makeWidgets (QWidget *parent) override; + void updateWidget() override; - void updateWidget() override; + private slots: - private slots: - - void textChanged (const QString& text); + void textChanged(const QString& text); }; } diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp index f91fc22f6..f642ab3a1 100644 --- a/apps/opencs/model/tools/birthsigncheck.cpp +++ b/apps/opencs/model/tools/birthsigncheck.cpp @@ -1,15 +1,25 @@ #include "birthsigncheck.hpp" +#include + +#include +#include +#include +#include +#include +#include + +#include #include #include "../prefs/state.hpp" #include "../world/universalid.hpp" -CSMTools::BirthsignCheckStage::BirthsignCheckStage (const CSMWorld::IdCollection& birthsigns, - const CSMWorld::Resources &textures) -: mBirthsigns(birthsigns), - mTextures(textures) +CSMTools::BirthsignCheckStage::BirthsignCheckStage( + const CSMWorld::IdCollection& birthsigns, const CSMWorld::Resources& textures) + : mBirthsigns(birthsigns) + , mTextures(textures) { mIgnoreBaseRecords = false; } @@ -21,9 +31,9 @@ int CSMTools::BirthsignCheckStage::setup() return mBirthsigns.getSize(); } -void CSMTools::BirthsignCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::BirthsignCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mBirthsigns.getRecord (stage); + const CSMWorld::Record& record = mBirthsigns.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -31,7 +41,7 @@ void CSMTools::BirthsignCheckStage::perform (int stage, CSMDoc::Messages& messag const ESM::BirthSign& birthsign = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Birthsign, birthsign.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Birthsign, birthsign.mId); if (birthsign.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); @@ -45,7 +55,7 @@ void CSMTools::BirthsignCheckStage::perform (int stage, CSMDoc::Messages& messag { std::string ddsTexture = birthsign.mTexture; if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsTexture) && mTextures.searchId(ddsTexture) != -1)) - messages.add(id, "Image '" + birthsign.mTexture + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Image '" + birthsign.mTexture + "' does not exist", "", CSMDoc::Message::Severity_Error); } /// \todo check data members that can't be edited in the table view diff --git a/apps/opencs/model/tools/birthsigncheck.hpp b/apps/opencs/model/tools/birthsigncheck.hpp index 498894f88..d01beb024 100644 --- a/apps/opencs/model/tools/birthsigncheck.hpp +++ b/apps/opencs/model/tools/birthsigncheck.hpp @@ -1,32 +1,43 @@ #ifndef CSM_TOOLS_BIRTHSIGNCHECK_H #define CSM_TOOLS_BIRTHSIGNCHECK_H -#include - #include "../world/idcollection.hpp" -#include "../world/resources.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class Resources; +} + +namespace ESM +{ + struct BirthSign; +} + namespace CSMTools { /// \brief VerifyStage: make sure that birthsign records are internally consistent class BirthsignCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection &mBirthsigns; - const CSMWorld::Resources &mTextures; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mBirthsigns; + const CSMWorld::Resources& mTextures; + bool mIgnoreBaseRecords; - public: + public: + BirthsignCheckStage( + const CSMWorld::IdCollection& birthsigns, const CSMWorld::Resources& textures); - BirthsignCheckStage (const CSMWorld::IdCollection &birthsigns, - const CSMWorld::Resources &textures); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/bodypartcheck.cpp b/apps/opencs/model/tools/bodypartcheck.cpp index 1490a8103..4fc9f5cf8 100644 --- a/apps/opencs/model/tools/bodypartcheck.cpp +++ b/apps/opencs/model/tools/bodypartcheck.cpp @@ -1,14 +1,24 @@ #include "bodypartcheck.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + #include "../prefs/state.hpp" -CSMTools::BodyPartCheckStage::BodyPartCheckStage( - const CSMWorld::IdCollection &bodyParts, - const CSMWorld::Resources &meshes, - const CSMWorld::IdCollection &races ) : - mBodyParts(bodyParts), - mMeshes(meshes), - mRaces(races) +CSMTools::BodyPartCheckStage::BodyPartCheckStage(const CSMWorld::IdCollection& bodyParts, + const CSMWorld::Resources& meshes, const CSMWorld::IdCollection& races) + : mBodyParts(bodyParts) + , mMeshes(meshes) + , mRaces(races) { mIgnoreBaseRecords = false; } @@ -20,37 +30,38 @@ int CSMTools::BodyPartCheckStage::setup() return mBodyParts.getSize(); } -void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &messages) +void CSMTools::BodyPartCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record &record = mBodyParts.getRecord(stage); + const CSMWorld::Record& record = mBodyParts.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; - const ESM::BodyPart &bodyPart = record.get(); + const ESM::BodyPart& bodyPart = record.get(); - CSMWorld::UniversalId id( CSMWorld::UniversalId::Type_BodyPart, bodyPart.mId ); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_BodyPart, bodyPart.mId); // Check BYDT - if (bodyPart.mData.mPart >= ESM::BodyPart::MP_Count ) + if (bodyPart.mData.mPart >= ESM::BodyPart::MP_Count) messages.add(id, "Invalid part", "", CSMDoc::Message::Severity_Error); - if (bodyPart.mData.mType > ESM::BodyPart::MT_Armor ) + if (bodyPart.mData.mType > ESM::BodyPart::MT_Armor) messages.add(id, "Invalid type", "", CSMDoc::Message::Severity_Error); // Check MODL - if ( bodyPart.mModel.empty() ) + if (bodyPart.mModel.empty()) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); - else if ( mMeshes.searchId( bodyPart.mModel ) == -1 ) + else if (mMeshes.searchId(bodyPart.mModel) == -1) messages.add(id, "Model '" + bodyPart.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); // Check FNAM for skin body parts (for non-skin body parts it's meaningless) - if ( bodyPart.mData.mType == ESM::BodyPart::MT_Skin ) + if (bodyPart.mData.mType == ESM::BodyPart::MT_Skin) { - if ( bodyPart.mRace.empty() ) + if (bodyPart.mRace.empty()) messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error); - else if ( mRaces.searchId( bodyPart.mRace ) == -1 ) - messages.add(id, "Race '" + bodyPart.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error); + else if (mRaces.searchId(bodyPart.mRace) == -1) + messages.add(id, "Race '" + bodyPart.mRace.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); } } diff --git a/apps/opencs/model/tools/bodypartcheck.hpp b/apps/opencs/model/tools/bodypartcheck.hpp index 2c379bd07..b945668e5 100644 --- a/apps/opencs/model/tools/bodypartcheck.hpp +++ b/apps/opencs/model/tools/bodypartcheck.hpp @@ -1,34 +1,46 @@ #ifndef CSM_TOOLS_BODYPARTCHECK_H #define CSM_TOOLS_BODYPARTCHECK_H -#include -#include +#include -#include "../world/resources.hpp" #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class Resources; +} + +namespace ESM +{ + struct BodyPart; + struct Race; +} + namespace CSMTools { /// \brief VerifyStage: make sure that body part records are internally consistent class BodyPartCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection &mBodyParts; - const CSMWorld::Resources &mMeshes; - const CSMWorld::IdCollection &mRaces; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mBodyParts; + const CSMWorld::Resources& mMeshes; + const CSMWorld::IdCollection& mRaces; + bool mIgnoreBaseRecords; public: - BodyPartCheckStage( - const CSMWorld::IdCollection &bodyParts, - const CSMWorld::Resources &meshes, - const CSMWorld::IdCollection &races ); + BodyPartCheckStage(const CSMWorld::IdCollection& bodyParts, const CSMWorld::Resources& meshes, + const CSMWorld::IdCollection& races); int setup() override; ///< \return number of steps - void perform(int stage, CSMDoc::Messages &messages) override; + void perform(int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index a82121597..0cbf5562f 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -1,16 +1,23 @@ #include "classcheck.hpp" #include +#include +#include -#include -#include +#include +#include +#include +#include +#include +#include + +#include +#include #include "../prefs/state.hpp" -#include "../world/universalid.hpp" - -CSMTools::ClassCheckStage::ClassCheckStage (const CSMWorld::IdCollection& classes) -: mClasses (classes) +CSMTools::ClassCheckStage::ClassCheckStage(const CSMWorld::IdCollection& classes) + : mClasses(classes) { mIgnoreBaseRecords = false; } @@ -22,9 +29,9 @@ int CSMTools::ClassCheckStage::setup() return mClasses.getSize(); } -void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::ClassCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mClasses.getRecord (stage); + const CSMWorld::Record& record = mClasses.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -32,7 +39,7 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) const ESM::Class& class_ = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Class, class_.mId); // A class should have a name if (class_.mName.empty()) @@ -43,27 +50,37 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) messages.add(id, "Description of a playable class is missing", "", CSMDoc::Message::Severity_Warning); // test for invalid attributes - for (int i=0; i<2; ++i) - if (class_.mData.mAttribute[i]==-1) - { - messages.add(id, "Attribute #" + std::to_string(i) + " is not set", "", CSMDoc::Message::Severity_Error); - } - - if (class_.mData.mAttribute[0]==class_.mData.mAttribute[1] && class_.mData.mAttribute[0]!=-1) + std::map attributeCount; + for (size_t i = 0; i < class_.mData.mAttribute.size(); ++i) { - messages.add(id, "Same attribute is listed twice", "", CSMDoc::Message::Severity_Error); + int attribute = class_.mData.mAttribute[i]; + if (attribute == -1) + messages.add(id, "Attribute #" + std::to_string(i) + " is not set", {}, CSMDoc::Message::Severity_Error); + else + { + auto it = attributeCount.find(attribute); + if (it == attributeCount.end()) + attributeCount.emplace(attribute, 1); + else + { + if (it->second == 1) + messages.add(id, "Same attribute is listed twice", {}, CSMDoc::Message::Severity_Error); + ++it->second; + } + } } // test for non-unique skill std::map skills; // ID, number of occurrences - for (int i=0; i<5; ++i) - for (int i2=0; i2<2; ++i2) - ++skills[class_.mData.mSkills[i][i2]]; + for (const auto& s : class_.mData.mSkills) + for (int skill : s) + ++skills[skill]; - for (auto &skill : skills) - if (skill.second>1) + for (auto& skill : skills) + if (skill.second > 1) { - messages.add(id, "Skill " + ESM::Skill::indexToId (skill.first) + " is listed more than once", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Skill " + ESM::Skill::indexToRefId(skill.first).toString() + " is listed more than once", + "", CSMDoc::Message::Severity_Error); } } diff --git a/apps/opencs/model/tools/classcheck.hpp b/apps/opencs/model/tools/classcheck.hpp index a78c2eb97..9830a2b9d 100644 --- a/apps/opencs/model/tools/classcheck.hpp +++ b/apps/opencs/model/tools/classcheck.hpp @@ -1,29 +1,36 @@ #ifndef CSM_TOOLS_CLASSCHECK_H #define CSM_TOOLS_CLASSCHECK_H -#include - #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + struct Class; +} + namespace CSMTools { /// \brief VerifyStage: make sure that class records are internally consistent class ClassCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mClasses; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mClasses; + bool mIgnoreBaseRecords; - public: + public: + ClassCheckStage(const CSMWorld::IdCollection& classes); - ClassCheckStage (const CSMWorld::IdCollection& classes); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/enchantmentcheck.cpp b/apps/opencs/model/tools/enchantmentcheck.cpp index 28f2b32cb..d6cb22b73 100644 --- a/apps/opencs/model/tools/enchantmentcheck.cpp +++ b/apps/opencs/model/tools/enchantmentcheck.cpp @@ -1,11 +1,24 @@ #include "enchantmentcheck.hpp" +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + #include "../prefs/state.hpp" #include "../world/universalid.hpp" -CSMTools::EnchantmentCheckStage::EnchantmentCheckStage (const CSMWorld::IdCollection& enchantments) - : mEnchantments (enchantments) +CSMTools::EnchantmentCheckStage::EnchantmentCheckStage(const CSMWorld::IdCollection& enchantments) + : mEnchantments(enchantments) { mIgnoreBaseRecords = false; } @@ -17,9 +30,9 @@ int CSMTools::EnchantmentCheckStage::setup() return mEnchantments.getSize(); } -void CSMTools::EnchantmentCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::EnchantmentCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mEnchantments.getRecord (stage); + const CSMWorld::Record& record = mEnchantments.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -27,7 +40,7 @@ void CSMTools::EnchantmentCheckStage::perform (int stage, CSMDoc::Messages& mess const ESM::Enchantment& enchantment = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Enchantment, enchantment.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Enchantment, enchantment.mId); if (enchantment.mData.mType < 0 || enchantment.mData.mType > 3) messages.add(id, "Invalid type", "", CSMDoc::Message::Severity_Error); @@ -61,9 +74,11 @@ void CSMTools::EnchantmentCheckStage::perform (int stage, CSMDoc::Messages& mess } if (effect->mSkill < -1 || effect->mSkill > 26) - messages.add(id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error); + messages.add( + id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error); if (effect->mAttribute < -1 || effect->mAttribute > 7) - messages.add(id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error); + messages.add( + id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error); if (effect->mRange < 0 || effect->mRange > 2) messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error); if (effect->mArea < 0) @@ -71,11 +86,14 @@ void CSMTools::EnchantmentCheckStage::perform (int stage, CSMDoc::Messages& mess if (effect->mDuration < 0) messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error); if (effect->mMagnMin < 0) - messages.add(id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error); + messages.add( + id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error); if (effect->mMagnMax < 0) - messages.add(id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error); + messages.add( + id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error); if (effect->mMagnMin > effect->mMagnMax) - messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "", + CSMDoc::Message::Severity_Error); ++effect; } } diff --git a/apps/opencs/model/tools/enchantmentcheck.hpp b/apps/opencs/model/tools/enchantmentcheck.hpp index e9c8b9eec..3fab99c2a 100644 --- a/apps/opencs/model/tools/enchantmentcheck.hpp +++ b/apps/opencs/model/tools/enchantmentcheck.hpp @@ -1,30 +1,36 @@ #ifndef CSM_TOOLS_ENCHANTMENTCHECK_H #define CSM_TOOLS_ENCHANTMENTCHECK_H -#include - #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + struct Enchantment; +} + namespace CSMTools { /// \brief Make sure that enchantment records are correct class EnchantmentCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mEnchantments; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mEnchantments; + bool mIgnoreBaseRecords; - public: + public: + EnchantmentCheckStage(const CSMWorld::IdCollection& enchantments); - EnchantmentCheckStage (const CSMWorld::IdCollection& enchantments); - - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + int setup() override; + ///< \return number of steps + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 8a198e953..69b790e65 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -1,15 +1,23 @@ #include "factioncheck.hpp" #include +#include +#include -#include +#include +#include +#include +#include +#include +#include + +#include +#include #include "../prefs/state.hpp" -#include "../world/universalid.hpp" - -CSMTools::FactionCheckStage::FactionCheckStage (const CSMWorld::IdCollection& factions) -: mFactions (factions) +CSMTools::FactionCheckStage::FactionCheckStage(const CSMWorld::IdCollection& factions) + : mFactions(factions) { mIgnoreBaseRecords = false; } @@ -21,9 +29,9 @@ int CSMTools::FactionCheckStage::setup() return mFactions.getSize(); } -void CSMTools::FactionCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::FactionCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mFactions.getRecord (stage); + const CSMWorld::Record& record = mFactions.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -31,29 +39,43 @@ void CSMTools::FactionCheckStage::perform (int stage, CSMDoc::Messages& messages const ESM::Faction& faction = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Faction, faction.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Faction, faction.mId); // test for empty name if (faction.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); // test for invalid attributes - if (faction.mData.mAttribute[0]==faction.mData.mAttribute[1] && faction.mData.mAttribute[0]!=-1) + std::map attributeCount; + for (size_t i = 0; i < faction.mData.mAttribute.size(); ++i) { - messages.add(id, "Same attribute is listed twice", "", CSMDoc::Message::Severity_Error); + int attribute = faction.mData.mAttribute[i]; + if (attribute != -1) + { + auto it = attributeCount.find(attribute); + if (it == attributeCount.end()) + attributeCount.emplace(attribute, 1); + else + { + if (it->second == 1) + messages.add(id, "Same attribute is listed twice", {}, CSMDoc::Message::Severity_Error); + ++it->second; + } + } } // test for non-unique skill std::map skills; // ID, number of occurrences - for (int i=0; i<7; ++i) - if (faction.mData.mSkills[i]!=-1) - ++skills[faction.mData.mSkills[i]]; + for (int skill : faction.mData.mSkills) + if (skill != -1) + ++skills[skill]; - for (auto &skill : skills) - if (skill.second>1) + for (auto& skill : skills) + if (skill.second > 1) { - messages.add(id, "Skill " + ESM::Skill::indexToId (skill.first) + " is listed more than once", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Skill " + ESM::Skill::indexToRefId(skill.first).toString() + " is listed more than once", + "", CSMDoc::Message::Severity_Error); } /// \todo check data members that can't be edited in the table view diff --git a/apps/opencs/model/tools/factioncheck.hpp b/apps/opencs/model/tools/factioncheck.hpp index d281c1b41..4339b3a6b 100644 --- a/apps/opencs/model/tools/factioncheck.hpp +++ b/apps/opencs/model/tools/factioncheck.hpp @@ -1,29 +1,36 @@ #ifndef CSM_TOOLS_FACTIONCHECK_H #define CSM_TOOLS_FACTIONCHECK_H -#include - #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + struct Faction; +} + namespace CSMTools { /// \brief VerifyStage: make sure that faction records are internally consistent class FactionCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mFactions; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mFactions; + bool mIgnoreBaseRecords; - public: + public: + FactionCheckStage(const CSMWorld::IdCollection& factions); - FactionCheckStage (const CSMWorld::IdCollection& factions); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/gmstcheck.cpp b/apps/opencs/model/tools/gmstcheck.cpp index 7cd13e5c2..858bc2c3e 100644 --- a/apps/opencs/model/tools/gmstcheck.cpp +++ b/apps/opencs/model/tools/gmstcheck.cpp @@ -1,6 +1,16 @@ #include "gmstcheck.hpp" #include +#include + +#include +#include +#include +#include +#include +#include + +#include #include "../prefs/state.hpp" @@ -21,93 +31,93 @@ int CSMTools::GmstCheckStage::setup() void CSMTools::GmstCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mGameSettings.getRecord (stage); - + const CSMWorld::Record& record = mGameSettings.getRecord(stage); + // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; - + const ESM::GameSetting& gmst = record.get(); - - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Gmst, gmst.mId); - + + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Gmst, gmst.mId); + const std::string& gmstIdString = gmst.mId.getRefIdString(); // Test for empty string if (gmst.mValue.getType() == ESM::VT_String && gmst.mValue.getString().empty()) - messages.add(id, gmst.mId + " is an empty string", "", CSMDoc::Message::Severity_Warning); - + messages.add(id, gmstIdString + " is an empty string", "", CSMDoc::Message::Severity_Warning); + // Checking type and limits // optimization - compare it to lists based on naming convention (f-float,i-int,s-string) - if (gmst.mId[0] == 'f') + if (gmst.mId.startsWith("f")) { for (size_t i = 0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) { - if (gmst.mId == CSMWorld::DefaultGmsts::Floats[i]) + if (gmst.mId == ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::Floats[i])) { if (gmst.mValue.getType() != ESM::VT_Float) { std::ostringstream stream; stream << "Expected float type for " << gmst.mId << " but found " - << varTypeToString(gmst.mValue.getType()) << " type"; - + << varTypeToString(gmst.mValue.getType()) << " type"; + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); } - - if (gmst.mValue.getFloat() < CSMWorld::DefaultGmsts::FloatLimits[i*2]) - messages.add(id, gmst.mId + " is less than the suggested range", "", - CSMDoc::Message::Severity_Warning); - - if (gmst.mValue.getFloat() > CSMWorld::DefaultGmsts::FloatLimits[i*2+1]) - messages.add(id, gmst.mId + " is more than the suggested range", "", - CSMDoc::Message::Severity_Warning); - + + if (gmst.mValue.getFloat() < CSMWorld::DefaultGmsts::FloatLimits[i * 2]) + messages.add( + id, gmstIdString + " is less than the suggested range", "", CSMDoc::Message::Severity_Warning); + + if (gmst.mValue.getFloat() > CSMWorld::DefaultGmsts::FloatLimits[i * 2 + 1]) + messages.add( + id, gmstIdString + " is more than the suggested range", "", CSMDoc::Message::Severity_Warning); + break; // for loop } } } - else if (gmst.mId[0] == 'i') + else if (gmst.mId.startsWith("i")) { for (size_t i = 0; i < CSMWorld::DefaultGmsts::IntCount; ++i) - { - if (gmst.mId == CSMWorld::DefaultGmsts::Ints[i]) + { + if (gmst.mId == ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::Ints[i])) { if (gmst.mValue.getType() != ESM::VT_Int) { std::ostringstream stream; stream << "Expected int type for " << gmst.mId << " but found " - << varTypeToString(gmst.mValue.getType()) << " type"; - + << varTypeToString(gmst.mValue.getType()) << " type"; + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); } - - if (gmst.mValue.getInteger() < CSMWorld::DefaultGmsts::IntLimits[i*2]) - messages.add(id, gmst.mId + " is less than the suggested range", "", - CSMDoc::Message::Severity_Warning); - - if (gmst.mValue.getInteger() > CSMWorld::DefaultGmsts::IntLimits[i*2+1]) - messages.add(id, gmst.mId + " is more than the suggested range", "", - CSMDoc::Message::Severity_Warning); - + + if (gmst.mValue.getInteger() < CSMWorld::DefaultGmsts::IntLimits[i * 2]) + messages.add( + id, gmstIdString + " is less than the suggested range", "", CSMDoc::Message::Severity_Warning); + + if (gmst.mValue.getInteger() > CSMWorld::DefaultGmsts::IntLimits[i * 2 + 1]) + messages.add( + id, gmstIdString + " is more than the suggested range", "", CSMDoc::Message::Severity_Warning); + break; // for loop } } } - else if (gmst.mId[0] == 's') + else if (gmst.mId.startsWith("s")) { for (size_t i = 0; i < CSMWorld::DefaultGmsts::StringCount; ++i) { - if (gmst.mId == CSMWorld::DefaultGmsts::Strings[i]) + if (gmst.mId == ESM::RefId::stringRefId(CSMWorld::DefaultGmsts::Strings[i])) { ESM::VarType type = gmst.mValue.getType(); - + if (type != ESM::VT_String && type != ESM::VT_None) { std::ostringstream stream; - stream << "Expected string or none type for " << gmst.mId << " but found " - << varTypeToString(gmst.mValue.getType()) << " type"; - + stream << "Expected string or none type for " << gmstIdString << " but found " + << varTypeToString(gmst.mValue.getType()) << " type"; + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); } - + break; // for loop } } @@ -118,13 +128,21 @@ std::string CSMTools::GmstCheckStage::varTypeToString(ESM::VarType type) { switch (type) { - case ESM::VT_Unknown: return "unknown"; - case ESM::VT_None: return "none"; - case ESM::VT_Short: return "short"; - case ESM::VT_Int: return "int"; - case ESM::VT_Long: return "long"; - case ESM::VT_Float: return "float"; - case ESM::VT_String: return "string"; - default: return "unhandled"; + case ESM::VT_Unknown: + return "unknown"; + case ESM::VT_None: + return "none"; + case ESM::VT_Short: + return "short"; + case ESM::VT_Int: + return "int"; + case ESM::VT_Long: + return "long"; + case ESM::VT_Float: + return "float"; + case ESM::VT_String: + return "string"; + default: + return "unhandled"; } } diff --git a/apps/opencs/model/tools/gmstcheck.hpp b/apps/opencs/model/tools/gmstcheck.hpp index 2c12a8607..c4c258c8f 100644 --- a/apps/opencs/model/tools/gmstcheck.hpp +++ b/apps/opencs/model/tools/gmstcheck.hpp @@ -1,34 +1,43 @@ #ifndef CSM_TOOLS_GMSTCHECK_H #define CSM_TOOLS_GMSTCHECK_H -#include +#include + +#include #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + struct GameSetting; +} + namespace CSMTools { /// \brief VerifyStage: make sure that GMSTs are alright class GmstCheckStage : public CSMDoc::Stage { public: - GmstCheckStage(const CSMWorld::IdCollection& gameSettings); - + int setup() override; ///< \return number of steps void perform(int stage, CSMDoc::Messages& messages) override; ///< Messages resulting from this stage will be appended to \a messages - + private: - const CSMWorld::IdCollection& mGameSettings; bool mIgnoreBaseRecords; - + std::string varTypeToString(ESM::VarType); - }; } diff --git a/apps/opencs/model/tools/journalcheck.cpp b/apps/opencs/model/tools/journalcheck.cpp index ae83abfa0..1f34073a4 100644 --- a/apps/opencs/model/tools/journalcheck.cpp +++ b/apps/opencs/model/tools/journalcheck.cpp @@ -1,12 +1,30 @@ #include "journalcheck.hpp" +#include #include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #include "../prefs/state.hpp" -CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection &journals, - const CSMWorld::InfoCollection& journalInfos) - : mJournals(journals), mJournalInfos(journalInfos) +CSMTools::JournalCheckStage::JournalCheckStage( + const CSMWorld::IdCollection& journals, const CSMWorld::InfoCollection& journalInfos) + : mJournals(journals) + , mJournalInfos(journalInfos) { mIgnoreBaseRecords = false; } @@ -14,58 +32,57 @@ CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection &journalRecord = mJournals.getRecord(stage); + const CSMWorld::Record& journalRecord = mJournals.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records - if ((mIgnoreBaseRecords && journalRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || journalRecord.isDeleted()) + if ((mIgnoreBaseRecords && journalRecord.mState == CSMWorld::RecordBase::State_BaseOnly) + || journalRecord.isDeleted()) return; - const ESM::Dialogue &journal = journalRecord.get(); + const ESM::Dialogue& journal = journalRecord.get(); int statusNamedCount = 0; int totalInfoCount = 0; std::set questIndices; - CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId); - - for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it) + if (const auto infos = mInfosByTopic.find(journal.mId); infos != mInfosByTopic.end()) { - const CSMWorld::Record infoRecord = (*it); - - if (infoRecord.isDeleted()) - continue; - - const CSMWorld::Info& journalInfo = infoRecord.get(); - - totalInfoCount += 1; - - if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name) + for (const CSMWorld::Record* record : infos->second) { - statusNamedCount += 1; - } + if (record->isDeleted()) + continue; - // Skip "Base" records (setting!) - if (mIgnoreBaseRecords && infoRecord.mState == CSMWorld::RecordBase::State_BaseOnly) - continue; + const CSMWorld::Info& journalInfo = record->get(); - if (journalInfo.mResponse.empty()) - { - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); - messages.add(id, "Missing journal entry text", "", CSMDoc::Message::Severity_Warning); - } + totalInfoCount += 1; - std::pair::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); + if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name) + { + statusNamedCount += 1; + } - // Duplicate index - if (!result.second) - { - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); - messages.add(id, "Duplicated quest index " + std::to_string(journalInfo.mData.mJournalIndex), "", CSMDoc::Message::Severity_Error); + // Skip "Base" records (setting!) + if (mIgnoreBaseRecords && record->mState == CSMWorld::RecordBase::State_BaseOnly) + continue; + + if (journalInfo.mResponse.empty()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + messages.add(id, "Missing journal entry text", "", CSMDoc::Message::Severity_Warning); + } + + // Duplicate index + if (!questIndices.insert(journalInfo.mData.mJournalIndex).second) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + messages.add(id, "Duplicated quest index " + std::to_string(journalInfo.mData.mJournalIndex), "", + CSMDoc::Message::Severity_Error); + } } } diff --git a/apps/opencs/model/tools/journalcheck.hpp b/apps/opencs/model/tools/journalcheck.hpp index b63127b52..a3bee1492 100644 --- a/apps/opencs/model/tools/journalcheck.hpp +++ b/apps/opencs/model/tools/journalcheck.hpp @@ -1,22 +1,34 @@ #ifndef CSM_TOOLS_JOURNALCHECK_H #define CSM_TOOLS_JOURNALCHECK_H -#include - #include "../world/idcollection.hpp" #include "../world/infocollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class InfoCollection; +} + +namespace ESM +{ + struct Dialogue; +} + namespace CSMTools { /// \brief VerifyStage: make sure that journal infos are good class JournalCheckStage : public CSMDoc::Stage { public: - - JournalCheckStage(const CSMWorld::IdCollection& journals, - const CSMWorld::InfoCollection& journalInfos); + JournalCheckStage( + const CSMWorld::IdCollection& journals, const CSMWorld::InfoCollection& journalInfos); int setup() override; ///< \return number of steps @@ -25,11 +37,10 @@ namespace CSMTools ///< Messages resulting from this stage will be appended to \a messages private: - const CSMWorld::IdCollection& mJournals; const CSMWorld::InfoCollection& mJournalInfos; bool mIgnoreBaseRecords; - + CSMWorld::InfosRecordPtrByTopic mInfosByTopic; }; } diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index f55fb14ee..a9ad4023f 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -1,31 +1,43 @@ #include "magiceffectcheck.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include #include "../prefs/state.hpp" -std::string CSMTools::MagicEffectCheckStage::checkObject(const std::string &id, - const CSMWorld::UniversalId &type, - const std::string &column) const +namespace ESM +{ + struct Sound; +} + +std::string CSMTools::MagicEffectCheckStage::checkObject( + const ESM::RefId& id, const CSMWorld::UniversalId& type, const std::string& column) const { CSMWorld::RefIdData::LocalIndex index = mObjects.getDataSet().searchId(id); - if (index.first == -1) - return (column + " '" + id + "' does not exist"); - else if (index.second != type.getType()) - return (column + " '" + id + "' does not have " + type.getTypeName() + " type"); + if (index.first == -1) + return (column + " '" + id.getRefIdString() + "' does not exist"); + else if (index.second != type.getType()) + return (column + " '" + id.getRefIdString() + "' does not have " + type.getTypeName() + " type"); return std::string(); } -CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection &effects, - const CSMWorld::IdCollection &sounds, - const CSMWorld::RefIdCollection &objects, - const CSMWorld::Resources &icons, - const CSMWorld::Resources &textures) - : mMagicEffects(effects), - mSounds(sounds), - mObjects(objects), - mIcons(icons), - mTextures(textures) +CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection& effects, + const CSMWorld::IdCollection& sounds, const CSMWorld::RefIdCollection& objects, + const CSMWorld::Resources& icons, const CSMWorld::Resources& textures) + : mMagicEffects(effects) + , mSounds(sounds) + , mObjects(objects) + , mIcons(icons) + , mTextures(textures) { mIgnoreBaseRecords = false; } @@ -37,9 +49,9 @@ int CSMTools::MagicEffectCheckStage::setup() return mMagicEffects.getSize(); } -void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages) +void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record &record = mMagicEffects.getRecord(stage); + const CSMWorld::Record& record = mMagicEffects.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -78,7 +90,8 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messa { std::string ddsParticle = effect.mParticle; if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsParticle) && mTextures.searchId(ddsParticle) != -1)) - messages.add(id, "Particle texture '" + effect.mParticle + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Particle texture '" + effect.mParticle + "' does not exist", "", + CSMDoc::Message::Severity_Error); } } @@ -111,11 +124,15 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messa } if (!effect.mCastSound.empty() && mSounds.searchId(effect.mCastSound) == -1) - messages.add(id, "Casting sound '" + effect.mCastSound + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Casting sound '" + effect.mCastSound.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); if (!effect.mHitSound.empty() && mSounds.searchId(effect.mHitSound) == -1) - messages.add(id, "Hit sound '" + effect.mHitSound + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Hit sound '" + effect.mHitSound.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); if (!effect.mAreaSound.empty() && mSounds.searchId(effect.mAreaSound) == -1) - messages.add(id, "Area sound '" + effect.mAreaSound + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Area sound '" + effect.mAreaSound.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); if (!effect.mBoltSound.empty() && mSounds.searchId(effect.mBoltSound) == -1) - messages.add(id, "Bolt sound '" + effect.mBoltSound + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Bolt sound '" + effect.mBoltSound.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); } diff --git a/apps/opencs/model/tools/magiceffectcheck.hpp b/apps/opencs/model/tools/magiceffectcheck.hpp index 4b2c24cc7..3ebb2621f 100644 --- a/apps/opencs/model/tools/magiceffectcheck.hpp +++ b/apps/opencs/model/tools/magiceffectcheck.hpp @@ -1,41 +1,58 @@ #ifndef CSM_TOOLS_MAGICEFFECTCHECK_HPP #define CSM_TOOLS_MAGICEFFECTCHECK_HPP -#include -#include +#include + +#include + +#include #include "../world/idcollection.hpp" -#include "../world/refidcollection.hpp" -#include "../world/resources.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class RefIdCollection; + class Resources; +} + +namespace ESM +{ + struct MagicEffect; + struct Sound; +} + namespace CSMTools { /// \brief VerifyStage: make sure that magic effect records are internally consistent class MagicEffectCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection &mMagicEffects; - const CSMWorld::IdCollection &mSounds; - const CSMWorld::RefIdCollection &mObjects; - const CSMWorld::Resources &mIcons; - const CSMWorld::Resources &mTextures; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mMagicEffects; + const CSMWorld::IdCollection& mSounds; + const CSMWorld::RefIdCollection& mObjects; + const CSMWorld::Resources& mIcons; + const CSMWorld::Resources& mTextures; + bool mIgnoreBaseRecords; - private: - std::string checkObject(const std::string &id, const CSMWorld::UniversalId &type, const std::string &column) const; + private: + std::string checkObject( + const ESM::RefId& id, const CSMWorld::UniversalId& type, const std::string& column) const; - public: - MagicEffectCheckStage(const CSMWorld::IdCollection &effects, - const CSMWorld::IdCollection &sounds, - const CSMWorld::RefIdCollection &objects, - const CSMWorld::Resources &icons, - const CSMWorld::Resources &textures); + public: + MagicEffectCheckStage(const CSMWorld::IdCollection& effects, + const CSMWorld::IdCollection& sounds, const CSMWorld::RefIdCollection& objects, + const CSMWorld::Resources& icons, const CSMWorld::Resources& textures); - int setup() override; - ///< \return number of steps - void perform (int stage, CSMDoc::Messages &messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + int setup() override; + ///< \return number of steps + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp index d0d9cc0b9..b6515539e 100644 --- a/apps/opencs/model/tools/mandatoryid.cpp +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -1,22 +1,29 @@ #include "mandatoryid.hpp" +#include + #include "../world/collectionbase.hpp" #include "../world/record.hpp" -CSMTools::MandatoryIdStage::MandatoryIdStage (const CSMWorld::CollectionBase& idCollection, - const CSMWorld::UniversalId& collectionId, const std::vector& ids) -: mIdCollection (idCollection), mCollectionId (collectionId), mIds (ids) -{} +#include +#include + +CSMTools::MandatoryIdStage::MandatoryIdStage(const CSMWorld::CollectionBase& idCollection, + const CSMWorld::UniversalId& collectionId, const std::vector& ids) + : mIdCollection(idCollection) + , mCollectionId(collectionId) + , mIds(ids) +{ +} int CSMTools::MandatoryIdStage::setup() { return static_cast(mIds.size()); } -void CSMTools::MandatoryIdStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::MandatoryIdStage::perform(int stage, CSMDoc::Messages& messages) { - if (mIdCollection.searchId (mIds.at (stage))==-1 || - mIdCollection.getRecord (mIds.at (stage)).isDeleted()) - messages.add (mCollectionId, "Missing mandatory record: " + mIds.at (stage)); + if (mIdCollection.searchId(mIds.at(stage)) == -1 || mIdCollection.getRecord(mIds.at(stage)).isDeleted()) + messages.add(mCollectionId, "Missing mandatory record: " + mIds.at(stage).toDebugString()); } diff --git a/apps/opencs/model/tools/mandatoryid.hpp b/apps/opencs/model/tools/mandatoryid.hpp index 9d069a2da..24e295539 100644 --- a/apps/opencs/model/tools/mandatoryid.hpp +++ b/apps/opencs/model/tools/mandatoryid.hpp @@ -4,9 +4,14 @@ #include #include -#include "../world/universalid.hpp" - #include "../doc/stage.hpp" +#include "../world/universalid.hpp" +#include + +namespace CSMDoc +{ + class Messages; +} namespace CSMWorld { @@ -18,20 +23,19 @@ namespace CSMTools /// \brief Verify stage: make sure that records with specific IDs exist. class MandatoryIdStage : public CSMDoc::Stage { - const CSMWorld::CollectionBase& mIdCollection; - CSMWorld::UniversalId mCollectionId; - std::vector mIds; + const CSMWorld::CollectionBase& mIdCollection; + CSMWorld::UniversalId mCollectionId; + std::vector mIds; - public: + public: + MandatoryIdStage(const CSMWorld::CollectionBase& idCollection, const CSMWorld::UniversalId& collectionId, + const std::vector& ids); - MandatoryIdStage (const CSMWorld::CollectionBase& idCollection, const CSMWorld::UniversalId& collectionId, - const std::vector& ids); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp index b15b4b83f..2c7a8bbc4 100644 --- a/apps/opencs/model/tools/mergeoperation.cpp +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -1,52 +1,88 @@ - #include "mergeoperation.hpp" -#include "../doc/state.hpp" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "../doc/document.hpp" +#include "../doc/state.hpp" #include "mergestages.hpp" -CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding) -: CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) +CSMTools::MergeOperation::MergeOperation(CSMDoc::Document& document, ToUTF8::FromType encoding) + : CSMDoc::Operation(CSMDoc::State_Merging, true) + , mState(document) { - appendStage (new StartMergeStage (mState)); + appendStage(new StartMergeStage(mState)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGlobals)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGmsts)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSkills)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getClasses)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFactions)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRaces)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSounds)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getScripts)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRegions)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBirthsigns)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSpells)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopics)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournals)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getCells)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFilters)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getEnchantments)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBodyParts)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getDebugProfiles)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSoundGens)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getMagicEffects)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getStartScripts)); - appendStage (new MergeIdCollectionStage > (mState, &CSMWorld::Data::getPathgrids)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopicInfos)); - appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); - appendStage (new MergeRefIdsStage (mState)); - appendStage (new MergeReferencesStage (mState)); - appendStage (new MergeReferencesStage (mState)); - appendStage (new PopulateLandTexturesMergeStage (mState)); - appendStage (new MergeLandStage (mState)); - appendStage (new FixLandsAndLandTexturesMergeStage (mState)); - appendStage (new CleanupLandTexturesMergeStage (mState)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getGlobals)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getGmsts)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getSkills)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getClasses)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getFactions)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getRaces)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getSounds)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getScripts)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getRegions)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getBirthsigns)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getSpells)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getTopics)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getJournals)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getCells)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getFilters)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getEnchantments)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getBodyParts)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getDebugProfiles)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getSoundGens)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getMagicEffects)); + appendStage(new MergeIdCollectionStage(mState, &CSMWorld::Data::getStartScripts)); + appendStage(new MergeIdCollectionStage>( + mState, &CSMWorld::Data::getPathgrids)); + appendStage( + new MergeIdCollectionStage(mState, &CSMWorld::Data::getTopicInfos)); + appendStage( + new MergeIdCollectionStage(mState, &CSMWorld::Data::getJournalInfos)); + appendStage(new MergeRefIdsStage(mState)); + appendStage(new MergeReferencesStage(mState)); + appendStage(new MergeReferencesStage(mState)); + appendStage(new PopulateLandTexturesMergeStage(mState)); + appendStage(new MergeLandStage(mState)); + appendStage(new FixLandsAndLandTexturesMergeStage(mState)); + appendStage(new CleanupLandTexturesMergeStage(mState)); - appendStage (new FinishMergedDocumentStage (mState, encoding)); + appendStage(new FinishMergedDocumentStage(mState, encoding)); } -void CSMTools::MergeOperation::setTarget (std::unique_ptr document) +void CSMTools::MergeOperation::setTarget(std::unique_ptr document) { mState.mTarget = std::move(document); } @@ -56,5 +92,5 @@ void CSMTools::MergeOperation::operationDone() CSMDoc::Operation::operationDone(); if (mState.mCompleted) - emit mergeDone (mState.mTarget.release()); + emit mergeDone(mState.mTarget.release()); } diff --git a/apps/opencs/model/tools/mergeoperation.hpp b/apps/opencs/model/tools/mergeoperation.hpp index 427967190..2cce2bec0 100644 --- a/apps/opencs/model/tools/mergeoperation.hpp +++ b/apps/opencs/model/tools/mergeoperation.hpp @@ -9,6 +9,8 @@ #include "mergestate.hpp" +class QObject; + namespace CSMDoc { class Document; @@ -18,27 +20,25 @@ namespace CSMTools { class MergeOperation : public CSMDoc::Operation { - Q_OBJECT + Q_OBJECT - MergeState mState; + MergeState mState; - public: + public: + MergeOperation(CSMDoc::Document& document, ToUTF8::FromType encoding); - MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding); + /// \attention Do not call this function while a merge is running. + void setTarget(std::unique_ptr document); - /// \attention Do not call this function while a merge is running. - void setTarget (std::unique_ptr document); + protected slots: - protected slots: + void operationDone() override; - void operationDone() override; - - signals: - - /// \attention When this signal is emitted, *this hands over the ownership of the - /// document. This signal must be handled to avoid a leak. - void mergeDone (CSMDoc::Document *document); + signals: + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone(CSMDoc::Document* document); }; } diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 016e2da39..5a7fa6c1b 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -1,9 +1,20 @@ - #include "mergestages.hpp" -#include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #include "mergestate.hpp" @@ -12,75 +23,82 @@ #include "../world/data.hpp" #include "../world/idtable.hpp" +namespace CSMDoc +{ + class Messages; +} -CSMTools::StartMergeStage::StartMergeStage (MergeState& state) -: mState (state) -{} +CSMTools::StartMergeStage::StartMergeStage(MergeState& state) + : mState(state) +{ +} int CSMTools::StartMergeStage::setup() { return 1; } -void CSMTools::StartMergeStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::StartMergeStage::perform(int stage, CSMDoc::Messages& messages) { mState.mCompleted = false; mState.mTextureIndices.clear(); } - -CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding) -: mState (state), mEncoder (encoding) -{} +CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage(MergeState& state, ToUTF8::FromType encoding) + : mState(state) + , mEncoder(encoding) +{ +} int CSMTools::FinishMergedDocumentStage::setup() { return 1; } -void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::FinishMergedDocumentStage::perform(int stage, CSMDoc::Messages& messages) { // We know that the content file list contains at least two entries and that the first one // does exist on disc (otherwise it would have been impossible to initiate a merge on that // document). - boost::filesystem::path path = mState.mSource.getContentFiles()[0]; + std::filesystem::path path = mState.mSource.getContentFiles()[0]; ESM::ESMReader reader; - reader.setEncoder (&mEncoder); - reader.open (path.string()); + reader.setEncoder(&mEncoder); + reader.open(path); CSMWorld::MetaData source; - source.mId = "sys::meta"; - source.load (reader); + source.mId = ESM::RefId::stringRefId("sys::meta"); + source.load(reader); CSMWorld::MetaData target = mState.mTarget->getData().getMetaData(); target.mAuthor = source.mAuthor; target.mDescription = source.mDescription; - mState.mTarget->getData().setMetaData (target); + mState.mTarget->getData().setMetaData(target); mState.mCompleted = true; } - -CSMTools::MergeRefIdsStage::MergeRefIdsStage (MergeState& state) : mState (state) {} +CSMTools::MergeRefIdsStage::MergeRefIdsStage(MergeState& state) + : mState(state) +{ +} int CSMTools::MergeRefIdsStage::setup() { return mState.mSource.getData().getReferenceables().getSize(); } -void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::MergeRefIdsStage::perform(int stage, CSMDoc::Messages& messages) { - mState.mSource.getData().getReferenceables().copyTo ( - stage, mState.mTarget->getData().getReferenceables()); + mState.mSource.getData().getReferenceables().copyTo(stage, mState.mTarget->getData().getReferenceables()); } - -CSMTools::MergeReferencesStage::MergeReferencesStage (MergeState& state) -: mState (state) -{} +CSMTools::MergeReferencesStage::MergeReferencesStage(MergeState& state) + : mState(state) +{ +} int CSMTools::MergeReferencesStage::setup() { @@ -88,10 +106,9 @@ int CSMTools::MergeReferencesStage::setup() return mState.mSource.getData().getReferences().getSize(); } -void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::MergeReferencesStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = - mState.mSource.getData().getReferences().getRecord (stage); + const CSMWorld::Record& record = mState.mSource.getData().getReferences().getRecord(stage); if (!record.isDeleted()) { @@ -99,20 +116,17 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa ref.mOriginalCell = ref.mCell; - ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; + ref.mRefNum.mIndex = mIndex[ref.mCell]++; ref.mRefNum.mContentFile = 0; ref.mNew = false; - CSMWorld::Record newRecord ( - CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &ref); - - mState.mTarget->getData().getReferences().appendRecord (newRecord); + mState.mTarget->getData().getReferences().appendRecord(std::make_unique>( + CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &ref))); } } - -CSMTools::PopulateLandTexturesMergeStage::PopulateLandTexturesMergeStage (MergeState& state) - : mState (state) +CSMTools::PopulateLandTexturesMergeStage::PopulateLandTexturesMergeStage(MergeState& state) + : mState(state) { } @@ -121,20 +135,20 @@ int CSMTools::PopulateLandTexturesMergeStage::setup() return mState.mSource.getData().getLandTextures().getSize(); } -void CSMTools::PopulateLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::PopulateLandTexturesMergeStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = - mState.mSource.getData().getLandTextures().getRecord (stage); + const CSMWorld::Record& record = mState.mSource.getData().getLandTextures().getRecord(stage); if (!record.isDeleted()) { - mState.mTarget->getData().getLandTextures().appendRecord(record); + mState.mTarget->getData().getLandTextures().appendRecord( + std::make_unique>(CSMWorld::Record( + CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get()))); } } - -CSMTools::MergeLandStage::MergeLandStage (MergeState& state) - : mState (state) +CSMTools::MergeLandStage::MergeLandStage(MergeState& state) + : mState(state) { } @@ -143,20 +157,19 @@ int CSMTools::MergeLandStage::setup() return mState.mSource.getData().getLand().getSize(); } -void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::MergeLandStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = - mState.mSource.getData().getLand().getRecord (stage); + const CSMWorld::Record& record = mState.mSource.getData().getLand().getRecord(stage); if (!record.isDeleted()) { - mState.mTarget->getData().getLand().appendRecord (record); + mState.mTarget->getData().getLand().appendRecord(std::make_unique>( + CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get()))); } } - -CSMTools::FixLandsAndLandTexturesMergeStage::FixLandsAndLandTexturesMergeStage (MergeState& state) - : mState (state) +CSMTools::FixLandsAndLandTexturesMergeStage::FixLandsAndLandTexturesMergeStage(MergeState& state) + : mState(state) { } @@ -166,7 +179,7 @@ int CSMTools::FixLandsAndLandTexturesMergeStage::setup() return mState.mSource.getData().getLand().getSize(); } -void CSMTools::FixLandsAndLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::FixLandsAndLandTexturesMergeStage::perform(int stage, CSMDoc::Messages& messages) { if (stage < mState.mTarget->getData().getLand().getSize()) { @@ -176,24 +189,22 @@ void CSMTools::FixLandsAndLandTexturesMergeStage::perform (int stage, CSMDoc::Me CSMWorld::IdTable& ltexTable = dynamic_cast( *mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); - std::string id = mState.mTarget->getData().getLand().getId(stage); + const std::string& id = mState.mTarget->getData().getLand().getId(stage).getRefIdString(); CSMWorld::TouchLandCommand cmd(landTable, ltexTable, id); cmd.redo(); // Get rid of base data - const CSMWorld::Record& oldRecord = - mState.mTarget->getData().getLand().getRecord (stage); + const CSMWorld::Record& oldRecord = mState.mTarget->getData().getLand().getRecord(stage); - CSMWorld::Record newRecord(CSMWorld::RecordBase::State_ModifiedOnly, - nullptr, &oldRecord.get()); - - mState.mTarget->getData().getLand().setRecord(stage, newRecord); + mState.mTarget->getData().getLand().setRecord(stage, + std::make_unique>( + CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &oldRecord.get()))); } } -CSMTools::CleanupLandTexturesMergeStage::CleanupLandTexturesMergeStage (MergeState& state) - : mState (state) +CSMTools::CleanupLandTexturesMergeStage::CleanupLandTexturesMergeStage(MergeState& state) + : mState(state) { } @@ -202,10 +213,10 @@ int CSMTools::CleanupLandTexturesMergeStage::setup() return 1; } -void CSMTools::CleanupLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::CleanupLandTexturesMergeStage::perform(int stage, CSMDoc::Messages& messages) { auto& landTextures = mState.mTarget->getData().getLandTextures(); - for (int i = 0; i < landTextures.getSize(); ) + for (int i = 0; i < landTextures.getSize();) { if (!landTextures.getRecord(i).isModified()) landTextures.removeRows(i, 1); diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index a6b6de58f..42f06858b 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -1,8 +1,13 @@ #ifndef CSM_TOOLS_MERGESTAGES_H #define CSM_TOOLS_MERGESTAGES_H -#include #include +#include +#include + +#include +#include +#include #include @@ -12,173 +17,173 @@ #include "mergestate.hpp" +namespace CSMDoc +{ + class Messages; +} + namespace CSMTools { class StartMergeStage : public CSMDoc::Stage { - MergeState& mState; + MergeState& mState; - public: + public: + StartMergeStage(MergeState& state); - StartMergeStage (MergeState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; class FinishMergedDocumentStage : public CSMDoc::Stage { - MergeState& mState; - ToUTF8::Utf8Encoder mEncoder; + MergeState& mState; + ToUTF8::Utf8Encoder mEncoder; - public: + public: + FinishMergedDocumentStage(MergeState& state, ToUTF8::FromType encoding); - FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - template > + template > class MergeIdCollectionStage : public CSMDoc::Stage { - MergeState& mState; - Collection& (CSMWorld::Data::*mAccessor)(); + MergeState& mState; + Collection& (CSMWorld::Data::*mAccessor)(); - public: + public: + MergeIdCollectionStage(MergeState& state, Collection& (CSMWorld::Data::*accessor)()); - MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; - template - MergeIdCollectionStage::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()) - : mState (state), mAccessor (accessor) - {} + template + MergeIdCollectionStage::MergeIdCollectionStage( + MergeState& state, Collection& (CSMWorld::Data::*accessor)()) + : mState(state) + , mAccessor(accessor) + { + } - template + template int MergeIdCollectionStage::setup() { return (mState.mSource.getData().*mAccessor)().getSize(); } - template - void MergeIdCollectionStage::perform (int stage, CSMDoc::Messages& messages) + template + void MergeIdCollectionStage::perform(int stage, CSMDoc::Messages& messages) { const Collection& source = (mState.mSource.getData().*mAccessor)(); Collection& target = (mState.mTarget->getData().*mAccessor)(); - const CSMWorld::Record& record = source.getRecord (stage); + const CSMWorld::Record& record = source.getRecord(stage); if (!record.isDeleted()) - target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get())); + target.appendRecord(std::make_unique>( + CSMWorld::Record(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get()))); } class MergeRefIdsStage : public CSMDoc::Stage { - MergeState& mState; + MergeState& mState; - public: + public: + MergeRefIdsStage(MergeState& state); - MergeRefIdsStage (MergeState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; class MergeReferencesStage : public CSMDoc::Stage { - MergeState& mState; - std::map mIndex; + MergeState& mState; + std::map mIndex; - public: + public: + MergeReferencesStage(MergeState& state); - MergeReferencesStage (MergeState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; /// Adds all land texture records that could potentially be referenced when merging class PopulateLandTexturesMergeStage : public CSMDoc::Stage { - MergeState& mState; + MergeState& mState; - public: + public: + PopulateLandTexturesMergeStage(MergeState& state); - PopulateLandTexturesMergeStage (MergeState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; class MergeLandStage : public CSMDoc::Stage { - MergeState& mState; + MergeState& mState; - public: + public: + MergeLandStage(MergeState& state); - MergeLandStage (MergeState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; /// During this stage, the complex process of combining LandTextures from /// potentially multiple plugins is undertaken. class FixLandsAndLandTexturesMergeStage : public CSMDoc::Stage { - MergeState& mState; + MergeState& mState; - public: + public: + FixLandsAndLandTexturesMergeStage(MergeState& state); - FixLandsAndLandTexturesMergeStage (MergeState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; /// Removes base LandTexture records. This gets rid of the base records previously /// needed in FixLandsAndLandTexturesMergeStage. class CleanupLandTexturesMergeStage : public CSMDoc::Stage { - MergeState& mState; + MergeState& mState; - public: + public: + CleanupLandTexturesMergeStage(MergeState& state); - CleanupLandTexturesMergeStage (MergeState& state); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/mergestate.hpp b/apps/opencs/model/tools/mergestate.hpp index 96e6752e2..9174854be 100644 --- a/apps/opencs/model/tools/mergestate.hpp +++ b/apps/opencs/model/tools/mergestate.hpp @@ -1,10 +1,10 @@ #ifndef CSM_TOOLS_MERGESTATE_H #define CSM_TOOLS_MERGESTATE_H -#include +#include -#include #include +#include #include "../doc/document.hpp" @@ -17,7 +17,11 @@ namespace CSMTools bool mCompleted; std::map, int> mTextureIndices; // (texture, content file) -> new texture - MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {} + MergeState(CSMDoc::Document& source) + : mSource(source) + , mCompleted(false) + { + } }; } diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index febb79c64..6420c1c83 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -1,17 +1,25 @@ #include "pathgridcheck.hpp" -#include #include +#include +#include + +#include +#include +#include +#include + +#include #include "../prefs/state.hpp" -#include "../world/universalid.hpp" #include "../world/idcollection.hpp" -#include "../world/subcellcollection.hpp" #include "../world/pathgrid.hpp" +#include "../world/subcellcollection.hpp" +#include "../world/universalid.hpp" -CSMTools::PathgridCheckStage::PathgridCheckStage (const CSMWorld::SubCellCollection& pathgrids) -: mPathgrids (pathgrids) +CSMTools::PathgridCheckStage::PathgridCheckStage(const CSMWorld::SubCellCollection& pathgrids) + : mPathgrids(pathgrids) { mIgnoreBaseRecords = false; } @@ -23,9 +31,9 @@ int CSMTools::PathgridCheckStage::setup() return mPathgrids.getSize(); } -void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::PathgridCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mPathgrids.getRecord (stage); + const CSMWorld::Record& record = mPathgrids.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -33,56 +41,57 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message const CSMWorld::Pathgrid& pathgrid = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId); // check the number of pathgrid points if (pathgrid.mData.mS2 < static_cast(pathgrid.mPoints.size())) - messages.add (id, "Less points than expected", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Less points than expected", "", CSMDoc::Message::Severity_Error); else if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) - messages.add (id, "More points than expected", "", CSMDoc::Message::Severity_Error); + messages.add(id, "More points than expected", "", CSMDoc::Message::Severity_Error); std::vector pointList(pathgrid.mPoints.size()); - std::vector duplList; + std::vector duplList; - for (unsigned int i = 0; i < pathgrid.mEdges.size(); ++i) + for (const auto& edge : pathgrid.mEdges) { - if (pathgrid.mEdges[i].mV0 < static_cast(pathgrid.mPoints.size()) && pathgrid.mEdges[i].mV0 >= 0) + if (edge.mV0 < pathgrid.mPoints.size()) { - pointList[pathgrid.mEdges[i].mV0].mConnectionNum++; + auto& point = pointList[edge.mV0]; + point.mConnectionNum++; // first check for duplicate edges - unsigned int j = 0; - for (; j < pointList[pathgrid.mEdges[i].mV0].mOtherIndex.size(); ++j) + size_t j = 0; + for (; j < point.mOtherIndex.size(); ++j) { - if (pointList[pathgrid.mEdges[i].mV0].mOtherIndex[j] == pathgrid.mEdges[i].mV1) + if (point.mOtherIndex[j] == edge.mV1) { std::ostringstream ss; - ss << "Duplicate edge between points #" << pathgrid.mEdges[i].mV0 << " and #" << pathgrid.mEdges[i].mV1; - messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); + ss << "Duplicate edge between points #" << edge.mV0 << " and #" << edge.mV1; + messages.add(id, ss.str(), {}, CSMDoc::Message::Severity_Error); break; } } // only add if not a duplicate - if (j == pointList[pathgrid.mEdges[i].mV0].mOtherIndex.size()) - pointList[pathgrid.mEdges[i].mV0].mOtherIndex.push_back(pathgrid.mEdges[i].mV1); + if (j == point.mOtherIndex.size()) + point.mOtherIndex.push_back(edge.mV1); } else { std::ostringstream ss; - ss << "An edge is connected to a non-existent point #" << pathgrid.mEdges[i].mV0; - messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); + ss << "An edge is connected to a non-existent point #" << edge.mV0; + messages.add(id, ss.str(), {}, CSMDoc::Message::Severity_Error); } } - for (unsigned int i = 0; i < pathgrid.mPoints.size(); ++i) + for (size_t i = 0; i < pathgrid.mPoints.size(); ++i) { // check that edges are bidirectional bool foundReverse = false; - for (unsigned int j = 0; j < pointList[i].mOtherIndex.size(); ++j) + for (const auto& otherIndex : pointList[i].mOtherIndex) { - for (unsigned int k = 0; k < pointList[pointList[i].mOtherIndex[j]].mOtherIndex.size(); ++k) + for (const auto& other : pointList[otherIndex].mOtherIndex) { - if (pointList[pointList[i].mOtherIndex[j]].mOtherIndex[k] == static_cast(i)) + if (other == i) { foundReverse = true; break; @@ -92,26 +101,25 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message if (!foundReverse) { std::ostringstream ss; - ss << "Missing edge between points #" << i << " and #" << pointList[i].mOtherIndex[j]; - messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); + ss << "Missing edge between points #" << i << " and #" << otherIndex; + messages.add(id, ss.str(), {}, CSMDoc::Message::Severity_Error); } } // check duplicate points // FIXME: how to do this efficiently? - for (unsigned int j = 0; j != i; ++j) + for (size_t j = 0; j != i; ++j) { - if (pathgrid.mPoints[i].mX == pathgrid.mPoints[j].mX && - pathgrid.mPoints[i].mY == pathgrid.mPoints[j].mY && - pathgrid.mPoints[i].mZ == pathgrid.mPoints[j].mZ) + if (pathgrid.mPoints[i].mX == pathgrid.mPoints[j].mX && pathgrid.mPoints[i].mY == pathgrid.mPoints[j].mY + && pathgrid.mPoints[i].mZ == pathgrid.mPoints[j].mZ) { - std::vector::const_iterator it = find(duplList.begin(), duplList.end(), static_cast(i)); + auto it = std::find(duplList.begin(), duplList.end(), i); if (it == duplList.end()) { std::ostringstream ss; - ss << "Point #" << i << " duplicates point #" << j - << " (" << pathgrid.mPoints[i].mX << ", " << pathgrid.mPoints[i].mY << ", " << pathgrid.mPoints[i].mZ << ")"; - messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Warning); + ss << "Point #" << i << " duplicates point #" << j << " (" << pathgrid.mPoints[i].mX << ", " + << pathgrid.mPoints[i].mY << ", " << pathgrid.mPoints[i].mZ << ")"; + messages.add(id, ss.str(), {}, CSMDoc::Message::Severity_Warning); duplList.push_back(i); break; @@ -121,16 +129,14 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message } // check pathgrid points that are not connected to anything - for (unsigned int i = 0; i < pointList.size(); ++i) + for (size_t i = 0; i < pointList.size(); ++i) { if (pointList[i].mConnectionNum == 0) { std::ostringstream ss; - ss << "Point #" << i << " (" - << pathgrid.mPoints[i].mX << ", " - << pathgrid.mPoints[i].mY << ", " - << pathgrid.mPoints[i].mZ << ") is disconnected from other points"; - messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Warning); + ss << "Point #" << i << " (" << pathgrid.mPoints[i].mX << ", " << pathgrid.mPoints[i].mY << ", " + << pathgrid.mPoints[i].mZ << ") is disconnected from other points"; + messages.add(id, ss.str(), {}, CSMDoc::Message::Severity_Warning); } } diff --git a/apps/opencs/model/tools/pathgridcheck.hpp b/apps/opencs/model/tools/pathgridcheck.hpp index 212637fd4..29c705f20 100644 --- a/apps/opencs/model/tools/pathgridcheck.hpp +++ b/apps/opencs/model/tools/pathgridcheck.hpp @@ -1,14 +1,20 @@ #ifndef CSM_TOOLS_PATHGRIDCHECK_H #define CSM_TOOLS_PATHGRIDCHECK_H -#include "../world/collection.hpp" +#include +#include #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + namespace CSMWorld { struct Pathgrid; - template + template class SubCellCollection; } @@ -17,24 +23,25 @@ namespace CSMTools struct Point { unsigned char mConnectionNum; - std::vector mOtherIndex; - Point() : mConnectionNum(0), mOtherIndex(0) {} + std::vector mOtherIndex; + Point() + : mConnectionNum(0) + , mOtherIndex(0) + { + } }; class PathgridCheckStage : public CSMDoc::Stage { - const CSMWorld::SubCellCollection >& mPathgrids; + const CSMWorld::SubCellCollection& mPathgrids; bool mIgnoreBaseRecords; public: - - PathgridCheckStage (const CSMWorld::SubCellCollection >& pathgrids); + explicit PathgridCheckStage(const CSMWorld::SubCellCollection& pathgrids); int setup() override; - void perform (int stage, CSMDoc::Messages& messages) override; + void perform(int stage, CSMDoc::Messages& messages) override; }; } diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 6585a31cc..78f72f44c 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -1,12 +1,21 @@ #include "racecheck.hpp" +#include + #include "../prefs/state.hpp" #include "../world/universalid.hpp" -void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& messages) +#include +#include +#include +#include +#include +#include + +void CSMTools::RaceCheckStage::performPerRecord(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mRaces.getRecord (stage); + const CSMWorld::Record& record = mRaces.getRecord(stage); if (record.isDeleted()) return; @@ -21,42 +30,44 @@ void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& me if (mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) return; - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Race, race.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Race, race.mId); // test for empty name and description if (race.mName.empty()) - messages.add(id, "Name is missing", "", (race.mData.mFlags & 0x1) ? CSMDoc::Message::Severity_Error : CSMDoc::Message::Severity_Warning); + messages.add(id, "Name is missing", "", + (race.mData.mFlags & 0x1) ? CSMDoc::Message::Severity_Error : CSMDoc::Message::Severity_Warning); if (race.mDescription.empty()) messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); // test for positive height - if (race.mData.mHeight.mMale<=0) + if (race.mData.mHeight.mMale <= 0) messages.add(id, "Male height is non-positive", "", CSMDoc::Message::Severity_Error); - if (race.mData.mHeight.mFemale<=0) + if (race.mData.mHeight.mFemale <= 0) messages.add(id, "Female height is non-positive", "", CSMDoc::Message::Severity_Error); // test for non-negative weight - if (race.mData.mWeight.mMale<0) + if (race.mData.mWeight.mMale < 0) messages.add(id, "Male weight is negative", "", CSMDoc::Message::Severity_Error); - if (race.mData.mWeight.mFemale<0) + if (race.mData.mWeight.mFemale < 0) messages.add(id, "Female weight is negative", "", CSMDoc::Message::Severity_Error); /// \todo check data members that can't be edited in the table view } -void CSMTools::RaceCheckStage::performFinal (CSMDoc::Messages& messages) +void CSMTools::RaceCheckStage::performFinal(CSMDoc::Messages& messages) { - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Races); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Races); if (!mPlayable) messages.add(id, "No playable race", "", CSMDoc::Message::Severity_SeriousError); } -CSMTools::RaceCheckStage::RaceCheckStage (const CSMWorld::IdCollection& races) -: mRaces (races), mPlayable (false) +CSMTools::RaceCheckStage::RaceCheckStage(const CSMWorld::IdCollection& races) + : mRaces(races) + , mPlayable(false) { mIgnoreBaseRecords = false; } @@ -66,13 +77,13 @@ int CSMTools::RaceCheckStage::setup() mPlayable = false; mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); - return mRaces.getSize()+1; + return mRaces.getSize() + 1; } -void CSMTools::RaceCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::RaceCheckStage::perform(int stage, CSMDoc::Messages& messages) { - if (stage==mRaces.getSize()) - performFinal (messages); + if (stage == mRaces.getSize()) + performFinal(messages); else - performPerRecord (stage, messages); + performPerRecord(stage, messages); } diff --git a/apps/opencs/model/tools/racecheck.hpp b/apps/opencs/model/tools/racecheck.hpp index 7c70f13b0..600e0d7fc 100644 --- a/apps/opencs/model/tools/racecheck.hpp +++ b/apps/opencs/model/tools/racecheck.hpp @@ -1,34 +1,41 @@ #ifndef CSM_TOOLS_RACECHECK_H #define CSM_TOOLS_RACECHECK_H -#include - #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + struct Race; +} + namespace CSMTools { /// \brief VerifyStage: make sure that race records are internally consistent class RaceCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mRaces; - bool mPlayable; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mRaces; + bool mPlayable; + bool mIgnoreBaseRecords; - void performPerRecord (int stage, CSMDoc::Messages& messages); + void performPerRecord(int stage, CSMDoc::Messages& messages); - void performFinal (CSMDoc::Messages& messages); + void performFinal(CSMDoc::Messages& messages); - public: + public: + RaceCheckStage(const CSMWorld::IdCollection& races); - RaceCheckStage (const CSMWorld::IdCollection& races); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 981e8c195..6effa2cbf 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -1,37 +1,73 @@ #include "referenceablecheck.hpp" -#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include "../prefs/state.hpp" #include "../world/record.hpp" #include "../world/universalid.hpp" -CSMTools::ReferenceableCheckStage::ReferenceableCheckStage( - const CSMWorld::RefIdData& referenceable, const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& faction, - const CSMWorld::IdCollection& scripts, - const CSMWorld::Resources& models, - const CSMWorld::Resources& icons, +namespace ESM +{ + class Script; + struct BodyPart; + struct Class; + struct Faction; + struct Race; +} + +CSMTools::ReferenceableCheckStage::ReferenceableCheckStage(const CSMWorld::RefIdData& referenceable, + const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& faction, const CSMWorld::IdCollection& scripts, + const CSMWorld::Resources& models, const CSMWorld::Resources& icons, const CSMWorld::IdCollection& bodyparts) - :mReferencables(referenceable), - mRaces(races), - mClasses(classes), - mFactions(faction), - mScripts(scripts), - mModels(models), - mIcons(icons), - mBodyParts(bodyparts), - mPlayerPresent(false) + : mReferencables(referenceable) + , mRaces(races) + , mClasses(classes) + , mFactions(faction) + , mScripts(scripts) + , mModels(models) + , mIcons(icons) + , mBodyParts(bodyparts) + , mPlayerPresent(false) { mIgnoreBaseRecords = false; } -void CSMTools::ReferenceableCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::ReferenceableCheckStage::perform(int stage, CSMDoc::Messages& messages) { - //Checks for books, than, when stage is above mBooksSize goes to other checks, with (stage - PrevSum) as stage. + // Checks for books, than, when stage is above mBooksSize goes to other checks, with (stage - PrevSum) as stage. const int bookSize(mReferencables.getBooks().getSize()); if (stage < bookSize) @@ -229,7 +265,7 @@ void CSMTools::ReferenceableCheckStage::perform (int stage, CSMDoc::Messages& me creatureCheck(stage, mReferencables.getCreatures(), messages); return; } -// if we come that far, we are about to perform our last, final check. + // if we come that far, we are about to perform our last, final check. finalCheck(messages); return; } @@ -243,9 +279,7 @@ int CSMTools::ReferenceableCheckStage::setup() } void CSMTools::ReferenceableCheckStage::bookCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Book >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -253,7 +287,7 @@ void CSMTools::ReferenceableCheckStage::bookCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Book& book = (dynamic_cast& >(baseRecord)).get(); + const ESM::Book& book = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Book, book.mId); inventoryItemCheck(book, messages, id.toString(), true); @@ -263,9 +297,7 @@ void CSMTools::ReferenceableCheckStage::bookCheck( } void CSMTools::ReferenceableCheckStage::activatorCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Activator >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -273,7 +305,7 @@ void CSMTools::ReferenceableCheckStage::activatorCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Activator& activator = (dynamic_cast& >(baseRecord)).get(); + const ESM::Activator& activator = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId); if (activator.mModel.empty()) @@ -286,9 +318,7 @@ void CSMTools::ReferenceableCheckStage::activatorCheck( } void CSMTools::ReferenceableCheckStage::potionCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Potion >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -296,7 +326,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Potion& potion = (dynamic_cast& >(baseRecord)).get(); + const ESM::Potion& potion = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId); inventoryItemCheck(potion, messages, id.toString()); @@ -306,11 +336,8 @@ void CSMTools::ReferenceableCheckStage::potionCheck( scriptCheck(potion, messages, id.toString()); } - void CSMTools::ReferenceableCheckStage::apparatusCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Apparatus >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -318,7 +345,7 @@ void CSMTools::ReferenceableCheckStage::apparatusCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Apparatus& apparatus = (dynamic_cast& >(baseRecord)).get(); + const ESM::Apparatus& apparatus = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Apparatus, apparatus.mId); inventoryItemCheck(apparatus, messages, id.toString()); @@ -330,9 +357,7 @@ void CSMTools::ReferenceableCheckStage::apparatusCheck( } void CSMTools::ReferenceableCheckStage::armorCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Armor >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -340,7 +365,7 @@ void CSMTools::ReferenceableCheckStage::armorCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Armor& armor = (dynamic_cast& >(baseRecord)).get(); + const ESM::Armor& armor = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Armor, armor.mId); inventoryItemCheck(armor, messages, id.toString(), true); @@ -358,9 +383,7 @@ void CSMTools::ReferenceableCheckStage::armorCheck( } void CSMTools::ReferenceableCheckStage::clothingCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Clothing >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -368,7 +391,7 @@ void CSMTools::ReferenceableCheckStage::clothingCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Clothing& clothing = (dynamic_cast& >(baseRecord)).get(); + const ESM::Clothing& clothing = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Clothing, clothing.mId); inventoryItemCheck(clothing, messages, id.toString(), true); @@ -377,9 +400,7 @@ void CSMTools::ReferenceableCheckStage::clothingCheck( } void CSMTools::ReferenceableCheckStage::containerCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Container >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -387,33 +408,32 @@ void CSMTools::ReferenceableCheckStage::containerCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Container& container = (dynamic_cast& >(baseRecord)).get(); + const ESM::Container& container = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId); - //checking for name + // checking for name if (container.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); - //Checking for model + // Checking for model if (container.mModel.empty()) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(container.mModel) == -1) messages.add(id, "Model '" + container.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); - //Checking for capacity (weight) - if (container.mWeight < 0) //0 is allowed + // Checking for capacity (weight) + if (container.mWeight < 0) // 0 is allowed messages.add(id, "Capacity is negative", "", CSMDoc::Message::Severity_Error); - - //checking contained items + + // checking contained items inventoryListCheck(container.mInventory.mList, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(container, messages, id.toString()); } -void CSMTools::ReferenceableCheckStage::creatureCheck ( - int stage, const CSMWorld::RefIdDataContainer< ESM::Creature >& records, - CSMDoc::Messages& messages) +void CSMTools::ReferenceableCheckStage::creatureCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -432,7 +452,7 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( else if (mModels.searchId(creature.mModel) == -1) messages.add(id, "Model '" + creature.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); - //stats checks + // stats checks if (creature.mData.mLevel <= 0) messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); @@ -480,9 +500,13 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( for (int i = 0; i < 6; ++i) { if (creature.mData.mAttack[i] < 0) - messages.add(id, "Attack " + std::to_string(i/2 + 1) + " has negative" + (i % 2 == 0 ? " minimum " : " maximum ") + "damage", "", CSMDoc::Message::Severity_Error); - if (i % 2 == 0 && creature.mData.mAttack[i] > creature.mData.mAttack[i+1]) - messages.add(id, "Attack " + std::to_string(i/2 + 1) + " has minimum damage higher than maximum damage", "", CSMDoc::Message::Severity_Error); + messages.add(id, + "Attack " + std::to_string(i / 2 + 1) + " has negative" + (i % 2 == 0 ? " minimum " : " maximum ") + + "damage", + "", CSMDoc::Message::Severity_Error); + if (i % 2 == 0 && creature.mData.mAttack[i] > creature.mData.mAttack[i + 1]) + messages.add(id, "Attack " + std::to_string(i / 2 + 1) + " has minimum damage higher than maximum damage", + "", CSMDoc::Message::Severity_Error); } if (creature.mData.mGold < 0) @@ -495,22 +519,23 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( { CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(creature.mOriginal); if (index.first == -1) - messages.add(id, "Parent creature '" + creature.mOriginal + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Parent creature '" + creature.mOriginal.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); else if (index.second != CSMWorld::UniversalId::Type_Creature) - messages.add(id, "'" + creature.mOriginal + "' is not a creature", "", CSMDoc::Message::Severity_Error); + messages.add(id, "'" + creature.mOriginal.getRefIdString() + "' is not a creature", "", + CSMDoc::Message::Severity_Error); } // Check inventory inventoryListCheck(creature.mInventory.mList, messages, id.toString()); - + // Check that mentioned scripts exist scriptCheck(creature, messages, id.toString()); /// \todo Check spells, teleport table, AI data and AI packages for validity } void CSMTools::ReferenceableCheckStage::doorCheck( - int stage, const CSMWorld::RefIdDataContainer< ESM::Door >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -521,7 +546,7 @@ void CSMTools::ReferenceableCheckStage::doorCheck( const ESM::Door& door = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Door, door.mId); - //usual, name or model + // usual, name or model if (door.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); @@ -535,9 +560,7 @@ void CSMTools::ReferenceableCheckStage::doorCheck( } void CSMTools::ReferenceableCheckStage::ingredientCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Ingredient >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -545,7 +568,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Ingredient& ingredient = (dynamic_cast& >(baseRecord)).get(); + const ESM::Ingredient& ingredient = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Ingredient, ingredient.mId); inventoryItemCheck(ingredient, messages, id.toString()); @@ -555,9 +578,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck( } void CSMTools::ReferenceableCheckStage::creaturesLevListCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -565,16 +586,16 @@ void CSMTools::ReferenceableCheckStage::creaturesLevListCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::CreatureLevList& CreatureLevList = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_CreatureLevelledList, CreatureLevList.mId); //CreatureLevList but Type_CreatureLevelledList :/ + const ESM::CreatureLevList& CreatureLevList + = (dynamic_cast&>(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_CreatureLevelledList, + CreatureLevList.mId); // CreatureLevList but Type_CreatureLevelledList :/ listCheck(CreatureLevList, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::itemLevelledListCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -582,15 +603,14 @@ void CSMTools::ReferenceableCheckStage::itemLevelledListCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::ItemLevList& ItemLevList = (dynamic_cast& >(baseRecord)).get(); + const ESM::ItemLevList& ItemLevList = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_ItemLevelledList, ItemLevList.mId); listCheck(ItemLevList, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::lightCheck( - int stage, const CSMWorld::RefIdDataContainer< ESM::Light >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -598,7 +618,7 @@ void CSMTools::ReferenceableCheckStage::lightCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Light& light = (dynamic_cast& >(baseRecord)).get(); + const ESM::Light& light = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Light, light.mId); if (light.mData.mRadius < 0) @@ -612,9 +632,7 @@ void CSMTools::ReferenceableCheckStage::lightCheck( } void CSMTools::ReferenceableCheckStage::lockpickCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Lockpick >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -622,7 +640,7 @@ void CSMTools::ReferenceableCheckStage::lockpickCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Lockpick& lockpick = (dynamic_cast& >(baseRecord)).get(); + const ESM::Lockpick& lockpick = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Lockpick, lockpick.mId); inventoryItemCheck(lockpick, messages, id.toString()); @@ -634,9 +652,7 @@ void CSMTools::ReferenceableCheckStage::lockpickCheck( } void CSMTools::ReferenceableCheckStage::miscCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -644,7 +660,8 @@ void CSMTools::ReferenceableCheckStage::miscCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Miscellaneous& miscellaneous = (dynamic_cast& >(baseRecord)).get(); + const ESM::Miscellaneous& miscellaneous + = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Miscellaneous, miscellaneous.mId); inventoryItemCheck(miscellaneous, messages, id.toString()); @@ -653,20 +670,19 @@ void CSMTools::ReferenceableCheckStage::miscCheck( scriptCheck(miscellaneous, messages, id.toString()); } -void CSMTools::ReferenceableCheckStage::npcCheck ( - int stage, const CSMWorld::RefIdDataContainer< ESM::NPC >& records, - CSMDoc::Messages& messages) +void CSMTools::ReferenceableCheckStage::npcCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) return; - const ESM::NPC& npc = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId); + const ESM::NPC& npc = (dynamic_cast&>(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Npc, npc.mId); - //Detect if player is present - if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl? + // Detect if player is present + if (npc.mId == "Player") // Happy now, scrawl? mPlayerPresent = true; // Skip "Base" records (setting!) @@ -676,15 +692,16 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( short level(npc.mNpdt.mLevel); int gold(npc.mNpdt.mGold); - if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) // 12 = autocalculated { - if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag + if ((npc.mFlags & ESM::NPC::Autocalc) == 0) // 0x0010 = autocalculated flag { - messages.add(id, "NPC with autocalculated stats doesn't have autocalc flag turned on", "", CSMDoc::Message::Severity_Error); //should not happen? + messages.add(id, "NPC with autocalculated stats doesn't have autocalc flag turned on", "", + CSMDoc::Message::Severity_Error); // should not happen? return; } } - else + else if (npc.mNpdt.mHealth != 0) { if (npc.mNpdt.mStrength == 0) messages.add(id, "Strength is equal to zero", "", CSMDoc::Message::Severity_Warning); @@ -722,23 +739,27 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( if (npc.mClass.empty()) messages.add(id, "Class is missing", "", CSMDoc::Message::Severity_Error); - else if (mClasses.searchId (npc.mClass) == -1) - messages.add(id, "Class '" + npc.mClass + "' does not exist", "", CSMDoc::Message::Severity_Error); + else if (mClasses.searchId(npc.mClass) == -1) + messages.add( + id, "Class '" + npc.mClass.getRefIdString() + "' does not exist", "", CSMDoc::Message::Severity_Error); if (npc.mRace.empty()) messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error); - else if (mRaces.searchId (npc.mRace) == -1) - messages.add(id, "Race '" + npc.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error); + else if (mRaces.searchId(npc.mRace) == -1) + messages.add( + id, "Race '" + npc.mRace.getRefIdString() + "' does not exist", "", CSMDoc::Message::Severity_Error); if (!npc.mFaction.empty() && mFactions.searchId(npc.mFaction) == -1) - messages.add(id, "Faction '" + npc.mFaction + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add( + id, "Faction '" + npc.mFaction.getRefIdString() + "' does not exist", "", CSMDoc::Message::Severity_Error); if (npc.mHead.empty()) messages.add(id, "Head is missing", "", CSMDoc::Message::Severity_Error); else { if (mBodyParts.searchId(npc.mHead) == -1) - messages.add(id, "Head body part '" + npc.mHead + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Head body part '" + npc.mHead.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); /// \todo Check gender, race and other body parts stuff validity for the specific NPC } @@ -747,66 +768,50 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( else { if (mBodyParts.searchId(npc.mHair) == -1) - messages.add(id, "Hair body part '" + npc.mHair + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Hair body part '" + npc.mHair.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); /// \todo Check gender, race and other body part stuff validity for the specific NPC } // Check inventory inventoryListCheck(npc.mInventory.mList, messages, id.toString()); - + // Check that mentioned scripts exist scriptCheck(npc, messages, id.toString()); } void CSMTools::ReferenceableCheckStage::weaponCheck( - int stage, const CSMWorld::RefIdDataContainer< ESM::Weapon >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { - const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Weapon& weapon = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Weapon, weapon.mId); + const ESM::Weapon& weapon = (dynamic_cast&>(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Weapon, weapon.mId); - //TODO, It seems that this stuff for spellcasting is obligatory and In fact We should check if records are present - if - ( //THOSE ARE HARDCODED! - !(weapon.mId == "VFX_Hands" || - weapon.mId == "VFX_Absorb" || - weapon.mId == "VFX_Reflect" || - weapon.mId == "VFX_DefaultBolt" || - //TODO I don't know how to get full list of effects :/ - //DANGER!, ACHTUNG! FIXME! The following is the list of the magical bolts, valid for Morrowind.esm. However those are not hardcoded. - weapon.mId == "magic_bolt" || - weapon.mId == "shock_bolt" || - weapon.mId == "shield_bolt" || - weapon.mId == "VFX_DestructBolt" || - weapon.mId == "VFX_PoisonBolt" || - weapon.mId == "VFX_RestoreBolt" || - weapon.mId == "VFX_AlterationBolt" || - weapon.mId == "VFX_ConjureBolt" || - weapon.mId == "VFX_FrostBolt" || - weapon.mId == "VFX_MysticismBolt" || - weapon.mId == "VFX_IllusionBolt" || - weapon.mId == "VFX_Multiple2" || - weapon.mId == "VFX_Multiple3" || - weapon.mId == "VFX_Multiple4" || - weapon.mId == "VFX_Multiple5" || - weapon.mId == "VFX_Multiple6" || - weapon.mId == "VFX_Multiple7" || - weapon.mId == "VFX_Multiple8" || - weapon.mId == "VFX_Multiple9")) + // TODO, It seems that this stuff for spellcasting is obligatory and In fact We should check if records are present + if ( // THOSE ARE HARDCODED! + !(weapon.mId == "VFX_Hands" || weapon.mId == "VFX_Absorb" || weapon.mId == "VFX_Reflect" + || weapon.mId == "VFX_DefaultBolt" || + // TODO I don't know how to get full list of effects :/ + // DANGER!, ACHTUNG! FIXME! The following is the list of the magical bolts, valid for Morrowind.esm. However + // those are not hardcoded. + weapon.mId == "magic_bolt" || weapon.mId == "shock_bolt" || weapon.mId == "shield_bolt" + || weapon.mId == "VFX_DestructBolt" || weapon.mId == "VFX_PoisonBolt" || weapon.mId == "VFX_RestoreBolt" + || weapon.mId == "VFX_AlterationBolt" || weapon.mId == "VFX_ConjureBolt" || weapon.mId == "VFX_FrostBolt" + || weapon.mId == "VFX_MysticismBolt" || weapon.mId == "VFX_IllusionBolt" || weapon.mId == "VFX_Multiple2" + || weapon.mId == "VFX_Multiple3" || weapon.mId == "VFX_Multiple4" || weapon.mId == "VFX_Multiple5" + || weapon.mId == "VFX_Multiple6" || weapon.mId == "VFX_Multiple7" || weapon.mId == "VFX_Multiple8" + || weapon.mId == "VFX_Multiple9")) { inventoryItemCheck(weapon, messages, id.toString(), true); - if (!(weapon.mData.mType == ESM::Weapon::MarksmanBow || - weapon.mData.mType == ESM::Weapon::MarksmanCrossbow || - weapon.mData.mType == ESM::Weapon::MarksmanThrown || - weapon.mData.mType == ESM::Weapon::Arrow || - weapon.mData.mType == ESM::Weapon::Bolt)) + if (!(weapon.mData.mType == ESM::Weapon::MarksmanBow || weapon.mData.mType == ESM::Weapon::MarksmanCrossbow + || weapon.mData.mType == ESM::Weapon::MarksmanThrown || weapon.mData.mType == ESM::Weapon::Arrow + || weapon.mData.mType == ESM::Weapon::Bolt)) { if (weapon.mData.mSlash[0] > weapon.mData.mSlash[1]) messages.add(id, "Minimum slash damage higher than maximum", "", CSMDoc::Message::Severity_Warning); @@ -818,11 +823,10 @@ void CSMTools::ReferenceableCheckStage::weaponCheck( if (weapon.mData.mChop[0] > weapon.mData.mChop[1]) messages.add(id, "Minimum chop damage higher than maximum", "", CSMDoc::Message::Severity_Warning); - if (!(weapon.mData.mType == ESM::Weapon::Arrow || - weapon.mData.mType == ESM::Weapon::Bolt || - weapon.mData.mType == ESM::Weapon::MarksmanThrown)) + if (!(weapon.mData.mType == ESM::Weapon::Arrow || weapon.mData.mType == ESM::Weapon::Bolt + || weapon.mData.mType == ESM::Weapon::MarksmanThrown)) { - //checking of health + // checking of health if (weapon.mData.mHealth == 0) messages.add(id, "Durability is equal to zero", "", CSMDoc::Message::Severity_Warning); @@ -836,9 +840,7 @@ void CSMTools::ReferenceableCheckStage::weaponCheck( } void CSMTools::ReferenceableCheckStage::probeCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Probe >& records, - CSMDoc::Messages& messages) + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -846,7 +848,7 @@ void CSMTools::ReferenceableCheckStage::probeCheck( if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Probe& probe = (dynamic_cast& >(baseRecord)).get(); + const ESM::Probe& probe = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Probe, probe.mId); inventoryItemCheck(probe, messages, id.toString()); @@ -856,38 +858,36 @@ void CSMTools::ReferenceableCheckStage::probeCheck( scriptCheck(probe, messages, id.toString()); } -void CSMTools::ReferenceableCheckStage::repairCheck ( - int stage, const CSMWorld::RefIdDataContainer< ESM::Repair >& records, - CSMDoc::Messages& messages) +void CSMTools::ReferenceableCheckStage::repairCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { - const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Repair& repair = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Repair, repair.mId); + const ESM::Repair& repair = (dynamic_cast&>(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Repair, repair.mId); - inventoryItemCheck (repair, messages, id.toString()); - toolCheck (repair, messages, id.toString(), true); + inventoryItemCheck(repair, messages, id.toString()); + toolCheck(repair, messages, id.toString(), true); // Check that mentioned scripts exist scriptCheck(repair, messages, id.toString()); } -void CSMTools::ReferenceableCheckStage::staticCheck ( - int stage, const CSMWorld::RefIdDataContainer< ESM::Static >& records, - CSMDoc::Messages& messages) +void CSMTools::ReferenceableCheckStage::staticCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages) { - const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); + const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted()) return; - const ESM::Static& staticElement = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Static, staticElement.mId); + const ESM::Static& staticElement = (dynamic_cast&>(baseRecord)).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Static, staticElement.mId); if (staticElement.mModel.empty()) messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); @@ -895,23 +895,23 @@ void CSMTools::ReferenceableCheckStage::staticCheck ( messages.add(id, "Model '" + staticElement.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); } -//final check +// final check -void CSMTools::ReferenceableCheckStage::finalCheck (CSMDoc::Messages& messages) +void CSMTools::ReferenceableCheckStage::finalCheck(CSMDoc::Messages& messages) { if (!mPlayerPresent) - messages.add(CSMWorld::UniversalId::Type_Referenceables, "Player record is missing", "", CSMDoc::Message::Severity_SeriousError); + messages.add(CSMWorld::UniversalId::Type_Referenceables, "Player record is missing", "", + CSMDoc::Message::Severity_SeriousError); } void CSMTools::ReferenceableCheckStage::inventoryListCheck( - const std::vector& itemList, - CSMDoc::Messages& messages, - const std::string& id) + const std::vector& itemList, CSMDoc::Messages& messages, const std::string& id) { for (size_t i = 0; i < itemList.size(); ++i) { - std::string itemName = itemList[i].mItem; - CSMWorld::RefIdData::LocalIndex localIndex = mReferencables.searchId(itemName); + const ESM::RefId& item = itemList[i].mItem; + const auto& itemName = item.getRefIdString(); + CSMWorld::RefIdData::LocalIndex localIndex = mReferencables.searchId(item); if (localIndex.first == -1) messages.add(id, "Item '" + itemName + "' does not exist", "", CSMDoc::Message::Severity_Error); @@ -920,50 +920,51 @@ void CSMTools::ReferenceableCheckStage::inventoryListCheck( // Needs to accommodate containers, creatures, and NPCs switch (localIndex.second) { - case CSMWorld::UniversalId::Type_Potion: - case CSMWorld::UniversalId::Type_Apparatus: - case CSMWorld::UniversalId::Type_Armor: - case CSMWorld::UniversalId::Type_Book: - case CSMWorld::UniversalId::Type_Clothing: - case CSMWorld::UniversalId::Type_Ingredient: - case CSMWorld::UniversalId::Type_Light: - case CSMWorld::UniversalId::Type_Lockpick: - case CSMWorld::UniversalId::Type_Miscellaneous: - case CSMWorld::UniversalId::Type_Probe: - case CSMWorld::UniversalId::Type_Repair: - case CSMWorld::UniversalId::Type_Weapon: - case CSMWorld::UniversalId::Type_ItemLevelledList: - break; - default: - messages.add(id, "'" + itemName + "' is not an item", "", CSMDoc::Message::Severity_Error); + case CSMWorld::UniversalId::Type_Potion: + case CSMWorld::UniversalId::Type_Apparatus: + case CSMWorld::UniversalId::Type_Armor: + case CSMWorld::UniversalId::Type_Book: + case CSMWorld::UniversalId::Type_Clothing: + case CSMWorld::UniversalId::Type_Ingredient: + case CSMWorld::UniversalId::Type_Light: + case CSMWorld::UniversalId::Type_Lockpick: + case CSMWorld::UniversalId::Type_Miscellaneous: + case CSMWorld::UniversalId::Type_Probe: + case CSMWorld::UniversalId::Type_Repair: + case CSMWorld::UniversalId::Type_Weapon: + case CSMWorld::UniversalId::Type_ItemLevelledList: + break; + default: + messages.add(id, "'" + itemName + "' is not an item", "", CSMDoc::Message::Severity_Error); } } } } -//Templates begins here +// Templates begins here -template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( +template +void CSMTools::ReferenceableCheckStage::inventoryItemCheck( const Item& someItem, CSMDoc::Messages& messages, const std::string& someID, bool enchantable) { if (someItem.mName.empty()) messages.add(someID, "Name is missing", "", CSMDoc::Message::Severity_Error); - //Checking for weight + // Checking for weight if (someItem.mData.mWeight < 0) messages.add(someID, "Weight is negative", "", CSMDoc::Message::Severity_Error); - //Checking for value + // Checking for value if (someItem.mData.mValue < 0) messages.add(someID, "Value is negative", "", CSMDoc::Message::Severity_Error); - //checking for model + // checking for model if (someItem.mModel.empty()) messages.add(someID, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(someItem.mModel) == -1) messages.add(someID, "Model '" + someItem.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); - //checking for icon + // checking for icon if (someItem.mIcon.empty()) messages.add(someID, "Icon is missing", "", CSMDoc::Message::Severity_Error); else if (mIcons.searchId(someItem.mIcon) == -1) @@ -977,27 +978,28 @@ template void CSMTools::ReferenceableCheckStage::inventoryItemChe messages.add(someID, "Enchantment points number is negative", "", CSMDoc::Message::Severity_Error); } -template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( +template +void CSMTools::ReferenceableCheckStage::inventoryItemCheck( const Item& someItem, CSMDoc::Messages& messages, const std::string& someID) { if (someItem.mName.empty()) messages.add(someID, "Name is missing", "", CSMDoc::Message::Severity_Error); - //Checking for weight + // Checking for weight if (someItem.mData.mWeight < 0) messages.add(someID, "Weight is negative", "", CSMDoc::Message::Severity_Error); - //Checking for value + // Checking for value if (someItem.mData.mValue < 0) messages.add(someID, "Value is negative", "", CSMDoc::Message::Severity_Error); - //checking for model + // checking for model if (someItem.mModel.empty()) messages.add(someID, "Model is missing", "", CSMDoc::Message::Severity_Error); else if (mModels.searchId(someItem.mModel) == -1) messages.add(someID, "Model '" + someItem.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); - //checking for icon + // checking for icon if (someItem.mIcon.empty()) messages.add(someID, "Icon is missing", "", CSMDoc::Message::Severity_Error); else if (mIcons.searchId(someItem.mIcon) == -1) @@ -1008,47 +1010,55 @@ template void CSMTools::ReferenceableCheckStage::inventoryItemChe } } -template void CSMTools::ReferenceableCheckStage::toolCheck ( +template +void CSMTools::ReferenceableCheckStage::toolCheck( const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID, bool canBeBroken) { if (someTool.mData.mQuality <= 0) messages.add(someID, "Quality is non-positive", "", CSMDoc::Message::Severity_Error); - if (canBeBroken && someTool.mData.mUses<=0) + if (canBeBroken && someTool.mData.mUses <= 0) messages.add(someID, "Number of uses is non-positive", "", CSMDoc::Message::Severity_Error); } -template void CSMTools::ReferenceableCheckStage::toolCheck ( +template +void CSMTools::ReferenceableCheckStage::toolCheck( const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID) { if (someTool.mData.mQuality <= 0) messages.add(someID, "Quality is non-positive", "", CSMDoc::Message::Severity_Error); } -template void CSMTools::ReferenceableCheckStage::listCheck ( +template +void CSMTools::ReferenceableCheckStage::listCheck( const List& someList, CSMDoc::Messages& messages, const std::string& someID) { if (someList.mChanceNone > 100) { - messages.add(someID, "Chance that no object is used is over 100 percent", "", CSMDoc::Message::Severity_Warning); + messages.add( + someID, "Chance that no object is used is over 100 percent", "", CSMDoc::Message::Severity_Warning); } - for (unsigned i = 0; i < someList.mList.size(); ++i) + for (const auto& element : someList.mList) { - if (mReferencables.searchId(someList.mList[i].mId).first == -1) - messages.add(someID, "Object '" + someList.mList[i].mId + "' does not exist", "", CSMDoc::Message::Severity_Error); + if (mReferencables.searchId(element.mId).first == -1) + messages.add(someID, "Object '" + element.mId.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); - if (someList.mList[i].mLevel < 1) - messages.add(someID, "Level of item '" + someList.mList[i].mId + "' is non-positive", "", CSMDoc::Message::Severity_Error); + if (element.mLevel < 1) + messages.add(someID, "Level of item '" + element.mId.getRefIdString() + "' is non-positive", "", + CSMDoc::Message::Severity_Error); } } -template void CSMTools::ReferenceableCheckStage::scriptCheck ( +template +void CSMTools::ReferenceableCheckStage::scriptCheck( const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID) { if (!someTool.mScript.empty()) { if (mScripts.searchId(someTool.mScript) == -1) - messages.add(someID, "Script '" + someTool.mScript + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(someID, "Script '" + someTool.mScript.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); } } diff --git a/apps/opencs/model/tools/referenceablecheck.hpp b/apps/opencs/model/tools/referenceablecheck.hpp index 83c8f1232..1c578eced 100644 --- a/apps/opencs/model/tools/referenceablecheck.hpp +++ b/apps/opencs/model/tools/referenceablecheck.hpp @@ -1,95 +1,122 @@ #ifndef REFERENCEABLECHECKSTAGE_H #define REFERENCEABLECHECKSTAGE_H -#include "../world/universalid.hpp" +#include +#include + #include "../doc/stage.hpp" -#include "../world/data.hpp" + +#include "../world/idcollection.hpp" #include "../world/refiddata.hpp" -#include "../world/resources.hpp" + +#include +#include +#include +#include +#include + +namespace CSMWorld +{ + class Resources; +} + +namespace CSMDoc +{ + class Messages; +} namespace CSMTools { class ReferenceableCheckStage : public CSMDoc::Stage { - public: + public: + ReferenceableCheckStage(const CSMWorld::RefIdData& referenceable, + const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, const CSMWorld::IdCollection& scripts, + const CSMWorld::Resources& models, const CSMWorld::Resources& icons, + const CSMWorld::IdCollection& bodyparts); - ReferenceableCheckStage (const CSMWorld::RefIdData& referenceable, - const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& factions, - const CSMWorld::IdCollection& scripts, - const CSMWorld::Resources& models, - const CSMWorld::Resources& icons, - const CSMWorld::IdCollection& bodyparts); + void perform(int stage, CSMDoc::Messages& messages) override; + int setup() override; - void perform(int stage, CSMDoc::Messages& messages) override; - int setup() override; + private: + // CONCRETE CHECKS + void bookCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void activatorCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void potionCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void apparatusCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void armorCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void clothingCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void containerCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void creatureCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void doorCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void ingredientCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void creaturesLevListCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void itemLevelledListCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void lightCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void lockpickCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void miscCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void npcCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void weaponCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void probeCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void repairCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void staticCheck( + int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - private: - //CONCRETE CHECKS - void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, CSMDoc::Messages& messages); - void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, CSMDoc::Messages& messages); - void potionCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void armorCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void clothingCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void containerCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void creatureCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void doorCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void lightCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void miscCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void npcCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void weaponCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void probeCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void repairCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); - void staticCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + // FINAL CHECK + void finalCheck(CSMDoc::Messages& messages); - //FINAL CHECK - void finalCheck (CSMDoc::Messages& messages); + // Convenience functions + void inventoryListCheck( + const std::vector& itemList, CSMDoc::Messages& messages, const std::string& id); - //Convenience functions - void inventoryListCheck(const std::vector& itemList, CSMDoc::Messages& messages, const std::string& id); + /// for all enchantable items. + template + inline void inventoryItemCheck( + const Item& someItem, CSMDoc::Messages& messages, const std::string& someID, bool enchantable); - template void inventoryItemCheck(const ITEM& someItem, - CSMDoc::Messages& messages, - const std::string& someID, - bool enchantable); //for all enchantable items. + /// for non-enchantable items. + template + inline void inventoryItemCheck(const Item& someItem, CSMDoc::Messages& messages, const std::string& someID); - template void inventoryItemCheck(const ITEM& someItem, - CSMDoc::Messages& messages, - const std::string& someID); //for non-enchantable items. + /// for tools with uses. + template + inline void toolCheck( + const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID, bool canbebroken); - template void toolCheck(const TOOL& someTool, - CSMDoc::Messages& messages, - const std::string& someID, - bool canbebroken); //for tools with uses. + /// for tools without uses. + template + inline void toolCheck(const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID); - template void toolCheck(const TOOL& someTool, - CSMDoc::Messages& messages, - const std::string& someID); //for tools without uses. + template + inline void listCheck(const List& someList, CSMDoc::Messages& messages, const std::string& someID); - template void listCheck(const LIST& someList, - CSMDoc::Messages& messages, - const std::string& someID); + template + inline void scriptCheck(const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID); - template void scriptCheck(const TOOL& someTool, - CSMDoc::Messages& messages, - const std::string& someID); - - const CSMWorld::RefIdData& mReferencables; - const CSMWorld::IdCollection& mRaces; - const CSMWorld::IdCollection& mClasses; - const CSMWorld::IdCollection& mFactions; - const CSMWorld::IdCollection& mScripts; - const CSMWorld::Resources& mModels; - const CSMWorld::Resources& mIcons; - const CSMWorld::IdCollection& mBodyParts; - bool mPlayerPresent; - bool mIgnoreBaseRecords; + const CSMWorld::RefIdData& mReferencables; + const CSMWorld::IdCollection& mRaces; + const CSMWorld::IdCollection& mClasses; + const CSMWorld::IdCollection& mFactions; + const CSMWorld::IdCollection& mScripts; + const CSMWorld::Resources& mModels; + const CSMWorld::Resources& mIcons; + const CSMWorld::IdCollection& mBodyParts; + bool mPlayerPresent; + bool mIgnoreBaseRecords; }; } #endif // REFERENCEABLECHECKSTAGE_H diff --git a/apps/opencs/model/tools/referencecheck.cpp b/apps/opencs/model/tools/referencecheck.cpp index d8ff9f20e..87d133a59 100644 --- a/apps/opencs/model/tools/referencecheck.cpp +++ b/apps/opencs/model/tools/referencecheck.cpp @@ -1,23 +1,38 @@ #include "referencecheck.hpp" +#include + #include "../prefs/state.hpp" -CSMTools::ReferenceCheckStage::ReferenceCheckStage( - const CSMWorld::RefCollection& references, - const CSMWorld::RefIdCollection& referencables, - const CSMWorld::IdCollection& cells, +#include "../../model/world/cell.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +CSMTools::ReferenceCheckStage::ReferenceCheckStage(const CSMWorld::RefCollection& references, + const CSMWorld::RefIdCollection& referencables, const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& factions) - : - mReferences(references), - mObjects(referencables), - mDataSet(referencables.getDataSet()), - mCells(cells), - mFactions(factions) + : mReferences(references) + , mObjects(referencables) + , mDataSet(referencables.getDataSet()) + , mCells(cells) + , mFactions(factions) { mIgnoreBaseRecords = false; } -void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &messages) +void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mReferences.getRecord(stage); @@ -31,12 +46,13 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &message // Check reference id if (cellRef.mRefID.empty()) messages.add(id, "Instance is not based on an object", "", CSMDoc::Message::Severity_Error); - else + else { // Check for non existing referenced object if (mObjects.searchId(cellRef.mRefID) == -1) - messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID + "'", "", CSMDoc::Message::Severity_Error); - else + messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID.getRefIdString() + "'", "", + CSMDoc::Message::Severity_Error); + else { // Check if reference charge is valid for it's proper referenced type CSMWorld::RefIdData::LocalIndex localIndex = mDataSet.searchId(cellRef.mRefID); @@ -48,12 +64,14 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &message // If object have owner, check if that owner reference is valid if (!cellRef.mOwner.empty() && mObjects.searchId(cellRef.mOwner) == -1) - messages.add(id, "Owner object '" + cellRef.mOwner + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Owner object '" + cellRef.mOwner.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); // If object have creature soul trapped, check if that creature reference is valid if (!cellRef.mSoul.empty()) if (mObjects.searchId(cellRef.mSoul) == -1) - messages.add(id, "Trapped soul object '" + cellRef.mSoul + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Trapped soul object '" + cellRef.mSoul.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); if (cellRef.mFaction.empty()) { @@ -63,13 +81,15 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &message else { if (mFactions.searchId(cellRef.mFaction) == -1) - messages.add(id, "Faction '" + cellRef.mFaction + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Faction '" + cellRef.mFaction.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); else if (cellRef.mFactionRank < -1) messages.add(id, "Invalid faction rank", "", CSMDoc::Message::Severity_Error); } - if (!cellRef.mDestCell.empty() && mCells.searchId(cellRef.mDestCell) == -1) - messages.add(id, "Destination cell '" + cellRef.mDestCell + "' does not exist", "", CSMDoc::Message::Severity_Error); + if (!cellRef.mDestCell.empty() && mCells.searchId(ESM::RefId::stringRefId(cellRef.mDestCell)) == -1) + messages.add( + id, "Destination cell '" + cellRef.mDestCell + "' does not exist", "", CSMDoc::Message::Severity_Error); if (cellRef.mScale < 0) messages.add(id, "Negative scale", "", CSMDoc::Message::Severity_Error); diff --git a/apps/opencs/model/tools/referencecheck.hpp b/apps/opencs/model/tools/referencecheck.hpp index 2da139869..04cca2b80 100644 --- a/apps/opencs/model/tools/referencecheck.hpp +++ b/apps/opencs/model/tools/referencecheck.hpp @@ -1,29 +1,46 @@ #ifndef CSM_TOOLS_REFERENCECHECK_H #define CSM_TOOLS_REFERENCECHECK_H -#include "../doc/state.hpp" -#include "../doc/document.hpp" +#include "../world/idcollection.hpp" +#include "../world/refcollection.hpp" + +#include "../doc/stage.hpp" + +namespace ESM +{ + struct Faction; +} + +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class RefIdCollection; + class RefIdData; + struct Cell; +} namespace CSMTools { class ReferenceCheckStage : public CSMDoc::Stage { - public: - ReferenceCheckStage (const CSMWorld::RefCollection& references, - const CSMWorld::RefIdCollection& referencables, - const CSMWorld::IdCollection& cells, - const CSMWorld::IdCollection& factions); + public: + ReferenceCheckStage(const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables, + const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& factions); - void perform(int stage, CSMDoc::Messages& messages) override; - int setup() override; + void perform(int stage, CSMDoc::Messages& messages) override; + int setup() override; - private: - const CSMWorld::RefCollection& mReferences; - const CSMWorld::RefIdCollection& mObjects; - const CSMWorld::RefIdData& mDataSet; - const CSMWorld::IdCollection& mCells; - const CSMWorld::IdCollection& mFactions; - bool mIgnoreBaseRecords; + private: + const CSMWorld::RefCollection& mReferences; + const CSMWorld::RefIdCollection& mObjects; + const CSMWorld::RefIdData& mDataSet; + const CSMWorld::IdCollection& mCells; + const CSMWorld::IdCollection& mFactions; + bool mIgnoreBaseRecords; }; } diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 27a73be93..4affc1bd4 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -1,11 +1,22 @@ #include "regioncheck.hpp" +#include +#include + #include "../prefs/state.hpp" #include "../world/universalid.hpp" -CSMTools::RegionCheckStage::RegionCheckStage (const CSMWorld::IdCollection& regions) -: mRegions (regions) +#include +#include +#include +#include +#include + +#include + +CSMTools::RegionCheckStage::RegionCheckStage(const CSMWorld::IdCollection& regions) + : mRegions(regions) { mIgnoreBaseRecords = false; } @@ -17,9 +28,9 @@ int CSMTools::RegionCheckStage::setup() return mRegions.getSize(); } -void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::RegionCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mRegions.getRecord (stage); + const CSMWorld::Record& record = mRegions.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -27,7 +38,7 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) const ESM::Region& region = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Region, region.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Region, region.mId); // test for empty name if (region.mName.empty()) @@ -36,16 +47,17 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) /// \todo test that the ID in mSleeplist exists // test that chances add up to 100 - int chances = region.mData.mClear + region.mData.mCloudy + region.mData.mFoggy + region.mData.mOvercast + - region.mData.mRain + region.mData.mThunder + region.mData.mAsh + region.mData.mBlight + - region.mData.mA + region.mData.mB; + int chances = region.mData.mClear + region.mData.mCloudy + region.mData.mFoggy + region.mData.mOvercast + + region.mData.mRain + region.mData.mThunder + region.mData.mAsh + region.mData.mBlight + region.mData.mSnow + + region.mData.mBlizzard; if (chances != 100) messages.add(id, "Weather chances do not add up to 100", "", CSMDoc::Message::Severity_Error); for (const ESM::Region::SoundRef& sound : region.mSoundList) { if (sound.mChance > 100) - messages.add(id, "Chance of '" + sound.mSound + "' sound to play is over 100 percent", "", CSMDoc::Message::Severity_Warning); + messages.add(id, "Chance of '" + sound.mSound.getRefIdString() + "' sound to play is over 100 percent", "", + CSMDoc::Message::Severity_Warning); } /// \todo check data members that can't be edited in the table view diff --git a/apps/opencs/model/tools/regioncheck.hpp b/apps/opencs/model/tools/regioncheck.hpp index e7ddb0bca..9ac6e4e92 100644 --- a/apps/opencs/model/tools/regioncheck.hpp +++ b/apps/opencs/model/tools/regioncheck.hpp @@ -1,29 +1,36 @@ #ifndef CSM_TOOLS_REGIONCHECK_H #define CSM_TOOLS_REGIONCHECK_H -#include - #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + struct Region; +} + namespace CSMTools { /// \brief VerifyStage: make sure that region records are internally consistent class RegionCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mRegions; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mRegions; + bool mIgnoreBaseRecords; - public: + public: + RegionCheckStage(const CSMWorld::IdCollection& regions); - RegionCheckStage (const CSMWorld::IdCollection& regions); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index a2901a663..84a8c71f9 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -1,12 +1,18 @@ #include "reportmodel.hpp" -#include +#include +#include #include +#include + +#include +#include #include "../world/columns.hpp" -CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn) -: mColumnField (-1), mColumnSeverity (-1) +CSMTools::ReportModel::ReportModel(bool fieldColumn, bool severityColumn) + : mColumnField(-1) + , mColumnSeverity(-1) { int index = 3; @@ -19,7 +25,7 @@ CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn) mColumnDescription = index; } -int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const +int CSMTools::ReportModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; @@ -27,110 +33,118 @@ int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const return static_cast(mRows.size()); } -int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const +int CSMTools::ReportModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; - return mColumnDescription+1; + return mColumnDescription + 1; } -QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const +QVariant CSMTools::ReportModel::data(const QModelIndex& index, int role) const { - if (role!=Qt::DisplayRole && role!=Qt::UserRole) + if (role != Qt::DisplayRole && role != Qt::UserRole) return QVariant(); switch (index.column()) { case Column_Type: - if(role == Qt::UserRole) - return QString::fromUtf8 ( - mRows.at (index.row()).mId.getTypeName().c_str()); + if (role == Qt::UserRole) + return QString::fromUtf8(mRows.at(index.row()).mId.getTypeName().c_str()); else - return static_cast (mRows.at (index.row()).mId.getType()); + return static_cast(mRows.at(index.row()).mId.getType()); case Column_Id: { - CSMWorld::UniversalId id = mRows.at (index.row()).mId; + CSMWorld::UniversalId id = mRows.at(index.row()).mId; - if (id.getArgumentType()==CSMWorld::UniversalId::ArgumentType_Id) - return QString::fromUtf8 (id.getId().c_str()); + switch (id.getArgumentType()) + { + case CSMWorld::UniversalId::ArgumentType_None: + return QString("-"); + case CSMWorld::UniversalId::ArgumentType_Index: + return QString::number(id.getIndex()); + case CSMWorld::UniversalId::ArgumentType_Id: + return QString::fromStdString(id.getId()); + case CSMWorld::UniversalId::ArgumentType_RefId: + return QString::fromStdString(id.getRefId().toString()); + } - return QString ("-"); + return QString("unsupported"); } case Column_Hint: - return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str()); + return QString::fromUtf8(mRows.at(index.row()).mHint.c_str()); } - if (index.column()==mColumnDescription) - return QString::fromUtf8 (mRows.at (index.row()).mMessage.c_str()); + if (index.column() == mColumnDescription) + return QString::fromUtf8(mRows.at(index.row()).mMessage.c_str()); - if (index.column()==mColumnField) + if (index.column() == mColumnField) { std::string field; - std::istringstream stream (mRows.at (index.row()).mHint); + std::istringstream stream(mRows.at(index.row()).mHint); char type, ignore; int fieldIndex; - if ((stream >> type >> ignore >> fieldIndex) && (type=='r' || type=='R')) + if ((stream >> type >> ignore >> fieldIndex) && (type == 'r' || type == 'R')) { - field = CSMWorld::Columns::getName ( - static_cast (fieldIndex)); + field = CSMWorld::Columns::getName(static_cast(fieldIndex)); } - return QString::fromUtf8 (field.c_str()); + return QString::fromUtf8(field.c_str()); } - if (index.column()==mColumnSeverity) + if (index.column() == mColumnSeverity) { - return QString::fromUtf8 ( - CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str()); + return QString::fromUtf8(CSMDoc::Message::toString(mRows.at(index.row()).mSeverity).c_str()); } return QVariant(); } -QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orientation, int role) const +QVariant CSMTools::ReportModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role!=Qt::DisplayRole) + if (role != Qt::DisplayRole) return QVariant(); - if (orientation==Qt::Vertical) + if (orientation == Qt::Vertical) return QVariant(); switch (section) { - case Column_Type: return "Type"; - case Column_Id: return "ID"; + case Column_Type: + return "Type"; + case Column_Id: + return "ID"; } - if (section==mColumnDescription) + if (section == mColumnDescription) return "Description"; - if (section==mColumnField) + if (section == mColumnField) return "Field"; - if (section==mColumnSeverity) + if (section == mColumnSeverity) return "Severity"; return "-"; } -bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& parent) +bool CSMTools::ReportModel::removeRows(int row, int count, const QModelIndex& parent) { if (parent.isValid()) return false; - if (count>0) + if (count > 0) { - beginRemoveRows (parent, row, row+count-1); + beginRemoveRows(parent, row, row + count - 1); - mRows.erase (mRows.begin()+row, mRows.begin()+row+count); + mRows.erase(mRows.begin() + row, mRows.begin() + row + count); endRemoveRows(); } @@ -138,45 +152,45 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p return true; } -void CSMTools::ReportModel::add (const CSMDoc::Message& message) +void CSMTools::ReportModel::add(const CSMDoc::Message& message) { - beginInsertRows (QModelIndex(), static_cast(mRows.size()), static_cast(mRows.size())); + beginInsertRows(QModelIndex(), static_cast(mRows.size()), static_cast(mRows.size())); - mRows.push_back (message); + mRows.push_back(message); endInsertRows(); } -void CSMTools::ReportModel::flagAsReplaced (int index) +void CSMTools::ReportModel::flagAsReplaced(int index) { - CSMDoc::Message& line = mRows.at (index); + CSMDoc::Message& line = mRows.at(index); std::string hint = line.mHint; - if (hint.empty() || hint[0]!='R') - throw std::logic_error ("trying to flag message as replaced that is not replaceable"); + if (hint.empty() || hint[0] != 'R') + throw std::logic_error("trying to flag message as replaced that is not replaceable"); hint[0] = 'r'; line.mHint = hint; - emit dataChanged (this->index (index, 0), this->index (index, columnCount())); + emit dataChanged(this->index(index, 0), this->index(index, columnCount())); } -const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const +const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId(int row) const { - return mRows.at (row).mId; + return mRows.at(row).mId; } -std::string CSMTools::ReportModel::getHint (int row) const +std::string CSMTools::ReportModel::getHint(int row) const { - return mRows.at (row).mHint; + return mRows.at(row).mHint; } void CSMTools::ReportModel::clear() { if (!mRows.empty()) { - beginRemoveRows (QModelIndex(), 0, static_cast(mRows.size())-1); + beginRemoveRows(QModelIndex(), 0, static_cast(mRows.size()) - 1); mRows.clear(); endRemoveRows(); } @@ -186,10 +200,9 @@ int CSMTools::ReportModel::countErrors() const { int count = 0; - for (std::vector::const_iterator iter (mRows.begin()); - iter!=mRows.end(); ++iter) - if (iter->mSeverity==CSMDoc::Message::Severity_Error || - iter->mSeverity==CSMDoc::Message::Severity_SeriousError) + for (std::vector::const_iterator iter(mRows.begin()); iter != mRows.end(); ++iter) + if (iter->mSeverity == CSMDoc::Message::Severity_Error + || iter->mSeverity == CSMDoc::Message::Severity_SeriousError) ++count; return count; diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp index 809dfcc3e..449105cea 100644 --- a/apps/opencs/model/tools/reportmodel.hpp +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -1,60 +1,66 @@ #ifndef CSM_TOOLS_REPORTMODEL_H #define CSM_TOOLS_REPORTMODEL_H -#include #include +#include #include +#include +#include #include "../doc/messages.hpp" -#include "../world/universalid.hpp" +namespace CSMWorld +{ + class UniversalId; +} namespace CSMTools { class ReportModel : public QAbstractTableModel { - Q_OBJECT + Q_OBJECT - std::vector mRows; + std::vector mRows; - // Fixed columns - enum Columns - { - Column_Type = 0, Column_Id = 1, Column_Hint = 2 - }; + // Fixed columns + enum Columns + { + Column_Type = 0, + Column_Id = 1, + Column_Hint = 2 + }; - // Configurable columns - int mColumnDescription; - int mColumnField; - int mColumnSeverity; + // Configurable columns + int mColumnDescription; + int mColumnField; + int mColumnSeverity; - public: + public: + ReportModel(bool fieldColumn = false, bool severityColumn = true); - ReportModel (bool fieldColumn = false, bool severityColumn = true); - - int rowCount (const QModelIndex & parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount (const QModelIndex & parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()) override; - - void add (const CSMDoc::Message& message); + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; - void flagAsReplaced (int index); - - const CSMWorld::UniversalId& getUniversalId (int row) const; + void add(const CSMDoc::Message& message); - std::string getHint (int row) const; + void flagAsReplaced(int index); - void clear(); + const CSMWorld::UniversalId& getUniversalId(int row) const; - // Return number of messages with Error or SeriousError severity. - int countErrors() const; + std::string getHint(int row) const; + + void clear(); + + // Return number of messages with Error or SeriousError severity. + int countErrors() const; }; } diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index 46a74362b..7a76efa48 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -1,10 +1,23 @@ #include "scriptcheck.hpp" -#include -#include -#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + #include #include +#include +#include +#include +#include #include "../doc/document.hpp" @@ -12,51 +25,56 @@ #include "../prefs/state.hpp" -CSMDoc::Message::Severity CSMTools::ScriptCheckStage::getSeverity (Type type) +CSMDoc::Message::Severity CSMTools::ScriptCheckStage::getSeverity(Type type) { switch (type) { - case WarningMessage: return CSMDoc::Message::Severity_Warning; - case ErrorMessage: return CSMDoc::Message::Severity_Error; + case WarningMessage: + return CSMDoc::Message::Severity_Warning; + case ErrorMessage: + return CSMDoc::Message::Severity_Error; } return CSMDoc::Message::Severity_SeriousError; } -void CSMTools::ScriptCheckStage::report (const std::string& message, const Compiler::TokenLoc& loc, - Type type) +void CSMTools::ScriptCheckStage::report(const std::string& message, const Compiler::TokenLoc& loc, Type type) { std::ostringstream stream; - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Script, mId); - stream << message << " (" << loc.mLiteral << ")" << " @ line " << loc.mLine+1 << ", column " << loc.mColumn; + stream << message << " (" << loc.mLiteral << ")" + << " @ line " << loc.mLine + 1 << ", column " << loc.mColumn; std::ostringstream hintStream; - hintStream << "l:" << loc.mLine+1 << " " << loc.mColumn; + hintStream << "l:" << loc.mLine + 1 << " " << loc.mColumn; - mMessages->add (id, stream.str(), hintStream.str(), getSeverity (type)); + mMessages->add(id, stream.str(), hintStream.str(), getSeverity(type)); } -void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) +void CSMTools::ScriptCheckStage::report(const std::string& message, Type type) { - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Script, mId); std::ostringstream stream; stream << message; - mMessages->add (id, stream.str(), "", getSeverity (type)); + mMessages->add(id, stream.str(), "", getSeverity(type)); } -CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document) -: mDocument (document), mContext (document.getData()), mMessages (nullptr), mWarningMode (Mode_Ignore) +CSMTools::ScriptCheckStage::ScriptCheckStage(const CSMDoc::Document& document) + : mDocument(document) + , mContext(document.getData()) + , mMessages(nullptr) + , mWarningMode(Mode_Ignore) { /// \todo add an option to configure warning mode - setWarningsMode (0); + setWarningsMode(0); - Compiler::registerExtensions (mExtensions); - mContext.setExtensions (&mExtensions); + Compiler::registerExtensions(mExtensions); + mContext.setExtensions(&mExtensions); mIgnoreBaseRecords = false; } @@ -65,16 +83,16 @@ int CSMTools::ScriptCheckStage::setup() { std::string warnings = CSMPrefs::get()["Scripts"]["warnings"].toString(); - if (warnings=="Ignore") + if (warnings == "Ignore") mWarningMode = Mode_Ignore; - else if (warnings=="Normal") + else if (warnings == "Normal") mWarningMode = Mode_Normal; - else if (warnings=="Strict") + else if (warnings == "Strict") mWarningMode = Mode_Strict; mContext.clear(); mMessages = nullptr; - mId.clear(); + mId = ESM::RefId(); Compiler::ErrorHandler::reset(); mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); @@ -82,14 +100,13 @@ int CSMTools::ScriptCheckStage::setup() return mDocument.getData().getScripts().getSize(); } -void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::ScriptCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record &record = mDocument.getData().getScripts().getRecord(stage); + const CSMWorld::Record& record = mDocument.getData().getScripts().getRecord(stage); - mId = mDocument.getData().getScripts().getId (stage); + mId = mDocument.getData().getScripts().getId(stage); - if (mDocument.isBlacklisted ( - CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, mId))) + if (mDocument.isBlacklisted(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Script, mId.getRefIdString()))) return; // Skip "Base" records (setting!) and "Deleted" records @@ -100,21 +117,27 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) switch (mWarningMode) { - case Mode_Ignore: setWarningsMode (0); break; - case Mode_Normal: setWarningsMode (1); break; - case Mode_Strict: setWarningsMode (2); break; + case Mode_Ignore: + setWarningsMode(0); + break; + case Mode_Normal: + setWarningsMode(1); + break; + case Mode_Strict: + setWarningsMode(2); + break; } try { - mFile = record.get().mId; - std::istringstream input (record.get().mScriptText); + mFile = record.get().mId.getRefIdString(); + std::istringstream input(record.get().mScriptText); - Compiler::Scanner scanner (*this, input, mContext.getExtensions()); + Compiler::Scanner scanner(*this, input, mContext.getExtensions()); - Compiler::FileParser parser (*this, mContext); + Compiler::FileParser parser(*this, mContext); - scanner.scan (parser); + scanner.scan(parser); } catch (const Compiler::SourceException&) { @@ -122,12 +145,12 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) } catch (const std::exception& error) { - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Script, mId); std::ostringstream stream; stream << error.what(); - messages.add (id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); } mMessages = nullptr; diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp index d975e02da..31b3f3291 100644 --- a/apps/opencs/model/tools/scriptcheck.hpp +++ b/apps/opencs/model/tools/scriptcheck.hpp @@ -1,9 +1,13 @@ #ifndef CSM_TOOLS_SCRIPTCHECK_H #define CSM_TOOLS_SCRIPTCHECK_H +#include + #include #include +#include + #include "../doc/stage.hpp" #include "../world/scriptcontext.hpp" @@ -13,44 +17,48 @@ namespace CSMDoc class Document; } +namespace Compiler +{ + struct TokenLoc; +} + namespace CSMTools { /// \brief VerifyStage: make sure that scripts compile class ScriptCheckStage : public CSMDoc::Stage, private Compiler::ErrorHandler { - enum WarningMode - { - Mode_Ignore, - Mode_Normal, - Mode_Strict - }; + enum WarningMode + { + Mode_Ignore, + Mode_Normal, + Mode_Strict + }; - const CSMDoc::Document& mDocument; - Compiler::Extensions mExtensions; - CSMWorld::ScriptContext mContext; - std::string mId; - std::string mFile; - CSMDoc::Messages *mMessages; - WarningMode mWarningMode; - bool mIgnoreBaseRecords; + const CSMDoc::Document& mDocument; + Compiler::Extensions mExtensions; + CSMWorld::ScriptContext mContext; + ESM::RefId mId; + std::string mFile; + CSMDoc::Messages* mMessages; + WarningMode mWarningMode; + bool mIgnoreBaseRecords; - CSMDoc::Message::Severity getSeverity (Type type); + CSMDoc::Message::Severity getSeverity(Type type); - void report (const std::string& message, const Compiler::TokenLoc& loc, Type type) override; - ///< Report error to the user. + void report(const std::string& message, const Compiler::TokenLoc& loc, Type type) override; + ///< Report error to the user. - void report (const std::string& message, Type type) override; - ///< Report a file related error + void report(const std::string& message, Type type) override; + ///< Report a file related error - public: + public: + ScriptCheckStage(const CSMDoc::Document& document); - ScriptCheckStage (const CSMDoc::Document& document); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 7005480d1..624cfef82 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -1,157 +1,185 @@ #include "search.hpp" -#include +#include +#include +#include + +#include #include +#include +#include +#include + +#include -#include "../doc/messages.hpp" #include "../doc/document.hpp" +#include "../doc/messages.hpp" -#include "../world/idtablebase.hpp" #include "../world/columnbase.hpp" -#include "../world/universalid.hpp" #include "../world/commands.hpp" +#include "../world/idtablebase.hpp" +#include "../world/universalid.hpp" -void CSMTools::Search::searchTextCell (const CSMWorld::IdTableBase *model, - const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, - CSMDoc::Messages& messages) const +void CSMTools::Search::searchTextCell(const CSMWorld::IdTableBase* model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { // using QString here for easier handling of case folding. - - QString search = QString::fromUtf8 (mText.c_str()); - QString text = model->data (index).toString(); + + QString search = QString::fromUtf8(mText.c_str()); + QString text = model->data(index).toString(); int pos = 0; Qt::CaseSensitivity caseSensitivity = mCase ? Qt::CaseSensitive : Qt::CaseInsensitive; - while ((pos = text.indexOf (search, pos, caseSensitivity))!=-1) + while ((pos = text.indexOf(search, pos, caseSensitivity)) != -1) { std::ostringstream hint; - hint - << (writable ? 'R' : 'r') - <<": " - << model->getColumnId (index.column()) - << " " << pos - << " " << search.length(); - - messages.add (id, formatDescription (text, pos, search.length()).toUtf8().data(), hint.str()); + hint << (writable ? 'R' : 'r') << ": " << model->getColumnId(index.column()) << " " << pos << " " + << search.length(); + + messages.add(id, formatDescription(text, pos, search.length()).toUtf8().data(), hint.str()); pos += search.length(); } } -void CSMTools::Search::searchRegExCell (const CSMWorld::IdTableBase *model, - const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, - CSMDoc::Messages& messages) const +void CSMTools::Search::searchRegExCell(const CSMWorld::IdTableBase* model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { - QString text = model->data (index).toString(); + // TODO: verify regular expression before starting a search + if (!mRegExp.isValid()) + return; - int pos = 0; + QString text = model->data(index).toString(); - while ((pos = mRegExp.indexIn (text, pos))!=-1) + QRegularExpressionMatchIterator i = mRegExp.globalMatch(text); + while (i.hasNext()) { - int length = mRegExp.matchedLength(); - - std::ostringstream hint; - hint - << (writable ? 'R' : 'r') - <<": " - << model->getColumnId (index.column()) - << " " << pos - << " " << length; - - messages.add (id, formatDescription (text, pos, length).toUtf8().data(), hint.str()); + QRegularExpressionMatch match = i.next(); - pos += length; + int pos = match.capturedStart(); + int length = match.capturedLength(); + + std::ostringstream hint; + hint << (writable ? 'R' : 'r') << ": " << model->getColumnId(index.column()) << " " << pos << " " << length; + + messages.add(id, formatDescription(text, pos, length).toUtf8().data(), hint.str()); } } -void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model, - const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const +void CSMTools::Search::searchRecordStateCell(const CSMWorld::IdTableBase* model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { if (writable) - throw std::logic_error ("Record state can not be modified by search and replace"); - - int data = model->data (index).toInt(); + throw std::logic_error("Record state can not be modified by search and replace"); - if (data==mValue) + int data = model->data(index).toInt(); + + if (data == mValue) { - std::vector> states = - CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); + std::vector> states + = CSMWorld::Columns::getEnums(CSMWorld::Columns::ColumnId_Modification); const std::string hint = "r: " + std::to_string(model->getColumnId(index.column())); - messages.add (id, states.at(data).second, hint); + messages.add(id, states.at(data).second, hint); } } -QString CSMTools::Search::formatDescription (const QString& description, int pos, int length) const +QString CSMTools::Search::formatDescription(const QString& description, int pos, int length) const { - QString text (description); + QString text(description); // split - QString highlight = flatten (text.mid (pos, length)); - QString before = flatten (mPaddingBefore>=pos ? - text.mid (0, pos) : text.mid (pos-mPaddingBefore, mPaddingBefore)); - QString after = flatten (text.mid (pos+length, mPaddingAfter)); + QString highlight = flatten(text.mid(pos, length)); + QString before = flatten(mPaddingBefore >= pos ? text.mid(0, pos) : text.mid(pos - mPaddingBefore, mPaddingBefore)); + QString after = flatten(text.mid(pos + length, mPaddingAfter)); // compensate for Windows nonsense - text.remove ('\r'); + text.remove('\r'); // join text = before + "" + highlight + "" + after; // improve layout for single line display - text.replace ("\n", "<CR>"); - text.replace ('\t', ' '); - - return text; + text.replace("\n", "<CR>"); + text.replace('\t', ' '); + + return text; } -QString CSMTools::Search::flatten (const QString& text) const +QString CSMTools::Search::flatten(const QString& text) const { - QString flat (text); + QString flat(text); - flat.replace ("&", "&"); - flat.replace ("<", "<"); + flat.replace("&", "&"); + flat.replace("<", "<"); return flat; } -CSMTools::Search::Search() : mType (Type_None), mValue (0), mCase (false), mIdColumn (0), mTypeColumn (0), - mPaddingBefore (10), mPaddingAfter (10) {} - -CSMTools::Search::Search (Type type, bool caseSensitive, const std::string& value) -: mType (type), mText (value), mValue (0), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) +CSMTools::Search::Search() + : mType(Type_None) + , mValue(0) + , mCase(false) + , mIdColumn(0) + , mTypeColumn(0) + , mPaddingBefore(10) + , mPaddingAfter(10) { - if (type!=Type_Text && type!=Type_Id) - throw std::logic_error ("Invalid search parameter (string)"); } -CSMTools::Search::Search (Type type, bool caseSensitive, const QRegExp& value) -: mType (type), mRegExp (value), mValue (0), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) +CSMTools::Search::Search(Type type, bool caseSensitive, const std::string& value) + : mType(type) + , mText(value) + , mValue(0) + , mCase(caseSensitive) + , mIdColumn(0) + , mTypeColumn(0) + , mPaddingBefore(10) + , mPaddingAfter(10) { - mRegExp.setCaseSensitivity(mCase ? Qt::CaseSensitive : Qt::CaseInsensitive); - if (type!=Type_TextRegEx && type!=Type_IdRegEx) - throw std::logic_error ("Invalid search parameter (RegExp)"); + if (type != Type_Text && type != Type_Id) + throw std::logic_error("Invalid search parameter (string)"); } -CSMTools::Search::Search (Type type, bool caseSensitive, int value) -: mType (type), mValue (value), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10) +CSMTools::Search::Search(Type type, bool caseSensitive, const QRegularExpression& value) + : mType(type) + , mRegExp(value) + , mValue(0) + , mCase(caseSensitive) + , mIdColumn(0) + , mTypeColumn(0) + , mPaddingBefore(10) + , mPaddingAfter(10) { - if (type!=Type_RecordState) - throw std::logic_error ("invalid search parameter (int)"); + mRegExp.setPatternOptions(mCase ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); + if (type != Type_TextRegEx && type != Type_IdRegEx) + throw std::logic_error("Invalid search parameter (RegExp)"); } -void CSMTools::Search::configure (const CSMWorld::IdTableBase *model) +CSMTools::Search::Search(Type type, bool caseSensitive, int value) + : mType(type) + , mValue(value) + , mCase(caseSensitive) + , mIdColumn(0) + , mTypeColumn(0) + , mPaddingBefore(10) + , mPaddingAfter(10) +{ + if (type != Type_RecordState) + throw std::logic_error("invalid search parameter (int)"); +} + +void CSMTools::Search::configure(const CSMWorld::IdTableBase* model) { mColumns.clear(); int columns = model->columnCount(); - for (int i=0; i ( - model->headerData ( - i, Qt::Horizontal, static_cast (CSMWorld::ColumnBase::Role_Display)).toInt()); + CSMWorld::ColumnBase::Display display = static_cast( + model->headerData(i, Qt::Horizontal, static_cast(CSMWorld::ColumnBase::Role_Display)).toInt()); bool consider = false; @@ -160,28 +188,26 @@ void CSMTools::Search::configure (const CSMWorld::IdTableBase *model) case Type_Text: case Type_TextRegEx: - if (CSMWorld::ColumnBase::isText (display) || - CSMWorld::ColumnBase::isScript (display)) + if (CSMWorld::ColumnBase::isText(display) || CSMWorld::ColumnBase::isScript(display)) { consider = true; } break; - + case Type_Id: case Type_IdRegEx: - if (CSMWorld::ColumnBase::isId (display) || - CSMWorld::ColumnBase::isScript (display)) + if (CSMWorld::ColumnBase::isId(display) || CSMWorld::ColumnBase::isScript(display)) { consider = true; } break; - + case Type_RecordState: - if (display==CSMWorld::ColumnBase::Display_RecordState) + if (display == CSMWorld::ColumnBase::Display_RecordState) consider = true; break; @@ -192,45 +218,43 @@ void CSMTools::Search::configure (const CSMWorld::IdTableBase *model) } if (consider) - mColumns.insert (i); + mColumns.insert(i); } - mIdColumn = model->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - mTypeColumn = model->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + mIdColumn = model->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + mTypeColumn = model->findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); } -void CSMTools::Search::searchRow (const CSMWorld::IdTableBase *model, int row, - CSMDoc::Messages& messages) const +void CSMTools::Search::searchRow(const CSMWorld::IdTableBase* model, int row, CSMDoc::Messages& messages) const { - for (std::set::const_iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter) + for (std::set::const_iterator iter(mColumns.begin()); iter != mColumns.end(); ++iter) { - QModelIndex index = model->index (row, *iter); + QModelIndex index = model->index(row, *iter); - CSMWorld::UniversalId::Type type = static_cast ( - model->data (model->index (row, mTypeColumn)).toInt()); - - CSMWorld::UniversalId id ( - type, model->data (model->index (row, mIdColumn)).toString().toUtf8().data()); + CSMWorld::UniversalId::Type type + = static_cast(model->data(model->index(row, mTypeColumn)).toInt()); + + CSMWorld::UniversalId id(type, model->data(model->index(row, mIdColumn)).toString().toUtf8().data()); + + bool writable = model->flags(index) & Qt::ItemIsEditable; - bool writable = model->flags (index) & Qt::ItemIsEditable; - switch (mType) { case Type_Text: case Type_Id: - searchTextCell (model, index, id, writable, messages); + searchTextCell(model, index, id, writable, messages); break; - + case Type_TextRegEx: case Type_IdRegEx: - searchRegExCell (model, index, id, writable, messages); + searchRegExCell(model, index, id, writable, messages); break; - + case Type_RecordState: - searchRecordStateCell (model, index, id, writable, messages); + searchRecordStateCell(model, index, id, writable, messages); break; case Type_None: @@ -240,54 +264,49 @@ void CSMTools::Search::searchRow (const CSMWorld::IdTableBase *model, int row, } } -void CSMTools::Search::setPadding (int before, int after) +void CSMTools::Search::setPadding(int before, int after) { mPaddingBefore = before; mPaddingAfter = after; } -void CSMTools::Search::replace (CSMDoc::Document& document, CSMWorld::IdTableBase *model, - const CSMWorld::UniversalId& id, const std::string& messageHint, - const std::string& replaceText) const +void CSMTools::Search::replace(CSMDoc::Document& document, CSMWorld::IdTableBase* model, + const CSMWorld::UniversalId& id, const std::string& messageHint, const std::string& replaceText) const { - std::istringstream stream (messageHint.c_str()); + std::istringstream stream(messageHint.c_str()); char hint, ignore; int columnId, pos, length; if (stream >> hint >> ignore >> columnId >> pos >> length) { - int column = - model->findColumnIndex (static_cast (columnId)); - - QModelIndex index = model->getModelIndex (id.getId(), column); + int column = model->findColumnIndex(static_cast(columnId)); - std::string text = model->data (index).toString().toUtf8().constData(); + QModelIndex index = model->getModelIndex(id.getId(), column); - std::string before = text.substr (0, pos); - std::string after = text.substr (pos+length); + std::string text = model->data(index).toString().toUtf8().constData(); + + std::string before = text.substr(0, pos); + std::string after = text.substr(pos + length); std::string newText = before + replaceText + after; - - document.getUndoStack().push ( - new CSMWorld::ModifyCommand (*model, index, QString::fromUtf8 (newText.c_str()))); + + document.getUndoStack().push(new CSMWorld::ModifyCommand(*model, index, QString::fromUtf8(newText.c_str()))); } } -bool CSMTools::Search::verify (CSMDoc::Document& document, CSMWorld::IdTableBase *model, - const CSMWorld::UniversalId& id, const std::string& messageHint) const +bool CSMTools::Search::verify(CSMDoc::Document& document, CSMWorld::IdTableBase* model, const CSMWorld::UniversalId& id, + const std::string& messageHint) const { - CSMDoc::Messages messages (CSMDoc::Message::Severity_Info); + CSMDoc::Messages messages(CSMDoc::Message::Severity_Info); - int row = model->getModelIndex (id.getId(), - model->findColumnIndex (CSMWorld::Columns::ColumnId_Id)).row(); - - searchRow (model, row, messages); + int row = model->getModelIndex(id.getId(), model->findColumnIndex(CSMWorld::Columns::ColumnId_Id)).row(); - for (CSMDoc::Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) - if (iter->mHint==messageHint) + searchRow(model, row, messages); + + for (CSMDoc::Messages::Iterator iter(messages.begin()); iter != messages.end(); ++iter) + if (iter->mHint == messageHint) return true; return false; } - diff --git a/apps/opencs/model/tools/search.hpp b/apps/opencs/model/tools/search.hpp index 35cfa6402..6e1b72cd6 100644 --- a/apps/opencs/model/tools/search.hpp +++ b/apps/opencs/model/tools/search.hpp @@ -1,11 +1,11 @@ #ifndef CSM_TOOLS_SEARCH_H #define CSM_TOOLS_SEARCH_H -#include #include +#include -#include #include +#include class QModelIndex; @@ -25,77 +25,71 @@ namespace CSMTools { class Search { - public: + public: + enum Type + { + Type_Text = 0, + Type_TextRegEx = 1, + Type_Id = 2, + Type_IdRegEx = 3, + Type_RecordState = 4, + Type_None + }; - enum Type - { - Type_Text = 0, - Type_TextRegEx = 1, - Type_Id = 2, - Type_IdRegEx = 3, - Type_RecordState = 4, - Type_None - }; + private: + Type mType; + std::string mText; + QRegularExpression mRegExp; + int mValue; + bool mCase; + std::set mColumns; + int mIdColumn; + int mTypeColumn; + int mPaddingBefore; + int mPaddingAfter; - private: + void searchTextCell(const CSMWorld::IdTableBase* model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; - Type mType; - std::string mText; - QRegExp mRegExp; - int mValue; - bool mCase; - std::set mColumns; - int mIdColumn; - int mTypeColumn; - int mPaddingBefore; - int mPaddingAfter; + void searchRegExCell(const CSMWorld::IdTableBase* model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; - void searchTextCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, - const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; + void searchRecordStateCell(const CSMWorld::IdTableBase* model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; - void searchRegExCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, - const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; + QString formatDescription(const QString& description, int pos, int length) const; - void searchRecordStateCell (const CSMWorld::IdTableBase *model, - const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, - CSMDoc::Messages& messages) const; + QString flatten(const QString& text) const; - QString formatDescription (const QString& description, int pos, int length) const; + public: + Search(); - QString flatten (const QString& text) const; - - public: + Search(Type type, bool caseSensitive, const std::string& value); - Search(); - - Search (Type type, bool caseSensitive, const std::string& value); + Search(Type type, bool caseSensitive, const QRegularExpression& value); - Search (Type type, bool caseSensitive, const QRegExp& value); + Search(Type type, bool caseSensitive, int value); - Search (Type type, bool caseSensitive, int value); + // Configure search for the specified model. + void configure(const CSMWorld::IdTableBase* model); - // Configure search for the specified model. - void configure (const CSMWorld::IdTableBase *model); + // Search row in \a model and store results in \a messages. + // + // \attention *this needs to be configured for \a model. + void searchRow(const CSMWorld::IdTableBase* model, int row, CSMDoc::Messages& messages) const; - // Search row in \a model and store results in \a messages. - // - // \attention *this needs to be configured for \a model. - void searchRow (const CSMWorld::IdTableBase *model, int row, - CSMDoc::Messages& messages) const; + void setPadding(int before, int after); - void setPadding (int before, int after); + // Configuring *this for the model is not necessary when calling this function. + void replace(CSMDoc::Document& document, CSMWorld::IdTableBase* model, const CSMWorld::UniversalId& id, + const std::string& messageHint, const std::string& replaceText) const; - // Configuring *this for the model is not necessary when calling this function. - void replace (CSMDoc::Document& document, CSMWorld::IdTableBase *model, - const CSMWorld::UniversalId& id, const std::string& messageHint, - const std::string& replaceText) const; - - // Check if model still matches search results. - bool verify (CSMDoc::Document& document, CSMWorld::IdTableBase *model, - const CSMWorld::UniversalId& id, const std::string& messageHint) const; + // Check if model still matches search results. + bool verify(CSMDoc::Document& document, CSMWorld::IdTableBase* model, const CSMWorld::UniversalId& id, + const std::string& messageHint) const; }; } -Q_DECLARE_METATYPE (CSMTools::Search) +Q_DECLARE_METATYPE(CSMTools::Search) #endif diff --git a/apps/opencs/model/tools/searchoperation.cpp b/apps/opencs/model/tools/searchoperation.cpp index 8fba1cc1e..d7e5dd419 100644 --- a/apps/opencs/model/tools/searchoperation.cpp +++ b/apps/opencs/model/tools/searchoperation.cpp @@ -1,38 +1,41 @@ #include "searchoperation.hpp" -#include "../doc/state.hpp" #include "../doc/document.hpp" +#include "../doc/state.hpp" #include "../world/data.hpp" #include "../world/idtablebase.hpp" +#include + +#include +#include +#include +#include + #include "searchstage.hpp" -CSMTools::SearchOperation::SearchOperation (CSMDoc::Document& document) -: CSMDoc::Operation (CSMDoc::State_Searching, false) +CSMTools::SearchOperation::SearchOperation(CSMDoc::Document& document) + : CSMDoc::Operation(CSMDoc::State_Searching, false) { - std::vector types = CSMWorld::UniversalId::listTypes ( - CSMWorld::UniversalId::Class_RecordList | - CSMWorld::UniversalId::Class_ResourceList - ); + std::vector types = CSMWorld::UniversalId::listTypes( + CSMWorld::UniversalId::Class_RecordList | CSMWorld::UniversalId::Class_ResourceList); - for (std::vector::const_iterator iter (types.begin()); - iter!=types.end(); ++iter) - appendStage (new SearchStage (&dynamic_cast ( - *document.getData().getTableModel (*iter)))); + for (std::vector::const_iterator iter(types.begin()); iter != types.end(); ++iter) + appendStage(new SearchStage(&dynamic_cast(*document.getData().getTableModel(*iter)))); - setDefaultSeverity (CSMDoc::Message::Severity_Info); + setDefaultSeverity(CSMDoc::Message::Severity_Info); } -void CSMTools::SearchOperation::configure (const Search& search) +void CSMTools::SearchOperation::configure(const Search& search) { mSearch = search; } -void CSMTools::SearchOperation::appendStage (SearchStage *stage) +void CSMTools::SearchOperation::appendStage(SearchStage* stage) { - CSMDoc::Operation::appendStage (stage); - stage->setOperation (this); + CSMDoc::Operation::appendStage(stage); + stage->setOperation(this); } const CSMTools::Search& CSMTools::SearchOperation::getSearch() const diff --git a/apps/opencs/model/tools/searchoperation.hpp b/apps/opencs/model/tools/searchoperation.hpp index fbbb38898..87a046d09 100644 --- a/apps/opencs/model/tools/searchoperation.hpp +++ b/apps/opencs/model/tools/searchoperation.hpp @@ -13,25 +13,23 @@ namespace CSMDoc namespace CSMTools { class SearchStage; - + class SearchOperation : public CSMDoc::Operation { - Search mSearch; - - public: + Search mSearch; - SearchOperation (CSMDoc::Document& document); + public: + SearchOperation(CSMDoc::Document& document); - /// \attention Do not call this function while a search is running. - void configure (const Search& search); + /// \attention Do not call this function while a search is running. + void configure(const Search& search); - void appendStage (SearchStage *stage); - ///< The ownership of \a stage is transferred to *this. - /// - /// \attention Do no call this function while this Operation is running. + void appendStage(SearchStage* stage); + ///< The ownership of \a stage is transferred to *this. + /// + /// \attention Do no call this function while this Operation is running. - const Search& getSearch() const; - + const Search& getSearch() const; }; } diff --git a/apps/opencs/model/tools/searchstage.cpp b/apps/opencs/model/tools/searchstage.cpp index 7cd3f4924..c52eed39e 100644 --- a/apps/opencs/model/tools/searchstage.cpp +++ b/apps/opencs/model/tools/searchstage.cpp @@ -2,28 +2,37 @@ #include "../world/idtablebase.hpp" +#include + #include "searchoperation.hpp" -CSMTools::SearchStage::SearchStage (const CSMWorld::IdTableBase *model) -: mModel (model), mOperation (nullptr) -{} +namespace CSMDoc +{ + class Messages; +} + +CSMTools::SearchStage::SearchStage(const CSMWorld::IdTableBase* model) + : mModel(model) + , mOperation(nullptr) +{ +} int CSMTools::SearchStage::setup() { if (mOperation) mSearch = mOperation->getSearch(); - mSearch.configure (mModel); - + mSearch.configure(mModel); + return mModel->rowCount(); } -void CSMTools::SearchStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::SearchStage::perform(int stage, CSMDoc::Messages& messages) { - mSearch.searchRow (mModel, stage, messages); + mSearch.searchRow(mModel, stage, messages); } -void CSMTools::SearchStage::setOperation (const SearchOperation *operation) +void CSMTools::SearchStage::setOperation(const SearchOperation* operation) { mOperation = operation; } diff --git a/apps/opencs/model/tools/searchstage.hpp b/apps/opencs/model/tools/searchstage.hpp index 86abf31a3..69a5eed46 100644 --- a/apps/opencs/model/tools/searchstage.hpp +++ b/apps/opencs/model/tools/searchstage.hpp @@ -5,6 +5,11 @@ #include "search.hpp" +namespace CSMDoc +{ + class Messages; +} + namespace CSMWorld { class IdTableBase; @@ -13,24 +18,23 @@ namespace CSMWorld namespace CSMTools { class SearchOperation; - + class SearchStage : public CSMDoc::Stage { - const CSMWorld::IdTableBase *mModel; - Search mSearch; - const SearchOperation *mOperation; + const CSMWorld::IdTableBase* mModel; + Search mSearch; + const SearchOperation* mOperation; - public: + public: + SearchStage(const CSMWorld::IdTableBase* model); - SearchStage (const CSMWorld::IdTableBase *model); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this stage will be appended to \a messages. - - void setOperation (const SearchOperation *operation); + void setOperation(const SearchOperation* operation); }; } diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp index c5b38dc1e..b3300c4fa 100644 --- a/apps/opencs/model/tools/skillcheck.cpp +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -1,11 +1,20 @@ #include "skillcheck.hpp" +#include + #include "../prefs/state.hpp" #include "../world/universalid.hpp" -CSMTools::SkillCheckStage::SkillCheckStage (const CSMWorld::IdCollection& skills) -: mSkills (skills) +#include +#include +#include +#include +#include +#include + +CSMTools::SkillCheckStage::SkillCheckStage(const CSMWorld::IdCollection& skills) + : mSkills(skills) { mIgnoreBaseRecords = false; } @@ -17,9 +26,9 @@ int CSMTools::SkillCheckStage::setup() return mSkills.getSize(); } -void CSMTools::SkillCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::SkillCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mSkills.getRecord (stage); + const CSMWorld::Record& record = mSkills.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -27,13 +36,13 @@ void CSMTools::SkillCheckStage::perform (int stage, CSMDoc::Messages& messages) const ESM::Skill& skill = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Skill, skill.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Skill, skill.mId); if (skill.mDescription.empty()) messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); - for (int i=0; i<4; ++i) - if (skill.mData.mUseValue[i]<0) + for (int i = 0; i < 4; ++i) + if (skill.mData.mUseValue[i] < 0) { messages.add(id, "Use value #" + std::to_string(i) + " is negative", "", CSMDoc::Message::Severity_Error); } diff --git a/apps/opencs/model/tools/skillcheck.hpp b/apps/opencs/model/tools/skillcheck.hpp index b1af887f6..517dfc807 100644 --- a/apps/opencs/model/tools/skillcheck.hpp +++ b/apps/opencs/model/tools/skillcheck.hpp @@ -1,29 +1,36 @@ #ifndef CSM_TOOLS_SKILLCHECK_H #define CSM_TOOLS_SKILLCHECK_H -#include - #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + struct Skill; +} + namespace CSMTools { /// \brief VerifyStage: make sure that skill records are internally consistent class SkillCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mSkills; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mSkills; + bool mIgnoreBaseRecords; - public: + public: + SkillCheckStage(const CSMWorld::IdCollection& skills); - SkillCheckStage (const CSMWorld::IdCollection& skills); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index c0d893f1a..b14222523 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -1,13 +1,24 @@ #include "soundcheck.hpp" +#include + #include "../prefs/state.hpp" #include "../world/universalid.hpp" -CSMTools::SoundCheckStage::SoundCheckStage (const CSMWorld::IdCollection &sounds, - const CSMWorld::Resources &soundfiles) - : mSounds (sounds), - mSoundFiles (soundfiles) +#include +#include +#include +#include +#include +#include + +#include + +CSMTools::SoundCheckStage::SoundCheckStage( + const CSMWorld::IdCollection& sounds, const CSMWorld::Resources& soundfiles) + : mSounds(sounds) + , mSoundFiles(soundfiles) { mIgnoreBaseRecords = false; } @@ -19,9 +30,9 @@ int CSMTools::SoundCheckStage::setup() return mSounds.getSize(); } -void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::SoundCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mSounds.getRecord (stage); + const CSMWorld::Record& record = mSounds.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -29,9 +40,9 @@ void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages) const ESM::Sound& sound = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Sound, sound.mId); - if (sound.mData.mMinRange>sound.mData.mMaxRange) + if (sound.mData.mMinRange > sound.mData.mMaxRange) { messages.add(id, "Minimum range is larger than maximum range", "", CSMDoc::Message::Severity_Warning); } diff --git a/apps/opencs/model/tools/soundcheck.hpp b/apps/opencs/model/tools/soundcheck.hpp index 80eb9e7f2..4f540a6fa 100644 --- a/apps/opencs/model/tools/soundcheck.hpp +++ b/apps/opencs/model/tools/soundcheck.hpp @@ -1,32 +1,42 @@ #ifndef CSM_TOOLS_SOUNDCHECK_H #define CSM_TOOLS_SOUNDCHECK_H -#include - -#include "../world/resources.hpp" #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class Resources; +} + +namespace ESM +{ + struct Sound; +} + namespace CSMTools { /// \brief VerifyStage: make sure that sound records are internally consistent class SoundCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mSounds; - const CSMWorld::Resources &mSoundFiles; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mSounds; + const CSMWorld::Resources& mSoundFiles; + bool mIgnoreBaseRecords; - public: + public: + SoundCheckStage(const CSMWorld::IdCollection& sounds, const CSMWorld::Resources& soundfiles); - SoundCheckStage (const CSMWorld::IdCollection& sounds, - const CSMWorld::Resources &soundfiles); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/soundgencheck.cpp b/apps/opencs/model/tools/soundgencheck.cpp index ec29e23fe..9caf007a4 100644 --- a/apps/opencs/model/tools/soundgencheck.cpp +++ b/apps/opencs/model/tools/soundgencheck.cpp @@ -1,16 +1,27 @@ #include "soundgencheck.hpp" +#include + #include "../prefs/state.hpp" #include "../world/refiddata.hpp" #include "../world/universalid.hpp" -CSMTools::SoundGenCheckStage::SoundGenCheckStage(const CSMWorld::IdCollection &soundGens, - const CSMWorld::IdCollection &sounds, - const CSMWorld::RefIdCollection &objects) - : mSoundGens(soundGens), - mSounds(sounds), - mObjects(objects) +#include +#include +#include +#include +#include +#include + +#include +#include + +CSMTools::SoundGenCheckStage::SoundGenCheckStage(const CSMWorld::IdCollection& soundGens, + const CSMWorld::IdCollection& sounds, const CSMWorld::RefIdCollection& objects) + : mSoundGens(soundGens) + , mSounds(sounds) + , mObjects(objects) { mIgnoreBaseRecords = false; } @@ -22,10 +33,10 @@ int CSMTools::SoundGenCheckStage::setup() return mSoundGens.getSize(); } -void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages) +void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record &record = mSoundGens.getRecord(stage); - + const CSMWorld::Record& record = mSoundGens.getRecord(stage); + // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; @@ -38,11 +49,13 @@ void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages CSMWorld::RefIdData::LocalIndex creatureIndex = mObjects.getDataSet().searchId(soundGen.mCreature); if (creatureIndex.first == -1) { - messages.add(id, "Creature '" + soundGen.mCreature + "' doesn't exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Creature '" + soundGen.mCreature.getRefIdString() + "' doesn't exist", "", + CSMDoc::Message::Severity_Error); } else if (creatureIndex.second != CSMWorld::UniversalId::Type_Creature) { - messages.add(id, "'" + soundGen.mCreature + "' is not a creature", "", CSMDoc::Message::Severity_Error); + messages.add(id, "'" + soundGen.mCreature.getRefIdString() + "' is not a creature", "", + CSMDoc::Message::Severity_Error); } } @@ -52,6 +65,7 @@ void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages } else if (mSounds.searchId(soundGen.mSound) == -1) { - messages.add(id, "Sound '" + soundGen.mSound + "' doesn't exist", "", CSMDoc::Message::Severity_Error); + messages.add( + id, "Sound '" + soundGen.mSound.getRefIdString() + "' doesn't exist", "", CSMDoc::Message::Severity_Error); } } diff --git a/apps/opencs/model/tools/soundgencheck.hpp b/apps/opencs/model/tools/soundgencheck.hpp index 306d35ded..0a23107a6 100644 --- a/apps/opencs/model/tools/soundgencheck.hpp +++ b/apps/opencs/model/tools/soundgencheck.hpp @@ -1,30 +1,45 @@ #ifndef CSM_TOOLS_SOUNDGENCHECK_HPP #define CSM_TOOLS_SOUNDGENCHECK_HPP -#include "../world/data.hpp" +#include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class RefIdCollection; +} + +namespace ESM +{ + struct SoundGenerator; + struct Sound; +} + namespace CSMTools { /// \brief VerifyStage: make sure that sound gen records are internally consistent class SoundGenCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection &mSoundGens; - const CSMWorld::IdCollection &mSounds; - const CSMWorld::RefIdCollection &mObjects; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mSoundGens; + const CSMWorld::IdCollection& mSounds; + const CSMWorld::RefIdCollection& mObjects; + bool mIgnoreBaseRecords; - public: - SoundGenCheckStage(const CSMWorld::IdCollection &soundGens, - const CSMWorld::IdCollection &sounds, - const CSMWorld::RefIdCollection &objects); + public: + SoundGenCheckStage(const CSMWorld::IdCollection& soundGens, + const CSMWorld::IdCollection& sounds, const CSMWorld::RefIdCollection& objects); - int setup() override; - ///< \return number of steps + int setup() override; + ///< \return number of steps - void perform(int stage, CSMDoc::Messages &messages) override; - ///< Messages resulting from this stage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this stage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index dc9ce65c0..f91438dc2 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -1,16 +1,20 @@ #include "spellcheck.hpp" -#include -#include +#include -#include +#include +#include +#include +#include +#include +#include + +#include #include "../prefs/state.hpp" -#include "../world/universalid.hpp" - -CSMTools::SpellCheckStage::SpellCheckStage (const CSMWorld::IdCollection& spells) -: mSpells (spells) +CSMTools::SpellCheckStage::SpellCheckStage(const CSMWorld::IdCollection& spells) + : mSpells(spells) { mIgnoreBaseRecords = false; } @@ -22,9 +26,9 @@ int CSMTools::SpellCheckStage::setup() return mSpells.getSize(); } -void CSMTools::SpellCheckStage::perform (int stage, CSMDoc::Messages& messages) +void CSMTools::SpellCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mSpells.getRecord (stage); + const CSMWorld::Record& record = mSpells.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) @@ -32,14 +36,14 @@ void CSMTools::SpellCheckStage::perform (int stage, CSMDoc::Messages& messages) const ESM::Spell& spell = record.get(); - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Spell, spell.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Spell, spell.mId); // test for empty name if (spell.mName.empty()) messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); // test for invalid cost values - if (spell.mData.mCost<0) + if (spell.mData.mCost < 0) messages.add(id, "Spell cost is negative", "", CSMDoc::Message::Severity_Error); /// \todo check data members that can't be edited in the table view diff --git a/apps/opencs/model/tools/spellcheck.hpp b/apps/opencs/model/tools/spellcheck.hpp index bfc962810..6d1278689 100644 --- a/apps/opencs/model/tools/spellcheck.hpp +++ b/apps/opencs/model/tools/spellcheck.hpp @@ -1,29 +1,36 @@ #ifndef CSM_TOOLS_SPELLCHECK_H #define CSM_TOOLS_SPELLCHECK_H -#include - #include "../world/idcollection.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + struct Spell; +} + namespace CSMTools { /// \brief VerifyStage: make sure that spell records are internally consistent class SpellCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mSpells; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mSpells; + bool mIgnoreBaseRecords; - public: + public: + SpellCheckStage(const CSMWorld::IdCollection& spells); - SpellCheckStage (const CSMWorld::IdCollection& spells); + int setup() override; + ///< \return number of steps - int setup() override; - ///< \return number of steps - - void perform (int stage, CSMDoc::Messages& messages) override; - ///< Messages resulting from this tage will be appended to \a messages. + void perform(int stage, CSMDoc::Messages& messages) override; + ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/startscriptcheck.cpp b/apps/opencs/model/tools/startscriptcheck.cpp index deb7d384f..bf3013338 100644 --- a/apps/opencs/model/tools/startscriptcheck.cpp +++ b/apps/opencs/model/tools/startscriptcheck.cpp @@ -1,31 +1,47 @@ #include "startscriptcheck.hpp" +#include + #include "../prefs/state.hpp" -#include +#include +#include +#include +#include +#include +#include -CSMTools::StartScriptCheckStage::StartScriptCheckStage ( - const CSMWorld::IdCollection& startScripts, - const CSMWorld::IdCollection& scripts) -: mStartScripts (startScripts), mScripts (scripts) +#include +#include + +namespace ESM +{ + class Script; +} + +CSMTools::StartScriptCheckStage::StartScriptCheckStage( + const CSMWorld::IdCollection& startScripts, const CSMWorld::IdCollection& scripts) + : mStartScripts(startScripts) + , mScripts(scripts) { mIgnoreBaseRecords = false; } void CSMTools::StartScriptCheckStage::perform(int stage, CSMDoc::Messages& messages) { - const CSMWorld::Record& record = mStartScripts.getRecord (stage); + const CSMWorld::Record& record = mStartScripts.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; - std::string scriptId = record.get().mId; + const auto& scriptId = record.get().mId; - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_StartScript, scriptId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_StartScript, scriptId); - if (mScripts.searchId (Misc::StringUtils::lowerCase (scriptId))==-1) - messages.add(id, "Start script " + scriptId + " does not exist", "", CSMDoc::Message::Severity_Error); + if (mScripts.searchId(scriptId) == -1) + messages.add( + id, "Start script " + scriptId.getRefIdString() + " does not exist", "", CSMDoc::Message::Severity_Error); } int CSMTools::StartScriptCheckStage::setup() diff --git a/apps/opencs/model/tools/startscriptcheck.hpp b/apps/opencs/model/tools/startscriptcheck.hpp index a45d3c943..31e5e451d 100644 --- a/apps/opencs/model/tools/startscriptcheck.hpp +++ b/apps/opencs/model/tools/startscriptcheck.hpp @@ -1,28 +1,37 @@ #ifndef CSM_TOOLS_STARTSCRIPTCHECK_H #define CSM_TOOLS_STARTSCRIPTCHECK_H -#include -#include +#include #include "../doc/stage.hpp" #include "../world/idcollection.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace ESM +{ + class Script; + struct StartScript; +} + namespace CSMTools { class StartScriptCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mStartScripts; - const CSMWorld::IdCollection& mScripts; - bool mIgnoreBaseRecords; + const CSMWorld::IdCollection& mStartScripts; + const CSMWorld::IdCollection& mScripts; + bool mIgnoreBaseRecords; - public: + public: + StartScriptCheckStage(const CSMWorld::IdCollection& startScripts, + const CSMWorld::IdCollection& scripts); - StartScriptCheckStage (const CSMWorld::IdCollection& startScripts, - const CSMWorld::IdCollection& scripts); - - void perform(int stage, CSMDoc::Messages& messages) override; - int setup() override; + void perform(int stage, CSMDoc::Messages& messages) override; + int setup() override; }; } diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index a3d7db497..e1e7a0dcd 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -1,157 +1,165 @@ #include "tools.hpp" -#include +#include +#include +#include +#include +#include -#include "../doc/state.hpp" -#include "../doc/operation.hpp" #include "../doc/document.hpp" -#include "../world/data.hpp" -#include "../world/universalid.hpp" - -#include "reportmodel.hpp" -#include "mandatoryid.hpp" -#include "skillcheck.hpp" -#include "classcheck.hpp" -#include "factioncheck.hpp" -#include "racecheck.hpp" -#include "soundcheck.hpp" -#include "regioncheck.hpp" #include "birthsigncheck.hpp" -#include "spellcheck.hpp" -#include "referenceablecheck.hpp" -#include "scriptcheck.hpp" #include "bodypartcheck.hpp" -#include "referencecheck.hpp" -#include "startscriptcheck.hpp" -#include "searchoperation.hpp" -#include "pathgridcheck.hpp" -#include "soundgencheck.hpp" -#include "magiceffectcheck.hpp" -#include "mergeoperation.hpp" -#include "gmstcheck.hpp" -#include "topicinfocheck.hpp" -#include "journalcheck.hpp" +#include "classcheck.hpp" #include "enchantmentcheck.hpp" +#include "factioncheck.hpp" +#include "gmstcheck.hpp" +#include "journalcheck.hpp" +#include "magiceffectcheck.hpp" +#include "mandatoryid.hpp" +#include "mergeoperation.hpp" +#include "pathgridcheck.hpp" +#include "racecheck.hpp" +#include "referenceablecheck.hpp" +#include "referencecheck.hpp" +#include "regioncheck.hpp" +#include "reportmodel.hpp" +#include "scriptcheck.hpp" +#include "searchoperation.hpp" +#include "skillcheck.hpp" +#include "soundcheck.hpp" +#include "soundgencheck.hpp" +#include "spellcheck.hpp" +#include "startscriptcheck.hpp" +#include "topicinfocheck.hpp" -CSMDoc::OperationHolder *CSMTools::Tools::get (int type) +#include +#include +#include + +namespace CSMDoc +{ + struct Message; +} + +CSMDoc::OperationHolder* CSMTools::Tools::get(int type) { switch (type) { - case CSMDoc::State_Verifying: return &mVerifier; - case CSMDoc::State_Searching: return &mSearch; - case CSMDoc::State_Merging: return &mMerge; + case CSMDoc::State_Verifying: + return &mVerifier; + case CSMDoc::State_Searching: + return &mSearch; + case CSMDoc::State_Merging: + return &mMerge; } return nullptr; } -const CSMDoc::OperationHolder *CSMTools::Tools::get (int type) const +const CSMDoc::OperationHolder* CSMTools::Tools::get(int type) const { - return const_cast (this)->get (type); + return const_cast(this)->get(type); } -CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() +CSMDoc::OperationHolder* CSMTools::Tools::getVerifier() { if (!mVerifierOperation) { - mVerifierOperation = new CSMDoc::Operation (CSMDoc::State_Verifying, false); + mVerifierOperation = new CSMDoc::Operation(CSMDoc::State_Verifying, false); - connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); - connect (&mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); - connect (&mVerifier, SIGNAL (reportMessage (const CSMDoc::Message&, int)), - this, SLOT (verifierMessage (const CSMDoc::Message&, int))); + connect(&mVerifier, &CSMDoc::OperationHolder::progress, this, &Tools::progress); + connect(&mVerifier, &CSMDoc::OperationHolder::done, this, &Tools::done); + connect(&mVerifier, &CSMDoc::OperationHolder::reportMessage, this, &Tools::verifierMessage); - std::vector mandatoryIds {"Day", "DaysPassed", "GameHour", "Month", "PCRace"}; + std::vector mandatoryRefIds; + { + auto mandatoryIds = { "Day", "DaysPassed", "GameHour", "Month", "PCRace" }; + for (auto& id : mandatoryIds) + mandatoryRefIds.push_back(ESM::RefId::stringRefId(id)); + } - mVerifierOperation->appendStage (new MandatoryIdStage (mData.getGlobals(), - CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); + mVerifierOperation->appendStage(new MandatoryIdStage( + mData.getGlobals(), CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Globals), mandatoryRefIds)); - mVerifierOperation->appendStage (new SkillCheckStage (mData.getSkills())); + mVerifierOperation->appendStage(new SkillCheckStage(mData.getSkills())); - mVerifierOperation->appendStage (new ClassCheckStage (mData.getClasses())); + mVerifierOperation->appendStage(new ClassCheckStage(mData.getClasses())); - mVerifierOperation->appendStage (new FactionCheckStage (mData.getFactions())); + mVerifierOperation->appendStage(new FactionCheckStage(mData.getFactions())); - mVerifierOperation->appendStage (new RaceCheckStage (mData.getRaces())); - - mVerifierOperation->appendStage (new SoundCheckStage (mData.getSounds(), mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); - - mVerifierOperation->appendStage (new RegionCheckStage (mData.getRegions())); - - mVerifierOperation->appendStage (new BirthsignCheckStage (mData.getBirthsigns(), mData.getResources (CSMWorld::UniversalId::Type_Textures))); - - mVerifierOperation->appendStage (new SpellCheckStage (mData.getSpells())); - - mVerifierOperation->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions(), mData.getScripts(), - mData.getResources (CSMWorld::UniversalId::Type_Meshes), mData.getResources (CSMWorld::UniversalId::Type_Icons), - mData.getBodyParts())); - - mVerifierOperation->appendStage (new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); - - mVerifierOperation->appendStage (new ScriptCheckStage (mDocument)); - - mVerifierOperation->appendStage (new StartScriptCheckStage (mData.getStartScripts(), mData.getScripts())); + mVerifierOperation->appendStage(new RaceCheckStage(mData.getRaces())); mVerifierOperation->appendStage( - new BodyPartCheckStage( - mData.getBodyParts(), - mData.getResources( - CSMWorld::UniversalId( CSMWorld::UniversalId::Type_Meshes )), - mData.getRaces() )); + new SoundCheckStage(mData.getSounds(), mData.getResources(CSMWorld::UniversalId::Type_SoundsRes))); - mVerifierOperation->appendStage (new PathgridCheckStage (mData.getPathgrids())); + mVerifierOperation->appendStage(new RegionCheckStage(mData.getRegions())); - mVerifierOperation->appendStage (new SoundGenCheckStage (mData.getSoundGens(), - mData.getSounds(), - mData.getReferenceables())); + mVerifierOperation->appendStage( + new BirthsignCheckStage(mData.getBirthsigns(), mData.getResources(CSMWorld::UniversalId::Type_Textures))); - mVerifierOperation->appendStage (new MagicEffectCheckStage (mData.getMagicEffects(), - mData.getSounds(), - mData.getReferenceables(), - mData.getResources (CSMWorld::UniversalId::Type_Icons), - mData.getResources (CSMWorld::UniversalId::Type_Textures))); + mVerifierOperation->appendStage(new SpellCheckStage(mData.getSpells())); - mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); + mVerifierOperation->appendStage( + new ReferenceableCheckStage(mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), + mData.getFactions(), mData.getScripts(), mData.getResources(CSMWorld::UniversalId::Type_Meshes), + mData.getResources(CSMWorld::UniversalId::Type_Icons), mData.getBodyParts())); - mVerifierOperation->appendStage (new TopicInfoCheckStage (mData.getTopicInfos(), - mData.getCells(), - mData.getClasses(), - mData.getFactions(), - mData.getGmsts(), - mData.getGlobals(), - mData.getJournals(), - mData.getRaces(), - mData.getRegions(), - mData.getTopics(), - mData.getReferenceables().getDataSet(), - mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); + mVerifierOperation->appendStage(new ReferenceCheckStage( + mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); - mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); + mVerifierOperation->appendStage(new ScriptCheckStage(mDocument)); - mVerifierOperation->appendStage (new EnchantmentCheckStage(mData.getEnchantments())); + mVerifierOperation->appendStage(new StartScriptCheckStage(mData.getStartScripts(), mData.getScripts())); - mVerifier.setOperation (mVerifierOperation); + mVerifierOperation->appendStage(new BodyPartCheckStage(mData.getBodyParts(), + mData.getResources(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Meshes)), mData.getRaces())); + + mVerifierOperation->appendStage(new PathgridCheckStage(mData.getPathgrids())); + + mVerifierOperation->appendStage( + new SoundGenCheckStage(mData.getSoundGens(), mData.getSounds(), mData.getReferenceables())); + + mVerifierOperation->appendStage(new MagicEffectCheckStage(mData.getMagicEffects(), mData.getSounds(), + mData.getReferenceables(), mData.getResources(CSMWorld::UniversalId::Type_Icons), + mData.getResources(CSMWorld::UniversalId::Type_Textures))); + + mVerifierOperation->appendStage(new GmstCheckStage(mData.getGmsts())); + + mVerifierOperation->appendStage(new TopicInfoCheckStage(mData.getTopicInfos(), mData.getCells(), + mData.getClasses(), mData.getFactions(), mData.getGmsts(), mData.getGlobals(), mData.getJournals(), + mData.getRaces(), mData.getRegions(), mData.getTopics(), mData.getReferenceables().getDataSet(), + mData.getResources(CSMWorld::UniversalId::Type_SoundsRes))); + + mVerifierOperation->appendStage(new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); + + mVerifierOperation->appendStage(new EnchantmentCheckStage(mData.getEnchantments())); + + mVerifier.setOperation(mVerifierOperation); } return &mVerifier; } -CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding) -: mDocument (document), mData (document.getData()), mVerifierOperation (nullptr), - mSearchOperation (nullptr), mMergeOperation (nullptr), mNextReportNumber (0), mEncoding (encoding) +CSMTools::Tools::Tools(CSMDoc::Document& document, ToUTF8::FromType encoding) + : mDocument(document) + , mData(document.getData()) + , mVerifierOperation(nullptr) + , mSearchOperation(nullptr) + , mMergeOperation(nullptr) + , mNextReportNumber(0) + , mEncoding(encoding) { // index 0: load error log - mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); - mActiveReports.insert (std::make_pair (CSMDoc::State_Loading, 0)); + mReports.insert(std::make_pair(mNextReportNumber++, new ReportModel)); + mActiveReports.insert(std::make_pair(CSMDoc::State_Loading, 0)); - connect (&mSearch, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); - connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); - connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)), - this, SLOT (verifierMessage (const CSMDoc::Message&, int))); + connect(&mSearch, &CSMDoc::OperationHolder::progress, this, &Tools::progress); + connect(&mSearch, &CSMDoc::OperationHolder::done, this, &Tools::done); + connect(&mSearch, &CSMDoc::OperationHolder::reportMessage, this, &Tools::verifierMessage); - connect (&mMerge, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); - connect (&mMerge, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); + connect(&mMerge, &CSMDoc::OperationHolder::progress, this, &Tools::progress); + connect(&mMerge, &CSMDoc::OperationHolder::done, this, &Tools::done); // don't need to connect report message, since there are no messages for merge } @@ -175,106 +183,104 @@ CSMTools::Tools::~Tools() delete mMergeOperation; } - for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) + for (std::map::iterator iter(mReports.begin()); iter != mReports.end(); ++iter) delete iter->second; } -CSMWorld::UniversalId CSMTools::Tools::runVerifier (const CSMWorld::UniversalId& reportId) +CSMWorld::UniversalId CSMTools::Tools::runVerifier(const CSMWorld::UniversalId& reportId) { - int reportNumber = reportId.getType()==CSMWorld::UniversalId::Type_VerificationResults ? - reportId.getIndex() : mNextReportNumber++; + int reportNumber = reportId.getType() == CSMWorld::UniversalId::Type_VerificationResults ? reportId.getIndex() + : mNextReportNumber++; - if (mReports.find (reportNumber)==mReports.end()) - mReports.insert (std::make_pair (reportNumber, new ReportModel)); + if (mReports.find(reportNumber) == mReports.end()) + mReports.insert(std::make_pair(reportNumber, new ReportModel)); mActiveReports[CSMDoc::State_Verifying] = reportNumber; getVerifier()->start(); - return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, reportNumber); + return CSMWorld::UniversalId(CSMWorld::UniversalId::Type_VerificationResults, reportNumber); } CSMWorld::UniversalId CSMTools::Tools::newSearch() { - mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel (true, false))); + mReports.insert(std::make_pair(mNextReportNumber++, new ReportModel(true, false))); - return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Search, mNextReportNumber-1); + return CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Search, mNextReportNumber - 1); } -void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Search& search) +void CSMTools::Tools::runSearch(const CSMWorld::UniversalId& searchId, const Search& search) { mActiveReports[CSMDoc::State_Searching] = searchId.getIndex(); if (!mSearchOperation) { - mSearchOperation = new SearchOperation (mDocument); - mSearch.setOperation (mSearchOperation); + mSearchOperation = new SearchOperation(mDocument); + mSearch.setOperation(mSearchOperation); } - mSearchOperation->configure (search); + mSearchOperation->configure(search); mSearch.start(); } -void CSMTools::Tools::runMerge (std::unique_ptr target) +void CSMTools::Tools::runMerge(std::unique_ptr target) { // not setting an active report, because merge does not produce messages if (!mMergeOperation) { - mMergeOperation = new MergeOperation (mDocument, mEncoding); - mMerge.setOperation (mMergeOperation); - connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)), - this, SIGNAL (mergeDone (CSMDoc::Document*))); + mMergeOperation = new MergeOperation(mDocument, mEncoding); + mMerge.setOperation(mMergeOperation); + connect(mMergeOperation, &MergeOperation::mergeDone, this, &Tools::mergeDone); } target->flagAsDirty(); - mMergeOperation->setTarget (std::move(target)); + mMergeOperation->setTarget(std::move(target)); mMerge.start(); } -void CSMTools::Tools::abortOperation (int type) +void CSMTools::Tools::abortOperation(int type) { - if (CSMDoc::OperationHolder *operation = get (type)) + if (CSMDoc::OperationHolder* operation = get(type)) operation->abort(); } int CSMTools::Tools::getRunningOperations() const { - static const int sOperations[] = - { - CSMDoc::State_Verifying, - CSMDoc::State_Searching, - CSMDoc::State_Merging, - -1 + static const int sOperations[] = { + CSMDoc::State_Verifying, + CSMDoc::State_Searching, + CSMDoc::State_Merging, + -1, }; int result = 0; - for (int i=0; sOperations[i]!=-1; ++i) - if (const CSMDoc::OperationHolder *operation = get (sOperations[i])) + for (int i = 0; sOperations[i] != -1; ++i) + if (const CSMDoc::OperationHolder* operation = get(sOperations[i])) if (operation->isRunning()) result |= sOperations[i]; return result; } -CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& id) +CSMTools::ReportModel* CSMTools::Tools::getReport(const CSMWorld::UniversalId& id) { - if (id.getType()!=CSMWorld::UniversalId::Type_VerificationResults && - id.getType()!=CSMWorld::UniversalId::Type_LoadErrorLog && - id.getType()!=CSMWorld::UniversalId::Type_Search) - throw std::logic_error ("invalid request for report model: " + id.toString()); + if (id.getType() != CSMWorld::UniversalId::Type_VerificationResults + && id.getType() != CSMWorld::UniversalId::Type_LoadErrorLog + && id.getType() != CSMWorld::UniversalId::Type_Search) + throw std::logic_error("invalid request for report model: " + id.toString()); - return mReports.at (id.getIndex()); + return mReports.at(id.getIndex()); } -void CSMTools::Tools::verifierMessage (const CSMDoc::Message& message, int type) +void CSMTools::Tools::verifierMessage(const CSMDoc::Message& message, int type) { - std::map::iterator iter = mActiveReports.find (type); + std::map::iterator iter = mActiveReports.find(type); - if (iter!=mActiveReports.end()) - mReports[iter->second]->add (message); + if (iter != mActiveReports.end()) + mReports[iter->second]->add(message); } diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index f544c8312..c9e8937c9 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -1,27 +1,27 @@ #ifndef CSM_TOOLS_TOOLS_H #define CSM_TOOLS_TOOLS_H -#include #include +#include + +#include #include #include -#include - #include "../doc/operationholder.hpp" namespace CSMWorld { class Data; - class UniversalId; } namespace CSMDoc { class Operation; class Document; + struct Message; } namespace CSMTools @@ -33,73 +33,72 @@ namespace CSMTools class Tools : public QObject { - Q_OBJECT + Q_OBJECT - CSMDoc::Document& mDocument; - CSMWorld::Data& mData; - CSMDoc::Operation *mVerifierOperation; - CSMDoc::OperationHolder mVerifier; - SearchOperation *mSearchOperation; - CSMDoc::OperationHolder mSearch; - MergeOperation *mMergeOperation; - CSMDoc::OperationHolder mMerge; - std::map mReports; - int mNextReportNumber; - std::map mActiveReports; // type, report number - ToUTF8::FromType mEncoding; + CSMDoc::Document& mDocument; + CSMWorld::Data& mData; + CSMDoc::Operation* mVerifierOperation; + CSMDoc::OperationHolder mVerifier; + SearchOperation* mSearchOperation; + CSMDoc::OperationHolder mSearch; + MergeOperation* mMergeOperation; + CSMDoc::OperationHolder mMerge; + std::map mReports; + int mNextReportNumber; + std::map mActiveReports; // type, report number + ToUTF8::FromType mEncoding; - // not implemented - Tools (const Tools&); - Tools& operator= (const Tools&); + // not implemented + Tools(const Tools&); + Tools& operator=(const Tools&); - CSMDoc::OperationHolder *getVerifier(); + CSMDoc::OperationHolder* getVerifier(); - CSMDoc::OperationHolder *get (int type); - ///< Returns a 0-pointer, if operation hasn't been used yet. + CSMDoc::OperationHolder* get(int type); + ///< Returns a 0-pointer, if operation hasn't been used yet. - const CSMDoc::OperationHolder *get (int type) const; - ///< Returns a 0-pointer, if operation hasn't been used yet. + const CSMDoc::OperationHolder* get(int type) const; + ///< Returns a 0-pointer, if operation hasn't been used yet. - public: + public: + Tools(CSMDoc::Document& document, ToUTF8::FromType encoding); - Tools (CSMDoc::Document& document, ToUTF8::FromType encoding); + virtual ~Tools(); - virtual ~Tools(); + /// \param reportId If a valid VerificationResults ID, run verifier for the + /// specified report instead of creating a new one. + /// + /// \return ID of the report for this verification run + CSMWorld::UniversalId runVerifier(const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); - /// \param reportId If a valid VerificationResults ID, run verifier for the - /// specified report instead of creating a new one. - /// - /// \return ID of the report for this verification run - CSMWorld::UniversalId runVerifier (const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); + /// Return ID of the report for this search. + CSMWorld::UniversalId newSearch(); - /// Return ID of the report for this search. - CSMWorld::UniversalId newSearch(); + void runSearch(const CSMWorld::UniversalId& searchId, const Search& search); - void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); + void runMerge(std::unique_ptr target); - void runMerge (std::unique_ptr target); + void abortOperation(int type); + ///< \attention The operation is not aborted immediately. - void abortOperation (int type); - ///< \attention The operation is not aborted immediately. + int getRunningOperations() const; - int getRunningOperations() const; + ReportModel* getReport(const CSMWorld::UniversalId& id); + ///< The ownership of the returned report is not transferred. - ReportModel *getReport (const CSMWorld::UniversalId& id); - ///< The ownership of the returned report is not transferred. + private slots: - private slots: + void verifierMessage(const CSMDoc::Message& message, int type); - void verifierMessage (const CSMDoc::Message& message, int type); + signals: - signals: + void progress(int current, int max, int type); - void progress (int current, int max, int type); + void done(int type, bool failed); - void done (int type, bool failed); - - /// \attention When this signal is emitted, *this hands over the ownership of the - /// document. This signal must be handled to avoid a leak. - void mergeDone (CSMDoc::Document *document); + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone(CSMDoc::Document* document); }; } diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp index fe9bc991d..ba99a33a2 100644 --- a/apps/opencs/model/tools/topicinfocheck.cpp +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -1,36 +1,54 @@ #include "topicinfocheck.hpp" #include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../prefs/state.hpp" #include "../world/infoselectwrapper.hpp" -CSMTools::TopicInfoCheckStage::TopicInfoCheckStage( - const CSMWorld::InfoCollection& topicInfos, - const CSMWorld::IdCollection& cells, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& factions, - const CSMWorld::IdCollection& gmsts, - const CSMWorld::IdCollection& globals, - const CSMWorld::IdCollection& journals, - const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& regions, - const CSMWorld::IdCollection &topics, - const CSMWorld::RefIdData& referencables, +CSMTools::TopicInfoCheckStage::TopicInfoCheckStage(const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection& topics, const CSMWorld::RefIdData& referencables, const CSMWorld::Resources& soundFiles) - : mTopicInfos(topicInfos), - mCells(cells), - mClasses(classes), - mFactions(factions), - mGameSettings(gmsts), - mGlobals(globals), - mJournals(journals), - mRaces(races), - mRegions(regions), - mTopics(topics), - mReferencables(referencables), - mSoundFiles(soundFiles) + : mTopicInfos(topicInfos) + , mCells(cells) + , mClasses(classes) + , mFactions(factions) + , mGameSettings(gmsts) + , mGlobals(globals) + , mJournals(journals) + , mRaces(races) + , mRegions(regions) + , mTopics(topics) + , mReferencables(referencables) + , mSoundFiles(soundFiles) { mIgnoreBaseRecords = false; } @@ -60,7 +78,7 @@ int CSMTools::TopicInfoCheckStage::setup() mCellNames.insert(regionRecord.get().mName); } // Default cell name - int index = mGameSettings.searchId("sDefaultCellname"); + const int index = mGameSettings.searchId(ESM::RefId::stringRefId("sDefaultCellname")); if (index != -1) { const CSMWorld::Record& gmstRecord = mGameSettings.getRecord(index); @@ -112,7 +130,7 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message if (!topicInfo.mCell.empty()) { - verifyCell(topicInfo.mCell, id, messages); + verifyCell(topicInfo.mCell.getRefIdString(), id, messages); } if (!topicInfo.mFaction.empty() && !topicInfo.mFactionLess) @@ -162,26 +180,29 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message // Verification functions -bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, - CSMDoc::Messages& messages) +bool CSMTools::TopicInfoCheckStage::verifyActor( + const ESM::RefId& actor, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { + const std::string& actorString = actor.getRefIdString(); CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); if (index.first == -1) { - messages.add(id, "Actor '" + actor + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Actor '" + actorString + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (mReferencables.getRecord(index).isDeleted()) { - messages.add(id, "Deleted actor '" + actor + "' is being referenced", "", CSMDoc::Message::Severity_Error); + messages.add( + id, "Deleted actor '" + actorString + "' is being referenced", "", CSMDoc::Message::Severity_Error); return false; } else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature) { CSMWorld::UniversalId tempId(index.second, actor); std::ostringstream stream; - stream << "Object '" << actor << "' has invalid type " << tempId.getTypeName() << " (an actor must be an NPC or a creature)"; + stream << "Object '" << actor << "' has invalid type " << tempId.getTypeName() + << " (an actor must be an NPC or a creature)"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; } @@ -189,8 +210,8 @@ bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const return true; } -bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, - CSMDoc::Messages& messages) +bool CSMTools::TopicInfoCheckStage::verifyCell( + const std::string& cell, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { if (mCellNames.find(cell) == mCellNames.end()) { @@ -201,8 +222,8 @@ bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CS return true; } -bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& factionName, int rank, const CSMWorld::UniversalId& id, - CSMDoc::Messages& messages) +bool CSMTools::TopicInfoCheckStage::verifyFactionRank( + const ESM::RefId& factionName, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { if (rank < -1) { @@ -214,7 +235,7 @@ bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& faction int index = mFactions.searchId(factionName); - const ESM::Faction &faction = mFactions.getRecord(index).get(); + const ESM::Faction& faction = mFactions.getRecord(index).get(); int limit = 0; for (; limit < 10; ++limit) @@ -236,19 +257,20 @@ bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& faction return true; } -bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, - CSMDoc::Messages& messages) +bool CSMTools::TopicInfoCheckStage::verifyItem( + const ESM::RefId& item, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { + const std::string& idString = item.getRefIdString(); CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); if (index.first == -1) { - messages.add(id, ("Item '" + item + "' does not exist"), "", CSMDoc::Message::Severity_Error); + messages.add(id, ("Item '" + idString + "' does not exist"), "", CSMDoc::Message::Severity_Error); return false; } else if (mReferencables.getRecord(index).isDeleted()) { - messages.add(id, ("Deleted item '" + item + "' is being referenced"), "", CSMDoc::Message::Severity_Error); + messages.add(id, ("Deleted item '" + idString + "' is being referenced"), "", CSMDoc::Message::Severity_Error); return false; } else @@ -274,7 +296,8 @@ bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CS { CSMWorld::UniversalId tempId(index.second, item); std::ostringstream stream; - stream << "Object '" << item << "' has invalid type " << tempId.getTypeName() << " (an item can be a potion, an armor piece, a book and so on)"; + stream << "Object '" << item << "' has invalid type " << tempId.getTypeName() + << " (an item can be a potion, an armor piece, a book and so on)"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; } @@ -284,8 +307,8 @@ bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CS return true; } -bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::SelectStruct& select, - const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +bool CSMTools::TopicInfoCheckStage::verifySelectStruct( + const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::ConstInfoSelectWrapper infoCondition(select); @@ -301,13 +324,27 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::Sele switch (select.mValue.getType()) { - case ESM::VT_None: stream << "None"; break; - case ESM::VT_Short: stream << "Short"; break; - case ESM::VT_Int: stream << "Int"; break; - case ESM::VT_Long: stream << "Long"; break; - case ESM::VT_Float: stream << "Float"; break; - case ESM::VT_String: stream << "String"; break; - default: stream << "unknown"; break; + case ESM::VT_None: + stream << "None"; + break; + case ESM::VT_Short: + stream << "Short"; + break; + case ESM::VT_Int: + stream << "Int"; + break; + case ESM::VT_Long: + stream << "Long"; + break; + case ESM::VT_Float: + stream << "Float"; + break; + case ESM::VT_String: + stream << "String"; + break; + default: + stream << "unknown"; + break; } stream << " type"; @@ -316,58 +353,60 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::Sele } else if (infoCondition.conditionIsAlwaysTrue()) { - messages.add(id, "Condition '" + infoCondition.toString() + "' is always true", "", CSMDoc::Message::Severity_Warning); + messages.add( + id, "Condition '" + infoCondition.toString() + "' is always true", "", CSMDoc::Message::Severity_Warning); return false; } else if (infoCondition.conditionIsNeverTrue()) { - messages.add(id, "Condition '" + infoCondition.toString() + "' is never true", "", CSMDoc::Message::Severity_Warning); + messages.add( + id, "Condition '" + infoCondition.toString() + "' is never true", "", CSMDoc::Message::Severity_Warning); return false; } // Id checks - if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global && - !verifyId(infoCondition.getVariableName(), mGlobals, id, messages)) + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global + && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mGlobals, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal && - !verifyId(infoCondition.getVariableName(), mJournals, id, messages)) + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal + && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mJournals, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item && - !verifyItem(infoCondition.getVariableName(), id, messages)) + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item + && !verifyItem(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead && - !verifyActor(infoCondition.getVariableName(), id, messages)) + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead + && !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId && - !verifyActor(infoCondition.getVariableName(), id, messages)) + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId + && !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction && - !verifyId(infoCondition.getVariableName(), mFactions, id, messages)) + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction + && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mFactions, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass && - !verifyId(infoCondition.getVariableName(), mClasses, id, messages)) + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass + && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mClasses, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace && - !verifyId(infoCondition.getVariableName(), mRaces, id, messages)) + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace + && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mRaces, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell && - !verifyCell(infoCondition.getVariableName(), id, messages)) + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell + && !verifyCell(infoCondition.getVariableName(), id, messages)) { return false; } @@ -375,8 +414,8 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::Sele return true; } -bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const CSMWorld::UniversalId& id, - CSMDoc::Messages& messages) +bool CSMTools::TopicInfoCheckStage::verifySound( + const std::string& sound, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { if (mSoundFiles.searchId(sound) == -1) { @@ -388,19 +427,23 @@ bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const } template -bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMWorld::IdCollection& collection, +bool CSMTools::TopicInfoCheckStage::verifyId(const ESM::RefId& name, const CSMWorld::IdCollection& collection, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { int index = collection.searchId(name); if (index == -1) { - messages.add(id, T::getRecordType() + " '" + name + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, std::string(T::getRecordType()) + " '" + name.getRefIdString() + "' does not exist", "", + CSMDoc::Message::Severity_Error); return false; } else if (collection.getRecord(index).isDeleted()) { - messages.add(id, "Deleted " + T::getRecordType() + " record '" + name + "' is being referenced", "", CSMDoc::Message::Severity_Error); + messages.add(id, + "Deleted " + std::string(T::getRecordType()) + " record '" + name.getRefIdString() + + "' is being referenced", + "", CSMDoc::Message::Severity_Error); return false; } diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp index b9dbdc153..1bb2fc61d 100644 --- a/apps/opencs/model/tools/topicinfocheck.hpp +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -2,42 +2,53 @@ #define CSM_TOOLS_TOPICINFOCHECK_HPP #include +#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include -#include "../world/cell.hpp" #include "../world/idcollection.hpp" -#include "../world/infocollection.hpp" -#include "../world/refiddata.hpp" -#include "../world/resources.hpp" #include "../doc/stage.hpp" +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class InfoCollection; + class RefIdData; + class Resources; + struct Cell; +} + +namespace ESM +{ + struct Class; + struct Dialogue; + struct Faction; + struct GameSetting; + struct Global; + struct Race; + struct Region; +} + namespace CSMTools { /// \brief VerifyStage: check topics class TopicInfoCheckStage : public CSMDoc::Stage { public: - - TopicInfoCheckStage( - const CSMWorld::InfoCollection& topicInfos, - const CSMWorld::IdCollection& cells, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& factions, - const CSMWorld::IdCollection& gmsts, - const CSMWorld::IdCollection& globals, - const CSMWorld::IdCollection& journals, - const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& regions, - const CSMWorld::IdCollection& topics, - const CSMWorld::RefIdData& referencables, + TopicInfoCheckStage(const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection& topics, const CSMWorld::RefIdData& referencables, const CSMWorld::Resources& soundFiles); int setup() override; @@ -47,7 +58,6 @@ namespace CSMTools ///< Messages resulting from this stage will be appended to \a messages private: - const CSMWorld::InfoCollection& mTopicInfos; const CSMWorld::IdCollection& mCells; @@ -63,22 +73,22 @@ namespace CSMTools const CSMWorld::RefIdData& mReferencables; const CSMWorld::Resources& mSoundFiles; - std::set mCellNames; + std::set mCellNames; bool mIgnoreBaseRecords; // These return false when not successful and write an error - bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); - bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); - bool verifyFactionRank(const std::string& name, int rank, const CSMWorld::UniversalId& id, - CSMDoc::Messages& messages); - bool verifyItem(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); - bool verifySelectStruct(const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, - CSMDoc::Messages& messages); + bool verifyActor(const ESM::RefId& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyFactionRank( + const ESM::RefId& name, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyItem(const ESM::RefId& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifySelectStruct( + const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); template - bool verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + bool verifyId(const ESM::RefId& name, const CSMWorld::IdCollection& collection, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); }; } diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 8558aa9bc..e57ad648b 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -1,17 +1,32 @@ #include "actoradapter.hpp" -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include #include #include "data.hpp" namespace CSMWorld { - const std::string& ActorAdapter::RaceData::getId() const + const ESM::RefId& ActorAdapter::RaceData::getId() const { return mId; } @@ -41,60 +56,60 @@ namespace CSMWorld } } - const std::string& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const + const ESM::RefId& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const { return mFemaleParts[ESM::getMeshPart(index)]; } - const std::string& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const + const ESM::RefId& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const { return mMaleParts[ESM::getMeshPart(index)]; } - bool ActorAdapter::RaceData::hasDependency(const std::string& id) const + bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); } - void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const std::string& partId) + void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const ESM::RefId& partId) { mFemaleParts[index] = partId; addOtherDependency(partId); } - void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const std::string& partId) + void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const ESM::RefId& partId) { mMaleParts[index] = partId; addOtherDependency(partId); } - void ActorAdapter::RaceData::addOtherDependency(const std::string& id) + void ActorAdapter::RaceData::addOtherDependency(const ESM::RefId& id) { - if (!id.empty()) mDependencies.emplace(id); + if (!id.empty()) + mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset_data(const std::string& id, bool isBeast) + void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, bool isBeast) { mId = id; mIsBeast = isBeast; for (auto& str : mFemaleParts) - str.clear(); + str = ESM::RefId(); for (auto& str : mMaleParts) - str.clear(); + str = ESM::RefId(); mDependencies.clear(); // Mark self as a dependency addOtherDependency(id); } - ActorAdapter::ActorData::ActorData() { mCreature = false; mFemale = false; } - const std::string& ActorAdapter::ActorData::getId() const + const ESM::RefId& ActorAdapter::ActorData::getId() const { return mId; } @@ -121,7 +136,7 @@ namespace CSMWorld return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); } - const std::string ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const + ESM::RefId ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { auto it = mParts.find(index); if (it == mParts.end()) @@ -131,27 +146,25 @@ namespace CSMWorld if (mFemale) { // Note: we should use male parts for females as fallback - const std::string femalePart = mRaceData->getFemalePart(index); - if (!femalePart.empty()) + if (const ESM::RefId femalePart = mRaceData->getFemalePart(index); !femalePart.empty()) return femalePart; } return mRaceData->getMalePart(index); } - return ""; + return {}; } - const std::string& partName = it->second.first; - return partName; + return it->second.first; } - bool ActorAdapter::ActorData::hasDependency(const std::string& id) const + bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); } - void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId, int priority) + void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const ESM::RefId& partId, int priority) { auto it = mParts.find(index); if (it != mParts.end()) @@ -164,12 +177,14 @@ namespace CSMWorld addOtherDependency(partId); } - void ActorAdapter::ActorData::addOtherDependency(const std::string& id) + void ActorAdapter::ActorData::addOtherDependency(const ESM::RefId& id) { - if (!id.empty()) mDependencies.emplace(id); + if (!id.empty()) + mDependencies.emplace(id); } - void ActorAdapter::ActorData::reset_data(const std::string& id, const std::string& skeleton, bool isCreature, bool isFemale, RaceDataPtr raceData) + void ActorAdapter::ActorData::reset_data( + const ESM::RefId& id, const std::string& skeleton, bool isCreature, bool isFemale, RaceDataPtr raceData) { mId = id; mCreature = isCreature; @@ -181,10 +196,10 @@ namespace CSMWorld // Mark self and race as a dependency addOtherDependency(id); - if (raceData) addOtherDependency(raceData->getId()); + if (raceData) + addOtherDependency(raceData->getId()); } - ActorAdapter::ActorAdapter(Data& data) : mReferenceables(data.getReferenceables()) , mRaces(data.getRaces()) @@ -192,31 +207,24 @@ namespace CSMWorld { // Setup qt slots and signals QAbstractItemModel* refModel = data.getTableModel(UniversalId::Type_Referenceable); - connect(refModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), - this, SLOT(handleReferenceablesInserted(const QModelIndex&, int, int))); - connect(refModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), - this, SLOT(handleReferenceableChanged(const QModelIndex&, const QModelIndex&))); - connect(refModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), - this, SLOT(handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int))); + connect(refModel, &QAbstractItemModel::rowsInserted, this, &ActorAdapter::handleReferenceablesInserted); + connect(refModel, &QAbstractItemModel::dataChanged, this, &ActorAdapter::handleReferenceableChanged); + connect(refModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, + &ActorAdapter::handleReferenceablesAboutToBeRemoved); QAbstractItemModel* raceModel = data.getTableModel(UniversalId::Type_Race); - connect(raceModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), - this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int))); - connect(raceModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), - this, SLOT(handleRaceChanged(const QModelIndex&, const QModelIndex&))); - connect(raceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), - this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int))); + connect(raceModel, &QAbstractItemModel::rowsInserted, this, &ActorAdapter::handleRacesAboutToBeRemoved); + connect(raceModel, &QAbstractItemModel::dataChanged, this, &ActorAdapter::handleRaceChanged); + connect(raceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ActorAdapter::handleRacesAboutToBeRemoved); QAbstractItemModel* partModel = data.getTableModel(UniversalId::Type_BodyPart); - connect(partModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), - this, SLOT(handleBodyPartsInserted(const QModelIndex&, int, int))); - connect(partModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), - this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&))); - connect(partModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), - this, SLOT(handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int))); + connect(partModel, &QAbstractItemModel::rowsInserted, this, &ActorAdapter::handleBodyPartsInserted); + connect(partModel, &QAbstractItemModel::dataChanged, this, &ActorAdapter::handleBodyPartChanged); + connect( + partModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ActorAdapter::handleBodyPartsAboutToBeRemoved); } - ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const std::string& id) + ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const ESM::RefId& id) { // Return cached actor data if it exists ActorDataPtr data = mCachedActors.get(id); @@ -226,7 +234,7 @@ namespace CSMWorld } // Create the actor data - data.reset(new ActorData()); + data = std::make_shared(); setupActor(id, data); mCachedActors.insert(id, data); return data; @@ -239,7 +247,7 @@ namespace CSMWorld { for (int row = start; row <= end; ++row) { - std::string refId = mReferenceables.getId(row); + auto refId = mReferenceables.getId(row); markDirtyDependency(refId); } } @@ -260,7 +268,7 @@ namespace CSMWorld // Handle each record for (int row = start; row <= end; ++row) { - std::string refId = mReferenceables.getId(row); + auto refId = mReferenceables.getId(row); markDirtyDependency(refId); } @@ -275,7 +283,7 @@ namespace CSMWorld { for (int row = start; row <= end; ++row) { - std::string refId = mReferenceables.getId(row); + auto refId = mReferenceables.getId(row); markDirtyDependency(refId); } } @@ -294,7 +302,7 @@ namespace CSMWorld { for (int row = start; row <= end; ++row) { - std::string raceId = mReferenceables.getId(row); + auto raceId = mReferenceables.getId(row); markDirtyDependency(raceId); } } @@ -314,7 +322,7 @@ namespace CSMWorld for (int row = start; row <= end; ++row) { - std::string raceId = mRaces.getId(row); + auto raceId = mRaces.getId(row); markDirtyDependency(raceId); } @@ -329,7 +337,7 @@ namespace CSMWorld { for (int row = start; row <= end; ++row) { - std::string raceId = mRaces.getId(row); + auto raceId = mRaces.getId(row); markDirtyDependency(raceId); } } @@ -355,7 +363,7 @@ namespace CSMWorld markDirtyDependency(record.get().mRace); } - std::string partId = mBodyParts.getId(row); + auto partId = mBodyParts.getId(row); markDirtyDependency(partId); } } @@ -383,7 +391,7 @@ namespace CSMWorld } // Update entries with a tracked dependency - std::string partId = mBodyParts.getId(row); + auto partId = mBodyParts.getId(row); markDirtyDependency(partId); } @@ -398,7 +406,7 @@ namespace CSMWorld { for (int row = start; row <= end; ++row) { - std::string partId = mBodyParts.getId(row); + auto partId = mBodyParts.getId(row); markDirtyDependency(partId); } } @@ -417,25 +425,21 @@ namespace CSMWorld return index; } - bool ActorAdapter::is1stPersonPart(const std::string& name) const - { - return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; - } - - ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const std::string& id) + ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const ESM::RefId& id) { // Return cached race data if it exists RaceDataPtr data = mCachedRaces.get(id); - if (data) return data; + if (data) + return data; // Create the race data - data.reset(new RaceData()); + data = std::make_shared(); setupRace(id, data); mCachedRaces.insert(id, data); return data; } - void ActorAdapter::setupActor(const std::string& id, ActorDataPtr data) + void ActorAdapter::setupActor(const ESM::RefId& id, ActorDataPtr data) { int index = mReferenceables.searchId(id); if (index == -1) @@ -477,7 +481,7 @@ namespace CSMWorld } } - void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data) + void ActorAdapter::setupRace(const ESM::RefId& id, RaceDataPtr data) { int index = mRaces.searchId(id); if (index == -1) @@ -501,7 +505,6 @@ namespace CSMWorld // Setup body parts for (int i = 0; i < mBodyParts.getSize(); ++i) { - std::string partId = mBodyParts.getId(i); auto& partRecord = mBodyParts.getRecord(i); if (partRecord.isDeleted()) @@ -511,17 +514,19 @@ namespace CSMWorld } auto& part = partRecord.get(); - if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) + if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !ESM::isFirstPersonBodyPart(part)) { - auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; + auto type = (ESM::BodyPart::MeshPart)part.mData.mPart; bool female = part.mData.mFlags & ESM::BodyPart::BPF_Female; - if (female) data->setFemalePart(type, part.mId); - else data->setMalePart(type, part.mId); + if (female) + data->setFemalePart(type, part.mId); + else + data->setMalePart(type, part.mId); } } } - void ActorAdapter::setupNpc(const std::string& id, ActorDataPtr data) + void ActorAdapter::setupNpc(const ESM::RefId& id, ActorDataPtr data) { // Common setup, record is known to exist and is not deleted int index = mReferenceables.searchId(id); @@ -537,13 +542,14 @@ namespace CSMWorld // Add inventory items for (auto& item : npc.mInventory.mList) { - if (item.mCount <= 0) continue; - std::string itemId = item.mItem; + if (item.mCount <= 0) + continue; + auto itemId = item.mItem; addNpcItem(itemId, data); } } - void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data) + void ActorAdapter::addNpcItem(const ESM::RefId& itemId, ActorDataPtr data) { int index = mReferenceables.searchId(itemId); if (index == -1) @@ -565,8 +571,8 @@ namespace CSMWorld auto addParts = [&](const std::vector& list, int priority) { for (auto& part : list) { - std::string partId; - auto partType = (ESM::PartReferenceType) part.mPart; + ESM::RefId partId; + auto partType = (ESM::PartReferenceType)part.mPart; if (data->isFemale()) partId = part.mFemale; @@ -577,7 +583,7 @@ namespace CSMWorld // An another vanilla quirk: hide hairs if an item replaces Head part if (partType == ESM::PRT_Head) - data->setPart(ESM::PRT_Hair, "", priority); + data->setPart(ESM::PRT_Hair, ESM::RefId(), priority); } }; @@ -598,15 +604,13 @@ namespace CSMWorld std::vector parts; if (clothing.mData.mType == ESM::Clothing::Robe) { - parts = { - ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, - ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, - ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass - }; + parts = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, + ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, ESM::PRT_RForearm, ESM::PRT_LForearm, + ESM::PRT_Cuirass }; } else if (clothing.mData.mType == ESM::Clothing::Skirt) { - parts = {ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg}; + parts = { ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg }; } std::vector reservedList; @@ -626,7 +630,7 @@ namespace CSMWorld } } - void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data) + void ActorAdapter::setupCreature(const ESM::RefId& id, ActorDataPtr data) { // Record is known to exist and is not deleted int index = mReferenceables.searchId(id); @@ -635,7 +639,7 @@ namespace CSMWorld data->reset_data(id, creature.mModel, true); } - void ActorAdapter::markDirtyDependency(const std::string& dep) + void ActorAdapter::markDirtyDependency(const ESM::RefId& dep) { for (auto raceIt : mCachedRaces) { diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 912a6bcb3..9747f448a 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -3,16 +3,19 @@ #include #include +#include +#include +#include #include +#include -#include #include +#include -#include -#include +#include +#include #include -#include "refidcollection.hpp" #include "idcollection.hpp" namespace ESM @@ -23,6 +26,7 @@ namespace ESM namespace CSMWorld { class Data; + class RefIdCollection; /// Adapts multiple collections to provide the data needed to render /// an npc or creature. @@ -30,14 +34,12 @@ namespace CSMWorld { Q_OBJECT public: - /// A list indexed by ESM::PartReferenceType - using ActorPartList = std::map>; + using ActorPartList = std::map>; /// A list indexed by ESM::BodyPart::MeshPart - using RacePartList = std::array; + using RacePartList = std::array; /// Tracks unique strings - using StringSet = std::unordered_set; - + using RefIdSet = std::unordered_set; /// Contains base race data shared between actors class RaceData @@ -46,34 +48,34 @@ namespace CSMWorld RaceData(); /// Retrieves the id of the race represented - const std::string& getId() const; + const ESM::RefId& getId() const; /// Checks if it's a beast race bool isBeast() const; /// Checks if a part could exist for the given type bool handlesPart(ESM::PartReferenceType type) const; /// Retrieves the associated body part - const std::string& getFemalePart(ESM::PartReferenceType index) const; + const ESM::RefId& getFemalePart(ESM::PartReferenceType index) const; /// Retrieves the associated body part - const std::string& getMalePart(ESM::PartReferenceType index) const; + const ESM::RefId& getMalePart(ESM::PartReferenceType index) const; /// Checks if the race has a data dependency - bool hasDependency(const std::string& id) const; + bool hasDependency(const ESM::RefId& id) const; /// Sets the associated part if it's empty and marks a dependency - void setFemalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); + void setFemalePart(ESM::BodyPart::MeshPart partIndex, const ESM::RefId& partId); /// Sets the associated part if it's empty and marks a dependency - void setMalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); + void setMalePart(ESM::BodyPart::MeshPart partIndex, const ESM::RefId& partId); /// Marks an additional dependency - void addOtherDependency(const std::string& id); + void addOtherDependency(const ESM::RefId& id); /// Clears parts and dependencies - void reset_data(const std::string& raceId, bool isBeast=false); + void reset_data(const ESM::RefId& raceId, bool isBeast = false); private: bool handles(ESM::PartReferenceType type) const; - std::string mId; + ESM::RefId mId; bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; - StringSet mDependencies; + RefIdSet mDependencies; }; using RaceDataPtr = std::shared_ptr; @@ -85,7 +87,7 @@ namespace CSMWorld ActorData(); /// Retrieves the id of the actor represented - const std::string& getId() const; + const ESM::RefId& getId() const; /// Checks if the actor is a creature bool isCreature() const; /// Checks if the actor is female @@ -93,37 +95,37 @@ namespace CSMWorld /// Returns the skeleton the actor should use for attaching parts to std::string getSkeleton() const; /// Retrieves the associated actor part - const std::string getPart(ESM::PartReferenceType index) const; + ESM::RefId getPart(ESM::PartReferenceType index) const; /// Checks if the actor has a data dependency - bool hasDependency(const std::string& id) const; + bool hasDependency(const ESM::RefId& id) const; /// Sets the actor part used and marks a dependency - void setPart(ESM::PartReferenceType partIndex, const std::string& partId, int priority); + void setPart(ESM::PartReferenceType partIndex, const ESM::RefId& partId, int priority); /// Marks an additional dependency for the actor - void addOtherDependency(const std::string& id); + void addOtherDependency(const ESM::RefId& id); /// Clears race, parts, and dependencies - void reset_data(const std::string& actorId, const std::string& skeleton="", bool isCreature=false, bool female=true, RaceDataPtr raceData=nullptr); + void reset_data(const ESM::RefId& actorId, const std::string& skeleton = "", bool isCreature = false, + bool female = true, RaceDataPtr raceData = nullptr); private: - std::string mId; + ESM::RefId mId; bool mCreature; bool mFemale; std::string mSkeletonOverride; RaceDataPtr mRaceData; ActorPartList mParts; - StringSet mDependencies; + RefIdSet mDependencies; }; using ActorDataPtr = std::shared_ptr; - ActorAdapter(Data& data); /// Obtains the shared data for a given actor - ActorDataPtr getActorData(const std::string& refId); + ActorDataPtr getActorData(const ESM::RefId& refId); signals: - void actorChanged(const std::string& refId); + void actorChanged(const ESM::RefId& refId); public slots: @@ -143,35 +145,33 @@ namespace CSMWorld void handleBodyPartsRemoved(const QModelIndex&, int, int); private: - ActorAdapter(const ActorAdapter&) = delete; ActorAdapter& operator=(const ActorAdapter&) = delete; QModelIndex getHighestIndex(QModelIndex) const; - bool is1stPersonPart(const std::string& id) const; - RaceDataPtr getRaceData(const std::string& raceId); + RaceDataPtr getRaceData(const ESM::RefId& raceId); - void setupActor(const std::string& id, ActorDataPtr data); - void setupRace(const std::string& id, RaceDataPtr data); + void setupActor(const ESM::RefId& id, ActorDataPtr data); + void setupRace(const ESM::RefId& id, RaceDataPtr data); - void setupNpc(const std::string& id, ActorDataPtr data); - void addNpcItem(const std::string& itemId, ActorDataPtr data); + void setupNpc(const ESM::RefId& id, ActorDataPtr data); + void addNpcItem(const ESM::RefId& itemId, ActorDataPtr data); - void setupCreature(const std::string& id, ActorDataPtr data); + void setupCreature(const ESM::RefId& id, ActorDataPtr data); - void markDirtyDependency(const std::string& dependency); + void markDirtyDependency(const ESM::RefId& dependency); void updateDirty(); RefIdCollection& mReferenceables; IdCollection& mRaces; IdCollection& mBodyParts; - Misc::WeakCache mCachedActors; // Key: referenceable id - Misc::WeakCache mCachedRaces; // Key: race id + Misc::WeakCache mCachedActors; // Key: referenceable id + Misc::WeakCache mCachedRaces; // Key: race id - StringSet mDirtyActors; // Actors that need updating - StringSet mDirtyRaces; // Races that need updating + RefIdSet mDirtyActors; // Actors that need updating + RefIdSet mDirtyRaces; // Races that need updating }; } diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 93f3c500d..edf3b0d40 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -2,15 +2,9 @@ #include -void CSMWorld::Cell::load (ESM::ESMReader &esm, bool &isDeleted) +void CSMWorld::Cell::load(ESM::ESMReader& esm, bool& isDeleted) { - ESM::Cell::load (esm, isDeleted, false); + ESM::Cell::load(esm, isDeleted, false); - mId = mName; - if (isExterior()) - { - std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); - } + mId = ESM::RefId::stringRefId(ESM::Cell::mId.toString()); } diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index 160610874..5d4aefbda 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -1,10 +1,14 @@ #ifndef CSM_WOLRD_CELL_H #define CSM_WOLRD_CELL_H -#include #include -#include +#include + +namespace ESM +{ + class ESMReader; +} namespace CSMWorld { @@ -14,10 +18,9 @@ namespace CSMWorld /// Exterior cell coordinates are encoded in the cell ID. struct Cell : public ESM::Cell { - std::string mId; - - void load (ESM::ESMReader &esm, bool &isDeleted); + ESM::RefId mId; + void load(ESM::ESMReader& esm, bool& isDeleted); }; } diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index af8c26d70..bb5c73fc5 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -1,19 +1,32 @@ #include "cellcoordinates.hpp" #include - -#include #include +#include -#include +#include + +#include +#include #include -CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} +CSMWorld::CellCoordinates::CellCoordinates() + : mX(0) + , mY(0) +{ +} -CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {} +CSMWorld::CellCoordinates::CellCoordinates(int x, int y) + : mX(x) + , mY(y) +{ +} -CSMWorld::CellCoordinates::CellCoordinates (const std::pair& coordinates) -: mX (coordinates.first), mY (coordinates.second) {} +CSMWorld::CellCoordinates::CellCoordinates(const std::pair& coordinates) + : mX(coordinates.first) + , mY(coordinates.second) +{ +} int CSMWorld::CellCoordinates::getX() const { @@ -25,30 +38,34 @@ int CSMWorld::CellCoordinates::getY() const return mY; } -CSMWorld::CellCoordinates CSMWorld::CellCoordinates::move (int x, int y) const +CSMWorld::CellCoordinates CSMWorld::CellCoordinates::move(int x, int y) const { - return CellCoordinates (mX + x, mY + y); + return CellCoordinates(mX + x, mY + y); } -std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) const +std::string CSMWorld::CellCoordinates::getId(const std::string& worldspace) const { // we ignore the worldspace for now, since there is only one (will change in 1.1) return generateId(mX, mY); } -std::string CSMWorld::CellCoordinates::generateId (int x, int y) +std::string CSMWorld::CellCoordinates::generateId(int x, int y) { std::string cellId = "#" + std::to_string(x) + " " + std::to_string(y); return cellId; } -bool CSMWorld::CellCoordinates::isExteriorCell (const std::string& id) +bool CSMWorld::CellCoordinates::isExteriorCell(const std::string& id) { - return (!id.empty() && id[0]=='#'); + return (!id.empty() && id[0] == '#'); } -std::pair CSMWorld::CellCoordinates::fromId ( - const std::string& id) +bool CSMWorld::CellCoordinates::isExteriorCell(const ESM::RefId& id) +{ + return id.startsWith("#"); +} + +std::pair CSMWorld::CellCoordinates::fromId(const std::string& id) { // no worldspace for now, needs to be changed for 1.1 if (isExteriorCell(id)) @@ -56,17 +73,17 @@ std::pair CSMWorld::CellCoordinates::fromId ( int x, y; char ignore; - std::istringstream stream (id); + std::istringstream stream(id); if (stream >> ignore >> x >> y) - return std::make_pair (CellCoordinates (x, y), true); + return std::make_pair(CellCoordinates(x, y), true); } - return std::make_pair (CellCoordinates(), false); + return std::make_pair(CellCoordinates(), false); } -std::pair CSMWorld::CellCoordinates::coordinatesToCellIndex (float x, float y) +std::pair CSMWorld::CellCoordinates::coordinatesToCellIndex(float x, float y) { - return std::make_pair (std::floor (x / Constants::CellSizeInUnits), std::floor (y / Constants::CellSizeInUnits)); + return std::make_pair(std::floor(x / Constants::CellSizeInUnits), std::floor(y / Constants::CellSizeInUnits)); } std::pair CSMWorld::CellCoordinates::toTextureCoords(const osg::Vec3d& worldPos) @@ -108,7 +125,8 @@ float CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(int vertexGlobal) int CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(int vertexGlobal) { - return static_cast(vertexGlobal - std::floor(static_cast(vertexGlobal) / (ESM::Land::LAND_SIZE - 1)) * (ESM::Land::LAND_SIZE - 1)); + return static_cast(vertexGlobal + - std::floor(static_cast(vertexGlobal) / (ESM::Land::LAND_SIZE - 1)) * (ESM::Land::LAND_SIZE - 1)); } std::string CSMWorld::CellCoordinates::textureGlobalToCellId(const std::pair& textureGlobal) @@ -125,28 +143,28 @@ std::string CSMWorld::CellCoordinates::vertexGlobalToCellId(const std::pairright.getX()) + if (left.getX() > right.getX()) return false; - return left.getY() -#include +namespace osg +{ + class Vec3d; +} + +namespace ESM +{ + class RefId; +} namespace CSMWorld { class CellCoordinates { - int mX; - int mY; + int mX; + int mY; - public: + public: + CellCoordinates(); - CellCoordinates(); + CellCoordinates(int x, int y); - CellCoordinates (int x, int y); + CellCoordinates(const std::pair& coordinates); - CellCoordinates (const std::pair& coordinates); + int getX() const; - int getX() const; + int getY() const; - int getY() const; + CellCoordinates move(int x, int y) const; + ///< Return a copy of *this, moved by the given offset. - CellCoordinates move (int x, int y) const; - ///< Return a copy of *this, moved by the given offset. + /// Generate cell id string from x and y coordinates + static std::string generateId(int x, int y); - ///Generate cell id string from x and y coordinates - static std::string generateId (int x, int y); + std::string getId(const std::string& worldspace) const; + ///< Return the ID for the cell at these coordinates. - std::string getId (const std::string& worldspace) const; - ///< Return the ID for the cell at these coordinates. + static bool isExteriorCell(const std::string& id); - static bool isExteriorCell (const std::string& id); + static bool isExteriorCell(const ESM::RefId& id); - /// \return first: CellCoordinates (or 0, 0 if cell does not have coordinates), - /// second: is cell paged? - /// - /// \note The worldspace part of \a id is ignored - static std::pair fromId (const std::string& id); + /// \return first: CellCoordinates (or 0, 0 if cell does not have coordinates), + /// second: is cell paged? + /// + /// \note The worldspace part of \a id is ignored + static std::pair fromId(const std::string& id); - /// \return cell coordinates such that given world coordinates are in it. - static std::pair coordinatesToCellIndex (float x, float y); + /// \return cell coordinates such that given world coordinates are in it. + static std::pair coordinatesToCellIndex(float x, float y); - ///Converts worldspace coordinates to global texture selection, taking in account the texture offset. - static std::pair toTextureCoords(const osg::Vec3d& worldPos); + /// Converts worldspace coordinates to global texture selection, taking in account the texture offset. + static std::pair toTextureCoords(const osg::Vec3d& worldPos); - ///Converts worldspace coordinates to global vertex selection. - static std::pair toVertexCoords(const osg::Vec3d& worldPos); + /// Converts worldspace coordinates to global vertex selection. + static std::pair toVertexCoords(const osg::Vec3d& worldPos); - ///Converts global texture coordinate X to worldspace coordinate, offset by 0.25f. - static float textureGlobalXToWorldCoords(int textureGlobal); + /// Converts global texture coordinate X to worldspace coordinate, offset by 0.25f. + static float textureGlobalXToWorldCoords(int textureGlobal); - ///Converts global texture coordinate Y to worldspace coordinate, offset by 0.25f. - static float textureGlobalYToWorldCoords(int textureGlobal); + /// Converts global texture coordinate Y to worldspace coordinate, offset by 0.25f. + static float textureGlobalYToWorldCoords(int textureGlobal); - ///Converts global vertex coordinate to worldspace coordinate - static float vertexGlobalToWorldCoords(int vertexGlobal); + /// Converts global vertex coordinate to worldspace coordinate + static float vertexGlobalToWorldCoords(int vertexGlobal); - ///Converts global vertex coordinate to local cell's heightmap coordinates - static int vertexGlobalToInCellCoords(int vertexGlobal); + /// Converts global vertex coordinate to local cell's heightmap coordinates + static int vertexGlobalToInCellCoords(int vertexGlobal); - ///Converts global texture coordinates to cell id - static std::string textureGlobalToCellId(const std::pair& textureGlobal); + /// Converts global texture coordinates to cell id + static std::string textureGlobalToCellId(const std::pair& textureGlobal); - ///Converts global vertex coordinates to cell id - static std::string vertexGlobalToCellId(const std::pair& vertexGlobal); + /// Converts global vertex coordinates to cell id + static std::string vertexGlobalToCellId(const std::pair& vertexGlobal); }; - bool operator== (const CellCoordinates& left, const CellCoordinates& right); - bool operator!= (const CellCoordinates& left, const CellCoordinates& right); - bool operator< (const CellCoordinates& left, const CellCoordinates& right); + bool operator==(const CellCoordinates& left, const CellCoordinates& right); + bool operator!=(const CellCoordinates& left, const CellCoordinates& right); + bool operator<(const CellCoordinates& left, const CellCoordinates& right); - std::ostream& operator<< (std::ostream& stream, const CellCoordinates& coordiantes); + std::ostream& operator<<(std::ostream& stream, const CellCoordinates& coordiantes); } -Q_DECLARE_METATYPE (CSMWorld::CellCoordinates) +Q_DECLARE_METATYPE(CSMWorld::CellCoordinates) #endif diff --git a/apps/opencs/model/world/cellselection.cpp b/apps/opencs/model/world/cellselection.cpp index c6988e488..e6b346be9 100644 --- a/apps/opencs/model/world/cellselection.cpp +++ b/apps/opencs/model/world/cellselection.cpp @@ -1,8 +1,11 @@ #include "cellselection.hpp" +#include "cellcoordinates.hpp" + #include -#include #include +#include +#include CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::begin() const { @@ -14,19 +17,19 @@ CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::end() const return mCells.end(); } -bool CSMWorld::CellSelection::add (const CellCoordinates& coordinates) +bool CSMWorld::CellSelection::add(const CellCoordinates& coordinates) { - return mCells.insert (coordinates).second; + return mCells.insert(coordinates).second; } -void CSMWorld::CellSelection::remove (const CellCoordinates& coordinates) +void CSMWorld::CellSelection::remove(const CellCoordinates& coordinates) { - mCells.erase (coordinates); + mCells.erase(coordinates); } -bool CSMWorld::CellSelection::has (const CellCoordinates& coordinates) const +bool CSMWorld::CellSelection::has(const CellCoordinates& coordinates) const { - return mCells.find (coordinates)!=end(); + return mCells.find(coordinates) != end(); } int CSMWorld::CellSelection::getSize() const @@ -37,12 +40,12 @@ int CSMWorld::CellSelection::getSize() const CSMWorld::CellCoordinates CSMWorld::CellSelection::getCentre() const { if (mCells.empty()) - throw std::logic_error ("call of getCentre on empty cell selection"); + throw std::logic_error("call of getCentre on empty cell selection"); double x = 0; double y = 0; - for (Iterator iter = begin(); iter!=end(); ++iter) + for (Iterator iter = begin(); iter != end(); ++iter) { x += iter->getX(); y += iter->getY(); @@ -54,14 +57,14 @@ CSMWorld::CellCoordinates CSMWorld::CellSelection::getCentre() const Iterator closest = begin(); double distance = std::numeric_limits::max(); - for (Iterator iter (begin()); iter!=end(); ++iter) + for (Iterator iter(begin()); iter != end(); ++iter) { double deltaX = x - iter->getX(); double deltaY = y - iter->getY(); - double delta = std::sqrt (deltaX * deltaX + deltaY * deltaY); + double delta = std::sqrt(deltaX * deltaX + deltaY * deltaY); - if (deltamove (x, y)); + for (Iterator iter = begin(); iter != end(); ++iter) + moved.insert(iter->move(x, y)); - mCells.swap (moved); + mCells.swap(moved); } diff --git a/apps/opencs/model/world/cellselection.hpp b/apps/opencs/model/world/cellselection.hpp index 042416a33..5a948aab9 100644 --- a/apps/opencs/model/world/cellselection.hpp +++ b/apps/opencs/model/world/cellselection.hpp @@ -5,7 +5,7 @@ #include -#include "cellcoordinates.hpp" +#include namespace CSMWorld { @@ -14,44 +14,41 @@ namespace CSMWorld /// \note The CellSelection does not specify the worldspace it applies to. class CellSelection { - public: + public: + typedef std::set Container; + typedef Container::const_iterator Iterator; - typedef std::set Container; - typedef Container::const_iterator Iterator; + private: + Container mCells; - private: + public: + Iterator begin() const; - Container mCells; + Iterator end() const; - public: + bool add(const CellCoordinates& coordinates); + ///< Ignored if the cell specified by \a coordinates is already part of the selection. + /// + /// \return Was a cell added to the collection? - Iterator begin() const; + void remove(const CellCoordinates& coordinates); + ///< ignored if the cell specified by \a coordinates is not part of the selection. - Iterator end() const; + bool has(const CellCoordinates& coordinates) const; + ///< \return Is the cell specified by \a coordinates part of the selection? - bool add (const CellCoordinates& coordinates); - ///< Ignored if the cell specified by \a coordinates is already part of the selection. - /// - /// \return Was a cell added to the collection? + int getSize() const; + ///< Return number of cells. - void remove (const CellCoordinates& coordinates); - ///< ignored if the cell specified by \a coordinates is not part of the selection. + CellCoordinates getCentre() const; + ///< Return the selected cell that is closest to the geometric centre of the selection. + /// + /// \attention This function must not be called on selections that are empty. - bool has (const CellCoordinates& coordinates) const; - ///< \return Is the cell specified by \a coordinates part of the selection? - - int getSize() const; - ///< Return number of cells. - - CellCoordinates getCentre() const; - ///< Return the selected cell that is closest to the geometric centre of the selection. - /// - /// \attention This function must not be called on selections that are empty. - - void move (int x, int y); + void move(int x, int y); }; } -Q_DECLARE_METATYPE (CSMWorld::CellSelection) +Q_DECLARE_METATYPE(CSMWorld::CellSelection) #endif diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 451ef9d0e..17207ae15 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -1,285 +1,301 @@ #ifndef CSM_WOLRD_COLLECTION_H #define CSM_WOLRD_COLLECTION_H -#include -#include #include #include +#include +#include +#include #include #include -#include +#include +#include +#include #include -#include +#include +#include -#include "columnbase.hpp" #include "collectionbase.hpp" +#include "columnbase.hpp" +#include "info.hpp" #include "land.hpp" #include "landtexture.hpp" +#include "record.hpp" #include "ref.hpp" namespace CSMWorld { - /// \brief Access to ID field in records - template - struct IdAccessor + inline std::pair parseInfoRefId(const ESM::RefId& infoId) { - void setId(ESXRecordT& record, const std::string& id) const; - const std::string getId (const ESXRecordT& record) const; - }; + const auto separator = infoId.getRefIdString().find('#'); + if (separator == std::string::npos) + throw std::runtime_error("Invalid info id: " + infoId.getRefIdString()); + const std::string_view view(infoId.getRefIdString()); + return { view.substr(0, separator), view.substr(separator + 1) }; + } - template - void IdAccessor::setId(ESXRecordT& record, const std::string& id) const + template + void setRecordId(const decltype(T::mId)& id, T& record) { record.mId = id; } - template - const std::string IdAccessor::getId (const ESXRecordT& record) const + inline void setRecordId(const ESM::RefId& id, Info& record) + { + record.mId = id; + const auto [topicId, originalId] = parseInfoRefId(id); + record.mTopicId = ESM::RefId::stringRefId(topicId); + record.mOriginalId = ESM::RefId::stringRefId(originalId); + } + + template + auto getRecordId(const T& record) { return record.mId; } - template<> - inline void IdAccessor::setId (Land& record, const std::string& id) const + inline void setRecordId(const ESM::RefId& id, Land& record) { - int x=0, y=0; + int x = 0; + int y = 0; - Land::parseUniqueRecordId(id, x, y); + Land::parseUniqueRecordId(id.getRefIdString(), x, y); record.mX = x; record.mY = y; } - template<> - inline void IdAccessor::setId (LandTexture& record, const std::string& id) const + inline ESM::RefId getRecordId(const Land& record) + { + return ESM::RefId::stringRefId(Land::createUniqueRecordId(record.mX, record.mY)); + } + + inline void setRecordId(const ESM::RefId& id, LandTexture& record) { int plugin = 0; int index = 0; - LandTexture::parseUniqueRecordId(id, plugin, index); + LandTexture::parseUniqueRecordId(id.getRefIdString(), plugin, index); record.mPluginIndex = plugin; record.mIndex = index; } - template<> - inline const std::string IdAccessor::getId (const Land& record) const + inline ESM::RefId getRecordId(const LandTexture& record) { - return Land::createUniqueRecordId(record.mX, record.mY); - } - - template<> - inline const std::string IdAccessor::getId (const LandTexture& record) const - { - return LandTexture::createUniqueRecordId(record.mPluginIndex, record.mIndex); + return ESM::RefId::stringRefId(LandTexture::createUniqueRecordId(record.mPluginIndex, record.mIndex)); } /// \brief Single-type record collection - template > + template class Collection : public CollectionBase { - public: + public: + typedef ESXRecordT ESXRecord; - typedef ESXRecordT ESXRecord; + private: + std::vector>> mRecords; + std::map mIndex; + std::vector*> mColumns; - private: + protected: + const std::vector>>& getRecords() const; - std::vector > mRecords; - std::map mIndex; - std::vector *> mColumns; + void reorderRowsImp(const std::vector& indexOrder); - // not implemented - Collection (const Collection&); - Collection& operator= (const Collection&); + bool reorderRowsImp(int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? - protected: + int cloneRecordImp(const ESM::RefId& origin, const ESM::RefId& dest, UniversalId::Type type); + ///< Returns the index of the clone. - const std::map& getIdMap() const; + int touchRecordImp(const ESM::RefId& id); + ///< Returns the index of the record on success, -1 on failure. - const std::vector >& getRecords() const; + public: + Collection() = default; + Collection(const Collection&) = delete; + Collection& operator=(const Collection&) = delete; - bool reorderRowsImp (int baseIndex, const std::vector& newOrder); - ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices - /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). - /// - /// \return Success? + ~Collection() override; - int cloneRecordImp (const std::string& origin, const std::string& dest, - UniversalId::Type type); - ///< Returns the index of the clone. + void add(const ESXRecordT& record); + ///< Add a new record (modified) - int touchRecordImp (const std::string& id); - ///< Returns the index of the record on success, -1 on failure. + int getSize() const override; - public: + ESM::RefId getId(int index) const override; - Collection(); + int getIndex(const ESM::RefId& id) const override; - virtual ~Collection(); + int getColumns() const override; - void add (const ESXRecordT& record); - ///< Add a new record (modified) + QVariant getData(int index, int column) const override; - int getSize() const override; + void setData(int index, int column, const QVariant& data) override; - std::string getId (int index) const override; + const ColumnBase& getColumn(int column) const override; - int getIndex (const std::string& id) const override; + void merge(); + ///< Merge modified into base. - int getColumns() const override; + void purge(); + ///< Remove records that are flagged as erased. - QVariant getData (int index, int column) const override; + void removeRows(int index, int count) override; - void setData (int index, int column, const QVariant& data) override; + void appendBlankRecord(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) override; + ///< \param type Will be ignored, unless the collection supports multiple record types - const ColumnBase& getColumn (int column) const override; + void cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) override; - virtual void merge(); - ///< Merge modified into base. + bool touchRecord(const ESM::RefId& id) override; + ///< Change the state of a record from base to modified, if it is not already. + /// \return True if the record was changed. - virtual void purge(); - ///< Remove records that are flagged as erased. + int searchId(const ESM::RefId& id) const override; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) - void removeRows (int index, int count) override; + void replace(int index, std::unique_ptr record) override; + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. - void appendBlankRecord (const std::string& id, - UniversalId::Type type = UniversalId::Type_None) override; - ///< \param type Will be ignored, unless the collection supports multiple record types + void appendRecord(std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) override; + ///< If the record type does not match, an exception is thrown. + ///< \param type Will be ignored, unless the collection supports multiple record types - void cloneRecord(const std::string& origin, - const std::string& destination, - const UniversalId::Type type) override; + const Record& getRecord(const ESM::RefId& id) const override; - bool touchRecord(const std::string& id) override; - ///< Change the state of a record from base to modified, if it is not already. - /// \return True if the record was changed. + const Record& getRecord(int index) const override; - int searchId (const std::string& id) const override; - ////< Search record with \a id. - /// \return index of record (if found) or -1 (not found) + int getAppendIndex(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) const override; + ///< \param type Will be ignored, unless the collection supports multiple record types - void replace (int index, const RecordBase& record) override; - ///< If the record type does not match, an exception is thrown. - /// - /// \attention \a record must not change the ID. + std::vector getIds(bool listDeleted = true) const override; + ///< Return a sorted collection of all IDs + /// + /// \param listDeleted include deleted record in the list - void appendRecord (const RecordBase& record, - UniversalId::Type type = UniversalId::Type_None) override; - ///< If the record type does not match, an exception is thrown. - ///< \param type Will be ignored, unless the collection supports multiple record types + virtual void insertRecord( + std::unique_ptr record, int index, UniversalId::Type type = UniversalId::Type_None); + ///< Insert record before index. + /// + /// If the record type does not match, an exception is thrown. + /// + /// If the index is invalid either generally (by being out of range) or for the particular + /// record, an exception is thrown. - const Record& getRecord (const std::string& id) const override; + bool reorderRows(int baseIndex, const std::vector& newOrder) override; + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? - const Record& getRecord (int index) const override; + void addColumn(Column* column); - int getAppendIndex (const std::string& id, - UniversalId::Type type = UniversalId::Type_None) const override; - ///< \param type Will be ignored, unless the collection supports multiple record types + void setRecord(int index, std::unique_ptr> record); + ///< \attention This function must not change the ID. - std::vector getIds (bool listDeleted = true) const override; - ///< Return a sorted collection of all IDs - /// - /// \param listDeleted include deleted record in the list - - virtual void insertRecord (const RecordBase& record, int index, - UniversalId::Type type = UniversalId::Type_None); - ///< Insert record before index. - /// - /// If the record type does not match, an exception is thrown. - /// - /// If the index is invalid either generally (by being out of range) or for the particular - /// record, an exception is thrown. - - bool reorderRows (int baseIndex, const std::vector& newOrder) override; - ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices - /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). - /// - /// \return Success? - - void addColumn (Column *column); - - void setRecord (int index, const Record& record); - ///< \attention This function must not change the ID. - - NestableColumn *getNestableColumn (int column) const; + NestableColumn* getNestableColumn(int column) const; }; - template - const std::map& Collection::getIdMap() const - { - return mIndex; - } - - template - const std::vector >& Collection::getRecords() const + template + const std::vector>>& Collection::getRecords() const { return mRecords; } - template - bool Collection::reorderRowsImp (int baseIndex, - const std::vector& newOrder) + template + void Collection::reorderRowsImp(const std::vector& indexOrder) + { + assert(indexOrder.size() == mRecords.size()); + assert(std::unordered_set(indexOrder.begin(), indexOrder.end()).size() == indexOrder.size()); + std::vector>> orderedRecords; + for (const int index : indexOrder) + { + mIndex.at(mRecords[index]->get().mId) = static_cast(orderedRecords.size()); + orderedRecords.push_back(std::move(mRecords[index])); + } + mRecords = std::move(orderedRecords); + } + + template + bool Collection::reorderRowsImp(int baseIndex, const std::vector& newOrder) { if (!newOrder.empty()) { - int size = static_cast (newOrder.size()); + int size = static_cast(newOrder.size()); // check that all indices are present - std::vector test (newOrder); - std::sort (test.begin(), test.end()); - if (*test.begin()!=0 || *--test.end()!=size-1) + std::vector test(newOrder); + std::sort(test.begin(), test.end()); + if (*test.begin() != 0 || *--test.end() != size - 1) return false; // reorder records - std::vector > buffer (size); + std::vector>> buffer(size); - for (int i=0; isetModified(buffer[newOrder[i]]->get()); } - std::copy (buffer.begin(), buffer.end(), mRecords.begin()+baseIndex); + std::move(buffer.begin(), buffer.end(), mRecords.begin() + baseIndex); // adjust index - for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); - ++iter) - if (iter->second>=baseIndex && iter->secondsecond = newOrder.at (iter->second-baseIndex)+baseIndex; + for (auto& [id, index] : mIndex) + if (index >= baseIndex && index < baseIndex + size) + index = newOrder.at(index - baseIndex) + baseIndex; } return true; } - template - int Collection::cloneRecordImp(const std::string& origin, - const std::string& destination, UniversalId::Type type) + template + int Collection::cloneRecordImp( + const ESM::RefId& origin, const ESM::RefId& destination, UniversalId::Type type) { - Record copy; - copy.mModified = getRecord(origin).get(); - copy.mState = RecordBase::State_ModifiedOnly; - IdAccessorT().setId(copy.get(), destination); + auto copy = std::make_unique>(); + copy->mModified = getRecord(origin).get(); + copy->mState = RecordBase::State_ModifiedOnly; + setRecordId(destination, copy->get()); - if (type == UniversalId::Type_Reference) { - CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified; - ptr->mRefNum.mIndex = 0; + if constexpr (std::is_same_v) + { + if (type == UniversalId::Type_Reference) + { + CSMWorld::CellRef* ptr = (CSMWorld::CellRef*)©->mModified; + ptr->mRefNum.mIndex = 0; + } } - int index = getAppendIndex(destination, type); - insertRecord(copy, getAppendIndex(destination, type)); + if constexpr (std::is_same_v) + { + copy->mModified.mStringId = copy->mModified.mId.getRefIdString(); + } + + const int index = getAppendIndex(destination, type); + insertRecord(std::move(copy), getAppendIndex(destination, type)); return index; } - template - int Collection::touchRecordImp(const std::string& id) + template + int Collection::touchRecordImp(const ESM::RefId& id) { - int index = getIndex(id); - Record& record = mRecords.at(index); + const int index = getIndex(id); + Record& record = *mRecords.at(index); if (record.isDeleted()) - { - throw std::runtime_error("attempt to touch deleted record"); - } + throw std::runtime_error("attempt to touch deleted record from collection of " + + std::string(ESXRecordT::getRecordType()) + ": " + id.toDebugString()); if (!record.isModified()) { @@ -290,176 +306,175 @@ namespace CSMWorld return -1; } - template - void Collection::cloneRecord(const std::string& origin, - const std::string& destination, const UniversalId::Type type) + template + void Collection::cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) { cloneRecordImp(origin, destination, type); } - template<> - inline void Collection >::cloneRecord(const std::string& origin, - const std::string& destination, const UniversalId::Type type) + template <> + inline void Collection::cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) { - int index = cloneRecordImp(origin, destination, type); - mRecords.at(index).get().mPlugin = 0; + const int index = cloneRecordImp(origin, destination, type); + mRecords.at(index)->get().setPlugin(0); } - template - bool Collection::touchRecord(const std::string& id) + template + bool Collection::touchRecord(const ESM::RefId& id) { return touchRecordImp(id) != -1; } - template<> - inline bool Collection >::touchRecord(const std::string& id) + template <> + inline bool Collection::touchRecord(const ESM::RefId& id) { - int index = touchRecordImp(id); + const int index = touchRecordImp(id); if (index >= 0) { - mRecords.at(index).get().mPlugin = 0; + mRecords.at(index)->get().setPlugin(0); return true; } return false; } - template - Collection::Collection() - {} - - template - Collection::~Collection() + template + Collection::~Collection() { - for (typename std::vector *>::iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter) + for (typename std::vector*>::iterator iter(mColumns.begin()); iter != mColumns.end(); ++iter) delete *iter; } - template - void Collection::add (const ESXRecordT& record) + template + void Collection::add(const ESXRecordT& record) { - std::string id = Misc::StringUtils::lowerCase (IdAccessorT().getId (record)); + const ESM::RefId id = getRecordId(record); - std::map::iterator iter = mIndex.find (id); + auto iter = mIndex.find(id); - if (iter==mIndex.end()) + if (iter == mIndex.end()) { - Record record2; - record2.mState = Record::State_ModifiedOnly; - record2.mModified = record; + auto record2 = std::make_unique>(); + record2->mState = Record::State_ModifiedOnly; + record2->mModified = record; - insertRecord (record2, getAppendIndex (id)); + insertRecord(std::move(record2), getAppendIndex(id)); } else { - mRecords[iter->second].setModified (record); + mRecords[iter->second]->setModified(record); } } - template - int Collection::getSize() const + template + int Collection::getSize() const { return mRecords.size(); } - template - std::string Collection::getId (int index) const + template + ESM::RefId Collection::getId(int index) const { - return IdAccessorT().getId (mRecords.at (index).get()); + return getRecordId(mRecords.at(index)->get()); } - template - int Collection::getIndex (const std::string& id) const + template + int Collection::getIndex(const ESM::RefId& id) const { - int index = searchId (id); + int index = searchId(id); - if (index==-1) - throw std::runtime_error ("invalid ID: " + id); + if (index == -1) + throw std::runtime_error("ID is not found in collection of " + std::string(ESXRecordT::getRecordType()) + + " records: " + id.getRefIdString()); return index; } - template - int Collection::getColumns() const + template + int Collection::getColumns() const { return mColumns.size(); } - template - QVariant Collection::getData (int index, int column) const + template + QVariant Collection::getData(int index, int column) const { - return mColumns.at (column)->get (mRecords.at (index)); + return mColumns.at(column)->get(*mRecords.at(index)); } - template - void Collection::setData (int index, int column, const QVariant& data) + template + void Collection::setData(int index, int column, const QVariant& data) { - return mColumns.at (column)->set (mRecords.at (index), data); + return mColumns.at(column)->set(*mRecords.at(index), data); } - template - const ColumnBase& Collection::getColumn (int column) const + template + const ColumnBase& Collection::getColumn(int column) const { - return *mColumns.at (column); + return *mColumns.at(column); } - template - NestableColumn *Collection::getNestableColumn (int column) const + template + NestableColumn* Collection::getNestableColumn(int column) const { if (column < 0 || column >= static_cast(mColumns.size())) - throw std::runtime_error("column index out of range"); + throw std::runtime_error( + "column index out of range [0, " + std::to_string(mColumns.size()) + "): " + std::to_string(column)); - return mColumns.at (column); + return mColumns.at(column); } - template - void Collection::addColumn (Column *column) + template + void Collection::addColumn(Column* column) { - mColumns.push_back (column); + mColumns.push_back(column); } - template - void Collection::merge() + template + void Collection::merge() { - for (typename std::vector >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter) - iter->merge(); + for (typename std::vector>>::iterator iter(mRecords.begin()); + iter != mRecords.end(); ++iter) + (*iter)->merge(); purge(); } - template - void Collection::purge() + template + void Collection::purge() { int i = 0; - while (i (mRecords.size())) + while (i < static_cast(mRecords.size())) { - if (mRecords[i].isErased()) - removeRows (i, 1); + if (mRecords[i]->isErased()) + removeRows(i, 1); else ++i; } } - template - void Collection::removeRows (int index, int count) + template + void Collection::removeRows(int index, int count) { - mRecords.erase (mRecords.begin()+index, mRecords.begin()+index+count); + mRecords.erase(mRecords.begin() + index, mRecords.begin() + index + count); - typename std::map::iterator iter = mIndex.begin(); + auto iter = mIndex.begin(); - while (iter!=mIndex.end()) + while (iter != mIndex.end()) { - if (iter->second>=index) + if (iter->second >= index) { - if (iter->second>=index+count) + if (iter->second >= index + count) { iter->second -= count; ++iter; } else { - mIndex.erase (iter++); + iter = mIndex.erase(iter); } } else @@ -467,119 +482,121 @@ namespace CSMWorld } } - template - void Collection::appendBlankRecord (const std::string& id, - UniversalId::Type type) + template + void Collection::appendBlankRecord(const ESM::RefId& id, UniversalId::Type type) { ESXRecordT record; - IdAccessorT().setId(record, id); + setRecordId(id, record); record.blank(); - Record record2; - record2.mState = Record::State_ModifiedOnly; - record2.mModified = record; + if constexpr (std::is_same_v) + { + record.mStringId = record.mId.getRefIdString(); + } - insertRecord (record2, getAppendIndex (id, type), type); + auto record2 = std::make_unique>(); + record2->mState = Record::State_ModifiedOnly; + record2->mModified = record; + + insertRecord(std::move(record2), getAppendIndex(id, type), type); } - template - int Collection::searchId (const std::string& id) const + template + int Collection::searchId(const ESM::RefId& id) const { - std::string id2 = Misc::StringUtils::lowerCase(id); + const auto iter = mIndex.find(id); - std::map::const_iterator iter = mIndex.find (id2); - - if (iter==mIndex.end()) + if (iter == mIndex.end()) return -1; return iter->second; } - template - void Collection::replace (int index, const RecordBase& record) + template + void Collection::replace(int index, std::unique_ptr record) { - mRecords.at (index) = dynamic_cast&> (record); + std::unique_ptr> tmp(static_cast*>(record.release())); + mRecords.at(index) = std::move(tmp); } - template - void Collection::appendRecord (const RecordBase& record, - UniversalId::Type type) + template + void Collection::appendRecord(std::unique_ptr record, UniversalId::Type type) { - insertRecord (record, - getAppendIndex (IdAccessorT().getId ( - dynamic_cast&> (record).get()), type), type); + int index = getAppendIndex(getRecordId(static_cast*>(record.get())->get()), type); + insertRecord(std::move(record), index, type); } - template - int Collection::getAppendIndex (const std::string& id, - UniversalId::Type type) const + template + int Collection::getAppendIndex(const ESM::RefId& id, UniversalId::Type type) const { - return static_cast (mRecords.size()); + return static_cast(mRecords.size()); } - template - std::vector Collection::getIds (bool listDeleted) const + template + std::vector Collection::getIds(bool listDeleted) const { - std::vector ids; + std::vector ids; - for (typename std::map::const_iterator iter = mIndex.begin(); - iter!=mIndex.end(); ++iter) + for (auto iter = mIndex.begin(); iter != mIndex.end(); ++iter) { - if (listDeleted || !mRecords[iter->second].isDeleted()) - ids.push_back (IdAccessorT().getId (mRecords[iter->second].get())); + if (listDeleted || !mRecords[iter->second]->isDeleted()) + ids.push_back(getRecordId(mRecords[iter->second]->get())); } return ids; } - template - const Record& Collection::getRecord (const std::string& id) const + template + const Record& Collection::getRecord(const ESM::RefId& id) const { - int index = getIndex (id); - return mRecords.at (index); + int index = getIndex(id); + return *mRecords.at(index); } - template - const Record& Collection::getRecord (int index) const + template + const Record& Collection::getRecord(int index) const { - return mRecords.at (index); + return *mRecords.at(index); } - template - void Collection::insertRecord (const RecordBase& record, int index, - UniversalId::Type type) + template + void Collection::insertRecord(std::unique_ptr record, int index, UniversalId::Type type) { - if (index<0 || index>static_cast (mRecords.size())) - throw std::runtime_error ("index out of range"); + int size = static_cast(mRecords.size()); + if (index < 0 || index > size) + throw std::runtime_error("index out of range"); - const Record& record2 = dynamic_cast&> (record); + std::unique_ptr> record2(static_cast*>(record.release())); + ESM::RefId id = getRecordId(record2->get()); - mRecords.insert (mRecords.begin()+index, record2); + if (index == size) + mRecords.push_back(std::move(record2)); + else + mRecords.insert(mRecords.begin() + index, std::move(record2)); - if (index (mRecords.size())-1) + if (index < size - 1) { - for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); - ++iter) - if (iter->second>=index) - ++(iter->second); + for (auto& [key, value] : mIndex) + { + if (value >= index) + ++value; + } } - mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( - record2.get())), index)); + mIndex.insert(std::make_pair(id, index)); } - template - void Collection::setRecord (int index, const Record& record) + template + void Collection::setRecord(int index, std::unique_ptr> record) { - if (Misc::StringUtils::lowerCase (IdAccessorT().getId (mRecords.at (index).get()))!= - Misc::StringUtils::lowerCase (IdAccessorT().getId (record.get()))) - throw std::runtime_error ("attempt to change the ID of a record"); + if (getRecordId(mRecords.at(index)->get()) != getRecordId(record->get())) + throw std::runtime_error("attempt to change the ID of a record"); - mRecords.at (index) = record; + mRecords.at(index) = std::move(record); } - template - bool Collection::reorderRows (int baseIndex, const std::vector& newOrder) + template + bool Collection::reorderRows(int baseIndex, const std::vector& newOrder) { return false; } diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp index 6134dc172..c71bb0afc 100644 --- a/apps/opencs/model/world/collectionbase.cpp +++ b/apps/opencs/model/world/collectionbase.cpp @@ -2,29 +2,33 @@ #include +#include +#include + #include "columnbase.hpp" -CSMWorld::CollectionBase::CollectionBase() {} +int CSMWorld::CollectionBase::getInsertIndex(const ESM::RefId& id, UniversalId::Type type, RecordBase* record) const +{ + return getAppendIndex(id, type); +} -CSMWorld::CollectionBase::~CollectionBase() {} - -int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const +int CSMWorld::CollectionBase::searchColumnIndex(Columns::ColumnId id) const { int columns = getColumns(); - for (int i=0; i #include +#include #include -#include "universalid.hpp" #include "columns.hpp" +#include "universalid.hpp" class QVariant; +namespace ESM +{ + class RefId; +} + namespace CSMWorld { struct ColumnBase; @@ -22,89 +29,87 @@ namespace CSMWorld /// manually. class CollectionBase { - // not implemented - CollectionBase (const CollectionBase&); - CollectionBase& operator= (const CollectionBase&); + public: + CollectionBase() = default; + CollectionBase(const CollectionBase&) = delete; + CollectionBase& operator=(const CollectionBase&) = delete; + virtual ~CollectionBase() = default; - public: + virtual int getSize() const = 0; - CollectionBase(); + virtual ESM::RefId getId(int index) const = 0; - virtual ~CollectionBase(); + virtual int getIndex(const ESM::RefId& id) const = 0; - virtual int getSize() const = 0; + virtual int getColumns() const = 0; - virtual std::string getId (int index) const = 0; + virtual const ColumnBase& getColumn(int column) const = 0; - virtual int getIndex (const std::string& id) const = 0; + virtual QVariant getData(int index, int column) const = 0; - virtual int getColumns() const = 0; + virtual void setData(int index, int column, const QVariant& data) = 0; - virtual const ColumnBase& getColumn (int column) const = 0; + // Not in use. Temporarily removed so that the implementation of RefIdCollection can continue without + // these functions for now. + // virtual void merge() = 0; + ///< Merge modified into base. - virtual QVariant getData (int index, int column) const = 0; + // virtual void purge() = 0; + ///< Remove records that are flagged as erased. - virtual void setData (int index, int column, const QVariant& data) = 0; + virtual void removeRows(int index, int count) = 0; -// Not in use. Temporarily removed so that the implementation of RefIdCollection can continue without -// these functions for now. -// virtual void merge() = 0; - ///< Merge modified into base. + virtual void appendBlankRecord(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) = 0; + ///< \param type Will be ignored, unless the collection supports multiple record types -// virtual void purge() = 0; - ///< Remove records that are flagged as erased. + virtual int searchId(const ESM::RefId& id) const = 0; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) - virtual void removeRows (int index, int count) = 0; + virtual void replace(int index, std::unique_ptr record) = 0; + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. + ///< \param type Will be ignored, unless the collection supports multiple record types - virtual void appendBlankRecord (const std::string& id, - UniversalId::Type type = UniversalId::Type_None) = 0; - ///< \param type Will be ignored, unless the collection supports multiple record types + virtual void appendRecord(std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) + = 0; + ///< If the record type does not match, an exception is thrown. - virtual int searchId (const std::string& id) const = 0; - ////< Search record with \a id. - /// \return index of record (if found) or -1 (not found) + virtual void cloneRecord(const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) + = 0; - virtual void replace (int index, const RecordBase& record) = 0; - ///< If the record type does not match, an exception is thrown. - /// - /// \attention \a record must not change the ID. - ///< \param type Will be ignored, unless the collection supports multiple record types + virtual bool touchRecord(const ESM::RefId& id) = 0; - virtual void appendRecord (const RecordBase& record, - UniversalId::Type type = UniversalId::Type_None) = 0; - ///< If the record type does not match, an exception is thrown. + virtual const RecordBase& getRecord(const ESM::RefId& id) const = 0; - virtual void cloneRecord(const std::string& origin, - const std::string& destination, - const UniversalId::Type type) = 0; + virtual const RecordBase& getRecord(int index) const = 0; - virtual bool touchRecord(const std::string& id) = 0; + virtual int getAppendIndex(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) const = 0; + ///< \param type Will be ignored, unless the collection supports multiple record types - virtual const RecordBase& getRecord (const std::string& id) const = 0; + virtual std::vector getIds(bool listDeleted = true) const = 0; + ///< Return a sorted collection of all IDs + /// + /// \param listDeleted include deleted record in the list - virtual const RecordBase& getRecord (int index) const = 0; + virtual bool reorderRows(int baseIndex, const std::vector& newOrder) = 0; + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? - virtual int getAppendIndex (const std::string& id, - UniversalId::Type type = UniversalId::Type_None) const = 0; - ///< \param type Will be ignored, unless the collection supports multiple record types + virtual int getInsertIndex( + const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None, RecordBase* record = nullptr) const; + ///< Works like getAppendIndex unless an overloaded method uses the record pointer + /// to get additional info about the record that results in an alternative index. - virtual std::vector getIds (bool listDeleted = true) const = 0; - ///< Return a sorted collection of all IDs - /// - /// \param listDeleted include deleted record in the list + int searchColumnIndex(Columns::ColumnId id) const; + ///< Return index of column with the given \a id. If no such column exists, -1 is returned. - virtual bool reorderRows (int baseIndex, const std::vector& newOrder) = 0; - ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices - /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). - /// - /// \return Success? - - int searchColumnIndex (Columns::ColumnId id) const; - ///< Return index of column with the given \a id. If no such column exists, -1 is returned. - - int findColumnIndex (Columns::ColumnId id) const; - ///< Return index of column with the given \a id. If no such column exists, an exception is - /// thrown. + int findColumnIndex(Columns::ColumnId id) const; + ///< Return index of column with the given \a id. If no such column exists, an exception is + /// thrown. }; } diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index cf333c1b1..1f963b0e4 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -1,12 +1,15 @@ #include "columnbase.hpp" +#include + #include "columns.hpp" -CSMWorld::ColumnBase::ColumnBase (int columnId, Display displayType, int flags) - : mColumnId (columnId), mFlags (flags), mDisplayType (displayType) -{} - -CSMWorld::ColumnBase::~ColumnBase() {} +CSMWorld::ColumnBase::ColumnBase(int columnId, Display displayType, int flags) + : mColumnId(columnId) + , mFlags(flags) + , mDisplayType(displayType) +{ +} bool CSMWorld::ColumnBase::isUserEditable() const { @@ -15,18 +18,17 @@ bool CSMWorld::ColumnBase::isUserEditable() const std::string CSMWorld::ColumnBase::getTitle() const { - return Columns::getName (static_cast (mColumnId)); + return Columns::getName(static_cast(mColumnId)); } -int CSMWorld::ColumnBase::getId() const +int CSMWorld::ColumnBase::getId() const { return mColumnId; } -bool CSMWorld::ColumnBase::isId (Display display) +bool CSMWorld::ColumnBase::isId(Display display) { - static const Display ids[] = - { + static const Display ids[] = { Display_Skill, Display_Class, Display_Faction, @@ -91,28 +93,28 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_EffectAttribute, Display_IngredEffectId, - Display_None + Display_None, }; - for (int i=0; ids[i]!=Display_None; ++i) - if (ids[i]==display) + for (int i = 0; ids[i] != Display_None; ++i) + if (ids[i] == display) return true; return false; } -bool CSMWorld::ColumnBase::isText (Display display) +bool CSMWorld::ColumnBase::isText(Display display) { - return display==Display_String || display==Display_LongString || - display==Display_String32 || display==Display_LongString256; + return display == Display_String || display == Display_LongString || display == Display_String32 + || display == Display_String64 || display == Display_LongString256; } -bool CSMWorld::ColumnBase::isScript (Display display) +bool CSMWorld::ColumnBase::isScript(Display display) { - return display==Display_ScriptFile || display==Display_ScriptLines; + return display == Display_ScriptFile || display == Display_ScriptLines; } -void CSMWorld::NestableColumn::addColumn(CSMWorld::NestableColumn *column) +void CSMWorld::NestableColumn::addColumn(CSMWorld::NestableColumn* column) { mNestedColumns.push_back(column); } @@ -125,16 +127,16 @@ const CSMWorld::ColumnBase& CSMWorld::NestableColumn::nestedColumn(int subColumn return *mNestedColumns.at(subColumn); } -CSMWorld::NestableColumn::NestableColumn(int columnId, CSMWorld::ColumnBase::Display displayType, - int flag) +CSMWorld::NestableColumn::NestableColumn(int columnId, CSMWorld::ColumnBase::Display displayType, int flag) : CSMWorld::ColumnBase(columnId, displayType, flag) -{} +{ +} CSMWorld::NestableColumn::~NestableColumn() { - for (unsigned int i = 0; i < mNestedColumns.size(); ++i) + for (auto* col : mNestedColumns) { - delete mNestedColumns[i]; + delete col; } } @@ -143,12 +145,14 @@ bool CSMWorld::NestableColumn::hasChildren() const return !mNestedColumns.empty(); } -CSMWorld::NestedChildColumn::NestedChildColumn (int id, - CSMWorld::ColumnBase::Display display, int flags, bool isEditable) - : NestableColumn (id, display, flags) , mIsEditable(isEditable) -{} +CSMWorld::NestedChildColumn::NestedChildColumn( + int id, CSMWorld::ColumnBase::Display display, int flags, bool isEditable) + : NestableColumn(id, display, flags) + , mIsEditable(isEditable) +{ +} -bool CSMWorld::NestedChildColumn::isEditable () const +bool CSMWorld::NestedChildColumn::isEditable() const { return mIsEditable; } diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 6dc58bd63..e1576bba9 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -1,31 +1,31 @@ #ifndef CSM_WOLRD_COLUMNBASE_H #define CSM_WOLRD_COLUMNBASE_H +#include #include #include -#include -#include #include -#include "record.hpp" - namespace CSMWorld { + template + struct Record; + struct ColumnBase { enum TableEditModes { - TableEdit_None, // no editing - TableEdit_Full, // edit cells and add/remove rows - TableEdit_FixedRows // edit cells only + TableEdit_None, // no editing + TableEdit_Full, // edit cells and add/remove rows + TableEdit_FixedRows // edit cells only }; enum Roles { Role_Flags = Qt::UserRole, - Role_Display = Qt::UserRole+1, - Role_ColumnId = Qt::UserRole+2 + Role_Display = Qt::UserRole + 1, + Role_ColumnId = Qt::UserRole + 2 }; enum Flags @@ -38,11 +38,11 @@ namespace CSMWorld enum Display { - Display_None, //Do not use + Display_None, // Do not use Display_String, Display_LongString, - //CONCRETE TYPES STARTS HERE (for drag and drop) + // CONCRETE TYPES STARTS HERE (for drag and drop) Display_Skill, Display_Class, Display_Faction, @@ -84,7 +84,7 @@ namespace CSMWorld Display_GlobalVariable, Display_BodyPart, Display_Enchantment, - //CONCRETE TYPES ENDS HERE + // CONCRETE TYPES ENDS HERE Display_SignedInteger8, Display_SignedInteger16, @@ -135,17 +135,18 @@ namespace CSMWorld Display_InfoCondVar, Display_InfoCondComp, Display_String32, + Display_String64, Display_LongString256, Display_BookType, Display_BloodType, Display_EmitterType, - Display_EffectSkill, // must display at least one, unlike Display_Skill + Display_EffectSkill, // must display at least one, unlike Display_Skill Display_EffectAttribute, // must display at least one, unlike Display_Attribute - Display_IngredEffectId, // display none allowed, unlike Display_EffectId - Display_GenderNpc, // must display at least one, unlike Display_Gender + Display_IngredEffectId, // display none allowed, unlike Display_EffectId + Display_GenderNpc, // must display at least one, unlike Display_Gender - //top level columns that nest other columns + // top level columns that nest other columns Display_NestedHeader }; @@ -153,9 +154,9 @@ namespace CSMWorld int mFlags; Display mDisplayType; - ColumnBase (int columnId, Display displayType, int flag); + ColumnBase(int columnId, Display displayType, int flag); - virtual ~ColumnBase(); + virtual ~ColumnBase() = default; virtual bool isEditable() const = 0; @@ -166,58 +167,61 @@ namespace CSMWorld virtual int getId() const; - static bool isId (Display display); + static bool isId(Display display); - static bool isText (Display display); + static bool isText(Display display); - static bool isScript (Display display); + static bool isScript(Display display); }; class NestableColumn : public ColumnBase { - std::vector mNestedColumns; + std::vector mNestedColumns; public: - NestableColumn(int columnId, Display displayType, int flag); ~NestableColumn(); - void addColumn(CSMWorld::NestableColumn *column); + void addColumn(CSMWorld::NestableColumn* column); const ColumnBase& nestedColumn(int subColumn) const; bool hasChildren() const; }; - template + template struct Column : public NestableColumn { - Column (int columnId, Display displayType, int flags = Flag_Table | Flag_Dialogue) - : NestableColumn (columnId, displayType, flags) {} - - virtual QVariant get (const Record& record) const = 0; - - virtual void set (Record& record, const QVariant& data) + Column(int columnId, Display displayType, int flags = Flag_Table | Flag_Dialogue) + : NestableColumn(columnId, displayType, flags) { - throw std::logic_error ("Column " + getTitle() + " is not editable"); + } + + virtual QVariant get(const Record& record) const = 0; + + virtual void set(Record& record, const QVariant& data) + { + throw std::logic_error("Column " + getTitle() + " is not editable"); } }; - template + template struct NestedParentColumn : public Column { - NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue, bool fixedRows = false) - : Column (id, ColumnBase::Display_NestedHeader, flags), mFixedRows(fixedRows) - {} + NestedParentColumn(int id, int flags = ColumnBase::Flag_Dialogue, bool fixedRows = false) + : Column(id, ColumnBase::Display_NestedHeader, flags) + , mFixedRows(fixedRows) + { + } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { // There is nothing to do here. // This prevents exceptions from parent's implementation } - QVariant get (const Record& record) const override + QVariant get(const Record& record) const override { // by default editable; also see IdTree::hasChildren() if (mFixedRows) @@ -226,10 +230,7 @@ namespace CSMWorld return QVariant::fromValue(ColumnBase::TableEdit_Full); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } private: bool mFixedRows; @@ -237,8 +238,7 @@ namespace CSMWorld struct NestedChildColumn : public NestableColumn { - NestedChildColumn (int id, - Display display, int flags = ColumnBase::Flag_Dialogue, bool isEditable = true); + NestedChildColumn(int id, Display display, int flags = ColumnBase::Flag_Dialogue, bool isEditable = true); bool isEditable() const override; diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index bec5008d3..7d43d1e4f 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -1,10 +1,50 @@ #include "columnimp.hpp" +#include +#include +#include +#include +#include + +#include +#include + +#include #include -#include namespace CSMWorld { + namespace + { + struct GetStringId + { + std::string operator()(ESM::EmptyRefId /*value*/) const { return std::string(); } + + std::string operator()(ESM::StringRefId value) const { return value.getValue(); } + + std::string operator()(ESM::IndexRefId value) const + { + switch (value.getRecordType()) + { + case ESM::REC_SKIL: + return ESM::Skill::sSkillNames[value.getValue()]; + case ESM::REC_MGEF: + return std::string(ESM::MagicEffect::sIndexNames[value.getValue()]); + default: + break; + } + + return value.toDebugString(); + } + + template + std::string operator()(const T& value) const + { + return value.toDebugString(); + } + }; + } + /* LandTextureNicknameColumn */ LandTextureNicknameColumn::LandTextureNicknameColumn() : Column(Columns::ColumnId_TextureNickname, ColumnBase::Display_String) @@ -13,13 +53,13 @@ namespace CSMWorld QVariant LandTextureNicknameColumn::get(const Record& record) const { - return QString::fromUtf8(record.get().mId.c_str()); + return QString::fromStdString(record.get().mId.toString()); } void LandTextureNicknameColumn::set(Record& record, const QVariant& data) { LandTexture copy = record.get(); - copy.mId = data.toString().toUtf8().constData(); + copy.mId = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); record.setModified(copy); } @@ -52,7 +92,7 @@ namespace CSMWorld QVariant LandPluginIndexColumn::get(const Record& record) const { - return record.get().mPlugin; + return record.get().getPlugin(); } bool LandPluginIndexColumn::isEditable() const @@ -265,24 +305,25 @@ namespace CSMWorld } /* BodyPartRaceColumn */ - BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn *meshType) + BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn* meshType) : mMeshType(meshType) - {} + { + } - QVariant BodyPartRaceColumn::get(const Record &record) const + QVariant BodyPartRaceColumn::get(const Record& record) const { if (mMeshType != nullptr && mMeshType->get(record) == ESM::BodyPart::MT_Skin) { - return QString::fromUtf8(record.get().mRace.c_str()); + return QString::fromUtf8(record.get().mRace.getRefIdString().c_str()); } return QVariant(QVariant::UserType); } - void BodyPartRaceColumn::set(Record &record, const QVariant &data) + void BodyPartRaceColumn::set(Record& record, const QVariant& data) { ESM::BodyPart record2 = record.get(); - record2.mRace = data.toString().toUtf8().constData(); + record2.mRace = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); record.setModified(record2); } @@ -291,4 +332,17 @@ namespace CSMWorld { return true; } + + std::optional getSkillIndex(std::string_view value) + { + const auto it = std::find(std::begin(ESM::Skill::sSkillNames), std::end(ESM::Skill::sSkillNames), value); + if (it == std::end(ESM::Skill::sSkillNames)) + return std::nullopt; + return static_cast(it - std::begin(ESM::Skill::sSkillNames)); + } + + std::string getStringId(ESM::RefId value) + { + return visit(GetStringId{}, value); + } } diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 17518937c..3f137fadb 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -5,171 +5,174 @@ #include #include #include +#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include #include -#include -#include -#include - #include "columnbase.hpp" #include "columns.hpp" #include "info.hpp" - #include "land.hpp" #include "landtexture.hpp" +#include "record.hpp" namespace CSMWorld { + std::optional getSkillIndex(std::string_view value); + + std::string getStringId(ESM::RefId value); + /// \note Shares ID with VarValueColumn. A table can not have both. - template + template struct FloatValueColumn : public Column { - FloatValueColumn() : Column (Columns::ColumnId_Value, ColumnBase::Display_Float) {} - - QVariant get (const Record& record) const override + FloatValueColumn() + : Column(Columns::ColumnId_Value, ColumnBase::Display_Float) { - return record.get().mValue.getFloat(); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mValue.getFloat(); } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mValue.setFloat (data.toFloat()); - record.setModified (record2); + record2.mValue.setFloat(data.toFloat()); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct StringIdColumn : public Column { - StringIdColumn (bool hidden = false) - : Column (Columns::ColumnId_Id, ColumnBase::Display_Id, - hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) - {} - - QVariant get (const Record& record) const override + StringIdColumn(bool hidden = false) + : Column(Columns::ColumnId_Id, ColumnBase::Display_Id, + hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) { - return QString::fromUtf8 (record.get().mId.c_str()); } - bool isEditable() const override + QVariant get(const Record& record) const override { - return false; + return QString::fromStdString(getStringId(record.get().mId)); } + + bool isEditable() const override { return false; } }; - template<> + template <> inline QVariant StringIdColumn::get(const Record& record) const { const Land& land = record.get(); return QString::fromUtf8(Land::createUniqueRecordId(land.mX, land.mY).c_str()); } - template<> + template <> inline QVariant StringIdColumn::get(const Record& record) const { const LandTexture& ltex = record.get(); return QString::fromUtf8(LandTexture::createUniqueRecordId(ltex.mPluginIndex, ltex.mIndex).c_str()); } - template + template struct RecordStateColumn : public Column { RecordStateColumn() - : Column (Columns::ColumnId_Modification, ColumnBase::Display_RecordState) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Modification, ColumnBase::Display_RecordState) { - if (record.mState==Record::State_Erased) - return static_cast (Record::State_Deleted); - - return static_cast (record.mState); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { - record.mState = static_cast (data.toInt()); + if (record.mState == Record::State_Erased) + return static_cast(Record::State_Deleted); + + return static_cast(record.mState); } - bool isEditable() const override + void set(Record& record, const QVariant& data) override { - return true; + record.mState = static_cast(data.toInt()); } - bool isUserEditable() const override - { - return false; - } + bool isEditable() const override { return true; } + + bool isUserEditable() const override { return false; } }; - template + template struct FixedRecordTypeColumn : public Column { int mType; - FixedRecordTypeColumn (int type) - : Column (Columns::ColumnId_RecordType, ColumnBase::Display_Integer, 0), - mType (type) - {} - - QVariant get (const Record& record) const override + FixedRecordTypeColumn(int type) + : Column(Columns::ColumnId_RecordType, ColumnBase::Display_Integer, 0) + , mType(type) { - return mType; } - bool isEditable() const override - { - return false; - } + QVariant get(const Record& record) const override { return mType; } + + bool isEditable() const override { return false; } }; /// \attention A var type column must be immediately followed by a suitable value column. - template + template struct VarTypeColumn : public Column { - VarTypeColumn (ColumnBase::Display display) - : Column (Columns::ColumnId_ValueType, display, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) - {} - - QVariant get (const Record& record) const override + VarTypeColumn(ColumnBase::Display display) + : Column(Columns::ColumnId_ValueType, display, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) { - return static_cast (record.get().mValue.getType()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mValue.getType()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mValue.setType (static_cast (data.toInt())); - record.setModified (record2); + record2.mValue.setType(static_cast(data.toInt())); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; /// \note Shares ID with FloatValueColumn. A table can not have both. - template + template struct VarValueColumn : public Column { - VarValueColumn() : Column (Columns::ColumnId_Value, ColumnBase::Display_Var, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) {} + VarValueColumn() + : Column(Columns::ColumnId_Value, ColumnBase::Display_Var, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) + { + } - QVariant get (const Record& record) const override + QVariant get(const Record& record) const override { switch (record.get().mValue.getType()) { case ESM::VT_String: - return QString::fromUtf8 (record.get().mValue.getString().c_str()); + return QString::fromUtf8(record.get().mValue.getString().c_str()); case ESM::VT_Int: case ESM::VT_Short: @@ -181,11 +184,12 @@ namespace CSMWorld return record.get().mValue.getFloat(); - default: return QVariant(); + default: + return QVariant(); } } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); @@ -193,310 +197,297 @@ namespace CSMWorld { case ESM::VT_String: - record2.mValue.setString (data.toString().toUtf8().constData()); + record2.mValue.setString(data.toString().toUtf8().constData()); break; case ESM::VT_Int: case ESM::VT_Short: case ESM::VT_Long: - record2.mValue.setInteger (data.toInt()); + record2.mValue.setInteger(data.toInt()); break; case ESM::VT_Float: - record2.mValue.setFloat (data.toFloat()); + record2.mValue.setFloat(data.toFloat()); break; - default: break; + default: + break; } - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct DescriptionColumn : public Column { DescriptionColumn() - : Column (Columns::ColumnId_Description, ColumnBase::Display_LongString) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Description, ColumnBase::Display_LongString) { - return QString::fromUtf8 (record.get().mDescription.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mDescription.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mDescription = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SpecialisationColumn : public Column { SpecialisationColumn() - : Column (Columns::ColumnId_Specialisation, ColumnBase::Display_Specialisation) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Specialisation, ColumnBase::Display_Specialisation) { - return record.get().mData.mSpecialization; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mSpecialization; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mSpecialization = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct UseValueColumn : public Column { int mIndex; - UseValueColumn (int index) - : Column (Columns::ColumnId_UseValue1 + index, ColumnBase::Display_Float), - mIndex (index) - {} - - QVariant get (const Record& record) const override + UseValueColumn(int index) + : Column(Columns::ColumnId_UseValue1 + index, ColumnBase::Display_Float) + , mIndex(index) { - return record.get().mData.mUseValue[mIndex]; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mUseValue[mIndex]; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mUseValue[mIndex] = data.toFloat(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct AttributeColumn : public Column { AttributeColumn() - : Column (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Attribute, ColumnBase::Display_Attribute) { - return record.get().mData.mAttribute; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mAttribute; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mAttribute = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct NameColumn : public Column { - NameColumn() : Column (Columns::ColumnId_Name, ColumnBase::Display_String) {} - - QVariant get (const Record& record) const override + NameColumn(ColumnBase::Display display = ColumnBase::Display_String) + : Column(Columns::ColumnId_Name, display) { - return QString::fromUtf8 (record.get().mName.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mName.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mName = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template <> + struct NameColumn : public Column + { + NameColumn(ColumnBase::Display display = ColumnBase::Display_String) + : Column(Columns::ColumnId_Name, display) + { + } + + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mName.c_str()); + } + + void set(Record& record, const QVariant& data) override + { + CSMWorld::Cell record2 = record.get(); + + record2.mName = data.toString().toUtf8().constData(); + + record.setModified(record2); + } + + bool isEditable() const override { return true; } + }; + + template struct AttributesColumn : public Column { int mIndex; - AttributesColumn (int index) - : Column (Columns::ColumnId_Attribute1 + index, ColumnBase::Display_Attribute), - mIndex (index) - {} - - QVariant get (const Record& record) const override + AttributesColumn(int index) + : Column(Columns::ColumnId_Attribute1 + index, ColumnBase::Display_Attribute) + , mIndex(index) { - return record.get().mData.mAttribute[mIndex]; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mAttribute[mIndex]; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mAttribute[mIndex] = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SkillsColumn : public Column { int mIndex; bool mMajor; - SkillsColumn (int index, bool typePrefix = false, bool major = false) - : Column ((typePrefix ? ( - major ? Columns::ColumnId_MajorSkill1 : Columns::ColumnId_MinorSkill1) : - Columns::ColumnId_Skill1) + index, ColumnBase::Display_Skill), - mIndex (index), mMajor (major) - {} - - QVariant get (const Record& record) const override + SkillsColumn(int index, bool typePrefix = false, bool major = false) + : Column((typePrefix ? (major ? Columns::ColumnId_MajorSkill1 : Columns::ColumnId_MinorSkill1) + : Columns::ColumnId_Skill1) + + index, + ColumnBase::Display_Skill) + , mIndex(index) + , mMajor(major) { - int skill = record.get().mData.getSkill (mIndex, mMajor); - - return QString::fromUtf8 (ESM::Skill::indexToId (skill).c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { - std::istringstream stream (data.toString().toUtf8().constData()); + return QString::fromStdString(ESM::Skill::sSkillNames[record.get().mData.getSkill(mIndex, mMajor)]); + } - int index = -1; - char c; - - stream >> c >> index; - - if (index!=-1) + void set(Record& record, const QVariant& data) override + { + if (const auto index = getSkillIndex(data.toString().toStdString())) { ESXRecordT record2 = record.get(); - record2.mData.getSkill (mIndex, mMajor) = index; + record2.mData.getSkill(mIndex, mMajor) = static_cast(*index); - record.setModified (record2); + record.setModified(record2); } } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct PlayableColumn : public Column { - PlayableColumn() : Column (Columns::ColumnId_Playable, ColumnBase::Display_Boolean) - {} - - QVariant get (const Record& record) const override + PlayableColumn() + : Column(Columns::ColumnId_Playable, ColumnBase::Display_Boolean) { - return record.get().mData.mIsPlayable!=0; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mIsPlayable != 0; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mIsPlayable = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct HiddenColumn : public Column { - HiddenColumn() : Column (Columns::ColumnId_Hidden, ColumnBase::Display_Boolean) {} - - QVariant get (const Record& record) const override + HiddenColumn() + : Column(Columns::ColumnId_Hidden, ColumnBase::Display_Boolean) { - return record.get().mData.mIsHidden!=0; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mIsHidden != 0; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mIsHidden = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct FlagColumn : public Column { int mMask; bool mInverted; - FlagColumn (int columnId, int mask, - int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, bool inverted = false) - : Column (columnId, ColumnBase::Display_Boolean, flags), mMask (mask), - mInverted (inverted) - {} - - QVariant get (const Record& record) const override + FlagColumn(int columnId, int mask, int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, + bool inverted = false) + : Column(columnId, ColumnBase::Display_Boolean, flags) + , mMask(mask) + , mInverted(inverted) { - bool flag = (record.get().mData.mFlags & mMask)!=0; + } + + QVariant get(const Record& record) const override + { + bool flag = (record.get().mData.mFlags & mMask) != 0; if (mInverted) flag = !flag; @@ -504,40 +495,39 @@ namespace CSMWorld return flag; } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); int flags = record2.mData.mFlags & ~mMask; - if ((data.toInt()!=0)!=mInverted) + if ((data.toInt() != 0) != mInverted) flags |= mMask; record2.mData.mFlags = flags; - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct FlagColumn2 : public Column { int mMask; bool mInverted; - FlagColumn2 (int columnId, int mask, bool inverted = false) - : Column (columnId, ColumnBase::Display_Boolean), mMask (mask), - mInverted (inverted) - {} - - QVariant get (const Record& record) const override + FlagColumn2(int columnId, int mask, bool inverted = false) + : Column(columnId, ColumnBase::Display_Boolean) + , mMask(mask) + , mInverted(inverted) { - bool flag = (record.get().mFlags & mMask)!=0; + } + + QVariant get(const Record& record) const override + { + bool flag = (record.get().mFlags & mMask) != 0; if (mInverted) flag = !flag; @@ -545,67 +535,60 @@ namespace CSMWorld return flag; } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); int flags = record2.mFlags & ~mMask; - if ((data.toInt()!=0)!=mInverted) + if ((data.toInt() != 0) != mInverted) flags |= mMask; record2.mFlags = flags; - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct WeightHeightColumn : public Column { bool mMale; bool mWeight; - WeightHeightColumn (bool male, bool weight) - : Column (male ? - (weight ? Columns::ColumnId_MaleWeight : Columns::ColumnId_MaleHeight) : - (weight ? Columns::ColumnId_FemaleWeight : Columns::ColumnId_FemaleHeight), - ColumnBase::Display_Float), - mMale (male), mWeight (weight) - {} - - QVariant get (const Record& record) const override + WeightHeightColumn(bool male, bool weight) + : Column(male ? (weight ? Columns::ColumnId_MaleWeight : Columns::ColumnId_MaleHeight) + : (weight ? Columns::ColumnId_FemaleWeight : Columns::ColumnId_FemaleHeight), + ColumnBase::Display_Float) + , mMale(male) + , mWeight(weight) { - const ESM::Race::MaleFemaleF& value = - mWeight ? record.get().mData.mWeight : record.get().mData.mHeight; + } + + QVariant get(const Record& record) const override + { + const ESM::Race::MaleFemaleF& value = mWeight ? record.get().mData.mWeight : record.get().mData.mHeight; return mMale ? value.mMale : value.mFemale; } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - ESM::Race::MaleFemaleF& value = - mWeight ? record2.mData.mWeight : record2.mData.mHeight; + ESM::Race::MaleFemaleF& value = mWeight ? record2.mData.mWeight : record2.mData.mHeight; (mMale ? value.mMale : value.mFemale) = data.toFloat(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SoundParamColumn : public Column { enum Type @@ -617,209 +600,203 @@ namespace CSMWorld Type mType; - SoundParamColumn (Type type) - : Column (type==Type_Volume ? Columns::ColumnId_Volume : - (type==Type_MinRange ? Columns::ColumnId_MinRange : Columns::ColumnId_MaxRange), - ColumnBase::Display_Integer), - mType (type) - {} + SoundParamColumn(Type type) + : Column(type == Type_Volume + ? Columns::ColumnId_Volume + : (type == Type_MinRange ? Columns::ColumnId_MinRange : Columns::ColumnId_MaxRange), + ColumnBase::Display_Integer) + , mType(type) + { + } - QVariant get (const Record& record) const override + QVariant get(const Record& record) const override { int value = 0; switch (mType) { - case Type_Volume: value = record.get().mData.mVolume; break; - case Type_MinRange: value = record.get().mData.mMinRange; break; - case Type_MaxRange: value = record.get().mData.mMaxRange; break; + case Type_Volume: + value = record.get().mData.mVolume; + break; + case Type_MinRange: + value = record.get().mData.mMinRange; + break; + case Type_MaxRange: + value = record.get().mData.mMaxRange; + break; } return value; } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { int value = data.toInt(); - if (value<0) + if (value < 0) value = 0; - else if (value>255) + else if (value > 255) value = 255; ESXRecordT record2 = record.get(); switch (mType) { - case Type_Volume: record2.mData.mVolume = static_cast (value); break; - case Type_MinRange: record2.mData.mMinRange = static_cast (value); break; - case Type_MaxRange: record2.mData.mMaxRange = static_cast (value); break; + case Type_Volume: + record2.mData.mVolume = static_cast(value); + break; + case Type_MinRange: + record2.mData.mMinRange = static_cast(value); + break; + case Type_MaxRange: + record2.mData.mMaxRange = static_cast(value); + break; } - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SoundFileColumn : public Column { SoundFileColumn() - : Column (Columns::ColumnId_SoundFile, ColumnBase::Display_SoundRes) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_SoundFile, ColumnBase::Display_SoundRes) { - return QString::fromUtf8 (record.get().mSound.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mSound.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mSound = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct MapColourColumn : public Column { MapColourColumn() - : Column (Columns::ColumnId_MapColour, ColumnBase::Display_Colour) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_MapColour, ColumnBase::Display_Colour) { - return record.get().mMapColor; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mMapColor; } + + void set(Record& record, const QVariant& data) override { ESXRecordT copy = record.get(); copy.mMapColor = data.toInt(); - record.setModified (copy); + record.setModified(copy); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SleepListColumn : public Column { SleepListColumn() - : Column (Columns::ColumnId_SleepEncounter, ColumnBase::Display_CreatureLevelledList) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_SleepEncounter, ColumnBase::Display_CreatureLevelledList) { - return QString::fromUtf8 (record.get().mSleepList.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mSleepList.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mSleepList = data.toString().toUtf8().constData(); + record2.mSleepList = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct TextureColumn : public Column { - TextureColumn() : Column (Columns::ColumnId_Texture, ColumnBase::Display_Texture) {} - - QVariant get (const Record& record) const override + TextureColumn() + : Column(Columns::ColumnId_Texture, ColumnBase::Display_Texture) { - return QString::fromUtf8 (record.get().mTexture.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mTexture.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mTexture = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SpellTypeColumn : public Column { SpellTypeColumn() - : Column (Columns::ColumnId_SpellType, ColumnBase::Display_SpellType) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_SpellType, ColumnBase::Display_SpellType) { - return record.get().mData.mType; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mType; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mType = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct CostColumn : public Column { - CostColumn() : Column (Columns::ColumnId_Cost, ColumnBase::Display_Integer) {} - - QVariant get (const Record& record) const override + CostColumn() + : Column(Columns::ColumnId_Cost, ColumnBase::Display_Integer) { - return record.get().mData.mCost; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mCost; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mCost = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct ScriptColumn : public Column { enum Type @@ -829,523 +806,478 @@ namespace CSMWorld Type_Info // dialogue context (not implemented yet) }; - ScriptColumn (Type type) - : Column (Columns::ColumnId_ScriptText, - type==Type_File ? ColumnBase::Display_ScriptFile : ColumnBase::Display_ScriptLines, - type==Type_File ? 0 : ColumnBase::Flag_Dialogue) - {} - - QVariant get (const Record& record) const override + ScriptColumn(Type type) + : Column(Columns::ColumnId_ScriptText, + type == Type_File ? ColumnBase::Display_ScriptFile : ColumnBase::Display_ScriptLines, + type == Type_File ? 0 : ColumnBase::Flag_Dialogue) { - return QString::fromUtf8 (record.get().mScriptText.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mScriptText.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mScriptText = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct RegionColumn : public Column { - RegionColumn() : Column (Columns::ColumnId_Region, ColumnBase::Display_Region) {} - - QVariant get (const Record& record) const override + RegionColumn() + : Column(Columns::ColumnId_Region, ColumnBase::Display_Region) { - return QString::fromUtf8 (record.get().mRegion.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mRegion.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mRegion = data.toString().toUtf8().constData(); + record2.mRegion = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct CellColumn : public Column { bool mBlocked; /// \param blocked Do not allow user-modification - CellColumn (bool blocked = false) - : Column (Columns::ColumnId_Cell, ColumnBase::Display_Cell), - mBlocked (blocked) - {} - - QVariant get (const Record& record) const override + CellColumn(bool blocked = false) + : Column(Columns::ColumnId_Cell, ColumnBase::Display_Cell) + , mBlocked(blocked) { - return QString::fromUtf8 (record.get().mCell.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mCell.toString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mCell = data.toString().toUtf8().constData(); + record2.mCell = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } - bool isUserEditable() const override - { - return !mBlocked; - } + bool isUserEditable() const override { return !mBlocked; } }; - template + template struct OriginalCellColumn : public Column { OriginalCellColumn() - : Column (Columns::ColumnId_OriginalCell, ColumnBase::Display_Cell) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_OriginalCell, ColumnBase::Display_Cell) { - return QString::fromUtf8 (record.get().mOriginalCell.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mOriginalCell.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mOriginalCell = data.toString().toUtf8().constData(); + record2.mOriginalCell = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } - bool isUserEditable() const override - { - return false; - } + bool isUserEditable() const override { return false; } }; - template + template struct IdColumn : public Column { - IdColumn() : Column (Columns::ColumnId_ReferenceableId, - ColumnBase::Display_Referenceable) {} - - QVariant get (const Record& record) const override + IdColumn() + : Column(Columns::ColumnId_ReferenceableId, ColumnBase::Display_Referenceable) { - return QString::fromUtf8 (record.get().mRefID.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mRefID.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mRefID = data.toString().toUtf8().constData(); + record2.mRefID = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct ScaleColumn : public Column { - ScaleColumn() : Column (Columns::ColumnId_Scale, ColumnBase::Display_Float) {} - - QVariant get (const Record& record) const override + ScaleColumn() + : Column(Columns::ColumnId_Scale, ColumnBase::Display_Float) { - return record.get().mScale; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mScale; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mScale = data.toFloat(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct OwnerColumn : public Column { - OwnerColumn() : Column (Columns::ColumnId_Owner, ColumnBase::Display_Npc) {} - - QVariant get (const Record& record) const override + OwnerColumn() + : Column(Columns::ColumnId_Owner, ColumnBase::Display_Npc) { - return QString::fromUtf8 (record.get().mOwner.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mOwner.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mOwner = data.toString().toUtf8().constData(); + record2.mOwner = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SoulColumn : public Column { - SoulColumn() : Column (Columns::ColumnId_Soul, ColumnBase::Display_Creature) {} - - QVariant get (const Record& record) const override + SoulColumn() + : Column(Columns::ColumnId_Soul, ColumnBase::Display_Creature) { - return QString::fromUtf8 (record.get().mSoul.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mSoul.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mSoul = data.toString().toUtf8().constData(); + record2.mSoul = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct FactionColumn : public Column { - FactionColumn() : Column (Columns::ColumnId_Faction, ColumnBase::Display_Faction) {} - - QVariant get (const Record& record) const override + FactionColumn() + : Column(Columns::ColumnId_Faction, ColumnBase::Display_Faction) { - return QString::fromUtf8 (record.get().mFaction.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mFaction.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mFaction = data.toString().toUtf8().constData(); + record2.mFaction = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct FactionIndexColumn : public Column { FactionIndexColumn() - : Column (Columns::ColumnId_FactionIndex, ColumnBase::Display_Integer) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_FactionIndex, ColumnBase::Display_Integer) { - return record.get().mFactionRank; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mFactionRank; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mFactionRank = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct ChargesColumn : public Column { - ChargesColumn() : Column (Columns::ColumnId_Charges, ColumnBase::Display_Integer) {} - - QVariant get (const Record& record) const override + ChargesColumn() + : Column(Columns::ColumnId_Charges, ColumnBase::Display_Integer) { - return record.get().mChargeInt; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mChargeInt; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mChargeInt = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct EnchantmentChargesColumn : public Column { EnchantmentChargesColumn() - : Column (Columns::ColumnId_Enchantment, ColumnBase::Display_Float) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Enchantment, ColumnBase::Display_Float) { - return record.get().mEnchantmentCharge; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mEnchantmentCharge; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mEnchantmentCharge = data.toFloat(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct GoldValueColumn : public Column { GoldValueColumn() - : Column (Columns::ColumnId_CoinValue, ColumnBase::Display_Integer) {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_CoinValue, ColumnBase::Display_Integer) { - return record.get().mGoldValue; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mGoldValue; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mGoldValue = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct TeleportColumn : public Column { TeleportColumn() - : Column (Columns::ColumnId_Teleport, ColumnBase::Display_Boolean) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean) { - return record.get().mTeleport; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mTeleport; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mTeleport = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct TeleportCellColumn : public Column { TeleportCellColumn() - : Column (Columns::ColumnId_TeleportCell, ColumnBase::Display_Cell) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_TeleportCell, ColumnBase::Display_Cell) { - return QString::fromUtf8 (record.get().mDestCell.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mDestCell.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mDestCell = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } - bool isUserEditable() const override - { - return true; - } + bool isUserEditable() const override { return true; } }; - template + template struct LockLevelColumn : public Column { LockLevelColumn() - : Column (Columns::ColumnId_LockLevel, ColumnBase::Display_Integer) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_LockLevel, ColumnBase::Display_Integer) { - return record.get().mLockLevel; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mLockLevel; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mLockLevel = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct KeyColumn : public Column { - KeyColumn() : Column (Columns::ColumnId_Key, ColumnBase::Display_Miscellaneous) {} - - QVariant get (const Record& record) const override + KeyColumn() + : Column(Columns::ColumnId_Key, ColumnBase::Display_Miscellaneous) { - return QString::fromUtf8 (record.get().mKey.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mKey.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mKey = data.toString().toUtf8().constData(); + record2.mKey = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct TrapColumn : public Column { - TrapColumn() : Column (Columns::ColumnId_Trap, ColumnBase::Display_Spell) {} - - QVariant get (const Record& record) const override + TrapColumn() + : Column(Columns::ColumnId_Trap, ColumnBase::Display_Spell) { - return QString::fromUtf8 (record.get().mTrap.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mTrap.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mTrap = data.toString().toUtf8().constData(); + record2.mTrap = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct FilterColumn : public Column { - FilterColumn() : Column (Columns::ColumnId_Filter, ColumnBase::Display_Filter) {} - - QVariant get (const Record& record) const override + FilterColumn() + : Column(Columns::ColumnId_Filter, ColumnBase::Display_Filter) { - return QString::fromUtf8 (record.get().mFilter.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mFilter.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mFilter = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct PosColumn : public Column { - ESM::Position ESXRecordT::* mPosition; + ESM::Position ESXRecordT::*mPosition; int mIndex; - PosColumn (ESM::Position ESXRecordT::* position, int index, bool door) - : Column ( - (door ? Columns::ColumnId_DoorPositionXPos : Columns::ColumnId_PositionXPos)+index, - ColumnBase::Display_Float), mPosition (position), mIndex (index) {} + PosColumn(ESM::Position ESXRecordT::*position, int index, bool door) + : Column((door ? Columns::ColumnId_DoorPositionXPos : Columns::ColumnId_PositionXPos) + index, + ColumnBase::Display_Float) + , mPosition(position) + , mIndex(index) + { + } - QVariant get (const Record& record) const override + QVariant get(const Record& record) const override { const ESM::Position& position = record.get().*mPosition; return position.pos[mIndex]; } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); @@ -1353,33 +1285,33 @@ namespace CSMWorld position.pos[mIndex] = data.toFloat(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct RotColumn : public Column { - ESM::Position ESXRecordT::* mPosition; + ESM::Position ESXRecordT::*mPosition; int mIndex; - RotColumn (ESM::Position ESXRecordT::* position, int index, bool door) - : Column ( - (door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot)+index, - ColumnBase::Display_Double), mPosition (position), mIndex (index) {} + RotColumn(ESM::Position ESXRecordT::*position, int index, bool door) + : Column((door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot) + index, + ColumnBase::Display_Double) + , mPosition(position) + , mIndex(index) + { + } - QVariant get (const Record& record) const override + QVariant get(const Record& record) const override { const ESM::Position& position = record.get().*mPosition; return osg::RadiansToDegrees(position.rot[mIndex]); } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); @@ -1387,391 +1319,358 @@ namespace CSMWorld position.rot[mIndex] = osg::DegreesToRadians(data.toFloat()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct DialogueTypeColumn : public Column { - DialogueTypeColumn (bool hidden = false) - : Column (Columns::ColumnId_DialogueType, ColumnBase::Display_DialogueType, - hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) - {} - - QVariant get (const Record& record) const override + DialogueTypeColumn(bool hidden = false) + : Column(Columns::ColumnId_DialogueType, ColumnBase::Display_DialogueType, + hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) { - return static_cast (record.get().mType); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return static_cast(record.get().mType); } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mType = data.toInt(); + record2.mType = static_cast(data.toInt()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } - bool isUserEditable() const override - { - return false; - } + bool isUserEditable() const override { return false; } }; - template + template struct QuestStatusTypeColumn : public Column { QuestStatusTypeColumn() - : Column (Columns::ColumnId_QuestStatusType, ColumnBase::Display_QuestStatusType) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_QuestStatusType, ColumnBase::Display_QuestStatusType) { - return static_cast (record.get().mQuestStatus); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mQuestStatus); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mQuestStatus = static_cast (data.toInt()); + record2.mQuestStatus = static_cast(data.toInt()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct QuestDescriptionColumn : public Column { - QuestDescriptionColumn() : Column (Columns::ColumnId_QuestDescription, ColumnBase::Display_LongString) {} - - QVariant get (const Record& record) const override + QuestDescriptionColumn() + : Column(Columns::ColumnId_QuestDescription, ColumnBase::Display_LongString) { - return QString::fromUtf8 (record.get().mResponse.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mResponse.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mResponse = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct QuestIndexColumn : public Column { QuestIndexColumn() - : Column (Columns::ColumnId_QuestIndex, ColumnBase::Display_Integer) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_QuestIndex, ColumnBase::Display_Integer) { - return record.get().mData.mDisposition; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mDisposition; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mDisposition = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct TopicColumn : public Column { - TopicColumn (bool journal) - : Column (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, - journal ? ColumnBase::Display_Journal : ColumnBase::Display_Topic) - {} - - QVariant get (const Record& record) const override + TopicColumn(bool journal) + : Column(journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, + journal ? ColumnBase::Display_Journal : ColumnBase::Display_Topic) { - return QString::fromUtf8 (record.get().mTopicId.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mTopicId.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mTopicId = data.toString().toUtf8().constData(); + record2.mTopicId = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } - bool isUserEditable() const override - { - return false; - } + bool isUserEditable() const override { return false; } }; - template + template struct ActorColumn : public Column { - ActorColumn() : Column (Columns::ColumnId_Actor, ColumnBase::Display_Npc) {} - - QVariant get (const Record& record) const override + ActorColumn() + : Column(Columns::ColumnId_Actor, ColumnBase::Display_Npc) { - return QString::fromUtf8 (record.get().mActor.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mActor.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mActor = data.toString().toUtf8().constData(); + record2.mActor = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct RaceColumn : public Column { - RaceColumn() : Column (Columns::ColumnId_Race, ColumnBase::Display_Race) {} - - QVariant get (const Record& record) const override + RaceColumn() + : Column(Columns::ColumnId_Race, ColumnBase::Display_Race) { - return QString::fromUtf8 (record.get().mRace.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mRace.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mRace = data.toString().toUtf8().constData(); + record2.mRace = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct ClassColumn : public Column { - ClassColumn() : Column (Columns::ColumnId_Class, ColumnBase::Display_Class) {} - - QVariant get (const Record& record) const override + ClassColumn() + : Column(Columns::ColumnId_Class, ColumnBase::Display_Class) { - return QString::fromUtf8 (record.get().mClass.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mClass.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mClass = data.toString().toUtf8().constData(); + record2.mClass = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct PcFactionColumn : public Column { - PcFactionColumn() : Column (Columns::ColumnId_PcFaction, ColumnBase::Display_Faction) {} - - QVariant get (const Record& record) const override + PcFactionColumn() + : Column(Columns::ColumnId_PcFaction, ColumnBase::Display_Faction) { - return QString::fromUtf8 (record.get().mPcFaction.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mPcFaction.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mPcFaction = data.toString().toUtf8().constData(); + record2.mPcFaction = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct ResponseColumn : public Column { - ResponseColumn() : Column (Columns::ColumnId_Response, ColumnBase::Display_LongString) {} - - QVariant get (const Record& record) const override + ResponseColumn() + : Column(Columns::ColumnId_Response, ColumnBase::Display_LongString) { - return QString::fromUtf8 (record.get().mResponse.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mResponse.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mResponse = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct DispositionColumn : public Column { DispositionColumn() - : Column (Columns::ColumnId_Disposition, ColumnBase::Display_Integer) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Disposition, ColumnBase::Display_Integer) { - return record.get().mData.mDisposition; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mDisposition; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mDisposition = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct RankColumn : public Column { RankColumn() - : Column (Columns::ColumnId_Rank, ColumnBase::Display_Integer) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Rank, ColumnBase::Display_Integer) { - return static_cast (record.get().mData.mRank); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mData.mRank); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mData.mRank = static_cast (data.toInt()); - record.setModified (record2); + record2.mData.mRank = static_cast(data.toInt()); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct PcRankColumn : public Column { PcRankColumn() - : Column (Columns::ColumnId_PcRank, ColumnBase::Display_Integer) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_PcRank, ColumnBase::Display_Integer) { - return static_cast (record.get().mData.mPCrank); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mData.mPCrank); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mData.mPCrank = static_cast (data.toInt()); - record.setModified (record2); + record2.mData.mPCrank = static_cast(data.toInt()); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct GenderColumn : public Column { GenderColumn() - : Column (Columns::ColumnId_Gender, ColumnBase::Display_Gender) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Gender, ColumnBase::Display_Gender) { - return static_cast (record.get().mData.mGender); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mData.mGender); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mGender = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct GenderNpcColumn : public Column { GenderNpcColumn() : Column(Columns::ColumnId_Gender, ColumnBase::Display_GenderNpc) - {} + { + } QVariant get(const Record& record) const override { @@ -1795,637 +1694,598 @@ namespace CSMWorld record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct EnchantmentTypeColumn : public Column { EnchantmentTypeColumn() - : Column (Columns::ColumnId_EnchantmentType, ColumnBase::Display_EnchantmentType) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_EnchantmentType, ColumnBase::Display_EnchantmentType) { - return static_cast (record.get().mData.mType); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mData.mType); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mType = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct ChargesColumn2 : public Column { - ChargesColumn2() : Column (Columns::ColumnId_Charges, ColumnBase::Display_Integer) {} - - QVariant get (const Record& record) const override + ChargesColumn2() + : Column(Columns::ColumnId_Charges, ColumnBase::Display_Integer) { - return record.get().mData.mCharge; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mCharge; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mCharge = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct AutoCalcColumn : public Column { - AutoCalcColumn() : Column (Columns::ColumnId_AutoCalc, ColumnBase::Display_Boolean) - {} - - QVariant get (const Record& record) const override + AutoCalcColumn() + : Column(Columns::ColumnId_AutoCalc, ColumnBase::Display_Boolean) { - return record.get().mData.mAutocalc!=0; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mAutocalc != 0; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mAutocalc = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct ModelColumn : public Column { - ModelColumn() : Column (Columns::ColumnId_Model, ColumnBase::Display_Mesh) {} - - QVariant get (const Record& record) const override + ModelColumn() + : Column(Columns::ColumnId_Model, ColumnBase::Display_Mesh) { - return QString::fromUtf8 (record.get().mModel.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mModel.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mModel = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct VampireColumn : public Column { - VampireColumn() : Column (Columns::ColumnId_Vampire, ColumnBase::Display_Boolean) - {} - - QVariant get (const Record& record) const override + VampireColumn() + : Column(Columns::ColumnId_Vampire, ColumnBase::Display_Boolean) { - return record.get().mData.mVampire!=0; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mVampire != 0; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mVampire = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct BodyPartTypeColumn : public Column { BodyPartTypeColumn() - : Column (Columns::ColumnId_BodyPartType, ColumnBase::Display_BodyPartType) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_BodyPartType, ColumnBase::Display_BodyPartType) { - return static_cast (record.get().mData.mPart); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mData.mPart); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mPart = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct MeshTypeColumn : public Column { MeshTypeColumn(int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) - : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType, flags) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_MeshType, ColumnBase::Display_MeshType, flags) { - return static_cast (record.get().mData.mType); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mData.mType); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mType = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct OwnerGlobalColumn : public Column { OwnerGlobalColumn() - : Column (Columns::ColumnId_OwnerGlobal, ColumnBase::Display_GlobalVariable) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_OwnerGlobal, ColumnBase::Display_GlobalVariable) { - return QString::fromUtf8 (record.get().mGlobalVariable.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mGlobalVariable.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mGlobalVariable = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct RefNumCounterColumn : public Column { RefNumCounterColumn() - : Column (Columns::ColumnId_RefNumCounter, ColumnBase::Display_Integer, 0) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_RefNumCounter, ColumnBase::Display_Integer, 0) { - return static_cast (record.get().mRefNumCounter); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mRefNumCounter); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mRefNumCounter = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } - bool isUserEditable() const override - { - return false; - } + bool isUserEditable() const override { return false; } }; - template + template struct RefNumColumn : public Column { RefNumColumn() - : Column (Columns::ColumnId_RefNum, ColumnBase::Display_Integer, 0) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_RefNum, ColumnBase::Display_Integer, 0) { - return static_cast (record.get().mRefNum.mIndex); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return static_cast(record.get().mRefNum.mIndex); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mRefNum.mIndex = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } - bool isUserEditable() const override - { - return false; - } + bool isUserEditable() const override { return false; } }; - template + template struct SoundColumn : public Column { SoundColumn() - : Column (Columns::ColumnId_Sound, ColumnBase::Display_Sound) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Sound, ColumnBase::Display_Sound) { - return QString::fromUtf8 (record.get().mSound.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mSound.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mSound = data.toString().toUtf8().constData(); + record2.mSound = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct CreatureColumn : public Column { CreatureColumn() - : Column (Columns::ColumnId_Creature, ColumnBase::Display_Creature) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Creature, ColumnBase::Display_Creature) { - return QString::fromUtf8 (record.get().mCreature.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mCreature.getRefIdString().c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mCreature = data.toString().toUtf8().constData(); + record2.mCreature = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SoundGeneratorTypeColumn : public Column { SoundGeneratorTypeColumn() - : Column (Columns::ColumnId_SoundGeneratorType, ColumnBase::Display_SoundGeneratorType) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_SoundGeneratorType, ColumnBase::Display_SoundGeneratorType) { - return static_cast (record.get().mType); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return static_cast(record.get().mType); } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mType = data.toInt(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct BaseCostColumn : public Column { - BaseCostColumn() : Column (Columns::ColumnId_BaseCost, ColumnBase::Display_Float) {} - - QVariant get (const Record& record) const override + BaseCostColumn() + : Column(Columns::ColumnId_BaseCost, ColumnBase::Display_Float) { - return record.get().mData.mBaseCost; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override { return record.get().mData.mBaseCost; } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mData.mBaseCost = data.toFloat(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct SchoolColumn : public Column { SchoolColumn() - : Column (Columns::ColumnId_School, ColumnBase::Display_School) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_School, ColumnBase::Display_School) { - return record.get().mData.mSchool; } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return ESM::MagicSchool::skillRefIdToIndex(record.get().mData.mSchool); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mData.mSchool = data.toInt(); + record2.mData.mSchool = ESM::MagicSchool::indexToSkillRefId(data.toInt()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct EffectTextureColumn : public Column { - EffectTextureColumn (Columns::ColumnId columnId) - : Column (columnId, - columnId == Columns::ColumnId_Particle ? ColumnBase::Display_Texture - : ColumnBase::Display_Icon) + EffectTextureColumn(Columns::ColumnId columnId) + : Column(columnId, + columnId == Columns::ColumnId_Particle ? ColumnBase::Display_Texture : ColumnBase::Display_Icon) { - assert (this->mColumnId==Columns::ColumnId_Icon || - this->mColumnId==Columns::ColumnId_Particle); + assert(this->mColumnId == Columns::ColumnId_Icon || this->mColumnId == Columns::ColumnId_Particle); } - QVariant get (const Record& record) const override + QVariant get(const Record& record) const override { - return QString::fromUtf8 ( - (this->mColumnId==Columns::ColumnId_Icon ? - record.get().mIcon : record.get().mParticle).c_str()); + return QString::fromUtf8( + (this->mColumnId == Columns::ColumnId_Icon ? record.get().mIcon : record.get().mParticle).c_str()); } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - (this->mColumnId==Columns::ColumnId_Icon ? - record2.mIcon : record2.mParticle) + (this->mColumnId == Columns::ColumnId_Icon ? record2.mIcon : record2.mParticle) = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct EffectObjectColumn : public Column { - EffectObjectColumn (Columns::ColumnId columnId) - : Column (columnId, columnId==Columns::ColumnId_BoltObject ? ColumnBase::Display_Weapon : ColumnBase::Display_Static) + EffectObjectColumn(Columns::ColumnId columnId) + : Column(columnId, + columnId == Columns::ColumnId_BoltObject ? ColumnBase::Display_Weapon : ColumnBase::Display_Static) { - assert (this->mColumnId==Columns::ColumnId_CastingObject || - this->mColumnId==Columns::ColumnId_HitObject || - this->mColumnId==Columns::ColumnId_AreaObject || - this->mColumnId==Columns::ColumnId_BoltObject); + assert(this->mColumnId == Columns::ColumnId_CastingObject || this->mColumnId == Columns::ColumnId_HitObject + || this->mColumnId == Columns::ColumnId_AreaObject || this->mColumnId == Columns::ColumnId_BoltObject); } - QVariant get (const Record& record) const override + QVariant get(const Record& record) const override { - const std::string *string = nullptr; + const ESM::RefId* string = nullptr; switch (this->mColumnId) { - case Columns::ColumnId_CastingObject: string = &record.get().mCasting; break; - case Columns::ColumnId_HitObject: string = &record.get().mHit; break; - case Columns::ColumnId_AreaObject: string = &record.get().mArea; break; - case Columns::ColumnId_BoltObject: string = &record.get().mBolt; break; + case Columns::ColumnId_CastingObject: + string = &record.get().mCasting; + break; + case Columns::ColumnId_HitObject: + string = &record.get().mHit; + break; + case Columns::ColumnId_AreaObject: + string = &record.get().mArea; + break; + case Columns::ColumnId_BoltObject: + string = &record.get().mBolt; + break; } if (!string) - throw std::logic_error ("Unsupported column ID"); + throw std::logic_error("Unsupported column ID"); - return QString::fromUtf8 (string->c_str()); + return QString::fromUtf8(string->getRefIdString().c_str()); } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { - std::string *string = nullptr; + ESM::RefId* id = nullptr; ESXRecordT record2 = record.get(); switch (this->mColumnId) { - case Columns::ColumnId_CastingObject: string = &record2.mCasting; break; - case Columns::ColumnId_HitObject: string = &record2.mHit; break; - case Columns::ColumnId_AreaObject: string = &record2.mArea; break; - case Columns::ColumnId_BoltObject: string = &record2.mBolt; break; + case Columns::ColumnId_CastingObject: + id = &record2.mCasting; + break; + case Columns::ColumnId_HitObject: + id = &record2.mHit; + break; + case Columns::ColumnId_AreaObject: + id = &record2.mArea; + break; + case Columns::ColumnId_BoltObject: + id = &record2.mBolt; + break; } - if (!string) - throw std::logic_error ("Unsupported column ID"); + if (!id) + throw std::logic_error("Unsupported column ID"); - *string = data.toString().toUtf8().constData(); + *id = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct EffectSoundColumn : public Column { - EffectSoundColumn (Columns::ColumnId columnId) - : Column (columnId, ColumnBase::Display_Sound) + EffectSoundColumn(Columns::ColumnId columnId) + : Column(columnId, ColumnBase::Display_Sound) { - assert (this->mColumnId==Columns::ColumnId_CastingSound || - this->mColumnId==Columns::ColumnId_HitSound || - this->mColumnId==Columns::ColumnId_AreaSound || - this->mColumnId==Columns::ColumnId_BoltSound); + assert(this->mColumnId == Columns::ColumnId_CastingSound || this->mColumnId == Columns::ColumnId_HitSound + || this->mColumnId == Columns::ColumnId_AreaSound || this->mColumnId == Columns::ColumnId_BoltSound); } - QVariant get (const Record& record) const override + QVariant get(const Record& record) const override { - const std::string *string = nullptr; + const ESM::RefId* id = nullptr; switch (this->mColumnId) { - case Columns::ColumnId_CastingSound: string = &record.get().mCastSound; break; - case Columns::ColumnId_HitSound: string = &record.get().mHitSound; break; - case Columns::ColumnId_AreaSound: string = &record.get().mAreaSound; break; - case Columns::ColumnId_BoltSound: string = &record.get().mBoltSound; break; + case Columns::ColumnId_CastingSound: + id = &record.get().mCastSound; + break; + case Columns::ColumnId_HitSound: + id = &record.get().mHitSound; + break; + case Columns::ColumnId_AreaSound: + id = &record.get().mAreaSound; + break; + case Columns::ColumnId_BoltSound: + id = &record.get().mBoltSound; + break; } - if (!string) - throw std::logic_error ("Unsupported column ID"); + if (!id) + throw std::logic_error("Unsupported column ID"); - return QString::fromUtf8 (string->c_str()); + return QString::fromUtf8(id->getRefIdString().c_str()); } - void set (Record& record, const QVariant& data) override + void set(Record& record, const QVariant& data) override { - std::string *string = nullptr; + ESM::RefId* id = nullptr; ESXRecordT record2 = record.get(); switch (this->mColumnId) { - case Columns::ColumnId_CastingSound: string = &record2.mCastSound; break; - case Columns::ColumnId_HitSound: string = &record2.mHitSound; break; - case Columns::ColumnId_AreaSound: string = &record2.mAreaSound; break; - case Columns::ColumnId_BoltSound: string = &record2.mBoltSound; break; + case Columns::ColumnId_CastingSound: + id = &record2.mCastSound; + break; + case Columns::ColumnId_HitSound: + id = &record2.mHitSound; + break; + case Columns::ColumnId_AreaSound: + id = &record2.mAreaSound; + break; + case Columns::ColumnId_BoltSound: + id = &record2.mBoltSound; + break; } - if (!string) - throw std::logic_error ("Unsupported column ID"); + if (!id) + throw std::logic_error("Unsupported column ID"); - *string = data.toString().toUtf8().constData(); + *id = ESM::RefId::stringRefId(data.toString().toUtf8().constData()); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct FormatColumn : public Column { FormatColumn() - : Column (Columns::ColumnId_FileFormat, ColumnBase::Display_Integer) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_FileFormat, ColumnBase::Display_Integer) { - return record.get().mFormat; } - bool isEditable() const override - { - return false; - } + QVariant get(const Record& record) const override { return record.get().mFormatVersion; } + + bool isEditable() const override { return false; } }; - template + template struct AuthorColumn : public Column { AuthorColumn() - : Column (Columns::ColumnId_Author, ColumnBase::Display_String32) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_Author, ColumnBase::Display_String32) { - return QString::fromUtf8 (record.get().mAuthor.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mAuthor.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mAuthor = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; - template + template struct FileDescriptionColumn : public Column { FileDescriptionColumn() - : Column (Columns::ColumnId_FileDescription, ColumnBase::Display_LongString256) - {} - - QVariant get (const Record& record) const override + : Column(Columns::ColumnId_FileDescription, ColumnBase::Display_LongString256) { - return QString::fromUtf8 (record.get().mDescription.c_str()); } - void set (Record& record, const QVariant& data) override + QVariant get(const Record& record) const override + { + return QString::fromUtf8(record.get().mDescription.c_str()); + } + + void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); record2.mDescription = data.toString().toUtf8().constData(); - record.setModified (record2); + record.setModified(record2); } - bool isEditable() const override - { - return true; - } + bool isEditable() const override { return true; } }; struct LandTextureNicknameColumn : public Column @@ -2507,12 +2367,12 @@ namespace CSMWorld struct BodyPartRaceColumn : public RaceColumn { - const MeshTypeColumn *mMeshType; + const MeshTypeColumn* mMeshType; - BodyPartRaceColumn(const MeshTypeColumn *meshType); + BodyPartRaceColumn(const MeshTypeColumn* meshType); - QVariant get(const Record &record) const override; - void set(Record &record, const QVariant &data) override; + QVariant get(const Record& record) const override; + void set(Record& record, const QVariant& data) override; bool isEditable() const override; }; } diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index eaea66c2f..4a476b52f 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -1,10 +1,13 @@ #include "columns.hpp" -#include -#include +#include +#include + +#include +#include -#include "universalid.hpp" #include "infoselectwrapper.hpp" +#include "universalid.hpp" namespace CSMWorld { @@ -13,11 +16,10 @@ namespace CSMWorld struct ColumnDesc { int mId; - const char *mName; + const char* mName; }; - const ColumnDesc sNames[] = - { + const ColumnDesc sNames[] = { { ColumnId_Value, "Value" }, { ColumnId_Id, "ID" }, { ColumnId_Modification, "Modified" }, @@ -175,7 +177,7 @@ namespace CSMWorld { ColumnId_ContainerContent, "Content" }, { ColumnId_ItemCount, "Count" }, - { ColumnId_InventoryItemId, "Item ID"}, + { ColumnId_InventoryItemId, "Item ID" }, { ColumnId_CombatState, "Combat" }, { ColumnId_MagicState, "Magic" }, @@ -187,16 +189,16 @@ namespace CSMWorld { ColumnId_ActorInventory, "Inventory" }, { ColumnId_SpellList, "Spells" }, - { ColumnId_SpellId, "Spell ID"}, + { ColumnId_SpellId, "Spell ID" }, { ColumnId_NpcDestinations, "Destinations" }, - { ColumnId_DestinationCell, "Dest Cell"}, - { ColumnId_PosX, "Dest X"}, - { ColumnId_PosY, "Dest Y"}, - { ColumnId_PosZ, "Dest Z"}, - { ColumnId_RotX, "Rotation X"}, - { ColumnId_RotY, "Rotation Y"}, - { ColumnId_RotZ, "Rotation Z"}, + { ColumnId_DestinationCell, "Dest Cell" }, + { ColumnId_PosX, "Dest X" }, + { ColumnId_PosY, "Dest Y" }, + { ColumnId_PosZ, "Dest Z" }, + { ColumnId_RotX, "Rotation X" }, + { ColumnId_RotY, "Rotation Y" }, + { ColumnId_RotZ, "Rotation Z" }, { ColumnId_OwnerGlobal, "Owner Global" }, { ColumnId_DefaultProfile, "Default Profile" }, @@ -255,7 +257,7 @@ namespace CSMWorld { ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiWanderToD, "Wander ToD" }, - { ColumnId_AiWanderRepeat, "Wander Repeat" }, + { ColumnId_AiWanderRepeat, "Ai Repeat" }, { ColumnId_AiActivateName, "Activate" }, { ColumnId_AiTargetId, "Target ID" }, { ColumnId_AiTargetCell, "Target Cell" }, @@ -265,11 +267,11 @@ namespace CSMWorld { ColumnId_PartRefMale, "Male Part" }, { ColumnId_PartRefFemale, "Female Part" }, - { ColumnId_LevelledList,"Levelled List" }, - { ColumnId_LevelledItemId,"Levelled Item" }, - { ColumnId_LevelledItemLevel,"Item Level" }, + { ColumnId_LevelledList, "Levelled List" }, + { ColumnId_LevelledItemId, "Levelled Item" }, + { ColumnId_LevelledItemLevel, "PC Level" }, { ColumnId_LevelledItemType, "Calculate all levels <= player" }, - { ColumnId_LevelledItemTypeEach, "Select a new item each instance" }, + { ColumnId_LevelledItemTypeEach, "Select a new item for each instance" }, { ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_PowerList, "Powers" }, @@ -294,7 +296,6 @@ namespace CSMWorld { ColumnId_NpcReputation, "Reputation" }, { ColumnId_NpcRank, "NPC Rank" }, { ColumnId_Gold, "Gold" }, - { ColumnId_NpcPersistence, "Persistent" }, { ColumnId_RaceAttributes, "Race Attributes" }, { ColumnId_Male, "Male" }, @@ -371,26 +372,30 @@ namespace CSMWorld { ColumnId_Skill6, "Skill 6" }, { ColumnId_Skill7, "Skill 7" }, - { -1, 0 } // end marker + { ColumnId_Persistent, "Persistent" }, + { ColumnId_Blocked, "Blocked" }, + + { ColumnId_LevelledCreatureId, "Levelled Creature" }, + + // end marker + { -1, 0 }, }; } } -std::string CSMWorld::Columns::getName (ColumnId column) +std::string CSMWorld::Columns::getName(ColumnId column) { - for (int i=0; sNames[i].mName; ++i) - if (column==sNames[i].mId) + for (int i = 0; sNames[i].mName; ++i) + if (column == sNames[i].mId) return sNames[i].mName; return ""; } -int CSMWorld::Columns::getId (const std::string& name) +int CSMWorld::Columns::getId(const std::string& name) { - std::string name2 = Misc::StringUtils::lowerCase (name); - - for (int i=0; sNames[i].mName; ++i) - if (Misc::StringUtils::ciEqual(sNames[i].mName, name2)) + for (int i = 0; sNames[i].mName; ++i) + if (Misc::StringUtils::ciEqual(std::string_view(sNames[i].mName), name)) return sNames[i].mId; return -1; @@ -398,246 +403,191 @@ int CSMWorld::Columns::getId (const std::string& name) namespace { - static const char *sSpecialisations[] = - { - "Combat", "Magic", "Stealth", 0 - }; + static const char* sSpecialisations[] = { "Combat", "Magic", "Stealth", 0 }; - // see ESM::Attribute::AttributeID in - static const char *sAttributes[] = - { - "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", - "Luck", 0 - }; + // see ESM::Attribute::AttributeID in + static const char* sAttributes[] + = { "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", "Luck", 0 }; - static const char *sSpellTypes[] = - { - "Spell", "Ability", "Blight", "Disease", "Curse", "Power", 0 - }; + static const char* sSpellTypes[] = { "Spell", "Ability", "Blight", "Disease", "Curse", "Power", 0 }; - static const char *sApparatusTypes[] = - { - "Mortar & Pestle", "Alembic", "Calcinator", "Retort", 0 - }; + static const char* sApparatusTypes[] = { "Mortar & Pestle", "Alembic", "Calcinator", "Retort", 0 }; - static const char *sArmorTypes[] = - { - "Helmet", "Cuirass", "Left Pauldron", "Right Pauldron", "Greaves", "Boots", "Left Gauntlet", - "Right Gauntlet", "Shield", "Left Bracer", "Right Bracer", 0 - }; + static const char* sArmorTypes[] = { "Helmet", "Cuirass", "Left Pauldron", "Right Pauldron", "Greaves", "Boots", + "Left Gauntlet", "Right Gauntlet", "Shield", "Left Bracer", "Right Bracer", 0 }; - static const char *sClothingTypes[] = - { - "Pants", "Shoes", "Shirt", "Belt", "Robe", "Right Glove", "Left Glove", "Skirt", "Ring", - "Amulet", 0 - }; + static const char* sClothingTypes[] + = { "Pants", "Shoes", "Shirt", "Belt", "Robe", "Right Glove", "Left Glove", "Skirt", "Ring", "Amulet", 0 }; - static const char *sCreatureTypes[] = - { - "Creature", "Daedra", "Undead", "Humanoid", 0 - }; + static const char* sCreatureTypes[] = { "Creature", "Daedra", "Undead", "Humanoid", 0 }; - static const char *sWeaponTypes[] = - { - "Short Blade 1H", "Long Blade 1H", "Long Blade 2H", "Blunt 1H", "Blunt 2H Close", - "Blunt 2H Wide", "Spear 2H", "Axe 1H", "Axe 2H", "Bow", "Crossbow", "Thrown", "Arrow", - "Bolt", 0 - }; + static const char* sWeaponTypes[] + = { "Short Blade 1H", "Long Blade 1H", "Long Blade 2H", "Blunt 1H", "Blunt 2H Close", "Blunt 2H Wide", + "Spear 2H", "Axe 1H", "Axe 2H", "Bow", "Crossbow", "Thrown", "Arrow", "Bolt", 0 }; - static const char *sModificationEnums[] = - { - "Base", "Modified", "Added", "Deleted", "Deleted", 0 - }; + static const char* sModificationEnums[] = { "Base", "Modified", "Added", "Deleted", "Deleted", 0 }; - static const char *sVarTypeEnums[] = - { - "unknown", "none", "short", "integer", "long", "float", "string", 0 - }; + static const char* sVarTypeEnums[] = { "unknown", "none", "short", "integer", "long", "float", "string", 0 }; - static const char *sDialogueTypeEnums[] = - { - "Topic", "Voice", "Greeting", "Persuasion", 0 - }; + static const char* sDialogueTypeEnums[] = { "Topic", "Voice", "Greeting", "Persuasion", 0 }; - static const char *sQuestStatusTypes[] = - { - "None", "Name", "Finished", "Restart", 0 - }; + static const char* sQuestStatusTypes[] = { "None", "Name", "Finished", "Restart", 0 }; - static const char *sGenderEnums[] = - { - "Male", "Female", 0 - }; + static const char* sGenderEnums[] = { "Male", "Female", 0 }; - static const char *sEnchantmentTypes[] = - { - "Cast Once", "When Strikes", "When Used", "Constant Effect", 0 - }; + static const char* sEnchantmentTypes[] = { "Cast Once", "When Strikes", "When Used", "Constant Effect", 0 }; - static const char *sBodyPartTypes[] = - { - "Head", "Hair", "Neck", "Chest", "Groin", "Hand", "Wrist", "Forearm", "Upper Arm", - "Foot", "Ankle", "Knee", "Upper Leg", "Clavicle", "Tail", 0 - }; + static const char* sBodyPartTypes[] = { "Head", "Hair", "Neck", "Chest", "Groin", "Hand", "Wrist", "Forearm", + "Upper Arm", "Foot", "Ankle", "Knee", "Upper Leg", "Clavicle", "Tail", 0 }; - static const char *sMeshTypes[] = - { - "Skin", "Clothing", "Armour", 0 - }; + static const char* sMeshTypes[] = { "Skin", "Clothing", "Armour", 0 }; - static const char *sSoundGeneratorType[] = - { - "Left Foot", "Right Foot", "Swim Left", "Swim Right", "Moan", "Roar", "Scream", - "Land", 0 - }; + static const char* sSoundGeneratorType[] + = { "Left Foot", "Right Foot", "Swim Left", "Swim Right", "Moan", "Roar", "Scream", "Land", 0 }; - static const char *sSchools[] = - { - "Alteration", "Conjuration", "Destruction", "Illusion", "Mysticism", "Restoration", 0 - }; + static const char* sSchools[] + = { "Alteration", "Conjuration", "Destruction", "Illusion", "Mysticism", "Restoration", 0 }; - // impact from magic effects, see ESM::Skill::SkillEnum in - static const char *sSkills[] = - { - "Block", "Armorer", "MediumArmor", "HeavyArmor", "BluntWeapon", - "LongBlade", "Axe", "Spear", "Athletics", "Enchant", - "Destruction", "Alteration", "Illusion", "Conjuration", "Mysticism", - "Restoration", "Alchemy", "Unarmored", "Security", "Sneak", - "Acrobatics", "LightArmor", "ShortBlade", "Marksman", "Mercantile", - "Speechcraft", "HandToHand", 0 - }; + // impact from magic effects, see ESM::Skill::SkillEnum in + static const char* sSkills[] = { "Block", "Armorer", "MediumArmor", "HeavyArmor", "BluntWeapon", "LongBlade", "Axe", + "Spear", "Athletics", "Enchant", "Destruction", "Alteration", "Illusion", "Conjuration", "Mysticism", + "Restoration", "Alchemy", "Unarmored", "Security", "Sneak", "Acrobatics", "LightArmor", "ShortBlade", + "Marksman", "Mercantile", "Speechcraft", "HandToHand", 0 }; - // range of magic effects, see ESM::RangeType in - static const char *sEffectRange[] = - { - "Self", "Touch", "Target", 0 - }; + // range of magic effects, see ESM::RangeType in + static const char* sEffectRange[] = { "Self", "Touch", "Target", 0 }; - // magic effect names, see ESM::MagicEffect::Effects in - static const char *sEffectId[] = - { - "WaterBreathing", "SwiftSwim", "WaterWalking", "Shield", "FireShield", - "LightningShield", "FrostShield", "Burden", "Feather", "Jump", - "Levitate", "SlowFall", "Lock", "Open", "FireDamage", - "ShockDamage", "FrostDamage", "DrainAttribute", "DrainHealth", "DrainMagicka", - "DrainFatigue", "DrainSkill", "DamageAttribute", "DamageHealth", "DamageMagicka", - "DamageFatigue", "DamageSkill", "Poison", "WeaknessToFire", "WeaknessToFrost", - "WeaknessToShock", "WeaknessToMagicka", "WeaknessToCommonDisease", "WeaknessToBlightDisease", "WeaknessToCorprusDisease", - "WeaknessToPoison", "WeaknessToNormalWeapons", "DisintegrateWeapon", "DisintegrateArmor", "Invisibility", - "Chameleon", "Light", "Sanctuary", "NightEye", "Charm", - "Paralyze", "Silence", "Blind", "Sound", "CalmHumanoid", - "CalmCreature", "FrenzyHumanoid", "FrenzyCreature", "DemoralizeHumanoid", "DemoralizeCreature", - "RallyHumanoid", "RallyCreature", "Dispel", "Soultrap", "Telekinesis", - "Mark", "Recall", "DivineIntervention", "AlmsiviIntervention", "DetectAnimal", - "DetectEnchantment", "DetectKey", "SpellAbsorption", "Reflect", "CureCommonDisease", - "CureBlightDisease", "CureCorprusDisease", "CurePoison", "CureParalyzation", "RestoreAttribute", - "RestoreHealth", "RestoreMagicka", "RestoreFatigue", "RestoreSkill", "FortifyAttribute", - "FortifyHealth", "FortifyMagicka", "FortifyFatigue", "FortifySkill", "FortifyMaximumMagicka", - "AbsorbAttribute", "AbsorbHealth", "AbsorbMagicka", "AbsorbFatigue", "AbsorbSkill", - "ResistFire", "ResistFrost", "ResistShock", "ResistMagicka", "ResistCommonDisease", + // magic effect names, see ESM::MagicEffect::Effects in + static const char* sEffectId[] = { "WaterBreathing", "SwiftSwim", "WaterWalking", "Shield", "FireShield", + "LightningShield", "FrostShield", "Burden", "Feather", "Jump", "Levitate", "SlowFall", "Lock", "Open", + "FireDamage", "ShockDamage", "FrostDamage", "DrainAttribute", "DrainHealth", "DrainMagicka", "DrainFatigue", + "DrainSkill", "DamageAttribute", "DamageHealth", "DamageMagicka", "DamageFatigue", "DamageSkill", "Poison", + "WeaknessToFire", "WeaknessToFrost", "WeaknessToShock", "WeaknessToMagicka", "WeaknessToCommonDisease", + "WeaknessToBlightDisease", "WeaknessToCorprusDisease", "WeaknessToPoison", "WeaknessToNormalWeapons", + "DisintegrateWeapon", "DisintegrateArmor", "Invisibility", "Chameleon", "Light", "Sanctuary", "NightEye", + "Charm", "Paralyze", "Silence", "Blind", "Sound", "CalmHumanoid", "CalmCreature", "FrenzyHumanoid", + "FrenzyCreature", "DemoralizeHumanoid", "DemoralizeCreature", "RallyHumanoid", "RallyCreature", "Dispel", + "Soultrap", "Telekinesis", "Mark", "Recall", "DivineIntervention", "AlmsiviIntervention", "DetectAnimal", + "DetectEnchantment", "DetectKey", "SpellAbsorption", "Reflect", "CureCommonDisease", "CureBlightDisease", + "CureCorprusDisease", "CurePoison", "CureParalyzation", "RestoreAttribute", "RestoreHealth", "RestoreMagicka", + "RestoreFatigue", "RestoreSkill", "FortifyAttribute", "FortifyHealth", "FortifyMagicka", "FortifyFatigue", + "FortifySkill", "FortifyMaximumMagicka", "AbsorbAttribute", "AbsorbHealth", "AbsorbMagicka", "AbsorbFatigue", + "AbsorbSkill", "ResistFire", "ResistFrost", "ResistShock", "ResistMagicka", "ResistCommonDisease", "ResistBlightDisease", "ResistCorprusDisease", "ResistPoison", "ResistNormalWeapons", "ResistParalysis", - "RemoveCurse", "TurnUndead", "SummonScamp", "SummonClannfear", "SummonDaedroth", - "SummonDremora", "SummonAncestralGhost", "SummonSkeletalMinion", "SummonBonewalker", "SummonGreaterBonewalker", - "SummonBonelord", "SummonWingedTwilight", "SummonHunger", "SummonGoldenSaint", "SummonFlameAtronach", - "SummonFrostAtronach", "SummonStormAtronach", "FortifyAttack", "CommandCreature", "CommandHumanoid", - "BoundDagger", "BoundLongsword", "BoundMace", "BoundBattleAxe", "BoundSpear", - "BoundLongbow", "ExtraSpell", "BoundCuirass", "BoundHelm", "BoundBoots", - "BoundShield", "BoundGloves", "Corprus", "Vampirism", "SummonCenturionSphere", - "SunDamage", "StuntedMagicka", "SummonFabricant", "SummonWolf", "SummonBear", - "SummonBonewolf", "SummonCreature04", "SummonCreature05", 0 - }; + "RemoveCurse", "TurnUndead", "SummonScamp", "SummonClannfear", "SummonDaedroth", "SummonDremora", + "SummonAncestralGhost", "SummonSkeletalMinion", "SummonBonewalker", "SummonGreaterBonewalker", "SummonBonelord", + "SummonWingedTwilight", "SummonHunger", "SummonGoldenSaint", "SummonFlameAtronach", "SummonFrostAtronach", + "SummonStormAtronach", "FortifyAttack", "CommandCreature", "CommandHumanoid", "BoundDagger", "BoundLongsword", + "BoundMace", "BoundBattleAxe", "BoundSpear", "BoundLongbow", "ExtraSpell", "BoundCuirass", "BoundHelm", + "BoundBoots", "BoundShield", "BoundGloves", "Corprus", "Vampirism", "SummonCenturionSphere", "SunDamage", + "StuntedMagicka", "SummonFabricant", "SummonWolf", "SummonBear", "SummonBonewolf", "SummonCreature04", + "SummonCreature05", 0 }; - // see ESM::PartReferenceType in - static const char *sPartRefType[] = - { - "Head", "Hair", "Neck", "Cuirass", "Groin", - "Skirt", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", - "Shield", "Right Forearm", "Left Forearm", "Right Upperarm", "Left Upperarm", - "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", - "Left Knee", "Right Leg", "Left Leg", "Right Pauldron", "Left Pauldron", - "Weapon", "Tail", 0 - }; + // see ESM::PartReferenceType in + static const char* sPartRefType[] = { "Head", "Hair", "Neck", "Cuirass", "Groin", "Skirt", "Right Hand", + "Left Hand", "Right Wrist", "Left Wrist", "Shield", "Right Forearm", "Left Forearm", "Right Upperarm", + "Left Upperarm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Leg", + "Left Leg", "Right Pauldron", "Left Pauldron", "Weapon", "Tail", 0 }; - // see the enums in - static const char *sAiPackageType[] = - { - "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 - }; + // see the enums in + static const char* sAiPackageType[] = { "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sBookType[] = - { - "Book", "Scroll", 0 - }; + static const char* sBookType[] = { "Book", "Scroll", 0 }; - static const char *sEmitterType[] = - { - "", "Flickering", "Flickering (Slow)", "Pulsing", "Pulsing (Slow)", 0 - }; + static const char* sEmitterType[] = { "", "Flickering", "Flickering (Slow)", "Pulsing", "Pulsing (Slow)", 0 }; - const char **getEnumNames (CSMWorld::Columns::ColumnId column) + const char** getEnumNames(CSMWorld::Columns::ColumnId column) { switch (column) { - case CSMWorld::Columns::ColumnId_Specialisation: return sSpecialisations; - case CSMWorld::Columns::ColumnId_Attribute: return sAttributes; - case CSMWorld::Columns::ColumnId_SpellType: return sSpellTypes; - case CSMWorld::Columns::ColumnId_ApparatusType: return sApparatusTypes; - case CSMWorld::Columns::ColumnId_ArmorType: return sArmorTypes; - case CSMWorld::Columns::ColumnId_ClothingType: return sClothingTypes; - case CSMWorld::Columns::ColumnId_CreatureType: return sCreatureTypes; - case CSMWorld::Columns::ColumnId_WeaponType: return sWeaponTypes; - case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums; - case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums; - case CSMWorld::Columns::ColumnId_DialogueType: return sDialogueTypeEnums; - case CSMWorld::Columns::ColumnId_QuestStatusType: return sQuestStatusTypes; - case CSMWorld::Columns::ColumnId_Gender: return sGenderEnums; - case CSMWorld::Columns::ColumnId_EnchantmentType: return sEnchantmentTypes; - case CSMWorld::Columns::ColumnId_BodyPartType: return sBodyPartTypes; - case CSMWorld::Columns::ColumnId_MeshType: return sMeshTypes; - case CSMWorld::Columns::ColumnId_SoundGeneratorType: return sSoundGeneratorType; - case CSMWorld::Columns::ColumnId_School: return sSchools; - case CSMWorld::Columns::ColumnId_Skill: return sSkills; - case CSMWorld::Columns::ColumnId_EffectRange: return sEffectRange; - case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; - case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; - case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; - case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; - case CSMWorld::Columns::ColumnId_BookType: return sBookType; - case CSMWorld::Columns::ColumnId_EmitterType: return sEmitterType; + case CSMWorld::Columns::ColumnId_Specialisation: + return sSpecialisations; + case CSMWorld::Columns::ColumnId_Attribute: + return sAttributes; + case CSMWorld::Columns::ColumnId_SpellType: + return sSpellTypes; + case CSMWorld::Columns::ColumnId_ApparatusType: + return sApparatusTypes; + case CSMWorld::Columns::ColumnId_ArmorType: + return sArmorTypes; + case CSMWorld::Columns::ColumnId_ClothingType: + return sClothingTypes; + case CSMWorld::Columns::ColumnId_CreatureType: + return sCreatureTypes; + case CSMWorld::Columns::ColumnId_WeaponType: + return sWeaponTypes; + case CSMWorld::Columns::ColumnId_Modification: + return sModificationEnums; + case CSMWorld::Columns::ColumnId_ValueType: + return sVarTypeEnums; + case CSMWorld::Columns::ColumnId_DialogueType: + return sDialogueTypeEnums; + case CSMWorld::Columns::ColumnId_QuestStatusType: + return sQuestStatusTypes; + case CSMWorld::Columns::ColumnId_Gender: + return sGenderEnums; + case CSMWorld::Columns::ColumnId_EnchantmentType: + return sEnchantmentTypes; + case CSMWorld::Columns::ColumnId_BodyPartType: + return sBodyPartTypes; + case CSMWorld::Columns::ColumnId_MeshType: + return sMeshTypes; + case CSMWorld::Columns::ColumnId_SoundGeneratorType: + return sSoundGeneratorType; + case CSMWorld::Columns::ColumnId_School: + return sSchools; + case CSMWorld::Columns::ColumnId_Skill: + return sSkills; + case CSMWorld::Columns::ColumnId_EffectRange: + return sEffectRange; + case CSMWorld::Columns::ColumnId_EffectId: + return sEffectId; + case CSMWorld::Columns::ColumnId_PartRefType: + return sPartRefType; + case CSMWorld::Columns::ColumnId_AiPackageType: + return sAiPackageType; + case CSMWorld::Columns::ColumnId_InfoCondFunc: + return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; + case CSMWorld::Columns::ColumnId_InfoCondComp: + return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; + case CSMWorld::Columns::ColumnId_BookType: + return sBookType; + case CSMWorld::Columns::ColumnId_EmitterType: + return sEmitterType; - default: return 0; + default: + return 0; } } } -bool CSMWorld::Columns::hasEnums (ColumnId column) +bool CSMWorld::Columns::hasEnums(ColumnId column) { - return getEnumNames (column)!=0 || column==ColumnId_RecordType; + return getEnumNames(column) != 0 || column == ColumnId_RecordType; } -std::vector>CSMWorld::Columns::getEnums (ColumnId column) +std::vector> CSMWorld::Columns::getEnums(ColumnId column) { - std::vector> enums; + std::vector> enums; - if (const char **table = getEnumNames (column)) - for (int i=0; table[i]; ++i) + if (const char** table = getEnumNames(column)) + for (int i = 0; table[i]; ++i) enums.emplace_back(i, table[i]); - else if (column==ColumnId_BloodType) + else if (column == ColumnId_BloodType) { - for (int i=0; i<8; i++) + for (int i = 0; i < 8; i++) { - const std::string& bloodName = Fallback::Map::getString("Blood_Texture_Name_" + std::to_string(i)); + std::string_view bloodName = Fallback::Map::getString("Blood_Texture_Name_" + std::to_string(i)); if (!bloodName.empty()) enums.emplace_back(i, bloodName); } } - else if (column==ColumnId_RecordType) + else if (column == ColumnId_RecordType) { enums.emplace_back(UniversalId::Type_None, ""); // none - for (int i=UniversalId::Type_None+1; i (i)).getTypeName()); + for (int i = UniversalId::Type_None + 1; i < UniversalId::NumberOfTypes; ++i) + enums.emplace_back(i, UniversalId(static_cast(i)).getTypeName()); } return enums; diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index c85eaac5f..92f41a2f2 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -2,10 +2,9 @@ #define CSM_WOLRD_COLUMNS_H #include +#include #include -#include "columnbase.hpp" - namespace CSMWorld { namespace Columns @@ -280,7 +279,7 @@ namespace CSMWorld ColumnId_NpcReputation = 258, ColumnId_NpcRank = 259, ColumnId_Gold = 260, - ColumnId_NpcPersistence = 261, + // unused ColumnId_RaceAttributes = 262, ColumnId_Male = 263, @@ -343,6 +342,11 @@ namespace CSMWorld ColumnId_FactionAttrib1 = 311, ColumnId_FactionAttrib2 = 312, + ColumnId_Persistent = 313, + ColumnId_Blocked = 314, + + ColumnId_LevelledCreatureId = 315, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, @@ -380,14 +384,14 @@ namespace CSMWorld ColumnId_Skill7 = 0x50006 }; - std::string getName (ColumnId column); + std::string getName(ColumnId column); - int getId (const std::string& name); + int getId(const std::string& name); ///< Will return -1 for an invalid name. - bool hasEnums (ColumnId column); + bool hasEnums(ColumnId column); - std::vector> getEnums (ColumnId column); + std::vector> getEnums(ColumnId column); ///< Returns an empty vector, if \a column isn't an enum type column. } } diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 36b3ba2e0..b211477ca 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -1,51 +1,66 @@ #include "commanddispatcher.hpp" #include +#include #include +#include -#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include #include +#include #include "../doc/document.hpp" -#include "idtable.hpp" -#include "record.hpp" -#include "commands.hpp" -#include "idtableproxymodel.hpp" #include "commandmacro.hpp" +#include "commands.hpp" +#include "idtable.hpp" +#include "idtableproxymodel.hpp" +#include "record.hpp" std::vector CSMWorld::CommandDispatcher::getDeletableRecords() const { std::vector result; - IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + IdTable& model = dynamic_cast(*mDocument.getData().getTableModel(mId)); - int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification); + int stateColumnIndex = model.findColumnIndex(Columns::ColumnId_Modification); - for (std::vector::const_iterator iter (mSelection.begin()); - iter!=mSelection.end(); ++iter) + for (std::vector::const_iterator iter(mSelection.begin()); iter != mSelection.end(); ++iter) { - int row = model.getModelIndex (*iter, 0).row(); + int row = model.getModelIndex(*iter, 0).row(); // check record state - RecordBase::State state = static_cast ( - model.data (model.index (row, stateColumnIndex)).toInt()); + RecordBase::State state + = static_cast(model.data(model.index(row, stateColumnIndex)).toInt()); - if (state==RecordBase::State_Deleted) + if (state == RecordBase::State_Deleted) continue; // check other columns (only relevant for a subset of the tables) - int dialogueTypeIndex = model.searchColumnIndex (Columns::ColumnId_DialogueType); + int dialogueTypeIndex = model.searchColumnIndex(Columns::ColumnId_DialogueType); - if (dialogueTypeIndex!=-1) + if (dialogueTypeIndex != -1) { - int type = model.data (model.index (row, dialogueTypeIndex)).toInt(); + int type = model.data(model.index(row, dialogueTypeIndex)).toInt(); - if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) + if (type != ESM::Dialogue::Topic && type != ESM::Dialogue::Journal) continue; } - result.push_back (*iter); + result.push_back(*iter); } return result; @@ -55,51 +70,53 @@ std::vector CSMWorld::CommandDispatcher::getRevertableRecords() con { std::vector result; - IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + IdTable& model = dynamic_cast(*mDocument.getData().getTableModel(mId)); /// \todo Reverting temporarily disabled on tables that support reordering, because /// revert logic currently can not handle reordering. if (model.getFeatures() & IdTable::Feature_ReorderWithinTopic) return result; - int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification); + int stateColumnIndex = model.findColumnIndex(Columns::ColumnId_Modification); - for (std::vector::const_iterator iter (mSelection.begin()); - iter!=mSelection.end(); ++iter) + for (std::vector::const_iterator iter(mSelection.begin()); iter != mSelection.end(); ++iter) { - int row = model.getModelIndex (*iter, 0).row(); + int row = model.getModelIndex(*iter, 0).row(); // check record state - RecordBase::State state = static_cast ( - model.data (model.index (row, stateColumnIndex)).toInt()); + RecordBase::State state + = static_cast(model.data(model.index(row, stateColumnIndex)).toInt()); - if (state==RecordBase::State_BaseOnly) + if (state == RecordBase::State_BaseOnly) continue; - result.push_back (*iter); + result.push_back(*iter); } return result; } -CSMWorld::CommandDispatcher::CommandDispatcher (CSMDoc::Document& document, - const CSMWorld::UniversalId& id, QObject *parent) -: QObject (parent), mLocked (false), mDocument (document), mId (id) -{} +CSMWorld::CommandDispatcher::CommandDispatcher( + CSMDoc::Document& document, const CSMWorld::UniversalId& id, QObject* parent) + : QObject(parent) + , mLocked(false) + , mDocument(document) + , mId(id) +{ +} -void CSMWorld::CommandDispatcher::setEditLock (bool locked) +void CSMWorld::CommandDispatcher::setEditLock(bool locked) { mLocked = locked; } -void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) +void CSMWorld::CommandDispatcher::setSelection(const std::vector& selection) { mSelection = selection; - std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCaseInPlace); - std::sort (mSelection.begin(), mSelection.end()); + std::sort(mSelection.begin(), mSelection.end(), Misc::StringUtils::CiComp{}); } -void CSMWorld::CommandDispatcher::setExtendedTypes (const std::vector& types) +void CSMWorld::CommandDispatcher::setExtendedTypes(const std::vector& types) { mExtendedTypes = types; } @@ -109,7 +126,7 @@ bool CSMWorld::CommandDispatcher::canDelete() const if (mLocked) return false; - return getDeletableRecords().size()!=0; + return getDeletableRecords().size() != 0; } bool CSMWorld::CommandDispatcher::canRevert() const @@ -117,16 +134,16 @@ bool CSMWorld::CommandDispatcher::canRevert() const if (mLocked) return false; - return getRevertableRecords().size()!=0; + return getRevertableRecords().size() != 0; } std::vector CSMWorld::CommandDispatcher::getExtendedTypes() const { std::vector tables; - if (mId==UniversalId::Type_Cells) + if (mId == UniversalId::Type_Cells) { - tables.push_back (mId); + tables.push_back(mId); tables.emplace_back(UniversalId::Type_References); /// \todo add other cell-specific types } @@ -134,68 +151,59 @@ std::vector CSMWorld::CommandDispatcher::getExtendedTypes return tables; } -void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_) +void CSMWorld::CommandDispatcher::executeModify( + QAbstractItemModel* model, const QModelIndex& index, const QVariant& new_) { if (mLocked) return; std::unique_ptr modifyCell; - std::unique_ptr modifyDataRefNum; + int columnId = model->data(index, ColumnBase::Role_ColumnId).toInt(); - int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); - - if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos) + if (columnId == Columns::ColumnId_PositionXPos || columnId == Columns::ColumnId_PositionYPos) { - const float oldPosition = model->data (index).toFloat(); + const float oldPosition = model->data(index).toFloat(); // Modulate by cell size, update cell id if reference has been moved to a new cell if (std::abs(std::fmod(oldPosition, Constants::CellSizeInUnits)) - - std::abs(std::fmod(new_.toFloat(), Constants::CellSizeInUnits)) >= 0.5f) + - std::abs(std::fmod(new_.toFloat(), Constants::CellSizeInUnits)) + >= 0.5f) { - IdTableProxyModel *proxy = dynamic_cast (model); + IdTableProxyModel* proxy = dynamic_cast(model); - int row = proxy ? proxy->mapToSource (index).row() : index.row(); + int row = proxy ? proxy->mapToSource(index).row() : index.row(); // This is not guaranteed to be the same as \a model, since a proxy could be used. - IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel (mId)); + IdTable& model2 = dynamic_cast(*mDocument.getData().getTableModel(mId)); - int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell); + int cellColumn = model2.searchColumnIndex(Columns::ColumnId_Cell); - if (cellColumn!=-1) + if (cellColumn != -1) { - QModelIndex cellIndex = model2.index (row, cellColumn); + QModelIndex cellIndex = model2.index(row, cellColumn); - std::string cellId = model2.data (cellIndex).toString().toUtf8().data(); + std::string cellId = model2.data(cellIndex).toString().toUtf8().data(); - if (cellId.find ('#')!=std::string::npos) + if (cellId.find('#') != std::string::npos) { - // Need to recalculate the cell and (if necessary) clear the instance's refNum - modifyCell.reset (new UpdateCellCommand (model2, row)); - - // Not sure which model this should be applied to - int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum); - - if (refNumColumn!=-1) - modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0)); + // Need to recalculate the cell + modifyCell = std::make_unique(model2, row); } } } } - std::unique_ptr modifyData ( - new CSMWorld::ModifyCommand (*model, index, new_)); + auto modifyData = std::make_unique(*model, index, new_); if (modifyCell.get()) { - CommandMacro macro (mDocument.getUndoStack()); - macro.push (modifyData.release()); - macro.push (modifyCell.release()); - if (modifyDataRefNum.get()) - macro.push (modifyDataRefNum.release()); + CommandMacro macro(mDocument.getUndoStack()); + macro.push(modifyData.release()); + macro.push(modifyCell.release()); } else - mDocument.getUndoStack().push (modifyData.release()); + mDocument.getUndoStack().push(modifyData.release()); } void CSMWorld::CommandDispatcher::executeDelete() @@ -208,25 +216,26 @@ void CSMWorld::CommandDispatcher::executeDelete() if (rows.empty()) return; - IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + IdTable& model = dynamic_cast(*mDocument.getData().getTableModel(mId)); - int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); + int columnIndex = model.findColumnIndex(Columns::ColumnId_Id); - CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Delete multiple records" : ""); - for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + CommandMacro macro(mDocument.getUndoStack(), rows.size() > 1 ? "Delete multiple records" : ""); + for (std::vector::const_iterator iter(rows.begin()); iter != rows.end(); ++iter) { - std::string id = model.data (model.getModelIndex (*iter, columnIndex)). - toString().toUtf8().constData(); + std::string id = model.data(model.getModelIndex(*iter, columnIndex)).toString().toUtf8().constData(); if (mId.getType() == UniversalId::Type_Referenceables) { - macro.push (new CSMWorld::DeleteCommand (model, id, - static_cast(model.data (model.index ( - model.getModelIndex (id, columnIndex).row(), - model.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType))).toInt()))); + macro.push(new CSMWorld::DeleteCommand(model, id, + static_cast( + model + .data(model.index(model.getModelIndex(id, columnIndex).row(), + model.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType))) + .toInt()))); } else - mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (model, id)); + mDocument.getUndoStack().push(new CSMWorld::DeleteCommand(model, id)); } } @@ -240,50 +249,47 @@ void CSMWorld::CommandDispatcher::executeRevert() if (rows.empty()) return; - IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + IdTable& model = dynamic_cast(*mDocument.getData().getTableModel(mId)); - int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); + int columnIndex = model.findColumnIndex(Columns::ColumnId_Id); - CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Revert multiple records" : ""); - for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + CommandMacro macro(mDocument.getUndoStack(), rows.size() > 1 ? "Revert multiple records" : ""); + for (std::vector::const_iterator iter(rows.begin()); iter != rows.end(); ++iter) { - std::string id = model.data (model.getModelIndex (*iter, columnIndex)). - toString().toUtf8().constData(); + std::string id = model.data(model.getModelIndex(*iter, columnIndex)).toString().toUtf8().constData(); - macro.push (new CSMWorld::RevertCommand (model, id)); + macro.push(new CSMWorld::RevertCommand(model, id)); } } void CSMWorld::CommandDispatcher::executeExtendedDelete() { - CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended delete of multiple records") : ""); + CommandMacro macro( + mDocument.getUndoStack(), mExtendedTypes.size() > 1 ? tr("Extended delete of multiple records") : ""); - for (std::vector::const_iterator iter (mExtendedTypes.begin()); - iter!=mExtendedTypes.end(); ++iter) + for (std::vector::const_iterator iter(mExtendedTypes.begin()); iter != mExtendedTypes.end(); ++iter) { - if (*iter==mId) + if (*iter == mId) executeDelete(); - else if (*iter==UniversalId::Type_References) + else if (*iter == UniversalId::Type_References) { - IdTable& model = dynamic_cast ( - *mDocument.getData().getTableModel (*iter)); + IdTable& model = dynamic_cast(*mDocument.getData().getTableModel(*iter)); const RefCollection& collection = mDocument.getData().getReferences(); int size = collection.getSize(); - for (int i=size-1; i>=0; --i) + for (int i = size - 1; i >= 0; --i) { - const Record& record = collection.getRecord (i); + const Record& record = collection.getRecord(i); - if (record.mState==RecordBase::State_Deleted) + if (record.mState == RecordBase::State_Deleted) continue; - if (!std::binary_search (mSelection.begin(), mSelection.end(), - Misc::StringUtils::lowerCase (record.get().mCell))) + if (!std::binary_search(mSelection.begin(), mSelection.end(), record.get().mCell)) continue; - macro.push (new CSMWorld::DeleteCommand (model, record.get().mId)); + macro.push(new CSMWorld::DeleteCommand(model, record.get().mId.getRefIdString())); } } } @@ -291,31 +297,29 @@ void CSMWorld::CommandDispatcher::executeExtendedDelete() void CSMWorld::CommandDispatcher::executeExtendedRevert() { - CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended revert of multiple records") : ""); + CommandMacro macro( + mDocument.getUndoStack(), mExtendedTypes.size() > 1 ? tr("Extended revert of multiple records") : ""); - for (std::vector::const_iterator iter (mExtendedTypes.begin()); - iter!=mExtendedTypes.end(); ++iter) + for (std::vector::const_iterator iter(mExtendedTypes.begin()); iter != mExtendedTypes.end(); ++iter) { - if (*iter==mId) + if (*iter == mId) executeRevert(); - else if (*iter==UniversalId::Type_References) + else if (*iter == UniversalId::Type_References) { - IdTable& model = dynamic_cast ( - *mDocument.getData().getTableModel (*iter)); + IdTable& model = dynamic_cast(*mDocument.getData().getTableModel(*iter)); const RefCollection& collection = mDocument.getData().getReferences(); int size = collection.getSize(); - for (int i=size-1; i>=0; --i) + for (int i = size - 1; i >= 0; --i) { - const Record& record = collection.getRecord (i); + const Record& record = collection.getRecord(i); - if (!std::binary_search (mSelection.begin(), mSelection.end(), - Misc::StringUtils::lowerCase (record.get().mCell))) + if (!std::binary_search(mSelection.begin(), mSelection.end(), record.get().mCell)) continue; - macro.push (new CSMWorld::RevertCommand (model, record.get().mId)); + macro.push(new CSMWorld::RevertCommand(model, record.get().mId.getRefIdString())); } } } diff --git a/apps/opencs/model/world/commanddispatcher.hpp b/apps/opencs/model/world/commanddispatcher.hpp index 538fd7f18..46faa4524 100644 --- a/apps/opencs/model/world/commanddispatcher.hpp +++ b/apps/opencs/model/world/commanddispatcher.hpp @@ -1,9 +1,13 @@ #ifndef CSM_WOLRD_COMMANDDISPATCHER_H #define CSM_WOLRD_COMMANDDISPATCHER_H +#include #include +#include +#include #include +#include #include "universalid.hpp" @@ -19,59 +23,56 @@ namespace CSMWorld { class CommandDispatcher : public QObject { - Q_OBJECT + Q_OBJECT - bool mLocked; - CSMDoc::Document& mDocument; - UniversalId mId; - std::vector mSelection; - std::vector mExtendedTypes; + bool mLocked; + CSMDoc::Document& mDocument; + UniversalId mId; + std::vector mSelection; + std::vector mExtendedTypes; - std::vector getDeletableRecords() const; + std::vector getDeletableRecords() const; - std::vector getRevertableRecords() const; + std::vector getRevertableRecords() const; - public: + public: + CommandDispatcher(CSMDoc::Document& document, const CSMWorld::UniversalId& id, QObject* parent = nullptr); + ///< \param id ID of the table the commands should operate on primarily. - CommandDispatcher (CSMDoc::Document& document, const CSMWorld::UniversalId& id, - QObject *parent = nullptr); - ///< \param id ID of the table the commands should operate on primarily. + void setEditLock(bool locked); - void setEditLock (bool locked); + void setSelection(const std::vector& selection); - void setSelection (const std::vector& selection); + void setExtendedTypes(const std::vector& types); + ///< Set record lists selected by the user for extended operations. - void setExtendedTypes (const std::vector& types); - ///< Set record lists selected by the user for extended operations. + bool canDelete() const; - bool canDelete() const; + bool canRevert() const; - bool canRevert() const; + /// Return IDs of the record collection that can also be affected when + /// operating on the record collection this dispatcher is used for. + /// + /// \note The returned collection contains the ID of the record collection this + /// dispatcher is used for. However if that record collection does not support + /// the extended mode, the returned vector will be empty instead. + std::vector getExtendedTypes() const; - /// Return IDs of the record collection that can also be affected when - /// operating on the record collection this dispatcher is used for. - /// - /// \note The returned collection contains the ID of the record collection this - /// dispatcher is used for. However if that record collection does not support - /// the extended mode, the returned vector will be empty instead. - std::vector getExtendedTypes() const; + /// Add a modify command to the undo stack. + /// + /// \attention model must either be a model for the table operated on by this + /// dispatcher or a proxy of it. + void executeModify(QAbstractItemModel* model, const QModelIndex& index, const QVariant& new_); - /// Add a modify command to the undo stack. - /// - /// \attention model must either be a model for the table operated on by this - /// dispatcher or a proxy of it. - void executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_); + public slots: - public slots: + void executeDelete(); - void executeDelete(); + void executeRevert(); - void executeRevert(); - - void executeExtendedDelete(); - - void executeExtendedRevert(); + void executeExtendedDelete(); + void executeExtendedRevert(); }; } diff --git a/apps/opencs/model/world/commandmacro.cpp b/apps/opencs/model/world/commandmacro.cpp index 0bd74cbe2..2c3b7e172 100644 --- a/apps/opencs/model/world/commandmacro.cpp +++ b/apps/opencs/model/world/commandmacro.cpp @@ -1,12 +1,15 @@ #include "commandmacro.hpp" -#include #include +#include -CSMWorld::CommandMacro::CommandMacro (QUndoStack& undoStack, const QString& description) -: mUndoStack (undoStack), mDescription (description), mStarted (false) -{} +CSMWorld::CommandMacro::CommandMacro(QUndoStack& undoStack, const QString& description) + : mUndoStack(undoStack) + , mDescription(description) + , mStarted(false) +{ +} CSMWorld::CommandMacro::~CommandMacro() { @@ -14,13 +17,13 @@ CSMWorld::CommandMacro::~CommandMacro() mUndoStack.endMacro(); } -void CSMWorld::CommandMacro::push (QUndoCommand *command) +void CSMWorld::CommandMacro::push(QUndoCommand* command) { if (!mStarted) { - mUndoStack.beginMacro (mDescription.isEmpty() ? command->text() : mDescription); + mUndoStack.beginMacro(mDescription.isEmpty() ? command->text() : mDescription); mStarted = true; } - mUndoStack.push (command); + mUndoStack.push(command); } diff --git a/apps/opencs/model/world/commandmacro.hpp b/apps/opencs/model/world/commandmacro.hpp index b1f6301d9..bc21e935c 100644 --- a/apps/opencs/model/world/commandmacro.hpp +++ b/apps/opencs/model/world/commandmacro.hpp @@ -10,24 +10,23 @@ namespace CSMWorld { class CommandMacro { - QUndoStack& mUndoStack; - QString mDescription; - bool mStarted; + QUndoStack& mUndoStack; + QString mDescription; + bool mStarted; - /// not implemented - CommandMacro (const CommandMacro&); + /// not implemented + CommandMacro(const CommandMacro&); - /// not implemented - CommandMacro& operator= (const CommandMacro&); + /// not implemented + CommandMacro& operator=(const CommandMacro&); - public: + public: + /// If \a description is empty, the description of the first command is used. + CommandMacro(QUndoStack& undoStack, const QString& description = ""); - /// If \a description is empty, the description of the first command is used. - CommandMacro (QUndoStack& undoStack, const QString& description = ""); + ~CommandMacro(); - ~CommandMacro(); - - void push (QUndoCommand *command); + void push(QUndoCommand* command); }; } diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 5ec7401dc..da49caef1 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -1,16 +1,24 @@ #include "commands.hpp" +#include #include #include #include -#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include #include "cellcoordinates.hpp" -#include "idcollection.hpp" #include "idtable.hpp" #include "idtree.hpp" #include "nestedtablewrapper.hpp" @@ -24,11 +32,11 @@ CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUnd , mChanged(false) { setText(("Touch " + mId).c_str()); - mOld.reset(mTable.getRecord(mId).clone()); } void CSMWorld::TouchCommand::redo() { + mOld.reset(mTable.getRecord(mId).clone().get()); mChanged = mTable.touchRecord(mId); } @@ -36,13 +44,13 @@ void CSMWorld::TouchCommand::undo() { if (mChanged) { - mTable.setRecord(mId, *mOld); + mTable.setRecord(mId, std::move(mOld)); mChanged = false; } } -CSMWorld::ImportLandTexturesCommand::ImportLandTexturesCommand(IdTable& landTable, - IdTable& ltexTable, QUndoCommand* parent) +CSMWorld::ImportLandTexturesCommand::ImportLandTexturesCommand( + IdTable& landTable, IdTable& ltexTable, QUndoCommand* parent) : QUndoCommand(parent) , mLands(landTable) , mLtexs(ltexTable) @@ -133,8 +141,8 @@ void CSMWorld::ImportLandTexturesCommand::undo() mCreatedTextures.clear(); } -CSMWorld::CopyLandTexturesCommand::CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, - const std::string& origin, const std::string& dest, QUndoCommand* parent) +CSMWorld::CopyLandTexturesCommand::CopyLandTexturesCommand( + IdTable& landTable, IdTable& ltexTable, const std::string& origin, const std::string& dest, QUndoCommand* parent) : ImportLandTexturesCommand(landTable, ltexTable, parent) , mOriginId(origin) , mDestId(dest) @@ -151,15 +159,14 @@ const std::string& CSMWorld::CopyLandTexturesCommand::getDestinationId() const return mDestId; } -CSMWorld::TouchLandCommand::TouchLandCommand(IdTable& landTable, IdTable& ltexTable, - const std::string& id, QUndoCommand* parent) +CSMWorld::TouchLandCommand::TouchLandCommand( + IdTable& landTable, IdTable& ltexTable, const std::string& id, QUndoCommand* parent) : ImportLandTexturesCommand(landTable, ltexTable, parent) , mId(id) , mOld(nullptr) , mChanged(false) { setText(("Touch " + mId).c_str()); - mOld.reset(mLands.getRecord(mId).clone()); } const std::string& CSMWorld::TouchLandCommand::getOriginId() const @@ -175,41 +182,52 @@ const std::string& CSMWorld::TouchLandCommand::getDestinationId() const void CSMWorld::TouchLandCommand::onRedo() { mChanged = mLands.touchRecord(mId); + if (mChanged) + mOld.reset(mLands.getRecord(mId).clone().get()); } void CSMWorld::TouchLandCommand::onUndo() { if (mChanged) { - mLands.setRecord(mId, *mOld); + mLands.setRecord(mId, std::move(mOld)); mChanged = false; } } -CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, - const QVariant& new_, QUndoCommand* parent) - : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) +CSMWorld::ModifyCommand::ModifyCommand( + QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) + : QUndoCommand(parent) + , mModel(&model) + , mIndex(index) + , mNew(new_) + , mHasRecordState(false) + , mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) { - if (QAbstractProxyModel *proxy = dynamic_cast (&model)) + if (QAbstractProxyModel* proxy = dynamic_cast(mModel)) { // Replace proxy with actual model - mIndex = proxy->mapToSource (index); + mIndex = proxy->mapToSource(mIndex); mModel = proxy->sourceModel(); } +} +void CSMWorld::ModifyCommand::redo() +{ if (mIndex.parent().isValid()) { CSMWorld::IdTree* tree = &dynamic_cast(*mModel); - setText ("Modify " + tree->nestedHeaderData ( - mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + setText("Modify " + + tree->nestedHeaderData(mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole) + .toString()); } else { - setText ("Modify " + mModel->headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + setText("Modify " + mModel->headerData(mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); } // Remember record state before the modification - if (CSMWorld::IdTable *table = dynamic_cast(mModel)) + if (CSMWorld::IdTable* table = dynamic_cast(mModel)) { mHasRecordState = true; int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification); @@ -223,190 +241,183 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mOldRecordState = static_cast(table->data(mRecordStateIndex).toInt()); } -} -void CSMWorld::ModifyCommand::redo() -{ - mOld = mModel->data (mIndex, Qt::EditRole); - mModel->setData (mIndex, mNew); + mOld = mModel->data(mIndex, Qt::EditRole); + mModel->setData(mIndex, mNew); } void CSMWorld::ModifyCommand::undo() { - mModel->setData (mIndex, mOld); + mModel->setData(mIndex, mOld); if (mHasRecordState) { mModel->setData(mRecordStateIndex, mOldRecordState); } } - void CSMWorld::CreateCommand::applyModifications() { if (!mNestedValues.empty()) { CSMWorld::IdTree* tree = &dynamic_cast(mModel); - std::map >::const_iterator current = mNestedValues.begin(); - std::map >::const_iterator end = mNestedValues.end(); + std::map>::const_iterator current = mNestedValues.begin(); + std::map>::const_iterator end = mNestedValues.end(); for (; current != end; ++current) { - QModelIndex index = tree->index(0, - current->second.first, - tree->getNestedModelIndex(mId, current->first)); + QModelIndex index = tree->index(0, current->second.first, tree->getNestedModelIndex(mId, current->first)); tree->setData(index, current->second.second); } } } -CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None) +CSMWorld::CreateCommand::CreateCommand(IdTable& model, const std::string& id, QUndoCommand* parent) + : QUndoCommand(parent) + , mModel(model) + , mId(id) + , mType(UniversalId::Type_None) { - setText (("Create record " + id).c_str()); + setText(("Create record " + id).c_str()); } -void CSMWorld::CreateCommand::addValue (int column, const QVariant& value) +void CSMWorld::CreateCommand::addValue(int column, const QVariant& value) { mValues[column] = value; } -void CSMWorld::CreateCommand::addNestedValue(int parentColumn, int nestedColumn, const QVariant &value) +void CSMWorld::CreateCommand::addNestedValue(int parentColumn, int nestedColumn, const QVariant& value) { mNestedValues[parentColumn] = std::make_pair(nestedColumn, value); } -void CSMWorld::CreateCommand::setType (UniversalId::Type type) +void CSMWorld::CreateCommand::setType(UniversalId::Type type) { mType = type; } void CSMWorld::CreateCommand::redo() { - mModel.addRecordWithData (mId, mValues, mType); + mModel.addRecordWithData(mId, mValues, mType); applyModifications(); } void CSMWorld::CreateCommand::undo() { - mModel.removeRow (mModel.getModelIndex (mId, 0).row()); + mModel.removeRow(mModel.getModelIndex(mId, 0).row()); } -CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id), mOld (nullptr) +CSMWorld::RevertCommand::RevertCommand(IdTable& model, const std::string& id, QUndoCommand* parent) + : QUndoCommand(parent) + , mModel(model) + , mId(id) + , mOld(nullptr) { - setText (("Revert record " + id).c_str()); - - mOld = model.getRecord (id).clone(); -} - -CSMWorld::RevertCommand::~RevertCommand() -{ - delete mOld; + setText(("Revert record " + id).c_str()); } void CSMWorld::RevertCommand::redo() { - int column = mModel.findColumnIndex (Columns::ColumnId_Modification); + mOld = mModel.getRecord(mId).clone(); - QModelIndex index = mModel.getModelIndex (mId, column); - RecordBase::State state = static_cast (mModel.data (index).toInt()); + int column = mModel.findColumnIndex(Columns::ColumnId_Modification); - if (state==RecordBase::State_ModifiedOnly) + QModelIndex index = mModel.getModelIndex(mId, column); + RecordBase::State state = static_cast(mModel.data(index).toInt()); + + if (state == RecordBase::State_ModifiedOnly) { - mModel.removeRows (index.row(), 1); + mModel.removeRows(index.row(), 1); } else { - mModel.setData (index, static_cast (RecordBase::State_BaseOnly)); + mModel.setData(index, static_cast(RecordBase::State_BaseOnly)); } } void CSMWorld::RevertCommand::undo() { - mModel.setRecord (mId, *mOld); + mModel.setRecord(mId, std::move(mOld)); } -CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, - const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id), mOld (nullptr), mType(type) +CSMWorld::DeleteCommand::DeleteCommand( + IdTable& model, const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent) + : QUndoCommand(parent) + , mModel(model) + , mId(id) + , mOld(nullptr) + , mType(type) { - setText (("Delete record " + id).c_str()); - - mOld = model.getRecord (id).clone(); -} - -CSMWorld::DeleteCommand::~DeleteCommand() -{ - delete mOld; + setText(("Delete record " + id).c_str()); } void CSMWorld::DeleteCommand::redo() { - int column = mModel.findColumnIndex (Columns::ColumnId_Modification); + mOld = mModel.getRecord(mId).clone(); - QModelIndex index = mModel.getModelIndex (mId, column); - RecordBase::State state = static_cast (mModel.data (index).toInt()); + int column = mModel.findColumnIndex(Columns::ColumnId_Modification); - if (state==RecordBase::State_ModifiedOnly) + QModelIndex index = mModel.getModelIndex(mId, column); + RecordBase::State state = static_cast(mModel.data(index).toInt()); + + if (state == RecordBase::State_ModifiedOnly) { - mModel.removeRows (index.row(), 1); + mModel.removeRows(index.row(), 1); } else { - mModel.setData (index, static_cast (RecordBase::State_Deleted)); + mModel.setData(index, static_cast(RecordBase::State_Deleted)); } } void CSMWorld::DeleteCommand::undo() { - mModel.setRecord (mId, *mOld, mType); + mModel.setRecord(mId, std::move(mOld), mType); } - -CSMWorld::ReorderRowsCommand::ReorderRowsCommand (IdTable& model, int baseIndex, - const std::vector& newOrder) -: mModel (model), mBaseIndex (baseIndex), mNewOrder (newOrder) -{} +CSMWorld::ReorderRowsCommand::ReorderRowsCommand(IdTable& model, int baseIndex, const std::vector& newOrder) + : mModel(model) + , mBaseIndex(baseIndex) + , mNewOrder(newOrder) +{ +} void CSMWorld::ReorderRowsCommand::redo() { - mModel.reorderRows (mBaseIndex, mNewOrder); + mModel.reorderRows(mBaseIndex, mNewOrder); } void CSMWorld::ReorderRowsCommand::undo() { - int size = static_cast (mNewOrder.size()); - std::vector reverse (size); + int size = static_cast(mNewOrder.size()); + std::vector reverse(size); - for (int i=0; i< size; ++i) - reverse.at (mNewOrder[i]) = i; + for (int i = 0; i < size; ++i) + reverse.at(mNewOrder[i]) = i; - mModel.reorderRows (mBaseIndex, reverse); + mModel.reorderRows(mBaseIndex, reverse); } -CSMWorld::CloneCommand::CloneCommand (CSMWorld::IdTable& model, - const std::string& idOrigin, - const std::string& idDestination, - const CSMWorld::UniversalId::Type type, - QUndoCommand* parent) -: CreateCommand (model, idDestination, parent), mIdOrigin (idOrigin) +CSMWorld::CloneCommand::CloneCommand(CSMWorld::IdTable& model, const std::string& idOrigin, + const std::string& idDestination, const CSMWorld::UniversalId::Type type, QUndoCommand* parent) + : CreateCommand(model, idDestination, parent) + , mIdOrigin(idOrigin) { - setType (type); - setText ( ("Clone record " + idOrigin + " to the " + idDestination).c_str()); + setType(type); + setText(("Clone record " + idOrigin + " to the " + idDestination).c_str()); } void CSMWorld::CloneCommand::redo() { - mModel.cloneRecord (mIdOrigin, mId, mType); + mModel.cloneRecord(ESM::RefId::stringRefId(mIdOrigin), ESM::RefId::stringRefId(mId), mType); applyModifications(); for (auto& value : mOverrideValues) { - mModel.setData(mModel.getModelIndex (mId, value.first), value.second); + mModel.setData(mModel.getModelIndex(mId, value.first), value.second); } } void CSMWorld::CloneCommand::undo() { - mModel.removeRow (mModel.getModelIndex (mId, 0).row()); + mModel.removeRow(mModel.getModelIndex(mId, 0).row()); } void CSMWorld::CloneCommand::setOverrideValue(int column, QVariant value) @@ -414,7 +425,7 @@ void CSMWorld::CloneCommand::setOverrideValue(int column, QVariant value) mOverrideValues.emplace_back(std::make_pair(column, value)); } -CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent) +CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand* parent) : CreateCommand(model, id, parent) { setType(UniversalId::Type_Pathgrid); @@ -424,73 +435,69 @@ void CSMWorld::CreatePathgridCommand::redo() { CreateCommand::redo(); - Record record = static_cast& >(mModel.getRecord(mId)); - record.get().blank(); - record.get().mCell = mId; + std::unique_ptr> record + = std::make_unique>(static_cast&>(mModel.getRecord(mId))); + record->get().blank(); + record->get().mCell = ESM::RefId::stringRefId(mId); std::pair coords = CellCoordinates::fromId(mId); if (coords.second) { - record.get().mData.mX = coords.first.getX(); - record.get().mData.mY = coords.first.getY(); + record->get().mData.mX = coords.first.getX(); + record->get().mData.mY = coords.first.getY(); } - mModel.setRecord(mId, record, mType); + mModel.setRecord(mId, std::move(record), mType); } -CSMWorld::UpdateCellCommand::UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent) -: QUndoCommand (parent), mModel (model), mRow (row) +CSMWorld::UpdateCellCommand::UpdateCellCommand(IdTable& model, int row, QUndoCommand* parent) + : QUndoCommand(parent) + , mModel(model) + , mRow(row) { - setText ("Update cell ID"); + setText("Update cell ID"); } void CSMWorld::UpdateCellCommand::redo() { if (!mNew.isValid()) { - int cellColumn = mModel.searchColumnIndex (Columns::ColumnId_Cell); - mIndex = mModel.index (mRow, cellColumn); + int cellColumn = mModel.searchColumnIndex(Columns::ColumnId_Cell); + mIndex = mModel.index(mRow, cellColumn); - QModelIndex xIndex = mModel.index ( - mRow, mModel.findColumnIndex (Columns::ColumnId_PositionXPos)); + QModelIndex xIndex = mModel.index(mRow, mModel.findColumnIndex(Columns::ColumnId_PositionXPos)); - QModelIndex yIndex = mModel.index ( - mRow, mModel.findColumnIndex (Columns::ColumnId_PositionYPos)); + QModelIndex yIndex = mModel.index(mRow, mModel.findColumnIndex(Columns::ColumnId_PositionYPos)); - int x = std::floor (mModel.data (xIndex).toFloat() / Constants::CellSizeInUnits); - int y = std::floor (mModel.data (yIndex).toFloat() / Constants::CellSizeInUnits); + int x = std::floor(mModel.data(xIndex).toFloat() / Constants::CellSizeInUnits); + int y = std::floor(mModel.data(yIndex).toFloat() / Constants::CellSizeInUnits); std::ostringstream stream; stream << "#" << x << " " << y; - mNew = QString::fromUtf8 (stream.str().c_str()); + mNew = QString::fromUtf8(stream.str().c_str()); } - mModel.setData (mIndex, mNew); + mModel.setData(mIndex, mNew); } void CSMWorld::UpdateCellCommand::undo() { - mModel.setData (mIndex, mOld); + mModel.setData(mIndex, mOld); } - -CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model, - const std::string& id, - int nestedRow, - int parentColumn, - QUndoCommand* parent) : - QUndoCommand(parent), - NestedTableStoring(model, id, parentColumn), - mModel(model), - mId(id), - mParentColumn(parentColumn), - mNestedRow(nestedRow) +CSMWorld::DeleteNestedCommand::DeleteNestedCommand( + IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) + : QUndoCommand(parent) + , NestedTableStoring(model, id, parentColumn) + , mModel(model) + , mId(id) + , mParentColumn(parentColumn) + , mNestedRow(nestedRow) { - std::string title = - model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); - setText (("Delete row in " + title + " sub-table of " + mId).c_str()); + std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); + setText(("Delete row in " + title + " sub-table of " + mId).c_str()); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); @@ -499,11 +506,10 @@ CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model, void CSMWorld::DeleteNestedCommand::redo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); - mModel.removeRows (mNestedRow, 1, parentIndex); mModifyParentCommand->redo(); + mModel.removeRows(mNestedRow, 1, parentIndex); } - void CSMWorld::DeleteNestedCommand::undo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); @@ -511,17 +517,17 @@ void CSMWorld::DeleteNestedCommand::undo() mModifyParentCommand->undo(); } -CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) - : QUndoCommand(parent), - NestedTableStoring(model, id, parentColumn), - mModel(model), - mId(id), - mNewRow(nestedRow), - mParentColumn(parentColumn) +CSMWorld::AddNestedCommand::AddNestedCommand( + IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) + : QUndoCommand(parent) + , NestedTableStoring(model, id, parentColumn) + , mModel(model) + , mId(id) + , mNewRow(nestedRow) + , mParentColumn(parentColumn) { - std::string title = - model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); - setText (("Add row in " + title + " sub-table of " + mId).c_str()); + std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); + setText(("Add row in " + title + " sub-table of " + mId).c_str()); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); @@ -530,8 +536,8 @@ CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& i void CSMWorld::AddNestedCommand::redo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); - mModel.addNestedRow (parentIndex, mNewRow); mModifyParentCommand->redo(); + mModel.addNestedRow(parentIndex, mNewRow); } void CSMWorld::AddNestedCommand::undo() @@ -542,7 +548,9 @@ void CSMWorld::AddNestedCommand::undo() } CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn) - : mOld(model.nestedTable(model.getModelIndex(id, parentColumn))) {} + : mOld(model.nestedTable(model.getModelIndex(id, parentColumn))) +{ +} CSMWorld::NestedTableStoring::~NestedTableStoring() { diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 33608304f..a24395000 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -3,45 +3,40 @@ #include "record.hpp" -#include #include #include +#include +#include #include -#include -#include +#include #include +#include +#include #include "columnimp.hpp" #include "universalid.hpp" -#include "nestedtablewrapper.hpp" - -class QModelIndex; -class QAbstractItemModel; namespace CSMWorld { class IdTable; class IdTree; - struct RecordBase; struct NestedTableWrapperBase; class TouchCommand : public QUndoCommand { - public: + public: + TouchCommand(IdTable& model, const std::string& id, QUndoCommand* parent = nullptr); - TouchCommand(IdTable& model, const std::string& id, QUndoCommand* parent=nullptr); + void redo() override; + void undo() override; - void redo() override; - void undo() override; + private: + IdTable& mTable; + std::string mId; + std::unique_ptr mOld; - private: - - IdTable& mTable; - std::string mId; - std::unique_ptr mOld; - - bool mChanged; + bool mChanged; }; /// \brief Adds LandTexture records and modifies texture indices as needed. @@ -55,29 +50,26 @@ namespace CSMWorld /// LandRecord and within the Land record. class ImportLandTexturesCommand : public QUndoCommand { - public: + public: + ImportLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, QUndoCommand* parent); - ImportLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, - QUndoCommand* parent); + void redo() override; + void undo() override; - void redo() override; - void undo() override; + protected: + using DataType = LandTexturesColumn::DataType; - protected: + virtual const std::string& getOriginId() const = 0; + virtual const std::string& getDestinationId() const = 0; - using DataType = LandTexturesColumn::DataType; + virtual void onRedo() = 0; + virtual void onUndo() = 0; - virtual const std::string& getOriginId() const = 0; - virtual const std::string& getDestinationId() const = 0; - - virtual void onRedo() = 0; - virtual void onUndo() = 0; - - IdTable& mLands; - IdTable& mLtexs; - DataType mOld; - int mOldState; - std::vector mCreatedTextures; + IdTable& mLands; + IdTable& mLtexs; + DataType mOld; + int mOldState; + std::vector mCreatedTextures; }; /// \brief This command is used to fix LandTexture records and texture @@ -85,21 +77,19 @@ namespace CSMWorld /// details. class CopyLandTexturesCommand : public ImportLandTexturesCommand { - public: + public: + CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, const std::string& origin, + const std::string& dest, QUndoCommand* parent = nullptr); - CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, const std::string& origin, - const std::string& dest, QUndoCommand* parent = nullptr); + private: + const std::string& getOriginId() const override; + const std::string& getDestinationId() const override; - private: + void onRedo() override {} + void onUndo() override {} - const std::string& getOriginId() const override; - const std::string& getDestinationId() const override; - - void onRedo() override {} - void onUndo() override {} - - std::string mOriginId; - std::string mDestId; + std::string mOriginId; + std::string mDestId; }; /// \brief This command brings a land record into the current plugin, adding @@ -107,164 +97,150 @@ namespace CSMWorld /// \note See ImportLandTextures for more details. class TouchLandCommand : public ImportLandTexturesCommand { - public: + public: + TouchLandCommand(IdTable& landTable, IdTable& ltexTable, const std::string& id, QUndoCommand* parent = nullptr); - TouchLandCommand(IdTable& landTable, IdTable& ltexTable, - const std::string& id, QUndoCommand* parent = nullptr); + private: + const std::string& getOriginId() const override; + const std::string& getDestinationId() const override; - private: + void onRedo() override; + void onUndo() override; - const std::string& getOriginId() const override; - const std::string& getDestinationId() const override; + std::string mId; + std::unique_ptr mOld; - void onRedo() override; - void onUndo() override; - - std::string mId; - std::unique_ptr mOld; - - bool mChanged; + bool mChanged; }; class ModifyCommand : public QUndoCommand { - QAbstractItemModel *mModel; - QModelIndex mIndex; - QVariant mNew; - QVariant mOld; + QAbstractItemModel* mModel; + QModelIndex mIndex; + QVariant mNew; + QVariant mOld; - bool mHasRecordState; - QModelIndex mRecordStateIndex; - CSMWorld::RecordBase::State mOldRecordState; + bool mHasRecordState; + QModelIndex mRecordStateIndex; + CSMWorld::RecordBase::State mOldRecordState; - public: + public: + ModifyCommand( + QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent = nullptr); - ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, - QUndoCommand *parent = nullptr); + void redo() override; - void redo() override; - - void undo() override; + void undo() override; }; class CreateCommand : public QUndoCommand { - std::map mValues; - std::map > mNestedValues; - ///< Parameter order: a parent column, a nested column, a data. - ///< A nested row has index of 0. + std::map mValues; + std::map> mNestedValues; + ///< Parameter order: a parent column, a nested column, a data. + ///< A nested row has index of 0. - protected: + protected: + IdTable& mModel; + std::string mId; + UniversalId::Type mType; - IdTable& mModel; - std::string mId; - UniversalId::Type mType; + protected: + /// Apply modifications set via addValue. + void applyModifications(); - protected: + public: + CreateCommand(IdTable& model, const std::string& id, QUndoCommand* parent = nullptr); - /// Apply modifications set via addValue. - void applyModifications(); + void setType(UniversalId::Type type); - public: + void addValue(int column, const QVariant& value); - CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); + void addNestedValue(int parentColumn, int nestedColumn, const QVariant& value); - void setType (UniversalId::Type type); + void redo() override; - void addValue (int column, const QVariant& value); - - void addNestedValue(int parentColumn, int nestedColumn, const QVariant &value); - - void redo() override; - - void undo() override; + void undo() override; }; class CloneCommand : public CreateCommand { - std::string mIdOrigin; - std::vector> mOverrideValues; + std::string mIdOrigin; + std::vector> mOverrideValues; - public: + public: + CloneCommand(IdTable& model, const std::string& idOrigin, const std::string& IdDestination, + const UniversalId::Type type, QUndoCommand* parent = nullptr); - CloneCommand (IdTable& model, const std::string& idOrigin, - const std::string& IdDestination, - const UniversalId::Type type, - QUndoCommand* parent = nullptr); + void redo() override; - void redo() override; + void undo() override; - void undo() override; - - void setOverrideValue(int column, QVariant value); + void setOverrideValue(int column, QVariant value); }; class RevertCommand : public QUndoCommand { - IdTable& mModel; - std::string mId; - RecordBase *mOld; + IdTable& mModel; + std::string mId; + std::unique_ptr mOld; - // not implemented - RevertCommand (const RevertCommand&); - RevertCommand& operator= (const RevertCommand&); + // not implemented + RevertCommand(const RevertCommand&); + RevertCommand& operator=(const RevertCommand&); - public: + public: + RevertCommand(IdTable& model, const std::string& id, QUndoCommand* parent = nullptr); - RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); + ~RevertCommand() override = default; - virtual ~RevertCommand(); + void redo() override; - void redo() override; - - void undo() override; + void undo() override; }; class DeleteCommand : public QUndoCommand { - IdTable& mModel; - std::string mId; - RecordBase *mOld; - UniversalId::Type mType; + IdTable& mModel; + std::string mId; + std::unique_ptr mOld; + UniversalId::Type mType; - // not implemented - DeleteCommand (const DeleteCommand&); - DeleteCommand& operator= (const DeleteCommand&); + // not implemented + DeleteCommand(const DeleteCommand&); + DeleteCommand& operator=(const DeleteCommand&); - public: + public: + DeleteCommand(IdTable& model, const std::string& id, UniversalId::Type type = UniversalId::Type_None, + QUndoCommand* parent = nullptr); - DeleteCommand (IdTable& model, const std::string& id, - UniversalId::Type type = UniversalId::Type_None, QUndoCommand *parent = nullptr); + ~DeleteCommand() override = default; - virtual ~DeleteCommand(); + void redo() override; - void redo() override; - - void undo() override; + void undo() override; }; class ReorderRowsCommand : public QUndoCommand { - IdTable& mModel; - int mBaseIndex; - std::vector mNewOrder; + IdTable& mModel; + int mBaseIndex; + std::vector mNewOrder; - public: + public: + ReorderRowsCommand(IdTable& model, int baseIndex, const std::vector& newOrder); - ReorderRowsCommand (IdTable& model, int baseIndex, const std::vector& newOrder); + void redo() override; - void redo() override; - - void undo() override; + void undo() override; }; class CreatePathgridCommand : public CreateCommand { - public: + public: + CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand* parent = nullptr); - CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); - - void redo() override; + void redo() override; }; /// \brief Update cell ID according to x/y-coordinates @@ -274,22 +250,20 @@ namespace CSMWorld /// in a macro. class UpdateCellCommand : public QUndoCommand { - IdTable& mModel; - int mRow; - QModelIndex mIndex; - QVariant mNew; // invalid, if new cell ID has not been calculated yet - QVariant mOld; + IdTable& mModel; + int mRow; + QModelIndex mIndex; + QVariant mNew; // invalid, if new cell ID has not been calculated yet + QVariant mOld; - public: + public: + UpdateCellCommand(IdTable& model, int row, QUndoCommand* parent = nullptr); - UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent = nullptr); + void redo() override; - void redo() override; - - void undo() override; + void undo() override; }; - class NestedTableStoring { NestedTableWrapperBase* mOld; @@ -300,52 +274,51 @@ namespace CSMWorld ~NestedTableStoring(); protected: - const NestedTableWrapperBase& getOld() const; }; class DeleteNestedCommand : public QUndoCommand, private NestedTableStoring { - IdTree& mModel; + IdTree& mModel; - std::string mId; + std::string mId; - int mParentColumn; + int mParentColumn; - int mNestedRow; + int mNestedRow; - // The command to redo/undo the Modified status of a record - ModifyCommand *mModifyParentCommand; + // The command to redo/undo the Modified status of a record + ModifyCommand* mModifyParentCommand; - public: + public: + DeleteNestedCommand( + IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = nullptr); - DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = nullptr); + void redo() override; - void redo() override; - - void undo() override; + void undo() override; }; class AddNestedCommand : public QUndoCommand, private NestedTableStoring { - IdTree& mModel; + IdTree& mModel; - std::string mId; + std::string mId; - int mNewRow; + int mNewRow; - int mParentColumn; + int mParentColumn; - // The command to redo/undo the Modified status of a record - ModifyCommand *mModifyParentCommand; + // The command to redo/undo the Modified status of a record + ModifyCommand* mModifyParentCommand; - public: + public: + AddNestedCommand( + IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = nullptr); - AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = nullptr); + void redo() override; - void redo() override; - - void undo() override; + void undo() override; }; } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 4ccd2a06d..07e987979 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1,614 +1,676 @@ #include "data.hpp" -#include #include +#include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include #include +#include "../doc/messages.hpp" + +#include "columnimp.hpp" +#include "columns.hpp" #include "idtable.hpp" #include "idtree.hpp" -#include "columnimp.hpp" +#include "nestedcoladapterimp.hpp" #include "regionmap.hpp" -#include "columns.hpp" #include "resourcesmanager.hpp" #include "resourcetable.hpp" -#include "nestedcoladapterimp.hpp" -void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) +namespace CSMWorld { - mModels.push_back (model); - mModelIndex.insert (std::make_pair (type, model)); - - UniversalId::Type type2 = UniversalId::getParentType (type); - - if (type2!=UniversalId::Type_None) - mModelIndex.insert (std::make_pair (type2, model)); - - if (update) + namespace { - connect (model, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (dataChanged (const QModelIndex&, const QModelIndex&))); - connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (rowsChanged (const QModelIndex&, int, int))); - connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (rowsChanged (const QModelIndex&, int, int))); + void removeDialogueInfos( + const ESM::RefId& dialogueId, InfoOrderByTopic& infoOrders, InfoCollection& infoCollection) + { + const auto topicInfoOrder = infoOrders.find(dialogueId); + + if (topicInfoOrder == infoOrders.end()) + return; + + std::vector erasedRecords; + + for (const OrderedInfo& info : topicInfoOrder->second.getOrderedInfo()) + { + const ESM::RefId id = makeCompositeInfoRefId(dialogueId, info.mId); + const Record& record = infoCollection.getRecord(id); + + if (record.mState == RecordBase::State_ModifiedOnly) + { + erasedRecords.push_back(infoCollection.searchId(id)); + continue; + } + + auto deletedRecord = std::make_unique>(record); + deletedRecord->mState = RecordBase::State_Deleted; + infoCollection.setRecord(infoCollection.searchId(id), std::move(deletedRecord)); + } + + while (!erasedRecords.empty()) + { + infoCollection.removeRows(erasedRecords.back(), 1); + erasedRecords.pop_back(); + } + } } } -void CSMWorld::Data::appendIds (std::vector& ids, const CollectionBase& collection, - bool listDeleted) +void CSMWorld::Data::addModel(QAbstractItemModel* model, UniversalId::Type type, bool update) { - std::vector ids2 = collection.getIds (listDeleted); + mModels.push_back(model); + mModelIndex.insert(std::make_pair(type, model)); - ids.insert (ids.end(), ids2.begin(), ids2.end()); + UniversalId::Type type2 = UniversalId::getParentType(type); + + if (type2 != UniversalId::Type_None) + mModelIndex.insert(std::make_pair(type2, model)); + + if (update) + { + connect(model, &QAbstractItemModel::dataChanged, this, &Data::dataChanged); + connect(model, &QAbstractItemModel::rowsInserted, this, &Data::rowsChanged); + connect(model, &QAbstractItemModel::rowsRemoved, this, &Data::rowsChanged); + } } -int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collection) +void CSMWorld::Data::appendIds(std::vector& ids, const CollectionBase& collection, bool listDeleted) +{ + std::vector ids2 = collection.getIds(listDeleted); + + ids.insert(ids.end(), ids2.begin(), ids2.end()); +} + +int CSMWorld::Data::count(RecordBase::State state, const CollectionBase& collection) { int number = 0; - for (int i=0; i& archives, const boost::filesystem::path& resDir) -: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mReader (nullptr), mDialogue (nullptr), mReaderIndex(1), - mFsStrict(fsStrict), mDataPaths(dataPaths), mArchives(archives) +CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& dataPaths, + const std::vector& archives, const std::filesystem::path& resDir) + : mEncoder(encoding) + , mPathgrids(mCells) + , mRefs(mCells) + , mReader(nullptr) + , mDialogue(nullptr) + , mReaderIndex(1) + , mDataPaths(dataPaths) + , mArchives(archives) { - mVFS.reset(new VFS::Manager(mFsStrict)); - VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths, !mFsStrict), mArchives, true); + mVFS = std::make_unique(); + VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true); mResourcesManager.setVFS(mVFS.get()); - mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); + mResourceSystem = std::make_unique(mVFS.get()); - Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); + Shader::ShaderManager::DefineMap defines + = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); Shader::ShaderManager::DefineMap shadowDefines = SceneUtil::ShadowManager::getShadowsDisabledDefines(); defines["forcePPL"] = "0"; // Don't force per-pixel lighting defines["clamp"] = "1"; // Clamp lighting defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["radialFog"] = "0"; defines["lightingModel"] = "0"; + defines["reverseZ"] = "0"; + defines["refraction_enabled"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); - mResourceSystem->getSceneManager()->setShaderPath((resDir / "shaders").string()); + mResourceSystem->getSceneManager()->setShaderPath(resDir / "shaders"); int index = 0; - mGlobals.addColumn (new StringIdColumn); - mGlobals.addColumn (new RecordStateColumn); - mGlobals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Global)); - mGlobals.addColumn (new VarTypeColumn (ColumnBase::Display_GlobalVarType)); - mGlobals.addColumn (new VarValueColumn); + mGlobals.addColumn(new StringIdColumn); + mGlobals.addColumn(new RecordStateColumn); + mGlobals.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Global)); + mGlobals.addColumn(new VarTypeColumn(ColumnBase::Display_GlobalVarType)); + mGlobals.addColumn(new VarValueColumn); - mGmsts.addColumn (new StringIdColumn); - mGmsts.addColumn (new RecordStateColumn); - mGmsts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Gmst)); - mGmsts.addColumn (new VarTypeColumn (ColumnBase::Display_GmstVarType)); - mGmsts.addColumn (new VarValueColumn); + mGmsts.addColumn(new StringIdColumn); + mGmsts.addColumn(new RecordStateColumn); + mGmsts.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Gmst)); + mGmsts.addColumn(new VarTypeColumn(ColumnBase::Display_GmstVarType)); + mGmsts.addColumn(new VarValueColumn); - mSkills.addColumn (new StringIdColumn); - mSkills.addColumn (new RecordStateColumn); - mSkills.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Skill)); - mSkills.addColumn (new AttributeColumn); - mSkills.addColumn (new SpecialisationColumn); - for (int i=0; i<4; ++i) - mSkills.addColumn (new UseValueColumn (i)); - mSkills.addColumn (new DescriptionColumn); + mSkills.addColumn(new StringIdColumn); + mSkills.addColumn(new RecordStateColumn); + mSkills.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Skill)); + mSkills.addColumn(new AttributeColumn); + mSkills.addColumn(new SpecialisationColumn); + for (int i = 0; i < 4; ++i) + mSkills.addColumn(new UseValueColumn(i)); + mSkills.addColumn(new DescriptionColumn); - mClasses.addColumn (new StringIdColumn); - mClasses.addColumn (new RecordStateColumn); - mClasses.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Class)); - mClasses.addColumn (new NameColumn); - mClasses.addColumn (new AttributesColumn (0)); - mClasses.addColumn (new AttributesColumn (1)); - mClasses.addColumn (new SpecialisationColumn); - for (int i=0; i<5; ++i) - mClasses.addColumn (new SkillsColumn (i, true, true)); - for (int i=0; i<5; ++i) - mClasses.addColumn (new SkillsColumn (i, true, false)); - mClasses.addColumn (new PlayableColumn); - mClasses.addColumn (new DescriptionColumn); + mClasses.addColumn(new StringIdColumn); + mClasses.addColumn(new RecordStateColumn); + mClasses.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Class)); + mClasses.addColumn(new NameColumn); + mClasses.addColumn(new AttributesColumn(0)); + mClasses.addColumn(new AttributesColumn(1)); + mClasses.addColumn(new SpecialisationColumn); + for (int i = 0; i < 5; ++i) + mClasses.addColumn(new SkillsColumn(i, true, true)); + for (int i = 0; i < 5; ++i) + mClasses.addColumn(new SkillsColumn(i, true, false)); + mClasses.addColumn(new PlayableColumn); + mClasses.addColumn(new DescriptionColumn); - mFactions.addColumn (new StringIdColumn); - mFactions.addColumn (new RecordStateColumn); - mFactions.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Faction)); - mFactions.addColumn (new NameColumn); - mFactions.addColumn (new AttributesColumn (0)); - mFactions.addColumn (new AttributesColumn (1)); - mFactions.addColumn (new HiddenColumn); - for (int i=0; i<7; ++i) - mFactions.addColumn (new SkillsColumn (i)); + mFactions.addColumn(new StringIdColumn); + mFactions.addColumn(new RecordStateColumn); + mFactions.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Faction)); + mFactions.addColumn(new NameColumn(ColumnBase::Display_String32)); + mFactions.addColumn(new AttributesColumn(0)); + mFactions.addColumn(new AttributesColumn(1)); + mFactions.addColumn(new HiddenColumn); + for (int i = 0; i < 7; ++i) + mFactions.addColumn(new SkillsColumn(i)); // Faction Reactions - mFactions.addColumn (new NestedParentColumn (Columns::ColumnId_FactionReactions)); - index = mFactions.getColumns()-1; - mFactions.addAdapter (std::make_pair(&mFactions.getColumn(index), new FactionReactionsAdapter ())); + mFactions.addColumn(new NestedParentColumn(Columns::ColumnId_FactionReactions)); + index = mFactions.getColumns() - 1; + mFactions.addAdapter(std::make_pair(&mFactions.getColumn(index), new FactionReactionsAdapter())); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Faction, ColumnBase::Display_Faction)); + new NestedChildColumn(Columns::ColumnId_Faction, ColumnBase::Display_Faction)); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_FactionReaction, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_FactionReaction, ColumnBase::Display_Integer)); // Faction Ranks - mFactions.addColumn (new NestedParentColumn (Columns::ColumnId_FactionRanks)); - index = mFactions.getColumns()-1; - mFactions.addAdapter (std::make_pair(&mFactions.getColumn(index), new FactionRanksAdapter ())); + mFactions.addColumn(new NestedParentColumn(Columns::ColumnId_FactionRanks)); + index = mFactions.getColumns() - 1; + mFactions.addAdapter(std::make_pair(&mFactions.getColumn(index), new FactionRanksAdapter())); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RankName, ColumnBase::Display_Rank)); + new NestedChildColumn(Columns::ColumnId_RankName, ColumnBase::Display_Rank)); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_FactionAttrib1, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_FactionAttrib1, ColumnBase::Display_Integer)); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_FactionAttrib2, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_FactionAttrib2, ColumnBase::Display_Integer)); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_FactionPrimSkill, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_FactionPrimSkill, ColumnBase::Display_Integer)); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_FactionFavSkill, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_FactionFavSkill, ColumnBase::Display_Integer)); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_FactionRep, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_FactionRep, ColumnBase::Display_Integer)); - mRaces.addColumn (new StringIdColumn); - mRaces.addColumn (new RecordStateColumn); - mRaces.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Race)); - mRaces.addColumn (new NameColumn); - mRaces.addColumn (new DescriptionColumn); - mRaces.addColumn (new FlagColumn (Columns::ColumnId_Playable, 0x1)); - mRaces.addColumn (new FlagColumn (Columns::ColumnId_BeastRace, 0x2)); - mRaces.addColumn (new WeightHeightColumn (true, true)); - mRaces.addColumn (new WeightHeightColumn (true, false)); - mRaces.addColumn (new WeightHeightColumn (false, true)); - mRaces.addColumn (new WeightHeightColumn (false, false)); + mRaces.addColumn(new StringIdColumn); + mRaces.addColumn(new RecordStateColumn); + mRaces.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Race)); + mRaces.addColumn(new NameColumn); + mRaces.addColumn(new DescriptionColumn); + mRaces.addColumn(new FlagColumn(Columns::ColumnId_Playable, 0x1)); + mRaces.addColumn(new FlagColumn(Columns::ColumnId_BeastRace, 0x2)); + mRaces.addColumn(new WeightHeightColumn(true, true)); + mRaces.addColumn(new WeightHeightColumn(true, false)); + mRaces.addColumn(new WeightHeightColumn(false, true)); + mRaces.addColumn(new WeightHeightColumn(false, false)); // Race spells - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_PowerList)); - index = mRaces.getColumns()-1; - mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new SpellListAdapter ())); + mRaces.addColumn(new NestedParentColumn(Columns::ColumnId_PowerList)); + index = mRaces.getColumns() - 1; + mRaces.addAdapter(std::make_pair(&mRaces.getColumn(index), new SpellListAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); + new NestedChildColumn(Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); // Race attributes - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes, - ColumnBase::Flag_Dialogue, true)); // fixed rows table - index = mRaces.getColumns()-1; - mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); + mRaces.addColumn(new NestedParentColumn( + Columns::ColumnId_RaceAttributes, ColumnBase::Flag_Dialogue, true)); // fixed rows table + index = mRaces.getColumns() - 1; + mRaces.addAdapter(std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); + mRaces.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, ColumnBase::Flag_Dialogue, false)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, - ColumnBase::Flag_Dialogue, false)); + new NestedChildColumn(Columns::ColumnId_Male, ColumnBase::Display_Integer)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Male, ColumnBase::Display_Integer)); - mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Female, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_Female, ColumnBase::Display_Integer)); // Race skill bonus - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus, - ColumnBase::Flag_Dialogue, true)); // fixed rows table - index = mRaces.getColumns()-1; - mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); + mRaces.addColumn(new NestedParentColumn( + Columns::ColumnId_RaceSkillBonus, ColumnBase::Flag_Dialogue, true)); // fixed rows table + index = mRaces.getColumns() - 1; + mRaces.addAdapter(std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + new NestedChildColumn(Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); - mSounds.addColumn (new StringIdColumn); - mSounds.addColumn (new RecordStateColumn); - mSounds.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Sound)); - mSounds.addColumn (new SoundParamColumn (SoundParamColumn::Type_Volume)); - mSounds.addColumn (new SoundParamColumn (SoundParamColumn::Type_MinRange)); - mSounds.addColumn (new SoundParamColumn (SoundParamColumn::Type_MaxRange)); - mSounds.addColumn (new SoundFileColumn); + mSounds.addColumn(new StringIdColumn); + mSounds.addColumn(new RecordStateColumn); + mSounds.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Sound)); + mSounds.addColumn(new SoundParamColumn(SoundParamColumn::Type_Volume)); + mSounds.addColumn(new SoundParamColumn(SoundParamColumn::Type_MinRange)); + mSounds.addColumn(new SoundParamColumn(SoundParamColumn::Type_MaxRange)); + mSounds.addColumn(new SoundFileColumn); - mScripts.addColumn (new StringIdColumn); - mScripts.addColumn (new RecordStateColumn); - mScripts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Script)); - mScripts.addColumn (new ScriptColumn (ScriptColumn::Type_File)); + mScripts.addColumn(new StringIdColumn); + mScripts.addColumn(new RecordStateColumn); + mScripts.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Script)); + mScripts.addColumn(new ScriptColumn(ScriptColumn::Type_File)); - mRegions.addColumn (new StringIdColumn); - mRegions.addColumn (new RecordStateColumn); - mRegions.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Region)); - mRegions.addColumn (new NameColumn); - mRegions.addColumn (new MapColourColumn); - mRegions.addColumn (new SleepListColumn); + mRegions.addColumn(new StringIdColumn); + mRegions.addColumn(new RecordStateColumn); + mRegions.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Region)); + mRegions.addColumn(new NameColumn); + mRegions.addColumn(new MapColourColumn); + mRegions.addColumn(new SleepListColumn); // Region Weather - mRegions.addColumn (new NestedParentColumn (Columns::ColumnId_RegionWeather)); - index = mRegions.getColumns()-1; - mRegions.addAdapter (std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter ())); + mRegions.addColumn(new NestedParentColumn(Columns::ColumnId_RegionWeather)); + index = mRegions.getColumns() - 1; + mRegions.addAdapter(std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter())); mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); + new NestedChildColumn(Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); + new NestedChildColumn(Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); // Region Sounds - mRegions.addColumn (new NestedParentColumn (Columns::ColumnId_RegionSounds)); - index = mRegions.getColumns()-1; - mRegions.addAdapter (std::make_pair(&mRegions.getColumn(index), new RegionSoundListAdapter ())); + mRegions.addColumn(new NestedParentColumn(Columns::ColumnId_RegionSounds)); + index = mRegions.getColumns() - 1; + mRegions.addAdapter(std::make_pair(&mRegions.getColumn(index), new RegionSoundListAdapter())); mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); + new NestedChildColumn(Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); + new NestedChildColumn(Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); - mBirthsigns.addColumn (new StringIdColumn); - mBirthsigns.addColumn (new RecordStateColumn); - mBirthsigns.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Birthsign)); - mBirthsigns.addColumn (new NameColumn); - mBirthsigns.addColumn (new TextureColumn); - mBirthsigns.addColumn (new DescriptionColumn); + mBirthsigns.addColumn(new StringIdColumn); + mBirthsigns.addColumn(new RecordStateColumn); + mBirthsigns.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Birthsign)); + mBirthsigns.addColumn(new NameColumn); + mBirthsigns.addColumn(new TextureColumn); + mBirthsigns.addColumn(new DescriptionColumn); // Birthsign spells - mBirthsigns.addColumn (new NestedParentColumn (Columns::ColumnId_PowerList)); - index = mBirthsigns.getColumns()-1; - mBirthsigns.addAdapter (std::make_pair(&mBirthsigns.getColumn(index), - new SpellListAdapter ())); + mBirthsigns.addColumn(new NestedParentColumn(Columns::ColumnId_PowerList)); + index = mBirthsigns.getColumns() - 1; + mBirthsigns.addAdapter(std::make_pair(&mBirthsigns.getColumn(index), new SpellListAdapter())); mBirthsigns.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); + new NestedChildColumn(Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); - mSpells.addColumn (new StringIdColumn); - mSpells.addColumn (new RecordStateColumn); - mSpells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Spell)); - mSpells.addColumn (new NameColumn); - mSpells.addColumn (new SpellTypeColumn); - mSpells.addColumn (new CostColumn); - mSpells.addColumn (new FlagColumn (Columns::ColumnId_AutoCalc, 0x1)); - mSpells.addColumn (new FlagColumn (Columns::ColumnId_StarterSpell, 0x2)); - mSpells.addColumn (new FlagColumn (Columns::ColumnId_AlwaysSucceeds, 0x4)); + mSpells.addColumn(new StringIdColumn); + mSpells.addColumn(new RecordStateColumn); + mSpells.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Spell)); + mSpells.addColumn(new NameColumn); + mSpells.addColumn(new SpellTypeColumn); + mSpells.addColumn(new CostColumn); + mSpells.addColumn(new FlagColumn(Columns::ColumnId_AutoCalc, 0x1)); + mSpells.addColumn(new FlagColumn(Columns::ColumnId_StarterSpell, 0x2)); + mSpells.addColumn(new FlagColumn(Columns::ColumnId_AlwaysSucceeds, 0x4)); // Spell effects - mSpells.addColumn (new NestedParentColumn (Columns::ColumnId_EffectList)); - index = mSpells.getColumns()-1; - mSpells.addAdapter (std::make_pair(&mSpells.getColumn(index), new EffectsListAdapter ())); + mSpells.addColumn(new NestedParentColumn(Columns::ColumnId_EffectList)); + index = mSpells.getColumns() - 1; + mSpells.addAdapter(std::make_pair(&mSpells.getColumn(index), new EffectsListAdapter())); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); + new NestedChildColumn(Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); + new NestedChildColumn(Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); + new NestedChildColumn(Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); + new NestedChildColumn(Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_EffectArea, ColumnBase::Display_String)); + new NestedChildColumn(Columns::ColumnId_EffectArea, ColumnBase::Display_String)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light + new NestedChildColumn(Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); - mTopics.addColumn (new StringIdColumn); - mTopics.addColumn (new RecordStateColumn); - mTopics.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Topic)); - mTopics.addColumn (new DialogueTypeColumn); + mTopics.addColumn(new StringIdColumn); + mTopics.addColumn(new RecordStateColumn); + mTopics.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Topic)); + mTopics.addColumn(new DialogueTypeColumn); - mJournals.addColumn (new StringIdColumn); - mJournals.addColumn (new RecordStateColumn); - mJournals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Journal)); - mJournals.addColumn (new DialogueTypeColumn (true)); + mJournals.addColumn(new StringIdColumn); + mJournals.addColumn(new RecordStateColumn); + mJournals.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Journal)); + mJournals.addColumn(new DialogueTypeColumn(true)); - mTopicInfos.addColumn (new StringIdColumn (true)); - mTopicInfos.addColumn (new RecordStateColumn); - mTopicInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_TopicInfo)); - mTopicInfos.addColumn (new TopicColumn (false)); - mTopicInfos.addColumn (new ActorColumn); - mTopicInfos.addColumn (new RaceColumn); - mTopicInfos.addColumn (new ClassColumn); - mTopicInfos.addColumn (new FactionColumn); - mTopicInfos.addColumn (new CellColumn); - mTopicInfos.addColumn (new DispositionColumn); - mTopicInfos.addColumn (new RankColumn); - mTopicInfos.addColumn (new GenderColumn); - mTopicInfos.addColumn (new PcFactionColumn); - mTopicInfos.addColumn (new PcRankColumn); - mTopicInfos.addColumn (new SoundFileColumn); - mTopicInfos.addColumn (new ResponseColumn); + mTopicInfos.addColumn(new StringIdColumn(true)); + mTopicInfos.addColumn(new RecordStateColumn); + mTopicInfos.addColumn(new FixedRecordTypeColumn(UniversalId::Type_TopicInfo)); + mTopicInfos.addColumn(new TopicColumn(false)); + mTopicInfos.addColumn(new ResponseColumn); + mTopicInfos.addColumn(new ActorColumn); + mTopicInfos.addColumn(new RaceColumn); + mTopicInfos.addColumn(new ClassColumn); + mTopicInfos.addColumn(new FactionColumn); + mTopicInfos.addColumn(new CellColumn); + mTopicInfos.addColumn(new DispositionColumn); + mTopicInfos.addColumn(new RankColumn); + mTopicInfos.addColumn(new GenderColumn); + mTopicInfos.addColumn(new PcFactionColumn); + mTopicInfos.addColumn(new PcRankColumn); + mTopicInfos.addColumn(new SoundFileColumn); // Result script - mTopicInfos.addColumn (new NestedParentColumn (Columns::ColumnId_InfoList, - ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); - index = mTopicInfos.getColumns()-1; - mTopicInfos.addAdapter (std::make_pair(&mTopicInfos.getColumn(index), new InfoListAdapter ())); + mTopicInfos.addColumn(new NestedParentColumn( + Columns::ColumnId_InfoList, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); + index = mTopicInfos.getColumns() - 1; + mTopicInfos.addAdapter(std::make_pair(&mTopicInfos.getColumn(index), new InfoListAdapter())); mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_ScriptText, ColumnBase::Display_ScriptLines)); + new NestedChildColumn(Columns::ColumnId_ScriptText, ColumnBase::Display_ScriptLines)); // Special conditions - mTopicInfos.addColumn (new NestedParentColumn (Columns::ColumnId_InfoCondition)); - index = mTopicInfos.getColumns()-1; - mTopicInfos.addAdapter (std::make_pair(&mTopicInfos.getColumn(index), new InfoConditionAdapter ())); + mTopicInfos.addColumn(new NestedParentColumn(Columns::ColumnId_InfoCondition)); + index = mTopicInfos.getColumns() - 1; + mTopicInfos.addAdapter(std::make_pair(&mTopicInfos.getColumn(index), new InfoConditionAdapter())); mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); + new NestedChildColumn(Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); // FIXME: don't have dynamic value enum delegate, use Display_String for now mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar)); + new NestedChildColumn(Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar)); mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); + new NestedChildColumn(Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Value, ColumnBase::Display_Var)); + new NestedChildColumn(Columns::ColumnId_Value, ColumnBase::Display_Var)); - mJournalInfos.addColumn (new StringIdColumn (true)); - mJournalInfos.addColumn (new RecordStateColumn); - mJournalInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_JournalInfo)); - mJournalInfos.addColumn (new TopicColumn (true)); - mJournalInfos.addColumn (new QuestStatusTypeColumn); - mJournalInfos.addColumn (new QuestIndexColumn); - mJournalInfos.addColumn (new QuestDescriptionColumn); + mJournalInfos.addColumn(new StringIdColumn(true)); + mJournalInfos.addColumn(new RecordStateColumn); + mJournalInfos.addColumn(new FixedRecordTypeColumn(UniversalId::Type_JournalInfo)); + mJournalInfos.addColumn(new TopicColumn(true)); + mJournalInfos.addColumn(new QuestStatusTypeColumn); + mJournalInfos.addColumn(new QuestIndexColumn); + mJournalInfos.addColumn(new QuestDescriptionColumn); - mCells.addColumn (new StringIdColumn); - mCells.addColumn (new RecordStateColumn); - mCells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Cell)); - mCells.addColumn (new NameColumn); - mCells.addColumn (new FlagColumn (Columns::ColumnId_SleepForbidden, ESM::Cell::NoSleep)); - mCells.addColumn (new FlagColumn (Columns::ColumnId_InteriorWater, ESM::Cell::HasWater, + mCells.addColumn(new StringIdColumn); + mCells.addColumn(new RecordStateColumn); + mCells.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Cell)); + mCells.addColumn(new NameColumn(ColumnBase::Display_String64)); + mCells.addColumn(new FlagColumn(Columns::ColumnId_SleepForbidden, ESM::Cell::NoSleep)); + mCells.addColumn(new FlagColumn(Columns::ColumnId_InteriorWater, ESM::Cell::HasWater, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); - mCells.addColumn (new FlagColumn (Columns::ColumnId_InteriorSky, ESM::Cell::QuasiEx, + mCells.addColumn(new FlagColumn(Columns::ColumnId_InteriorSky, ESM::Cell::QuasiEx, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); - mCells.addColumn (new RegionColumn); - mCells.addColumn (new RefNumCounterColumn); + mCells.addColumn(new RegionColumn); + mCells.addColumn(new RefNumCounterColumn); // Misc Cell data - mCells.addColumn (new NestedParentColumn (Columns::ColumnId_Cell, - ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); - index = mCells.getColumns()-1; - mCells.addAdapter (std::make_pair(&mCells.getColumn(index), new CellListAdapter ())); + mCells.addColumn(new NestedParentColumn( + Columns::ColumnId_Cell, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); + index = mCells.getColumns() - 1; + mCells.addAdapter(std::make_pair(&mCells.getColumn(index), new CellListAdapter())); mCells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Interior, ColumnBase::Display_Boolean, - ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); + new NestedChildColumn(Columns::ColumnId_Interior, ColumnBase::Display_Boolean, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mCells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Ambient, ColumnBase::Display_Colour)); + new NestedChildColumn(Columns::ColumnId_Ambient, ColumnBase::Display_Colour)); mCells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Sunlight, ColumnBase::Display_Colour)); + new NestedChildColumn(Columns::ColumnId_Sunlight, ColumnBase::Display_Colour)); mCells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Fog, ColumnBase::Display_Colour)); + new NestedChildColumn(Columns::ColumnId_Fog, ColumnBase::Display_Colour)); mCells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_FogDensity, ColumnBase::Display_Float)); + new NestedChildColumn(Columns::ColumnId_FogDensity, ColumnBase::Display_Float)); mCells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_WaterLevel, ColumnBase::Display_Float)); + new NestedChildColumn(Columns::ColumnId_WaterLevel, ColumnBase::Display_Float)); mCells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MapColor, ColumnBase::Display_Colour)); + new NestedChildColumn(Columns::ColumnId_MapColor, ColumnBase::Display_Colour)); - mEnchantments.addColumn (new StringIdColumn); - mEnchantments.addColumn (new RecordStateColumn); - mEnchantments.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Enchantment)); - mEnchantments.addColumn (new EnchantmentTypeColumn); - mEnchantments.addColumn (new CostColumn); - mEnchantments.addColumn (new ChargesColumn2); - mEnchantments.addColumn (new FlagColumn (Columns::ColumnId_AutoCalc, ESM::Enchantment::Autocalc)); + mEnchantments.addColumn(new StringIdColumn); + mEnchantments.addColumn(new RecordStateColumn); + mEnchantments.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Enchantment)); + mEnchantments.addColumn(new EnchantmentTypeColumn); + mEnchantments.addColumn(new CostColumn); + mEnchantments.addColumn(new ChargesColumn2); + mEnchantments.addColumn(new FlagColumn(Columns::ColumnId_AutoCalc, ESM::Enchantment::Autocalc)); // Enchantment effects - mEnchantments.addColumn (new NestedParentColumn (Columns::ColumnId_EffectList)); - index = mEnchantments.getColumns()-1; - mEnchantments.addAdapter (std::make_pair(&mEnchantments.getColumn(index), - new EffectsListAdapter ())); + mEnchantments.addColumn(new NestedParentColumn(Columns::ColumnId_EffectList)); + index = mEnchantments.getColumns() - 1; + mEnchantments.addAdapter( + std::make_pair(&mEnchantments.getColumn(index), new EffectsListAdapter())); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); + new NestedChildColumn(Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); + new NestedChildColumn(Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); + new NestedChildColumn(Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); + new NestedChildColumn(Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_EffectArea, ColumnBase::Display_String)); + new NestedChildColumn(Columns::ColumnId_EffectArea, ColumnBase::Display_String)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light + new NestedChildColumn(Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); - mBodyParts.addColumn (new StringIdColumn); - mBodyParts.addColumn (new RecordStateColumn); - mBodyParts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_BodyPart)); - mBodyParts.addColumn (new BodyPartTypeColumn); - mBodyParts.addColumn (new VampireColumn); + mBodyParts.addColumn(new StringIdColumn); + mBodyParts.addColumn(new RecordStateColumn); + mBodyParts.addColumn(new FixedRecordTypeColumn(UniversalId::Type_BodyPart)); + mBodyParts.addColumn(new BodyPartTypeColumn); + mBodyParts.addColumn(new VampireColumn); mBodyParts.addColumn(new GenderNpcColumn); - mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Playable, - ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); + mBodyParts.addColumn(new FlagColumn(Columns::ColumnId_Playable, ESM::BodyPart::BPF_NotPlayable, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); int meshTypeFlags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh; - MeshTypeColumn *meshTypeColumn = new MeshTypeColumn(meshTypeFlags); - mBodyParts.addColumn (meshTypeColumn); - mBodyParts.addColumn (new ModelColumn); - mBodyParts.addColumn (new BodyPartRaceColumn(meshTypeColumn)); + MeshTypeColumn* meshTypeColumn = new MeshTypeColumn(meshTypeFlags); + mBodyParts.addColumn(meshTypeColumn); + mBodyParts.addColumn(new ModelColumn); + mBodyParts.addColumn(new BodyPartRaceColumn(meshTypeColumn)); - mSoundGens.addColumn (new StringIdColumn); - mSoundGens.addColumn (new RecordStateColumn); - mSoundGens.addColumn (new FixedRecordTypeColumn (UniversalId::Type_SoundGen)); - mSoundGens.addColumn (new CreatureColumn); - mSoundGens.addColumn (new SoundColumn); - mSoundGens.addColumn (new SoundGeneratorTypeColumn); + mSoundGens.addColumn(new StringIdColumn); + mSoundGens.addColumn(new RecordStateColumn); + mSoundGens.addColumn(new FixedRecordTypeColumn(UniversalId::Type_SoundGen)); + mSoundGens.addColumn(new CreatureColumn); + mSoundGens.addColumn(new SoundColumn); + mSoundGens.addColumn(new SoundGeneratorTypeColumn); - mMagicEffects.addColumn (new StringIdColumn); - mMagicEffects.addColumn (new RecordStateColumn); - mMagicEffects.addColumn (new FixedRecordTypeColumn (UniversalId::Type_MagicEffect)); - mMagicEffects.addColumn (new SchoolColumn); - mMagicEffects.addColumn (new BaseCostColumn); - mMagicEffects.addColumn (new EffectTextureColumn (Columns::ColumnId_Icon)); - mMagicEffects.addColumn (new EffectTextureColumn (Columns::ColumnId_Particle)); - mMagicEffects.addColumn (new EffectObjectColumn (Columns::ColumnId_CastingObject)); - mMagicEffects.addColumn (new EffectObjectColumn (Columns::ColumnId_HitObject)); - mMagicEffects.addColumn (new EffectObjectColumn (Columns::ColumnId_AreaObject)); - mMagicEffects.addColumn (new EffectObjectColumn (Columns::ColumnId_BoltObject)); - mMagicEffects.addColumn (new EffectSoundColumn (Columns::ColumnId_CastingSound)); - mMagicEffects.addColumn (new EffectSoundColumn (Columns::ColumnId_HitSound)); - mMagicEffects.addColumn (new EffectSoundColumn (Columns::ColumnId_AreaSound)); - mMagicEffects.addColumn (new EffectSoundColumn (Columns::ColumnId_BoltSound)); - mMagicEffects.addColumn (new FlagColumn ( - Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking)); - mMagicEffects.addColumn (new FlagColumn ( - Columns::ColumnId_AllowEnchanting, ESM::MagicEffect::AllowEnchanting)); - mMagicEffects.addColumn (new FlagColumn ( - Columns::ColumnId_NegativeLight, ESM::MagicEffect::NegativeLight)); - mMagicEffects.addColumn (new DescriptionColumn); + mMagicEffects.addColumn(new StringIdColumn); + mMagicEffects.addColumn(new RecordStateColumn); + mMagicEffects.addColumn(new FixedRecordTypeColumn(UniversalId::Type_MagicEffect)); + mMagicEffects.addColumn(new SchoolColumn); + mMagicEffects.addColumn(new BaseCostColumn); + mMagicEffects.addColumn(new EffectTextureColumn(Columns::ColumnId_Icon)); + mMagicEffects.addColumn(new EffectTextureColumn(Columns::ColumnId_Particle)); + mMagicEffects.addColumn(new EffectObjectColumn(Columns::ColumnId_CastingObject)); + mMagicEffects.addColumn(new EffectObjectColumn(Columns::ColumnId_HitObject)); + mMagicEffects.addColumn(new EffectObjectColumn(Columns::ColumnId_AreaObject)); + mMagicEffects.addColumn(new EffectObjectColumn(Columns::ColumnId_BoltObject)); + mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_CastingSound)); + mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_HitSound)); + mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_AreaSound)); + mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_BoltSound)); + mMagicEffects.addColumn( + new FlagColumn(Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking)); + mMagicEffects.addColumn( + new FlagColumn(Columns::ColumnId_AllowEnchanting, ESM::MagicEffect::AllowEnchanting)); + mMagicEffects.addColumn( + new FlagColumn(Columns::ColumnId_NegativeLight, ESM::MagicEffect::NegativeLight)); + mMagicEffects.addColumn(new DescriptionColumn); - mLand.addColumn (new StringIdColumn); - mLand.addColumn (new RecordStateColumn); - mLand.addColumn (new FixedRecordTypeColumn(UniversalId::Type_Land)); - mLand.addColumn (new LandPluginIndexColumn); - mLand.addColumn (new LandNormalsColumn); - mLand.addColumn (new LandHeightsColumn); - mLand.addColumn (new LandColoursColumn); - mLand.addColumn (new LandTexturesColumn); + mLand.addColumn(new StringIdColumn); + mLand.addColumn(new RecordStateColumn); + mLand.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Land)); + mLand.addColumn(new LandPluginIndexColumn); + mLand.addColumn(new LandNormalsColumn); + mLand.addColumn(new LandHeightsColumn); + mLand.addColumn(new LandColoursColumn); + mLand.addColumn(new LandTexturesColumn); - mLandTextures.addColumn (new StringIdColumn(true)); - mLandTextures.addColumn (new RecordStateColumn); - mLandTextures.addColumn (new FixedRecordTypeColumn(UniversalId::Type_LandTexture)); - mLandTextures.addColumn (new LandTextureNicknameColumn); - mLandTextures.addColumn (new LandTexturePluginIndexColumn); - mLandTextures.addColumn (new LandTextureIndexColumn); - mLandTextures.addColumn (new TextureColumn); + mLandTextures.addColumn(new StringIdColumn(true)); + mLandTextures.addColumn(new RecordStateColumn); + mLandTextures.addColumn(new FixedRecordTypeColumn(UniversalId::Type_LandTexture)); + mLandTextures.addColumn(new LandTextureNicknameColumn); + mLandTextures.addColumn(new LandTexturePluginIndexColumn); + mLandTextures.addColumn(new LandTextureIndexColumn); + mLandTextures.addColumn(new TextureColumn); - mPathgrids.addColumn (new StringIdColumn); - mPathgrids.addColumn (new RecordStateColumn); - mPathgrids.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Pathgrid)); + mPathgrids.addColumn(new StringIdColumn); + mPathgrids.addColumn(new RecordStateColumn); + mPathgrids.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Pathgrid)); // new object deleted in dtor of Collection - mPathgrids.addColumn (new NestedParentColumn (Columns::ColumnId_PathgridPoints)); - index = mPathgrids.getColumns()-1; + mPathgrids.addColumn(new NestedParentColumn(Columns::ColumnId_PathgridPoints)); + index = mPathgrids.getColumns() - 1; // new object deleted in dtor of NestedCollection - mPathgrids.addAdapter (std::make_pair(&mPathgrids.getColumn(index), new PathgridPointListAdapter ())); + mPathgrids.addAdapter(std::make_pair(&mPathgrids.getColumn(index), new PathgridPointListAdapter())); // new objects deleted in dtor of NestableColumn // WARNING: The order of the columns below are assumed in PathgridPointListAdapter + mPathgrids.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_PathgridIndex, ColumnBase::Display_Integer, ColumnBase::Flag_Dialogue, false)); mPathgrids.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_PathgridIndex, ColumnBase::Display_Integer, - ColumnBase::Flag_Dialogue, false)); + new NestedChildColumn(Columns::ColumnId_PathgridPosX, ColumnBase::Display_Integer)); mPathgrids.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_PathgridPosX, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_PathgridPosY, ColumnBase::Display_Integer)); mPathgrids.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_PathgridPosY, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_PathgridPosZ, ColumnBase::Display_Integer)); + + mPathgrids.addColumn(new NestedParentColumn(Columns::ColumnId_PathgridEdges)); + index = mPathgrids.getColumns() - 1; + mPathgrids.addAdapter(std::make_pair(&mPathgrids.getColumn(index), new PathgridEdgeListAdapter())); + mPathgrids.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_PathgridEdgeIndex, ColumnBase::Display_Integer, ColumnBase::Flag_Dialogue, false)); mPathgrids.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_PathgridPosZ, ColumnBase::Display_Integer)); - - mPathgrids.addColumn (new NestedParentColumn (Columns::ColumnId_PathgridEdges)); - index = mPathgrids.getColumns()-1; - mPathgrids.addAdapter (std::make_pair(&mPathgrids.getColumn(index), new PathgridEdgeListAdapter ())); + new NestedChildColumn(Columns::ColumnId_PathgridEdge0, ColumnBase::Display_Integer)); mPathgrids.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_PathgridEdgeIndex, ColumnBase::Display_Integer, - ColumnBase::Flag_Dialogue, false)); - mPathgrids.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_PathgridEdge0, ColumnBase::Display_Integer)); - mPathgrids.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_PathgridEdge1, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_PathgridEdge1, ColumnBase::Display_Integer)); - mStartScripts.addColumn (new StringIdColumn); - mStartScripts.addColumn (new RecordStateColumn); - mStartScripts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_StartScript)); + mStartScripts.addColumn(new StringIdColumn); + mStartScripts.addColumn(new RecordStateColumn); + mStartScripts.addColumn(new FixedRecordTypeColumn(UniversalId::Type_StartScript)); - mRefs.addColumn (new StringIdColumn (true)); - mRefs.addColumn (new RecordStateColumn); - mRefs.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Reference)); - mRefs.addColumn (new CellColumn (true)); - mRefs.addColumn (new OriginalCellColumn); - mRefs.addColumn (new IdColumn); - mRefs.addColumn (new PosColumn (&CellRef::mPos, 0, false)); - mRefs.addColumn (new PosColumn (&CellRef::mPos, 1, false)); - mRefs.addColumn (new PosColumn (&CellRef::mPos, 2, false)); - mRefs.addColumn (new RotColumn (&CellRef::mPos, 0, false)); - mRefs.addColumn (new RotColumn (&CellRef::mPos, 1, false)); - mRefs.addColumn (new RotColumn (&CellRef::mPos, 2, false)); - mRefs.addColumn (new ScaleColumn); - mRefs.addColumn (new OwnerColumn); - mRefs.addColumn (new SoulColumn); - mRefs.addColumn (new FactionColumn); - mRefs.addColumn (new FactionIndexColumn); - mRefs.addColumn (new ChargesColumn); - mRefs.addColumn (new EnchantmentChargesColumn); - mRefs.addColumn (new GoldValueColumn); - mRefs.addColumn (new TeleportColumn); - mRefs.addColumn (new TeleportCellColumn); - mRefs.addColumn (new PosColumn (&CellRef::mDoorDest, 0, true)); - mRefs.addColumn (new PosColumn (&CellRef::mDoorDest, 1, true)); - mRefs.addColumn (new PosColumn (&CellRef::mDoorDest, 2, true)); - mRefs.addColumn (new RotColumn (&CellRef::mDoorDest, 0, true)); - mRefs.addColumn (new RotColumn (&CellRef::mDoorDest, 1, true)); - mRefs.addColumn (new RotColumn (&CellRef::mDoorDest, 2, true)); - mRefs.addColumn (new LockLevelColumn); - mRefs.addColumn (new KeyColumn); - mRefs.addColumn (new TrapColumn); - mRefs.addColumn (new OwnerGlobalColumn); - mRefs.addColumn (new RefNumColumn); + mRefs.addColumn(new StringIdColumn(true)); + mRefs.addColumn(new RecordStateColumn); + mRefs.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Reference)); + mRefs.addColumn(new CellColumn(true)); + mRefs.addColumn(new OriginalCellColumn); + mRefs.addColumn(new IdColumn); + mRefs.addColumn(new PosColumn(&CellRef::mPos, 0, false)); + mRefs.addColumn(new PosColumn(&CellRef::mPos, 1, false)); + mRefs.addColumn(new PosColumn(&CellRef::mPos, 2, false)); + mRefs.addColumn(new RotColumn(&CellRef::mPos, 0, false)); + mRefs.addColumn(new RotColumn(&CellRef::mPos, 1, false)); + mRefs.addColumn(new RotColumn(&CellRef::mPos, 2, false)); + mRefs.addColumn(new ScaleColumn); + mRefs.addColumn(new OwnerColumn); + mRefs.addColumn(new SoulColumn); + mRefs.addColumn(new FactionColumn); + mRefs.addColumn(new FactionIndexColumn); + mRefs.addColumn(new ChargesColumn); + mRefs.addColumn(new EnchantmentChargesColumn); + mRefs.addColumn(new GoldValueColumn); + mRefs.addColumn(new TeleportColumn); + mRefs.addColumn(new TeleportCellColumn); + mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 0, true)); + mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 1, true)); + mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 2, true)); + mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 0, true)); + mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 1, true)); + mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 2, true)); + mRefs.addColumn(new LockLevelColumn); + mRefs.addColumn(new KeyColumn); + mRefs.addColumn(new TrapColumn); + mRefs.addColumn(new OwnerGlobalColumn); + mRefs.addColumn(new RefNumColumn); - mFilters.addColumn (new StringIdColumn); - mFilters.addColumn (new RecordStateColumn); - mFilters.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Filter)); - mFilters.addColumn (new FilterColumn); - mFilters.addColumn (new DescriptionColumn); + mFilters.addColumn(new StringIdColumn); + mFilters.addColumn(new RecordStateColumn); + mFilters.addColumn(new FixedRecordTypeColumn(UniversalId::Type_Filter)); + mFilters.addColumn(new FilterColumn); + mFilters.addColumn(new DescriptionColumn); - mDebugProfiles.addColumn (new StringIdColumn); - mDebugProfiles.addColumn (new RecordStateColumn); - mDebugProfiles.addColumn (new FixedRecordTypeColumn (UniversalId::Type_DebugProfile)); - mDebugProfiles.addColumn (new FlagColumn2 ( - Columns::ColumnId_DefaultProfile, ESM::DebugProfile::Flag_Default)); - mDebugProfiles.addColumn (new FlagColumn2 ( - Columns::ColumnId_BypassNewGame, ESM::DebugProfile::Flag_BypassNewGame)); - mDebugProfiles.addColumn (new FlagColumn2 ( - Columns::ColumnId_GlobalProfile, ESM::DebugProfile::Flag_Global)); - mDebugProfiles.addColumn (new DescriptionColumn); - mDebugProfiles.addColumn (new ScriptColumn ( - ScriptColumn::Type_Lines)); + mDebugProfiles.addColumn(new StringIdColumn); + mDebugProfiles.addColumn(new RecordStateColumn); + mDebugProfiles.addColumn(new FixedRecordTypeColumn(UniversalId::Type_DebugProfile)); + mDebugProfiles.addColumn( + new FlagColumn2(Columns::ColumnId_DefaultProfile, ESM::DebugProfile::Flag_Default)); + mDebugProfiles.addColumn( + new FlagColumn2(Columns::ColumnId_BypassNewGame, ESM::DebugProfile::Flag_BypassNewGame)); + mDebugProfiles.addColumn( + new FlagColumn2(Columns::ColumnId_GlobalProfile, ESM::DebugProfile::Flag_Global)); + mDebugProfiles.addColumn(new DescriptionColumn); + mDebugProfiles.addColumn(new ScriptColumn(ScriptColumn::Type_Lines)); - mMetaData.appendBlankRecord ("sys::meta"); + mMetaData.appendBlankRecord(ESM::RefId::stringRefId("sys::meta")); - mMetaData.addColumn (new StringIdColumn (true)); - mMetaData.addColumn (new RecordStateColumn); - mMetaData.addColumn (new FixedRecordTypeColumn (UniversalId::Type_MetaData)); - mMetaData.addColumn (new FormatColumn); - mMetaData.addColumn (new AuthorColumn); - mMetaData.addColumn (new FileDescriptionColumn); + mMetaData.addColumn(new StringIdColumn(true)); + mMetaData.addColumn(new RecordStateColumn); + mMetaData.addColumn(new FixedRecordTypeColumn(UniversalId::Type_MetaData)); + mMetaData.addColumn(new FormatColumn); + mMetaData.addColumn(new AuthorColumn); + mMetaData.addColumn(new FileDescriptionColumn); - addModel (new IdTable (&mGlobals), UniversalId::Type_Global); - addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); - addModel (new IdTable (&mSkills), UniversalId::Type_Skill); - addModel (new IdTable (&mClasses), UniversalId::Type_Class); - addModel (new IdTree (&mFactions, &mFactions), UniversalId::Type_Faction); - addModel (new IdTree (&mRaces, &mRaces), UniversalId::Type_Race); - addModel (new IdTable (&mSounds), UniversalId::Type_Sound); - addModel (new IdTable (&mScripts), UniversalId::Type_Script); - addModel (new IdTree (&mRegions, &mRegions), UniversalId::Type_Region); - addModel (new IdTree (&mBirthsigns, &mBirthsigns), UniversalId::Type_Birthsign); - addModel (new IdTree (&mSpells, &mSpells), UniversalId::Type_Spell); - addModel (new IdTable (&mTopics), UniversalId::Type_Topic); - addModel (new IdTable (&mJournals), UniversalId::Type_Journal); - addModel (new IdTree (&mTopicInfos, &mTopicInfos, IdTable::Feature_ReorderWithinTopic), - UniversalId::Type_TopicInfo); - addModel (new IdTable (&mJournalInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_JournalInfo); - addModel (new IdTree (&mCells, &mCells, IdTable::Feature_ViewId), UniversalId::Type_Cell); - addModel (new IdTree (&mEnchantments, &mEnchantments), UniversalId::Type_Enchantment); - addModel (new IdTable (&mBodyParts), UniversalId::Type_BodyPart); - addModel (new IdTable (&mSoundGens), UniversalId::Type_SoundGen); - addModel (new IdTable (&mMagicEffects), UniversalId::Type_MagicEffect); - addModel (new IdTable (&mLand, IdTable::Feature_AllowTouch), UniversalId::Type_Land); - addModel (new LandTextureIdTable (&mLandTextures), UniversalId::Type_LandTexture); - addModel (new IdTree (&mPathgrids, &mPathgrids), UniversalId::Type_Pathgrid); - addModel (new IdTable (&mStartScripts), UniversalId::Type_StartScript); - addModel (new IdTree (&mReferenceables, &mReferenceables, IdTable::Feature_Preview), - UniversalId::Type_Referenceable); - addModel (new IdTable (&mRefs, IdTable::Feature_ViewCell | IdTable::Feature_Preview), UniversalId::Type_Reference); - addModel (new IdTable (&mFilters), UniversalId::Type_Filter); - addModel (new IdTable (&mDebugProfiles), UniversalId::Type_DebugProfile); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Meshes)), - UniversalId::Type_Mesh); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Icons)), - UniversalId::Type_Icon); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Musics)), - UniversalId::Type_Music); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_SoundsRes)), - UniversalId::Type_SoundRes); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Textures)), - UniversalId::Type_Texture); - addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)), - UniversalId::Type_Video); - addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData); + addModel(new IdTable(&mGlobals), UniversalId::Type_Global); + addModel(new IdTable(&mGmsts), UniversalId::Type_Gmst); + addModel(new IdTable(&mSkills), UniversalId::Type_Skill); + addModel(new IdTable(&mClasses), UniversalId::Type_Class); + addModel(new IdTree(&mFactions, &mFactions), UniversalId::Type_Faction); + addModel(new IdTree(&mRaces, &mRaces), UniversalId::Type_Race); + addModel(new IdTable(&mSounds), UniversalId::Type_Sound); + addModel(new IdTable(&mScripts), UniversalId::Type_Script); + addModel(new IdTree(&mRegions, &mRegions), UniversalId::Type_Region); + addModel(new IdTree(&mBirthsigns, &mBirthsigns), UniversalId::Type_Birthsign); + addModel(new IdTree(&mSpells, &mSpells), UniversalId::Type_Spell); + addModel(new IdTable(&mTopics), UniversalId::Type_Topic); + addModel(new IdTable(&mJournals), UniversalId::Type_Journal); + addModel(new IdTree(&mTopicInfos, &mTopicInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_TopicInfo); + addModel(new IdTable(&mJournalInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_JournalInfo); + addModel(new IdTree(&mCells, &mCells, IdTable::Feature_ViewId), UniversalId::Type_Cell); + addModel(new IdTree(&mEnchantments, &mEnchantments), UniversalId::Type_Enchantment); + addModel(new IdTable(&mBodyParts), UniversalId::Type_BodyPart); + addModel(new IdTable(&mSoundGens), UniversalId::Type_SoundGen); + addModel(new IdTable(&mMagicEffects), UniversalId::Type_MagicEffect); + addModel(new IdTable(&mLand, IdTable::Feature_AllowTouch), UniversalId::Type_Land); + addModel(new LandTextureIdTable(&mLandTextures), UniversalId::Type_LandTexture); + addModel(new IdTree(&mPathgrids, &mPathgrids), UniversalId::Type_Pathgrid); + addModel(new IdTable(&mStartScripts), UniversalId::Type_StartScript); + addModel(new IdTree(&mReferenceables, &mReferenceables, IdTable::Feature_Preview), UniversalId::Type_Referenceable); + addModel(new IdTable(&mRefs, IdTable::Feature_ViewCell | IdTable::Feature_Preview), UniversalId::Type_Reference); + addModel(new IdTable(&mFilters), UniversalId::Type_Filter); + addModel(new IdTable(&mDebugProfiles), UniversalId::Type_DebugProfile); + addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Meshes)), UniversalId::Type_Mesh); + addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Icons)), UniversalId::Type_Icon); + addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Musics)), UniversalId::Type_Music); + addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_SoundsRes)), UniversalId::Type_SoundRes); + addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Textures)), UniversalId::Type_Texture); + addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Videos)), UniversalId::Type_Video); + addModel(new IdTable(&mMetaData), UniversalId::Type_MetaData); - mActorAdapter.reset(new ActorAdapter(*this)); + mActorAdapter = std::make_unique(*this); mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } CSMWorld::Data::~Data() { - for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) + for (std::vector::iterator iter(mModels.begin()); iter != mModels.end(); ++iter) delete *iter; delete mReader; @@ -734,7 +796,6 @@ CSMWorld::IdCollection& CSMWorld::Data::getSpells() return mSpells; } - const CSMWorld::IdCollection& CSMWorld::Data::getTopics() const { return mTopics; @@ -905,39 +966,39 @@ CSMWorld::IdCollection& CSMWorld::Data::getStartScripts() return mStartScripts; } -const CSMWorld::Resources& CSMWorld::Data::getResources (const UniversalId& id) const +const CSMWorld::Resources& CSMWorld::Data::getResources(const UniversalId& id) const { - return mResourcesManager.get (id.getType()); + return mResourcesManager.get(id.getType()); } const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const { - return mMetaData.getRecord (0).get(); + return mMetaData.getRecord(0).get(); } -void CSMWorld::Data::setMetaData (const MetaData& metaData) +void CSMWorld::Data::setMetaData(const MetaData& metaData) { - Record record (RecordBase::State_ModifiedOnly, nullptr, &metaData); - mMetaData.setRecord (0, record); + mMetaData.setRecord( + 0, std::make_unique>(Record(RecordBase::State_ModifiedOnly, nullptr, &metaData))); } -QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) +QAbstractItemModel* CSMWorld::Data::getTableModel(const CSMWorld::UniversalId& id) { - std::map::iterator iter = mModelIndex.find (id.getType()); + std::map::iterator iter = mModelIndex.find(id.getType()); - if (iter==mModelIndex.end()) + if (iter == mModelIndex.end()) { // try creating missing (secondary) tables on the fly // // Note: We create these tables here so we don't have to deal with them during load/initial // construction of the ESX data where no update signals are available. - if (id.getType()==UniversalId::Type_RegionMap) + if (id.getType() == UniversalId::Type_RegionMap) { - RegionMap *table = nullptr; - addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap, false); + RegionMap* table = nullptr; + addModel(table = new RegionMap(*this), UniversalId::Type_RegionMap, false); return table; } - throw std::logic_error ("No table model available for " + id.toString()); + throw std::logic_error("No table model available for " + id.toString()); } return iter->second; @@ -958,8 +1019,29 @@ void CSMWorld::Data::merge() mGlobals.merge(); } -int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base, bool project) +int CSMWorld::Data::getTotalRecords(const std::vector& files) { + int records = 0; + + std::unique_ptr reader = std::make_unique(); + + for (const auto& file : files) + { + if (!std::filesystem::exists(file)) + continue; + + reader->open(file); + records += reader->getRecordCount(); + reader->close(); + } + + return records; +} + +int CSMWorld::Data::startLoading(const std::filesystem::path& path, bool base, bool project) +{ + Log(Debug::Info) << "Loading content file " << path; + // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading std::shared_ptr ptr(mReader); mReaders.push_back(ptr); @@ -968,11 +1050,9 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mDialogue = nullptr; mReader = new ESM::ESMReader; - mReader->setEncoder (&mEncoder); + mReader->setEncoder(&mEncoder); mReader->setIndex((project || !base) ? 0 : mReaderIndex++); - mReader->open (path.string()); - - mContentFileNames.insert(std::make_pair(path.filename().string(), mReader->getIndex())); + mReader->open(path); mBase = base; mProject = project; @@ -980,10 +1060,11 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base if (!mProject && !mBase) { MetaData metaData; - metaData.mId = "sys::meta"; - metaData.load (*mReader); + metaData.mId = ESM::RefId::stringRefId("sys::meta"); + metaData.load(*mReader); - mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, nullptr, &metaData)); + mMetaData.setRecord(0, + std::make_unique>(Record(RecordBase::State_ModifiedOnly, nullptr, &metaData))); } return mReader->getRecordCount(); @@ -992,59 +1073,57 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base void CSMWorld::Data::loadFallbackEntries() { // Load default marker definitions, if game files do not have them for some reason - std::pair staticMarkers[] = { - std::make_pair("DivineMarker", "marker_divine.nif"), - std::make_pair("DoorMarker", "marker_arrow.nif"), - std::make_pair("NorthMarker", "marker_north.nif"), - std::make_pair("TempleMarker", "marker_temple.nif"), - std::make_pair("TravelMarker", "marker_travel.nif") - }; + std::pair staticMarkers[] = { std::make_pair("DivineMarker", "marker_divine.nif"), + std::make_pair("DoorMarker", "marker_arrow.nif"), std::make_pair("NorthMarker", "marker_north.nif"), + std::make_pair("TempleMarker", "marker_temple.nif"), std::make_pair("TravelMarker", "marker_travel.nif") }; - std::pair doorMarkers[] = { - std::make_pair("PrisonMarker", "marker_prison.nif") - }; + std::pair doorMarkers[] = { std::make_pair("PrisonMarker", "marker_prison.nif") }; - for (const auto &marker : staticMarkers) + for (const auto& [id, model] : staticMarkers) { - if (mReferenceables.searchId (marker.first)==-1) + const ESM::RefId refId = ESM::RefId::stringRefId(id); + if (mReferenceables.searchId(refId) == -1) { ESM::Static newMarker; - newMarker.mId = marker.first; - newMarker.mModel = marker.second; - CSMWorld::Record record; - record.mBase = newMarker; - record.mState = CSMWorld::RecordBase::State_BaseOnly; - mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static); + newMarker.mId = refId; + newMarker.mModel = model; + newMarker.mRecordFlags = 0; + auto record = std::make_unique>(); + record->mBase = newMarker; + record->mState = CSMWorld::RecordBase::State_BaseOnly; + mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Static); } } - for (const auto &marker : doorMarkers) + for (const auto& [id, model] : doorMarkers) { - if (mReferenceables.searchId (marker.first)==-1) + const ESM::RefId refId = ESM::RefId::stringRefId(id); + if (mReferenceables.searchId(refId) == -1) { ESM::Door newMarker; - newMarker.mId = marker.first; - newMarker.mModel = marker.second; - CSMWorld::Record record; - record.mBase = newMarker; - record.mState = CSMWorld::RecordBase::State_BaseOnly; - mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Door); + newMarker.mId = refId; + newMarker.mModel = model; + newMarker.mRecordFlags = 0; + auto record = std::make_unique>(); + record->mBase = newMarker; + record->mState = CSMWorld::RecordBase::State_BaseOnly; + mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Door); } } } -bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) +bool CSMWorld::Data::continueLoading(CSMDoc::Messages& messages) { if (!mReader) - throw std::logic_error ("can't continue loading, because no load has been started"); + throw std::logic_error("can't continue loading, because no load has been started"); if (!mReader->hasMoreRecs()) { if (mBase) { - // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading. - // We don't store non-base reader, because everything going into modified will be - // fully loaded during the initial loading process. + // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand + // loading. We don't store non-base reader, because everything going into modified will be fully loaded + // during the initial loading process. std::shared_ptr ptr(mReader); mReaders.push_back(ptr); } @@ -1065,107 +1144,182 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) bool unhandledRecord = false; - switch (n.intval) + switch (n.toInt()) { - case ESM::REC_GLOB: mGlobals.load (*mReader, mBase); break; - case ESM::REC_GMST: mGmsts.load (*mReader, mBase); break; - case ESM::REC_SKIL: mSkills.load (*mReader, mBase); break; - case ESM::REC_CLAS: mClasses.load (*mReader, mBase); break; - case ESM::REC_FACT: mFactions.load (*mReader, mBase); break; - case ESM::REC_RACE: mRaces.load (*mReader, mBase); break; - case ESM::REC_SOUN: mSounds.load (*mReader, mBase); break; - case ESM::REC_SCPT: mScripts.load (*mReader, mBase); break; - case ESM::REC_REGN: mRegions.load (*mReader, mBase); break; - case ESM::REC_BSGN: mBirthsigns.load (*mReader, mBase); break; - case ESM::REC_SPEL: mSpells.load (*mReader, mBase); break; - case ESM::REC_ENCH: mEnchantments.load (*mReader, mBase); break; - case ESM::REC_BODY: mBodyParts.load (*mReader, mBase); break; - case ESM::REC_SNDG: mSoundGens.load (*mReader, mBase); break; - case ESM::REC_MGEF: mMagicEffects.load (*mReader, mBase); break; - case ESM::REC_PGRD: mPathgrids.load (*mReader, mBase); break; - case ESM::REC_SSCR: mStartScripts.load (*mReader, mBase); break; + case ESM::REC_GLOB: + mGlobals.load(*mReader, mBase); + break; + case ESM::REC_GMST: + mGmsts.load(*mReader, mBase); + break; + case ESM::REC_SKIL: + mSkills.load(*mReader, mBase); + break; + case ESM::REC_CLAS: + mClasses.load(*mReader, mBase); + break; + case ESM::REC_FACT: + mFactions.load(*mReader, mBase); + break; + case ESM::REC_RACE: + mRaces.load(*mReader, mBase); + break; + case ESM::REC_SOUN: + mSounds.load(*mReader, mBase); + break; + case ESM::REC_SCPT: + mScripts.load(*mReader, mBase); + break; + case ESM::REC_REGN: + mRegions.load(*mReader, mBase); + break; + case ESM::REC_BSGN: + mBirthsigns.load(*mReader, mBase); + break; + case ESM::REC_SPEL: + mSpells.load(*mReader, mBase); + break; + case ESM::REC_ENCH: + mEnchantments.load(*mReader, mBase); + break; + case ESM::REC_BODY: + mBodyParts.load(*mReader, mBase); + break; + case ESM::REC_SNDG: + mSoundGens.load(*mReader, mBase); + break; + case ESM::REC_MGEF: + mMagicEffects.load(*mReader, mBase); + break; + case ESM::REC_PGRD: + mPathgrids.load(*mReader, mBase); + break; + case ESM::REC_SSCR: + mStartScripts.load(*mReader, mBase); + break; - case ESM::REC_LTEX: mLandTextures.load (*mReader, mBase); break; + case ESM::REC_LTEX: + mLandTextures.load(*mReader, mBase); + break; - case ESM::REC_LAND: mLand.load(*mReader, mBase); break; + case ESM::REC_LAND: + mLand.load(*mReader, mBase); + break; case ESM::REC_CELL: { - int index = mCells.load (*mReader, mBase); + int index = mCells.load(*mReader, mBase); if (index < 0 || index >= mCells.getSize()) { // log an error and continue loading the refs to the last loaded cell - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_None); - messages.add (id, "Logic error: cell index out of bounds", "", CSMDoc::Message::Severity_Error); - index = mCells.getSize()-1; + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_None); + messages.add(id, "Logic error: cell index out of bounds", "", CSMDoc::Message::Severity_Error); + index = mCells.getSize() - 1; } - std::string cellId = Misc::StringUtils::lowerCase (mCells.getId (index)); - mRefs.load (*mReader, index, mBase, mRefLoadCache[cellId], messages); + + mRefs.load(*mReader, index, mBase, mRefLoadCache[mCells.getId(index)], messages); break; } - case ESM::REC_ACTI: mReferenceables.load (*mReader, mBase, UniversalId::Type_Activator); break; - case ESM::REC_ALCH: mReferenceables.load (*mReader, mBase, UniversalId::Type_Potion); break; - case ESM::REC_APPA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Apparatus); break; - case ESM::REC_ARMO: mReferenceables.load (*mReader, mBase, UniversalId::Type_Armor); break; - case ESM::REC_BOOK: mReferenceables.load (*mReader, mBase, UniversalId::Type_Book); break; - case ESM::REC_CLOT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Clothing); break; - case ESM::REC_CONT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Container); break; - case ESM::REC_CREA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Creature); break; - case ESM::REC_DOOR: mReferenceables.load (*mReader, mBase, UniversalId::Type_Door); break; - case ESM::REC_INGR: mReferenceables.load (*mReader, mBase, UniversalId::Type_Ingredient); break; + case ESM::REC_ACTI: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Activator); + break; + case ESM::REC_ALCH: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Potion); + break; + case ESM::REC_APPA: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Apparatus); + break; + case ESM::REC_ARMO: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Armor); + break; + case ESM::REC_BOOK: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Book); + break; + case ESM::REC_CLOT: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Clothing); + break; + case ESM::REC_CONT: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Container); + break; + case ESM::REC_CREA: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Creature); + break; + case ESM::REC_DOOR: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Door); + break; + case ESM::REC_INGR: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Ingredient); + break; case ESM::REC_LEVC: - mReferenceables.load (*mReader, mBase, UniversalId::Type_CreatureLevelledList); break; + mReferenceables.load(*mReader, mBase, UniversalId::Type_CreatureLevelledList); + break; case ESM::REC_LEVI: - mReferenceables.load (*mReader, mBase, UniversalId::Type_ItemLevelledList); break; - case ESM::REC_LIGH: mReferenceables.load (*mReader, mBase, UniversalId::Type_Light); break; - case ESM::REC_LOCK: mReferenceables.load (*mReader, mBase, UniversalId::Type_Lockpick); break; + mReferenceables.load(*mReader, mBase, UniversalId::Type_ItemLevelledList); + break; + case ESM::REC_LIGH: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Light); + break; + case ESM::REC_LOCK: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Lockpick); + break; case ESM::REC_MISC: - mReferenceables.load (*mReader, mBase, UniversalId::Type_Miscellaneous); break; - case ESM::REC_NPC_: mReferenceables.load (*mReader, mBase, UniversalId::Type_Npc); break; - case ESM::REC_PROB: mReferenceables.load (*mReader, mBase, UniversalId::Type_Probe); break; - case ESM::REC_REPA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Repair); break; - case ESM::REC_STAT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Static); break; - case ESM::REC_WEAP: mReferenceables.load (*mReader, mBase, UniversalId::Type_Weapon); break; + mReferenceables.load(*mReader, mBase, UniversalId::Type_Miscellaneous); + break; + case ESM::REC_NPC_: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Npc); + break; + case ESM::REC_PROB: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Probe); + break; + case ESM::REC_REPA: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Repair); + break; + case ESM::REC_STAT: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Static); + break; + case ESM::REC_WEAP: + mReferenceables.load(*mReader, mBase, UniversalId::Type_Weapon); + break; case ESM::REC_DIAL: { ESM::Dialogue record; bool isDeleted = false; - record.load (*mReader, isDeleted); + record.load(*mReader, isDeleted); if (isDeleted) { // record vector can be shuffled around which would make pointer to record invalid mDialogue = nullptr; - if (mJournals.tryDelete (record.mId)) + if (mJournals.tryDelete(record.mId)) { - mJournalInfos.removeDialogueInfos(record.mId); + removeDialogueInfos(record.mId, mJournalInfoOrder, mJournalInfos); } - else if (mTopics.tryDelete (record.mId)) + else if (mTopics.tryDelete(record.mId)) { - mTopicInfos.removeDialogueInfos(record.mId); + removeDialogueInfos(record.mId, mTopicInfoOrder, mTopicInfos); } else { - messages.add (UniversalId::Type_None, - "Trying to delete dialogue record " + record.mId + " which does not exist", - "", CSMDoc::Message::Severity_Warning); + messages.add(UniversalId::Type_None, + "Trying to delete dialogue record " + record.mId.getRefIdString() + " which does not exist", "", + CSMDoc::Message::Severity_Warning); } } else { if (record.mType == ESM::Dialogue::Journal) { - mJournals.load (record, mBase); - mDialogue = &mJournals.getRecord (record.mId).get(); + mJournals.load(record, mBase); + mDialogue = &mJournals.getRecord(record.mId).get(); } else { - mTopics.load (record, mBase); - mDialogue = &mTopics.getRecord (record.mId).get(); + mTopics.load(record, mBase); + mDialogue = &mTopics.getRecord(record.mId).get(); } } @@ -1176,17 +1330,17 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { if (!mDialogue) { - messages.add (UniversalId::Type_None, - "Found info record not following a dialogue record", "", CSMDoc::Message::Severity_Error); + messages.add(UniversalId::Type_None, "Found info record not following a dialogue record", "", + CSMDoc::Message::Severity_Error); mReader->skipRecord(); break; } - if (mDialogue->mType==ESM::Dialogue::Journal) - mJournalInfos.load (*mReader, mBase, *mDialogue); + if (mDialogue->mType == ESM::Dialogue::Journal) + mJournalInfos.load(*mReader, mBase, *mDialogue, mJournalInfoOrder); else - mTopicInfos.load (*mReader, mBase, *mDialogue); + mTopicInfos.load(*mReader, mBase, *mDialogue, mTopicInfoOrder); break; } @@ -1199,7 +1353,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) break; } - mFilters.load (*mReader, mBase); + mFilters.load(*mReader, mBase); break; case ESM::REC_DBGP: @@ -1210,7 +1364,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) break; } - mDebugProfiles.load (*mReader, mBase); + mDebugProfiles.load(*mReader, mBase); break; default: @@ -1220,8 +1374,8 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) if (unhandledRecord) { - messages.add (UniversalId::Type_None, "Unsupported record type: " + n.toString(), "", - CSMDoc::Message::Severity_Error); + messages.add( + UniversalId::Type_None, "Unsupported record type: " + n.toString(), "", CSMDoc::Message::Severity_Error); mReader->skipRecord(); } @@ -1229,79 +1383,59 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) return false; } -bool CSMWorld::Data::hasId (const std::string& id) const +void CSMWorld::Data::finishLoading() { - return - getGlobals().searchId (id)!=-1 || - getGmsts().searchId (id)!=-1 || - getSkills().searchId (id)!=-1 || - getClasses().searchId (id)!=-1 || - getFactions().searchId (id)!=-1 || - getRaces().searchId (id)!=-1 || - getSounds().searchId (id)!=-1 || - getScripts().searchId (id)!=-1 || - getRegions().searchId (id)!=-1 || - getBirthsigns().searchId (id)!=-1 || - getSpells().searchId (id)!=-1 || - getTopics().searchId (id)!=-1 || - getJournals().searchId (id)!=-1 || - getCells().searchId (id)!=-1 || - getEnchantments().searchId (id)!=-1 || - getBodyParts().searchId (id)!=-1 || - getSoundGens().searchId (id)!=-1 || - getMagicEffects().searchId (id)!=-1 || - getReferenceables().searchId (id)!=-1; + mTopicInfos.sort(mTopicInfoOrder); + mJournalInfos.sort(mJournalInfoOrder); } -int CSMWorld::Data::count (RecordBase::State state) const +bool CSMWorld::Data::hasId(const std::string& id) const { - return - count (state, mGlobals) + - count (state, mGmsts) + - count (state, mSkills) + - count (state, mClasses) + - count (state, mFactions) + - count (state, mRaces) + - count (state, mSounds) + - count (state, mScripts) + - count (state, mRegions) + - count (state, mBirthsigns) + - count (state, mSpells) + - count (state, mCells) + - count (state, mEnchantments) + - count (state, mBodyParts) + - count (state, mLand) + - count (state, mLandTextures) + - count (state, mSoundGens) + - count (state, mMagicEffects) + - count (state, mReferenceables) + - count (state, mPathgrids); + const ESM::RefId refId = ESM::RefId::stringRefId(id); + return getGlobals().searchId(refId) != -1 || getGmsts().searchId(refId) != -1 || getSkills().searchId(refId) != -1 + || getClasses().searchId(refId) != -1 || getFactions().searchId(refId) != -1 || getRaces().searchId(refId) != -1 + || getSounds().searchId(refId) != -1 || getScripts().searchId(refId) != -1 || getRegions().searchId(refId) != -1 + || getBirthsigns().searchId(refId) != -1 || getSpells().searchId(refId) != -1 + || getTopics().searchId(refId) != -1 || getJournals().searchId(refId) != -1 || getCells().searchId(refId) != -1 + || getEnchantments().searchId(refId) != -1 || getBodyParts().searchId(refId) != -1 + || getSoundGens().searchId(refId) != -1 || getMagicEffects().searchId(refId) != -1 + || getReferenceables().searchId(refId) != -1; } -std::vector CSMWorld::Data::getIds (bool listDeleted) const +int CSMWorld::Data::count(RecordBase::State state) const { - std::vector ids; + return count(state, mGlobals) + count(state, mGmsts) + count(state, mSkills) + count(state, mClasses) + + count(state, mFactions) + count(state, mRaces) + count(state, mSounds) + count(state, mScripts) + + count(state, mRegions) + count(state, mBirthsigns) + count(state, mSpells) + count(state, mCells) + + count(state, mEnchantments) + count(state, mBodyParts) + count(state, mLand) + count(state, mLandTextures) + + count(state, mSoundGens) + count(state, mMagicEffects) + count(state, mReferenceables) + + count(state, mPathgrids); +} - appendIds (ids, mGlobals, listDeleted); - appendIds (ids, mGmsts, listDeleted); - appendIds (ids, mClasses, listDeleted); - appendIds (ids, mFactions, listDeleted); - appendIds (ids, mRaces, listDeleted); - appendIds (ids, mSounds, listDeleted); - appendIds (ids, mScripts, listDeleted); - appendIds (ids, mRegions, listDeleted); - appendIds (ids, mBirthsigns, listDeleted); - appendIds (ids, mSpells, listDeleted); - appendIds (ids, mTopics, listDeleted); - appendIds (ids, mJournals, listDeleted); - appendIds (ids, mCells, listDeleted); - appendIds (ids, mEnchantments, listDeleted); - appendIds (ids, mBodyParts, listDeleted); - appendIds (ids, mSoundGens, listDeleted); - appendIds (ids, mMagicEffects, listDeleted); - appendIds (ids, mReferenceables, listDeleted); +std::vector CSMWorld::Data::getIds(bool listDeleted) const +{ + std::vector ids; - std::sort (ids.begin(), ids.end()); + appendIds(ids, mGlobals, listDeleted); + appendIds(ids, mGmsts, listDeleted); + appendIds(ids, mClasses, listDeleted); + appendIds(ids, mFactions, listDeleted); + appendIds(ids, mRaces, listDeleted); + appendIds(ids, mSounds, listDeleted); + appendIds(ids, mScripts, listDeleted); + appendIds(ids, mRegions, listDeleted); + appendIds(ids, mBirthsigns, listDeleted); + appendIds(ids, mSpells, listDeleted); + appendIds(ids, mTopics, listDeleted); + appendIds(ids, mJournals, listDeleted); + appendIds(ids, mCells, listDeleted); + appendIds(ids, mEnchantments, listDeleted); + appendIds(ids, mBodyParts, listDeleted); + appendIds(ids, mSoundGens, listDeleted); + appendIds(ids, mMagicEffects, listDeleted); + appendIds(ids, mReferenceables, listDeleted); + + std::sort(ids.begin(), ids.end()); return ids; } @@ -1309,16 +1443,10 @@ std::vector CSMWorld::Data::getIds (bool listDeleted) const void CSMWorld::Data::assetsChanged() { mVFS.get()->reset(); - VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths, !mFsStrict), mArchives, true); + VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true); - const UniversalId assetTableIds[] = { - UniversalId::Type_Meshes, - UniversalId::Type_Icons, - UniversalId::Type_Musics, - UniversalId::Type_SoundsRes, - UniversalId::Type_Textures, - UniversalId::Type_Videos - }; + const UniversalId assetTableIds[] = { UniversalId::Type_Meshes, UniversalId::Type_Icons, UniversalId::Type_Musics, + UniversalId::Type_SoundsRes, UniversalId::Type_Textures, UniversalId::Type_Videos }; size_t numAssetTables = sizeof(assetTableIds) / sizeof(UniversalId); @@ -1343,13 +1471,13 @@ void CSMWorld::Data::assetsChanged() emit assetTablesChanged(); } -void CSMWorld::Data::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSMWorld::Data::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (topLeft.column()<=0) + if (topLeft.column() <= 0) emit idListChanged(); } -void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) +void CSMWorld::Data::rowsChanged(const QModelIndex& parent, int start, int end) { emit idListChanged(); } diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 51f921162..1b63986ea 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -1,330 +1,339 @@ #ifndef CSM_WOLRD_DATA_H #define CSM_WOLRD_DATA_H +#include + +#include #include +#include +#include +#include #include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include -#include "../doc/stage.hpp" - -#include "actoradapter.hpp" -#include "idcollection.hpp" -#include "nestedidcollection.hpp" -#include "universalid.hpp" #include "cell.hpp" +#include "idcollection.hpp" +#include "infocollection.hpp" #include "land.hpp" #include "landtexture.hpp" -#include "refidcollection.hpp" -#include "refcollection.hpp" -#include "infocollection.hpp" +#include "metadata.hpp" +#include "nestedidcollection.hpp" #include "nestedinfocollection.hpp" #include "pathgrid.hpp" +#include "refcollection.hpp" +#include "refidcollection.hpp" #include "resourcesmanager.hpp" -#include "metadata.hpp" +#include "universalid.hpp" #ifndef Q_MOC_RUN #include "subcellcollection.hpp" #endif class QAbstractItemModel; +class QModelIndex; + +namespace Resource +{ + class ResourceSystem; +} namespace VFS { class Manager; } -namespace Fallback -{ - class Map; -} - namespace ESM { class ESMReader; - struct Dialogue; +} + +namespace CSMDoc +{ + class Messages; } namespace CSMWorld { - class ResourcesManager; + class ActorAdapter; + class CollectionBase; class Resources; class Data : public QObject { - Q_OBJECT + Q_OBJECT - ToUTF8::Utf8Encoder mEncoder; - IdCollection mGlobals; - IdCollection mGmsts; - IdCollection mSkills; - IdCollection mClasses; - NestedIdCollection mFactions; - NestedIdCollection mRaces; - IdCollection mSounds; - IdCollection mScripts; - NestedIdCollection mRegions; - NestedIdCollection mBirthsigns; - NestedIdCollection mSpells; - IdCollection mTopics; - IdCollection mJournals; - NestedIdCollection mEnchantments; - IdCollection mBodyParts; - IdCollection mMagicEffects; - SubCellCollection mPathgrids; - IdCollection mDebugProfiles; - IdCollection mSoundGens; - IdCollection mStartScripts; - NestedInfoCollection mTopicInfos; - InfoCollection mJournalInfos; - NestedIdCollection mCells; - IdCollection mLandTextures; - IdCollection mLand; - RefIdCollection mReferenceables; - RefCollection mRefs; - IdCollection mFilters; - Collection mMetaData; - std::unique_ptr mActorAdapter; - std::vector mModels; - std::map mModelIndex; - ESM::ESMReader *mReader; - const ESM::Dialogue *mDialogue; // last loaded dialogue - bool mBase; - bool mProject; - std::map > mRefLoadCache; - int mReaderIndex; + ToUTF8::Utf8Encoder mEncoder; + IdCollection mGlobals; + IdCollection mGmsts; + IdCollection mSkills; + IdCollection mClasses; + NestedIdCollection mFactions; + NestedIdCollection mRaces; + IdCollection mSounds; + IdCollection mScripts; + NestedIdCollection mRegions; + NestedIdCollection mBirthsigns; + NestedIdCollection mSpells; + IdCollection mTopics; + IdCollection mJournals; + NestedIdCollection mEnchantments; + IdCollection mBodyParts; + IdCollection mMagicEffects; + IdCollection mDebugProfiles; + IdCollection mSoundGens; + IdCollection mStartScripts; + NestedInfoCollection mTopicInfos; + InfoCollection mJournalInfos; + NestedIdCollection mCells; + SubCellCollection mPathgrids; + IdCollection mLandTextures; + IdCollection mLand; + RefIdCollection mReferenceables; + RefCollection mRefs; + IdCollection mFilters; + Collection mMetaData; + std::unique_ptr mActorAdapter; + std::vector mModels; + std::map mModelIndex; + ESM::ESMReader* mReader; + const ESM::Dialogue* mDialogue; // last loaded dialogue + bool mBase; + bool mProject; + std::map> mRefLoadCache; + int mReaderIndex; - bool mFsStrict; - Files::PathContainer mDataPaths; - std::vector mArchives; - std::unique_ptr mVFS; - ResourcesManager mResourcesManager; - std::shared_ptr mResourceSystem; + Files::PathContainer mDataPaths; + std::vector mArchives; + std::unique_ptr mVFS; + ResourcesManager mResourcesManager; + std::shared_ptr mResourceSystem; - std::vector > mReaders; + std::vector> mReaders; - std::map mContentFileNames; + InfoOrderByTopic mJournalInfoOrder; + InfoOrderByTopic mTopicInfoOrder; - // not implemented - Data (const Data&); - Data& operator= (const Data&); + // not implemented + Data(const Data&); + Data& operator=(const Data&); - void addModel (QAbstractItemModel *model, UniversalId::Type type, - bool update = true); + void addModel(QAbstractItemModel* model, UniversalId::Type type, bool update = true); - static void appendIds (std::vector& ids, const CollectionBase& collection, - bool listDeleted); - ///< Append all IDs from collection to \a ids. + static void appendIds(std::vector& ids, const CollectionBase& collection, bool listDeleted); + ///< Append all IDs from collection to \a ids. - static int count (RecordBase::State state, const CollectionBase& collection); + static int count(RecordBase::State state, const CollectionBase& collection); - void loadFallbackEntries(); + void loadFallbackEntries(); - public: + public: + Data(ToUTF8::FromType encoding, const Files::PathContainer& dataPaths, const std::vector& archives, + const std::filesystem::path& resDir); - Data (ToUTF8::FromType encoding, bool fsStrict, const Files::PathContainer& dataPaths, - const std::vector& archives, const boost::filesystem::path& resDir); + ~Data() override; - virtual ~Data(); + const VFS::Manager* getVFS() const; - const VFS::Manager* getVFS() const; + std::shared_ptr getResourceSystem(); - std::shared_ptr getResourceSystem(); + std::shared_ptr getResourceSystem() const; - std::shared_ptr getResourceSystem() const; + const IdCollection& getGlobals() const; - const IdCollection& getGlobals() const; + IdCollection& getGlobals(); - IdCollection& getGlobals(); + const IdCollection& getGmsts() const; - const IdCollection& getGmsts() const; + IdCollection& getGmsts(); - IdCollection& getGmsts(); + const IdCollection& getSkills() const; - const IdCollection& getSkills() const; + IdCollection& getSkills(); - IdCollection& getSkills(); + const IdCollection& getClasses() const; - const IdCollection& getClasses() const; + IdCollection& getClasses(); - IdCollection& getClasses(); + const IdCollection& getFactions() const; - const IdCollection& getFactions() const; + IdCollection& getFactions(); - IdCollection& getFactions(); + const IdCollection& getRaces() const; - const IdCollection& getRaces() const; + IdCollection& getRaces(); - IdCollection& getRaces(); + const IdCollection& getSounds() const; - const IdCollection& getSounds() const; + IdCollection& getSounds(); - IdCollection& getSounds(); + const IdCollection& getScripts() const; - const IdCollection& getScripts() const; + IdCollection& getScripts(); - IdCollection& getScripts(); + const IdCollection& getRegions() const; - const IdCollection& getRegions() const; + IdCollection& getRegions(); - IdCollection& getRegions(); + const IdCollection& getBirthsigns() const; - const IdCollection& getBirthsigns() const; + IdCollection& getBirthsigns(); - IdCollection& getBirthsigns(); + const IdCollection& getSpells() const; - const IdCollection& getSpells() const; + IdCollection& getSpells(); - IdCollection& getSpells(); + const IdCollection& getTopics() const; - const IdCollection& getTopics() const; + IdCollection& getTopics(); - IdCollection& getTopics(); + const IdCollection& getJournals() const; - const IdCollection& getJournals() const; + IdCollection& getJournals(); - IdCollection& getJournals(); + const InfoCollection& getTopicInfos() const; - const InfoCollection& getTopicInfos() const; + InfoCollection& getTopicInfos(); - InfoCollection& getTopicInfos(); + const InfoCollection& getJournalInfos() const; - const InfoCollection& getJournalInfos() const; + InfoCollection& getJournalInfos(); - InfoCollection& getJournalInfos(); + const IdCollection& getCells() const; - const IdCollection& getCells() const; + IdCollection& getCells(); - IdCollection& getCells(); + const RefIdCollection& getReferenceables() const; - const RefIdCollection& getReferenceables() const; + RefIdCollection& getReferenceables(); - RefIdCollection& getReferenceables(); + const RefCollection& getReferences() const; - const RefCollection& getReferences() const; + RefCollection& getReferences(); - RefCollection& getReferences(); + const IdCollection& getFilters() const; - const IdCollection& getFilters() const; + IdCollection& getFilters(); - IdCollection& getFilters(); + const IdCollection& getEnchantments() const; - const IdCollection& getEnchantments() const; + IdCollection& getEnchantments(); - IdCollection& getEnchantments(); + const IdCollection& getBodyParts() const; - const IdCollection& getBodyParts() const; + IdCollection& getBodyParts(); - IdCollection& getBodyParts(); + const IdCollection& getDebugProfiles() const; - const IdCollection& getDebugProfiles() const; + IdCollection& getDebugProfiles(); - IdCollection& getDebugProfiles(); + const IdCollection& getLand() const; - const IdCollection& getLand() const; + IdCollection& getLand(); - IdCollection& getLand(); + const IdCollection& getLandTextures() const; - const IdCollection& getLandTextures() const; + IdCollection& getLandTextures(); - IdCollection& getLandTextures(); + const IdCollection& getSoundGens() const; - const IdCollection& getSoundGens() const; + IdCollection& getSoundGens(); - IdCollection& getSoundGens(); + const IdCollection& getMagicEffects() const; - const IdCollection& getMagicEffects() const; + IdCollection& getMagicEffects(); - IdCollection& getMagicEffects(); + const SubCellCollection& getPathgrids() const; - const SubCellCollection& getPathgrids() const; + SubCellCollection& getPathgrids(); - SubCellCollection& getPathgrids(); + const IdCollection& getStartScripts() const; - const IdCollection& getStartScripts() const; + IdCollection& getStartScripts(); - IdCollection& getStartScripts(); + /// Throws an exception, if \a id does not match a resources list. + const Resources& getResources(const UniversalId& id) const; - /// Throws an exception, if \a id does not match a resources list. - const Resources& getResources (const UniversalId& id) const; + const MetaData& getMetaData() const; - const MetaData& getMetaData() const; + void setMetaData(const MetaData& metaData); - void setMetaData (const MetaData& metaData); + QAbstractItemModel* getTableModel(const UniversalId& id); + ///< If no table model is available for \a id, an exception is thrown. + /// + /// \note The returned table may either be the model for the ID itself or the model that + /// contains the record specified by the ID. - QAbstractItemModel *getTableModel (const UniversalId& id); - ///< If no table model is available for \a id, an exception is thrown. - /// - /// \note The returned table may either be the model for the ID itself or the model that - /// contains the record specified by the ID. + const ActorAdapter* getActorAdapter() const; - const ActorAdapter* getActorAdapter() const; + ActorAdapter* getActorAdapter(); - ActorAdapter* getActorAdapter(); + void merge(); + ///< Merge modified into base. - void merge(); - ///< Merge modified into base. + int getTotalRecords(const std::vector& files); // for better loading bar - int startLoading (const boost::filesystem::path& path, bool base, bool project); - ///< Begin merging content of a file into base or modified. - /// - /// \param project load project file instead of content file - /// - ///< \return estimated number of records + int startLoading(const std::filesystem::path& path, bool base, bool project); + ///< Begin merging content of a file into base or modified. + /// + /// \param project load project file instead of content file + /// + ///< \return estimated number of records - bool continueLoading (CSMDoc::Messages& messages); - ///< \return Finished? + bool continueLoading(CSMDoc::Messages& messages); + ///< \return Finished? - bool hasId (const std::string& id) const; + void finishLoading(); - std::vector getIds (bool listDeleted = true) const; - ///< Return a sorted collection of all IDs that are not internal to the editor. - /// - /// \param listDeleted include deleted record in the list + bool hasId(const std::string& id) const; - int count (RecordBase::State state) const; - ///< Return number of top-level records with the given \a state. + std::vector getIds(bool listDeleted = true) const; + ///< Return a sorted collection of all IDs that are not internal to the editor. + /// + /// \param listDeleted include deleted record in the list - signals: + int count(RecordBase::State state) const; + ///< Return number of top-level records with the given \a state. - void idListChanged(); + signals: - void assetTablesChanged(); + void idListChanged(); - private slots: + void assetTablesChanged(); - void assetsChanged(); + public slots: - void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void assetsChanged(); - void rowsChanged (const QModelIndex& parent, int start, int end); + private slots: + + void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void rowsChanged(const QModelIndex& parent, int start, int end); }; } diff --git a/apps/opencs/model/world/defaultgmsts.cpp b/apps/opencs/model/world/defaultgmsts.cpp index da83a86be..2c9a8a66e 100644 --- a/apps/opencs/model/world/defaultgmsts.cpp +++ b/apps/opencs/model/world/defaultgmsts.cpp @@ -8,8 +8,7 @@ const float FEps = std::numeric_limits::epsilon(); const int IMax = std::numeric_limits::max(); const int IMin = std::numeric_limits::min(); -const char* CSMWorld::DefaultGmsts::Floats[CSMWorld::DefaultGmsts::FloatCount] = -{ +const char* CSMWorld::DefaultGmsts::Floats[CSMWorld::DefaultGmsts::FloatCount] = { "fAIFleeFleeMult", "fAIFleeHealthMult", "fAIMagicSpellMult", @@ -267,11 +266,10 @@ const char* CSMWorld::DefaultGmsts::Floats[CSMWorld::DefaultGmsts::FloatCount] = "fWereWolfStrength", "fWereWolfUnarmored", "fWereWolfWillPower", - "fWortChanceValue" + "fWortChanceValue", }; -const char * CSMWorld::DefaultGmsts::Ints[CSMWorld::DefaultGmsts::IntCount] = -{ +const char* CSMWorld::DefaultGmsts::Ints[CSMWorld::DefaultGmsts::IntCount] = { "i1stPersonSneakDelta", "iAlarmAttack", "iAlarmKilling", @@ -360,11 +358,10 @@ const char * CSMWorld::DefaultGmsts::Ints[CSMWorld::DefaultGmsts::IntCount] = "iWereWolfBounty", "iWereWolfFightMod", "iWereWolfFleeMod", - "iWereWolfLevelToAttack" + "iWereWolfLevelToAttack", }; -const char * CSMWorld::DefaultGmsts::Strings[CSMWorld::DefaultGmsts::StringCount] = -{ +const char* CSMWorld::DefaultGmsts::Strings[CSMWorld::DefaultGmsts::StringCount] = { "s3dAudio", "s3dHardware", "s3dSoftware", @@ -1538,11 +1535,10 @@ const char * CSMWorld::DefaultGmsts::Strings[CSMWorld::DefaultGmsts::StringCount "sXTimes", "sXTimesINT", "sYes", - "sYourGold" + "sYourGold", }; -const char * CSMWorld::DefaultGmsts::OptionalFloats[CSMWorld::DefaultGmsts::OptionalFloatCount] = -{ +const char* CSMWorld::DefaultGmsts::OptionalFloats[CSMWorld::DefaultGmsts::OptionalFloatCount] = { "fCombatDistanceWerewolfMod", "fFleeDistance", "fWereWolfAcrobatics", @@ -1584,19 +1580,17 @@ const char * CSMWorld::DefaultGmsts::OptionalFloats[CSMWorld::DefaultGmsts::Opti "fWereWolfSpeed", "fWereWolfStrength", "fWereWolfUnarmored", - "fWereWolfWillPower" + "fWereWolfWillPower", }; -const char * CSMWorld::DefaultGmsts::OptionalInts[CSMWorld::DefaultGmsts::OptionalIntCount] = -{ +const char* CSMWorld::DefaultGmsts::OptionalInts[CSMWorld::DefaultGmsts::OptionalIntCount] = { "iWereWolfBounty", "iWereWolfFightMod", "iWereWolfFleeMod", - "iWereWolfLevelToAttack" + "iWereWolfLevelToAttack", }; -const char * CSMWorld::DefaultGmsts::OptionalStrings[CSMWorld::DefaultGmsts::OptionalStringCount] = -{ +const char* CSMWorld::DefaultGmsts::OptionalStrings[CSMWorld::DefaultGmsts::OptionalStringCount] = { "sCompanionShare", "sCompanionWarningButtonOne", "sCompanionWarningButtonTwo", @@ -1622,715 +1616,711 @@ const char * CSMWorld::DefaultGmsts::OptionalStrings[CSMWorld::DefaultGmsts::Opt "sWerewolfAlarmMessage", "sWerewolfPopup", "sWerewolfRefusal", - "sWerewolfRestMessage" + "sWerewolfRestMessage", }; -const float CSMWorld::DefaultGmsts::FloatsDefaultValues[CSMWorld::DefaultGmsts::FloatCount] = -{ - 0.3f, // fAIFleeFleeMult - 7.0f, // fAIFleeHealthMult - 3.0f, // fAIMagicSpellMult - 1.0f, // fAIMeleeArmorMult - 1.0f, // fAIMeleeSummWeaponMult - 2.0f, // fAIMeleeWeaponMult - 5.0f, // fAIRangeMagicSpellMult - 5.0f, // fAIRangeMeleeWeaponMult +const float CSMWorld::DefaultGmsts::FloatsDefaultValues[CSMWorld::DefaultGmsts::FloatCount] = { + 0.3f, // fAIFleeFleeMult + 7.0f, // fAIFleeHealthMult + 3.0f, // fAIMagicSpellMult + 1.0f, // fAIMeleeArmorMult + 1.0f, // fAIMeleeSummWeaponMult + 2.0f, // fAIMeleeWeaponMult + 5.0f, // fAIRangeMagicSpellMult + 5.0f, // fAIRangeMeleeWeaponMult 2000.0f, // fAlarmRadius - 1.0f, // fAthleticsRunBonus - 40.0f, // fAudioDefaultMaxDistance - 5.0f, // fAudioDefaultMinDistance - 50.0f, // fAudioMaxDistanceMult - 20.0f, // fAudioMinDistanceMult - 60.0f, // fAudioVoiceDefaultMaxDistance - 10.0f, // fAudioVoiceDefaultMinDistance - 50.0f, // fAutoPCSpellChance - 80.0f, // fAutoSpellChance - 50.0f, // fBargainOfferBase - -4.0f, // fBargainOfferMulti - 24.0f, // fBarterGoldResetDelay - 1.75f, // fBaseRunMultiplier - 1.25f, // fBlockStillBonus - 150.0f, // fBribe1000Mod - 75.0f, // fBribe100Mod - 35.0f, // fBribe10Mod - 60.0f, // fCombatAngleXY - 60.0f, // fCombatAngleZ - 0.25f, // fCombatArmorMinMult - -90.0f, // fCombatBlockLeftAngle - 30.0f, // fCombatBlockRightAngle - 4.0f, // fCombatCriticalStrikeMult - 0.1f, // fCombatDelayCreature - 0.1f, // fCombatDelayNPC - 128.0f, // fCombatDistance - 0.3f, // fCombatDistanceWerewolfMod - 30.0f, // fCombatForceSideAngle - 0.2f, // fCombatInvisoMult - 1.5f, // fCombatKODamageMult - 45.0f, // fCombatTorsoSideAngle - 0.3f, // fCombatTorsoStartPercent - 0.8f, // fCombatTorsoStopPercent - 15.0f, // fConstantEffectMult - 72.0f, // fCorpseClearDelay - 72.0f, // fCorpseRespawnDelay - 0.5f, // fCrimeGoldDiscountMult - 0.9f, // fCrimeGoldTurnInMult - 1.0f, // fCrimeStealing - 0.5f, // fDamageStrengthBase - 0.1f, // fDamageStrengthMult - 5.0f, // fDifficultyMult - 2.5f, // fDiseaseXferChance - -10.0f, // fDispAttacking - -1.0f, // fDispBargainFailMod - 1.0f, // fDispBargainSuccessMod - 0.0f, // fDispCrimeMod - -10.0f, // fDispDiseaseMod - 3.0f, // fDispFactionMod - 1.0f, // fDispFactionRankBase - 0.5f, // fDispFactionRankMult - 1.0f, // fDispositionMod - 50.0f, // fDispPersonalityBase - 0.5f, // fDispPersonalityMult - -25.0f, // fDispPickPocketMod - 5.0f, // fDispRaceMod - -0.5f, // fDispStealing - -5.0f, // fDispWeaponDrawn - 0.5f, // fEffectCostMult - 0.1f, // fElementalShieldMult - 3.0f, // fEnchantmentChanceMult - 0.5f, // fEnchantmentConstantChanceMult - 100.0f, // fEnchantmentConstantDurationMult - 0.1f, // fEnchantmentMult + 1.0f, // fAthleticsRunBonus + 40.0f, // fAudioDefaultMaxDistance + 5.0f, // fAudioDefaultMinDistance + 50.0f, // fAudioMaxDistanceMult + 20.0f, // fAudioMinDistanceMult + 60.0f, // fAudioVoiceDefaultMaxDistance + 10.0f, // fAudioVoiceDefaultMinDistance + 50.0f, // fAutoPCSpellChance + 80.0f, // fAutoSpellChance + 50.0f, // fBargainOfferBase + -4.0f, // fBargainOfferMulti + 24.0f, // fBarterGoldResetDelay + 1.75f, // fBaseRunMultiplier + 1.25f, // fBlockStillBonus + 150.0f, // fBribe1000Mod + 75.0f, // fBribe100Mod + 35.0f, // fBribe10Mod + 60.0f, // fCombatAngleXY + 60.0f, // fCombatAngleZ + 0.25f, // fCombatArmorMinMult + -90.0f, // fCombatBlockLeftAngle + 30.0f, // fCombatBlockRightAngle + 4.0f, // fCombatCriticalStrikeMult + 0.1f, // fCombatDelayCreature + 0.1f, // fCombatDelayNPC + 128.0f, // fCombatDistance + 0.3f, // fCombatDistanceWerewolfMod + 30.0f, // fCombatForceSideAngle + 0.2f, // fCombatInvisoMult + 1.5f, // fCombatKODamageMult + 45.0f, // fCombatTorsoSideAngle + 0.3f, // fCombatTorsoStartPercent + 0.8f, // fCombatTorsoStopPercent + 15.0f, // fConstantEffectMult + 72.0f, // fCorpseClearDelay + 72.0f, // fCorpseRespawnDelay + 0.5f, // fCrimeGoldDiscountMult + 0.9f, // fCrimeGoldTurnInMult + 1.0f, // fCrimeStealing + 0.5f, // fDamageStrengthBase + 0.1f, // fDamageStrengthMult + 5.0f, // fDifficultyMult + 2.5f, // fDiseaseXferChance + -10.0f, // fDispAttacking + -1.0f, // fDispBargainFailMod + 1.0f, // fDispBargainSuccessMod + 0.0f, // fDispCrimeMod + -10.0f, // fDispDiseaseMod + 3.0f, // fDispFactionMod + 1.0f, // fDispFactionRankBase + 0.5f, // fDispFactionRankMult + 1.0f, // fDispositionMod + 50.0f, // fDispPersonalityBase + 0.5f, // fDispPersonalityMult + -25.0f, // fDispPickPocketMod + 5.0f, // fDispRaceMod + -0.5f, // fDispStealing + -5.0f, // fDispWeaponDrawn + 0.5f, // fEffectCostMult + 0.1f, // fElementalShieldMult + 3.0f, // fEnchantmentChanceMult + 0.5f, // fEnchantmentConstantChanceMult + 100.0f, // fEnchantmentConstantDurationMult + 0.1f, // fEnchantmentMult 1000.0f, // fEnchantmentValueMult - 0.3f, // fEncumberedMoveEffect - 5.0f, // fEncumbranceStrMult - 0.04f, // fEndFatigueMult - 0.25f, // fFallAcroBase - 0.01f, // fFallAcroMult - 400.0f, // fFallDamageDistanceMin - 0.0f, // fFallDistanceBase - 0.07f, // fFallDistanceMult - 2.0f, // fFatigueAttackBase - 0.0f, // fFatigueAttackMult - 1.25f, // fFatigueBase - 4.0f, // fFatigueBlockBase - 0.0f, // fFatigueBlockMult - 5.0f, // fFatigueJumpBase - 0.0f, // fFatigueJumpMult - 0.5f, // fFatigueMult - 2.5f, // fFatigueReturnBase - 0.02f, // fFatigueReturnMult - 5.0f, // fFatigueRunBase - 2.0f, // fFatigueRunMult - 1.5f, // fFatigueSneakBase - 1.5f, // fFatigueSneakMult - 0.0f, // fFatigueSpellBase - 0.0f, // fFatigueSpellCostMult - 0.0f, // fFatigueSpellMult - 7.0f, // fFatigueSwimRunBase - 0.0f, // fFatigueSwimRunMult - 2.5f, // fFatigueSwimWalkBase - 0.0f, // fFatigueSwimWalkMult - 0.2f, // fFightDispMult - 0.005f, // fFightDistanceMultiplier - 50.0f, // fFightStealing + 0.3f, // fEncumberedMoveEffect + 5.0f, // fEncumbranceStrMult + 0.04f, // fEndFatigueMult + 0.25f, // fFallAcroBase + 0.01f, // fFallAcroMult + 400.0f, // fFallDamageDistanceMin + 0.0f, // fFallDistanceBase + 0.07f, // fFallDistanceMult + 2.0f, // fFatigueAttackBase + 0.0f, // fFatigueAttackMult + 1.25f, // fFatigueBase + 4.0f, // fFatigueBlockBase + 0.0f, // fFatigueBlockMult + 5.0f, // fFatigueJumpBase + 0.0f, // fFatigueJumpMult + 0.5f, // fFatigueMult + 2.5f, // fFatigueReturnBase + 0.02f, // fFatigueReturnMult + 5.0f, // fFatigueRunBase + 2.0f, // fFatigueRunMult + 1.5f, // fFatigueSneakBase + 1.5f, // fFatigueSneakMult + 0.0f, // fFatigueSpellBase + 0.0f, // fFatigueSpellCostMult + 0.0f, // fFatigueSpellMult + 7.0f, // fFatigueSwimRunBase + 0.0f, // fFatigueSwimRunMult + 2.5f, // fFatigueSwimWalkBase + 0.0f, // fFatigueSwimWalkMult + 0.2f, // fFightDispMult + 0.005f, // fFightDistanceMultiplier + 50.0f, // fFightStealing 3000.0f, // fFleeDistance - 512.0f, // fGreetDistanceReset - 0.1f, // fHandtoHandHealthPer - 1.0f, // fHandToHandReach - 0.5f, // fHoldBreathEndMult - 20.0f, // fHoldBreathTime - 0.75f, // fIdleChanceMultiplier - 1.0f, // fIngredientMult - 0.5f, // fInteriorHeadTrackMult - 128.0f, // fJumpAcrobaticsBase - 4.0f, // fJumpAcroMultiplier - 0.5f, // fJumpEncumbranceBase - 1.0f, // fJumpEncumbranceMultiplier - 0.5f, // fJumpMoveBase - 0.5f, // fJumpMoveMult - 1.0f, // fJumpRunMultiplier - 0.5f, // fKnockDownMult - 5.0f, // fLevelMod - 0.1f, // fLevelUpHealthEndMult - 0.6f, // fLightMaxMod - 10.0f, // fLuckMod - 10.0f, // fMagesGuildTravel - 1.5f, // fMagicCreatureCastDelay + 512.0f, // fGreetDistanceReset + 0.1f, // fHandtoHandHealthPer + 1.0f, // fHandToHandReach + 0.5f, // fHoldBreathEndMult + 20.0f, // fHoldBreathTime + 0.75f, // fIdleChanceMultiplier + 1.0f, // fIngredientMult + 0.5f, // fInteriorHeadTrackMult + 128.0f, // fJumpAcrobaticsBase + 4.0f, // fJumpAcroMultiplier + 0.5f, // fJumpEncumbranceBase + 1.0f, // fJumpEncumbranceMultiplier + 0.5f, // fJumpMoveBase + 0.5f, // fJumpMoveMult + 1.0f, // fJumpRunMultiplier + 0.5f, // fKnockDownMult + 5.0f, // fLevelMod + 0.1f, // fLevelUpHealthEndMult + 0.6f, // fLightMaxMod + 10.0f, // fLuckMod + 10.0f, // fMagesGuildTravel + 1.5f, // fMagicCreatureCastDelay 0.0167f, // fMagicDetectRefreshRate - 1.0f, // fMagicItemConstantMult - 1.0f, // fMagicItemCostMult - 1.0f, // fMagicItemOnceMult - 1.0f, // fMagicItemPriceMult - 0.05f, // fMagicItemRechargePerSecond - 1.0f, // fMagicItemStrikeMult - 1.0f, // fMagicItemUsedMult - 3.0f, // fMagicStartIconBlink - 0.5f, // fMagicSunBlockedMult - 0.75f, // fMajorSkillBonus - 300.0f, // fMaxFlySpeed - 0.5f, // fMaxHandToHandMult - 400.0f, // fMaxHeadTrackDistance - 200.0f, // fMaxWalkSpeed - 300.0f, // fMaxWalkSpeedCreature - 0.9f, // fMedMaxMod - 0.1f, // fMessageTimePerChar - 5.0f, // fMinFlySpeed - 0.1f, // fMinHandToHandMult - 1.0f, // fMinorSkillBonus - 100.0f, // fMinWalkSpeed - 5.0f, // fMinWalkSpeedCreature - 1.25f, // fMiscSkillBonus - 2.0f, // fNPCbaseMagickaMult - 0.5f, // fNPCHealthBarFade - 3.0f, // fNPCHealthBarTime - 1.0f, // fPCbaseMagickaMult - 0.3f, // fPerDieRollMult - 5.0f, // fPersonalityMod - 1.0f, // fPerTempMult - -1.0f, // fPickLockMult - 0.3f, // fPickPocketMod - 20.0f, // fPotionMinUsefulDuration - 0.5f, // fPotionStrengthMult - 0.5f, // fPotionT1DurMult - 1.5f, // fPotionT1MagMult - 20.0f, // fPotionT4BaseStrengthMult - 12.0f, // fPotionT4EquipStrengthMult + 1.0f, // fMagicItemConstantMult + 1.0f, // fMagicItemCostMult + 1.0f, // fMagicItemOnceMult + 1.0f, // fMagicItemPriceMult + 0.05f, // fMagicItemRechargePerSecond + 1.0f, // fMagicItemStrikeMult + 1.0f, // fMagicItemUsedMult + 3.0f, // fMagicStartIconBlink + 0.5f, // fMagicSunBlockedMult + 0.75f, // fMajorSkillBonus + 300.0f, // fMaxFlySpeed + 0.5f, // fMaxHandToHandMult + 400.0f, // fMaxHeadTrackDistance + 200.0f, // fMaxWalkSpeed + 300.0f, // fMaxWalkSpeedCreature + 0.9f, // fMedMaxMod + 0.1f, // fMessageTimePerChar + 5.0f, // fMinFlySpeed + 0.1f, // fMinHandToHandMult + 1.0f, // fMinorSkillBonus + 100.0f, // fMinWalkSpeed + 5.0f, // fMinWalkSpeedCreature + 1.25f, // fMiscSkillBonus + 2.0f, // fNPCbaseMagickaMult + 0.5f, // fNPCHealthBarFade + 3.0f, // fNPCHealthBarTime + 1.0f, // fPCbaseMagickaMult + 0.3f, // fPerDieRollMult + 5.0f, // fPersonalityMod + 1.0f, // fPerTempMult + -1.0f, // fPickLockMult + 0.3f, // fPickPocketMod + 20.0f, // fPotionMinUsefulDuration + 0.5f, // fPotionStrengthMult + 0.5f, // fPotionT1DurMult + 1.5f, // fPotionT1MagMult + 20.0f, // fPotionT4BaseStrengthMult + 12.0f, // fPotionT4EquipStrengthMult 3000.0f, // fProjectileMaxSpeed - 400.0f, // fProjectileMinSpeed - 25.0f, // fProjectileThrownStoreChance - 3.0f, // fRepairAmountMult - 1.0f, // fRepairMult - 1.0f, // fReputationMod - 0.15f, // fRestMagicMult - 0.0f, // fSeriousWoundMult - 0.25f, // fSleepRandMod - 0.3f, // fSleepRestMod - -1.0f, // fSneakBootMult - 0.5f, // fSneakDistanceBase - 0.002f, // fSneakDistanceMultiplier - 0.5f, // fSneakNoViewMult - 1.0f, // fSneakSkillMult - 0.75f, // fSneakSpeedMultiplier - 1.0f, // fSneakUseDelay - 500.0f, // fSneakUseDist - 1.5f, // fSneakViewMult - 3.0f, // fSoulGemMult - 0.8f, // fSpecialSkillBonus - 7.0f, // fSpellMakingValueMult - 2.0f, // fSpellPriceMult - 10.0f, // fSpellValueMult - 0.25f, // fStromWalkMult - 0.7f, // fStromWindSpeed - 3.0f, // fSuffocationDamage - 0.9f, // fSwimHeightScale - 0.1f, // fSwimRunAthleticsMult - 0.5f, // fSwimRunBase - 0.02f, // fSwimWalkAthleticsMult - 0.5f, // fSwimWalkBase - 1.0f, // fSwingBlockBase - 1.0f, // fSwingBlockMult + 400.0f, // fProjectileMinSpeed + 25.0f, // fProjectileThrownStoreChance + 3.0f, // fRepairAmountMult + 1.0f, // fRepairMult + 1.0f, // fReputationMod + 0.15f, // fRestMagicMult + 0.0f, // fSeriousWoundMult + 0.25f, // fSleepRandMod + 0.3f, // fSleepRestMod + -1.0f, // fSneakBootMult + 0.5f, // fSneakDistanceBase + 0.002f, // fSneakDistanceMultiplier + 0.5f, // fSneakNoViewMult + 1.0f, // fSneakSkillMult + 0.75f, // fSneakSpeedMultiplier + 1.0f, // fSneakUseDelay + 500.0f, // fSneakUseDist + 1.5f, // fSneakViewMult + 3.0f, // fSoulGemMult + 0.8f, // fSpecialSkillBonus + 7.0f, // fSpellMakingValueMult + 2.0f, // fSpellPriceMult + 10.0f, // fSpellValueMult + 0.25f, // fStromWalkMult + 0.7f, // fStromWindSpeed + 3.0f, // fSuffocationDamage + 0.9f, // fSwimHeightScale + 0.1f, // fSwimRunAthleticsMult + 0.5f, // fSwimRunBase + 0.02f, // fSwimWalkAthleticsMult + 0.5f, // fSwimWalkBase + 1.0f, // fSwingBlockBase + 1.0f, // fSwingBlockMult 1000.0f, // fTargetSpellMaxSpeed 1000.0f, // fThrownWeaponMaxSpeed - 300.0f, // fThrownWeaponMinSpeed - 0.0f, // fTrapCostMult + 300.0f, // fThrownWeaponMinSpeed + 0.0f, // fTrapCostMult 4000.0f, // fTravelMult - 16000.0f,// fTravelTimeMult - 0.1f, // fUnarmoredBase1 - 0.065f, // fUnarmoredBase2 - 30.0f, // fVanityDelay - 10.0f, // fVoiceIdleOdds - 0.0f, // fWaterReflectUpdateAlways - 10.0f, // fWaterReflectUpdateSeldom - 0.1f, // fWeaponDamageMult - 1.0f, // fWeaponFatigueBlockMult - 0.25f, // fWeaponFatigueMult - 150.0f, // fWereWolfAcrobatics - 150.0f, // fWereWolfAgility - 1.0f, // fWereWolfAlchemy - 1.0f, // fWereWolfAlteration - 1.0f, // fWereWolfArmorer - 150.0f, // fWereWolfAthletics - 1.0f, // fWereWolfAxe - 1.0f, // fWereWolfBlock - 1.0f, // fWereWolfBluntWeapon - 1.0f, // fWereWolfConjuration - 1.0f, // fWereWolfDestruction - 1.0f, // fWereWolfEnchant - 150.0f, // fWereWolfEndurance - 400.0f, // fWereWolfFatigue - 100.0f, // fWereWolfHandtoHand - 2.0f, // fWereWolfHealth - 1.0f, // fWereWolfHeavyArmor - 1.0f, // fWereWolfIllusion - 1.0f, // fWereWolfIntellegence - 1.0f, // fWereWolfLightArmor - 1.0f, // fWereWolfLongBlade - 1.0f, // fWereWolfLuck - 100.0f, // fWereWolfMagicka - 1.0f, // fWereWolfMarksman - 1.0f, // fWereWolfMediumArmor - 1.0f, // fWereWolfMerchantile - 1.0f, // fWereWolfMysticism - 1.0f, // fWereWolfPersonality - 1.0f, // fWereWolfRestoration - 1.5f, // fWereWolfRunMult - 1.0f, // fWereWolfSecurity - 1.0f, // fWereWolfShortBlade - 1.5f, // fWereWolfSilverWeaponDamageMult - 1.0f, // fWereWolfSneak - 1.0f, // fWereWolfSpear - 1.0f, // fWereWolfSpeechcraft - 150.0f, // fWereWolfSpeed - 150.0f, // fWereWolfStrength - 100.0f, // fWereWolfUnarmored - 1.0f, // fWereWolfWillPower - 15.0f // fWortChanceValue + 16000.0f, // fTravelTimeMult + 0.1f, // fUnarmoredBase1 + 0.065f, // fUnarmoredBase2 + 30.0f, // fVanityDelay + 10.0f, // fVoiceIdleOdds + 0.0f, // fWaterReflectUpdateAlways + 10.0f, // fWaterReflectUpdateSeldom + 0.1f, // fWeaponDamageMult + 1.0f, // fWeaponFatigueBlockMult + 0.25f, // fWeaponFatigueMult + 150.0f, // fWereWolfAcrobatics + 150.0f, // fWereWolfAgility + 1.0f, // fWereWolfAlchemy + 1.0f, // fWereWolfAlteration + 1.0f, // fWereWolfArmorer + 150.0f, // fWereWolfAthletics + 1.0f, // fWereWolfAxe + 1.0f, // fWereWolfBlock + 1.0f, // fWereWolfBluntWeapon + 1.0f, // fWereWolfConjuration + 1.0f, // fWereWolfDestruction + 1.0f, // fWereWolfEnchant + 150.0f, // fWereWolfEndurance + 400.0f, // fWereWolfFatigue + 100.0f, // fWereWolfHandtoHand + 2.0f, // fWereWolfHealth + 1.0f, // fWereWolfHeavyArmor + 1.0f, // fWereWolfIllusion + 1.0f, // fWereWolfIntellegence + 1.0f, // fWereWolfLightArmor + 1.0f, // fWereWolfLongBlade + 1.0f, // fWereWolfLuck + 100.0f, // fWereWolfMagicka + 1.0f, // fWereWolfMarksman + 1.0f, // fWereWolfMediumArmor + 1.0f, // fWereWolfMerchantile + 1.0f, // fWereWolfMysticism + 1.0f, // fWereWolfPersonality + 1.0f, // fWereWolfRestoration + 1.5f, // fWereWolfRunMult + 1.0f, // fWereWolfSecurity + 1.0f, // fWereWolfShortBlade + 1.5f, // fWereWolfSilverWeaponDamageMult + 1.0f, // fWereWolfSneak + 1.0f, // fWereWolfSpear + 1.0f, // fWereWolfSpeechcraft + 150.0f, // fWereWolfSpeed + 150.0f, // fWereWolfStrength + 100.0f, // fWereWolfUnarmored + 1.0f, // fWereWolfWillPower + 15.0f, // fWortChanceValue }; -const int CSMWorld::DefaultGmsts::IntsDefaultValues[CSMWorld::DefaultGmsts::IntCount] = -{ - 10, // i1stPersonSneakDelta - 50, // iAlarmAttack - 90, // iAlarmKilling - 20, // iAlarmPickPocket - 1, // iAlarmStealing - 5, // iAlarmTresspass - 2, // iAlchemyMod - 100, // iAutoPCSpellMax - 2, // iAutoRepFacMod - 0, // iAutoRepLevMod - 5, // iAutoSpellAlterationMax - 70, // iAutoSpellAttSkillMin - 2, // iAutoSpellConjurationMax - 5, // iAutoSpellDestructionMax - 5, // iAutoSpellIllusionMax - 5, // iAutoSpellMysticismMax - 5, // iAutoSpellRestorationMax - 3, // iAutoSpellTimesCanCast - -1, // iBarterFailDisposition - 1, // iBarterSuccessDisposition - 30, // iBaseArmorSkill - 50, // iBlockMaxChance - 10, // iBlockMinChance - 20, // iBootsWeight - 40, // iCrimeAttack - 1000, // iCrimeKilling - 25, // iCrimePickPocket - 1000, // iCrimeThreshold - 10, // iCrimeThresholdMultiplier - 5, // iCrimeTresspass - 30, // iCuirassWeight - 100, // iDaysinPrisonMod - -50, // iDispAttackMod - -50, // iDispKilling - -20, // iDispTresspass - 1, // iFightAlarmMult - 100, // iFightAttack - 50, // iFightAttacking - 20, // iFightDistanceBase - 50, // iFightKilling - 25, // iFightPickpocket - 25, // iFightTrespass - 0, // iFlee - 5, // iGauntletWeight - 15, // iGreavesWeight - 6, // iGreetDistanceMultiplier - 4, // iGreetDuration - 5, // iHelmWeight - 50, // iKnockDownOddsBase - 50, // iKnockDownOddsMult - 2, // iLevelUp01Mult - 2, // iLevelUp02Mult - 2, // iLevelUp03Mult - 2, // iLevelUp04Mult - 3, // iLevelUp05Mult - 3, // iLevelUp06Mult - 3, // iLevelUp07Mult - 4, // iLevelUp08Mult - 4, // iLevelUp09Mult - 5, // iLevelUp10Mult - 1, // iLevelupMajorMult - 1, // iLevelupMajorMultAttribute - 1, // iLevelupMinorMult - 1, // iLevelupMinorMultAttribute - 1, // iLevelupMiscMultAttriubte - 1, // iLevelupSpecialization - 10, // iLevelupTotal - 10, // iMagicItemChargeConst - 1, // iMagicItemChargeOnce - 10, // iMagicItemChargeStrike - 5, // iMagicItemChargeUse - 192, // iMaxActivateDist - 192, // iMaxInfoDist - 4, // iMonthsToRespawn - 1, // iNumberCreatures - 10, // iPauldronWeight - 5, // iPerMinChance - 10, // iPerMinChange - 75, // iPickMaxChance - 5, // iPickMinChance - 15, // iShieldWeight - 400, // iSoulAmountForConstantEffect - 10, // iTrainingMod - 10, // iVoiceAttackOdds - 30, // iVoiceHitOdds - 10000, // iWereWolfBounty - 100, // iWereWolfFightMod - 100, // iWereWolfFleeMod - 20 // iWereWolfLevelToAttack +const int CSMWorld::DefaultGmsts::IntsDefaultValues[CSMWorld::DefaultGmsts::IntCount] = { + 10, // i1stPersonSneakDelta + 50, // iAlarmAttack + 90, // iAlarmKilling + 20, // iAlarmPickPocket + 1, // iAlarmStealing + 5, // iAlarmTresspass + 2, // iAlchemyMod + 100, // iAutoPCSpellMax + 2, // iAutoRepFacMod + 0, // iAutoRepLevMod + 5, // iAutoSpellAlterationMax + 70, // iAutoSpellAttSkillMin + 2, // iAutoSpellConjurationMax + 5, // iAutoSpellDestructionMax + 5, // iAutoSpellIllusionMax + 5, // iAutoSpellMysticismMax + 5, // iAutoSpellRestorationMax + 3, // iAutoSpellTimesCanCast + -1, // iBarterFailDisposition + 1, // iBarterSuccessDisposition + 30, // iBaseArmorSkill + 50, // iBlockMaxChance + 10, // iBlockMinChance + 20, // iBootsWeight + 40, // iCrimeAttack + 1000, // iCrimeKilling + 25, // iCrimePickPocket + 1000, // iCrimeThreshold + 10, // iCrimeThresholdMultiplier + 5, // iCrimeTresspass + 30, // iCuirassWeight + 100, // iDaysinPrisonMod + -50, // iDispAttackMod + -50, // iDispKilling + -20, // iDispTresspass + 1, // iFightAlarmMult + 100, // iFightAttack + 50, // iFightAttacking + 20, // iFightDistanceBase + 50, // iFightKilling + 25, // iFightPickpocket + 25, // iFightTrespass + 0, // iFlee + 5, // iGauntletWeight + 15, // iGreavesWeight + 6, // iGreetDistanceMultiplier + 4, // iGreetDuration + 5, // iHelmWeight + 50, // iKnockDownOddsBase + 50, // iKnockDownOddsMult + 2, // iLevelUp01Mult + 2, // iLevelUp02Mult + 2, // iLevelUp03Mult + 2, // iLevelUp04Mult + 3, // iLevelUp05Mult + 3, // iLevelUp06Mult + 3, // iLevelUp07Mult + 4, // iLevelUp08Mult + 4, // iLevelUp09Mult + 5, // iLevelUp10Mult + 1, // iLevelupMajorMult + 1, // iLevelupMajorMultAttribute + 1, // iLevelupMinorMult + 1, // iLevelupMinorMultAttribute + 1, // iLevelupMiscMultAttriubte + 1, // iLevelupSpecialization + 10, // iLevelupTotal + 10, // iMagicItemChargeConst + 1, // iMagicItemChargeOnce + 10, // iMagicItemChargeStrike + 5, // iMagicItemChargeUse + 192, // iMaxActivateDist + 192, // iMaxInfoDist + 4, // iMonthsToRespawn + 1, // iNumberCreatures + 10, // iPauldronWeight + 5, // iPerMinChance + 10, // iPerMinChange + 75, // iPickMaxChance + 5, // iPickMinChance + 15, // iShieldWeight + 400, // iSoulAmountForConstantEffect + 10, // iTrainingMod + 10, // iVoiceAttackOdds + 30, // iVoiceHitOdds + 10000, // iWereWolfBounty + 100, // iWereWolfFightMod + 100, // iWereWolfFleeMod + 20, // iWereWolfLevelToAttack }; -const float CSMWorld::DefaultGmsts::FloatLimits[CSMWorld::DefaultGmsts::FloatCount * 2] = -{ - -FInf, FInf, // fAIFleeFleeMult - -FInf, FInf, // fAIFleeHealthMult - -FInf, FInf, // fAIMagicSpellMult - -FInf, FInf, // fAIMeleeArmorMult - -FInf, FInf, // fAIMeleeSummWeaponMult - -FInf, FInf, // fAIMeleeWeaponMult - -FInf, FInf, // fAIRangeMagicSpellMult - -FInf, FInf, // fAIRangeMeleeWeaponMult - 0, FInf, // fAlarmRadius - -FInf, FInf, // fAthleticsRunBonus - 0, FInf, // fAudioDefaultMaxDistance - 0, FInf, // fAudioDefaultMinDistance - 0, FInf, // fAudioMaxDistanceMult - 0, FInf, // fAudioMinDistanceMult - 0, FInf, // fAudioVoiceDefaultMaxDistance - 0, FInf, // fAudioVoiceDefaultMinDistance - 0, FInf, // fAutoPCSpellChance - 0, FInf, // fAutoSpellChance - -FInf, FInf, // fBargainOfferBase - -FInf, 0, // fBargainOfferMulti - -FInf, FInf, // fBarterGoldResetDelay - 0, FInf, // fBaseRunMultiplier - -FInf, FInf, // fBlockStillBonus - 0, FInf, // fBribe1000Mod - 0, FInf, // fBribe100Mod - 0, FInf, // fBribe10Mod - 0, FInf, // fCombatAngleXY - 0, FInf, // fCombatAngleZ - 0, 1, // fCombatArmorMinMult - -180, 0, // fCombatBlockLeftAngle - 0, 180, // fCombatBlockRightAngle - 0, FInf, // fCombatCriticalStrikeMult - 0, FInf, // fCombatDelayCreature - 0, FInf, // fCombatDelayNPC - 0, FInf, // fCombatDistance - -FInf, FInf, // fCombatDistanceWerewolfMod - -FInf, FInf, // fCombatForceSideAngle - 0, FInf, // fCombatInvisoMult - 0, FInf, // fCombatKODamageMult - -FInf, FInf, // fCombatTorsoSideAngle - -FInf, FInf, // fCombatTorsoStartPercent - -FInf, FInf, // fCombatTorsoStopPercent - -FInf, FInf, // fConstantEffectMult - -FInf, FInf, // fCorpseClearDelay - -FInf, FInf, // fCorpseRespawnDelay - 0, 1, // fCrimeGoldDiscountMult - 0, FInf, // fCrimeGoldTurnInMult - 0, FInf, // fCrimeStealing - 0, FInf, // fDamageStrengthBase - 0, FInf, // fDamageStrengthMult - -FInf, FInf, // fDifficultyMult - 0, FInf, // fDiseaseXferChance - -FInf, 0, // fDispAttacking - -FInf, FInf, // fDispBargainFailMod - -FInf, FInf, // fDispBargainSuccessMod - -FInf, 0, // fDispCrimeMod - -FInf, 0, // fDispDiseaseMod - 0, FInf, // fDispFactionMod - 0, FInf, // fDispFactionRankBase - 0, FInf, // fDispFactionRankMult - 0, FInf, // fDispositionMod - 0, FInf, // fDispPersonalityBase - 0, FInf, // fDispPersonalityMult - -FInf, 0, // fDispPickPocketMod - 0, FInf, // fDispRaceMod - -FInf, 0, // fDispStealing - -FInf, 0, // fDispWeaponDrawn - 0, FInf, // fEffectCostMult - 0, FInf, // fElementalShieldMult - FEps, FInf, // fEnchantmentChanceMult - 0, FInf, // fEnchantmentConstantChanceMult - 0, FInf, // fEnchantmentConstantDurationMult - 0, FInf, // fEnchantmentMult - 0, FInf, // fEnchantmentValueMult - 0, FInf, // fEncumberedMoveEffect - 0, FInf, // fEncumbranceStrMult - 0, FInf, // fEndFatigueMult - -FInf, FInf, // fFallAcroBase - 0, FInf, // fFallAcroMult - 0, FInf, // fFallDamageDistanceMin - -FInf, FInf, // fFallDistanceBase - 0, FInf, // fFallDistanceMult - -FInf, FInf, // fFatigueAttackBase - 0, FInf, // fFatigueAttackMult - 0, FInf, // fFatigueBase - 0, FInf, // fFatigueBlockBase - 0, FInf, // fFatigueBlockMult - 0, FInf, // fFatigueJumpBase - 0, FInf, // fFatigueJumpMult - 0, FInf, // fFatigueMult - -FInf, FInf, // fFatigueReturnBase - 0, FInf, // fFatigueReturnMult - -FInf, FInf, // fFatigueRunBase - 0, FInf, // fFatigueRunMult - -FInf, FInf, // fFatigueSneakBase - 0, FInf, // fFatigueSneakMult - -FInf, FInf, // fFatigueSpellBase - -FInf, FInf, // fFatigueSpellCostMult - 0, FInf, // fFatigueSpellMult - -FInf, FInf, // fFatigueSwimRunBase - 0, FInf, // fFatigueSwimRunMult - -FInf, FInf, // fFatigueSwimWalkBase - 0, FInf, // fFatigueSwimWalkMult - -FInf, FInf, // fFightDispMult - -FInf, FInf, // fFightDistanceMultiplier - -FInf, FInf, // fFightStealing - -FInf, FInf, // fFleeDistance - -FInf, FInf, // fGreetDistanceReset - 0, FInf, // fHandtoHandHealthPer - 0, FInf, // fHandToHandReach - -FInf, FInf, // fHoldBreathEndMult - 0, FInf, // fHoldBreathTime - 0, FInf, // fIdleChanceMultiplier - -FInf, FInf, // fIngredientMult - 0, FInf, // fInteriorHeadTrackMult - -FInf, FInf, // fJumpAcrobaticsBase - 0, FInf, // fJumpAcroMultiplier - -FInf, FInf, // fJumpEncumbranceBase - 0, FInf, // fJumpEncumbranceMultiplier - -FInf, FInf, // fJumpMoveBase - 0, FInf, // fJumpMoveMult - 0, FInf, // fJumpRunMultiplier - -FInf, FInf, // fKnockDownMult - 0, FInf, // fLevelMod - 0, FInf, // fLevelUpHealthEndMult - 0, FInf, // fLightMaxMod - 0, FInf, // fLuckMod - 0, FInf, // fMagesGuildTravel - -FInf, FInf, // fMagicCreatureCastDelay - -FInf, FInf, // fMagicDetectRefreshRate - -FInf, FInf, // fMagicItemConstantMult - -FInf, FInf, // fMagicItemCostMult - -FInf, FInf, // fMagicItemOnceMult - -FInf, FInf, // fMagicItemPriceMult - 0, FInf, // fMagicItemRechargePerSecond - -FInf, FInf, // fMagicItemStrikeMult - -FInf, FInf, // fMagicItemUsedMult - 0, FInf, // fMagicStartIconBlink - 0, FInf, // fMagicSunBlockedMult - FEps, FInf, // fMajorSkillBonus - 0, FInf, // fMaxFlySpeed - 0, FInf, // fMaxHandToHandMult - 0, FInf, // fMaxHeadTrackDistance - 0, FInf, // fMaxWalkSpeed - 0, FInf, // fMaxWalkSpeedCreature - 0, FInf, // fMedMaxMod - 0, FInf, // fMessageTimePerChar - 0, FInf, // fMinFlySpeed - 0, FInf, // fMinHandToHandMult - FEps, FInf, // fMinorSkillBonus - 0, FInf, // fMinWalkSpeed - 0, FInf, // fMinWalkSpeedCreature - FEps, FInf, // fMiscSkillBonus - 0, FInf, // fNPCbaseMagickaMult - 0, FInf, // fNPCHealthBarFade - 0, FInf, // fNPCHealthBarTime - 0, FInf, // fPCbaseMagickaMult - 0, FInf, // fPerDieRollMult - 0, FInf, // fPersonalityMod - 0, FInf, // fPerTempMult - -FInf, 0, // fPickLockMult - 0, FInf, // fPickPocketMod - -FInf, FInf, // fPotionMinUsefulDuration - 0, FInf, // fPotionStrengthMult - FEps, FInf, // fPotionT1DurMult - FEps, FInf, // fPotionT1MagMult - -FInf, FInf, // fPotionT4BaseStrengthMult - -FInf, FInf, // fPotionT4EquipStrengthMult - 0, FInf, // fProjectileMaxSpeed - 0, FInf, // fProjectileMinSpeed - 0, FInf, // fProjectileThrownStoreChance - 0, FInf, // fRepairAmountMult - 0, FInf, // fRepairMult - 0, FInf, // fReputationMod - 0, FInf, // fRestMagicMult - -FInf, FInf, // fSeriousWoundMult - 0, FInf, // fSleepRandMod - 0, FInf, // fSleepRestMod - -FInf, 0, // fSneakBootMult - -FInf, FInf, // fSneakDistanceBase - 0, FInf, // fSneakDistanceMultiplier - 0, FInf, // fSneakNoViewMult - 0, FInf, // fSneakSkillMult - 0, FInf, // fSneakSpeedMultiplier - 0, FInf, // fSneakUseDelay - 0, FInf, // fSneakUseDist - 0, FInf, // fSneakViewMult - 0, FInf, // fSoulGemMult - 0, FInf, // fSpecialSkillBonus - 0, FInf, // fSpellMakingValueMult - -FInf, FInf, // fSpellPriceMult - 0, FInf, // fSpellValueMult - 0, FInf, // fStromWalkMult - 0, FInf, // fStromWindSpeed - 0, FInf, // fSuffocationDamage - 0, FInf, // fSwimHeightScale - 0, FInf, // fSwimRunAthleticsMult - 0, FInf, // fSwimRunBase - -FInf, FInf, // fSwimWalkAthleticsMult - -FInf, FInf, // fSwimWalkBase - 0, FInf, // fSwingBlockBase - 0, FInf, // fSwingBlockMult - 0, FInf, // fTargetSpellMaxSpeed - 0, FInf, // fThrownWeaponMaxSpeed - 0, FInf, // fThrownWeaponMinSpeed - 0, FInf, // fTrapCostMult - 0, FInf, // fTravelMult - 0, FInf, // fTravelTimeMult - 0, FInf, // fUnarmoredBase1 - 0, FInf, // fUnarmoredBase2 - 0, FInf, // fVanityDelay - 0, FInf, // fVoiceIdleOdds - -FInf, FInf, // fWaterReflectUpdateAlways - -FInf, FInf, // fWaterReflectUpdateSeldom - 0, FInf, // fWeaponDamageMult - 0, FInf, // fWeaponFatigueBlockMult - 0, FInf, // fWeaponFatigueMult - 0, FInf, // fWereWolfAcrobatics - -FInf, FInf, // fWereWolfAgility - -FInf, FInf, // fWereWolfAlchemy - -FInf, FInf, // fWereWolfAlteration - -FInf, FInf, // fWereWolfArmorer - -FInf, FInf, // fWereWolfAthletics - -FInf, FInf, // fWereWolfAxe - -FInf, FInf, // fWereWolfBlock - -FInf, FInf, // fWereWolfBluntWeapon - -FInf, FInf, // fWereWolfConjuration - -FInf, FInf, // fWereWolfDestruction - -FInf, FInf, // fWereWolfEnchant - -FInf, FInf, // fWereWolfEndurance - -FInf, FInf, // fWereWolfFatigue - -FInf, FInf, // fWereWolfHandtoHand - -FInf, FInf, // fWereWolfHealth - -FInf, FInf, // fWereWolfHeavyArmor - -FInf, FInf, // fWereWolfIllusion - -FInf, FInf, // fWereWolfIntellegence - -FInf, FInf, // fWereWolfLightArmor - -FInf, FInf, // fWereWolfLongBlade - -FInf, FInf, // fWereWolfLuck - -FInf, FInf, // fWereWolfMagicka - -FInf, FInf, // fWereWolfMarksman - -FInf, FInf, // fWereWolfMediumArmor - -FInf, FInf, // fWereWolfMerchantile - -FInf, FInf, // fWereWolfMysticism - -FInf, FInf, // fWereWolfPersonality - -FInf, FInf, // fWereWolfRestoration - 0, FInf, // fWereWolfRunMult - -FInf, FInf, // fWereWolfSecurity - -FInf, FInf, // fWereWolfShortBlade - -FInf, FInf, // fWereWolfSilverWeaponDamageMult - -FInf, FInf, // fWereWolfSneak - -FInf, FInf, // fWereWolfSpear - -FInf, FInf, // fWereWolfSpeechcraft - -FInf, FInf, // fWereWolfSpeed - -FInf, FInf, // fWereWolfStrength - -FInf, FInf, // fWereWolfUnarmored - -FInf, FInf, // fWereWolfWillPower - 0, FInf // fWortChanceValue +const float CSMWorld::DefaultGmsts::FloatLimits[CSMWorld::DefaultGmsts::FloatCount * 2] = { + -FInf, FInf, // fAIFleeFleeMult + -FInf, FInf, // fAIFleeHealthMult + -FInf, FInf, // fAIMagicSpellMult + -FInf, FInf, // fAIMeleeArmorMult + -FInf, FInf, // fAIMeleeSummWeaponMult + -FInf, FInf, // fAIMeleeWeaponMult + -FInf, FInf, // fAIRangeMagicSpellMult + -FInf, FInf, // fAIRangeMeleeWeaponMult + 0, FInf, // fAlarmRadius + -FInf, FInf, // fAthleticsRunBonus + 0, FInf, // fAudioDefaultMaxDistance + 0, FInf, // fAudioDefaultMinDistance + 0, FInf, // fAudioMaxDistanceMult + 0, FInf, // fAudioMinDistanceMult + 0, FInf, // fAudioVoiceDefaultMaxDistance + 0, FInf, // fAudioVoiceDefaultMinDistance + 0, FInf, // fAutoPCSpellChance + 0, FInf, // fAutoSpellChance + -FInf, FInf, // fBargainOfferBase + -FInf, 0, // fBargainOfferMulti + -FInf, FInf, // fBarterGoldResetDelay + 0, FInf, // fBaseRunMultiplier + -FInf, FInf, // fBlockStillBonus + 0, FInf, // fBribe1000Mod + 0, FInf, // fBribe100Mod + 0, FInf, // fBribe10Mod + 0, FInf, // fCombatAngleXY + 0, FInf, // fCombatAngleZ + 0, 1, // fCombatArmorMinMult + -180, 0, // fCombatBlockLeftAngle + 0, 180, // fCombatBlockRightAngle + 0, FInf, // fCombatCriticalStrikeMult + 0, FInf, // fCombatDelayCreature + 0, FInf, // fCombatDelayNPC + 0, FInf, // fCombatDistance + -FInf, FInf, // fCombatDistanceWerewolfMod + -FInf, FInf, // fCombatForceSideAngle + 0, FInf, // fCombatInvisoMult + 0, FInf, // fCombatKODamageMult + -FInf, FInf, // fCombatTorsoSideAngle + -FInf, FInf, // fCombatTorsoStartPercent + -FInf, FInf, // fCombatTorsoStopPercent + -FInf, FInf, // fConstantEffectMult + -FInf, FInf, // fCorpseClearDelay + -FInf, FInf, // fCorpseRespawnDelay + 0, 1, // fCrimeGoldDiscountMult + 0, FInf, // fCrimeGoldTurnInMult + 0, FInf, // fCrimeStealing + 0, FInf, // fDamageStrengthBase + 0, FInf, // fDamageStrengthMult + -FInf, FInf, // fDifficultyMult + 0, FInf, // fDiseaseXferChance + -FInf, 0, // fDispAttacking + -FInf, FInf, // fDispBargainFailMod + -FInf, FInf, // fDispBargainSuccessMod + -FInf, 0, // fDispCrimeMod + -FInf, 0, // fDispDiseaseMod + 0, FInf, // fDispFactionMod + 0, FInf, // fDispFactionRankBase + 0, FInf, // fDispFactionRankMult + 0, FInf, // fDispositionMod + 0, FInf, // fDispPersonalityBase + 0, FInf, // fDispPersonalityMult + -FInf, 0, // fDispPickPocketMod + 0, FInf, // fDispRaceMod + -FInf, 0, // fDispStealing + -FInf, 0, // fDispWeaponDrawn + 0, FInf, // fEffectCostMult + 0, FInf, // fElementalShieldMult + FEps, FInf, // fEnchantmentChanceMult + 0, FInf, // fEnchantmentConstantChanceMult + 0, FInf, // fEnchantmentConstantDurationMult + 0, FInf, // fEnchantmentMult + 0, FInf, // fEnchantmentValueMult + 0, FInf, // fEncumberedMoveEffect + 0, FInf, // fEncumbranceStrMult + 0, FInf, // fEndFatigueMult + -FInf, FInf, // fFallAcroBase + 0, FInf, // fFallAcroMult + 0, FInf, // fFallDamageDistanceMin + -FInf, FInf, // fFallDistanceBase + 0, FInf, // fFallDistanceMult + -FInf, FInf, // fFatigueAttackBase + 0, FInf, // fFatigueAttackMult + 0, FInf, // fFatigueBase + 0, FInf, // fFatigueBlockBase + 0, FInf, // fFatigueBlockMult + 0, FInf, // fFatigueJumpBase + 0, FInf, // fFatigueJumpMult + 0, FInf, // fFatigueMult + -FInf, FInf, // fFatigueReturnBase + 0, FInf, // fFatigueReturnMult + -FInf, FInf, // fFatigueRunBase + 0, FInf, // fFatigueRunMult + -FInf, FInf, // fFatigueSneakBase + 0, FInf, // fFatigueSneakMult + -FInf, FInf, // fFatigueSpellBase + -FInf, FInf, // fFatigueSpellCostMult + 0, FInf, // fFatigueSpellMult + -FInf, FInf, // fFatigueSwimRunBase + 0, FInf, // fFatigueSwimRunMult + -FInf, FInf, // fFatigueSwimWalkBase + 0, FInf, // fFatigueSwimWalkMult + -FInf, FInf, // fFightDispMult + -FInf, FInf, // fFightDistanceMultiplier + -FInf, FInf, // fFightStealing + -FInf, FInf, // fFleeDistance + -FInf, FInf, // fGreetDistanceReset + 0, FInf, // fHandtoHandHealthPer + 0, FInf, // fHandToHandReach + -FInf, FInf, // fHoldBreathEndMult + 0, FInf, // fHoldBreathTime + 0, FInf, // fIdleChanceMultiplier + -FInf, FInf, // fIngredientMult + 0, FInf, // fInteriorHeadTrackMult + -FInf, FInf, // fJumpAcrobaticsBase + 0, FInf, // fJumpAcroMultiplier + -FInf, FInf, // fJumpEncumbranceBase + 0, FInf, // fJumpEncumbranceMultiplier + -FInf, FInf, // fJumpMoveBase + 0, FInf, // fJumpMoveMult + 0, FInf, // fJumpRunMultiplier + -FInf, FInf, // fKnockDownMult + 0, FInf, // fLevelMod + 0, FInf, // fLevelUpHealthEndMult + 0, FInf, // fLightMaxMod + 0, FInf, // fLuckMod + 0, FInf, // fMagesGuildTravel + -FInf, FInf, // fMagicCreatureCastDelay + -FInf, FInf, // fMagicDetectRefreshRate + -FInf, FInf, // fMagicItemConstantMult + -FInf, FInf, // fMagicItemCostMult + -FInf, FInf, // fMagicItemOnceMult + -FInf, FInf, // fMagicItemPriceMult + 0, FInf, // fMagicItemRechargePerSecond + -FInf, FInf, // fMagicItemStrikeMult + -FInf, FInf, // fMagicItemUsedMult + 0, FInf, // fMagicStartIconBlink + 0, FInf, // fMagicSunBlockedMult + FEps, FInf, // fMajorSkillBonus + 0, FInf, // fMaxFlySpeed + 0, FInf, // fMaxHandToHandMult + 0, FInf, // fMaxHeadTrackDistance + 0, FInf, // fMaxWalkSpeed + 0, FInf, // fMaxWalkSpeedCreature + 0, FInf, // fMedMaxMod + 0, FInf, // fMessageTimePerChar + 0, FInf, // fMinFlySpeed + 0, FInf, // fMinHandToHandMult + FEps, FInf, // fMinorSkillBonus + 0, FInf, // fMinWalkSpeed + 0, FInf, // fMinWalkSpeedCreature + FEps, FInf, // fMiscSkillBonus + 0, FInf, // fNPCbaseMagickaMult + 0, FInf, // fNPCHealthBarFade + 0, FInf, // fNPCHealthBarTime + 0, FInf, // fPCbaseMagickaMult + 0, FInf, // fPerDieRollMult + 0, FInf, // fPersonalityMod + 0, FInf, // fPerTempMult + -FInf, 0, // fPickLockMult + 0, FInf, // fPickPocketMod + -FInf, FInf, // fPotionMinUsefulDuration + 0, FInf, // fPotionStrengthMult + FEps, FInf, // fPotionT1DurMult + FEps, FInf, // fPotionT1MagMult + -FInf, FInf, // fPotionT4BaseStrengthMult + -FInf, FInf, // fPotionT4EquipStrengthMult + 0, FInf, // fProjectileMaxSpeed + 0, FInf, // fProjectileMinSpeed + 0, FInf, // fProjectileThrownStoreChance + 0, FInf, // fRepairAmountMult + 0, FInf, // fRepairMult + 0, FInf, // fReputationMod + 0, FInf, // fRestMagicMult + -FInf, FInf, // fSeriousWoundMult + 0, FInf, // fSleepRandMod + 0, FInf, // fSleepRestMod + -FInf, 0, // fSneakBootMult + -FInf, FInf, // fSneakDistanceBase + 0, FInf, // fSneakDistanceMultiplier + 0, FInf, // fSneakNoViewMult + 0, FInf, // fSneakSkillMult + 0, FInf, // fSneakSpeedMultiplier + 0, FInf, // fSneakUseDelay + 0, FInf, // fSneakUseDist + 0, FInf, // fSneakViewMult + 0, FInf, // fSoulGemMult + 0, FInf, // fSpecialSkillBonus + 0, FInf, // fSpellMakingValueMult + -FInf, FInf, // fSpellPriceMult + 0, FInf, // fSpellValueMult + 0, FInf, // fStromWalkMult + 0, FInf, // fStromWindSpeed + 0, FInf, // fSuffocationDamage + 0, FInf, // fSwimHeightScale + 0, FInf, // fSwimRunAthleticsMult + 0, FInf, // fSwimRunBase + -FInf, FInf, // fSwimWalkAthleticsMult + -FInf, FInf, // fSwimWalkBase + 0, FInf, // fSwingBlockBase + 0, FInf, // fSwingBlockMult + 0, FInf, // fTargetSpellMaxSpeed + 0, FInf, // fThrownWeaponMaxSpeed + 0, FInf, // fThrownWeaponMinSpeed + 0, FInf, // fTrapCostMult + 0, FInf, // fTravelMult + 0, FInf, // fTravelTimeMult + 0, FInf, // fUnarmoredBase1 + 0, FInf, // fUnarmoredBase2 + 0, FInf, // fVanityDelay + 0, FInf, // fVoiceIdleOdds + -FInf, FInf, // fWaterReflectUpdateAlways + -FInf, FInf, // fWaterReflectUpdateSeldom + 0, FInf, // fWeaponDamageMult + 0, FInf, // fWeaponFatigueBlockMult + 0, FInf, // fWeaponFatigueMult + 0, FInf, // fWereWolfAcrobatics + -FInf, FInf, // fWereWolfAgility + -FInf, FInf, // fWereWolfAlchemy + -FInf, FInf, // fWereWolfAlteration + -FInf, FInf, // fWereWolfArmorer + -FInf, FInf, // fWereWolfAthletics + -FInf, FInf, // fWereWolfAxe + -FInf, FInf, // fWereWolfBlock + -FInf, FInf, // fWereWolfBluntWeapon + -FInf, FInf, // fWereWolfConjuration + -FInf, FInf, // fWereWolfDestruction + -FInf, FInf, // fWereWolfEnchant + -FInf, FInf, // fWereWolfEndurance + -FInf, FInf, // fWereWolfFatigue + -FInf, FInf, // fWereWolfHandtoHand + -FInf, FInf, // fWereWolfHealth + -FInf, FInf, // fWereWolfHeavyArmor + -FInf, FInf, // fWereWolfIllusion + -FInf, FInf, // fWereWolfIntellegence + -FInf, FInf, // fWereWolfLightArmor + -FInf, FInf, // fWereWolfLongBlade + -FInf, FInf, // fWereWolfLuck + -FInf, FInf, // fWereWolfMagicka + -FInf, FInf, // fWereWolfMarksman + -FInf, FInf, // fWereWolfMediumArmor + -FInf, FInf, // fWereWolfMerchantile + -FInf, FInf, // fWereWolfMysticism + -FInf, FInf, // fWereWolfPersonality + -FInf, FInf, // fWereWolfRestoration + 0, FInf, // fWereWolfRunMult + -FInf, FInf, // fWereWolfSecurity + -FInf, FInf, // fWereWolfShortBlade + -FInf, FInf, // fWereWolfSilverWeaponDamageMult + -FInf, FInf, // fWereWolfSneak + -FInf, FInf, // fWereWolfSpear + -FInf, FInf, // fWereWolfSpeechcraft + -FInf, FInf, // fWereWolfSpeed + -FInf, FInf, // fWereWolfStrength + -FInf, FInf, // fWereWolfUnarmored + -FInf, FInf, // fWereWolfWillPower + 0, FInf, // fWortChanceValue }; -const int CSMWorld::DefaultGmsts::IntLimits[CSMWorld::DefaultGmsts::IntCount * 2] = -{ - IMin, IMax, // i1stPersonSneakDelta - IMin, IMax, // iAlarmAttack - IMin, IMax, // iAlarmKilling - IMin, IMax, // iAlarmPickPocket - IMin, IMax, // iAlarmStealing - IMin, IMax, // iAlarmTresspass - IMin, IMax, // iAlchemyMod - 0, IMax, // iAutoPCSpellMax - IMin, IMax, // iAutoRepFacMod - IMin, IMax, // iAutoRepLevMod - IMin, IMax, // iAutoSpellAlterationMax - 0, IMax, // iAutoSpellAttSkillMin - IMin, IMax, // iAutoSpellConjurationMax - IMin, IMax, // iAutoSpellDestructionMax - IMin, IMax, // iAutoSpellIllusionMax - IMin, IMax, // iAutoSpellMysticismMax - IMin, IMax, // iAutoSpellRestorationMax - 0, IMax, // iAutoSpellTimesCanCast - IMin, 0, // iBarterFailDisposition - 0, IMax, // iBarterSuccessDisposition - 1, IMax, // iBaseArmorSkill - 0, IMax, // iBlockMaxChance - 0, IMax, // iBlockMinChance - 0, IMax, // iBootsWeight - IMin, IMax, // iCrimeAttack - IMin, IMax, // iCrimeKilling - IMin, IMax, // iCrimePickPocket - 0, IMax, // iCrimeThreshold - 0, IMax, // iCrimeThresholdMultiplier - IMin, IMax, // iCrimeTresspass - 0, IMax, // iCuirassWeight - 1, IMax, // iDaysinPrisonMod - IMin, 0, // iDispAttackMod - IMin, 0, // iDispKilling - IMin, 0, // iDispTresspass - IMin, IMax, // iFightAlarmMult - IMin, IMax, // iFightAttack - IMin, IMax, // iFightAttacking - 0, IMax, // iFightDistanceBase - IMin, IMax, // iFightKilling - IMin, IMax, // iFightPickpocket - IMin, IMax, // iFightTrespass - IMin, IMax, // iFlee - 0, IMax, // iGauntletWeight - 0, IMax, // iGreavesWeight - 0, IMax, // iGreetDistanceMultiplier - 0, IMax, // iGreetDuration - 0, IMax, // iHelmWeight - IMin, IMax, // iKnockDownOddsBase - IMin, IMax, // iKnockDownOddsMult - IMin, IMax, // iLevelUp01Mult - IMin, IMax, // iLevelUp02Mult - IMin, IMax, // iLevelUp03Mult - IMin, IMax, // iLevelUp04Mult - IMin, IMax, // iLevelUp05Mult - IMin, IMax, // iLevelUp06Mult - IMin, IMax, // iLevelUp07Mult - IMin, IMax, // iLevelUp08Mult - IMin, IMax, // iLevelUp09Mult - IMin, IMax, // iLevelUp10Mult - IMin, IMax, // iLevelupMajorMult - IMin, IMax, // iLevelupMajorMultAttribute - IMin, IMax, // iLevelupMinorMult - IMin, IMax, // iLevelupMinorMultAttribute - IMin, IMax, // iLevelupMiscMultAttriubte - IMin, IMax, // iLevelupSpecialization - IMin, IMax, // iLevelupTotal - IMin, IMax, // iMagicItemChargeConst - IMin, IMax, // iMagicItemChargeOnce - IMin, IMax, // iMagicItemChargeStrike - IMin, IMax, // iMagicItemChargeUse - IMin, IMax, // iMaxActivateDist - IMin, IMax, // iMaxInfoDist - 0, IMax, // iMonthsToRespawn - 0, IMax, // iNumberCreatures - 0, IMax, // iPauldronWeight - 0, IMax, // iPerMinChance - 0, IMax, // iPerMinChange - 0, IMax, // iPickMaxChance - 0, IMax, // iPickMinChance - 0, IMax, // iShieldWeight - 0, IMax, // iSoulAmountForConstantEffect - 0, IMax, // iTrainingMod - 0, IMax, // iVoiceAttackOdds - 0, IMax, // iVoiceHitOdds - IMin, IMax, // iWereWolfBounty - IMin, IMax, // iWereWolfFightMod - IMin, IMax, // iWereWolfFleeMod - IMin, IMax // iWereWolfLevelToAttack +const int CSMWorld::DefaultGmsts::IntLimits[CSMWorld::DefaultGmsts::IntCount * 2] = { + IMin, IMax, // i1stPersonSneakDelta + IMin, IMax, // iAlarmAttack + IMin, IMax, // iAlarmKilling + IMin, IMax, // iAlarmPickPocket + IMin, IMax, // iAlarmStealing + IMin, IMax, // iAlarmTresspass + IMin, IMax, // iAlchemyMod + 0, IMax, // iAutoPCSpellMax + IMin, IMax, // iAutoRepFacMod + IMin, IMax, // iAutoRepLevMod + IMin, IMax, // iAutoSpellAlterationMax + 0, IMax, // iAutoSpellAttSkillMin + IMin, IMax, // iAutoSpellConjurationMax + IMin, IMax, // iAutoSpellDestructionMax + IMin, IMax, // iAutoSpellIllusionMax + IMin, IMax, // iAutoSpellMysticismMax + IMin, IMax, // iAutoSpellRestorationMax + 0, IMax, // iAutoSpellTimesCanCast + IMin, 0, // iBarterFailDisposition + 0, IMax, // iBarterSuccessDisposition + 1, IMax, // iBaseArmorSkill + 0, IMax, // iBlockMaxChance + 0, IMax, // iBlockMinChance + 0, IMax, // iBootsWeight + IMin, IMax, // iCrimeAttack + IMin, IMax, // iCrimeKilling + IMin, IMax, // iCrimePickPocket + 0, IMax, // iCrimeThreshold + 0, IMax, // iCrimeThresholdMultiplier + IMin, IMax, // iCrimeTresspass + 0, IMax, // iCuirassWeight + 1, IMax, // iDaysinPrisonMod + IMin, 0, // iDispAttackMod + IMin, 0, // iDispKilling + IMin, 0, // iDispTresspass + IMin, IMax, // iFightAlarmMult + IMin, IMax, // iFightAttack + IMin, IMax, // iFightAttacking + 0, IMax, // iFightDistanceBase + IMin, IMax, // iFightKilling + IMin, IMax, // iFightPickpocket + IMin, IMax, // iFightTrespass + IMin, IMax, // iFlee + 0, IMax, // iGauntletWeight + 0, IMax, // iGreavesWeight + 0, IMax, // iGreetDistanceMultiplier + 0, IMax, // iGreetDuration + 0, IMax, // iHelmWeight + IMin, IMax, // iKnockDownOddsBase + IMin, IMax, // iKnockDownOddsMult + IMin, IMax, // iLevelUp01Mult + IMin, IMax, // iLevelUp02Mult + IMin, IMax, // iLevelUp03Mult + IMin, IMax, // iLevelUp04Mult + IMin, IMax, // iLevelUp05Mult + IMin, IMax, // iLevelUp06Mult + IMin, IMax, // iLevelUp07Mult + IMin, IMax, // iLevelUp08Mult + IMin, IMax, // iLevelUp09Mult + IMin, IMax, // iLevelUp10Mult + IMin, IMax, // iLevelupMajorMult + IMin, IMax, // iLevelupMajorMultAttribute + IMin, IMax, // iLevelupMinorMult + IMin, IMax, // iLevelupMinorMultAttribute + IMin, IMax, // iLevelupMiscMultAttriubte + IMin, IMax, // iLevelupSpecialization + IMin, IMax, // iLevelupTotal + IMin, IMax, // iMagicItemChargeConst + IMin, IMax, // iMagicItemChargeOnce + IMin, IMax, // iMagicItemChargeStrike + IMin, IMax, // iMagicItemChargeUse + IMin, IMax, // iMaxActivateDist + IMin, IMax, // iMaxInfoDist + 0, IMax, // iMonthsToRespawn + 0, IMax, // iNumberCreatures + 0, IMax, // iPauldronWeight + 0, IMax, // iPerMinChance + 0, IMax, // iPerMinChange + 0, IMax, // iPickMaxChance + 0, IMax, // iPickMinChance + 0, IMax, // iShieldWeight + 0, IMax, // iSoulAmountForConstantEffect + 0, IMax, // iTrainingMod + 0, IMax, // iVoiceAttackOdds + 0, IMax, // iVoiceHitOdds + IMin, IMax, // iWereWolfBounty + IMin, IMax, // iWereWolfFightMod + IMin, IMax, // iWereWolfFleeMod + IMin, IMax, // iWereWolfLevelToAttack }; diff --git a/apps/opencs/model/world/defaultgmsts.hpp b/apps/opencs/model/world/defaultgmsts.hpp index a2888ed6a..baee7d66a 100644 --- a/apps/opencs/model/world/defaultgmsts.hpp +++ b/apps/opencs/model/world/defaultgmsts.hpp @@ -3,24 +3,26 @@ #include -namespace CSMWorld { - namespace DefaultGmsts { - +namespace CSMWorld +{ + namespace DefaultGmsts + { + const size_t FloatCount = 258; const size_t IntCount = 89; const size_t StringCount = 1174; - + const size_t OptionalFloatCount = 42; const size_t OptionalIntCount = 4; const size_t OptionalStringCount = 26; - + extern const char* Floats[]; - extern const char * Ints[]; - extern const char * Strings[]; - - extern const char * OptionalFloats[]; - extern const char * OptionalInts[]; - extern const char * OptionalStrings[]; + extern const char* Ints[]; + extern const char* Strings[]; + + extern const char* OptionalFloats[]; + extern const char* OptionalInts[]; + extern const char* OptionalStrings[]; extern const float FloatsDefaultValues[]; extern const int IntsDefaultValues[]; diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp new file mode 100644 index 000000000..faab5a20c --- /dev/null +++ b/apps/opencs/model/world/idcollection.cpp @@ -0,0 +1,58 @@ +#include "idcollection.hpp" + +#include +#include +#include + +#include +#include +#include + +#include + +namespace ESM +{ + class ESMReader; +} + +namespace CSMWorld +{ + template <> + int IdCollection::load(ESM::ESMReader& reader, bool base) + { + Pathgrid record; + bool isDeleted = false; + + loadRecord(record, reader, isDeleted); + + const ESM::RefId id = getRecordId(record); + int index = this->searchId(id); + + if (record.mPoints.empty() || record.mEdges.empty()) + isDeleted = true; + + if (isDeleted) + { + if (index == -1) + { + // deleting a record that does not exist + // ignore it for now + /// \todo report the problem to the user + return -1; + } + + if (base) + { + this->removeRows(index, 1); + return -1; + } + + auto baseRecord = std::make_unique>(this->getRecord(index)); + baseRecord->mState = RecordBase::State_Deleted; + this->setRecord(index, std::move(baseRecord)); + return index; + } + + return load(record, base, index); + } +} diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 7849aab92..67b886c3f 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -1,75 +1,85 @@ #ifndef CSM_WOLRD_IDCOLLECTION_H #define CSM_WOLRD_IDCOLLECTION_H -#include +#include +#include +#include + +#include + +#include +#include #include "collection.hpp" #include "land.hpp" +#include "pathgrid.hpp" + +namespace ESM +{ + class ESMReader; +} namespace CSMWorld { + struct Pathgrid; + /// \brief Single type collection of top level records - template > - class IdCollection : public Collection + template + class IdCollection : public Collection { - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); + virtual void loadRecord(ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); - public: + public: + /// \return Index of loaded record (-1 if no record was loaded) + int load(ESM::ESMReader& reader, bool base); - /// \return Index of loaded record (-1 if no record was loaded) - int load (ESM::ESMReader& reader, bool base); + /// \param index Index at which the record can be found. + /// Special values: -2 index unknown, -1 record does not exist yet and therefore + /// does not have an index + /// + /// \return index + int load(const ESXRecordT& record, bool base, int index = -2); - /// \param index Index at which the record can be found. - /// Special values: -2 index unknown, -1 record does not exist yet and therefore - /// does not have an index - /// - /// \return index - int load (const ESXRecordT& record, bool base, int index = -2); - - bool tryDelete (const std::string& id); - ///< Try deleting \a id. If the id does not exist or can't be deleted the call is ignored. - /// - /// \return Has the ID been deleted? + bool tryDelete(const ESM::RefId& id); + ///< Try deleting \a id. If the id does not exist or can't be deleted the call is ignored. + /// + /// \return Has the ID been deleted? }; - template - void IdCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader, - bool& isDeleted) + template + void IdCollection::loadRecord(ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted) { - record.load (reader, isDeleted); + record.load(reader, isDeleted); } - template<> - inline void IdCollection >::loadRecord (Land& record, - ESM::ESMReader& reader, bool& isDeleted) + template <> + inline void IdCollection::loadRecord(Land& record, ESM::ESMReader& reader, bool& isDeleted) { - record.load (reader, isDeleted); + record.load(reader, isDeleted); // Load all land data for now. A future optimisation may only load non-base data // if a suitable mechanism for avoiding race conditions can be established. - int flags = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | - ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; - record.loadData (flags); + int flags = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; + record.loadData(flags); // Prevent data from being reloaded. record.mContext.filename.clear(); } - template - int IdCollection::load (ESM::ESMReader& reader, bool base) + template + int IdCollection::load(ESM::ESMReader& reader, bool base) { ESXRecordT record; bool isDeleted = false; - loadRecord (record, reader, isDeleted); + loadRecord(record, reader, isDeleted); - std::string id = IdAccessorT().getId (record); - int index = this->searchId (id); + ESM::RefId id = getRecordId(record); + int index = this->searchId(id); if (isDeleted) { - if (index==-1) + if (index == -1) { // deleting a record that does not exist // ignore it for now @@ -79,77 +89,80 @@ namespace CSMWorld if (base) { - this->removeRows (index, 1); + this->removeRows(index, 1); return -1; } - Record baseRecord = this->getRecord (index); - baseRecord.mState = RecordBase::State_Deleted; - this->setRecord (index, baseRecord); + auto baseRecord = std::make_unique>(this->getRecord(index)); + baseRecord->mState = RecordBase::State_Deleted; + this->setRecord(index, std::move(baseRecord)); return index; } - return load (record, base, index); + return load(record, base, index); } - template - int IdCollection::load (const ESXRecordT& record, bool base, - int index) + template + int IdCollection::load(const ESXRecordT& record, bool base, int index) { - if (index==-2) - index = this->searchId (IdAccessorT().getId (record)); + if (index == -2) // index unknown + index = this->searchId(getRecordId(record)); - if (index==-1) + if (index == -1) { // new record - Record record2; - record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record2.mBase : record2.mModified) = record; + auto record2 = std::make_unique>(); + record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2->mBase : record2->mModified) = record; index = this->getSize(); - this->appendRecord (record2); + this->appendRecord(std::move(record2)); } else { // old record - Record record2 = Collection::getRecord (index); + auto record2 = std::make_unique>(Collection::getRecord(index)); if (base) - record2.mBase = record; + record2->mBase = record; else - record2.setModified (record); + record2->setModified(record); - this->setRecord (index, record2); + this->setRecord(index, std::move(record2)); } return index; } - template - bool IdCollection::tryDelete (const std::string& id) + template + bool IdCollection::tryDelete(const ESM::RefId& id) { - int index = this->searchId (id); + int index = this->searchId(id); - if (index==-1) + if (index == -1) return false; - Record record = Collection::getRecord (index); + const Record& record = Collection::getRecord(index); if (record.isDeleted()) return false; - if (record.mState==RecordBase::State_ModifiedOnly) + if (record.mState == RecordBase::State_ModifiedOnly) { - Collection::removeRows (index, 1); + Collection::removeRows(index, 1); } else { - record.mState = RecordBase::State_Deleted; - this->setRecord (index, record); + auto record2 = std::make_unique>(Collection::getRecord(index)); + record2->mState = RecordBase::State_Deleted; + this->setRecord(index, std::move(record2)); } return true; } + + template <> + int IdCollection::load(ESM::ESMReader& reader, bool base); } #endif diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 649a96038..263f462b6 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -1,49 +1,59 @@ #include "idcompletionmanager.hpp" +#include #include +#include +#include +#include + +#include +#include +#include + #include "../../view/widget/completerpopup.hpp" #include "data.hpp" #include "idtablebase.hpp" +class QAbstractItemView; + namespace { std::map generateModelTypes() { std::map types; - types[CSMWorld::ColumnBase::Display_BodyPart ] = CSMWorld::UniversalId::Type_BodyPart; - types[CSMWorld::ColumnBase::Display_Cell ] = CSMWorld::UniversalId::Type_Cell; - types[CSMWorld::ColumnBase::Display_Class ] = CSMWorld::UniversalId::Type_Class; + types[CSMWorld::ColumnBase::Display_BodyPart] = CSMWorld::UniversalId::Type_BodyPart; + types[CSMWorld::ColumnBase::Display_Cell] = CSMWorld::UniversalId::Type_Cell; + types[CSMWorld::ColumnBase::Display_Class] = CSMWorld::UniversalId::Type_Class; types[CSMWorld::ColumnBase::Display_CreatureLevelledList] = CSMWorld::UniversalId::Type_Referenceable; - types[CSMWorld::ColumnBase::Display_Creature ] = CSMWorld::UniversalId::Type_Referenceable; - types[CSMWorld::ColumnBase::Display_Enchantment ] = CSMWorld::UniversalId::Type_Enchantment; - types[CSMWorld::ColumnBase::Display_Faction ] = CSMWorld::UniversalId::Type_Faction; - types[CSMWorld::ColumnBase::Display_GlobalVariable ] = CSMWorld::UniversalId::Type_Global; - types[CSMWorld::ColumnBase::Display_Icon ] = CSMWorld::UniversalId::Type_Icon; - types[CSMWorld::ColumnBase::Display_Journal ] = CSMWorld::UniversalId::Type_Journal; - types[CSMWorld::ColumnBase::Display_Mesh ] = CSMWorld::UniversalId::Type_Mesh; - types[CSMWorld::ColumnBase::Display_Miscellaneous ] = CSMWorld::UniversalId::Type_Referenceable; - types[CSMWorld::ColumnBase::Display_Npc ] = CSMWorld::UniversalId::Type_Referenceable; - types[CSMWorld::ColumnBase::Display_Race ] = CSMWorld::UniversalId::Type_Race; - types[CSMWorld::ColumnBase::Display_Region ] = CSMWorld::UniversalId::Type_Region; - types[CSMWorld::ColumnBase::Display_Referenceable ] = CSMWorld::UniversalId::Type_Referenceable; - types[CSMWorld::ColumnBase::Display_Script ] = CSMWorld::UniversalId::Type_Script; - types[CSMWorld::ColumnBase::Display_Skill ] = CSMWorld::UniversalId::Type_Skill; - types[CSMWorld::ColumnBase::Display_Sound ] = CSMWorld::UniversalId::Type_Sound; - types[CSMWorld::ColumnBase::Display_SoundRes ] = CSMWorld::UniversalId::Type_SoundRes; - types[CSMWorld::ColumnBase::Display_Spell ] = CSMWorld::UniversalId::Type_Spell; - types[CSMWorld::ColumnBase::Display_Static ] = CSMWorld::UniversalId::Type_Referenceable; - types[CSMWorld::ColumnBase::Display_Texture ] = CSMWorld::UniversalId::Type_Texture; - types[CSMWorld::ColumnBase::Display_Topic ] = CSMWorld::UniversalId::Type_Topic; - types[CSMWorld::ColumnBase::Display_Weapon ] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Creature] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Enchantment] = CSMWorld::UniversalId::Type_Enchantment; + types[CSMWorld::ColumnBase::Display_Faction] = CSMWorld::UniversalId::Type_Faction; + types[CSMWorld::ColumnBase::Display_GlobalVariable] = CSMWorld::UniversalId::Type_Global; + types[CSMWorld::ColumnBase::Display_Icon] = CSMWorld::UniversalId::Type_Icon; + types[CSMWorld::ColumnBase::Display_Journal] = CSMWorld::UniversalId::Type_Journal; + types[CSMWorld::ColumnBase::Display_Mesh] = CSMWorld::UniversalId::Type_Mesh; + types[CSMWorld::ColumnBase::Display_Miscellaneous] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Npc] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Race] = CSMWorld::UniversalId::Type_Race; + types[CSMWorld::ColumnBase::Display_Region] = CSMWorld::UniversalId::Type_Region; + types[CSMWorld::ColumnBase::Display_Referenceable] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Script] = CSMWorld::UniversalId::Type_Script; + types[CSMWorld::ColumnBase::Display_Skill] = CSMWorld::UniversalId::Type_Skill; + types[CSMWorld::ColumnBase::Display_Sound] = CSMWorld::UniversalId::Type_Sound; + types[CSMWorld::ColumnBase::Display_SoundRes] = CSMWorld::UniversalId::Type_SoundRes; + types[CSMWorld::ColumnBase::Display_Spell] = CSMWorld::UniversalId::Type_Spell; + types[CSMWorld::ColumnBase::Display_Static] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Texture] = CSMWorld::UniversalId::Type_Texture; + types[CSMWorld::ColumnBase::Display_Topic] = CSMWorld::UniversalId::Type_Topic; + types[CSMWorld::ColumnBase::Display_Weapon] = CSMWorld::UniversalId::Type_Referenceable; return types; } - typedef std::map::const_iterator ModelTypeConstIterator; + typedef std::map::const_iterator ModelTypeConstIterator; } const std::map @@ -65,7 +75,7 @@ std::vector CSMWorld::IdCompletionManager::getDis return types; } -CSMWorld::IdCompletionManager::IdCompletionManager(CSMWorld::Data &data) +CSMWorld::IdCompletionManager::IdCompletionManager(CSMWorld::Data& data) { generateCompleters(data); } @@ -84,14 +94,14 @@ std::shared_ptr CSMWorld::IdCompletionManager::getCompleter(CSMWorld return mCompleters[display]; } -void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) +void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data& data) { ModelTypeConstIterator current = sCompleterModelTypes.begin(); ModelTypeConstIterator end = sCompleterModelTypes.end(); for (; current != end; ++current) { - QAbstractItemModel *model = data.getTableModel(current->second); - CSMWorld::IdTableBase *table = dynamic_cast(model); + QAbstractItemModel* model = data.getTableModel(current->second); + CSMWorld::IdTableBase* table = dynamic_cast(model); if (table != nullptr) { int idColumn = table->searchColumnIndex(CSMWorld::Columns::ColumnId_Id); @@ -103,7 +113,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) completer->setCompletionRole(Qt::DisplayRole); completer->setCaseSensitivity(Qt::CaseInsensitive); - QAbstractItemView *popup = new CSVWidget::CompleterPopup(); + QAbstractItemView* popup = new CSVWidget::CompleterPopup(); completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); diff --git a/apps/opencs/model/world/idcompletionmanager.hpp b/apps/opencs/model/world/idcompletionmanager.hpp index e48360432..b1e9f732f 100644 --- a/apps/opencs/model/world/idcompletionmanager.hpp +++ b/apps/opencs/model/world/idcompletionmanager.hpp @@ -1,9 +1,9 @@ #ifndef CSM_WORLD_IDCOMPLETIONMANAGER_HPP #define CSM_WORLD_IDCOMPLETIONMANAGER_HPP -#include #include #include +#include #include "columnbase.hpp" #include "universalid.hpp" @@ -17,23 +17,23 @@ namespace CSMWorld /// \brief Creates and stores all ID completers class IdCompletionManager { - static const std::map sCompleterModelTypes; + static const std::map sCompleterModelTypes; - std::map > mCompleters; + std::map> mCompleters; - // Don't allow copying - IdCompletionManager(const IdCompletionManager &); - IdCompletionManager &operator = (const IdCompletionManager &); + // Don't allow copying + IdCompletionManager(const IdCompletionManager&); + IdCompletionManager& operator=(const IdCompletionManager&); - void generateCompleters(Data &data); + void generateCompleters(Data& data); - public: - static std::vector getDisplayTypes(); + public: + static std::vector getDisplayTypes(); - IdCompletionManager(Data &data); + IdCompletionManager(Data& data); - bool hasCompleterFor(ColumnBase::Display display) const; - std::shared_ptr getCompleter(ColumnBase::Display display); + bool hasCompleterFor(ColumnBase::Display display) const; + std::shared_ptr getCompleter(ColumnBase::Display display); }; } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 30fe6f639..69ac8a42b 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -1,26 +1,35 @@ #include "idtable.hpp" +#include +#include + #include -#include #include #include #include +#include #include +#include -#include +#include +#include +#include +#include + +#include +#include #include "collectionbase.hpp" #include "columnbase.hpp" #include "landtexture.hpp" -CSMWorld::IdTable::IdTable (CollectionBase *idCollection, unsigned int features) -: IdTableBase (features), mIdCollection (idCollection) -{} +CSMWorld::IdTable::IdTable(CollectionBase* idCollection, unsigned int features) + : IdTableBase(features) + , mIdCollection(idCollection) +{ +} -CSMWorld::IdTable::~IdTable() -{} - -int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const +int CSMWorld::IdTable::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; @@ -28,7 +37,7 @@ int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const return mIdCollection->getSize(); } -int CSMWorld::IdTable::columnCount (const QModelIndex & parent) const +int CSMWorld::IdTable::columnCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; @@ -36,54 +45,54 @@ int CSMWorld::IdTable::columnCount (const QModelIndex & parent) const return mIdCollection->getColumns(); } -QVariant CSMWorld::IdTable::data (const QModelIndex & index, int role) const +QVariant CSMWorld::IdTable::data(const QModelIndex& index, int role) const { if (index.row() < 0 || index.column() < 0) return QVariant(); - if (role==ColumnBase::Role_Display) + if (role == ColumnBase::Role_Display) return QVariant(mIdCollection->getColumn(index.column()).mDisplayType); - if (role==ColumnBase::Role_ColumnId) - return QVariant (getColumnId (index.column())); + if (role == ColumnBase::Role_ColumnId) + return QVariant(getColumnId(index.column())); - if ((role!=Qt::DisplayRole && role!=Qt::EditRole)) + if ((role != Qt::DisplayRole && role != Qt::EditRole)) return QVariant(); - if (role==Qt::EditRole && !mIdCollection->getColumn (index.column()).isEditable()) + if (role == Qt::EditRole && !mIdCollection->getColumn(index.column()).isEditable()) return QVariant(); - return mIdCollection->getData (index.row(), index.column()); + return mIdCollection->getData(index.row(), index.column()); } -QVariant CSMWorld::IdTable::headerData (int section, Qt::Orientation orientation, int role) const +QVariant CSMWorld::IdTable::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation==Qt::Vertical) + if (orientation == Qt::Vertical) return QVariant(); if (orientation != Qt::Horizontal) throw std::logic_error("Unknown header orientation specified"); - if (role==Qt::DisplayRole) - return tr (mIdCollection->getColumn (section).getTitle().c_str()); + if (role == Qt::DisplayRole) + return tr(mIdCollection->getColumn(section).getTitle().c_str()); - if (role==ColumnBase::Role_Flags) - return mIdCollection->getColumn (section).mFlags; + if (role == ColumnBase::Role_Flags) + return mIdCollection->getColumn(section).mFlags; - if (role==ColumnBase::Role_Display) - return mIdCollection->getColumn (section).mDisplayType; + if (role == ColumnBase::Role_Display) + return mIdCollection->getColumn(section).mDisplayType; - if (role==ColumnBase::Role_ColumnId) - return getColumnId (section); + if (role == ColumnBase::Role_ColumnId) + return getColumnId(section); return QVariant(); } -bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value, int role) +bool CSMWorld::IdTable::setData(const QModelIndex& index, const QVariant& value, int role) { - if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) + if (mIdCollection->getColumn(index.column()).isEditable() && role == Qt::EditRole) { - mIdCollection->setData (index.row(), index.column(), value); + mIdCollection->setData(index.row(), index.column(), value); int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); if (stateColumn != -1) @@ -93,10 +102,10 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value // modifying the state column can modify other values. we need to tell // views that the whole row has changed. - emit dataChanged(this->index(index.row(), 0), - this->index(index.row(), columnCount(index.parent()) - 1)); - - } else + emit dataChanged( + this->index(index.row(), 0), this->index(index.row(), columnCount(index.parent()) - 1)); + } + else { emit dataChanged(index, index); @@ -104,7 +113,8 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value QModelIndex stateIndex = this->index(index.row(), stateColumn); emit dataChanged(stateIndex, stateIndex); } - } else + } + else emit dataChanged(index, index); return true; @@ -113,73 +123,83 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value return false; } -Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const +Qt::ItemFlags CSMWorld::IdTable::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::ItemFlags(); Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; - if (mIdCollection->getColumn (index.column()).isUserEditable()) + if (mIdCollection->getColumn(index.column()).isUserEditable()) flags |= Qt::ItemIsEditable; + int blockedColumn = searchColumnIndex(Columns::ColumnId_Blocked); + if (blockedColumn != -1 && blockedColumn != index.column()) + { + bool isBlocked = mIdCollection->getData(index.row(), blockedColumn).toInt(); + if (isBlocked) + flags = Qt::ItemIsSelectable; // not enabled (to grey out) + } + return flags; } -bool CSMWorld::IdTable::removeRows (int row, int count, const QModelIndex& parent) +bool CSMWorld::IdTable::removeRows(int row, int count, const QModelIndex& parent) { if (parent.isValid()) return false; - beginRemoveRows (parent, row, row+count-1); + beginRemoveRows(parent, row, row + count - 1); - mIdCollection->removeRows (row, count); + mIdCollection->removeRows(row, count); endRemoveRows(); return true; } -QModelIndex CSMWorld::IdTable::index (int row, int column, const QModelIndex& parent) const +QModelIndex CSMWorld::IdTable::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid()) return QModelIndex(); - if (row<0 || row>=mIdCollection->getSize()) + if (row < 0 || row >= mIdCollection->getSize()) return QModelIndex(); - if (column<0 || column>=mIdCollection->getColumns()) + if (column < 0 || column >= mIdCollection->getColumns()) return QModelIndex(); - return createIndex (row, column); + return createIndex(row, column); } -QModelIndex CSMWorld::IdTable::parent (const QModelIndex& index) const +QModelIndex CSMWorld::IdTable::parent(const QModelIndex& index) const { return QModelIndex(); } -void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type) +void CSMWorld::IdTable::addRecord(const std::string& id, UniversalId::Type type) { - int index = mIdCollection->getAppendIndex (id, type); + ESM::RefId refId = ESM::RefId::stringRefId(id); + int index = mIdCollection->getAppendIndex(refId, type); - beginInsertRows (QModelIndex(), index, index); + beginInsertRows(QModelIndex(), index, index); - mIdCollection->appendBlankRecord (id, type); + mIdCollection->appendBlankRecord(refId, type); endInsertRows(); } -void CSMWorld::IdTable::addRecordWithData (const std::string& id, - const std::map& data, UniversalId::Type type) +void CSMWorld::IdTable::addRecordWithData( + const std::string& id, const std::map& data, UniversalId::Type type) { - int index = mIdCollection->getAppendIndex (id, type); + ESM::RefId refId = ESM::RefId::stringRefId(id); + int index = mIdCollection->getAppendIndex(refId, type); - beginInsertRows (QModelIndex(), index, index); + beginInsertRows(QModelIndex(), index, index); - mIdCollection->appendBlankRecord (id, type); + mIdCollection->appendBlankRecord(refId, type); - for (std::map::const_iterator iter (data.begin()); iter!=data.end(); ++iter) + for (std::map::const_iterator iter(data.begin()); iter != data.end(); ++iter) { mIdCollection->setData(index, iter->first, iter->second); } @@ -187,22 +207,22 @@ void CSMWorld::IdTable::addRecordWithData (const std::string& id, endInsertRows(); } -void CSMWorld::IdTable::cloneRecord(const std::string& origin, - const std::string& destination, - CSMWorld::UniversalId::Type type) +void CSMWorld::IdTable::cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, CSMWorld::UniversalId::Type type) { - int index = mIdCollection->getAppendIndex (destination); + int index = mIdCollection->getAppendIndex(destination, type); - beginInsertRows (QModelIndex(), index, index); + beginInsertRows(QModelIndex(), index, index); mIdCollection->cloneRecord(origin, destination, type); endInsertRows(); } bool CSMWorld::IdTable::touchRecord(const std::string& id) { - bool changed = mIdCollection->touchRecord(id); + ESM::RefId refId = ESM::RefId::stringRefId(id); + bool changed = mIdCollection->touchRecord(refId); - int row = mIdCollection->getIndex(id); + int row = mIdCollection->getIndex(refId); int column = mIdCollection->searchColumnIndex(Columns::ColumnId_RecordType); if (changed && column != -1) { @@ -215,104 +235,115 @@ bool CSMWorld::IdTable::touchRecord(const std::string& id) std::string CSMWorld::IdTable::getId(int row) const { - return mIdCollection->getId(row); + return mIdCollection->getId(row).getRefIdString(); } -///This method can return only indexes to the top level table cells -QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const +/// This method can return only indexes to the top level table cells +QModelIndex CSMWorld::IdTable::getModelIndex(const std::string& id, int column) const { - int row = mIdCollection->searchId (id); + const int row = mIdCollection->searchId(ESM::RefId::stringRefId(id)); + if (row != -1) return index(row, column); return QModelIndex(); } -void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& record, CSMWorld::UniversalId::Type type) +void CSMWorld::IdTable::setRecord( + const std::string& id, std::unique_ptr record, CSMWorld::UniversalId::Type type) { - int index = mIdCollection->searchId (id); + const ESM::RefId refId = ESM::RefId::stringRefId(id); + const int index = mIdCollection->searchId(refId); - if (index==-1) + if (index == -1) { - index = mIdCollection->getAppendIndex (id, type); + // For info records, appendRecord may use a different index than the one returned by + // getAppendIndex (because of prev/next links). This can result in the display not + // updating correctly after an undo + // + // Use an alternative method to get the correct index. For non-Info records the + // record pointer is ignored and internally calls getAppendIndex. + const int index2 = mIdCollection->getInsertIndex(refId, type, record.get()); - beginInsertRows (QModelIndex(), index, index); + beginInsertRows(QModelIndex(), index2, index2); - mIdCollection->appendRecord (record, type); + mIdCollection->appendRecord(std::move(record), type); endInsertRows(); } else { - mIdCollection->replace (index, record); - emit dataChanged (CSMWorld::IdTable::index (index, 0), - CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1)); + mIdCollection->replace(index, std::move(record)); + emit dataChanged( + CSMWorld::IdTable::index(index, 0), CSMWorld::IdTable::index(index, mIdCollection->getColumns() - 1)); } } -const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id) const +const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord(const std::string& id) const { - return mIdCollection->getRecord (id); + return mIdCollection->getRecord(ESM::RefId::stringRefId(id)); } -int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const +int CSMWorld::IdTable::searchColumnIndex(Columns::ColumnId id) const { - return mIdCollection->searchColumnIndex (id); + return mIdCollection->searchColumnIndex(id); } -int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const +int CSMWorld::IdTable::findColumnIndex(Columns::ColumnId id) const { - return mIdCollection->findColumnIndex (id); + return mIdCollection->findColumnIndex(id); } -void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newOrder) +void CSMWorld::IdTable::reorderRows(int baseIndex, const std::vector& newOrder) { - if (!newOrder.empty()) - if (mIdCollection->reorderRows (baseIndex, newOrder)) - emit dataChanged (index (baseIndex, 0), - index (baseIndex+static_cast(newOrder.size())-1, mIdCollection->getColumns()-1)); + if (newOrder.empty()) + return; + if (!mIdCollection->reorderRows(baseIndex, newOrder)) + return; + emit dataChanged( + index(baseIndex, 0), index(baseIndex + static_cast(newOrder.size()) - 1, mIdCollection->getColumns() - 1)); } -std::pair CSMWorld::IdTable::view (int row) const +std::pair CSMWorld::IdTable::view(int row) const { std::string id; std::string hint; if (getFeatures() & Feature_ViewCell) { - int cellColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Cell); - int idColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); + int cellColumn = mIdCollection->searchColumnIndex(Columns::ColumnId_Cell); + int idColumn = mIdCollection->searchColumnIndex(Columns::ColumnId_Id); - if (cellColumn!=-1 && idColumn!=-1) + if (cellColumn != -1 && idColumn != -1) { - id = mIdCollection->getData (row, cellColumn).toString().toUtf8().constData(); - hint = "r:" + std::string (mIdCollection->getData (row, idColumn).toString().toUtf8().constData()); + id = mIdCollection->getData(row, cellColumn).toString().toUtf8().constData(); + hint = "r:" + std::string(mIdCollection->getData(row, idColumn).toString().toUtf8().constData()); } } else if (getFeatures() & Feature_ViewId) { - int column = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); + int column = mIdCollection->searchColumnIndex(Columns::ColumnId_Id); - if (column!=-1) + if (column != -1) { - id = mIdCollection->getData (row, column).toString().toUtf8().constData(); + id = mIdCollection->getData(row, column).toString().toUtf8().constData(); hint = "c:" + id; } } if (id.empty()) - return std::make_pair (UniversalId::Type_None, ""); + return std::make_pair(UniversalId::Type_None, ""); - if (id[0]=='#') - id = ESM::CellId::sDefaultWorldspace; + if (id[0] == '#') + id = ESM::Cell::sDefaultWorldspaceId.getValue(); - return std::make_pair (UniversalId (UniversalId::Type_Scene, id), hint); + return std::make_pair(UniversalId(UniversalId::Type_Scene, id), hint); } -///For top level data/columns -bool CSMWorld::IdTable::isDeleted (const std::string& id) const +/// For top level data/columns +bool CSMWorld::IdTable::isDeleted(const std::string& id) const { - return getRecord (id).isDeleted(); + return getRecord(id).isDeleted(); } int CSMWorld::IdTable::getColumnId(int column) const @@ -320,7 +351,7 @@ int CSMWorld::IdTable::getColumnId(int column) const return mIdCollection->getColumn(column).getId(); } -CSMWorld::CollectionBase *CSMWorld::IdTable::idCollection() const +CSMWorld::CollectionBase* CSMWorld::IdTable::idCollection() const { return mIdCollection; } @@ -330,7 +361,8 @@ CSMWorld::LandTextureIdTable::LandTextureIdTable(CollectionBase* idCollection, u { } -CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::importTextures(const std::vector& ids) +CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::importTextures( + const std::vector& ids) { ImportResults results; @@ -339,18 +371,18 @@ CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::import for (int i = 0; i < idCollection()->getSize(); ++i) { auto& record = static_cast&>(idCollection()->getRecord(i)); - std::string texture = record.get().mTexture; - std::transform(texture.begin(), texture.end(), texture.begin(), tolower); if (record.isModified()) - reverseLookupMap.emplace(texture, idCollection()->getId(i)); + reverseLookupMap.emplace( + Misc::StringUtils::lowerCase(record.get().mTexture), idCollection()->getId(i).getRefIdString()); } for (const std::string& id : ids) { int plugin, index; - LandTexture::parseUniqueRecordId(id, plugin, index); - int oldRow = idCollection()->searchId(id); + + const ESM::RefId refId = ESM::RefId::stringRefId(id); + const int oldRow = idCollection()->searchId(refId); // If it does not exist or it is in the current plugin, it can be skipped. if (oldRow < 0 || plugin == 0) @@ -361,8 +393,7 @@ CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::import // Look for a pre-existing record auto& record = static_cast&>(idCollection()->getRecord(oldRow)); - std::string texture = record.get().mTexture; - std::transform(texture.begin(), texture.end(), texture.begin(), tolower); + std::string texture = Misc::StringUtils::lowerCase(record.get().mTexture); auto searchIt = reverseLookupMap.find(texture); if (searchIt != reverseLookupMap.end()) { @@ -372,14 +403,16 @@ CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::import // Iterate until an unused index or found, or the index has completely wrapped around. int startIndex = index; - do { + do + { std::string newId = LandTexture::createUniqueRecordId(0, index); - int newRow = idCollection()->searchId(newId); + const ESM::RefId newRefId = ESM::RefId::stringRefId(newId); + int newRow = idCollection()->searchId(newRefId); if (newRow < 0) { // Id not taken, clone it - cloneRecord(id, newId, UniversalId::Type_LandTexture); + cloneRecord(refId, newRefId, UniversalId::Type_LandTexture); results.createdRecords.push_back(newId); results.recordMapping.emplace_back(id, newId); reverseLookupMap.emplace(texture, newId); diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 6b7b8d318..3ec075ca9 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -1,11 +1,20 @@ #ifndef CSM_WOLRD_IDTABLE_H #define CSM_WOLRD_IDTABLE_H +#include +#include + +#include +#include +#include +#include #include +#include "columns.hpp" #include "idtablebase.hpp" #include "universalid.hpp" -#include "columns.hpp" + +class QObject; namespace CSMWorld { @@ -14,88 +23,84 @@ namespace CSMWorld class IdTable : public IdTableBase { - Q_OBJECT + Q_OBJECT - private: + private: + CollectionBase* mIdCollection; - CollectionBase *mIdCollection; + // not implemented + IdTable(const IdTable&); + IdTable& operator=(const IdTable&); - // not implemented - IdTable (const IdTable&); - IdTable& operator= (const IdTable&); + public: + IdTable(CollectionBase* idCollection, unsigned int features = 0); + ///< The ownership of \a idCollection is not transferred. - public: + virtual ~IdTable() = default; - IdTable (CollectionBase *idCollection, unsigned int features = 0); - ///< The ownership of \a idCollection is not transferred. + int rowCount(const QModelIndex& parent = QModelIndex()) const override; - virtual ~IdTable(); + int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount (const QModelIndex & parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - int columnCount (const QModelIndex & parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; - QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; - bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; - Qt::ItemFlags flags (const QModelIndex & index) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()) override; + QModelIndex parent(const QModelIndex& index) const override; - QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const override; + void addRecord(const std::string& id, UniversalId::Type type = UniversalId::Type_None); + ///< \param type Will be ignored, unless the collection supports multiple record types - QModelIndex parent (const QModelIndex& index) const override; + void addRecordWithData(const std::string& id, const std::map& data, + UniversalId::Type type = UniversalId::Type_None); + ///< \param type Will be ignored, unless the collection supports multiple record types - void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); - ///< \param type Will be ignored, unless the collection supports multiple record types + void cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, UniversalId::Type type = UniversalId::Type_None); - void addRecordWithData (const std::string& id, const std::map& data, - UniversalId::Type type = UniversalId::Type_None); - ///< \param type Will be ignored, unless the collection supports multiple record types + bool touchRecord(const std::string& id); + ///< Will change the record state to modified, if it is not already. - void cloneRecord(const std::string& origin, - const std::string& destination, - UniversalId::Type type = UniversalId::Type_None); + std::string getId(int row) const; - bool touchRecord(const std::string& id); - ///< Will change the record state to modified, if it is not already. + QModelIndex getModelIndex(const std::string& id, int column) const override; - std::string getId(int row) const; + void setRecord( + const std::string& id, std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None); + ///< Add record or overwrite existing record. - QModelIndex getModelIndex (const std::string& id, int column) const override; + const RecordBase& getRecord(const std::string& id) const; - void setRecord (const std::string& id, const RecordBase& record, - UniversalId::Type type = UniversalId::Type_None); - ///< Add record or overwrite existing record. + int searchColumnIndex(Columns::ColumnId id) const override; + ///< Return index of column with the given \a id. If no such column exists, -1 is returned. - const RecordBase& getRecord (const std::string& id) const; + int findColumnIndex(Columns::ColumnId id) const override; + ///< Return index of column with the given \a id. If no such column exists, an exception is + /// thrown. - int searchColumnIndex (Columns::ColumnId id) const override; - ///< Return index of column with the given \a id. If no such column exists, -1 is returned. + void reorderRows(int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). - int findColumnIndex (Columns::ColumnId id) const override; - ///< Return index of column with the given \a id. If no such column exists, an exception is - /// thrown. + std::pair view(int row) const override; + ///< Return the UniversalId and the hint for viewing \a row. If viewing is not + /// supported by this table, return (UniversalId::Type_None, ""). - void reorderRows (int baseIndex, const std::vector& newOrder); - ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices - /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// Is \a id flagged as deleted? + bool isDeleted(const std::string& id) const override; - std::pair view (int row) const override; - ///< Return the UniversalId and the hint for viewing \a row. If viewing is not - /// supported by this table, return (UniversalId::Type_None, ""). + int getColumnId(int column) const override; - /// Is \a id flagged as deleted? - bool isDeleted (const std::string& id) const override; - - int getColumnId(int column) const override; - - protected: - - virtual CollectionBase *idCollection() const; + protected: + virtual CollectionBase* idCollection() const; }; /// An IdTable customized to handle the more unique needs of LandTextureId's which behave @@ -103,22 +108,21 @@ namespace CSMWorld /// modified. class LandTextureIdTable : public IdTable { - public: + public: + struct ImportResults + { + using StringPair = std::pair; - struct ImportResults - { - using StringPair = std::pair; + /// The newly added records + std::vector createdRecords; + /// The 1st string is the original id, the 2nd is the mapped id + std::vector recordMapping; + }; - /// The newly added records - std::vector createdRecords; - /// The 1st string is the original id, the 2nd is the mapped id - std::vector recordMapping; - }; + LandTextureIdTable(CollectionBase* idCollection, unsigned int features = 0); - LandTextureIdTable(CollectionBase* idCollection, unsigned int features=0); - - /// Finds and maps/recreates the specified ids. - ImportResults importTextures(const std::vector& ids); + /// Finds and maps/recreates the specified ids. + ImportResults importTextures(const std::vector& ids); }; } diff --git a/apps/opencs/model/world/idtablebase.cpp b/apps/opencs/model/world/idtablebase.cpp index 274446b79..fd523d1ec 100644 --- a/apps/opencs/model/world/idtablebase.cpp +++ b/apps/opencs/model/world/idtablebase.cpp @@ -1,6 +1,9 @@ #include "idtablebase.hpp" -CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {} +CSMWorld::IdTableBase::IdTableBase(unsigned int features) + : mFeatures(features) +{ +} unsigned int CSMWorld::IdTableBase::getFeatures() const { diff --git a/apps/opencs/model/world/idtablebase.hpp b/apps/opencs/model/world/idtablebase.hpp index 8b82f984b..af84ff480 100644 --- a/apps/opencs/model/world/idtablebase.hpp +++ b/apps/opencs/model/world/idtablebase.hpp @@ -2,6 +2,10 @@ #define CSM_WOLRD_IDTABLEBASE_H #include +#include + +#include +#include #include "columns.hpp" @@ -11,60 +15,57 @@ namespace CSMWorld class IdTableBase : public QAbstractItemModel { - Q_OBJECT + Q_OBJECT - public: + public: + enum Features + { + Feature_ReorderWithinTopic = 1, - enum Features - { - Feature_ReorderWithinTopic = 1, + /// Use ID column to generate view request (ID is transformed into + /// worldspace and original ID is passed as hint with c: prefix). + Feature_ViewId = 2, - /// Use ID column to generate view request (ID is transformed into - /// worldspace and original ID is passed as hint with c: prefix). - Feature_ViewId = 2, + /// Use cell column to generate view request (cell ID is transformed + /// into worldspace and record ID is passed as hint with r: prefix). + Feature_ViewCell = 4, - /// Use cell column to generate view request (cell ID is transformed - /// into worldspace and record ID is passed as hint with r: prefix). - Feature_ViewCell = 4, + Feature_View = Feature_ViewId | Feature_ViewCell, - Feature_View = Feature_ViewId | Feature_ViewCell, + Feature_Preview = 8, - Feature_Preview = 8, + /// Table can not be modified through ordinary means. + Feature_Constant = 16, - /// Table can not be modified through ordinary means. - Feature_Constant = 16, + Feature_AllowTouch = 32 + }; - Feature_AllowTouch = 32 - }; + private: + unsigned int mFeatures; - private: + public: + IdTableBase(unsigned int features); - unsigned int mFeatures; + virtual QModelIndex getModelIndex(const std::string& id, int column) const = 0; - public: + /// Return index of column with the given \a id. If no such column exists, -1 is + /// returned. + virtual int searchColumnIndex(Columns::ColumnId id) const = 0; - IdTableBase (unsigned int features); + /// Return index of column with the given \a id. If no such column exists, an + /// exception is thrown. + virtual int findColumnIndex(Columns::ColumnId id) const = 0; - virtual QModelIndex getModelIndex (const std::string& id, int column) const = 0; + /// Return the UniversalId and the hint for viewing \a row. If viewing is not + /// supported by this table, return (UniversalId::Type_None, ""). + virtual std::pair view(int row) const = 0; - /// Return index of column with the given \a id. If no such column exists, -1 is - /// returned. - virtual int searchColumnIndex (Columns::ColumnId id) const = 0; + /// Is \a id flagged as deleted? + virtual bool isDeleted(const std::string& id) const = 0; - /// Return index of column with the given \a id. If no such column exists, an - /// exception is thrown. - virtual int findColumnIndex (Columns::ColumnId id) const = 0; + virtual int getColumnId(int column) const = 0; - /// Return the UniversalId and the hint for viewing \a row. If viewing is not - /// supported by this table, return (UniversalId::Type_None, ""). - virtual std::pair view (int row) const = 0; - - /// Is \a id flagged as deleted? - virtual bool isDeleted (const std::string& id) const = 0; - - virtual int getColumnId (int column) const = 0; - - unsigned int getFeatures() const; + unsigned int getFeatures() const; }; } diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 3e24f8d12..d4e342f5d 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -1,12 +1,25 @@ #include "idtableproxymodel.hpp" +#include +#include +#include +#include + +#include +#include #include +#include +#include + +#include "columnbase.hpp" #include "idtablebase.hpp" +class QObject; + namespace { - std::string getEnumValue(const std::vector> &values, int index) + std::string getEnumValue(const std::vector>& values, int index) { if (index < 0 || index >= static_cast(values.size())) { @@ -24,14 +37,13 @@ void CSMWorld::IdTableProxyModel::updateColumnMap() if (mFilter) { std::vector columns = mFilter->getReferencedColumns(); - for (std::vector::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter) - mColumnMap.insert (std::make_pair (*iter, - mSourceModel->searchColumnIndex (static_cast (*iter)))); + for (std::vector::const_iterator iter(columns.begin()); iter != columns.end(); ++iter) + mColumnMap.insert(std::make_pair( + *iter, mSourceModel->searchColumnIndex(static_cast(*iter)))); } } -bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) - const +bool CSMWorld::IdTableProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { Q_ASSERT(mSourceModel != nullptr); @@ -46,43 +58,34 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI if (!mFilter) return true; - return mFilter->test (*mSourceModel, sourceRow, mColumnMap); + return mFilter->test(*mSourceModel, sourceRow, mColumnMap); } -CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) - : QSortFilterProxyModel (parent), - mSourceModel(nullptr) +CSMWorld::IdTableProxyModel::IdTableProxyModel(QObject* parent) + : QSortFilterProxyModel(parent) + , mSourceModel(nullptr) { - setSortCaseSensitivity (Qt::CaseInsensitive); + setSortCaseSensitivity(Qt::CaseInsensitive); } -QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const +QModelIndex CSMWorld::IdTableProxyModel::getModelIndex(const std::string& id, int column) const { Q_ASSERT(mSourceModel != nullptr); - return mapFromSource(mSourceModel->getModelIndex (id, column)); + return mapFromSource(mSourceModel->getModelIndex(id, column)); } -void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model) +void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel* model) { QSortFilterProxyModel::setSourceModel(model); - mSourceModel = dynamic_cast(sourceModel()); - connect(mSourceModel, - SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, - SLOT(sourceRowsInserted(const QModelIndex &, int, int))); - connect(mSourceModel, - SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, - SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); - connect(mSourceModel, - SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), - this, - SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &))); + mSourceModel = dynamic_cast(sourceModel()); + connect(mSourceModel, &IdTableBase::rowsInserted, this, &IdTableProxyModel::sourceRowsInserted); + connect(mSourceModel, &IdTableBase::rowsRemoved, this, &IdTableProxyModel::sourceRowsRemoved); + connect(mSourceModel, &IdTableBase::dataChanged, this, &IdTableProxyModel::sourceDataChanged); } -void CSMWorld::IdTableProxyModel::setFilter (const std::shared_ptr& filter) +void CSMWorld::IdTableProxyModel::setFilter(const std::shared_ptr& filter) { beginResetModel(); mFilter = filter; @@ -90,7 +93,7 @@ void CSMWorld::IdTableProxyModel::setFilter (const std::shared_ptr(left.data(ColumnBase::Role_ColumnId).toInt()); EnumColumnCache::const_iterator valuesIt = mEnumColumnCache.find(id); @@ -121,11 +124,14 @@ QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const void CSMWorld::IdTableProxyModel::refreshFilter() { - updateColumnMap(); - invalidateFilter(); + if (mFilter) + { + updateColumnMap(); + invalidateFilter(); + } } -void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) +void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex& parent, int /*start*/, int end) { refreshFilter(); if (!parent.isValid()) @@ -134,12 +140,12 @@ void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, } } -void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex& /*parent*/, int /*start*/, int /*end*/) { refreshFilter(); } -void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/) +void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex& /*topLeft*/, const QModelIndex& /*bottomRight*/) { refreshFilter(); } diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 7e0563834..639cf4728 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -1,69 +1,76 @@ #ifndef CSM_WOLRD_IDTABLEPROXYMODEL_H #define CSM_WOLRD_IDTABLEPROXYMODEL_H -#include - #include +#include +#include +#include +#include +#include #include - -#include "../filter/node.hpp" +#include #include "columns.hpp" +class QObject; + +namespace CSMFilter +{ + class Node; +} + namespace CSMWorld { + class IdTableBase; + class IdTableProxyModel : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT - std::shared_ptr mFilter; - std::map mColumnMap; // column ID, column index in this model (or -1) + std::shared_ptr mFilter; + std::map mColumnMap; // column ID, column index in this model (or -1) - // Cache of enum values for enum columns (e.g. Modified, Record Type). - // Used to speed up comparisons during the sort by such columns. - typedef std::map> > EnumColumnCache; - mutable EnumColumnCache mEnumColumnCache; + // Cache of enum values for enum columns (e.g. Modified, Record Type). + // Used to speed up comparisons during the sort by such columns. + typedef std::map>> EnumColumnCache; + mutable EnumColumnCache mEnumColumnCache; - protected: + protected: + IdTableBase* mSourceModel; - IdTableBase *mSourceModel; + private: + void updateColumnMap(); - private: + public: + IdTableProxyModel(QObject* parent = nullptr); - void updateColumnMap(); + virtual QModelIndex getModelIndex(const std::string& id, int column) const; - public: + void setSourceModel(QAbstractItemModel* model) override; - IdTableProxyModel (QObject *parent = nullptr); + void setFilter(const std::shared_ptr& filter); - virtual QModelIndex getModelIndex (const std::string& id, int column) const; + void refreshFilter(); - void setSourceModel(QAbstractItemModel *model) override; + protected: + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; - void setFilter (const std::shared_ptr& filter); + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; - void refreshFilter(); + QString getRecordId(int sourceRow) const; - protected: + protected slots: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + virtual void sourceRowsInserted(const QModelIndex& parent, int start, int end); - bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const override; + virtual void sourceRowsRemoved(const QModelIndex& parent, int start, int end); - QString getRecordId(int sourceRow) const; + virtual void sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - protected slots: + signals: - virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); - - virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); - - virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - - signals: - - void rowAdded(const std::string &id); + void rowAdded(const std::string& id); }; } diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 1e3398bbb..da5c415ce 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -3,18 +3,22 @@ #include "nestedtablewrapper.hpp" #include "collectionbase.hpp" -#include "nestedcollection.hpp" #include "columnbase.hpp" +#include "nestedcollection.hpp" + +#include +#include + +#include // NOTE: parent class still needs idCollection -CSMWorld::IdTree::IdTree (NestedCollection *nestedCollection, CollectionBase *idCollection, unsigned int features) -: IdTable (idCollection, features), mNestedCollection (nestedCollection) -{} +CSMWorld::IdTree::IdTree(NestedCollection* nestedCollection, CollectionBase* idCollection, unsigned int features) + : IdTable(idCollection, features) + , mNestedCollection(nestedCollection) +{ +} -CSMWorld::IdTree::~IdTree() -{} - -int CSMWorld::IdTree::rowCount (const QModelIndex & parent) const +int CSMWorld::IdTree::rowCount(const QModelIndex& parent) const { if (hasChildren(parent)) return mNestedCollection->getNestedRowsCount(parent.row(), parent.column()); @@ -22,7 +26,7 @@ int CSMWorld::IdTree::rowCount (const QModelIndex & parent) const return IdTable::rowCount(parent); } -int CSMWorld::IdTree::columnCount (const QModelIndex & parent) const +int CSMWorld::IdTree::columnCount(const QModelIndex& parent) const { if (hasChildren(parent)) return mNestedCollection->getNestedColumnsCount(parent.row(), parent.column()); @@ -30,15 +34,15 @@ int CSMWorld::IdTree::columnCount (const QModelIndex & parent) const return IdTable::columnCount(parent); } -QVariant CSMWorld::IdTree::data (const QModelIndex & index, int role) const +QVariant CSMWorld::IdTree::data(const QModelIndex& index, int role) const { - if (!index.isValid()) - return QVariant(); + if (!index.isValid()) + return QVariant(); if (index.internalId() != 0) { std::pair parentAddress(unfoldIndexAddress(index.internalId())); - const NestableColumn *parentColumn = mNestedCollection->getNestableColumn(parentAddress.second); + const NestableColumn* parentColumn = mNestedCollection->getNestableColumn(parentAddress.second); if (role == ColumnBase::Role_Display) return parentColumn->nestedColumn(index.column()).mDisplayType; @@ -52,8 +56,7 @@ QVariant CSMWorld::IdTree::data (const QModelIndex & index, int role) const if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant(); - return mNestedCollection->getNestedData(parentAddress.first, - parentAddress.second, index.row(), index.column()); + return mNestedCollection->getNestedData(parentAddress.first, parentAddress.second, index.row(), index.column()); } else { @@ -66,35 +69,36 @@ QVariant CSMWorld::IdTree::nestedHeaderData(int section, int subSection, Qt::Ori if (section < 0 || section >= idCollection()->getColumns()) return QVariant(); - const NestableColumn *parentColumn = mNestedCollection->getNestableColumn(section); + const NestableColumn* parentColumn = mNestedCollection->getNestableColumn(section); - if (orientation==Qt::Vertical) + if (orientation == Qt::Vertical) return QVariant(); - if (role==Qt::DisplayRole) + if (role == Qt::DisplayRole) return tr(parentColumn->nestedColumn(subSection).getTitle().c_str()); - if (role==ColumnBase::Role_Flags) + if (role == ColumnBase::Role_Flags) return parentColumn->nestedColumn(subSection).mFlags; - if (role==ColumnBase::Role_Display) + if (role == ColumnBase::Role_Display) return parentColumn->nestedColumn(subSection).mDisplayType; - if (role==ColumnBase::Role_ColumnId) + if (role == ColumnBase::Role_ColumnId) return parentColumn->nestedColumn(subSection).mColumnId; return QVariant(); } -bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, int role) +bool CSMWorld::IdTree::setData(const QModelIndex& index, const QVariant& value, int role) { if (index.internalId() != 0) { - if (idCollection()->getColumn(parent(index).column()).isEditable() && role==Qt::EditRole) + if (idCollection()->getColumn(parent(index).column()).isEditable() && role == Qt::EditRole) { const std::pair& parentAddress(unfoldIndexAddress(index.internalId())); - mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column()); + mNestedCollection->setNestedData( + parentAddress.first, parentAddress.second, value, index.row(), index.column()); emit dataChanged(index, index); // Modifying a value can also change the Modified status of a record. @@ -113,7 +117,7 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, return IdTable::setData(index, value, role); } -Qt::ItemFlags CSMWorld::IdTree::flags (const QModelIndex & index) const +Qt::ItemFlags CSMWorld::IdTree::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::ItemFlags(); @@ -133,21 +137,21 @@ Qt::ItemFlags CSMWorld::IdTree::flags (const QModelIndex & index) const return IdTable::flags(index); } -bool CSMWorld::IdTree::removeRows (int row, int count, const QModelIndex& parent) +bool CSMWorld::IdTree::removeRows(int row, int count, const QModelIndex& parent) { if (parent.isValid()) { - beginRemoveRows (parent, row, row+count-1); + beginRemoveRows(parent, row, row + count - 1); for (int i = 0; i < count; ++i) { - mNestedCollection->removeNestedRows(parent.row(), parent.column(), row+i); + mNestedCollection->removeNestedRows(parent.row(), parent.column(), row + i); } endRemoveRows(); - emit dataChanged (CSMWorld::IdTree::index (parent.row(), 0), - CSMWorld::IdTree::index (parent.row(), idCollection()->getColumns()-1)); + emit dataChanged(CSMWorld::IdTree::index(parent.row(), 0), + CSMWorld::IdTree::index(parent.row(), idCollection()->getColumns() - 1)); return true; } @@ -166,11 +170,10 @@ void CSMWorld::IdTree::addNestedRow(const QModelIndex& parent, int position) mNestedCollection->addNestedRow(row, parent.column(), position); endInsertRows(); - emit dataChanged (CSMWorld::IdTree::index (row, 0), - CSMWorld::IdTree::index (row, idCollection()->getColumns()-1)); + emit dataChanged(CSMWorld::IdTree::index(row, 0), CSMWorld::IdTree::index(row, idCollection()->getColumns() - 1)); } -QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& parent) const +QModelIndex CSMWorld::IdTree::index(int row, int column, const QModelIndex& parent) const { unsigned int encodedId = 0; if (parent.isValid()) @@ -187,12 +190,12 @@ QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& par return createIndex(row, column, encodedId); // store internal id } -QModelIndex CSMWorld::IdTree::getNestedModelIndex (const std::string& id, int column) const +QModelIndex CSMWorld::IdTree::getNestedModelIndex(const std::string& id, int column) const { - return CSMWorld::IdTable::index(idCollection()->getIndex (id), column); + return CSMWorld::IdTable::index(idCollection()->getIndex(ESM::RefId::stringRefId(id)), column); } -QModelIndex CSMWorld::IdTree::parent (const QModelIndex& index) const +QModelIndex CSMWorld::IdTree::parent(const QModelIndex& index) const { if (index.internalId() == 0) // 0 is used for indexs with invalid parent (top level data) return QModelIndex(); @@ -206,14 +209,14 @@ QModelIndex CSMWorld::IdTree::parent (const QModelIndex& index) const return createIndex(address.first, address.second); } -unsigned int CSMWorld::IdTree::foldIndexAddress (const QModelIndex& index) const +unsigned int CSMWorld::IdTree::foldIndexAddress(const QModelIndex& index) const { unsigned int out = index.row() * this->columnCount(); out += index.column(); return ++out; } -std::pair< int, int > CSMWorld::IdTree::unfoldIndexAddress (unsigned int id) const +std::pair CSMWorld::IdTree::unfoldIndexAddress(unsigned int id) const { if (id == 0) throw std::runtime_error("Attempt to unfold index id of the top level data cell"); @@ -221,7 +224,7 @@ std::pair< int, int > CSMWorld::IdTree::unfoldIndexAddress (unsigned int id) con --id; int row = id / this->columnCount(); int column = id - row * this->columnCount(); - return std::make_pair (row, column); + return std::make_pair(row, column); } // FIXME: Not sure why this check is also needed? @@ -232,10 +235,8 @@ std::pair< int, int > CSMWorld::IdTree::unfoldIndexAddress (unsigned int id) con // Also see comments in refidadapter.hpp and refidadapterimp.hpp. bool CSMWorld::IdTree::hasChildren(const QModelIndex& index) const { - return (index.isValid() && - index.internalId() == 0 && - mNestedCollection->getNestableColumn(index.column())->hasChildren() && - index.data().isValid()); + return (index.isValid() && index.internalId() == 0 + && mNestedCollection->getNestableColumn(index.column())->hasChildren() && index.data().isValid()); } void CSMWorld::IdTree::setNestedTable(const QModelIndex& index, const CSMWorld::NestedTableWrapperBase& nestedTable) @@ -252,8 +253,8 @@ void CSMWorld::IdTree::setNestedTable(const QModelIndex& index, const CSMWorld:: mNestedCollection->setNestedTable(index.row(), index.column(), nestedTable); - emit dataChanged (CSMWorld::IdTree::index (index.row(), 0), - CSMWorld::IdTree::index (index.row(), idCollection()->getColumns()-1)); + emit dataChanged(CSMWorld::IdTree::index(index.row(), 0), + CSMWorld::IdTree::index(index.row(), idCollection()->getColumns() - 1)); if (removeRowsMode) { diff --git a/apps/opencs/model/world/idtree.hpp b/apps/opencs/model/world/idtree.hpp index c525a60b8..64e16e3dd 100644 --- a/apps/opencs/model/world/idtree.hpp +++ b/apps/opencs/model/world/idtree.hpp @@ -1,9 +1,15 @@ #ifndef CSM_WOLRD_IDTREE_H #define CSM_WOLRD_IDTREE_H -#include "idtable.hpp" -#include "universalid.hpp" #include "columns.hpp" +#include "idtable.hpp" + +#include +#include +#include + +#include +#include /*! \brief * Class for holding the model. Uses typical qt table abstraction/interface for granting access @@ -18,65 +24,61 @@ namespace CSMWorld { + class CollectionBase; class NestedCollection; - struct RecordBase; struct NestedTableWrapperBase; class IdTree : public IdTable { - Q_OBJECT + Q_OBJECT - private: + private: + NestedCollection* mNestedCollection; - NestedCollection *mNestedCollection; + unsigned int foldIndexAddress(const QModelIndex& index) const; + std::pair unfoldIndexAddress(unsigned int id) const; - // not implemented - IdTree (const IdTree&); - IdTree& operator= (const IdTree&); + public: + IdTree(NestedCollection* nestedCollection, CollectionBase* idCollection, unsigned int features = 0); + ///< The ownerships of \a nestedCollecton and \a idCollection are not transferred. + IdTree(const IdTree&) = delete; + IdTree& operator=(const IdTree&) = delete; + ~IdTree() override = default; - unsigned int foldIndexAddress(const QModelIndex& index) const; - std::pair unfoldIndexAddress(unsigned int id) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; - public: + int columnCount(const QModelIndex& parent = QModelIndex()) const override; - IdTree (NestedCollection *nestedCollection, CollectionBase *idCollection, unsigned int features = 0); - ///< The ownerships of \a nestedCollecton and \a idCollection are not transferred. + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - virtual ~IdTree(); + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; - int rowCount (const QModelIndex & parent = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; - int columnCount (const QModelIndex & parent = QModelIndex()) const override; + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; - QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + QModelIndex parent(const QModelIndex& index) const override; - Qt::ItemFlags flags (const QModelIndex & index) const override; + QModelIndex getNestedModelIndex(const std::string& id, int column) const; - bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()) override; + QVariant nestedHeaderData( + int section, int subSection, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const override; + NestedTableWrapperBase* nestedTable(const QModelIndex& index) const; - QModelIndex parent (const QModelIndex& index) const override; + void setNestedTable(const QModelIndex& index, const NestedTableWrapperBase& nestedTable); - QModelIndex getNestedModelIndex (const std::string& id, int column) const; + void addNestedRow(const QModelIndex& parent, int position); - QVariant nestedHeaderData(int section, int subSection, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool hasChildren(const QModelIndex& index) const override; - NestedTableWrapperBase* nestedTable(const QModelIndex &index) const; + virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or -1 if the requested column wasn't found. - void setNestedTable(const QModelIndex &index, const NestedTableWrapperBase& nestedTable); - - void addNestedRow (const QModelIndex& parent, int position); - - bool hasChildren (const QModelIndex& index) const override; - - virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); - ///< \return the column index or -1 if the requested column wasn't found. - - virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); - ///< \return the column index or throws an exception if the requested column wasn't found. + virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or throws an exception if the requested column wasn't found. signals: diff --git a/apps/opencs/model/world/info.hpp b/apps/opencs/model/world/info.hpp index 1bcb2dc2d..dd3741b26 100644 --- a/apps/opencs/model/world/info.hpp +++ b/apps/opencs/model/world/info.hpp @@ -1,13 +1,14 @@ #ifndef CSM_WOLRD_INFO_H #define CSM_WOLRD_INFO_H -#include +#include namespace CSMWorld { struct Info : public ESM::DialInfo { - std::string mTopicId; + ESM::RefId mTopicId; + ESM::RefId mOriginalId; }; } diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index be73aece6..7fc8a4e45 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -1,225 +1,141 @@ #include "infocollection.hpp" +#include +#include #include -#include +#include +#include -#include -#include +#include "components/debug/debuglog.hpp" +#include "components/esm3/infoorder.hpp" +#include "components/esm3/loaddial.hpp" +#include "components/esm3/loadinfo.hpp" -#include +#include "collection.hpp" +#include "info.hpp" -void CSMWorld::InfoCollection::load (const Info& record, bool base) +namespace CSMWorld { - int index = searchId (record.mId); + namespace + { + std::string_view getInfoTopicId(const ESM::RefId& infoId) + { + return parseInfoRefId(infoId).first; + } + } - if (index==-1) + ESM::RefId makeCompositeInfoRefId(const ESM::RefId& topicId, const ESM::RefId& infoId) + { + return ESM::RefId::stringRefId(topicId.getRefIdString() + '#' + infoId.getRefIdString()); + } +} + +void CSMWorld::InfoCollection::load(const Info& value, bool base) +{ + const int index = searchId(value.mId); + + if (index == -1) { // new record - Record record2; - record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record2.mBase : record2.mModified) = record; + auto record = std::make_unique>(); + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record->mBase : record->mModified) = value; - std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId); - - if (!record2.get().mPrev.empty()) - { - index = getInfoIndex (record2.get().mPrev, topic); - - if (index!=-1) - ++index; - } - - if (index==-1 && !record2.get().mNext.empty()) - { - index = getInfoIndex (record2.get().mNext, topic); - } - - if (index==-1) - { - Range range = getTopicRange (topic); - - index = std::distance (getRecords().begin(), range.second); - } - - insertRecord (record2, index); + insertRecord(std::move(record), getSize()); } else { // old record - Record record2 = getRecord (index); + auto record = std::make_unique>(getRecord(index)); if (base) - record2.mBase = record; + record->mBase = value; else - record2.setModified (record); + record->setModified(value); - setRecord (index, record2); + setRecord(index, std::move(record)); } } -int CSMWorld::InfoCollection::getInfoIndex (const std::string& id, const std::string& topic) const -{ - std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id; - - std::pair range = getTopicRange (topic); - - for (; range.first!=range.second; ++range.first) - if (Misc::StringUtils::ciEqual(range.first->get().mId, fullId)) - return std::distance (getRecords().begin(), range.first); - - return -1; -} - -int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const -{ - std::string::size_type separator = id.find_last_of ('#'); - - if (separator==std::string::npos) - throw std::runtime_error ("invalid info ID: " + id); - - std::pair range = getTopicRange (id.substr (0, separator)); - - if (range.first==range.second) - return Collection >::getAppendIndex (id, type); - - return std::distance (getRecords().begin(), range.second); -} - -bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector& newOrder) -{ - // check if the range is valid - int lastIndex = baseIndex + newOrder.size() -1; - - if (lastIndex>=getSize()) - return false; - - // Check that topics match - if (!Misc::StringUtils::ciEqual(getRecord(baseIndex).get().mTopicId, - getRecord(lastIndex).get().mTopicId)) - return false; - - // reorder - return reorderRowsImp (baseIndex, newOrder); -} - -void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) +void CSMWorld::InfoCollection::load( + ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfoOrderByTopic& infoOrders) { Info info; bool isDeleted = false; - info.load (reader, isDeleted); - std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" + info.mId; + info.load(reader, isDeleted); + + const ESM::RefId id = makeCompositeInfoRefId(dialogue.mId, info.mId); if (isDeleted) { - int index = searchId (id); + const int index = searchId(id); - if (index==-1) + if (index == -1) { - // deleting a record that does not exist - // ignore it for now - /// \todo report the problem to the user + Log(Debug::Warning) << "Trying to delete absent info \"" << info.mId << "\" from topic \"" << dialogue.mId + << "\""; + return; } - else if (base) + + if (base) { - removeRows (index, 1); - } - else - { - Record record = getRecord (index); - record.mState = RecordBase::State_Deleted; - setRecord (index, record); + infoOrders.at(dialogue.mId).removeInfo(info.mId); + removeRows(index, 1); + return; } + + auto record = std::make_unique>(getRecord(index)); + record->mState = RecordBase::State_Deleted; + setRecord(index, std::move(record)); + + return; } - else - { - info.mTopicId = dialogue.mId; - info.mId = id; - load (info, base); - } + + info.mTopicId = dialogue.mId; + info.mOriginalId = info.mId; + info.mId = id; + + load(info, base); + + infoOrders[dialogue.mId].insertInfo(OrderedInfo(info), isDeleted); } -CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic) - const +void CSMWorld::InfoCollection::sort(const InfoOrderByTopic& infoOrders) { - std::string topic2 = Misc::StringUtils::lowerCase (topic); - - std::map::const_iterator iter = getIdMap().lower_bound (topic2); - - // Skip invalid records: The beginning of a topic string could be identical to another topic - // string. - for (; iter!=getIdMap().end(); ++iter) - { - std::string testTopicId = - Misc::StringUtils::lowerCase (getRecord (iter->second).get().mTopicId); - - if (testTopicId==topic2) - break; - - std::size_t size = topic2.size(); - - if (testTopicId.size()second; - - while (begin != getRecords().begin()) - { - if (!Misc::StringUtils::ciEqual(begin->get().mTopicId, topic2)) - { - // we've gone one too far, go back - ++begin; - break; - } - --begin; - } - - // Find end - RecordConstIterator end = begin; - - for (; end!=getRecords().end(); ++end) - if (!Misc::StringUtils::ciEqual(end->get().mTopicId, topic2)) - break; - - return Range (begin, end); + std::vector order; + order.reserve(getSize()); + for (const auto& [topicId, infoOrder] : infoOrders) + for (const OrderedInfo& info : infoOrder.getOrderedInfo()) + order.push_back(getIndex(makeCompositeInfoRefId(topicId, info.mId))); + reorderRowsImp(order); } -void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId) +CSMWorld::InfosRecordPtrByTopic CSMWorld::InfoCollection::getInfosByTopic() const { - std::string id = Misc::StringUtils::lowerCase(dialogueId); - std::vector erasedRecords; - - std::map::const_iterator current = getIdMap().lower_bound(id); - std::map::const_iterator end = getIdMap().end(); - for (; current != end; ++current) - { - Record record = getRecord(current->second); - - if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) - { - if (record.mState == RecordBase::State_ModifiedOnly) - { - erasedRecords.push_back(current->second); - } - else - { - record.mState = RecordBase::State_Deleted; - setRecord(current->second, record); - } - } - else - { - break; - } - } - - while (!erasedRecords.empty()) - { - removeRows(erasedRecords.back(), 1); - erasedRecords.pop_back(); - } + InfosRecordPtrByTopic result; + for (const std::unique_ptr>& record : getRecords()) + result[record->get().mTopicId].push_back(record.get()); + return result; +} + +int CSMWorld::InfoCollection::getAppendIndex(const ESM::RefId& id, UniversalId::Type /*type*/) const +{ + const auto lessByTopicId + = [](std::string_view lhs, const std::unique_ptr>& rhs) { return lhs < rhs->get().mTopicId; }; + const auto it = std::upper_bound(getRecords().begin(), getRecords().end(), getInfoTopicId(id), lessByTopicId); + return static_cast(it - getRecords().begin()); +} + +bool CSMWorld::InfoCollection::reorderRows(int baseIndex, const std::vector& newOrder) +{ + const int lastIndex = baseIndex + static_cast(newOrder.size()) - 1; + + if (lastIndex >= getSize()) + return false; + + if (getRecord(baseIndex).get().mTopicId != getRecord(lastIndex).get().mTopicId) + return false; + + return reorderRowsImp(baseIndex, newOrder); } diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 8f5aea601..a26bd50df 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -1,52 +1,62 @@ #ifndef CSM_WOLRD_INFOCOLLECTION_H #define CSM_WOLRD_INFOCOLLECTION_H +#include +#include +#include +#include + #include "collection.hpp" #include "info.hpp" namespace ESM { struct Dialogue; + class ESMReader; + + template + class InfoOrder; } namespace CSMWorld { - class InfoCollection : public Collection > + using InfosRecordPtrByTopic = std::unordered_map*>>; + + struct OrderedInfo { - public: + ESM::RefId mId; + ESM::RefId mNext; + ESM::RefId mPrev; - typedef std::vector >::const_iterator RecordConstIterator; - typedef std::pair Range; - - private: - - void load (const Info& record, bool base); - - int getInfoIndex (const std::string& id, const std::string& topic) const; - ///< Return index for record \a id or -1 (if not present; deleted records are considered) - /// - /// \param id info ID without topic prefix - - public: - - int getAppendIndex (const std::string& id, - UniversalId::Type type = UniversalId::Type_None) const override; - ///< \param type Will be ignored, unless the collection supports multiple record types - - bool reorderRows (int baseIndex, const std::vector& newOrder) override; - ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices - /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). - /// - /// \return Success? - - void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue); - - Range getTopicRange (const std::string& topic) const; - ///< Return iterators that point to the beginning and past the end of the range for - /// the given topic. - - void removeDialogueInfos(const std::string& dialogueId); + explicit OrderedInfo(const Info& info) + : mId(info.mOriginalId) + , mNext(info.mNext) + , mPrev(info.mPrev) + { + } }; + + using InfoOrder = ESM::InfoOrder; + using InfoOrderByTopic = std::map>; + + class InfoCollection : public Collection + { + private: + void load(const Info& value, bool base); + + public: + void load(ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfoOrderByTopic& infoOrder); + + void sort(const InfoOrderByTopic& infoOrders); + + InfosRecordPtrByTopic getInfosByTopic() const; + + int getAppendIndex(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) const override; + + bool reorderRows(int baseIndex, const std::vector& newOrder) override; + }; + + ESM::RefId makeCompositeInfoRefId(const ESM::RefId& topicId, const ESM::RefId& infoId); } #endif diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index 3200d39fc..fb1adf16d 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -4,6 +4,8 @@ #include #include +#include + const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; @@ -11,8 +13,7 @@ const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; -const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = -{ +const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = { "Rank Low", "Rank High", "Rank Requirement", @@ -98,26 +99,24 @@ const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = "Not Race", "Not Cell", "Not Local", - 0 + nullptr, }; -const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = -{ +const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = { "=", "!=", ">", ">=", "<", "<=", - 0 + nullptr, }; -const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = -{ +const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = { "Boolean", "Integer", "Numeric", - 0 + nullptr, }; // static functions @@ -298,18 +297,42 @@ void CSMWorld::ConstInfoSelectWrapper::readFunctionName() mFunctionName = Function_None; break; - case '2': mFunctionName = Function_Global; break; - case '3': mFunctionName = Function_Local; break; - case '4': mFunctionName = Function_Journal; break; - case '5': mFunctionName = Function_Item; break; - case '6': mFunctionName = Function_Dead; break; - case '7': mFunctionName = Function_NotId; break; - case '8': mFunctionName = Function_NotFaction; break; - case '9': mFunctionName = Function_NotClass; break; - case 'A': mFunctionName = Function_NotRace; break; - case 'B': mFunctionName = Function_NotCell; break; - case 'C': mFunctionName = Function_NotLocal; break; - default: mFunctionName = Function_None; break; + case '2': + mFunctionName = Function_Global; + break; + case '3': + mFunctionName = Function_Local; + break; + case '4': + mFunctionName = Function_Journal; + break; + case '5': + mFunctionName = Function_Item; + break; + case '6': + mFunctionName = Function_Dead; + break; + case '7': + mFunctionName = Function_NotId; + break; + case '8': + mFunctionName = Function_NotFaction; + break; + case '9': + mFunctionName = Function_NotClass; + break; + case 'A': + mFunctionName = Function_NotRace; + break; + case 'B': + mFunctionName = Function_NotCell; + break; + case 'C': + mFunctionName = Function_NotLocal; + break; + default: + mFunctionName = Function_None; + break; } } @@ -319,13 +342,26 @@ void CSMWorld::ConstInfoSelectWrapper::readRelationType() switch (relationIndex) { - case '0': mRelationType = Relation_Equal; break; - case '1': mRelationType = Relation_NotEqual; break; - case '2': mRelationType = Relation_Greater; break; - case '3': mRelationType = Relation_GreaterOrEqual; break; - case '4': mRelationType = Relation_Less; break; - case '5': mRelationType = Relation_LessOrEqual; break; - default: mRelationType = Relation_None; + case '0': + mRelationType = Relation_Equal; + break; + case '1': + mRelationType = Relation_NotEqual; + break; + case '2': + mRelationType = Relation_Greater; + break; + case '3': + mRelationType = Relation_GreaterOrEqual; + break; + case '4': + mRelationType = Relation_Less; + break; + case '5': + mRelationType = Relation_LessOrEqual; + break; + default: + mRelationType = Relation_None; } } @@ -700,38 +736,37 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() c } template -bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const +bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const { return (value >= range.first && value <= range.second); } template -bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains(std::pair containingRange, - std::pair testRange) const +bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains( + std::pair containingRange, std::pair testRange) const { return (containingRange.first <= testRange.first) && (testRange.second <= containingRange.second); } template -bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const +bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const { // One of the bounds of either range should fall within the other range - return - (range1.first <= range2.first && range2.first <= range1.second) || - (range1.first <= range2.second && range2.second <= range1.second) || - (range2.first <= range1.first && range1.first <= range2.second) || - (range2.first <= range1.second && range1.second <= range2.second); + return (range1.first <= range2.first && range2.first <= range1.second) + || (range1.first <= range2.second && range2.second <= range1.second) + || (range2.first <= range1.first && range1.first <= range2.second) + || (range2.first <= range1.second && range1.second <= range2.second); } template -bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const +bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const { - return (range1.first == range2.first && range1.second == range2.second); + return (range1.first == range2.first && range1.second == range2.second); } template -bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair conditionRange, - std::pair validRange) const +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue( + std::pair conditionRange, std::pair validRange) const { switch (mRelationType) { @@ -757,8 +792,8 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair co } template -bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair conditionRange, - std::pair validRange) const +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue( + std::pair conditionRange, std::pair validRange) const { switch (mRelationType) { @@ -785,7 +820,8 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair con // InfoSelectWrapper CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) - : CSMWorld::ConstInfoSelectWrapper(select), mSelect(select) + : CSMWorld::ConstInfoSelectWrapper(select) + , mSelect(select) { } @@ -846,19 +882,46 @@ void CSMWorld::InfoSelectWrapper::update() switch (mFunctionName) { - case Function_None: stream << '0'; break; - case Function_Global: stream << '2'; break; - case Function_Local: stream << '3'; break; - case Function_Journal: stream << '4'; break; - case Function_Item: stream << '5'; break; - case Function_Dead: stream << '6'; break; - case Function_NotId: stream << '7'; break; - case Function_NotFaction: stream << '8'; break; - case Function_NotClass: stream << '9'; break; - case Function_NotRace: stream << 'A'; break; - case Function_NotCell: stream << 'B'; break; - case Function_NotLocal: stream << 'C'; break; - default: stream << '1'; writeIndex = true; break; + case Function_None: + stream << '0'; + break; + case Function_Global: + stream << '2'; + break; + case Function_Local: + stream << '3'; + break; + case Function_Journal: + stream << '4'; + break; + case Function_Item: + stream << '5'; + break; + case Function_Dead: + stream << '6'; + break; + case Function_NotId: + stream << '7'; + break; + case Function_NotFaction: + stream << '8'; + break; + case Function_NotClass: + stream << '9'; + break; + case Function_NotRace: + stream << 'A'; + break; + case Function_NotCell: + stream << 'B'; + break; + case Function_NotLocal: + stream << 'C'; + break; + default: + stream << '1'; + writeIndex = true; + break; } if (writeIndex && functionIndex < 10) // leading 0 @@ -871,13 +934,27 @@ void CSMWorld::InfoSelectWrapper::update() // Write Relation switch (mRelationType) { - case Relation_Equal: stream << '0'; break; - case Relation_NotEqual: stream << '1'; break; - case Relation_Greater: stream << '2'; break; - case Relation_GreaterOrEqual: stream << '3'; break; - case Relation_Less: stream << '4'; break; - case Relation_LessOrEqual: stream << '5'; break; - default: stream << '0'; break; + case Relation_Equal: + stream << '0'; + break; + case Relation_NotEqual: + stream << '1'; + break; + case Relation_Greater: + stream << '2'; + break; + case Relation_GreaterOrEqual: + stream << '3'; + break; + case Relation_Less: + stream << '4'; + break; + case Relation_LessOrEqual: + stream << '5'; + break; + default: + stream << '0'; + break; } if (mHasVariable) diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index ce26a46dc..4ecdc676d 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -1,7 +1,16 @@ #ifndef CSM_WORLD_INFOSELECTWRAPPER_H #define CSM_WORLD_INFOSELECTWRAPPER_H -#include +#include +#include +#include + +#include + +namespace ESM +{ + class Variant; +} namespace CSMWorld { @@ -20,11 +29,10 @@ namespace CSMWorld class ConstInfoSelectWrapper { public: - // Order matters enum FunctionName { - Function_RankLow=0, + Function_RankLow = 0, Function_RankHigh, Function_RankRequirement, Function_Reputation, @@ -97,7 +105,7 @@ namespace CSMWorld Function_Flee, Function_ShouldAttack, Function_Werewolf, - Function_PcWerewolfKills=73, + Function_PcWerewolfKills = 73, Function_Global, Function_Local, @@ -168,7 +176,6 @@ namespace CSMWorld std::string toString() const; protected: - void readRule(); void readFunctionName(); void readRelationType(); @@ -183,22 +190,22 @@ namespace CSMWorld std::pair getValidFloatRange() const; template - bool rangeContains(Type1 value, std::pair range) const; + bool rangeContains(Type1 value, std::pair range) const; template - bool rangesOverlap(std::pair range1, std::pair range2) const; + bool rangesOverlap(std::pair range1, std::pair range2) const; template - bool rangeFullyContains(std::pair containing, std::pair test) const; + bool rangeFullyContains(std::pair containing, std::pair test) const; template - bool rangesMatch(std::pair range1, std::pair range2) const; + bool rangesMatch(std::pair range1, std::pair range2) const; template - bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; + bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; template - bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; + bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; FunctionName mFunctionName; RelationType mRelationType; @@ -208,7 +215,6 @@ namespace CSMWorld std::string mVariableName; private: - const ESM::DialInfo::SelectStruct& mConstSelect; }; @@ -216,7 +222,6 @@ namespace CSMWorld class InfoSelectWrapper : public ConstInfoSelectWrapper { public: - InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); // Wrapped SelectStruct will not be modified until update() is called @@ -233,7 +238,6 @@ namespace CSMWorld ESM::Variant& getVariant(); private: - ESM::DialInfo::SelectStruct& mSelect; void writeRule(); diff --git a/apps/opencs/model/world/infotableproxymodel.cpp b/apps/opencs/model/world/infotableproxymodel.cpp index 7c6a9c323..171fbd9ff 100644 --- a/apps/opencs/model/world/infotableproxymodel.cpp +++ b/apps/opencs/model/world/infotableproxymodel.cpp @@ -1,30 +1,38 @@ #include "infotableproxymodel.hpp" -#include +#include +#include +#include + +#include +#include + +#include + +#include -#include "idtablebase.hpp" #include "columns.hpp" +#include "idtablebase.hpp" namespace { - QString toLower(const QString &str) + QString toLower(const QString& str) { return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toUtf8().constData()).c_str()); } } -CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent) - : IdTableProxyModel(parent), - mType(type), - mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : - Columns::ColumnId_Journal), - mInfoColumnIndex(-1), - mLastAddedSourceRow(-1) +CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject* parent) + : IdTableProxyModel(parent) + , mType(type) + , mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : Columns::ColumnId_Journal) + , mInfoColumnIndex(-1) + , mLastAddedSourceRow(-1) { Q_ASSERT(type == UniversalId::Type_TopicInfos || type == UniversalId::Type_JournalInfos); } -void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel* sourceModel) { IdTableProxyModel::setSourceModel(sourceModel); @@ -35,7 +43,7 @@ void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceMod } } -bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { Q_ASSERT(mSourceModel != nullptr); @@ -63,21 +71,21 @@ int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const return mFirstRowCache[info]; } - while (--row >= 0 && - toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()) == info); + while (--row >= 0 && toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()) == info) + ; ++row; mFirstRowCache[info] = row; return row; } -void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex& /*parent*/, int /*start*/, int /*end*/) { refreshFilter(); mFirstRowCache.clear(); } -void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) +void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex& parent, int /*start*/, int end) { refreshFilter(); @@ -90,19 +98,18 @@ void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &parent } } -void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { refreshFilter(); - if (mLastAddedSourceRow != -1 && - topLeft.row() <= mLastAddedSourceRow && bottomRight.row() >= mLastAddedSourceRow) + if (mLastAddedSourceRow != -1 && topLeft.row() <= mLastAddedSourceRow && bottomRight.row() >= mLastAddedSourceRow) { - // Now the topic of the last added row is set, + // Now the topic of the last added row is set, // so we can re-sort the model to ensure the corrent position of this row int column = sortColumn(); Qt::SortOrder order = sortOrder(); sort(mInfoColumnIndex); // Restore the correct position of an added row - sort(column, order); // Restore the original sort order + sort(column, order); // Restore the original sort order emit rowAdded(getRecordId(mLastAddedSourceRow).toUtf8().constData()); // Make sure that we perform a re-sorting only in the first dataChanged() after a row insertion diff --git a/apps/opencs/model/world/infotableproxymodel.hpp b/apps/opencs/model/world/infotableproxymodel.hpp index 92afdabdc..63f67e859 100644 --- a/apps/opencs/model/world/infotableproxymodel.hpp +++ b/apps/opencs/model/world/infotableproxymodel.hpp @@ -3,42 +3,44 @@ #include -#include "idtableproxymodel.hpp" #include "columns.hpp" +#include "idtableproxymodel.hpp" #include "universalid.hpp" +class QAbstractItemModel; +class QModelIndex; +class QObject; + namespace CSMWorld { - class IdTableBase; - class InfoTableProxyModel : public IdTableProxyModel { - Q_OBJECT + Q_OBJECT - UniversalId::Type mType; - Columns::ColumnId mInfoColumnId; - ///< Contains ID for Topic or Journal ID - int mInfoColumnIndex; - int mLastAddedSourceRow; + UniversalId::Type mType; + Columns::ColumnId mInfoColumnId; + ///< Contains ID for Topic or Journal ID + int mInfoColumnIndex; + int mLastAddedSourceRow; - mutable QHash mFirstRowCache; + mutable QHash mFirstRowCache; - int getFirstInfoRow(int currentRow) const; - ///< Finds the first row with the same topic (journal entry) as in \a currentRow - ///< \a currentRow is a row of the source model. + int getFirstInfoRow(int currentRow) const; + ///< Finds the first row with the same topic (journal entry) as in \a currentRow + ///< \a currentRow is a row of the source model. - public: - InfoTableProxyModel(UniversalId::Type type, QObject *parent = nullptr); + public: + InfoTableProxyModel(UniversalId::Type type, QObject* parent = nullptr); - void setSourceModel(QAbstractItemModel *sourceModel) override; + void setSourceModel(QAbstractItemModel* sourceModel) override; - protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + protected: + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; - protected slots: - void sourceRowsInserted(const QModelIndex &parent, int start, int end) override; - void sourceRowsRemoved(const QModelIndex &parent, int start, int end) override; - void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) override; + protected slots: + void sourceRowsInserted(const QModelIndex& parent, int start, int end) override; + void sourceRowsRemoved(const QModelIndex& parent, int start, int end) override; + void sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override; }; } diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index bfa927444..0a8a4a2aa 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -3,9 +3,16 @@ #include #include +#include + +namespace ESM +{ + class ESMReader; +} + namespace CSMWorld { - void Land::load(ESM::ESMReader &esm, bool &isDeleted) + void Land::load(ESM::ESMReader& esm, bool& isDeleted) { ESM::Land::load(esm, isDeleted); } @@ -24,7 +31,7 @@ namespace CSMWorld if (mid == std::string::npos || id[0] != '#') throw std::runtime_error("Invalid Land ID"); - x = std::stoi(id.substr(1, mid - 1)); - y = std::stoi(id.substr(mid + 1)); + x = Misc::StringUtils::toNumeric(id.substr(1, mid - 1), 0); + y = Misc::StringUtils::toNumeric(id.substr(mid + 1), 0); } } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index e604f1311..69a3e1b12 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -3,7 +3,12 @@ #include -#include +#include + +namespace ESM +{ + class ESMReader; +} namespace CSMWorld { @@ -13,7 +18,7 @@ namespace CSMWorld struct Land : public ESM::Land { /// Loads the metadata and ID - void load (ESM::ESMReader &esm, bool &isDeleted); + void load(ESM::ESMReader& esm, bool& isDeleted); static std::string createUniqueRecordId(int x, int y); static void parseUniqueRecordId(const std::string& id, int& x, int& y); diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 43deb64a4..ecf370c28 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -3,11 +3,12 @@ #include #include -#include +#include +#include namespace CSMWorld { - void LandTexture::load(ESM::ESMReader &esm, bool &isDeleted) + void LandTexture::load(ESM::ESMReader& esm, bool& isDeleted) { ESM::LandTexture::load(esm, isDeleted); @@ -28,7 +29,7 @@ namespace CSMWorld if (middle == std::string::npos || id[0] != 'L') throw std::runtime_error("Invalid LandTexture ID"); - plugin = std::stoi(id.substr(1,middle-1)); - index = std::stoi(id.substr(middle+1)); + plugin = Misc::StringUtils::toNumeric(id.substr(1, middle - 1), 0); + index = Misc::StringUtils::toNumeric(id.substr(middle + 1), 0); } } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index a7376438c..d3cac5d3d 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -3,7 +3,12 @@ #include -#include +#include + +namespace ESM +{ + class ESMReader; +} namespace CSMWorld { @@ -12,7 +17,7 @@ namespace CSMWorld { int mPluginIndex; - void load (ESM::ESMReader &esm, bool &isDeleted); + void load(ESM::ESMReader& esm, bool& isDeleted); /// Returns a string identifier that will be unique to any LandTexture. static std::string createUniqueRecordId(int plugin, int index); diff --git a/apps/opencs/model/world/landtexturetableproxymodel.cpp b/apps/opencs/model/world/landtexturetableproxymodel.cpp index e064bbe8a..438dba467 100644 --- a/apps/opencs/model/world/landtexturetableproxymodel.cpp +++ b/apps/opencs/model/world/landtexturetableproxymodel.cpp @@ -1,6 +1,6 @@ #include "landtexturetableproxymodel.hpp" -#include "idtable.hpp" +#include namespace CSMWorld { @@ -9,7 +9,7 @@ namespace CSMWorld { } - bool LandTextureTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const + bool LandTextureTableProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { return IdTableProxyModel::filterAcceptsRow(sourceRow, sourceParent); } diff --git a/apps/opencs/model/world/landtexturetableproxymodel.hpp b/apps/opencs/model/world/landtexturetableproxymodel.hpp index bbedecb53..b53c35c23 100644 --- a/apps/opencs/model/world/landtexturetableproxymodel.hpp +++ b/apps/opencs/model/world/landtexturetableproxymodel.hpp @@ -3,19 +3,20 @@ #include "idtableproxymodel.hpp" +class QModelIndex; +class QObject; + namespace CSMWorld { /// \brief Removes base records from filtered results. class LandTextureTableProxyModel : public IdTableProxyModel { - Q_OBJECT - public: + Q_OBJECT + public: + LandTextureTableProxyModel(QObject* parent = nullptr); - LandTextureTableProxyModel(QObject* parent = nullptr); - - protected: - - bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const override; + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; }; } diff --git a/apps/opencs/model/world/metadata.cpp b/apps/opencs/model/world/metadata.cpp index b2fa3487c..c1c6325e3 100644 --- a/apps/opencs/model/world/metadata.cpp +++ b/apps/opencs/model/world/metadata.cpp @@ -1,26 +1,28 @@ #include "metadata.hpp" -#include -#include -#include +#include +#include +#include void CSMWorld::MetaData::blank() { - mFormat = ESM::Header::CurrentFormat; + // ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs + // we use the format `0` for compatibility with old versions. + mFormatVersion = ESM::DefaultFormatVersion; mAuthor.clear(); mDescription.clear(); } -void CSMWorld::MetaData::load (ESM::ESMReader& esm) +void CSMWorld::MetaData::load(ESM::ESMReader& esm) { - mFormat = esm.getHeader().mFormat; + mFormatVersion = esm.getHeader().mFormatVersion; mAuthor = esm.getHeader().mData.author; mDescription = esm.getHeader().mData.desc; } -void CSMWorld::MetaData::save (ESM::ESMWriter& esm) const +void CSMWorld::MetaData::save(ESM::ESMWriter& esm) const { - esm.setFormat (mFormat); - esm.setAuthor (mAuthor); - esm.setDescription (mDescription); + esm.setFormatVersion(mFormatVersion); + esm.setAuthor(mAuthor); + esm.setDescription(mDescription); } diff --git a/apps/opencs/model/world/metadata.hpp b/apps/opencs/model/world/metadata.hpp index f8df2690e..963ca6453 100644 --- a/apps/opencs/model/world/metadata.hpp +++ b/apps/opencs/model/world/metadata.hpp @@ -1,6 +1,9 @@ #ifndef CSM_WOLRD_METADATA_H #define CSM_WOLRD_METADATA_H +#include +#include + #include namespace ESM @@ -13,16 +16,18 @@ namespace CSMWorld { struct MetaData { - std::string mId; + static constexpr std::string_view getRecordType() { return "MetaData"; } - int mFormat; + ESM::RefId mId; + + ESM::FormatVersion mFormatVersion; std::string mAuthor; std::string mDescription; void blank(); - void load (ESM::ESMReader& esm); - void save (ESM::ESMWriter& esm) const; + void load(ESM::ESMReader& esm); + void save(ESM::ESMWriter& esm) const; }; } diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index e8b4102d7..9572d8de3 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -1,17 +1,30 @@ #include "nestedcoladapterimp.hpp" -#include -#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include -#include "idcollection.hpp" -#include "pathgrid.hpp" #include "info.hpp" #include "infoselectwrapper.hpp" +#include "pathgrid.hpp" namespace CSMWorld { - PathgridPointListAdapter::PathgridPointListAdapter () {} - void PathgridPointListAdapter::addRow(Record& record, int position) const { Pathgrid pathgrid = record.get(); @@ -27,10 +40,10 @@ namespace CSMWorld point.mConnectionNum = 0; point.mUnknown = 0; - points.insert(points.begin()+position, point); + points.insert(points.begin() + position, point); pathgrid.mData.mS2 = pathgrid.mPoints.size(); - record.setModified (pathgrid); + record.setModified(pathgrid); } void PathgridPointListAdapter::removeRow(Record& record, int rowToRemove) const @@ -39,25 +52,24 @@ namespace CSMWorld ESM::Pathgrid::PointList& points = pathgrid.mPoints; - if (rowToRemove < 0 || rowToRemove >= static_cast (points.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(points.size())) + throw std::runtime_error("index out of range"); // Do not remove dangling edges, does not work with current undo mechanism // Do not automatically adjust indices, what would be done with dangling edges? - points.erase(points.begin()+rowToRemove); + points.erase(points.begin() + rowToRemove); pathgrid.mData.mS2 = pathgrid.mPoints.size(); - record.setModified (pathgrid); + record.setModified(pathgrid); } - void PathgridPointListAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void PathgridPointListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { Pathgrid pathgrid = record.get(); - pathgrid.mPoints = static_cast &>(nestedTable).mNestedTable; + pathgrid.mPoints = static_cast&>(nestedTable).mNestedTable; pathgrid.mData.mS2 = pathgrid.mPoints.size(); - record.setModified (pathgrid); + record.setModified(pathgrid); } NestedTableWrapperBase* PathgridPointListAdapter::table(const Record& record) const @@ -66,37 +78,49 @@ namespace CSMWorld return new NestedTableWrapper(record.get().mPoints); } - QVariant PathgridPointListAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant PathgridPointListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Pathgrid::Point point = record.get().mPoints[subRowIndex]; switch (subColIndex) { - case 0: return subRowIndex; - case 1: return point.mX; - case 2: return point.mY; - case 3: return point.mZ; - default: throw std::runtime_error("Pathgrid point subcolumn index out of range"); + case 0: + return subRowIndex; + case 1: + return point.mX; + case 2: + return point.mY; + case 3: + return point.mZ; + default: + throw std::runtime_error("Pathgrid point subcolumn index out of range"); } } - void PathgridPointListAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void PathgridPointListAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { Pathgrid pathgrid = record.get(); ESM::Pathgrid::Point point = pathgrid.mPoints[subRowIndex]; switch (subColIndex) { - case 0: return; // return without saving - case 1: point.mX = value.toInt(); break; - case 2: point.mY = value.toInt(); break; - case 3: point.mZ = value.toInt(); break; - default: throw std::runtime_error("Pathgrid point subcolumn index out of range"); + case 0: + return; // return without saving + case 1: + point.mX = value.toInt(); + break; + case 2: + point.mY = value.toInt(); + break; + case 3: + point.mZ = value.toInt(); + break; + default: + throw std::runtime_error("Pathgrid point subcolumn index out of range"); } pathgrid.mPoints[subRowIndex] = point; - record.setModified (pathgrid); + record.setModified(pathgrid); } int PathgridPointListAdapter::getColumnsCount(const Record& record) const @@ -109,8 +133,6 @@ namespace CSMWorld return static_cast(record.get().mPoints.size()); } - PathgridEdgeListAdapter::PathgridEdgeListAdapter () {} - void PathgridEdgeListAdapter::addRow(Record& record, int position) const { Pathgrid pathgrid = record.get(); @@ -127,9 +149,9 @@ namespace CSMWorld // // Currently the code assumes that the end user to know what he/she is doing. // e.g. Edges come in pairs, from points a->b and b->a - edges.insert(edges.begin()+position, edge); + edges.insert(edges.begin() + position, edge); - record.setModified (pathgrid); + record.setModified(pathgrid); } void PathgridEdgeListAdapter::removeRow(Record& record, int rowToRemove) const @@ -138,23 +160,21 @@ namespace CSMWorld ESM::Pathgrid::EdgeList& edges = pathgrid.mEdges; - if (rowToRemove < 0 || rowToRemove >= static_cast (edges.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(edges.size())) + throw std::runtime_error("index out of range"); - edges.erase(edges.begin()+rowToRemove); + edges.erase(edges.begin() + rowToRemove); - record.setModified (pathgrid); + record.setModified(pathgrid); } - void PathgridEdgeListAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void PathgridEdgeListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { Pathgrid pathgrid = record.get(); - pathgrid.mEdges = - static_cast &>(nestedTable).mNestedTable; + pathgrid.mEdges = static_cast&>(nestedTable).mNestedTable; - record.setModified (pathgrid); + record.setModified(pathgrid); } NestedTableWrapperBase* PathgridEdgeListAdapter::table(const Record& record) const @@ -163,44 +183,53 @@ namespace CSMWorld return new NestedTableWrapper(record.get().mEdges); } - QVariant PathgridEdgeListAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant PathgridEdgeListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Pathgrid pathgrid = record.get(); - if (subRowIndex < 0 || subRowIndex >= static_cast (pathgrid.mEdges.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(pathgrid.mEdges.size())) + throw std::runtime_error("index out of range"); ESM::Pathgrid::Edge edge = pathgrid.mEdges[subRowIndex]; switch (subColIndex) { - case 0: return subRowIndex; - case 1: return edge.mV0; - case 2: return edge.mV1; - default: throw std::runtime_error("Pathgrid edge subcolumn index out of range"); + case 0: + return subRowIndex; + case 1: + return static_cast(edge.mV0); + case 2: + return static_cast(edge.mV1); + default: + throw std::runtime_error("Pathgrid edge subcolumn index out of range"); } } - void PathgridEdgeListAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void PathgridEdgeListAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { Pathgrid pathgrid = record.get(); - if (subRowIndex < 0 || subRowIndex >= static_cast (pathgrid.mEdges.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(pathgrid.mEdges.size())) + throw std::runtime_error("index out of range"); ESM::Pathgrid::Edge edge = pathgrid.mEdges[subRowIndex]; switch (subColIndex) { - case 0: return; // return without saving - case 1: edge.mV0 = value.toInt(); break; - case 2: edge.mV1 = value.toInt(); break; - default: throw std::runtime_error("Pathgrid edge subcolumn index out of range"); + case 0: + return; // return without saving + case 1: + edge.mV0 = value.toInt(); + break; + case 2: + edge.mV1 = value.toInt(); + break; + default: + throw std::runtime_error("Pathgrid edge subcolumn index out of range"); } pathgrid.mEdges[subRowIndex] = edge; - record.setModified (pathgrid); + record.setModified(pathgrid); } int PathgridEdgeListAdapter::getColumnsCount(const Record& record) const @@ -213,96 +242,97 @@ namespace CSMWorld return static_cast(record.get().mEdges.size()); } - FactionReactionsAdapter::FactionReactionsAdapter () {} - void FactionReactionsAdapter::addRow(Record& record, int position) const { ESM::Faction faction = record.get(); - std::map& reactions = faction.mReactions; + std::map& reactions = faction.mReactions; // blank row - reactions.insert(std::make_pair("", 0)); + reactions.insert(std::make_pair(ESM::RefId(), 0)); - record.setModified (faction); + record.setModified(faction); } void FactionReactionsAdapter::removeRow(Record& record, int rowToRemove) const { ESM::Faction faction = record.get(); - std::map& reactions = faction.mReactions; + std::map& reactions = faction.mReactions; - if (rowToRemove < 0 || rowToRemove >= static_cast (reactions.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(reactions.size())) + throw std::runtime_error("index out of range"); // FIXME: how to ensure that the map entries correspond to table indicies? // WARNING: Assumed that the table view has the same order as std::map - std::map::iterator iter = reactions.begin(); - for(int i = 0; i < rowToRemove; ++i) + auto iter = reactions.begin(); + for (int i = 0; i < rowToRemove; ++i) ++iter; reactions.erase(iter); - record.setModified (faction); + record.setModified(faction); } - void FactionReactionsAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void FactionReactionsAdapter::setTable( + Record& record, const NestedTableWrapperBase& nestedTable) const { ESM::Faction faction = record.get(); - faction.mReactions = - static_cast >&>(nestedTable).mNestedTable; + faction.mReactions + = static_cast>&>(nestedTable).mNestedTable; - record.setModified (faction); + record.setModified(faction); } NestedTableWrapperBase* FactionReactionsAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mReactions); + return new NestedTableWrapper>(record.get().mReactions); } - QVariant FactionReactionsAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant FactionReactionsAdapter::getData( + const Record& record, int subRowIndex, int subColIndex) const { ESM::Faction faction = record.get(); - std::map& reactions = faction.mReactions; + std::map& reactions = faction.mReactions; - if (subRowIndex < 0 || subRowIndex >= static_cast (reactions.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(reactions.size())) + throw std::runtime_error("index out of range"); // FIXME: how to ensure that the map entries correspond to table indicies? // WARNING: Assumed that the table view has the same order as std::map - std::map::const_iterator iter = reactions.begin(); - for(int i = 0; i < subRowIndex; ++i) + auto iter = reactions.begin(); + for (int i = 0; i < subRowIndex; ++i) ++iter; switch (subColIndex) { - case 0: return QString((*iter).first.c_str()); - case 1: return (*iter).second; - default: throw std::runtime_error("Faction reactions subcolumn index out of range"); + case 0: + return QString((*iter).first.getRefIdString().c_str()); + case 1: + return (*iter).second; + default: + throw std::runtime_error("Faction reactions subcolumn index out of range"); } } - void FactionReactionsAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void FactionReactionsAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Faction faction = record.get(); - std::map& reactions = faction.mReactions; + std::map& reactions = faction.mReactions; - if (subRowIndex < 0 || subRowIndex >= static_cast (reactions.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(reactions.size())) + throw std::runtime_error("index out of range"); // FIXME: how to ensure that the map entries correspond to table indicies? // WARNING: Assumed that the table view has the same order as std::map - std::map::iterator iter = reactions.begin(); - for(int i = 0; i < subRowIndex; ++i) + auto iter = reactions.begin(); + for (int i = 0; i < subRowIndex; ++i) ++iter; - std::string factionId = (*iter).first; + ESM::RefId factionId = (*iter).first; int reaction = (*iter).second; switch (subColIndex) @@ -310,7 +340,8 @@ namespace CSMWorld case 0: { reactions.erase(iter); - reactions.insert(std::make_pair(value.toString().toUtf8().constData(), reaction)); + reactions.insert( + std::make_pair(ESM::RefId::stringRefId(value.toString().toUtf8().constData()), reaction)); break; } case 1: @@ -318,10 +349,11 @@ namespace CSMWorld reactions[factionId] = value.toInt(); break; } - default: throw std::runtime_error("Faction reactions subcolumn index out of range"); + default: + throw std::runtime_error("Faction reactions subcolumn index out of range"); } - record.setModified (faction); + record.setModified(faction); } int FactionReactionsAdapter::getColumnsCount(const Record& record) const @@ -334,8 +366,6 @@ namespace CSMWorld return static_cast(record.get().mReactions.size()); } - RegionSoundListAdapter::RegionSoundListAdapter () {} - void RegionSoundListAdapter::addRow(Record& record, int position) const { ESM::Region region = record.get(); @@ -344,12 +374,12 @@ namespace CSMWorld // blank row ESM::Region::SoundRef soundRef; - soundRef.mSound.assign(""); + soundRef.mSound = ESM::RefId(); soundRef.mChance = 0; - soundList.insert(soundList.begin()+position, soundRef); + soundList.insert(soundList.begin() + position, soundRef); - record.setModified (region); + record.setModified(region); } void RegionSoundListAdapter::removeRow(Record& record, int rowToRemove) const @@ -358,71 +388,77 @@ namespace CSMWorld std::vector& soundList = region.mSoundList; - if (rowToRemove < 0 || rowToRemove >= static_cast (soundList.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(soundList.size())) + throw std::runtime_error("index out of range"); - soundList.erase(soundList.begin()+rowToRemove); + soundList.erase(soundList.begin() + rowToRemove); - record.setModified (region); + record.setModified(region); } - void RegionSoundListAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void RegionSoundListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { ESM::Region region = record.get(); - region.mSoundList = - static_cast >&>(nestedTable).mNestedTable; + region.mSoundList + = static_cast>&>(nestedTable).mNestedTable; - record.setModified (region); + record.setModified(region); } NestedTableWrapperBase* RegionSoundListAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mSoundList); + return new NestedTableWrapper>(record.get().mSoundList); } - QVariant RegionSoundListAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant RegionSoundListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Region region = record.get(); std::vector& soundList = region.mSoundList; - if (subRowIndex < 0 || subRowIndex >= static_cast (soundList.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(soundList.size())) + throw std::runtime_error("index out of range"); ESM::Region::SoundRef soundRef = soundList[subRowIndex]; switch (subColIndex) { - case 0: return QString(soundRef.mSound.c_str()); - case 1: return soundRef.mChance; - default: throw std::runtime_error("Region sounds subcolumn index out of range"); + case 0: + return QString(soundRef.mSound.getRefIdString().c_str()); + case 1: + return soundRef.mChance; + default: + throw std::runtime_error("Region sounds subcolumn index out of range"); } } - void RegionSoundListAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void RegionSoundListAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Region region = record.get(); std::vector& soundList = region.mSoundList; - if (subRowIndex < 0 || subRowIndex >= static_cast (soundList.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(soundList.size())) + throw std::runtime_error("index out of range"); ESM::Region::SoundRef soundRef = soundList[subRowIndex]; switch (subColIndex) { - case 0: soundRef.mSound.assign(value.toString().toUtf8().constData()); break; - case 1: soundRef.mChance = static_cast(value.toInt()); break; - default: throw std::runtime_error("Region sounds subcolumn index out of range"); + case 0: + soundRef.mSound = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); + break; + case 1: + soundRef.mChance = static_cast(value.toInt()); + break; + default: + throw std::runtime_error("Region sounds subcolumn index out of range"); } region.mSoundList[subRowIndex] = soundRef; - record.setModified (region); + record.setModified(region); } int RegionSoundListAdapter::getColumnsCount(const Record& record) const @@ -435,31 +471,27 @@ namespace CSMWorld return static_cast(record.get().mSoundList.size()); } - InfoListAdapter::InfoListAdapter () {} - void InfoListAdapter::addRow(Record& record, int position) const { - throw std::logic_error ("cannot add a row to a fixed table"); + throw std::logic_error("cannot add a row to a fixed table"); } void InfoListAdapter::removeRow(Record& record, int rowToRemove) const { - throw std::logic_error ("cannot remove a row to a fixed table"); + throw std::logic_error("cannot remove a row to a fixed table"); } - void InfoListAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void InfoListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } NestedTableWrapperBase* InfoListAdapter::table(const Record& record) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } - QVariant InfoListAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant InfoListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Info info = record.get(); @@ -469,8 +501,7 @@ namespace CSMWorld throw std::runtime_error("Trying to access non-existing column in the nested table!"); } - void InfoListAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void InfoListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { Info info = record.get(); @@ -479,7 +510,7 @@ namespace CSMWorld else throw std::runtime_error("Trying to access non-existing column in the nested table!"); - record.setModified (info); + record.setModified(info); } int InfoListAdapter::getColumnsCount(const Record& record) const @@ -492,8 +523,6 @@ namespace CSMWorld return 1; // fixed at size 1 } - InfoConditionAdapter::InfoConditionAdapter () {} - void InfoConditionAdapter::addRow(Record& record, int position) const { Info info = record.get(); @@ -506,9 +535,9 @@ namespace CSMWorld condStruct.mValue = ESM::Variant(); condStruct.mValue.setType(ESM::VT_Int); - conditions.insert(conditions.begin()+position, condStruct); + conditions.insert(conditions.begin() + position, condStruct); - record.setModified (info); + record.setModified(info); } void InfoConditionAdapter::removeRow(Record& record, int rowToRemove) const @@ -517,40 +546,38 @@ namespace CSMWorld std::vector& conditions = info.mSelects; - if (rowToRemove < 0 || rowToRemove >= static_cast (conditions.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(conditions.size())) + throw std::runtime_error("index out of range"); - conditions.erase(conditions.begin()+rowToRemove); + conditions.erase(conditions.begin() + rowToRemove); - record.setModified (info); + record.setModified(info); } - void InfoConditionAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void InfoConditionAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { Info info = record.get(); - info.mSelects = - static_cast >&>(nestedTable).mNestedTable; + info.mSelects = static_cast>&>(nestedTable) + .mNestedTable; - record.setModified (info); + record.setModified(info); } NestedTableWrapperBase* InfoConditionAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mSelects); + return new NestedTableWrapper>(record.get().mSelects); } - QVariant InfoConditionAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Info info = record.get(); std::vector& conditions = info.mSelects; - if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(conditions.size())) + throw std::runtime_error("index out of range"); ConstInfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); @@ -583,22 +610,24 @@ namespace CSMWorld { return infoSelectWrapper.getVariant().getFloat(); } - default: return QVariant(); + default: + return QVariant(); } } - default: throw std::runtime_error("Info condition subcolumn index out of range"); + default: + throw std::runtime_error("Info condition subcolumn index out of range"); } } - void InfoConditionAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void InfoConditionAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { Info info = record.get(); std::vector& conditions = info.mSelects; - if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(conditions.size())) + throw std::runtime_error("index out of range"); InfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); bool conversionResult = false; @@ -609,8 +638,8 @@ namespace CSMWorld { infoSelectWrapper.setFunctionName(static_cast(value.toInt())); - if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric && - infoSelectWrapper.getVariant().getType() != ESM::VT_Int) + if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric + && infoSelectWrapper.getVariant().getType() != ESM::VT_Int) { infoSelectWrapper.getVariant().setType(ESM::VT_Int); } @@ -659,14 +688,16 @@ namespace CSMWorld } break; } - default: break; + default: + break; } break; } - default: throw std::runtime_error("Info condition subcolumn index out of range"); + default: + throw std::runtime_error("Info condition subcolumn index out of range"); } - record.setModified (info); + record.setModified(info); } int InfoConditionAdapter::getColumnsCount(const Record& record) const @@ -679,8 +710,6 @@ namespace CSMWorld return static_cast(record.get().mSelects.size()); } - RaceAttributeAdapter::RaceAttributeAdapter () {} - void RaceAttributeAdapter::addRow(Record& record, int position) const { // Do nothing, this table cannot be changed by the user @@ -691,15 +720,14 @@ namespace CSMWorld // Do nothing, this table cannot be changed by the user } - void RaceAttributeAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void RaceAttributeAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { ESM::Race race = record.get(); - race.mData = - static_cast >&>(nestedTable).mNestedTable.at(0); + race.mData = static_cast>&>(nestedTable) + .mNestedTable.at(0); - record.setModified (race); + record.setModified(race); } NestedTableWrapperBase* RaceAttributeAdapter::table(const Record& record) const @@ -707,43 +735,52 @@ namespace CSMWorld std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper>(wrap); } - QVariant RaceAttributeAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant RaceAttributeAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length) - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); switch (subColIndex) { - case 0: return subRowIndex; - case 1: return race.mData.mAttributeValues[subRowIndex].mMale; - case 2: return race.mData.mAttributeValues[subRowIndex].mFemale; - default: throw std::runtime_error("Race Attribute subcolumn index out of range"); + case 0: + return subRowIndex; + case 1: + return race.mData.mAttributeValues[subRowIndex].mMale; + case 2: + return race.mData.mAttributeValues[subRowIndex].mFemale; + default: + throw std::runtime_error("Race Attribute subcolumn index out of range"); } } - void RaceAttributeAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void RaceAttributeAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length) - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); switch (subColIndex) { - case 0: return; // throw an exception here? - case 1: race.mData.mAttributeValues[subRowIndex].mMale = value.toInt(); break; - case 2: race.mData.mAttributeValues[subRowIndex].mFemale = value.toInt(); break; - default: throw std::runtime_error("Race Attribute subcolumn index out of range"); + case 0: + return; // throw an exception here? + case 1: + race.mData.mAttributeValues[subRowIndex].mMale = value.toInt(); + break; + case 2: + race.mData.mAttributeValues[subRowIndex].mFemale = value.toInt(); + break; + default: + throw std::runtime_error("Race Attribute subcolumn index out of range"); } - record.setModified (race); + record.setModified(race); } int RaceAttributeAdapter::getColumnsCount(const Record& record) const @@ -756,8 +793,6 @@ namespace CSMWorld return ESM::Attribute::Length; // there are 8 attributes } - RaceSkillsBonusAdapter::RaceSkillsBonusAdapter () {} - void RaceSkillsBonusAdapter::addRow(Record& record, int position) const { // Do nothing, this table cannot be changed by the user @@ -768,15 +803,14 @@ namespace CSMWorld // Do nothing, this table cannot be changed by the user } - void RaceSkillsBonusAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void RaceSkillsBonusAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { ESM::Race race = record.get(); - race.mData = - static_cast >&>(nestedTable).mNestedTable.at(0); + race.mData = static_cast>&>(nestedTable) + .mNestedTable.at(0); - record.setModified (race); + record.setModified(race); } NestedTableWrapperBase* RaceSkillsBonusAdapter::table(const Record& record) const @@ -784,41 +818,48 @@ namespace CSMWorld std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper>(wrap); } - QVariant RaceSkillsBonusAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant RaceSkillsBonusAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); - if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(race.mData.mBonus)/sizeof(race.mData.mBonus[0]))) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || static_cast(subRowIndex) >= race.mData.mBonus.size()) + throw std::runtime_error("index out of range"); switch (subColIndex) { - case 0: return race.mData.mBonus[subRowIndex].mSkill; // can be -1 - case 1: return race.mData.mBonus[subRowIndex].mBonus; - default: throw std::runtime_error("Race skill bonus subcolumn index out of range"); + case 0: + return race.mData.mBonus[subRowIndex].mSkill; // can be -1 + case 1: + return race.mData.mBonus[subRowIndex].mBonus; + default: + throw std::runtime_error("Race skill bonus subcolumn index out of range"); } } - void RaceSkillsBonusAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void RaceSkillsBonusAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); - if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(race.mData.mBonus)/sizeof(race.mData.mBonus[0]))) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || static_cast(subRowIndex) >= race.mData.mBonus.size()) + throw std::runtime_error("index out of range"); switch (subColIndex) { - case 0: race.mData.mBonus[subRowIndex].mSkill = value.toInt(); break; // can be -1 - case 1: race.mData.mBonus[subRowIndex].mBonus = value.toInt(); break; - default: throw std::runtime_error("Race skill bonus subcolumn index out of range"); + case 0: + race.mData.mBonus[subRowIndex].mSkill = value.toInt(); + break; // can be -1 + case 1: + race.mData.mBonus[subRowIndex].mBonus = value.toInt(); + break; + default: + throw std::runtime_error("Race skill bonus subcolumn index out of range"); } - record.setModified (race); + record.setModified(race); } int RaceSkillsBonusAdapter::getColumnsCount(const Record& record) const @@ -828,35 +869,30 @@ namespace CSMWorld int RaceSkillsBonusAdapter::getRowsCount(const Record& record) const { - // there are 7 skill bonuses - return static_cast(sizeof(record.get().mData.mBonus)/sizeof(record.get().mData.mBonus[0])); + return record.get().mData.mBonus.size(); } - CellListAdapter::CellListAdapter () {} - void CellListAdapter::addRow(Record& record, int position) const { - throw std::logic_error ("cannot add a row to a fixed table"); + throw std::logic_error("cannot add a row to a fixed table"); } void CellListAdapter::removeRow(Record& record, int rowToRemove) const { - throw std::logic_error ("cannot remove a row to a fixed table"); + throw std::logic_error("cannot remove a row to a fixed table"); } - void CellListAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void CellListAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } NestedTableWrapperBase* CellListAdapter::table(const Record& record) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } - QVariant CellListAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant CellListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { CSMWorld::Cell cell = record.get(); @@ -866,17 +902,18 @@ namespace CSMWorld switch (subColIndex) { - case 0: return isInterior; + case 0: + return isInterior; // While the ambient information is not necessarily valid if the subrecord wasn't loaded, // the user should still be allowed to edit it - case 1: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mAmbient : QVariant(QVariant::UserType); - case 2: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mSunlight : QVariant(QVariant::UserType); - case 3: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mFog : QVariant(QVariant::UserType); - case 4: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); + case 1: + return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mAmbient : QVariant(QVariant::UserType); + case 2: + return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mSunlight : QVariant(QVariant::UserType); + case 3: + return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mFog : QVariant(QVariant::UserType); + case 4: + return (isInterior && !behaveLikeExterior) ? cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); case 5: { if (isInterior && interiorWater) @@ -884,16 +921,17 @@ namespace CSMWorld else return QVariant(QVariant::UserType); } - case 6: return isInterior ? - QVariant(QVariant::UserType) : cell.mMapColor; // TODO: how to select? - //case 7: return isInterior ? - //behaveLikeExterior : QVariant(QVariant::UserType); - default: throw std::runtime_error("Cell subcolumn index out of range"); + case 6: + return isInterior ? QVariant(QVariant::UserType) : cell.mMapColor; // TODO: how to select? + // case 7: return isInterior ? + // behaveLikeExterior : QVariant(QVariant::UserType); + default: + throw std::runtime_error("Cell subcolumn index out of range"); } } - void CellListAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void CellListAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { CSMWorld::Cell cell = record.get(); @@ -988,10 +1026,11 @@ namespace CSMWorld break; } #endif - default: throw std::runtime_error("Cell subcolumn index out of range"); + default: + throw std::runtime_error("Cell subcolumn index out of range"); } - record.setModified (cell); + record.setModified(cell); } int CellListAdapter::getColumnsCount(const Record& record) const @@ -1004,42 +1043,30 @@ namespace CSMWorld return 1; // fixed at size 1 } - RegionWeatherAdapter::RegionWeatherAdapter () {} - void RegionWeatherAdapter::addRow(Record& record, int position) const { - throw std::logic_error ("cannot add a row to a fixed table"); + throw std::logic_error("cannot add a row to a fixed table"); } void RegionWeatherAdapter::removeRow(Record& record, int rowToRemove) const { - throw std::logic_error ("cannot remove a row from a fixed table"); + throw std::logic_error("cannot remove a row from a fixed table"); } void RegionWeatherAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } NestedTableWrapperBase* RegionWeatherAdapter::table(const Record& record) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } QVariant RegionWeatherAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { - const char* WeatherNames[] = { - "Clear", - "Cloudy", - "Fog", - "Overcast", - "Rain", - "Thunder", - "Ash", - "Blight", - "Snow", - "Blizzard" - }; + const char* WeatherNames[] + = { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" }; const ESM::Region& region = record.get(); @@ -1051,25 +1078,36 @@ namespace CSMWorld { switch (subRowIndex) { - case 0: return region.mData.mClear; - case 1: return region.mData.mCloudy; - case 2: return region.mData.mFoggy; - case 3: return region.mData.mOvercast; - case 4: return region.mData.mRain; - case 5: return region.mData.mThunder; - case 6: return region.mData.mAsh; - case 7: return region.mData.mBlight; - case 8: return region.mData.mA; // Snow - case 9: return region.mData.mB; // Blizzard - default: break; + case 0: + return region.mData.mClear; + case 1: + return region.mData.mCloudy; + case 2: + return region.mData.mFoggy; + case 3: + return region.mData.mOvercast; + case 4: + return region.mData.mRain; + case 5: + return region.mData.mThunder; + case 6: + return region.mData.mAsh; + case 7: + return region.mData.mBlight; + case 8: + return region.mData.mSnow; + case 9: + return region.mData.mBlizzard; + default: + break; } } throw std::runtime_error("index out of range"); } - void RegionWeatherAdapter::setData(Record& record, const QVariant& value, int subRowIndex, - int subColIndex) const + void RegionWeatherAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Region region = record.get(); unsigned char chance = static_cast(value.toInt()); @@ -1078,20 +1116,41 @@ namespace CSMWorld { switch (subRowIndex) { - case 0: region.mData.mClear = chance; break; - case 1: region.mData.mCloudy = chance; break; - case 2: region.mData.mFoggy = chance; break; - case 3: region.mData.mOvercast = chance; break; - case 4: region.mData.mRain = chance; break; - case 5: region.mData.mThunder = chance; break; - case 6: region.mData.mAsh = chance; break; - case 7: region.mData.mBlight = chance; break; - case 8: region.mData.mA = chance; break; - case 9: region.mData.mB = chance; break; - default: throw std::runtime_error("index out of range"); + case 0: + region.mData.mClear = chance; + break; + case 1: + region.mData.mCloudy = chance; + break; + case 2: + region.mData.mFoggy = chance; + break; + case 3: + region.mData.mOvercast = chance; + break; + case 4: + region.mData.mRain = chance; + break; + case 5: + region.mData.mThunder = chance; + break; + case 6: + region.mData.mAsh = chance; + break; + case 7: + region.mData.mBlight = chance; + break; + case 8: + region.mData.mSnow = chance; + break; + case 9: + region.mData.mBlizzard = chance; + break; + default: + throw std::runtime_error("index out of range"); } - record.setModified (region); + record.setModified(region); } } @@ -1105,73 +1164,83 @@ namespace CSMWorld return 10; } - FactionRanksAdapter::FactionRanksAdapter () {} - void FactionRanksAdapter::addRow(Record& record, int position) const { - throw std::logic_error ("cannot add a row to a fixed table"); + throw std::logic_error("cannot add a row to a fixed table"); } void FactionRanksAdapter::removeRow(Record& record, int rowToRemove) const { - throw std::logic_error ("cannot remove a row from a fixed table"); + throw std::logic_error("cannot remove a row from a fixed table"); } - void FactionRanksAdapter::setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const + void FactionRanksAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } NestedTableWrapperBase* FactionRanksAdapter::table(const Record& record) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } - QVariant FactionRanksAdapter::getData(const Record& record, - int subRowIndex, int subColIndex) const + QVariant FactionRanksAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { - ESM::Faction faction = record.get(); + const ESM::Faction& faction = record.get(); - if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(faction.mData.mRankData)/sizeof(faction.mData.mRankData[0]))) - throw std::runtime_error ("index out of range"); - - auto& rankData = faction.mData.mRankData[subRowIndex]; + const auto& rankData = faction.mData.mRankData.at(subRowIndex); switch (subColIndex) { - case 0: return QString(faction.mRanks[subRowIndex].c_str()); - case 1: return rankData.mAttribute1; - case 2: return rankData.mAttribute2; - case 3: return rankData.mPrimarySkill; - case 4: return rankData.mFavouredSkill; - case 5: return rankData.mFactReaction; - default: throw std::runtime_error("Rank subcolumn index out of range"); + case 0: + return QString(faction.mRanks[subRowIndex].c_str()); + case 1: + return rankData.mAttribute1; + case 2: + return rankData.mAttribute2; + case 3: + return rankData.mPrimarySkill; + case 4: + return rankData.mFavouredSkill; + case 5: + return rankData.mFactReaction; + default: + throw std::runtime_error("Rank subcolumn index out of range"); } } - void FactionRanksAdapter::setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const + void FactionRanksAdapter::setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Faction faction = record.get(); - if (subRowIndex < 0 || subRowIndex >= static_cast(sizeof(faction.mData.mRankData)/sizeof(faction.mData.mRankData[0]))) - throw std::runtime_error ("index out of range"); - - auto& rankData = faction.mData.mRankData[subRowIndex]; + auto& rankData = faction.mData.mRankData.at(subRowIndex); switch (subColIndex) { - case 0: faction.mRanks[subRowIndex] = value.toString().toUtf8().constData(); break; - case 1: rankData.mAttribute1 = value.toInt(); break; - case 2: rankData.mAttribute2 = value.toInt(); break; - case 3: rankData.mPrimarySkill = value.toInt(); break; - case 4: rankData.mFavouredSkill = value.toInt(); break; - case 5: rankData.mFactReaction = value.toInt(); break; - default: throw std::runtime_error("Rank index out of range"); + case 0: + faction.mRanks[subRowIndex] = value.toString().toUtf8().constData(); + break; + case 1: + rankData.mAttribute1 = value.toInt(); + break; + case 2: + rankData.mAttribute2 = value.toInt(); + break; + case 3: + rankData.mPrimarySkill = value.toInt(); + break; + case 4: + rankData.mFavouredSkill = value.toInt(); + break; + case 5: + rankData.mFactReaction = value.toInt(); + break; + default: + throw std::runtime_error("Rank index out of range"); } - record.setModified (faction); + record.setModified(faction); } int FactionRanksAdapter::getColumnsCount(const Record& record) const diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 54780d290..235396c65 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -1,49 +1,52 @@ #ifndef CSM_WOLRD_NESTEDCOLADAPTERIMP_H #define CSM_WOLRD_NESTEDCOLADAPTERIMP_H +#include #include -#include -#include -#include // for converting magic effect id to string & back -#include // for converting skill names -#include // for converting attributes -#include +#include +#include +#include +#include + +#include +#include // for converting magic effect id to string & back #include "nestedcolumnadapter.hpp" #include "nestedtablewrapper.hpp" -#include "cell.hpp" namespace ESM { struct Faction; struct Region; + struct Race; } namespace CSMWorld { struct Pathgrid; struct Info; + struct Cell; + + template + struct Record; class PathgridPointListAdapter : public NestedColumnAdapter { public: - PathgridPointListAdapter (); + PathgridPointListAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -53,22 +56,19 @@ namespace CSMWorld class PathgridEdgeListAdapter : public NestedColumnAdapter { public: - PathgridEdgeListAdapter (); + PathgridEdgeListAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -78,22 +78,20 @@ namespace CSMWorld class FactionReactionsAdapter : public NestedColumnAdapter { public: - FactionReactionsAdapter (); + FactionReactionsAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -103,22 +101,20 @@ namespace CSMWorld class FactionRanksAdapter : public NestedColumnAdapter { public: - FactionRanksAdapter (); + FactionRanksAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -128,121 +124,120 @@ namespace CSMWorld class RegionSoundListAdapter : public NestedColumnAdapter { public: - RegionSoundListAdapter (); + RegionSoundListAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; int getRowsCount(const Record& record) const override; }; - template + template class SpellListAdapter : public NestedColumnAdapter { public: - SpellListAdapter () {} + SpellListAdapter() = default; void addRow(Record& record, int position) const override { ESXRecordT raceOrBthSgn = record.get(); - std::vector& spells = raceOrBthSgn.mPowers.mList; + std::vector& spells = raceOrBthSgn.mPowers.mList; // blank row - std::string spell = ""; + ESM::RefId spell; - spells.insert(spells.begin()+position, spell); + spells.insert(spells.begin() + position, spell); - record.setModified (raceOrBthSgn); + record.setModified(raceOrBthSgn); } void removeRow(Record& record, int rowToRemove) const override { ESXRecordT raceOrBthSgn = record.get(); - std::vector& spells = raceOrBthSgn.mPowers.mList; + std::vector& spells = raceOrBthSgn.mPowers.mList; - if (rowToRemove < 0 || rowToRemove >= static_cast (spells.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(spells.size())) + throw std::runtime_error("index out of range"); - spells.erase(spells.begin()+rowToRemove); + spells.erase(spells.begin() + rowToRemove); - record.setModified (raceOrBthSgn); + record.setModified(raceOrBthSgn); } void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override { ESXRecordT raceOrBthSgn = record.get(); - raceOrBthSgn.mPowers.mList = - static_cast >&>(nestedTable).mNestedTable; + raceOrBthSgn.mPowers.mList + = static_cast>&>(nestedTable).mNestedTable; - record.setModified (raceOrBthSgn); + record.setModified(raceOrBthSgn); } NestedTableWrapperBase* table(const Record& record) const override { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mPowers.mList); + return new NestedTableWrapper>(record.get().mPowers.mList); } QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override { ESXRecordT raceOrBthSgn = record.get(); - std::vector& spells = raceOrBthSgn.mPowers.mList; + std::vector& spells = raceOrBthSgn.mPowers.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(spells.size())) + throw std::runtime_error("index out of range"); - std::string spell = spells[subRowIndex]; + ESM::RefId spell = spells[subRowIndex]; switch (subColIndex) { - case 0: return QString(spell.c_str()); - default: throw std::runtime_error("Spells subcolumn index out of range"); + case 0: + return QString(spell.getRefIdString().c_str()); + default: + throw std::runtime_error("Spells subcolumn index out of range"); } } - void setData(Record& record, const QVariant& value, - int subRowIndex, int subColIndex) const override + void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override { ESXRecordT raceOrBthSgn = record.get(); - std::vector& spells = raceOrBthSgn.mPowers.mList; + std::vector& spells = raceOrBthSgn.mPowers.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(spells.size())) + throw std::runtime_error("index out of range"); - std::string spell = spells[subRowIndex]; + ESM::RefId spell = spells[subRowIndex]; switch (subColIndex) { - case 0: spell = value.toString().toUtf8().constData(); break; - default: throw std::runtime_error("Spells subcolumn index out of range"); + case 0: + spell = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); + break; + default: + throw std::runtime_error("Spells subcolumn index out of range"); } raceOrBthSgn.mPowers.mList[subRowIndex] = spell; - record.setModified (raceOrBthSgn); + record.setModified(raceOrBthSgn); } - int getColumnsCount(const Record& record) const override - { - return 1; - } + int getColumnsCount(const Record& record) const override { return 1; } int getRowsCount(const Record& record) const override { @@ -250,11 +245,11 @@ namespace CSMWorld } }; - template + template class EffectsListAdapter : public NestedColumnAdapter { public: - EffectsListAdapter () {} + EffectsListAdapter() = default; void addRow(Record& record, int position) const override { @@ -273,9 +268,9 @@ namespace CSMWorld effect.mMagnMin = 0; effect.mMagnMax = 0; - effectsList.insert(effectsList.begin()+position, effect); + effectsList.insert(effectsList.begin() + position, effect); - record.setModified (magic); + record.setModified(magic); } void removeRow(Record& record, int rowToRemove) const override @@ -284,28 +279,28 @@ namespace CSMWorld std::vector& effectsList = magic.mEffects.mList; - if (rowToRemove < 0 || rowToRemove >= static_cast (effectsList.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(effectsList.size())) + throw std::runtime_error("index out of range"); - effectsList.erase(effectsList.begin()+rowToRemove); + effectsList.erase(effectsList.begin() + rowToRemove); - record.setModified (magic); + record.setModified(magic); } void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override { ESXRecordT magic = record.get(); - magic.mEffects.mList = - static_cast >&>(nestedTable).mNestedTable; + magic.mEffects.mList + = static_cast>&>(nestedTable).mNestedTable; - record.setModified (magic); + record.setModified(magic); } NestedTableWrapperBase* table(const Record& record) const override { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mEffects.mList); + return new NestedTableWrapper>(record.get().mEffects.mList); } QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override @@ -314,15 +309,15 @@ namespace CSMWorld std::vector& effectsList = magic.mEffects.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (effectsList.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(effectsList.size())) + throw std::runtime_error("index out of range"); ESM::ENAMstruct effect = effectsList[subRowIndex]; switch (subColIndex) { case 0: { - if (effect.mEffectID >=0 && effect.mEffectID < ESM::MagicEffect::Length) + if (effect.mEffectID >= 0 && effect.mEffectID < ESM::MagicEffect::Length) return effect.mEffectID; else throw std::runtime_error("Magic effects ID unexpected value"); @@ -336,7 +331,7 @@ namespace CSMWorld case ESM::MagicEffect::RestoreSkill: case ESM::MagicEffect::FortifySkill: case ESM::MagicEffect::AbsorbSkill: - return effect.mSkill; + return effect.mSkill; default: return QVariant(); } @@ -350,35 +345,39 @@ namespace CSMWorld case ESM::MagicEffect::RestoreAttribute: case ESM::MagicEffect::FortifyAttribute: case ESM::MagicEffect::AbsorbAttribute: - return effect.mAttribute; + return effect.mAttribute; default: return QVariant(); } } case 3: { - if (effect.mRange >=0 && effect.mRange <=2) + if (effect.mRange >= 0 && effect.mRange <= 2) return effect.mRange; else throw std::runtime_error("Magic effects range unexpected value"); } - case 4: return effect.mArea; - case 5: return effect.mDuration; - case 6: return effect.mMagnMin; - case 7: return effect.mMagnMax; - default: throw std::runtime_error("Magic Effects subcolumn index out of range"); + case 4: + return effect.mArea; + case 5: + return effect.mDuration; + case 6: + return effect.mMagnMin; + case 7: + return effect.mMagnMax; + default: + throw std::runtime_error("Magic Effects subcolumn index out of range"); } } - void setData(Record& record, const QVariant& value, - int subRowIndex, int subColIndex) const override + void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override { ESXRecordT magic = record.get(); std::vector& effectsList = magic.mEffects.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (effectsList.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(effectsList.size())) + throw std::runtime_error("index out of range"); ESM::ENAMstruct effect = effectsList[subRowIndex]; switch (subColIndex) @@ -403,22 +402,28 @@ namespace CSMWorld effect.mRange = value.toInt(); break; } - case 4: effect.mArea = value.toInt(); break; - case 5: effect.mDuration = value.toInt(); break; - case 6: effect.mMagnMin = value.toInt(); break; - case 7: effect.mMagnMax = value.toInt(); break; - default: throw std::runtime_error("Magic Effects subcolumn index out of range"); + case 4: + effect.mArea = value.toInt(); + break; + case 5: + effect.mDuration = value.toInt(); + break; + case 6: + effect.mMagnMin = value.toInt(); + break; + case 7: + effect.mMagnMax = value.toInt(); + break; + default: + throw std::runtime_error("Magic Effects subcolumn index out of range"); } magic.mEffects.mList[subRowIndex] = effect; - record.setModified (magic); + record.setModified(magic); } - int getColumnsCount(const Record& record) const override - { - return 8; - } + int getColumnsCount(const Record& record) const override { return 8; } int getRowsCount(const Record& record) const override { @@ -429,22 +434,19 @@ namespace CSMWorld class InfoListAdapter : public NestedColumnAdapter { public: - InfoListAdapter (); + InfoListAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -454,22 +456,19 @@ namespace CSMWorld class InfoConditionAdapter : public NestedColumnAdapter { public: - InfoConditionAdapter (); + InfoConditionAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -479,22 +478,19 @@ namespace CSMWorld class RaceAttributeAdapter : public NestedColumnAdapter { public: - RaceAttributeAdapter (); + RaceAttributeAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -504,22 +500,19 @@ namespace CSMWorld class RaceSkillsBonusAdapter : public NestedColumnAdapter { public: - RaceSkillsBonusAdapter (); + RaceSkillsBonusAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -529,22 +522,20 @@ namespace CSMWorld class CellListAdapter : public NestedColumnAdapter { public: - CellListAdapter (); + CellListAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; @@ -554,22 +545,20 @@ namespace CSMWorld class RegionWeatherAdapter : public NestedColumnAdapter { public: - RegionWeatherAdapter (); + RegionWeatherAdapter() = default; void addRow(Record& record, int position) const override; void removeRow(Record& record, int rowToRemove) const override; - void setTable(Record& record, - const NestedTableWrapperBase& nestedTable) const override; + void setTable(Record& record, const NestedTableWrapperBase& nestedTable) const override; NestedTableWrapperBase* table(const Record& record) const override; - QVariant getData(const Record& record, - int subRowIndex, int subColIndex) const override; + QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override; - void setData(Record& record, - const QVariant& value, int subRowIndex, int subColIndex) const override; + void setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const override; int getColumnsCount(const Record& record) const override; diff --git a/apps/opencs/model/world/nestedcollection.cpp b/apps/opencs/model/world/nestedcollection.cpp index 850d8c385..14fe4b78a 100644 --- a/apps/opencs/model/world/nestedcollection.cpp +++ b/apps/opencs/model/world/nestedcollection.cpp @@ -1,10 +1,10 @@ #include "nestedcollection.hpp" -CSMWorld::NestedCollection::NestedCollection() -{} +#include -CSMWorld::NestedCollection::~NestedCollection() -{} +#include + +#include "columnbase.hpp" int CSMWorld::NestedCollection::getNestedRowsCount(int row, int column) const { @@ -19,7 +19,7 @@ int CSMWorld::NestedCollection::getNestedColumnsCount(int row, int column) const int CSMWorld::NestedCollection::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) { // Assumed that the parentColumn is always a valid index - const NestableColumn *parent = getNestableColumn(parentColumn); + const NestableColumn* parent = getNestableColumn(parentColumn); int nestedColumnCount = getNestedColumnsCount(0, parentColumn); for (int i = 0; i < nestedColumnCount; ++i) { diff --git a/apps/opencs/model/world/nestedcollection.hpp b/apps/opencs/model/world/nestedcollection.hpp index 4548cfb2b..08d1c7c5e 100644 --- a/apps/opencs/model/world/nestedcollection.hpp +++ b/apps/opencs/model/world/nestedcollection.hpp @@ -14,9 +14,8 @@ namespace CSMWorld { public: - - NestedCollection(); - virtual ~NestedCollection(); + NestedCollection() = default; + virtual ~NestedCollection() = default; virtual void addNestedRow(int row, int col, int position) = 0; @@ -34,7 +33,7 @@ namespace CSMWorld virtual int getNestedColumnsCount(int row, int column) const; - virtual NestableColumn *getNestableColumn(int column) = 0; + virtual NestableColumn* getNestableColumn(int column) = 0; virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); ///< \return the column index or -1 if the requested column wasn't found. diff --git a/apps/opencs/model/world/nestedcolumnadapter.hpp b/apps/opencs/model/world/nestedcolumnadapter.hpp index 462b5a5ab..ebe77baef 100644 --- a/apps/opencs/model/world/nestedcolumnadapter.hpp +++ b/apps/opencs/model/world/nestedcolumnadapter.hpp @@ -10,14 +10,13 @@ namespace CSMWorld template struct Record; - template + template class NestedColumnAdapter { public: + NestedColumnAdapter() = default; - NestedColumnAdapter() {} - - virtual ~NestedColumnAdapter() {} + virtual ~NestedColumnAdapter() = default; virtual void addRow(Record& record, int position) const = 0; @@ -29,7 +28,8 @@ namespace CSMWorld virtual QVariant getData(const Record& record, int subRowIndex, int subColIndex) const = 0; - virtual void setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const = 0; + virtual void setData( + Record& record, const QVariant& value, int subRowIndex, int subColIndex) const = 0; virtual int getColumnsCount(const Record& record) const = 0; diff --git a/apps/opencs/model/world/nestedidcollection.hpp b/apps/opencs/model/world/nestedidcollection.hpp index a699d4bd6..2e15f9b58 100644 --- a/apps/opencs/model/world/nestedidcollection.hpp +++ b/apps/opencs/model/world/nestedidcollection.hpp @@ -2,10 +2,12 @@ #define CSM_WOLRD_NESTEDIDCOLLECTION_H #include +#include #include +#include "collection.hpp" #include "nestedcollection.hpp" -#include "nestedcoladapterimp.hpp" +#include "nestedcolumnadapter.hpp" namespace ESM { @@ -16,156 +18,155 @@ namespace CSMWorld { struct NestedTableWrapperBase; struct Cell; + struct ColumnBase; - template + template class IdCollection; - template > - class NestedIdCollection : public IdCollection, public NestedCollection + template + class NestedColumnAdapter; + + template + class NestedIdCollection : public IdCollection, public NestedCollection { - std::map* > mAdapters; + std::map*> mAdapters; - const NestedColumnAdapter& getAdapter(const ColumnBase &column) const; + const NestedColumnAdapter& getAdapter(const ColumnBase& column) const; - public: + public: + NestedIdCollection(); + ~NestedIdCollection() override; - NestedIdCollection (); - ~NestedIdCollection(); + void addNestedRow(int row, int column, int position) override; - void addNestedRow(int row, int column, int position) override; + void removeNestedRows(int row, int column, int subRow) override; - void removeNestedRows(int row, int column, int subRow) override; + QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; - QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; + void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; - void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; + NestedTableWrapperBase* nestedTable(int row, int column) const override; - NestedTableWrapperBase* nestedTable(int row, int column) const override; + void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; - void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; + int getNestedRowsCount(int row, int column) const override; - int getNestedRowsCount(int row, int column) const override; + int getNestedColumnsCount(int row, int column) const override; - int getNestedColumnsCount(int row, int column) const override; + // this method is inherited from NestedCollection, not from Collection + NestableColumn* getNestableColumn(int column) override; - // this method is inherited from NestedCollection, not from Collection - NestableColumn *getNestableColumn(int column) override; - - void addAdapter(std::pair* > adapter); + void addAdapter(std::pair*> adapter); }; - template - NestedIdCollection::NestedIdCollection () - {} - - template - NestedIdCollection::~NestedIdCollection() + template + NestedIdCollection::NestedIdCollection() { - for (typename std::map* >::iterator - iter (mAdapters.begin()); iter!=mAdapters.end(); ++iter) + } + + template + NestedIdCollection::~NestedIdCollection() + { + for (typename std::map*>::iterator iter(mAdapters.begin()); + iter != mAdapters.end(); ++iter) { delete (*iter).second; } } - template - void NestedIdCollection::addAdapter(std::pair* > adapter) + template + void NestedIdCollection::addAdapter( + std::pair*> adapter) { mAdapters.insert(adapter); } - template - const NestedColumnAdapter& NestedIdCollection::getAdapter(const ColumnBase &column) const + template + const NestedColumnAdapter& NestedIdCollection::getAdapter(const ColumnBase& column) const { - typename std::map* >::const_iterator iter = - mAdapters.find (&column); + typename std::map*>::const_iterator iter + = mAdapters.find(&column); - if (iter==mAdapters.end()) + if (iter == mAdapters.end()) throw std::logic_error("No such column in the nestedidadapter"); return *iter->second; } - template - void NestedIdCollection::addNestedRow(int row, int column, int position) + template + void NestedIdCollection::addNestedRow(int row, int column, int position) { - Record record; - record.assign(Collection::getRecord(row)); + auto record = std::make_unique>(); + record->assign(Collection::getRecord(row)); - getAdapter(Collection::getColumn(column)).addRow(record, position); + getAdapter(Collection::getColumn(column)).addRow(*record, position); - Collection::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } - template - void NestedIdCollection::removeNestedRows(int row, int column, int subRow) + template + void NestedIdCollection::removeNestedRows(int row, int column, int subRow) { - Record record; - record.assign(Collection::getRecord(row)); + auto record = std::make_unique>(); + record->assign(Collection::getRecord(row)); - getAdapter(Collection::getColumn(column)).removeRow(record, subRow); + getAdapter(Collection::getColumn(column)).removeRow(*record, subRow); - Collection::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } - template - QVariant NestedIdCollection::getNestedData (int row, - int column, int subRow, int subColumn) const + template + QVariant NestedIdCollection::getNestedData(int row, int column, int subRow, int subColumn) const { - return getAdapter(Collection::getColumn(column)).getData( - Collection::getRecord(row), subRow, subColumn); + return getAdapter(Collection::getColumn(column)) + .getData(Collection::getRecord(row), subRow, subColumn); } - template - void NestedIdCollection::setNestedData(int row, - int column, const QVariant& data, int subRow, int subColumn) + template + void NestedIdCollection::setNestedData( + int row, int column, const QVariant& data, int subRow, int subColumn) { - Record record; - record.assign(Collection::getRecord(row)); + auto record = std::make_unique>(); + record->assign(Collection::getRecord(row)); - getAdapter(Collection::getColumn(column)).setData( - record, data, subRow, subColumn); + getAdapter(Collection::getColumn(column)).setData(*record, data, subRow, subColumn); - Collection::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } - template - CSMWorld::NestedTableWrapperBase* NestedIdCollection::nestedTable(int row, - int column) const + template + CSMWorld::NestedTableWrapperBase* NestedIdCollection::nestedTable(int row, int column) const { - return getAdapter(Collection::getColumn(column)).table( - Collection::getRecord(row)); + return getAdapter(Collection::getColumn(column)).table(Collection::getRecord(row)); } - template - void NestedIdCollection::setNestedTable(int row, - int column, const CSMWorld::NestedTableWrapperBase& nestedTable) + template + void NestedIdCollection::setNestedTable( + int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) { - Record record; - record.assign(Collection::getRecord(row)); + auto record = std::make_unique>(); + record->assign(Collection::getRecord(row)); - getAdapter(Collection::getColumn(column)).setTable( - record, nestedTable); + getAdapter(Collection::getColumn(column)).setTable(*record, nestedTable); - Collection::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } - template - int NestedIdCollection::getNestedRowsCount(int row, int column) const + template + int NestedIdCollection::getNestedRowsCount(int row, int column) const { - return getAdapter(Collection::getColumn(column)).getRowsCount( - Collection::getRecord(row)); + return getAdapter(Collection::getColumn(column)) + .getRowsCount(Collection::getRecord(row)); } - template - int NestedIdCollection::getNestedColumnsCount(int row, int column) const + template + int NestedIdCollection::getNestedColumnsCount(int row, int column) const { - const ColumnBase &nestedColumn = Collection::getColumn(column); - int numRecords = Collection::getSize(); + const ColumnBase& nestedColumn = Collection::getColumn(column); + int numRecords = Collection::getSize(); if (row >= 0 && row < numRecords) { - const Record& record = Collection::getRecord(row); + const Record& record = Collection::getRecord(row); return getAdapter(nestedColumn).getColumnsCount(record); } else @@ -176,10 +177,10 @@ namespace CSMWorld } } - template - CSMWorld::NestableColumn *NestedIdCollection::getNestableColumn(int column) + template + CSMWorld::NestableColumn* NestedIdCollection::getNestableColumn(int column) { - return Collection::getNestableColumn(column); + return Collection::getNestableColumn(column); } } diff --git a/apps/opencs/model/world/nestedinfocollection.cpp b/apps/opencs/model/world/nestedinfocollection.cpp index 4abaaf9c0..74a51a9c1 100644 --- a/apps/opencs/model/world/nestedinfocollection.cpp +++ b/apps/opencs/model/world/nestedinfocollection.cpp @@ -1,33 +1,41 @@ #include "nestedinfocollection.hpp" -#include "nestedcoladapterimp.hpp" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace CSMWorld { - NestedInfoCollection::NestedInfoCollection () - {} - NestedInfoCollection::~NestedInfoCollection() { - for (std::map* >::iterator - iter (mAdapters.begin()); iter!=mAdapters.end(); ++iter) + for (std::map*>::iterator iter(mAdapters.begin()); + iter != mAdapters.end(); ++iter) { delete (*iter).second; } } - void NestedInfoCollection::addAdapter(std::pair* > adapter) + void NestedInfoCollection::addAdapter(std::pair*> adapter) { mAdapters.insert(adapter); } - const NestedColumnAdapter& NestedInfoCollection::getAdapter(const ColumnBase &column) const + const NestedColumnAdapter& NestedInfoCollection::getAdapter(const ColumnBase& column) const { - std::map* >::const_iterator iter = - mAdapters.find (&column); + std::map*>::const_iterator iter = mAdapters.find(&column); - if (iter==mAdapters.end()) + if (iter == mAdapters.end()) throw std::logic_error("No such column in the nestedidadapter"); return *iter->second; @@ -35,76 +43,67 @@ namespace CSMWorld void NestedInfoCollection::addNestedRow(int row, int column, int position) { - Record record; - record.assign(Collection >::getRecord(row)); + auto record = std::make_unique>(); + record->assign(Collection::getRecord(row)); - getAdapter(Collection >::getColumn(column)).addRow(record, position); + getAdapter(Collection::getColumn(column)).addRow(*record, position); - Collection >::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } void NestedInfoCollection::removeNestedRows(int row, int column, int subRow) { - Record record; - record.assign(Collection >::getRecord(row)); + auto record = std::make_unique>(); + record->assign(Collection::getRecord(row)); - getAdapter(Collection >::getColumn(column)).removeRow(record, subRow); + getAdapter(Collection::getColumn(column)).removeRow(*record, subRow); - Collection >::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } - QVariant NestedInfoCollection::getNestedData (int row, - int column, int subRow, int subColumn) const + QVariant NestedInfoCollection::getNestedData(int row, int column, int subRow, int subColumn) const { - return getAdapter(Collection >::getColumn(column)).getData( - Collection >::getRecord(row), subRow, subColumn); + return getAdapter(Collection::getColumn(column)) + .getData(Collection::getRecord(row), subRow, subColumn); } - void NestedInfoCollection::setNestedData(int row, - int column, const QVariant& data, int subRow, int subColumn) + void NestedInfoCollection::setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) { - Record record; - record.assign(Collection >::getRecord(row)); + auto record = std::make_unique>(); + record->assign(Collection::getRecord(row)); - getAdapter(Collection >::getColumn(column)).setData( - record, data, subRow, subColumn); + getAdapter(Collection::getColumn(column)).setData(*record, data, subRow, subColumn); - Collection >::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } - CSMWorld::NestedTableWrapperBase* NestedInfoCollection::nestedTable(int row, - int column) const + CSMWorld::NestedTableWrapperBase* NestedInfoCollection::nestedTable(int row, int column) const { - return getAdapter(Collection >::getColumn(column)).table( - Collection >::getRecord(row)); + return getAdapter(Collection::getColumn(column)).table(Collection::getRecord(row)); } - void NestedInfoCollection::setNestedTable(int row, - int column, const CSMWorld::NestedTableWrapperBase& nestedTable) + void NestedInfoCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) { - Record record; - record.assign(Collection >::getRecord(row)); + auto record = std::make_unique>(); + record->assign(Collection::getRecord(row)); - getAdapter(Collection >::getColumn(column)).setTable( - record, nestedTable); + getAdapter(Collection::getColumn(column)).setTable(*record, nestedTable); - Collection >::setRecord(row, record); + Collection::setRecord(row, std::move(record)); } int NestedInfoCollection::getNestedRowsCount(int row, int column) const { - return getAdapter(Collection >::getColumn(column)).getRowsCount( - Collection >::getRecord(row)); + return getAdapter(Collection::getColumn(column)).getRowsCount(Collection::getRecord(row)); } int NestedInfoCollection::getNestedColumnsCount(int row, int column) const { - return getAdapter(Collection >::getColumn(column)).getColumnsCount( - Collection >::getRecord(row)); + return getAdapter(Collection::getColumn(column)).getColumnsCount(Collection::getRecord(row)); } - CSMWorld::NestableColumn *NestedInfoCollection::getNestableColumn(int column) + CSMWorld::NestableColumn* NestedInfoCollection::getNestableColumn(int column) { - return Collection >::getNestableColumn(column); + return Collection::getNestableColumn(column); } } diff --git a/apps/opencs/model/world/nestedinfocollection.hpp b/apps/opencs/model/world/nestedinfocollection.hpp index fe2cd43fa..a401317aa 100644 --- a/apps/opencs/model/world/nestedinfocollection.hpp +++ b/apps/opencs/model/world/nestedinfocollection.hpp @@ -1,7 +1,10 @@ #ifndef CSM_WOLRD_NESTEDINFOCOLLECTION_H #define CSM_WOLRD_NESTEDINFOCOLLECTION_H +#include + #include +#include #include "infocollection.hpp" #include "nestedcollection.hpp" @@ -9,41 +12,43 @@ namespace CSMWorld { struct NestedTableWrapperBase; + class NestableColumn; + struct ColumnBase; + struct Info; - template + template class NestedColumnAdapter; class NestedInfoCollection : public InfoCollection, public NestedCollection { - std::map* > mAdapters; + std::map*> mAdapters; - const NestedColumnAdapter& getAdapter(const ColumnBase &column) const; + const NestedColumnAdapter& getAdapter(const ColumnBase& column) const; - public: + public: + NestedInfoCollection() = default; + ~NestedInfoCollection() override; - NestedInfoCollection (); - ~NestedInfoCollection(); + void addNestedRow(int row, int column, int position) override; - void addNestedRow(int row, int column, int position) override; + void removeNestedRows(int row, int column, int subRow) override; - void removeNestedRows(int row, int column, int subRow) override; + QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; - QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; + void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; - void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; + NestedTableWrapperBase* nestedTable(int row, int column) const override; - NestedTableWrapperBase* nestedTable(int row, int column) const override; + void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; - void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; + int getNestedRowsCount(int row, int column) const override; - int getNestedRowsCount(int row, int column) const override; + int getNestedColumnsCount(int row, int column) const override; - int getNestedColumnsCount(int row, int column) const override; + // this method is inherited from NestedCollection, not from Collection + NestableColumn* getNestableColumn(int column) override; - // this method is inherited from NestedCollection, not from Collection > - NestableColumn *getNestableColumn(int column) override; - - void addAdapter(std::pair* > adapter); + void addAdapter(std::pair*> adapter); }; } diff --git a/apps/opencs/model/world/nestedtableproxymodel.cpp b/apps/opencs/model/world/nestedtableproxymodel.cpp index edcc7a070..f542ce4de 100644 --- a/apps/opencs/model/world/nestedtableproxymodel.cpp +++ b/apps/opencs/model/world/nestedtableproxymodel.cpp @@ -1,13 +1,15 @@ #include "nestedtableproxymodel.hpp" -#include #include "idtree.hpp" -CSMWorld::NestedTableProxyModel::NestedTableProxyModel(const QModelIndex& parent, - ColumnBase::Display columnId, - CSMWorld::IdTree* parentModel) - : mParentColumn(parent.column()), - mMainModel(parentModel) +#include + +#include + +CSMWorld::NestedTableProxyModel::NestedTableProxyModel( + const QModelIndex& parent, ColumnBase::Display columnId, CSMWorld::IdTree* parentModel) + : mParentColumn(parent.column()) + , mMainModel(parentModel) { const int parentRow = parent.row(); @@ -15,32 +17,25 @@ CSMWorld::NestedTableProxyModel::NestedTableProxyModel(const QModelIndex& parent QAbstractProxyModel::setSourceModel(parentModel); - connect(mMainModel, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), - this, SLOT(forwardRowsAboutToInserted(const QModelIndex &, int, int))); + connect(mMainModel, &IdTree::rowsAboutToBeInserted, this, &NestedTableProxyModel::forwardRowsAboutToInserted); - connect(mMainModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(forwardRowsInserted(const QModelIndex &, int, int))); + connect(mMainModel, &IdTree::rowsInserted, this, &NestedTableProxyModel::forwardRowsInserted); - connect(mMainModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), - this, SLOT(forwardRowsAboutToRemoved(const QModelIndex &, int, int))); + connect(mMainModel, &IdTree::rowsAboutToBeRemoved, this, &NestedTableProxyModel::forwardRowsAboutToRemoved); - connect(mMainModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(forwardRowsRemoved(const QModelIndex &, int, int))); + connect(mMainModel, &IdTree::rowsRemoved, this, &NestedTableProxyModel::forwardRowsRemoved); - connect(mMainModel, SIGNAL(resetStart(const QString&)), - this, SLOT(forwardResetStart(const QString&))); + connect(mMainModel, &IdTree::resetStart, this, &NestedTableProxyModel::forwardResetStart); - connect(mMainModel, SIGNAL(resetEnd(const QString&)), - this, SLOT(forwardResetEnd(const QString&))); + connect(mMainModel, &IdTree::resetEnd, this, &NestedTableProxyModel::forwardResetEnd); - connect(mMainModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), - this, SLOT(forwardDataChanged(const QModelIndex &, const QModelIndex &))); + connect(mMainModel, &IdTree::dataChanged, this, &NestedTableProxyModel::forwardDataChanged); } QModelIndex CSMWorld::NestedTableProxyModel::mapFromSource(const QModelIndex& sourceIndex) const { const QModelIndex& testedParent = mMainModel->parent(sourceIndex); - const QModelIndex& parent = mMainModel->getNestedModelIndex (mId, mParentColumn); + const QModelIndex& parent = mMainModel->getNestedModelIndex(mId, mParentColumn); if (testedParent == parent) { return createIndex(sourceIndex.row(), sourceIndex.column()); @@ -53,27 +48,27 @@ QModelIndex CSMWorld::NestedTableProxyModel::mapFromSource(const QModelIndex& so QModelIndex CSMWorld::NestedTableProxyModel::mapToSource(const QModelIndex& proxyIndex) const { - const QModelIndex& parent = mMainModel->getNestedModelIndex (mId, mParentColumn); + const QModelIndex& parent = mMainModel->getNestedModelIndex(mId, mParentColumn); return mMainModel->index(proxyIndex.row(), proxyIndex.column(), parent); } int CSMWorld::NestedTableProxyModel::rowCount(const QModelIndex& index) const { - assert (!index.isValid()); + assert(!index.isValid()); return mMainModel->rowCount(mMainModel->getModelIndex(mId, mParentColumn)); } int CSMWorld::NestedTableProxyModel::columnCount(const QModelIndex& parent) const { - assert (!parent.isValid()); + assert(!parent.isValid()); return mMainModel->columnCount(mMainModel->getModelIndex(mId, mParentColumn)); } QModelIndex CSMWorld::NestedTableProxyModel::index(int row, int column, const QModelIndex& parent) const { - assert (!parent.isValid()); + assert(!parent.isValid()); int numRows = rowCount(parent); int numColumns = columnCount(parent); @@ -89,9 +84,7 @@ QModelIndex CSMWorld::NestedTableProxyModel::parent(const QModelIndex& index) co return QModelIndex(); } -QVariant CSMWorld::NestedTableProxyModel::headerData(int section, - Qt::Orientation orientation, - int role) const +QVariant CSMWorld::NestedTableProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { return mMainModel->nestedHeaderData(mParentColumn, section, orientation, role); } @@ -104,7 +97,7 @@ QVariant CSMWorld::NestedTableProxyModel::data(const QModelIndex& index, int rol // NOTE: Due to mapToSouce(index) the dataChanged() signal resulting from setData() will have the // source model's index values. The indicies need to be converted to the proxy space values. // See forwardDataChanged() -bool CSMWorld::NestedTableProxyModel::setData (const QModelIndex & index, const QVariant & value, int role) +bool CSMWorld::NestedTableProxyModel::setData(const QModelIndex& index, const QVariant& value, int role) { return mMainModel->setData(mapToSource(index), value, role); } @@ -129,8 +122,7 @@ CSMWorld::IdTree* CSMWorld::NestedTableProxyModel::model() const return mMainModel; } -void CSMWorld::NestedTableProxyModel::forwardRowsAboutToInserted(const QModelIndex& parent, - int first, int last) +void CSMWorld::NestedTableProxyModel::forwardRowsAboutToInserted(const QModelIndex& parent, int first, int last) { if (indexIsParent(parent)) { @@ -148,13 +140,11 @@ void CSMWorld::NestedTableProxyModel::forwardRowsInserted(const QModelIndex& par bool CSMWorld::NestedTableProxyModel::indexIsParent(const QModelIndex& index) { - return (index.isValid() && - index.column() == mParentColumn && - mMainModel->data(mMainModel->index(index.row(), 0)).toString().toUtf8().constData() == mId); + return (index.isValid() && index.column() == mParentColumn + && mMainModel->data(mMainModel->index(index.row(), 0)).toString().toUtf8().constData() == mId); } -void CSMWorld::NestedTableProxyModel::forwardRowsAboutToRemoved(const QModelIndex& parent, - int first, int last) +void CSMWorld::NestedTableProxyModel::forwardRowsAboutToRemoved(const QModelIndex& parent, int first, int last) { if (indexIsParent(parent)) { @@ -182,15 +172,13 @@ void CSMWorld::NestedTableProxyModel::forwardResetEnd(const QString& id) endResetModel(); } -void CSMWorld::NestedTableProxyModel::forwardDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSMWorld::NestedTableProxyModel::forwardDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - const QModelIndex& parent = mMainModel->getNestedModelIndex (mId, mParentColumn); + const QModelIndex& parent = mMainModel->getNestedModelIndex(mId, mParentColumn); if (topLeft.column() <= parent.column() && bottomRight.column() >= parent.column()) { - emit dataChanged(index(0,0), - index(mMainModel->rowCount(parent)-1, mMainModel->columnCount(parent)-1)); + emit dataChanged(index(0, 0), index(mMainModel->rowCount(parent) - 1, mMainModel->columnCount(parent) - 1)); } else if (topLeft.parent() == parent && bottomRight.parent() == parent) { diff --git a/apps/opencs/model/world/nestedtableproxymodel.hpp b/apps/opencs/model/world/nestedtableproxymodel.hpp index b10f8a3a3..46737537e 100644 --- a/apps/opencs/model/world/nestedtableproxymodel.hpp +++ b/apps/opencs/model/world/nestedtableproxymodel.hpp @@ -1,12 +1,13 @@ #ifndef CSM_WOLRD_NESTEDTABLEPROXYMODEL_H #define CSM_WOLRD_NESTEDTABLEPROXYMODEL_H -#include +#include #include +#include +#include +#include -#include "universalid.hpp" -#include "columns.hpp" #include "columnbase.hpp" /*! \brief @@ -15,8 +16,6 @@ namespace CSMWorld { - class CollectionBase; - struct RecordBase; class IdTree; class NestedTableProxyModel : public QAbstractProxyModel @@ -28,10 +27,8 @@ namespace CSMWorld std::string mId; public: - NestedTableProxyModel(const QModelIndex& parent, - ColumnBase::Display displayType, - IdTree* parentModel); - //parent is the parent of columns to work with. Columnid provides information about the column + NestedTableProxyModel(const QModelIndex& parent, ColumnBase::Display displayType, IdTree* parentModel); + // parent is the parent of columns to work with. Columnid provides information about the column std::string getParentId() const; @@ -51,11 +48,11 @@ namespace CSMWorld QModelIndex parent(const QModelIndex& index) const override; - QVariant headerData (int section, Qt::Orientation orientation, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - bool setData (const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; @@ -65,19 +62,19 @@ namespace CSMWorld bool indexIsParent(const QModelIndex& index); private slots: - void forwardRowsAboutToInserted(const QModelIndex & parent, int first, int last); + void forwardRowsAboutToInserted(const QModelIndex& parent, int first, int last); - void forwardRowsInserted(const QModelIndex & parent, int first, int last); + void forwardRowsInserted(const QModelIndex& parent, int first, int last); - void forwardRowsAboutToRemoved(const QModelIndex & parent, int first, int last); + void forwardRowsAboutToRemoved(const QModelIndex& parent, int first, int last); - void forwardRowsRemoved(const QModelIndex & parent, int first, int last); + void forwardRowsRemoved(const QModelIndex& parent, int first, int last); void forwardResetStart(const QString& id); void forwardResetEnd(const QString& id); - void forwardDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void forwardDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); }; } diff --git a/apps/opencs/model/world/nestedtablewrapper.cpp b/apps/opencs/model/world/nestedtablewrapper.cpp index 3966dbc57..006027620 100644 --- a/apps/opencs/model/world/nestedtablewrapper.cpp +++ b/apps/opencs/model/world/nestedtablewrapper.cpp @@ -1,11 +1,5 @@ #include "nestedtablewrapper.hpp" -CSMWorld::NestedTableWrapperBase::NestedTableWrapperBase() -{} - -CSMWorld::NestedTableWrapperBase::~NestedTableWrapperBase() -{} - int CSMWorld::NestedTableWrapperBase::size() const { return -5; diff --git a/apps/opencs/model/world/nestedtablewrapper.hpp b/apps/opencs/model/world/nestedtablewrapper.hpp index 7d46dff8b..4756409c9 100644 --- a/apps/opencs/model/world/nestedtablewrapper.hpp +++ b/apps/opencs/model/world/nestedtablewrapper.hpp @@ -5,26 +5,28 @@ namespace CSMWorld { struct NestedTableWrapperBase { - virtual ~NestedTableWrapperBase(); - + virtual ~NestedTableWrapperBase() = default; + virtual int size() const; - - NestedTableWrapperBase(); + + NestedTableWrapperBase() = default; }; - - template + + template struct NestedTableWrapper : public NestedTableWrapperBase { NestedTable mNestedTable; NestedTableWrapper(const NestedTable& nestedTable) - : mNestedTable(nestedTable) {} + : mNestedTable(nestedTable) + { + } - virtual ~NestedTableWrapper() {} + ~NestedTableWrapper() override = default; int size() const override { - return mNestedTable.size(); //i hope that this will be enough + return mNestedTable.size(); // i hope that this will be enough } }; } diff --git a/apps/opencs/model/world/pathgrid.cpp b/apps/opencs/model/world/pathgrid.cpp index c995bd8f0..27d2b2960 100644 --- a/apps/opencs/model/world/pathgrid.cpp +++ b/apps/opencs/model/world/pathgrid.cpp @@ -1,31 +1,31 @@ +#include "pathgrid.hpp" #include "cell.hpp" #include "idcollection.hpp" -#include "pathgrid.hpp" #include -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells) +void CSMWorld::Pathgrid::load(ESM::ESMReader& esm, bool& isDeleted, const IdCollection& cells) { - load (esm, isDeleted); + load(esm, isDeleted); // correct ID - if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1) + if (!mId.empty() && !mId.startsWith("#") && cells.searchId(mId) == -1) { std::ostringstream stream; stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); + mId = ESM::RefId::stringRefId(stream.str()); } } -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted) +void CSMWorld::Pathgrid::load(ESM::ESMReader& esm, bool& isDeleted) { - ESM::Pathgrid::load (esm, isDeleted); + ESM::Pathgrid::load(esm, isDeleted); mId = mCell; if (mCell.empty()) { std::ostringstream stream; stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); + mId = ESM::RefId::stringRefId(stream.str()); } } diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index 22d01b071..a727e571a 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -1,15 +1,20 @@ #ifndef CSM_WOLRD_PATHGRID_H #define CSM_WOLRD_PATHGRID_H -#include #include -#include +#include +#include + +namespace ESM +{ + class ESMReader; +} namespace CSMWorld { struct Cell; - template + template class IdCollection; /// \brief Wrapper for Pathgrid record @@ -18,10 +23,10 @@ namespace CSMWorld /// Exterior cell coordinates are encoded in the pathgrid ID. struct Pathgrid : public ESM::Pathgrid { - std::string mId; + ESM::RefId mId; - void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells); - void load (ESM::ESMReader &esm, bool &isDeleted); + void load(ESM::ESMReader& esm, bool& isDeleted, const IdCollection& cells); + void load(ESM::ESMReader& esm, bool& isDeleted); }; } diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp index da1651f2b..ddd905082 100644 --- a/apps/opencs/model/world/record.cpp +++ b/apps/opencs/model/world/record.cpp @@ -1,20 +1,16 @@ #include "record.hpp" -CSMWorld::RecordBase::~RecordBase() {} - bool CSMWorld::RecordBase::isDeleted() const { - return mState==State_Deleted || mState==State_Erased; + return mState == State_Deleted || mState == State_Erased; } - bool CSMWorld::RecordBase::isErased() const { - return mState==State_Erased; + return mState == State_Erased; } - bool CSMWorld::RecordBase::isModified() const { - return mState==State_Modified || mState==State_ModifiedOnly; + return mState == State_Modified || mState == State_ModifiedOnly; } \ No newline at end of file diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index 5f67a93b1..d1f64fbfe 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -1,6 +1,7 @@ #ifndef CSM_WOLRD_RECORD_H #define CSM_WOLRD_RECORD_H +#include #include namespace CSMWorld @@ -18,13 +19,13 @@ namespace CSMWorld State mState; - virtual ~RecordBase(); + virtual ~RecordBase() = default; - virtual RecordBase *clone() const = 0; + virtual std::unique_ptr clone() const = 0; - virtual RecordBase *modifiedCopy() const = 0; + virtual std::unique_ptr modifiedCopy() const = 0; - virtual void assign (const RecordBase& record) = 0; + virtual void assign(const RecordBase& record) = 0; ///< Will throw an exception if the types don't match. bool isDeleted() const; @@ -42,14 +43,13 @@ namespace CSMWorld Record(); - Record(State state, - const ESXRecordT *base = 0, const ESXRecordT *modified = 0); + Record(State state, const ESXRecordT* base = 0, const ESXRecordT* modified = 0); - RecordBase *clone() const override; + std::unique_ptr clone() const override; - RecordBase *modifiedCopy() const override; + std::unique_ptr modifiedCopy() const override; - void assign (const RecordBase& record) override; + void assign(const RecordBase& record) override; const ESXRecordT& get() const; ///< Throws an exception, if the record is deleted. @@ -60,7 +60,7 @@ namespace CSMWorld const ESXRecordT& getBase() const; ///< Throws an exception, if the record is deleted. Returns modified, if there is no base. - void setModified (const ESXRecordT& modified); + void setModified(const ESXRecordT& modified); ///< Throws an exception, if the record is deleted. void merge(); @@ -69,75 +69,77 @@ namespace CSMWorld template Record::Record() - : mBase(), mModified() - { } + : mBase() + , mModified() + { + } template - Record::Record(State state, const ESXRecordT *base, const ESXRecordT *modified) + Record::Record(State state, const ESXRecordT* base, const ESXRecordT* modified) { - if(base) + if (base) mBase = *base; - if(modified) + if (modified) mModified = *modified; this->mState = state; } template - RecordBase *Record::modifiedCopy() const + std::unique_ptr Record::modifiedCopy() const { - return new Record (State_ModifiedOnly, nullptr, &(this->get())); + return std::make_unique>(Record(State_ModifiedOnly, nullptr, &(this->get()))); } template - RecordBase *Record::clone() const + std::unique_ptr Record::clone() const { - return new Record (*this); + return std::make_unique>(Record(*this)); } template - void Record::assign (const RecordBase& record) + void Record::assign(const RecordBase& record) { - *this = dynamic_cast& > (record); + *this = dynamic_cast&>(record); } template const ESXRecordT& Record::get() const { - if (mState==State_Erased) - throw std::logic_error ("attempt to access a deleted record"); + if (mState == State_Erased) + throw std::logic_error("attempt to access a deleted record"); - return mState==State_BaseOnly || mState==State_Deleted ? mBase : mModified; + return mState == State_BaseOnly || mState == State_Deleted ? mBase : mModified; } template ESXRecordT& Record::get() { - if (mState==State_Erased) - throw std::logic_error ("attempt to access a deleted record"); + if (mState == State_Erased) + throw std::logic_error("attempt to access a deleted record"); - return mState==State_BaseOnly || mState==State_Deleted ? mBase : mModified; + return mState == State_BaseOnly || mState == State_Deleted ? mBase : mModified; } template const ESXRecordT& Record::getBase() const { - if (mState==State_Erased) - throw std::logic_error ("attempt to access a deleted record"); + if (mState == State_Erased) + throw std::logic_error("attempt to access a deleted record"); - return mState==State_ModifiedOnly ? mModified : mBase; + return mState == State_ModifiedOnly ? mModified : mBase; } template - void Record::setModified (const ESXRecordT& modified) + void Record::setModified(const ESXRecordT& modified) { - if (mState==State_Erased) - throw std::logic_error ("attempt to modify a deleted record"); + if (mState == State_Erased) + throw std::logic_error("attempt to modify a deleted record"); mModified = modified; - if (mState!=State_ModifiedOnly) + if (mState != State_ModifiedOnly) mState = State_Modified; } @@ -149,7 +151,7 @@ namespace CSMWorld mBase = mModified; mState = State_BaseOnly; } - else if (mState==State_Deleted) + else if (mState == State_Deleted) { mState = State_Erased; } diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index b33623590..328d20510 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -1,8 +1,12 @@ #include "ref.hpp" +#include + #include "cellcoordinates.hpp" -CSMWorld::CellRef::CellRef() : mNew (true) +CSMWorld::CellRef::CellRef() + : mNew(true) + , mIdNum(0) { mRefNum.mIndex = 0; mRefNum.mContentFile = 0; @@ -10,5 +14,5 @@ CSMWorld::CellRef::CellRef() : mNew (true) std::pair CSMWorld::CellRef::getCellIndex() const { - return CellCoordinates::coordinatesToCellIndex (mPos.pos[0], mPos.pos[1]); + return CellCoordinates::coordinatesToCellIndex(mPos.pos[0], mPos.pos[1]); } diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index 5d10a3a1b..8e634d810 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -1,19 +1,21 @@ #ifndef CSM_WOLRD_REF_H #define CSM_WOLRD_REF_H +#include #include -#include +#include namespace CSMWorld { /// \brief Wrapper for CellRef sub record struct CellRef : public ESM::CellRef { - std::string mId; - std::string mCell; - std::string mOriginalCell; + ESM::RefId mId; + ESM::RefId mCell; + ESM::RefId mOriginalCell; bool mNew; // new reference, not counted yet, ref num not assigned yet + unsigned int mIdNum; CellRef(); diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index dfdb8e73b..1afa9027a 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,40 +1,82 @@ #include "refcollection.hpp" -#include +#include +#include +#include +#include + +#include + +#include +#include +#include -#include "ref.hpp" #include "cell.hpp" -#include "universalid.hpp" #include "record.hpp" +#include "ref.hpp" +#include "universalid.hpp" -void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, - std::map& cache, CSMDoc::Messages& messages) +#include "../doc/messages.hpp" + +namespace CSMWorld { - Record cell = mCells.getRecord (cellIndex); + template <> + void Collection::removeRows(int index, int count) + { + mRecords.erase(mRecords.begin() + index, mRecords.begin() + index + count); + + // index map is updated in RefCollection::removeRows() + } + + template <> + void Collection::insertRecord(std::unique_ptr record, int index, UniversalId::Type type) + { + int size = static_cast(mRecords.size()); + if (index < 0 || index > size) + throw std::runtime_error("index out of range"); + + std::unique_ptr> record2(static_cast*>(record.release())); + + if (index == size) + mRecords.push_back(std::move(record2)); + else + mRecords.insert(mRecords.begin() + index, std::move(record2)); + + // index map is updated in RefCollection::insertRecord() + } +} + +void CSMWorld::RefCollection::load(ESM::ESMReader& reader, int cellIndex, bool base, + std::map& cache, CSMDoc::Messages& messages) +{ + Record cell = mCells.getRecord(cellIndex); Cell& cell2 = base ? cell.mBase : cell.mModified; - CellRef ref; - ref.mNew = false; ESM::MovedCellRef mref; - mref.mRefNum.mIndex = 0; bool isDeleted = false; + bool isMoved = false; - while (ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref)) + while (true) { + CellRef ref; + ref.mNew = false; + + if (!ESM::Cell::getNextRef(reader, ref, isDeleted, mref, isMoved)) + break; // Keep mOriginalCell empty when in modified (as an indicator that the // original cell will always be equal the current cell). - ref.mOriginalCell = base ? cell2.mId : ""; + ref.mOriginalCell = base ? cell2.mId : ESM::RefId(); if (cell.get().isExterior()) { // Autocalculate the cell index from coordinates first std::pair index = ref.getCellIndex(); - ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second); + ref.mCell = ESM::RefId::stringRefId(ESM::RefId::esm3ExteriorCell(index.first, index.second).toString()); // Handle non-base moved references - if (!base && mref.mRefNum.mIndex != 0) + if (!base && isMoved) { // Moved references must have a link back to their original cell // See discussion: https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 @@ -46,12 +88,13 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // Log a warning if the record target cell is different if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1]) { - std::string indexCell = ref.mCell; - ref.mCell = "#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]); + ESM::RefId indexCell = ref.mCell; + ref.mCell = ESM::RefId::stringRefId( + ESM::RefId::esm3ExteriorCell(mref.mTarget[0], mref.mTarget[1]).toString()); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); - messages.add(id, "The position of the moved reference " + ref.mRefID + " (cell " + indexCell + ")" - " does not match the target cell (" + ref.mCell + ")", + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); + messages.add(id, "The position of the moved reference " + ref.mRefID.toDebugString() + " (cell " + indexCell.toDebugString() + ")" + " does not match the target cell (" + ref.mCell.toDebugString() + ")", std::string(), CSMDoc::Message::Severity_Warning); } } @@ -59,75 +102,123 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool else ref.mCell = cell2.mId; - mref.mRefNum.mIndex = 0; - - // ignore content file number - std::map::iterator iter = cache.begin(); - unsigned int thisIndex = ref.mRefNum.mIndex & 0x00ffffff; - if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; - - for (; iter != cache.end(); ++iter) + if (ref.mRefNum.mContentFile != -1 && !base) { - if (thisIndex == iter->first.mIndex) - break; + ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; + ref.mRefNum.mIndex &= 0x00ffffff; + } + + unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) + | (ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24; + + std::map::iterator iter = cache.find(refNum); + + if (isMoved) + { + if (iter == cache.end()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); + + messages.add(id, + "Attempt to move a non-existent reference - RefNum index " + std::to_string(ref.mRefNum.mIndex) + + ", refID " + ref.mRefID.toDebugString() + ", content file index " + + std::to_string(ref.mRefNum.mContentFile), + /*hint*/ "", CSMDoc::Message::Severity_Warning); + continue; + } + + int index = getIntIndex(iter->second); + + // ensure we have the same record id for setRecord() + ref.mId = getRecord(index).get().mId; + ref.mIdNum = extractIdNum(ref.mId.getRefIdString()); + + auto record = std::make_unique>(); + // TODO: check whether a base record be moved + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record->mBase : record->mModified) = std::move(ref); + + // overwrite original record + setRecord(index, std::move(record)); + + continue; // NOTE: assumed moved references are not deleted at the same time } if (isDeleted) { - if (iter==cache.end()) + if (iter == cache.end()) { - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, - mCells.getId (cellIndex)); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); - messages.add (id, "Attempt to delete a non-existent reference"); + messages.add(id, + "Attempt to delete a non-existent reference - RefNum index " + std::to_string(ref.mRefNum.mIndex) + + ", refID " + ref.mRefID.getRefIdString() + ", content file index " + + std::to_string(ref.mRefNum.mContentFile), + /*hint*/ "", CSMDoc::Message::Severity_Warning); continue; } - int index = getIndex (iter->second); - - Record record = getRecord (index); + int index = getIntIndex(iter->second); if (base) { - removeRows (index, 1); - cache.erase (iter); + removeRows(index, 1); + cache.erase(iter); } else { - record.mState = RecordBase::State_Deleted; - setRecord (index, record); + auto record = std::make_unique>(getRecord(index)); + record->mState = RecordBase::State_Deleted; + setRecord(index, std::move(record)); } continue; } - if (iter==cache.end()) + if (iter == cache.end()) { // new reference - ref.mId = getNewId(); + ref.mIdNum = mNextId; // FIXME: fragile + ref.mId = ESM::RefId::stringRefId(getNewId()); - Record record; - record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - const ESM::RefNum refNum = ref.mRefNum; - std::string refId = ref.mId; - (base ? record.mBase : record.mModified) = std::move(ref); + cache.emplace(refNum, ref.mIdNum); - appendRecord (record); + auto record = std::make_unique>(); + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record->mBase : record->mModified) = std::move(ref); - cache.emplace(refNum, std::move(refId)); + appendRecord(std::move(record)); } else { // old reference -> merge - ref.mId = iter->second; + int index = getIntIndex(iter->second); +#if 0 + // ref.mRefNum.mIndex : the key + // iter->second : previously cached idNum for the key + // index : position of the record for that idNum + // getRecord(index).get() : record in the index position + assert(iter->second != getRecord(index).get().mIdNum); // sanity check - int index = getIndex (ref.mId); + // check if the plugin used the same RefNum index for a different record + if (ref.mRefID != getRecord(index).get().mRefID) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex)); + messages.add(id, + "RefNum renamed from RefID \"" + getRecord(index).get().mRefID + "\" to \"" + + ref.mRefID + "\" (RefNum index " + std::to_string(ref.mRefNum.mIndex) + ")", + /*hint*/"", + CSMDoc::Message::Severity_Info); + } +#endif + ref.mId = getRecord(index).get().mId; + ref.mIdNum = extractIdNum(ref.mId.getRefIdString()); - Record record = getRecord (index); - record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; - (base ? record.mBase : record.mModified) = std::move(ref); + auto record = std::make_unique>(getRecord(index)); + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; + (base ? record->mBase : record->mModified) = std::move(ref); - setRecord (index, record); + setRecord(index, std::move(record)); } } } @@ -136,3 +227,115 @@ std::string CSMWorld::RefCollection::getNewId() { return "ref#" + std::to_string(mNextId++); } + +unsigned int CSMWorld::RefCollection::extractIdNum(std::string_view id) const +{ + std::string::size_type separator = id.find_last_of('#'); + + if (separator == std::string::npos) + throw std::runtime_error("invalid ref ID: " + std::string(id)); + + return Misc::StringUtils::toNumeric(id.substr(separator + 1), 0); +} + +int CSMWorld::RefCollection::getIntIndex(unsigned int id) const +{ + int index = searchId(id); + + if (index == -1) + throw std::runtime_error("invalid RefNum: " + std::to_string(id)); + + return index; +} + +int CSMWorld::RefCollection::searchId(unsigned int id) const +{ + std::map::const_iterator iter = mRefIndex.find(id); + + if (iter == mRefIndex.end()) + return -1; + + return iter->second; +} + +void CSMWorld::RefCollection::removeRows(int index, int count) +{ + Collection::removeRows(index, count); // erase records only + + std::map::iterator iter = mRefIndex.begin(); + while (iter != mRefIndex.end()) + { + if (iter->second >= index) + { + if (iter->second >= index + count) + { + iter->second -= count; + ++iter; + } + else + mRefIndex.erase(iter++); + } + else + ++iter; + } +} + +void CSMWorld::RefCollection::appendBlankRecord(const ESM::RefId& id, UniversalId::Type type) +{ + auto record = std::make_unique>(); + + record->mState = Record::State_ModifiedOnly; + record->mModified.blank(); + + record->get().mId = id; + record->get().mIdNum = extractIdNum(id.getRefIdString()); + + Collection::appendRecord(std::move(record)); +} + +void CSMWorld::RefCollection::cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) +{ + auto copy = std::make_unique>(); + + copy->mModified = getRecord(origin).get(); + copy->mState = RecordBase::State_ModifiedOnly; + + copy->get().mId = destination; + copy->get().mIdNum = extractIdNum(destination.getRefIdString()); + + insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord() +} + +int CSMWorld::RefCollection::searchId(const ESM::RefId& id) const +{ + return searchId(extractIdNum(id.getRefIdString())); +} + +void CSMWorld::RefCollection::appendRecord(std::unique_ptr record, UniversalId::Type type) +{ + int index = getAppendIndex(/*id*/ ESM::RefId(), type); // for CellRef records id is ignored + + mRefIndex.insert(std::make_pair(static_cast*>(record.get())->get().mIdNum, index)); + + Collection::insertRecord(std::move(record), index, type); // add records only +} + +void CSMWorld::RefCollection::insertRecord(std::unique_ptr record, int index, UniversalId::Type type) +{ + int size = getAppendIndex(/*id*/ ESM::RefId(), type); // for CellRef records id is ignored + unsigned int idNum = static_cast*>(record.get())->get().mIdNum; + + Collection::insertRecord(std::move(record), index, type); // add records only + + if (index < size - 1) + { + for (std::map::iterator iter(mRefIndex.begin()); iter != mRefIndex.end(); ++iter) + { + if (iter->second >= index) + ++(iter->second); + } + } + + mRefIndex.insert(std::make_pair(idNum, index)); +} diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index d031398d3..a5e5fd3b6 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -2,35 +2,78 @@ #define CSM_WOLRD_REFCOLLECTION_H #include +#include +#include +#include +#include -#include "../doc/stage.hpp" +#include #include "collection.hpp" -#include "ref.hpp" #include "record.hpp" +#include "ref.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace CSMDoc +{ + class Messages; +} namespace CSMWorld { struct Cell; - class UniversalId; + + template <> + void Collection::removeRows(int index, int count); + + template <> + void Collection::insertRecord(std::unique_ptr record, int index, UniversalId::Type type); /// \brief References in cells - class RefCollection : public Collection + class RefCollection final : public Collection { - Collection& mCells; - int mNextId; + Collection& mCells; + std::map mRefIndex; // CellRef index keyed by CSMWorld::CellRef::mIdNum - public: - // MSVC needs the constructor for a class inheriting a template to be defined in header - RefCollection (Collection& cells) - : mCells (cells), mNextId (0) - {} + int mNextId; - void load (ESM::ESMReader& reader, int cellIndex, bool base, - std::map& cache, CSMDoc::Messages& messages); - ///< Load a sequence of references. + unsigned int extractIdNum(std::string_view id) const; - std::string getNewId(); + int getIntIndex(unsigned int id) const; + + int searchId(unsigned int id) const; + + public: + // MSVC needs the constructor for a class inheriting a template to be defined in header + RefCollection(Collection& cells) + : mCells(cells) + , mNextId(0) + { + } + + void load(ESM::ESMReader& reader, int cellIndex, bool base, std::map& cache, + CSMDoc::Messages& messages); + ///< Load a sequence of references. + + std::string getNewId(); + + void removeRows(int index, int count) override; + + void appendBlankRecord(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) override; + + void cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) override; + + int searchId(const ESM::RefId& id) const override; + + void appendRecord(std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) override; + + void insertRecord( + std::unique_ptr record, int index, UniversalId::Type type = UniversalId::Type_None) override; }; } diff --git a/apps/opencs/model/world/refidadapter.cpp b/apps/opencs/model/world/refidadapter.cpp deleted file mode 100644 index 37cb67bca..000000000 --- a/apps/opencs/model/world/refidadapter.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "refidadapter.hpp" - -CSMWorld::RefIdAdapter::RefIdAdapter() {} - -CSMWorld::RefIdAdapter::~RefIdAdapter() {} - -CSMWorld::NestedRefIdAdapterBase::NestedRefIdAdapterBase() {} - -CSMWorld::NestedRefIdAdapterBase::~NestedRefIdAdapterBase() {} diff --git a/apps/opencs/model/world/refidadapter.hpp b/apps/opencs/model/world/refidadapter.hpp index 116adb69a..11ed82d0f 100644 --- a/apps/opencs/model/world/refidadapter.hpp +++ b/apps/opencs/model/world/refidadapter.hpp @@ -1,14 +1,17 @@ #ifndef CSM_WOLRD_REFIDADAPTER_H #define CSM_WOLRD_REFIDADAPTER_H +#include #include #include /*! \brief - * Adapters acts as indirection layer, abstracting details of the record types (in the wrappers) from the higher levels of model. - * Please notice that nested adaptor uses helper classes for actually performing any actions. Different record types require different helpers (needs to be created in the subclass and then fetched via member function). + * Adapters acts as indirection layer, abstracting details of the record types (in the wrappers) from the higher levels + * of model. Please notice that nested adaptor uses helper classes for actually performing any actions. Different record + * types require different helpers (needs to be created in the subclass and then fetched via member function). * - * Important point: don't forget to make sure that getData on the nestedColumn returns true (otherwise code will not treat the index pointing to the column as having children! + * Important point: don't forget to make sure that getData on the nestedColumn returns true (otherwise code will not + * treat the index pointing to the column as having children! */ class QVariant; @@ -19,61 +22,52 @@ namespace CSMWorld class RefIdData; struct RecordBase; struct NestedTableWrapperBase; - class HelperBase; class RefIdAdapter { - // not implemented - RefIdAdapter (const RefIdAdapter&); - RefIdAdapter& operator= (const RefIdAdapter&); + public: + RefIdAdapter() = default; + RefIdAdapter(const RefIdAdapter&) = delete; + RefIdAdapter& operator=(const RefIdAdapter&) = delete; + virtual ~RefIdAdapter() = default; - public: + virtual QVariant getData(const RefIdColumn* column, const RefIdData& data, int idnex) const = 0; + ///< If called on the nest column, should return QVariant(true). - RefIdAdapter(); + virtual void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const = 0; + ///< If the data type does not match an exception is thrown. - virtual ~RefIdAdapter(); + virtual ESM::RefId getId(const RecordBase& record) const = 0; - virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int idnex) - const = 0; - ///< If called on the nest column, should return QVariant(true). - - virtual void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const = 0; - ///< If the data type does not match an exception is thrown. - - virtual std::string getId (const RecordBase& record) const = 0; - - virtual void setId(RecordBase& record, const std::string& id) = 0; // used by RefIdCollection::cloneRecord() + virtual void setId(RecordBase& record, const std::string& id) = 0; // used by RefIdCollection::cloneRecord() }; class NestedRefIdAdapterBase { - public: - NestedRefIdAdapterBase(); + public: + NestedRefIdAdapterBase() = default; - virtual ~NestedRefIdAdapterBase(); + virtual ~NestedRefIdAdapterBase() = default; - virtual void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const = 0; + virtual void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, + int subRowIndex, int subColIndex) const = 0; - virtual QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const = 0; + virtual QVariant getNestedData( + const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const = 0; - virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const = 0; + virtual int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const = 0; - virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const = 0; + virtual int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const = 0; - virtual void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const = 0; + virtual void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const = 0; - virtual void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const = 0; + virtual void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const = 0; - virtual void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const = 0; + virtual void setNestedTable( + const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const = 0; - virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const = 0; + virtual NestedTableWrapperBase* nestedTable( + const RefIdColumn* column, const RefIdData& data, int index) const = 0; }; } diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 644092f16..149b5d19c 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1,52 +1,60 @@ #include "refidadapterimp.hpp" -#include #include -#include -#include -#include +#include +#include +#include +#include + +#include +#include +#include +#include #include "nestedtablewrapper.hpp" -CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) -: InventoryColumns (columns), mEffects(nullptr) {} - -CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const PotionColumns& columns, - const RefIdColumn *autoCalc) -: InventoryRefIdAdapter (UniversalId::Type_Potion, columns), - mColumns(columns), mAutoCalc (autoCalc) -{} - -QVariant CSMWorld::PotionRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const +CSMWorld::PotionColumns::PotionColumns(const InventoryColumns& columns) + : InventoryColumns(columns) + , mEffects(nullptr) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Potion))); - - if (column==mAutoCalc) - return record.get().mData.mAutoCalc!=0; - - // to show nested tables in dialogue subview, see IdTree::hasChildren() - if (column==mColumns.mEffects) - return QVariant::fromValue(ColumnBase::TableEdit_Full); - - return InventoryRefIdAdapter::getData (column, data, index); } -void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter(const PotionColumns& columns, const RefIdColumn* autoCalc) + : InventoryRefIdAdapter(UniversalId::Type_Potion, columns) + , mColumns(columns) + , mAutoCalc(autoCalc) { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Potion))); +} + +QVariant CSMWorld::PotionRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const +{ + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Potion))); + + if (column == mAutoCalc) + return record.get().mData.mAutoCalc != 0; + + // to show nested tables in dialogue subview, see IdTree::hasChildren() + if (column == mColumns.mEffects) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + return InventoryRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::PotionRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Potion))); ESM::Potion potion = record.get(); - if (column==mAutoCalc) + if (column == mAutoCalc) potion.mData.mAutoCalc = value.toInt(); else { - InventoryRefIdAdapter::setData (column, data, index, value); + InventoryRefIdAdapter::setData(column, data, index, value); return; } @@ -54,93 +62,91 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData record.setModified(potion); } - -CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) -: InventoryColumns (columns) -, mEffects(nullptr) -{} - -CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) -: InventoryRefIdAdapter (UniversalId::Type_Ingredient, columns), - mColumns(columns) -{} - -QVariant CSMWorld::IngredientRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const +CSMWorld::IngredientColumns::IngredientColumns(const InventoryColumns& columns) + : InventoryColumns(columns) + , mEffects(nullptr) { - if (column==mColumns.mEffects) - return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); - - return InventoryRefIdAdapter::getData (column, data, index); } -void CSMWorld::IngredientRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter(const IngredientColumns& columns) + : InventoryRefIdAdapter(UniversalId::Type_Ingredient, columns) + , mColumns(columns) { - InventoryRefIdAdapter::setData (column, data, index, value); +} + +QVariant CSMWorld::IngredientRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const +{ + if (column == mColumns.mEffects) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); + + return InventoryRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::IngredientRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + InventoryRefIdAdapter::setData(column, data, index, value); return; } - CSMWorld::IngredEffectRefIdAdapter::IngredEffectRefIdAdapter() -: mType(UniversalId::Type_Ingredient) -{} + : mType(UniversalId::Type_Ingredient) +{ +} -CSMWorld::IngredEffectRefIdAdapter::~IngredEffectRefIdAdapter() -{} - -void CSMWorld::IngredEffectRefIdAdapter::addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const +void CSMWorld::IngredEffectRefIdAdapter::addNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::IngredEffectRefIdAdapter::removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const +void CSMWorld::IngredEffectRefIdAdapter::removeNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +void CSMWorld::IngredEffectRefIdAdapter::setNestedTable( + const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESM::Ingredient ingredient = record.get(); - ingredient.mData = - static_cast >&>(nestedTable).mNestedTable.at(0); + ingredient.mData = static_cast>&>(nestedTable) + .mNestedTable.at(0); - record.setModified (ingredient); + record.setModified(ingredient); } -CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const +CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTable( + const RefIdColumn* column, const RefIdData& data, int index) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper>(wrap); } -QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const +QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData( + const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); if (subRowIndex < 0 || subRowIndex >= 4) - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); switch (subColIndex) { - case 0: return record.get().mData.mEffectID[subRowIndex]; + case 0: + return record.get().mData.mEffectID[subRowIndex]; case 1: { switch (record.get().mData.mEffectID[subRowIndex]) @@ -174,127 +180,135 @@ QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *c } } -void CSMWorld::IngredEffectRefIdAdapter::setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +void CSMWorld::IngredEffectRefIdAdapter::setNestedData( + const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); ESM::Ingredient ingredient = record.get(); if (subRowIndex < 0 || subRowIndex >= 4) - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); - switch(subColIndex) + switch (subColIndex) { - case 0: ingredient.mData.mEffectID[subRowIndex] = value.toInt(); break; - case 1: ingredient.mData.mSkills[subRowIndex] = value.toInt(); break; - case 2: ingredient.mData.mAttributes[subRowIndex] = value.toInt(); break; + case 0: + ingredient.mData.mEffectID[subRowIndex] = value.toInt(); + break; + case 1: + ingredient.mData.mSkills[subRowIndex] = value.toInt(); + break; + case 2: + ingredient.mData.mAttributes[subRowIndex] = value.toInt(); + break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } - record.setModified (ingredient); + record.setModified(ingredient); } -int CSMWorld::IngredEffectRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +int CSMWorld::IngredEffectRefIdAdapter::getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const { return 3; // effect, skill, attribute } -int CSMWorld::IngredEffectRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int CSMWorld::IngredEffectRefIdAdapter::getNestedRowsCount( + const RefIdColumn* column, const RefIdData& data, int index) const { return 4; // up to 4 effects } - -CSMWorld::ApparatusRefIdAdapter::ApparatusRefIdAdapter (const InventoryColumns& columns, - const RefIdColumn *type, const RefIdColumn *quality) -: InventoryRefIdAdapter (UniversalId::Type_Apparatus, columns), - mType (type), mQuality (quality) -{} - -QVariant CSMWorld::ApparatusRefIdAdapter::getData (const RefIdColumn *column, - const RefIdData& data, int index) const +CSMWorld::ApparatusRefIdAdapter::ApparatusRefIdAdapter( + const InventoryColumns& columns, const RefIdColumn* type, const RefIdColumn* quality) + : InventoryRefIdAdapter(UniversalId::Type_Apparatus, columns) + , mType(type) + , mQuality(quality) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Apparatus))); - - if (column==mType) - return record.get().mData.mType; - - if (column==mQuality) - return record.get().mData.mQuality; - - return InventoryRefIdAdapter::getData (column, data, index); } -void CSMWorld::ApparatusRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +QVariant CSMWorld::ApparatusRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Apparatus))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Apparatus))); + + if (column == mType) + return record.get().mData.mType; + + if (column == mQuality) + return record.get().mData.mQuality; + + return InventoryRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::ApparatusRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Apparatus))); ESM::Apparatus apparatus = record.get(); - if (column==mType) + if (column == mType) apparatus.mData.mType = value.toInt(); - else if (column==mQuality) + else if (column == mQuality) apparatus.mData.mQuality = value.toFloat(); else { - InventoryRefIdAdapter::setData (column, data, index, value); + InventoryRefIdAdapter::setData(column, data, index, value); return; } record.setModified(apparatus); } - -CSMWorld::ArmorRefIdAdapter::ArmorRefIdAdapter (const EnchantableColumns& columns, - const RefIdColumn *type, const RefIdColumn *health, const RefIdColumn *armor, - const RefIdColumn *partRef) -: EnchantableRefIdAdapter (UniversalId::Type_Armor, columns), - mType (type), mHealth (health), mArmor (armor), mPartRef(partRef) -{} - -QVariant CSMWorld::ArmorRefIdAdapter::getData (const RefIdColumn *column, - const RefIdData& data, int index) const +CSMWorld::ArmorRefIdAdapter::ArmorRefIdAdapter(const EnchantableColumns& columns, const RefIdColumn* type, + const RefIdColumn* health, const RefIdColumn* armor, const RefIdColumn* partRef) + : EnchantableRefIdAdapter(UniversalId::Type_Armor, columns) + , mType(type) + , mHealth(health) + , mArmor(armor) + , mPartRef(partRef) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Armor))); - - if (column==mType) - return record.get().mData.mType; - - if (column==mHealth) - return record.get().mData.mHealth; - - if (column==mArmor) - return record.get().mData.mArmor; - - if (column==mPartRef) - return QVariant::fromValue(ColumnBase::TableEdit_Full); - - return EnchantableRefIdAdapter::getData (column, data, index); } -void CSMWorld::ArmorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +QVariant CSMWorld::ArmorRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Armor))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Armor))); + + if (column == mType) + return record.get().mData.mType; + + if (column == mHealth) + return record.get().mData.mHealth; + + if (column == mArmor) + return record.get().mData.mArmor; + + if (column == mPartRef) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + return EnchantableRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::ArmorRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Armor))); ESM::Armor armor = record.get(); - if (column==mType) + if (column == mType) armor.mData.mType = value.toInt(); - else if (column==mHealth) + else if (column == mHealth) armor.mData.mHealth = value.toInt(); - else if (column==mArmor) + else if (column == mArmor) armor.mData.mArmor = value.toInt(); else { - EnchantableRefIdAdapter::setData (column, data, index, value); + EnchantableRefIdAdapter::setData(column, data, index, value); return; } @@ -302,47 +316,49 @@ void CSMWorld::ArmorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& record.setModified(armor); } -CSMWorld::BookRefIdAdapter::BookRefIdAdapter (const EnchantableColumns& columns, - const RefIdColumn *bookType, const RefIdColumn *skill, const RefIdColumn *text) -: EnchantableRefIdAdapter (UniversalId::Type_Book, columns), - mBookType (bookType), mSkill (skill), mText (text) -{} - -QVariant CSMWorld::BookRefIdAdapter::getData (const RefIdColumn *column, - const RefIdData& data, int index) const +CSMWorld::BookRefIdAdapter::BookRefIdAdapter( + const EnchantableColumns& columns, const RefIdColumn* bookType, const RefIdColumn* skill, const RefIdColumn* text) + : EnchantableRefIdAdapter(UniversalId::Type_Book, columns) + , mBookType(bookType) + , mSkill(skill) + , mText(text) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Book))); - - if (column==mBookType) - return record.get().mData.mIsScroll; - - if (column==mSkill) - return record.get().mData.mSkillId; - - if (column==mText) - return QString::fromUtf8 (record.get().mText.c_str()); - - return EnchantableRefIdAdapter::getData (column, data, index); } -void CSMWorld::BookRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +QVariant CSMWorld::BookRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Book))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Book))); + + if (column == mBookType) + return record.get().mData.mIsScroll; + + if (column == mSkill) + return record.get().mData.mSkillId; + + if (column == mText) + return QString::fromUtf8(record.get().mText.c_str()); + + return EnchantableRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::BookRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Book))); ESM::Book book = record.get(); - if (column==mBookType) + if (column == mBookType) book.mData.mIsScroll = value.toInt(); - else if (column==mSkill) + else if (column == mSkill) book.mData.mSkillId = value.toInt(); - else if (column==mText) + else if (column == mText) book.mText = value.toString().toUtf8().data(); else { - EnchantableRefIdAdapter::setData (column, data, index, value); + EnchantableRefIdAdapter::setData(column, data, index, value); return; } @@ -350,40 +366,41 @@ void CSMWorld::BookRefIdAdapter::setData (const RefIdColumn *column, RefIdData& record.setModified(book); } -CSMWorld::ClothingRefIdAdapter::ClothingRefIdAdapter (const EnchantableColumns& columns, - const RefIdColumn *type, const RefIdColumn *partRef) -: EnchantableRefIdAdapter (UniversalId::Type_Clothing, columns), mType (type), - mPartRef(partRef) -{} - -QVariant CSMWorld::ClothingRefIdAdapter::getData (const RefIdColumn *column, - const RefIdData& data, int index) const +CSMWorld::ClothingRefIdAdapter::ClothingRefIdAdapter( + const EnchantableColumns& columns, const RefIdColumn* type, const RefIdColumn* partRef) + : EnchantableRefIdAdapter(UniversalId::Type_Clothing, columns) + , mType(type) + , mPartRef(partRef) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Clothing))); - - if (column==mType) - return record.get().mData.mType; - - if (column==mPartRef) - return QVariant::fromValue(ColumnBase::TableEdit_Full); - - return EnchantableRefIdAdapter::getData (column, data, index); } -void CSMWorld::ClothingRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +QVariant CSMWorld::ClothingRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Clothing))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Clothing))); + + if (column == mType) + return record.get().mData.mType; + + if (column == mPartRef) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + return EnchantableRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::ClothingRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Clothing))); ESM::Clothing clothing = record.get(); - if (column==mType) + if (column == mType) clothing.mData.mType = value.toInt(); else { - EnchantableRefIdAdapter::setData (column, data, index, value); + EnchantableRefIdAdapter::setData(column, data, index, value); return; } @@ -391,52 +408,54 @@ void CSMWorld::ClothingRefIdAdapter::setData (const RefIdColumn *column, RefIdDa record.setModified(clothing); } -CSMWorld::ContainerRefIdAdapter::ContainerRefIdAdapter (const NameColumns& columns, - const RefIdColumn *weight, const RefIdColumn *organic, const RefIdColumn *respawn, const RefIdColumn *content) -: NameRefIdAdapter (UniversalId::Type_Container, columns), mWeight (weight), - mOrganic (organic), mRespawn (respawn), mContent(content) -{} - -QVariant CSMWorld::ContainerRefIdAdapter::getData (const RefIdColumn *column, - const RefIdData& data, - int index) const +CSMWorld::ContainerRefIdAdapter::ContainerRefIdAdapter(const NameColumns& columns, const RefIdColumn* weight, + const RefIdColumn* organic, const RefIdColumn* respawn, const RefIdColumn* content) + : NameRefIdAdapter(UniversalId::Type_Container, columns) + , mWeight(weight) + , mOrganic(organic) + , mRespawn(respawn) + , mContent(content) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Container))); - - if (column==mWeight) - return record.get().mWeight; - - if (column==mOrganic) - return (record.get().mFlags & ESM::Container::Organic)!=0; - - if (column==mRespawn) - return (record.get().mFlags & ESM::Container::Respawn)!=0; - - if (column==mContent) - return QVariant::fromValue(ColumnBase::TableEdit_Full); - - return NameRefIdAdapter::getData (column, data, index); } -void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +QVariant CSMWorld::ContainerRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Container))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Container))); + + if (column == mWeight) + return record.get().mWeight; + + if (column == mOrganic) + return (record.get().mFlags & ESM::Container::Organic) != 0; + + if (column == mRespawn) + return (record.get().mFlags & ESM::Container::Respawn) != 0; + + if (column == mContent) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + return NameRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::ContainerRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Container))); ESM::Container container = record.get(); - if (column==mWeight) + if (column == mWeight) container.mWeight = value.toFloat(); - else if (column==mOrganic) + else if (column == mOrganic) { if (value.toInt()) container.mFlags |= ESM::Container::Organic; else container.mFlags &= ~ESM::Container::Organic; } - else if (column==mRespawn) + else if (column == mRespawn) { if (value.toInt()) container.mFlags |= ESM::Container::Respawn; @@ -445,7 +464,7 @@ void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdD } else { - NameRefIdAdapter::setData (column, data, index, value); + NameRefIdAdapter::setData(column, data, index, value); return; } @@ -453,88 +472,88 @@ void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdD record.setModified(container); } -CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) -: ActorColumns (actorColumns), - mType(nullptr), - mScale(nullptr), - mOriginal(nullptr), - mAttributes(nullptr), - mAttacks(nullptr), - mMisc(nullptr), - mBloodType(nullptr) -{} - -CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) -: ActorRefIdAdapter (UniversalId::Type_Creature, columns), mColumns (columns) -{} - -QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const +CSMWorld::CreatureColumns::CreatureColumns(const ActorColumns& actorColumns) + : ActorColumns(actorColumns) + , mType(nullptr) + , mScale(nullptr) + , mOriginal(nullptr) + , mAttributes(nullptr) + , mAttacks(nullptr) + , mMisc(nullptr) + , mBloodType(nullptr) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); +} - if (column==mColumns.mType) +CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter(const CreatureColumns& columns) + : ActorRefIdAdapter(UniversalId::Type_Creature, columns) + , mColumns(columns) +{ +} + +QVariant CSMWorld::CreatureRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const +{ + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); + + if (column == mColumns.mType) return record.get().mData.mType; - if (column==mColumns.mScale) + if (column == mColumns.mScale) return record.get().mScale; - if (column==mColumns.mOriginal) - return QString::fromUtf8 (record.get().mOriginal.c_str()); + if (column == mColumns.mOriginal) + return QString::fromUtf8(record.get().mOriginal.getRefIdString().c_str()); - if (column==mColumns.mAttributes) + if (column == mColumns.mAttributes) return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); - if (column==mColumns.mAttacks) + if (column == mColumns.mAttacks) return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); - if (column==mColumns.mMisc) + if (column == mColumns.mMisc) return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column == mColumns.mBloodType) return record.get().mBloodType; - std::map::const_iterator iter = - mColumns.mFlags.find (column); + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter!=mColumns.mFlags.end()) - return (record.get().mFlags & iter->second)!=0; + if (iter != mColumns.mFlags.end()) + return (record.get().mFlags & iter->second) != 0; - return ActorRefIdAdapter::getData (column, data, index); + return ActorRefIdAdapter::getData(column, data, index); } -void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +void CSMWorld::CreatureRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); - if (column==mColumns.mType) + if (column == mColumns.mType) creature.mData.mType = value.toInt(); - else if (column==mColumns.mScale) + else if (column == mColumns.mScale) creature.mScale = value.toFloat(); - else if (column==mColumns.mOriginal) - creature.mOriginal = value.toString().toUtf8().constData(); + else if (column == mColumns.mOriginal) + creature.mOriginal = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); else if (column == mColumns.mBloodType) creature.mBloodType = value.toInt(); else { - std::map::const_iterator iter = - mColumns.mFlags.find (column); + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter!=mColumns.mFlags.end()) + if (iter != mColumns.mFlags.end()) { - if (value.toInt()!=0) + if (value.toInt() != 0) creature.mFlags |= iter->second; else creature.mFlags &= ~iter->second; } else { - ActorRefIdAdapter::setData (column, data, index, value); + ActorRefIdAdapter::setData(column, data, index, value); return; } @@ -543,42 +562,43 @@ void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdDa record.setModified(creature); } -CSMWorld::DoorRefIdAdapter::DoorRefIdAdapter (const NameColumns& columns, - const RefIdColumn *openSound, const RefIdColumn *closeSound) -: NameRefIdAdapter (UniversalId::Type_Door, columns), mOpenSound (openSound), - mCloseSound (closeSound) -{} - -QVariant CSMWorld::DoorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const +CSMWorld::DoorRefIdAdapter::DoorRefIdAdapter( + const NameColumns& columns, const RefIdColumn* openSound, const RefIdColumn* closeSound) + : NameRefIdAdapter(UniversalId::Type_Door, columns) + , mOpenSound(openSound) + , mCloseSound(closeSound) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Door))); - - if (column==mOpenSound) - return QString::fromUtf8 (record.get().mOpenSound.c_str()); - - if (column==mCloseSound) - return QString::fromUtf8 (record.get().mCloseSound.c_str()); - - return NameRefIdAdapter::getData (column, data, index); } -void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +QVariant CSMWorld::DoorRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Door))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Door))); + + if (column == mOpenSound) + return QString::fromUtf8(record.get().mOpenSound.getRefIdString().c_str()); + + if (column == mCloseSound) + return QString::fromUtf8(record.get().mCloseSound.getRefIdString().c_str()); + + return NameRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::DoorRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Door))); ESM::Door door = record.get(); - if (column==mOpenSound) - door.mOpenSound = value.toString().toUtf8().constData(); - else if (column==mCloseSound) - door.mCloseSound = value.toString().toUtf8().constData(); + if (column == mOpenSound) + door.mOpenSound = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); + else if (column == mCloseSound) + door.mCloseSound = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); else { - NameRefIdAdapter::setData (column, data, index, value); + NameRefIdAdapter::setData(column, data, index, value); return; } @@ -586,36 +606,38 @@ void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& record.setModified(door); } -CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns) -: InventoryColumns (columns) -, mTime(nullptr) -, mRadius(nullptr) -, mColor(nullptr) -, mSound(nullptr) -, mEmitterType(nullptr) -{} - -CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns) -: InventoryRefIdAdapter (UniversalId::Type_Light, columns), mColumns (columns) -{} - -QVariant CSMWorld::LightRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const +CSMWorld::LightColumns::LightColumns(const InventoryColumns& columns) + : InventoryColumns(columns) + , mTime(nullptr) + , mRadius(nullptr) + , mColor(nullptr) + , mSound(nullptr) + , mEmitterType(nullptr) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Light))); +} - if (column==mColumns.mTime) +CSMWorld::LightRefIdAdapter::LightRefIdAdapter(const LightColumns& columns) + : InventoryRefIdAdapter(UniversalId::Type_Light, columns) + , mColumns(columns) +{ +} + +QVariant CSMWorld::LightRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const +{ + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Light))); + + if (column == mColumns.mTime) return record.get().mData.mTime; - if (column==mColumns.mRadius) + if (column == mColumns.mRadius) return record.get().mData.mRadius; - if (column==mColumns.mColor) + if (column == mColumns.mColor) return record.get().mData.mColor; - if (column==mColumns.mSound) - return QString::fromUtf8 (record.get().mSound.c_str()); + if (column == mColumns.mSound) + return QString::fromUtf8(record.get().mSound.getRefIdString().c_str()); if (column == mColumns.mEmitterType) { @@ -636,31 +658,30 @@ QVariant CSMWorld::LightRefIdAdapter::getData (const RefIdColumn *column, const return 0; } - std::map::const_iterator iter = - mColumns.mFlags.find (column); + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter!=mColumns.mFlags.end()) - return (record.get().mData.mFlags & iter->second)!=0; + if (iter != mColumns.mFlags.end()) + return (record.get().mData.mFlags & iter->second) != 0; - return InventoryRefIdAdapter::getData (column, data, index); + return InventoryRefIdAdapter::getData(column, data, index); } -void CSMWorld::LightRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +void CSMWorld::LightRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Light))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Light))); ESM::Light light = record.get(); - if (column==mColumns.mTime) + if (column == mColumns.mTime) light.mData.mTime = value.toInt(); - else if (column==mColumns.mRadius) + else if (column == mColumns.mRadius) light.mData.mRadius = value.toInt(); - else if (column==mColumns.mColor) + else if (column == mColumns.mColor) light.mData.mColor = value.toInt(); - else if (column==mColumns.mSound) - light.mSound = value.toString().toUtf8().constData(); + else if (column == mColumns.mSound) + light.mSound = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); else if (column == mColumns.mEmitterType) { int mask = ~(ESM::Light::Flicker | ESM::Light::FlickerSlow | ESM::Light::Pulse | ESM::Light::PulseSlow); @@ -678,56 +699,56 @@ void CSMWorld::LightRefIdAdapter::setData (const RefIdColumn *column, RefIdData& } else { - std::map::const_iterator iter = - mColumns.mFlags.find (column); + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter!=mColumns.mFlags.end()) + if (iter != mColumns.mFlags.end()) { - if (value.toInt()!=0) + if (value.toInt() != 0) light.mData.mFlags |= iter->second; else light.mData.mFlags &= ~iter->second; } else { - InventoryRefIdAdapter::setData (column, data, index, value); + InventoryRefIdAdapter::setData(column, data, index, value); return; } } - record.setModified (light); + record.setModified(light); } -CSMWorld::MiscRefIdAdapter::MiscRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *key) -: InventoryRefIdAdapter (UniversalId::Type_Miscellaneous, columns), mKey (key) -{} - -QVariant CSMWorld::MiscRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const +CSMWorld::MiscRefIdAdapter::MiscRefIdAdapter(const InventoryColumns& columns, const RefIdColumn* key) + : InventoryRefIdAdapter(UniversalId::Type_Miscellaneous, columns) + , mKey(key) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Miscellaneous))); - - if (column==mKey) - return record.get().mData.mIsKey!=0; - - return InventoryRefIdAdapter::getData (column, data, index); } -void CSMWorld::MiscRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +QVariant CSMWorld::MiscRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Miscellaneous))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Miscellaneous))); + + if (column == mKey) + return bool(record.get().mData.mFlags & ESM::Miscellaneous::Key); + + return InventoryRefIdAdapter::getData(column, data, index); +} + +void CSMWorld::MiscRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const +{ + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Miscellaneous))); ESM::Miscellaneous misc = record.get(); - if (column==mKey) - misc.mData.mIsKey = value.toInt(); + if (column == mKey) + misc.mData.mFlags = value.toInt(); else { - InventoryRefIdAdapter::setData (column, data, index, value); + InventoryRefIdAdapter::setData(column, data, index, value); return; } @@ -735,46 +756,48 @@ void CSMWorld::MiscRefIdAdapter::setData (const RefIdColumn *column, RefIdData& record.setModified(misc); } -CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) -: ActorColumns (actorColumns), - mRace(nullptr), - mClass(nullptr), - mFaction(nullptr), - mHair(nullptr), - mHead(nullptr), - mAttributes(nullptr), - mSkills(nullptr), - mMisc(nullptr), - mBloodType(nullptr), - mGender(nullptr) -{} - -CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) -: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) -{} - -QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) - const +CSMWorld::NpcColumns::NpcColumns(const ActorColumns& actorColumns) + : ActorColumns(actorColumns) + , mRace(nullptr) + , mClass(nullptr) + , mFaction(nullptr) + , mHair(nullptr) + , mHead(nullptr) + , mAttributes(nullptr) + , mSkills(nullptr) + , mMisc(nullptr) + , mBloodType(nullptr) + , mGender(nullptr) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); +} - if (column==mColumns.mRace) - return QString::fromUtf8 (record.get().mRace.c_str()); +CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter(const NpcColumns& columns) + : ActorRefIdAdapter(UniversalId::Type_Npc, columns) + , mColumns(columns) +{ +} - if (column==mColumns.mClass) - return QString::fromUtf8 (record.get().mClass.c_str()); +QVariant CSMWorld::NpcRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const +{ + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); - if (column==mColumns.mFaction) - return QString::fromUtf8 (record.get().mFaction.c_str()); + if (column == mColumns.mRace) + return QString::fromUtf8(record.get().mRace.getRefIdString().c_str()); - if (column==mColumns.mHair) - return QString::fromUtf8 (record.get().mHair.c_str()); + if (column == mColumns.mClass) + return QString::fromUtf8(record.get().mClass.getRefIdString().c_str()); - if (column==mColumns.mHead) - return QString::fromUtf8 (record.get().mHead.c_str()); + if (column == mColumns.mFaction) + return QString::fromUtf8(record.get().mFaction.getRefIdString().c_str()); - if (column==mColumns.mAttributes || column==mColumns.mSkills) + if (column == mColumns.mHair) + return QString::fromUtf8(record.get().mHair.getRefIdString().c_str()); + + if (column == mColumns.mHead) + return QString::fromUtf8(record.get().mHead.getRefIdString().c_str()); + + if (column == mColumns.mAttributes || column == mColumns.mSkills) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) return QVariant::fromValue(ColumnBase::TableEdit_None); @@ -782,7 +805,7 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); } - if (column==mColumns.mMisc) + if (column == mColumns.mMisc) return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column == mColumns.mBloodType) @@ -797,33 +820,32 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re return 0; } - std::map::const_iterator iter = - mColumns.mFlags.find (column); + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter!=mColumns.mFlags.end()) - return (record.get().mFlags & iter->second)!=0; + if (iter != mColumns.mFlags.end()) + return (record.get().mFlags & iter->second) != 0; - return ActorRefIdAdapter::getData (column, data, index); + return ActorRefIdAdapter::getData(column, data, index); } -void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +void CSMWorld::NpcRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); - if (column==mColumns.mRace) - npc.mRace = value.toString().toUtf8().constData(); - else if (column==mColumns.mClass) - npc.mClass = value.toString().toUtf8().constData(); - else if (column==mColumns.mFaction) - npc.mFaction = value.toString().toUtf8().constData(); - else if (column==mColumns.mHair) - npc.mHair = value.toString().toUtf8().constData(); - else if (column==mColumns.mHead) - npc.mHead = value.toString().toUtf8().constData(); + if (column == mColumns.mRace) + npc.mRace = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); + else if (column == mColumns.mClass) + npc.mClass = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); + else if (column == mColumns.mFaction) + npc.mFaction = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); + else if (column == mColumns.mHair) + npc.mHair = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); + else if (column == mColumns.mHead) + npc.mHead = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); else if (column == mColumns.mBloodType) npc.mBloodType = value.toInt(); else if (column == mColumns.mGender) @@ -836,78 +858,73 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d } else { - std::map::const_iterator iter = - mColumns.mFlags.find (column); + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter!=mColumns.mFlags.end()) + if (iter != mColumns.mFlags.end()) { - if (value.toInt()!=0) + if (value.toInt() != 0) npc.mFlags |= iter->second; else npc.mFlags &= ~iter->second; if (iter->second == ESM::NPC::Autocalc) - npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS - : ESM::NPC::NPC_DEFAULT; + npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS : ESM::NPC::NPC_DEFAULT; } else { - ActorRefIdAdapter::setData (column, data, index, value); + ActorRefIdAdapter::setData(column, data, index, value); return; } } - record.setModified (npc); + record.setModified(npc); } -CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter () -{} - -void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const +void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::NpcAttributesRefIdAdapter::removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const +void CSMWorld::NpcAttributesRefIdAdapter::removeNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::NpcAttributesRefIdAdapter::setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +void CSMWorld::NpcAttributesRefIdAdapter::setNestedTable( + const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); // store the whole struct - npc.mNpdt = - static_cast > &>(nestedTable).mNestedTable.at(0); + npc.mNpdt + = static_cast>&>(nestedTable).mNestedTable.at(0); - record.setModified (npc); + record.setModified(npc); } -CSMWorld::NestedTableWrapperBase* CSMWorld::NpcAttributesRefIdAdapter::nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const +CSMWorld::NestedTableWrapperBase* CSMWorld::NpcAttributesRefIdAdapter::nestedTable( + const RefIdColumn* column, const RefIdData& data, int index) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mNpdt); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper>(wrap); } -QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const +QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData( + const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt; @@ -916,110 +933,133 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * else if (subColIndex == 1) switch (subRowIndex) { - case 0: return static_cast(npcStruct.mStrength); - case 1: return static_cast(npcStruct.mIntelligence); - case 2: return static_cast(npcStruct.mWillpower); - case 3: return static_cast(npcStruct.mAgility); - case 4: return static_cast(npcStruct.mSpeed); - case 5: return static_cast(npcStruct.mEndurance); - case 6: return static_cast(npcStruct.mPersonality); - case 7: return static_cast(npcStruct.mLuck); - default: return QVariant(); // throw an exception here? + case 0: + return static_cast(npcStruct.mStrength); + case 1: + return static_cast(npcStruct.mIntelligence); + case 2: + return static_cast(npcStruct.mWillpower); + case 3: + return static_cast(npcStruct.mAgility); + case 4: + return static_cast(npcStruct.mSpeed); + case 5: + return static_cast(npcStruct.mEndurance); + case 6: + return static_cast(npcStruct.mPersonality); + case 7: + return static_cast(npcStruct.mLuck); + default: + return QVariant(); // throw an exception here? } else return QVariant(); // throw an exception here? } -void CSMWorld::NpcAttributesRefIdAdapter::setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +void CSMWorld::NpcAttributesRefIdAdapter::setNestedData( + const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt; if (subColIndex == 1) - switch(subRowIndex) + switch (subRowIndex) { - case 0: npcStruct.mStrength = static_cast(value.toInt()); break; - case 1: npcStruct.mIntelligence = static_cast(value.toInt()); break; - case 2: npcStruct.mWillpower = static_cast(value.toInt()); break; - case 3: npcStruct.mAgility = static_cast(value.toInt()); break; - case 4: npcStruct.mSpeed = static_cast(value.toInt()); break; - case 5: npcStruct.mEndurance = static_cast(value.toInt()); break; - case 6: npcStruct.mPersonality = static_cast(value.toInt()); break; - case 7: npcStruct.mLuck = static_cast(value.toInt()); break; - default: return; // throw an exception here? + case 0: + npcStruct.mStrength = static_cast(value.toInt()); + break; + case 1: + npcStruct.mIntelligence = static_cast(value.toInt()); + break; + case 2: + npcStruct.mWillpower = static_cast(value.toInt()); + break; + case 3: + npcStruct.mAgility = static_cast(value.toInt()); + break; + case 4: + npcStruct.mSpeed = static_cast(value.toInt()); + break; + case 5: + npcStruct.mEndurance = static_cast(value.toInt()); + break; + case 6: + npcStruct.mPersonality = static_cast(value.toInt()); + break; + case 7: + npcStruct.mLuck = static_cast(value.toInt()); + break; + default: + return; // throw an exception here? } else return; // throw an exception here? - record.setModified (npc); + record.setModified(npc); } -int CSMWorld::NpcAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +int CSMWorld::NpcAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const { return 2; } -int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount( + const RefIdColumn* column, const RefIdData& data, int index) const { - // There are 8 attributes - return 8; + return ESM::Attribute::Length; } -CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter () -{} - -void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const +void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::NpcSkillsRefIdAdapter::removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const +void CSMWorld::NpcSkillsRefIdAdapter::removeNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::NpcSkillsRefIdAdapter::setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +void CSMWorld::NpcSkillsRefIdAdapter::setNestedTable( + const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); // store the whole struct - npc.mNpdt = - static_cast > &>(nestedTable).mNestedTable.at(0); + npc.mNpdt + = static_cast>&>(nestedTable).mNestedTable.at(0); - record.setModified (npc); + record.setModified(npc); } -CSMWorld::NestedTableWrapperBase* CSMWorld::NpcSkillsRefIdAdapter::nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const +CSMWorld::NestedTableWrapperBase* CSMWorld::NpcSkillsRefIdAdapter::nestedTable( + const RefIdColumn* column, const RefIdData& data, int index) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mNpdt); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper>(wrap); } -QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const +QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData( + const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt; if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); if (subColIndex == 0) return subRowIndex; @@ -1029,202 +1069,239 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu return QVariant(); // throw an exception here? } -void CSMWorld::NpcSkillsRefIdAdapter::setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +void CSMWorld::NpcSkillsRefIdAdapter::setNestedData( + const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt; if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); if (subColIndex == 1) npcStruct.mSkills[subRowIndex] = static_cast(value.toInt()); else return; // throw an exception here? - record.setModified (npc); + record.setModified(npc); } -int CSMWorld::NpcSkillsRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +int CSMWorld::NpcSkillsRefIdAdapter::getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const { return 2; } -int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount( + const RefIdColumn* column, const RefIdData& data, int index) const { // There are 27 skills return ESM::Skill::Length; } -CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter () -{} - -CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter() -{} - -void CSMWorld::NpcMiscRefIdAdapter::addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const +void CSMWorld::NpcMiscRefIdAdapter::addNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int position) const { - throw std::logic_error ("cannot add a row to a fixed table"); + throw std::logic_error("cannot add a row to a fixed table"); } -void CSMWorld::NpcMiscRefIdAdapter::removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const +void CSMWorld::NpcMiscRefIdAdapter::removeNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const { - throw std::logic_error ("cannot remove a row to a fixed table"); + throw std::logic_error("cannot remove a row to a fixed table"); } -void CSMWorld::NpcMiscRefIdAdapter::setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +void CSMWorld::NpcMiscRefIdAdapter::setNestedTable( + const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } -CSMWorld::NestedTableWrapperBase* CSMWorld::NpcMiscRefIdAdapter::nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const +CSMWorld::NestedTableWrapperBase* CSMWorld::NpcMiscRefIdAdapter::nestedTable( + const RefIdColumn* column, const RefIdData& data, int index) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } -QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const +QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData( + const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Npc))); bool autoCalc = (record.get().mFlags & ESM::NPC::Autocalc) != 0; if (autoCalc) switch (subColIndex) { - case 0: return static_cast(record.get().mNpdt.mLevel); - case 1: return QVariant(QVariant::UserType); - case 2: return QVariant(QVariant::UserType); - case 3: return QVariant(QVariant::UserType); - case 4: return static_cast(record.get().mNpdt.mDisposition); - case 5: return static_cast(record.get().mNpdt.mReputation); - case 6: return static_cast(record.get().mNpdt.mRank); - case 7: return record.get().mNpdt.mGold; - case 8: return record.get().mPersistent == true; - default: return QVariant(); // throw an exception here? + case 0: + return static_cast(record.get().mNpdt.mLevel); + case 1: + return QVariant(QVariant::UserType); + case 2: + return QVariant(QVariant::UserType); + case 3: + return QVariant(QVariant::UserType); + case 4: + return static_cast(record.get().mNpdt.mDisposition); + case 5: + return static_cast(record.get().mNpdt.mReputation); + case 6: + return static_cast(record.get().mNpdt.mRank); + case 7: + return record.get().mNpdt.mGold; + default: + return QVariant(); // throw an exception here? } else switch (subColIndex) { - case 0: return static_cast(record.get().mNpdt.mLevel); - case 1: return static_cast(record.get().mNpdt.mHealth); - case 2: return static_cast(record.get().mNpdt.mMana); - case 3: return static_cast(record.get().mNpdt.mFatigue); - case 4: return static_cast(record.get().mNpdt.mDisposition); - case 5: return static_cast(record.get().mNpdt.mReputation); - case 6: return static_cast(record.get().mNpdt.mRank); - case 7: return record.get().mNpdt.mGold; - case 8: return record.get().mPersistent == true; - default: return QVariant(); // throw an exception here? + case 0: + return static_cast(record.get().mNpdt.mLevel); + case 1: + return static_cast(record.get().mNpdt.mHealth); + case 2: + return static_cast(record.get().mNpdt.mMana); + case 3: + return static_cast(record.get().mNpdt.mFatigue); + case 4: + return static_cast(record.get().mNpdt.mDisposition); + case 5: + return static_cast(record.get().mNpdt.mReputation); + case 6: + return static_cast(record.get().mNpdt.mRank); + case 7: + return record.get().mNpdt.mGold; + default: + return QVariant(); // throw an exception here? } } -void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +void CSMWorld::NpcMiscRefIdAdapter::setNestedData( + const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Npc))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, UniversalId::Type_Npc))); ESM::NPC npc = record.get(); bool autoCalc = (record.get().mFlags & ESM::NPC::Autocalc) != 0; if (autoCalc) - switch(subColIndex) + switch (subColIndex) { - case 0: npc.mNpdt.mLevel = static_cast(value.toInt()); break; - case 1: return; - case 2: return; - case 3: return; - case 4: npc.mNpdt.mDisposition = static_cast(value.toInt()); break; - case 5: npc.mNpdt.mReputation = static_cast(value.toInt()); break; - case 6: npc.mNpdt.mRank = static_cast(value.toInt()); break; - case 7: npc.mNpdt.mGold = value.toInt(); break; - case 8: npc.mPersistent = value.toBool(); break; - default: return; // throw an exception here? + case 0: + npc.mNpdt.mLevel = static_cast(value.toInt()); + break; + case 1: + return; + case 2: + return; + case 3: + return; + case 4: + npc.mNpdt.mDisposition = static_cast(value.toInt()); + break; + case 5: + npc.mNpdt.mReputation = static_cast(value.toInt()); + break; + case 6: + npc.mNpdt.mRank = static_cast(value.toInt()); + break; + case 7: + npc.mNpdt.mGold = value.toInt(); + break; + default: + return; // throw an exception here? } else - switch(subColIndex) + switch (subColIndex) { - case 0: npc.mNpdt.mLevel = static_cast(value.toInt()); break; - case 1: npc.mNpdt.mHealth = static_cast(value.toInt()); break; - case 2: npc.mNpdt.mMana = static_cast(value.toInt()); break; - case 3: npc.mNpdt.mFatigue = static_cast(value.toInt()); break; - case 4: npc.mNpdt.mDisposition = static_cast(value.toInt()); break; - case 5: npc.mNpdt.mReputation = static_cast(value.toInt()); break; - case 6: npc.mNpdt.mRank = static_cast(value.toInt()); break; - case 7: npc.mNpdt.mGold = value.toInt(); break; - case 8: npc.mPersistent = value.toBool(); break; - default: return; // throw an exception here? + case 0: + npc.mNpdt.mLevel = static_cast(value.toInt()); + break; + case 1: + npc.mNpdt.mHealth = static_cast(value.toInt()); + break; + case 2: + npc.mNpdt.mMana = static_cast(value.toInt()); + break; + case 3: + npc.mNpdt.mFatigue = static_cast(value.toInt()); + break; + case 4: + npc.mNpdt.mDisposition = static_cast(value.toInt()); + break; + case 5: + npc.mNpdt.mReputation = static_cast(value.toInt()); + break; + case 6: + npc.mNpdt.mRank = static_cast(value.toInt()); + break; + case 7: + npc.mNpdt.mGold = value.toInt(); + break; + default: + return; // throw an exception here? } - record.setModified (npc); + record.setModified(npc); } -int CSMWorld::NpcMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +int CSMWorld::NpcMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const { - return 9; // Level, Health, Mana, Fatigue, Disposition, Reputation, Rank, Gold, Persist + return 8; // Level, Health, Mana, Fatigue, Disposition, Reputation, Rank, Gold } -int CSMWorld::NpcMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int CSMWorld::NpcMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const { return 1; // fixed at size 1 } -CSMWorld::CreatureAttributesRefIdAdapter::CreatureAttributesRefIdAdapter() -{} - -void CSMWorld::CreatureAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const +void CSMWorld::CreatureAttributesRefIdAdapter::addNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::CreatureAttributesRefIdAdapter::removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const +void CSMWorld::CreatureAttributesRefIdAdapter::removeNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable( + const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); // store the whole struct - creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + creature.mData = static_cast>&>(nestedTable) + .mNestedTable.at(0); - record.setModified (creature); + record.setModified(creature); } -CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nestedTable( + const RefIdColumn* column, const RefIdData& data, int index) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper>(wrap); } -QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const +QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData( + const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); const ESM::Creature& creature = record.get(); @@ -1233,331 +1310,380 @@ QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdCol else if (subColIndex == 1) switch (subRowIndex) { - case 0: return creature.mData.mStrength; - case 1: return creature.mData.mIntelligence; - case 2: return creature.mData.mWillpower; - case 3: return creature.mData.mAgility; - case 4: return creature.mData.mSpeed; - case 5: return creature.mData.mEndurance; - case 6: return creature.mData.mPersonality; - case 7: return creature.mData.mLuck; - default: return QVariant(); // throw an exception here? + case 0: + return creature.mData.mStrength; + case 1: + return creature.mData.mIntelligence; + case 2: + return creature.mData.mWillpower; + case 3: + return creature.mData.mAgility; + case 4: + return creature.mData.mSpeed; + case 5: + return creature.mData.mEndurance; + case 6: + return creature.mData.mPersonality; + case 7: + return creature.mData.mLuck; + default: + return QVariant(); // throw an exception here? } else return QVariant(); // throw an exception here? } -void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData( + const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); if (subColIndex == 1) - switch(subRowIndex) + switch (subRowIndex) { - case 0: creature.mData.mStrength = value.toInt(); break; - case 1: creature.mData.mIntelligence = value.toInt(); break; - case 2: creature.mData.mWillpower = value.toInt(); break; - case 3: creature.mData.mAgility = value.toInt(); break; - case 4: creature.mData.mSpeed = value.toInt(); break; - case 5: creature.mData.mEndurance = value.toInt(); break; - case 6: creature.mData.mPersonality = value.toInt(); break; - case 7: creature.mData.mLuck = value.toInt(); break; - default: return; // throw an exception here? + case 0: + creature.mData.mStrength = value.toInt(); + break; + case 1: + creature.mData.mIntelligence = value.toInt(); + break; + case 2: + creature.mData.mWillpower = value.toInt(); + break; + case 3: + creature.mData.mAgility = value.toInt(); + break; + case 4: + creature.mData.mSpeed = value.toInt(); + break; + case 5: + creature.mData.mEndurance = value.toInt(); + break; + case 6: + creature.mData.mPersonality = value.toInt(); + break; + case 7: + creature.mData.mLuck = value.toInt(); + break; + default: + return; // throw an exception here? } else return; // throw an exception here? - record.setModified (creature); + record.setModified(creature); } -int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount( + const RefIdColumn* column, const RefIdData& data) const { return 2; } -int CSMWorld::CreatureAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedRowsCount( + const RefIdColumn* column, const RefIdData& data, int index) const { - // There are 8 attributes - return 8; + return ESM::Attribute::Length; } -CSMWorld::CreatureAttackRefIdAdapter::CreatureAttackRefIdAdapter() -{} - -void CSMWorld::CreatureAttackRefIdAdapter::addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const +void CSMWorld::CreatureAttackRefIdAdapter::addNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int position) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::CreatureAttackRefIdAdapter::removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const +void CSMWorld::CreatureAttackRefIdAdapter::removeNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const { // Do nothing, this table cannot be changed by the user } -void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable( + const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); // store the whole struct - creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + creature.mData = static_cast>&>(nestedTable) + .mNestedTable.at(0); - record.setModified (creature); + record.setModified(creature); } -CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTable( + const RefIdColumn* column, const RefIdData& data, int index) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); // return the whole struct std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper>(wrap); } -QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const +QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData( + const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); const ESM::Creature& creature = record.get(); if (subRowIndex < 0 || subRowIndex > 2) - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); if (subColIndex == 0) return subRowIndex + 1; else if (subColIndex == 1 || subColIndex == 2) return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)]; else - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); } -void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +void CSMWorld::CreatureAttackRefIdAdapter::setNestedData( + const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); if (subRowIndex < 0 || subRowIndex > 2) - throw std::runtime_error ("index out of range"); + throw std::runtime_error("index out of range"); if (subColIndex == 1 || subColIndex == 2) creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)] = value.toInt(); else return; // throw an exception here? - record.setModified (creature); + record.setModified(creature); } -int CSMWorld::CreatureAttackRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +int CSMWorld::CreatureAttackRefIdAdapter::getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const { return 3; } -int CSMWorld::CreatureAttackRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int CSMWorld::CreatureAttackRefIdAdapter::getNestedRowsCount( + const RefIdColumn* column, const RefIdData& data, int index) const { // There are 3 attacks return 3; } -CSMWorld::CreatureMiscRefIdAdapter::CreatureMiscRefIdAdapter() -{} - -CSMWorld::CreatureMiscRefIdAdapter::~CreatureMiscRefIdAdapter() -{} - -void CSMWorld::CreatureMiscRefIdAdapter::addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const +void CSMWorld::CreatureMiscRefIdAdapter::addNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int position) const { - throw std::logic_error ("cannot add a row to a fixed table"); + throw std::logic_error("cannot add a row to a fixed table"); } -void CSMWorld::CreatureMiscRefIdAdapter::removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const +void CSMWorld::CreatureMiscRefIdAdapter::removeNestedRow( + const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const { - throw std::logic_error ("cannot remove a row to a fixed table"); + throw std::logic_error("cannot remove a row to a fixed table"); } -void CSMWorld::CreatureMiscRefIdAdapter::setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +void CSMWorld::CreatureMiscRefIdAdapter::setNestedTable( + const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } -CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureMiscRefIdAdapter::nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureMiscRefIdAdapter::nestedTable( + const RefIdColumn* column, const RefIdData& data, int index) const { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } -QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const +QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData( + const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Creature))); const ESM::Creature& creature = record.get(); switch (subColIndex) { - case 0: return creature.mData.mLevel; - case 1: return creature.mData.mHealth; - case 2: return creature.mData.mMana; - case 3: return creature.mData.mFatigue; - case 4: return creature.mData.mSoul; - case 5: return creature.mData.mCombat; - case 6: return creature.mData.mMagic; - case 7: return creature.mData.mStealth; - case 8: return creature.mData.mGold; - default: return QVariant(); // throw an exception here? + case 0: + return creature.mData.mLevel; + case 1: + return creature.mData.mHealth; + case 2: + return creature.mData.mMana; + case 3: + return creature.mData.mFatigue; + case 4: + return creature.mData.mSoul; + case 5: + return creature.mData.mCombat; + case 6: + return creature.mData.mMagic; + case 7: + return creature.mData.mStealth; + case 8: + return creature.mData.mGold; + default: + return QVariant(); // throw an exception here? } } -void CSMWorld::CreatureMiscRefIdAdapter::setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +void CSMWorld::CreatureMiscRefIdAdapter::setNestedData( + const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, UniversalId::Type_Creature))); ESM::Creature creature = record.get(); - switch(subColIndex) + switch (subColIndex) { - case 0: creature.mData.mLevel = value.toInt(); break; - case 1: creature.mData.mHealth = value.toInt(); break; - case 2: creature.mData.mMana = value.toInt(); break; - case 3: creature.mData.mFatigue = value.toInt(); break; - case 4: creature.mData.mSoul = value.toInt(); break; - case 5: creature.mData.mCombat = value.toInt(); break; - case 6: creature.mData.mMagic = value.toInt(); break; - case 7: creature.mData.mStealth = value.toInt(); break; - case 8: creature.mData.mGold = value.toInt(); break; - default: return; // throw an exception here? + case 0: + creature.mData.mLevel = value.toInt(); + break; + case 1: + creature.mData.mHealth = value.toInt(); + break; + case 2: + creature.mData.mMana = value.toInt(); + break; + case 3: + creature.mData.mFatigue = value.toInt(); + break; + case 4: + creature.mData.mSoul = value.toInt(); + break; + case 5: + creature.mData.mCombat = value.toInt(); + break; + case 6: + creature.mData.mMagic = value.toInt(); + break; + case 7: + creature.mData.mStealth = value.toInt(); + break; + case 8: + creature.mData.mGold = value.toInt(); + break; + default: + return; // throw an exception here? } - record.setModified (creature); + record.setModified(creature); } -int CSMWorld::CreatureMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +int CSMWorld::CreatureMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const { return 9; // Level, Health, Mana, Fatigue, Soul, Combat, Magic, Steath, Gold } -int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount( + const RefIdColumn* column, const RefIdData& data, int index) const { return 1; // fixed at size 1 } -CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) -: EnchantableColumns (columns) -, mType(nullptr) -, mHealth(nullptr) -, mSpeed(nullptr) -, mReach(nullptr) -, mChop{nullptr} -, mSlash{nullptr} -, mThrust{nullptr} -{} - -CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns) -: EnchantableRefIdAdapter (UniversalId::Type_Weapon, columns), mColumns (columns) -{} - -QVariant CSMWorld::WeaponRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const +CSMWorld::WeaponColumns::WeaponColumns(const EnchantableColumns& columns) + : EnchantableColumns(columns) + , mType(nullptr) + , mHealth(nullptr) + , mSpeed(nullptr) + , mReach(nullptr) + , mChop{ nullptr } + , mSlash{ nullptr } + , mThrust{ nullptr } { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Weapon))); +} - if (column==mColumns.mType) +CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter(const WeaponColumns& columns) + : EnchantableRefIdAdapter(UniversalId::Type_Weapon, columns) + , mColumns(columns) +{ +} + +QVariant CSMWorld::WeaponRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const +{ + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Weapon))); + + if (column == mColumns.mType) return record.get().mData.mType; - if (column==mColumns.mHealth) + if (column == mColumns.mHealth) return record.get().mData.mHealth; - if (column==mColumns.mSpeed) + if (column == mColumns.mSpeed) return record.get().mData.mSpeed; - if (column==mColumns.mReach) + if (column == mColumns.mReach) return record.get().mData.mReach; - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { - if (column==mColumns.mChop[i]) + if (column == mColumns.mChop[i]) return record.get().mData.mChop[i]; - if (column==mColumns.mSlash[i]) + if (column == mColumns.mSlash[i]) return record.get().mData.mSlash[i]; - if (column==mColumns.mThrust[i]) + if (column == mColumns.mThrust[i]) return record.get().mData.mThrust[i]; } - std::map::const_iterator iter = - mColumns.mFlags.find (column); + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter!=mColumns.mFlags.end()) - return (record.get().mData.mFlags & iter->second)!=0; + if (iter != mColumns.mFlags.end()) + return (record.get().mData.mFlags & iter->second) != 0; - return EnchantableRefIdAdapter::getData (column, data, index); + return EnchantableRefIdAdapter::getData(column, data, index); } -void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const +void CSMWorld::WeaponRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Weapon))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Weapon))); ESM::Weapon weapon = record.get(); - if (column==mColumns.mType) + if (column == mColumns.mType) weapon.mData.mType = value.toInt(); - else if (column==mColumns.mHealth) + else if (column == mColumns.mHealth) weapon.mData.mHealth = value.toInt(); - else if (column==mColumns.mSpeed) + else if (column == mColumns.mSpeed) weapon.mData.mSpeed = value.toFloat(); - else if (column==mColumns.mReach) + else if (column == mColumns.mReach) weapon.mData.mReach = value.toFloat(); - else if (column==mColumns.mChop[0]) + else if (column == mColumns.mChop[0]) weapon.mData.mChop[0] = value.toInt(); - else if (column==mColumns.mChop[1]) + else if (column == mColumns.mChop[1]) weapon.mData.mChop[1] = value.toInt(); - else if (column==mColumns.mSlash[0]) + else if (column == mColumns.mSlash[0]) weapon.mData.mSlash[0] = value.toInt(); - else if (column==mColumns.mSlash[1]) + else if (column == mColumns.mSlash[1]) weapon.mData.mSlash[1] = value.toInt(); - else if (column==mColumns.mThrust[0]) + else if (column == mColumns.mThrust[0]) weapon.mData.mThrust[0] = value.toInt(); - else if (column==mColumns.mThrust[1]) + else if (column == mColumns.mThrust[1]) weapon.mData.mThrust[1] = value.toInt(); else { - std::map::const_iterator iter = - mColumns.mFlags.find (column); + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter!=mColumns.mFlags.end()) + if (iter != mColumns.mFlags.end()) { - if (value.toInt()!=0) + if (value.toInt() != 0) weapon.mData.mFlags |= iter->second; else weapon.mData.mFlags &= ~iter->second; } else { - EnchantableRefIdAdapter::setData (column, data, index, value); + EnchantableRefIdAdapter::setData(column, data, index, value); return; // Don't overwrite changes made by base class } } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 95d1a09a2..5c54b9a0c 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1,172 +1,236 @@ #ifndef CSM_WOLRD_REFIDADAPTERIMP_H #define CSM_WOLRD_REFIDADAPTERIMP_H +#include #include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "columnbase.hpp" +#include "nestedtablewrapper.hpp" #include "record.hpp" +#include "refidadapter.hpp" #include "refiddata.hpp" #include "universalid.hpp" -#include "refidadapter.hpp" -#include "nestedtablewrapper.hpp" namespace CSMWorld { + class RefIdColumn; struct BaseColumns { - const RefIdColumn *mId; - const RefIdColumn *mModified; - const RefIdColumn *mType; + const RefIdColumn* mId; + const RefIdColumn* mModified; + const RefIdColumn* mType; + const RefIdColumn* mBlocked; + + BaseColumns() + : mId(nullptr) + , mModified(nullptr) + , mType(nullptr) + , mBlocked(nullptr) + { + } }; /// \brief Base adapter for all refereceable record types /// Adapters that can handle nested tables, needs to return valid qvariant for parent columns - template + template class BaseRefIdAdapter : public RefIdAdapter { - UniversalId::Type mType; - BaseColumns mBase; + UniversalId::Type mType; + BaseColumns mBase; - public: + public: + BaseRefIdAdapter(UniversalId::Type type, const BaseColumns& base); - BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base); + ESM::RefId getId(const RecordBase& record) const override; - std::string getId (const RecordBase& record) const override; + void setId(RecordBase& record, const std::string& id) override; - void setId (RecordBase& record, const std::string& id) override; + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. - - UniversalId::Type getType() const; + UniversalId::Type getType() const; }; - template - BaseRefIdAdapter::BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base) - : mType (type), mBase (base) - {} - - template - void BaseRefIdAdapter::setId (RecordBase& record, const std::string& id) + template + BaseRefIdAdapter::BaseRefIdAdapter(UniversalId::Type type, const BaseColumns& base) + : mType(type) + , mBase(base) { - (dynamic_cast&> (record).get().mId) = id; } - template - std::string BaseRefIdAdapter::getId (const RecordBase& record) const + template + void BaseRefIdAdapter::setId(RecordBase& record, const std::string& id) { - return dynamic_cast&> (record).get().mId; + (dynamic_cast&>(record).get().mId) = ESM::RefId::stringRefId(id); } - template - QVariant BaseRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const + template + ESM::RefId BaseRefIdAdapter::getId(const RecordBase& record) const { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, mType))); + return dynamic_cast&>(record).get().mId; + } - if (column==mBase.mId) - return QString::fromUtf8 (record.get().mId.c_str()); + template + QVariant BaseRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const + { + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); - if (column==mBase.mModified) + if (column == mBase.mId) + return QString::fromStdString(record.get().mId.toString()); + + if (column == mBase.mModified) { - if (record.mState==Record::State_Erased) - return static_cast (Record::State_Deleted); + if (record.mState == Record::State_Erased) + return static_cast(Record::State_Deleted); - return static_cast (record.mState); + return static_cast(record.mState); } - if (column==mBase.mType) - return static_cast (mType); + if (column == mBase.mType) + return static_cast(mType); + + if (column == mBase.mBlocked) + return (record.get().mRecordFlags & ESM::FLAG_Blocked) != 0; return QVariant(); } - template - void BaseRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const + template + void BaseRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); - if (column==mBase.mModified) - record.mState = static_cast (value.toInt()); + if (column == mBase.mModified) + record.mState = static_cast(value.toInt()); + else if (column == mBase.mBlocked) + { + RecordT record2 = record.get(); + + if (value.toInt() != 0) + record2.mRecordFlags |= ESM::FLAG_Blocked; + else + record2.mRecordFlags &= ~ESM::FLAG_Blocked; + + record.setModified(record2); + } } - template + template UniversalId::Type BaseRefIdAdapter::getType() const { return mType; } + // NOTE: Body Part should not have persistence (but BodyPart is not listed in the Objects + // table at the moment). + // + // Spellmaking - not persistent - currently not part of objects table + // Enchanting - not persistent - currently not part of objects table + // + // Leveled Creature - no model, so not persistent + // Leveled Item - no model, so not persistent struct ModelColumns : public BaseColumns { - const RefIdColumn *mModel; + const RefIdColumn* mModel; + const RefIdColumn* mPersistence; - ModelColumns (const BaseColumns& base) : BaseColumns (base), mModel(nullptr) {} + ModelColumns(const BaseColumns& base) + : BaseColumns(base) + , mModel(nullptr) + , mPersistence(nullptr) + { + } }; /// \brief Adapter for IDs with models (all but levelled lists) - template + template class ModelRefIdAdapter : public BaseRefIdAdapter { - ModelColumns mModel; + ModelColumns mModel; - public: + public: + ModelRefIdAdapter(UniversalId::Type type, const ModelColumns& columns); - ModelRefIdAdapter (UniversalId::Type type, const ModelColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; - template - ModelRefIdAdapter::ModelRefIdAdapter (UniversalId::Type type, const ModelColumns& columns) - : BaseRefIdAdapter (type, columns), mModel (columns) - {} - - template - QVariant ModelRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const + template + ModelRefIdAdapter::ModelRefIdAdapter(UniversalId::Type type, const ModelColumns& columns) + : BaseRefIdAdapter(type, columns) + , mModel(columns) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mModel.mModel) - return QString::fromUtf8 (record.get().mModel.c_str()); - - return BaseRefIdAdapter::getData (column, data, index); } - template - void ModelRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const + template + QVariant ModelRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); + + if (column == mModel.mModel) + return QString::fromUtf8(record.get().mModel.c_str()); + + if (column == mModel.mPersistence) + return (record.get().mRecordFlags & ESM::FLAG_Persistent) != 0; + + return BaseRefIdAdapter::getData(column, data, index); + } + + template + void ModelRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const + { + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mModel.mModel) + if (column == mModel.mModel) record2.mModel = value.toString().toUtf8().constData(); + else if (column == mModel.mPersistence) + { + if (value.toInt() != 0) + record2.mRecordFlags |= ESM::FLAG_Persistent; + else + record2.mRecordFlags &= ~ESM::FLAG_Persistent; + } else { - BaseRefIdAdapter::setData (column, data, index, value); + BaseRefIdAdapter::setData(column, data, index, value); return; } @@ -175,70 +239,69 @@ namespace CSMWorld struct NameColumns : public ModelColumns { - const RefIdColumn *mName; - const RefIdColumn *mScript; + const RefIdColumn* mName; + const RefIdColumn* mScript; - NameColumns (const ModelColumns& base) - : ModelColumns (base) - , mName(nullptr) - , mScript(nullptr) - {} + NameColumns(const ModelColumns& base) + : ModelColumns(base) + , mName(nullptr) + , mScript(nullptr) + { + } }; /// \brief Adapter for IDs with names (all but levelled lists and statics) - template + template class NameRefIdAdapter : public ModelRefIdAdapter { - NameColumns mName; + NameColumns mName; - public: + public: + NameRefIdAdapter(UniversalId::Type type, const NameColumns& columns); - NameRefIdAdapter (UniversalId::Type type, const NameColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; - template - NameRefIdAdapter::NameRefIdAdapter (UniversalId::Type type, const NameColumns& columns) - : ModelRefIdAdapter (type, columns), mName (columns) - {} - - template - QVariant NameRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const + template + NameRefIdAdapter::NameRefIdAdapter(UniversalId::Type type, const NameColumns& columns) + : ModelRefIdAdapter(type, columns) + , mName(columns) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mName.mName) - return QString::fromUtf8 (record.get().mName.c_str()); - - if (column==mName.mScript) - return QString::fromUtf8 (record.get().mScript.c_str()); - - return ModelRefIdAdapter::getData (column, data, index); } - template - void NameRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const + template + QVariant NameRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); + + if (column == mName.mName) + return QString::fromUtf8(record.get().mName.c_str()); + + if (column == mName.mScript) + return QString::fromUtf8(record.get().mScript.getRefIdString().c_str()); + + return ModelRefIdAdapter::getData(column, data, index); + } + + template + void NameRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const + { + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mName.mName) + if (column == mName.mName) record2.mName = value.toString().toUtf8().constData(); - else if (column==mName.mScript) - record2.mScript = value.toString().toUtf8().constData(); + else if (column == mName.mScript) + record2.mScript = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); else { - ModelRefIdAdapter::setData (column, data, index, value); + ModelRefIdAdapter::setData(column, data, index, value); return; } @@ -247,78 +310,76 @@ namespace CSMWorld struct InventoryColumns : public NameColumns { - const RefIdColumn *mIcon; - const RefIdColumn *mWeight; - const RefIdColumn *mValue; + const RefIdColumn* mIcon; + const RefIdColumn* mWeight; + const RefIdColumn* mValue; - InventoryColumns (const NameColumns& base) - : NameColumns (base) - , mIcon(nullptr) - , mWeight(nullptr) - , mValue(nullptr) - {} + InventoryColumns(const NameColumns& base) + : NameColumns(base) + , mIcon(nullptr) + , mWeight(nullptr) + , mValue(nullptr) + { + } }; /// \brief Adapter for IDs that can go into an inventory - template + template class InventoryRefIdAdapter : public NameRefIdAdapter { - InventoryColumns mInventory; + InventoryColumns mInventory; - public: + public: + InventoryRefIdAdapter(UniversalId::Type type, const InventoryColumns& columns); - InventoryRefIdAdapter (UniversalId::Type type, const InventoryColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; - template - InventoryRefIdAdapter::InventoryRefIdAdapter (UniversalId::Type type, - const InventoryColumns& columns) - : NameRefIdAdapter (type, columns), mInventory (columns) - {} - - template - QVariant InventoryRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const + template + InventoryRefIdAdapter::InventoryRefIdAdapter(UniversalId::Type type, const InventoryColumns& columns) + : NameRefIdAdapter(type, columns) + , mInventory(columns) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mInventory.mIcon) - return QString::fromUtf8 (record.get().mIcon.c_str()); - - if (column==mInventory.mWeight) - return record.get().mData.mWeight; - - if (column==mInventory.mValue) - return record.get().mData.mValue; - - return NameRefIdAdapter::getData (column, data, index); } - template - void InventoryRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const + template + QVariant InventoryRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); + + if (column == mInventory.mIcon) + return QString::fromUtf8(record.get().mIcon.c_str()); + + if (column == mInventory.mWeight) + return record.get().mData.mWeight; + + if (column == mInventory.mValue) + return record.get().mData.mValue; + + return NameRefIdAdapter::getData(column, data, index); + } + + template + void InventoryRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const + { + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mInventory.mIcon) + if (column == mInventory.mIcon) record2.mIcon = value.toString().toUtf8().constData(); - else if (column==mInventory.mWeight) + else if (column == mInventory.mWeight) record2.mData.mWeight = value.toFloat(); - else if (column==mInventory.mValue) + else if (column == mInventory.mValue) record2.mData.mValue = value.toInt(); else { - NameRefIdAdapter::setData (column, data, index, value); + NameRefIdAdapter::setData(column, data, index, value); return; } @@ -327,155 +388,141 @@ namespace CSMWorld struct PotionColumns : public InventoryColumns { - const RefIdColumn *mEffects; + const RefIdColumn* mEffects; - PotionColumns (const InventoryColumns& columns); + PotionColumns(const InventoryColumns& columns); }; class PotionRefIdAdapter : public InventoryRefIdAdapter { - PotionColumns mColumns; - const RefIdColumn *mAutoCalc; + PotionColumns mColumns; + const RefIdColumn* mAutoCalc; - public: + public: + PotionRefIdAdapter(const PotionColumns& columns, const RefIdColumn* autoCalc); - PotionRefIdAdapter (const PotionColumns& columns, const RefIdColumn *autoCalc); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; struct IngredientColumns : public InventoryColumns { - const RefIdColumn *mEffects; + const RefIdColumn* mEffects; - IngredientColumns (const InventoryColumns& columns); + IngredientColumns(const InventoryColumns& columns); }; class IngredientRefIdAdapter : public InventoryRefIdAdapter { - IngredientColumns mColumns; + IngredientColumns mColumns; - public: + public: + IngredientRefIdAdapter(const IngredientColumns& columns); - IngredientRefIdAdapter (const IngredientColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; class IngredEffectRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; - // not implemented - IngredEffectRefIdAdapter (const IngredEffectRefIdAdapter&); - IngredEffectRefIdAdapter& operator= (const IngredEffectRefIdAdapter&); - public: - IngredEffectRefIdAdapter(); + IngredEffectRefIdAdapter(const IngredEffectRefIdAdapter&) = delete; + IngredEffectRefIdAdapter& operator=(const IngredEffectRefIdAdapter&) = delete; + ~IngredEffectRefIdAdapter() override = default; - virtual ~IngredEffectRefIdAdapter(); + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override; - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override; + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override; - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override; + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override; - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override; - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override; + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override; - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override; - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override; - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; - - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override; }; struct EnchantableColumns : public InventoryColumns { - const RefIdColumn *mEnchantment; - const RefIdColumn *mEnchantmentPoints; + const RefIdColumn* mEnchantment; + const RefIdColumn* mEnchantmentPoints; - EnchantableColumns (const InventoryColumns& base) - : InventoryColumns (base) - , mEnchantment(nullptr) - , mEnchantmentPoints(nullptr) - {} + EnchantableColumns(const InventoryColumns& base) + : InventoryColumns(base) + , mEnchantment(nullptr) + , mEnchantmentPoints(nullptr) + { + } }; /// \brief Adapter for enchantable IDs - template + template class EnchantableRefIdAdapter : public InventoryRefIdAdapter { - EnchantableColumns mEnchantable; + EnchantableColumns mEnchantable; - public: + public: + EnchantableRefIdAdapter(UniversalId::Type type, const EnchantableColumns& columns); - EnchantableRefIdAdapter (UniversalId::Type type, const EnchantableColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; - template - EnchantableRefIdAdapter::EnchantableRefIdAdapter (UniversalId::Type type, - const EnchantableColumns& columns) - : InventoryRefIdAdapter (type, columns), mEnchantable (columns) - {} - - template - QVariant EnchantableRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const + template + EnchantableRefIdAdapter::EnchantableRefIdAdapter(UniversalId::Type type, const EnchantableColumns& columns) + : InventoryRefIdAdapter(type, columns) + , mEnchantable(columns) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mEnchantable.mEnchantment) - return QString::fromUtf8 (record.get().mEnchant.c_str()); - - if (column==mEnchantable.mEnchantmentPoints) - return static_cast (record.get().mData.mEnchant); - - return InventoryRefIdAdapter::getData (column, data, index); } - template - void EnchantableRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, - int index, const QVariant& value) const + template + QVariant EnchantableRefIdAdapter::getData( + const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); + + if (column == mEnchantable.mEnchantment) + return QString::fromUtf8(record.get().mEnchant.getRefIdString().c_str()); + + if (column == mEnchantable.mEnchantmentPoints) + return static_cast(record.get().mData.mEnchant); + + return InventoryRefIdAdapter::getData(column, data, index); + } + + template + void EnchantableRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const + { + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mEnchantable.mEnchantment) - record2.mEnchant = value.toString().toUtf8().constData(); - else if (column==mEnchantable.mEnchantmentPoints) + if (column == mEnchantable.mEnchantment) + record2.mEnchant = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); + else if (column == mEnchantable.mEnchantmentPoints) record2.mData.mEnchant = value.toInt(); else { - InventoryRefIdAdapter::setData (column, data, index, value); + InventoryRefIdAdapter::setData(column, data, index, value); return; } @@ -484,70 +531,69 @@ namespace CSMWorld struct ToolColumns : public InventoryColumns { - const RefIdColumn *mQuality; - const RefIdColumn *mUses; + const RefIdColumn* mQuality; + const RefIdColumn* mUses; - ToolColumns (const InventoryColumns& base) - : InventoryColumns (base) - , mQuality(nullptr) - , mUses(nullptr) - {} + ToolColumns(const InventoryColumns& base) + : InventoryColumns(base) + , mQuality(nullptr) + , mUses(nullptr) + { + } }; /// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes) - template + template class ToolRefIdAdapter : public InventoryRefIdAdapter { - ToolColumns mTools; + ToolColumns mTools; - public: + public: + ToolRefIdAdapter(UniversalId::Type type, const ToolColumns& columns); - ToolRefIdAdapter (UniversalId::Type type, const ToolColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; - template - ToolRefIdAdapter::ToolRefIdAdapter (UniversalId::Type type, const ToolColumns& columns) - : InventoryRefIdAdapter (type, columns), mTools (columns) - {} - - template - QVariant ToolRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const + template + ToolRefIdAdapter::ToolRefIdAdapter(UniversalId::Type type, const ToolColumns& columns) + : InventoryRefIdAdapter(type, columns) + , mTools(columns) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mTools.mQuality) - return record.get().mData.mQuality; - - if (column==mTools.mUses) - return record.get().mData.mUses; - - return InventoryRefIdAdapter::getData (column, data, index); } - template - void ToolRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, - int index, const QVariant& value) const + template + QVariant ToolRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); + + if (column == mTools.mQuality) + return record.get().mData.mQuality; + + if (column == mTools.mUses) + return record.get().mData.mUses; + + return InventoryRefIdAdapter::getData(column, data, index); + } + + template + void ToolRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const + { + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mTools.mQuality) + if (column == mTools.mQuality) record2.mData.mQuality = value.toFloat(); - else if (column==mTools.mUses) + else if (column == mTools.mUses) record2.mData.mUses = value.toInt(); else { - InventoryRefIdAdapter::setData (column, data, index, value); + InventoryRefIdAdapter::setData(column, data, index, value); return; } @@ -556,18 +602,18 @@ namespace CSMWorld struct ActorColumns : public NameColumns { - const RefIdColumn *mHello; - const RefIdColumn *mFlee; - const RefIdColumn *mFight; - const RefIdColumn *mAlarm; - const RefIdColumn *mInventory; - const RefIdColumn *mSpells; - const RefIdColumn *mDestinations; - const RefIdColumn *mAiPackages; - std::map mServices; + const RefIdColumn* mHello; + const RefIdColumn* mFlee; + const RefIdColumn* mFight; + const RefIdColumn* mAlarm; + const RefIdColumn* mInventory; + const RefIdColumn* mSpells; + const RefIdColumn* mDestinations; + const RefIdColumn* mAiPackages; + std::map mServices; - ActorColumns (const NameColumns& base) - : NameColumns (base) + ActorColumns(const NameColumns& base) + : NameColumns(base) , mHello(nullptr) , mFlee(nullptr) , mFight(nullptr) @@ -576,103 +622,99 @@ namespace CSMWorld , mSpells(nullptr) , mDestinations(nullptr) , mAiPackages(nullptr) - {} + { + } }; /// \brief Adapter for actor IDs (handles common AI functionality) - template + template class ActorRefIdAdapter : public NameRefIdAdapter { - ActorColumns mActors; + ActorColumns mActors; - public: + public: + ActorRefIdAdapter(UniversalId::Type type, const ActorColumns& columns); - ActorRefIdAdapter (UniversalId::Type type, const ActorColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; - template - ActorRefIdAdapter::ActorRefIdAdapter (UniversalId::Type type, - const ActorColumns& columns) - : NameRefIdAdapter (type, columns), mActors (columns) - {} - - template - QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const + template + ActorRefIdAdapter::ActorRefIdAdapter(UniversalId::Type type, const ActorColumns& columns) + : NameRefIdAdapter(type, columns) + , mActors(columns) { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mActors.mHello) - return record.get().mAiData.mHello; - - if (column==mActors.mFlee) - return record.get().mAiData.mFlee; - - if (column==mActors.mFight) - return record.get().mAiData.mFight; - - if (column==mActors.mAlarm) - return record.get().mAiData.mAlarm; - - if (column==mActors.mInventory) - return QVariant::fromValue(ColumnBase::TableEdit_Full); - - if (column==mActors.mSpells) - return QVariant::fromValue(ColumnBase::TableEdit_Full); - - if (column==mActors.mDestinations) - return QVariant::fromValue(ColumnBase::TableEdit_Full); - - if (column==mActors.mAiPackages) - return QVariant::fromValue(ColumnBase::TableEdit_Full); - - std::map::const_iterator iter = - mActors.mServices.find (column); - - if (iter!=mActors.mServices.end()) - return (record.get().mAiData.mServices & iter->second)!=0; - - return NameRefIdAdapter::getData (column, data, index); } - template - void ActorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const + template + QVariant ActorRefIdAdapter::getData(const RefIdColumn* column, const RefIdData& data, int index) const { - Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + const Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); + + if (column == mActors.mHello) + return record.get().mAiData.mHello; + + if (column == mActors.mFlee) + return record.get().mAiData.mFlee; + + if (column == mActors.mFight) + return record.get().mAiData.mFight; + + if (column == mActors.mAlarm) + return record.get().mAiData.mAlarm; + + if (column == mActors.mInventory) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + if (column == mActors.mSpells) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + if (column == mActors.mDestinations) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + if (column == mActors.mAiPackages) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + std::map::const_iterator iter = mActors.mServices.find(column); + + if (iter != mActors.mServices.end()) + return (record.get().mAiData.mServices & iter->second) != 0; + + return NameRefIdAdapter::getData(column, data, index); + } + + template + void ActorRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const + { + Record& record = static_cast&>( + data.getRecord(RefIdData::LocalIndex(index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mActors.mHello) + if (column == mActors.mHello) record2.mAiData.mHello = value.toInt(); - else if (column==mActors.mFlee) // Flee, Fight and Alarm ratings are probabilities. + else if (column == mActors.mFlee) // Flee, Fight and Alarm ratings are probabilities. record2.mAiData.mFlee = std::min(100, value.toInt()); - else if (column==mActors.mFight) + else if (column == mActors.mFight) record2.mAiData.mFight = std::min(100, value.toInt()); - else if (column==mActors.mAlarm) + else if (column == mActors.mAlarm) record2.mAiData.mAlarm = std::min(100, value.toInt()); else { - typename std::map::const_iterator iter = - mActors.mServices.find (column); - if (iter!=mActors.mServices.end()) + typename std::map::const_iterator iter = mActors.mServices.find(column); + if (iter != mActors.mServices.end()) { - if (value.toInt()!=0) + if (value.toInt() != 0) record2.mAiData.mServices |= iter->second; else record2.mAiData.mServices &= ~iter->second; } else { - NameRefIdAdapter::setData (column, data, index, value); + NameRefIdAdapter::setData(column, data, index, value); return; } } @@ -682,514 +724,449 @@ namespace CSMWorld class ApparatusRefIdAdapter : public InventoryRefIdAdapter { - const RefIdColumn *mType; - const RefIdColumn *mQuality; + const RefIdColumn* mType; + const RefIdColumn* mQuality; - public: + public: + ApparatusRefIdAdapter(const InventoryColumns& columns, const RefIdColumn* type, const RefIdColumn* quality); - ApparatusRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *type, - const RefIdColumn *quality); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; class ArmorRefIdAdapter : public EnchantableRefIdAdapter { - const RefIdColumn *mType; - const RefIdColumn *mHealth; - const RefIdColumn *mArmor; - const RefIdColumn *mPartRef; + const RefIdColumn* mType; + const RefIdColumn* mHealth; + const RefIdColumn* mArmor; + const RefIdColumn* mPartRef; - public: + public: + ArmorRefIdAdapter(const EnchantableColumns& columns, const RefIdColumn* type, const RefIdColumn* health, + const RefIdColumn* armor, const RefIdColumn* partRef); - ArmorRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *type, - const RefIdColumn *health, const RefIdColumn *armor, const RefIdColumn *partRef); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; class BookRefIdAdapter : public EnchantableRefIdAdapter { - const RefIdColumn *mBookType; - const RefIdColumn *mSkill; - const RefIdColumn *mText; + const RefIdColumn* mBookType; + const RefIdColumn* mSkill; + const RefIdColumn* mText; - public: + public: + BookRefIdAdapter(const EnchantableColumns& columns, const RefIdColumn* bookType, const RefIdColumn* skill, + const RefIdColumn* text); - BookRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *bookType, - const RefIdColumn *skill, const RefIdColumn *text); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; class ClothingRefIdAdapter : public EnchantableRefIdAdapter { - const RefIdColumn *mType; - const RefIdColumn *mPartRef; + const RefIdColumn* mType; + const RefIdColumn* mPartRef; - public: + public: + ClothingRefIdAdapter(const EnchantableColumns& columns, const RefIdColumn* type, const RefIdColumn* partRef); - ClothingRefIdAdapter (const EnchantableColumns& columns, - const RefIdColumn *type, const RefIdColumn *partRef); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; class ContainerRefIdAdapter : public NameRefIdAdapter { - const RefIdColumn *mWeight; - const RefIdColumn *mOrganic; - const RefIdColumn *mRespawn; - const RefIdColumn *mContent; + const RefIdColumn* mWeight; + const RefIdColumn* mOrganic; + const RefIdColumn* mRespawn; + const RefIdColumn* mContent; - public: + public: + ContainerRefIdAdapter(const NameColumns& columns, const RefIdColumn* weight, const RefIdColumn* organic, + const RefIdColumn* respawn, const RefIdColumn* content); - ContainerRefIdAdapter (const NameColumns& columns, const RefIdColumn *weight, - const RefIdColumn *organic, const RefIdColumn *respawn, const RefIdColumn *content); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; struct CreatureColumns : public ActorColumns { - std::map mFlags; - const RefIdColumn *mType; - const RefIdColumn *mScale; - const RefIdColumn *mOriginal; - const RefIdColumn *mAttributes; - const RefIdColumn *mAttacks; - const RefIdColumn *mMisc; - const RefIdColumn *mBloodType; + std::map mFlags; + const RefIdColumn* mType; + const RefIdColumn* mScale; + const RefIdColumn* mOriginal; + const RefIdColumn* mAttributes; + const RefIdColumn* mAttacks; + const RefIdColumn* mMisc; + const RefIdColumn* mBloodType; - CreatureColumns (const ActorColumns& actorColumns); + CreatureColumns(const ActorColumns& actorColumns); }; class CreatureRefIdAdapter : public ActorRefIdAdapter { - CreatureColumns mColumns; + CreatureColumns mColumns; - public: + public: + CreatureRefIdAdapter(const CreatureColumns& columns); - CreatureRefIdAdapter (const CreatureColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; class DoorRefIdAdapter : public NameRefIdAdapter { - const RefIdColumn *mOpenSound; - const RefIdColumn *mCloseSound; + const RefIdColumn* mOpenSound; + const RefIdColumn* mCloseSound; - public: + public: + DoorRefIdAdapter(const NameColumns& columns, const RefIdColumn* openSound, const RefIdColumn* closeSound); - DoorRefIdAdapter (const NameColumns& columns, const RefIdColumn *openSound, - const RefIdColumn *closeSound); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; struct LightColumns : public InventoryColumns { - const RefIdColumn *mTime; - const RefIdColumn *mRadius; - const RefIdColumn *mColor; - const RefIdColumn *mSound; - const RefIdColumn *mEmitterType; - std::map mFlags; + const RefIdColumn* mTime; + const RefIdColumn* mRadius; + const RefIdColumn* mColor; + const RefIdColumn* mSound; + const RefIdColumn* mEmitterType; + std::map mFlags; - LightColumns (const InventoryColumns& columns); + LightColumns(const InventoryColumns& columns); }; class LightRefIdAdapter : public InventoryRefIdAdapter { - LightColumns mColumns; + LightColumns mColumns; - public: + public: + LightRefIdAdapter(const LightColumns& columns); - LightRefIdAdapter (const LightColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; class MiscRefIdAdapter : public InventoryRefIdAdapter { - const RefIdColumn *mKey; + const RefIdColumn* mKey; - public: + public: + MiscRefIdAdapter(const InventoryColumns& columns, const RefIdColumn* key); - MiscRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *key); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; struct NpcColumns : public ActorColumns { - std::map mFlags; - const RefIdColumn *mRace; - const RefIdColumn *mClass; - const RefIdColumn *mFaction; - const RefIdColumn *mHair; - const RefIdColumn *mHead; - const RefIdColumn *mAttributes; // depends on npc type - const RefIdColumn *mSkills; // depends on npc type - const RefIdColumn *mMisc; // may depend on npc type, e.g. FactionID - const RefIdColumn *mBloodType; - const RefIdColumn *mGender; + std::map mFlags; + const RefIdColumn* mRace; + const RefIdColumn* mClass; + const RefIdColumn* mFaction; + const RefIdColumn* mHair; + const RefIdColumn* mHead; + const RefIdColumn* mAttributes; // depends on npc type + const RefIdColumn* mSkills; // depends on npc type + const RefIdColumn* mMisc; // may depend on npc type, e.g. FactionID + const RefIdColumn* mBloodType; + const RefIdColumn* mGender; - NpcColumns (const ActorColumns& actorColumns); + NpcColumns(const ActorColumns& actorColumns); }; class NpcRefIdAdapter : public ActorRefIdAdapter { - NpcColumns mColumns; + NpcColumns mColumns; - public: + public: + NpcRefIdAdapter(const NpcColumns& columns); - NpcRefIdAdapter (const NpcColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; struct WeaponColumns : public EnchantableColumns { - const RefIdColumn *mType; - const RefIdColumn *mHealth; - const RefIdColumn *mSpeed; - const RefIdColumn *mReach; - const RefIdColumn *mChop[2]; - const RefIdColumn *mSlash[2]; - const RefIdColumn *mThrust[2]; - std::map mFlags; + const RefIdColumn* mType; + const RefIdColumn* mHealth; + const RefIdColumn* mSpeed; + const RefIdColumn* mReach; + const RefIdColumn* mChop[2]; + const RefIdColumn* mSlash[2]; + const RefIdColumn* mThrust[2]; + std::map mFlags; - WeaponColumns (const EnchantableColumns& columns); + WeaponColumns(const EnchantableColumns& columns); }; class WeaponRefIdAdapter : public EnchantableRefIdAdapter { - WeaponColumns mColumns; + WeaponColumns mColumns; - public: + public: + WeaponRefIdAdapter(const WeaponColumns& columns); - WeaponRefIdAdapter (const WeaponColumns& columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; - - class NestedRefIdAdapterBase; - class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase { public: + NpcAttributesRefIdAdapter() = default; - NpcAttributesRefIdAdapter (); + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override; - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override; + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override; - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override; + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override; - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override; - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override; + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override; - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override; - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override; - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; - - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override; }; class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase { public: + NpcSkillsRefIdAdapter() = default; - NpcSkillsRefIdAdapter (); + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override; - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override; + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override; - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override; + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override; - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override; - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override; + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override; - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override; - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override; - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; - - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override; }; class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase { - NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&); - NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&); - public: + NpcMiscRefIdAdapter() = default; + NpcMiscRefIdAdapter(const NpcMiscRefIdAdapter&) = delete; + NpcMiscRefIdAdapter& operator=(const NpcMiscRefIdAdapter&) = delete; + ~NpcMiscRefIdAdapter() override = default; - NpcMiscRefIdAdapter (); - virtual ~NpcMiscRefIdAdapter(); + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override; - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override; + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override; - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override; + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override; - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override; - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override; + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override; - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override; - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override; - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; - - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override; }; class CreatureAttributesRefIdAdapter : public NestedRefIdAdapterBase { public: + CreatureAttributesRefIdAdapter() = default; - CreatureAttributesRefIdAdapter (); + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override; - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override; + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override; - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override; + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override; - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override; - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override; + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override; - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override; - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override; - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; - - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override; }; class CreatureAttackRefIdAdapter : public NestedRefIdAdapterBase { public: + CreatureAttackRefIdAdapter() = default; - CreatureAttackRefIdAdapter (); + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override; - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override; + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override; - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override; + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override; - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override; - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override; + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override; - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override; - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override; - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; - - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override; }; class CreatureMiscRefIdAdapter : public NestedRefIdAdapterBase { - CreatureMiscRefIdAdapter (const CreatureMiscRefIdAdapter&); - CreatureMiscRefIdAdapter& operator= (const CreatureMiscRefIdAdapter&); - public: + CreatureMiscRefIdAdapter() = default; + CreatureMiscRefIdAdapter(const CreatureMiscRefIdAdapter&) = delete; + CreatureMiscRefIdAdapter& operator=(const CreatureMiscRefIdAdapter&) = delete; + ~CreatureMiscRefIdAdapter() override = default; - CreatureMiscRefIdAdapter (); - virtual ~CreatureMiscRefIdAdapter(); + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override; - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override; + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override; - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override; + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override; - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override; + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override; - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override; + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override; - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override; + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override; - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override; + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override; - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override; - - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override; + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override; }; - template + template class EffectsListAdapter; - template + template class EffectsRefIdAdapter : public EffectsListAdapter, public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented - EffectsRefIdAdapter (const EffectsRefIdAdapter&); - EffectsRefIdAdapter& operator= (const EffectsRefIdAdapter&); + EffectsRefIdAdapter(const EffectsRefIdAdapter&); + EffectsRefIdAdapter& operator=(const EffectsRefIdAdapter&); public: - - EffectsRefIdAdapter(UniversalId::Type type) :mType(type) {} - - virtual ~EffectsRefIdAdapter() {} - - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override + EffectsRefIdAdapter(UniversalId::Type type) + : mType(type) { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + } + + virtual ~EffectsRefIdAdapter() = default; + + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override + { + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); EffectsListAdapter::addRow(record, position); } - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); EffectsListAdapter::removeRow(record, rowToRemove); } - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); EffectsListAdapter::setTable(record, nestedTable); } - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return EffectsListAdapter::table(record); } - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return EffectsListAdapter::getData(record, subRowIndex, subColIndex); } - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); EffectsListAdapter::setData(record, value, subRowIndex, subColIndex); } - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { const Record record; // not used, just a dummy return EffectsListAdapter::getColumnsCount(record); } - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return EffectsListAdapter::getRowsCount(record); } }; @@ -1200,20 +1177,21 @@ namespace CSMWorld UniversalId::Type mType; // not implemented - NestedInventoryRefIdAdapter (const NestedInventoryRefIdAdapter&); - NestedInventoryRefIdAdapter& operator= (const NestedInventoryRefIdAdapter&); + NestedInventoryRefIdAdapter(const NestedInventoryRefIdAdapter&); + NestedInventoryRefIdAdapter& operator=(const NestedInventoryRefIdAdapter&); public: - - NestedInventoryRefIdAdapter(UniversalId::Type type) :mType(type) {} - - virtual ~NestedInventoryRefIdAdapter() {} - - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override + NestedInventoryRefIdAdapter(UniversalId::Type type) + : mType(type) { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + } + + virtual ~NestedInventoryRefIdAdapter() = default; + + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override + { + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT container = record.get(); std::vector& list = container.mInventory.mList; @@ -1223,88 +1201,88 @@ namespace CSMWorld if (position >= (int)list.size()) list.push_back(newRow); else - list.insert(list.begin()+position, newRow); + list.insert(list.begin() + position, newRow); - record.setModified (container); + record.setModified(container); } - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT container = record.get(); std::vector& list = container.mInventory.mList; - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - list.erase (list.begin () + rowToRemove); + list.erase(list.begin() + rowToRemove); - record.setModified (container); + record.setModified(container); } - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT container = record.get(); - container.mInventory.mList = - static_cast >&>(nestedTable).mNestedTable; + container.mInventory.mList + = static_cast>&>(nestedTable).mNestedTable; - record.setModified (container); + record.setModified(container); } - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mInventory.mList); + return new NestedTableWrapper>(record.get().mInventory.mList); } - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); const std::vector& list = record.get().mInventory.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); const ESM::ContItem& content = list.at(subRowIndex); switch (subColIndex) { - case 0: return QString::fromUtf8(content.mItem.c_str()); - case 1: return content.mCount; + case 0: + return QString::fromUtf8(content.mItem.getRefIdString().c_str()); + case 1: + return content.mCount; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); ESXRecordT container = record.get(); std::vector& list = container.mInventory.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - switch(subColIndex) + switch (subColIndex) { case 0: - list.at(subRowIndex).mItem.assign(std::string(value.toString().toUtf8().constData())); + list.at(subRowIndex).mItem = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); break; case 1: @@ -1315,18 +1293,15 @@ namespace CSMWorld throw std::runtime_error("Trying to access non-existing column in the nested table!"); } - record.setModified (container); + record.setModified(container); } - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override - { - return 2; - } + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 2; } - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return static_cast(record.get().mInventory.mList.size()); } @@ -1338,121 +1313,117 @@ namespace CSMWorld UniversalId::Type mType; // not implemented - NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&); - NestedSpellRefIdAdapter& operator= (const NestedSpellRefIdAdapter&); + NestedSpellRefIdAdapter(const NestedSpellRefIdAdapter&); + NestedSpellRefIdAdapter& operator=(const NestedSpellRefIdAdapter&); public: - - NestedSpellRefIdAdapter(UniversalId::Type type) :mType(type) {} - - virtual ~NestedSpellRefIdAdapter() {} - - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override + NestedSpellRefIdAdapter(UniversalId::Type type) + : mType(type) { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + } + + virtual ~NestedSpellRefIdAdapter() = default; + + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override + { + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT caster = record.get(); - std::vector& list = caster.mSpells.mList; + std::vector& list = caster.mSpells.mList; - std::string newString; + ESM::RefId newString; if (position >= (int)list.size()) list.push_back(newString); else - list.insert(list.begin()+position, newString); + list.insert(list.begin() + position, newString); - record.setModified (caster); + record.setModified(caster); } - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT caster = record.get(); - std::vector& list = caster.mSpells.mList; + std::vector& list = caster.mSpells.mList; - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - list.erase (list.begin () + rowToRemove); + list.erase(list.begin() + rowToRemove); - record.setModified (caster); + record.setModified(caster); } - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT caster = record.get(); - caster.mSpells.mList = - static_cast >&>(nestedTable).mNestedTable; + caster.mSpells.mList + = static_cast>&>(nestedTable).mNestedTable; - record.setModified (caster); + record.setModified(caster); } - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mSpells.mList); + return new NestedTableWrapper>(record.get().mSpells.mList); } - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); - const std::vector& list = record.get().mSpells.mList; + const std::vector& list = record.get().mSpells.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - const std::string& content = list.at(subRowIndex); + const ESM::RefId& content = list.at(subRowIndex); if (subColIndex == 0) - return QString::fromUtf8(content.c_str()); + return QString::fromUtf8(content.getRefIdString().c_str()); else throw std::runtime_error("Trying to access non-existing column in the nested table!"); } - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); ESXRecordT caster = record.get(); - std::vector& list = caster.mSpells.mList; + std::vector& list = caster.mSpells.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); if (subColIndex == 0) - list.at(subRowIndex) = std::string(value.toString().toUtf8()); + list.at(subRowIndex) = ESM::RefId::stringRefId(value.toString().toUtf8().constData()); else throw std::runtime_error("Trying to access non-existing column in the nested table!"); - record.setModified (caster); + record.setModified(caster); } - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override - { - return 1; - } + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 1; } - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return static_cast(record.get().mSpells.mList.size()); } @@ -1464,20 +1435,21 @@ namespace CSMWorld UniversalId::Type mType; // not implemented - NestedTravelRefIdAdapter (const NestedTravelRefIdAdapter&); - NestedTravelRefIdAdapter& operator= (const NestedTravelRefIdAdapter&); + NestedTravelRefIdAdapter(const NestedTravelRefIdAdapter&); + NestedTravelRefIdAdapter& operator=(const NestedTravelRefIdAdapter&); public: - - NestedTravelRefIdAdapter(UniversalId::Type type) :mType(type) {} - - virtual ~NestedTravelRefIdAdapter() {} - - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override + NestedTravelRefIdAdapter(UniversalId::Type type) + : mType(type) { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + } + + virtual ~NestedTravelRefIdAdapter() = default; + + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override + { + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT traveller = record.get(); std::vector& list = traveller.mTransport.mList; @@ -1491,119 +1463,136 @@ namespace CSMWorld ESM::Transport::Dest newRow; newRow.mPos = newPos; - newRow.mCellName = ""; + newRow.mCellName.clear(); if (position >= (int)list.size()) list.push_back(newRow); else - list.insert(list.begin()+position, newRow); + list.insert(list.begin() + position, newRow); - record.setModified (traveller); + record.setModified(traveller); } - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT traveller = record.get(); std::vector& list = traveller.mTransport.mList; - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - list.erase (list.begin () + rowToRemove); + list.erase(list.begin() + rowToRemove); - record.setModified (traveller); + record.setModified(traveller); } - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT traveller = record.get(); - traveller.mTransport.mList = - static_cast >&>(nestedTable).mNestedTable; + traveller.mTransport.mList + = static_cast>&>(nestedTable) + .mNestedTable; - record.setModified (traveller); + record.setModified(traveller); } - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mTransport.mList); + return new NestedTableWrapper>(record.get().mTransport.mList); } - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); const std::vector& list = record.get().mTransport.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); const ESM::Transport::Dest& content = list.at(subRowIndex); switch (subColIndex) { - case 0: return QString::fromUtf8(content.mCellName.c_str()); - case 1: return content.mPos.pos[0]; - case 2: return content.mPos.pos[1]; - case 3: return content.mPos.pos[2]; - case 4: return content.mPos.rot[0]; - case 5: return content.mPos.rot[1]; - case 6: return content.mPos.rot[2]; + case 0: + return QString::fromUtf8(content.mCellName.c_str()); + case 1: + return content.mPos.pos[0]; + case 2: + return content.mPos.pos[1]; + case 3: + return content.mPos.pos[2]; + case 4: + return content.mPos.rot[0]; + case 5: + return content.mPos.rot[1]; + case 6: + return content.mPos.rot[2]; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); ESXRecordT traveller = record.get(); std::vector& list = traveller.mTransport.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - switch(subColIndex) + switch (subColIndex) { - case 0: list.at(subRowIndex).mCellName = std::string(value.toString().toUtf8().constData()); break; - case 1: list.at(subRowIndex).mPos.pos[0] = value.toFloat(); break; - case 2: list.at(subRowIndex).mPos.pos[1] = value.toFloat(); break; - case 3: list.at(subRowIndex).mPos.pos[2] = value.toFloat(); break; - case 4: list.at(subRowIndex).mPos.rot[0] = value.toFloat(); break; - case 5: list.at(subRowIndex).mPos.rot[1] = value.toFloat(); break; - case 6: list.at(subRowIndex).mPos.rot[2] = value.toFloat(); break; + case 0: + list.at(subRowIndex).mCellName = value.toString().toUtf8().constData(); + break; + case 1: + list.at(subRowIndex).mPos.pos[0] = value.toFloat(); + break; + case 2: + list.at(subRowIndex).mPos.pos[1] = value.toFloat(); + break; + case 3: + list.at(subRowIndex).mPos.pos[2] = value.toFloat(); + break; + case 4: + list.at(subRowIndex).mPos.rot[0] = value.toFloat(); + break; + case 5: + list.at(subRowIndex).mPos.rot[1] = value.toFloat(); + break; + case 6: + list.at(subRowIndex).mPos.rot[2] = value.toFloat(); + break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } - record.setModified (traveller); + record.setModified(traveller); } - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override - { - return 7; - } + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 7; } - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return static_cast(record.get().mTransport.mList.size()); } @@ -1615,22 +1604,23 @@ namespace CSMWorld UniversalId::Type mType; // not implemented - ActorAiRefIdAdapter (const ActorAiRefIdAdapter&); - ActorAiRefIdAdapter& operator= (const ActorAiRefIdAdapter&); + ActorAiRefIdAdapter(const ActorAiRefIdAdapter&); + ActorAiRefIdAdapter& operator=(const ActorAiRefIdAdapter&); public: + ActorAiRefIdAdapter(UniversalId::Type type) + : mType(type) + { + } - ActorAiRefIdAdapter(UniversalId::Type type) :mType(type) {} - - virtual ~ActorAiRefIdAdapter() {} + virtual ~ActorAiRefIdAdapter() = default; // FIXME: should check if the AI package type is already in the list and use a default // that wasn't used already (in extreme case do not add anything at all? - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT actor = record.get(); std::vector& list = actor.mAiPackage.mList; @@ -1642,67 +1632,66 @@ namespace CSMWorld newRow.mWander.mTimeOfDay = 0; for (int i = 0; i < 8; ++i) newRow.mWander.mIdle[i] = 0; - newRow.mWander.mShouldRepeat = 0; - newRow.mCellName = ""; + newRow.mWander.mShouldRepeat = 1; + newRow.mCellName.clear(); if (position >= (int)list.size()) list.push_back(newRow); else - list.insert(list.begin()+position, newRow); + list.insert(list.begin() + position, newRow); - record.setModified (actor); + record.setModified(actor); } - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT actor = record.get(); std::vector& list = actor.mAiPackage.mList; - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - list.erase (list.begin () + rowToRemove); + list.erase(list.begin() + rowToRemove); - record.setModified (actor); + record.setModified(actor); } - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT actor = record.get(); - actor.mAiPackage.mList = - static_cast >&>(nestedTable).mNestedTable; + actor.mAiPackage.mList + = static_cast>&>(nestedTable) + .mNestedTable; - record.setModified (actor); + record.setModified(actor); } - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mAiPackage.mList); + return new NestedTableWrapper>(record.get().mAiPackage.mList); } - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); const std::vector& list = record.get().mAiPackage.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); const ESM::AIPackage& content = list.at(subRowIndex); @@ -1712,23 +1701,29 @@ namespace CSMWorld // FIXME: should more than one AI package type be allowed? Check vanilla switch (content.mType) { - case ESM::AI_Wander: return 0; - case ESM::AI_Travel: return 1; - case ESM::AI_Follow: return 2; - case ESM::AI_Escort: return 3; - case ESM::AI_Activate: return 4; - case ESM::AI_CNDT: - default: return QVariant(); + case ESM::AI_Wander: + return 0; + case ESM::AI_Travel: + return 1; + case ESM::AI_Follow: + return 2; + case ESM::AI_Escort: + return 3; + case ESM::AI_Activate: + return 4; + default: + return QVariant(); } case 1: // wander dist if (content.mType == ESM::AI_Wander) return content.mWander.mDistance; else return QVariant(); - case 2: // wander dur - if (content.mType == ESM::AI_Wander || - content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + case 2: // wander/follow dur + if (content.mType == ESM::AI_Wander) return content.mWander.mDuration; + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + return content.mTarget.mDuration; else return QVariant(); case 3: // wander ToD @@ -1745,12 +1740,18 @@ namespace CSMWorld case 10: case 11: if (content.mType == ESM::AI_Wander) - return static_cast(content.mWander.mIdle[subColIndex-4]); + return static_cast(content.mWander.mIdle[subColIndex - 4]); else return QVariant(); - case 12: // wander repeat + case 12: // repeat if (content.mType == ESM::AI_Wander) return content.mWander.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Travel) + return content.mTravel.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + return content.mTarget.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Activate) + return content.mActivate.mShouldRepeat != 0; else return QVariant(); case 13: // activate name @@ -1794,30 +1795,41 @@ namespace CSMWorld } } - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); ESXRecordT actor = record.get(); std::vector& list = actor.mAiPackage.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); ESM::AIPackage& content = list.at(subRowIndex); - switch(subColIndex) + switch (subColIndex) { case 0: // ai package type switch (value.toInt()) { - case 0: content.mType = ESM::AI_Wander; break; - case 1: content.mType = ESM::AI_Travel; break; - case 2: content.mType = ESM::AI_Follow; break; - case 3: content.mType = ESM::AI_Escort; break; - case 4: content.mType = ESM::AI_Activate; break; - default: return; // return without saving + case 0: + content.mType = ESM::AI_Wander; + break; + case 1: + content.mType = ESM::AI_Travel; + break; + case 2: + content.mType = ESM::AI_Follow; + break; + case 3: + content.mType = ESM::AI_Escort; + break; + case 4: + content.mType = ESM::AI_Activate; + break; + default: + return; // return without saving } break; // always save @@ -1829,11 +1841,13 @@ namespace CSMWorld break; // always save case 2: - if (content.mType == ESM::AI_Wander || - content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + if (content.mType == ESM::AI_Wander) content.mWander.mDuration = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mDuration = static_cast(value.toInt()); else return; // return without saving + break; case 3: if (content.mType == ESM::AI_Wander) content.mWander.mTimeOfDay = static_cast(value.toInt()); @@ -1850,7 +1864,7 @@ namespace CSMWorld case 10: case 11: if (content.mType == ESM::AI_Wander) - content.mWander.mIdle[subColIndex-4] = static_cast(value.toInt()); + content.mWander.mIdle[subColIndex - 4] = static_cast(value.toInt()); else return; // return without saving @@ -1858,20 +1872,32 @@ namespace CSMWorld case 12: if (content.mType == ESM::AI_Wander) content.mWander.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Travel) + content.mTravel.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Activate) + content.mActivate.mShouldRepeat = static_cast(value.toInt()); else return; // return without saving break; // always save case 13: // NAME32 if (content.mType == ESM::AI_Activate) - content.mActivate.mName.assign(value.toString().toUtf8().constData()); + { + const QByteArray name = value.toString().toUtf8(); + content.mActivate.mName.assign(std::string_view(name.constData(), name.size())); + } else return; // return without saving break; // always save case 14: // NAME32 if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mId.assign(value.toString().toUtf8().constData()); + { + const QByteArray id = value.toString().toUtf8(); + content.mTarget.mId.assign(std::string_view(id.constData(), id.size())); + } else return; // return without saving @@ -1914,111 +1940,107 @@ namespace CSMWorld throw std::runtime_error("Trying to access non-existing column in the nested table!"); } - record.setModified (actor); + record.setModified(actor); } - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override - { - return 19; - } + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 19; } - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return static_cast(record.get().mAiPackage.mList.size()); } }; - template class BodyPartRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; // not implemented - BodyPartRefIdAdapter (const BodyPartRefIdAdapter&); - BodyPartRefIdAdapter& operator= (const BodyPartRefIdAdapter&); + BodyPartRefIdAdapter(const BodyPartRefIdAdapter&); + BodyPartRefIdAdapter& operator=(const BodyPartRefIdAdapter&); public: - - BodyPartRefIdAdapter(UniversalId::Type type) :mType(type) {} - - virtual ~BodyPartRefIdAdapter() {} - - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override + BodyPartRefIdAdapter(UniversalId::Type type) + : mType(type) { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + } + + virtual ~BodyPartRefIdAdapter() = default; + + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override + { + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT apparel = record.get(); std::vector& list = apparel.mParts.mParts; ESM::PartReference newPart; newPart.mPart = 0; // 0 == head - newPart.mMale = ""; - newPart.mFemale = ""; + newPart.mMale = ESM::RefId(); + newPart.mFemale = ESM::RefId(); if (position >= (int)list.size()) list.push_back(newPart); else - list.insert(list.begin()+position, newPart); + list.insert(list.begin() + position, newPart); - record.setModified (apparel); + record.setModified(apparel); } - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT apparel = record.get(); std::vector& list = apparel.mParts.mParts; - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - list.erase (list.begin () + rowToRemove); + list.erase(list.begin() + rowToRemove); - record.setModified (apparel); + record.setModified(apparel); } - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT apparel = record.get(); - apparel.mParts.mParts = - static_cast >&>(nestedTable).mNestedTable; + apparel.mParts.mParts + = static_cast>&>(nestedTable) + .mNestedTable; - record.setModified (apparel); + record.setModified(apparel); } - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mParts.mParts); + return new NestedTableWrapper>(record.get().mParts.mParts); } - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); const std::vector& list = record.get().mParts.mParts; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); const ESM::PartReference& content = list.at(subRowIndex); @@ -2031,105 +2053,107 @@ namespace CSMWorld else throw std::runtime_error("Part Reference Type unexpected value"); } - case 1: return QString(content.mMale.c_str()); - case 2: return QString(content.mFemale.c_str()); + case 1: + return QString(content.mMale.getRefIdString().c_str()); + case 2: + return QString(content.mFemale.getRefIdString().c_str()); default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); ESXRecordT apparel = record.get(); std::vector& list = apparel.mParts.mParts; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - switch(subColIndex) + switch (subColIndex) { - case 0: list.at(subRowIndex).mPart = static_cast(value.toInt()); break; - case 1: list.at(subRowIndex).mMale = value.toString().toStdString(); break; - case 2: list.at(subRowIndex).mFemale = value.toString().toStdString(); break; + case 0: + list.at(subRowIndex).mPart = static_cast(value.toInt()); + break; + case 1: + list.at(subRowIndex).mMale = ESM::RefId::stringRefId(value.toString().toStdString()); + break; + case 2: + list.at(subRowIndex).mFemale = ESM::RefId::stringRefId(value.toString().toStdString()); + break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } - record.setModified (apparel); + record.setModified(apparel); } - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override - { - return 3; - } + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 3; } - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return static_cast(record.get().mParts.mParts.size()); } }; - struct LevListColumns : public BaseColumns { - const RefIdColumn *mLevList; - const RefIdColumn *mNestedListLevList; + const RefIdColumn* mLevList; + const RefIdColumn* mNestedListLevList; - LevListColumns (const BaseColumns& base) - : BaseColumns (base) - , mLevList(nullptr) - , mNestedListLevList(nullptr) - {} + LevListColumns(const BaseColumns& base) + : BaseColumns(base) + , mLevList(nullptr) + , mNestedListLevList(nullptr) + { + } }; - template + template class LevelledListRefIdAdapter : public BaseRefIdAdapter { - LevListColumns mLevList; + LevListColumns mLevList; - public: + public: + LevelledListRefIdAdapter(UniversalId::Type type, const LevListColumns& columns); - LevelledListRefIdAdapter (UniversalId::Type type, const LevListColumns &columns); + QVariant getData(const RefIdColumn* column, const RefIdData& data, int index) const override; - QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) - const override; - - void setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const override; - ///< If the data type does not match an exception is thrown. + void setData(const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const override; + ///< If the data type does not match an exception is thrown. }; - template - LevelledListRefIdAdapter::LevelledListRefIdAdapter (UniversalId::Type type, - const LevListColumns &columns) - : BaseRefIdAdapter (type, columns), mLevList (columns) - {} - - template - QVariant LevelledListRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const + template + LevelledListRefIdAdapter::LevelledListRefIdAdapter(UniversalId::Type type, const LevListColumns& columns) + : BaseRefIdAdapter(type, columns) + , mLevList(columns) { - if (column==mLevList.mLevList || column == mLevList.mNestedListLevList) + } + + template + QVariant LevelledListRefIdAdapter::getData( + const RefIdColumn* column, const RefIdData& data, int index) const + { + if (column == mLevList.mLevList || column == mLevList.mNestedListLevList) return QVariant::fromValue(ColumnBase::TableEdit_Full); - return BaseRefIdAdapter::getData (column, data, index); + return BaseRefIdAdapter::getData(column, data, index); } - template - void LevelledListRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, - const QVariant& value) const + template + void LevelledListRefIdAdapter::setData( + const RefIdColumn* column, RefIdData& data, int index, const QVariant& value) const { - BaseRefIdAdapter::setData (column, data, index, value); + BaseRefIdAdapter::setData(column, data, index, value); return; } - // for non-tables template class NestedListLevListRefIdAdapter : public NestedRefIdAdapterBase @@ -2137,53 +2161,54 @@ namespace CSMWorld UniversalId::Type mType; // not implemented - NestedListLevListRefIdAdapter (const NestedListLevListRefIdAdapter&); - NestedListLevListRefIdAdapter& operator= (const NestedListLevListRefIdAdapter&); + NestedListLevListRefIdAdapter(const NestedListLevListRefIdAdapter&); + NestedListLevListRefIdAdapter& operator=(const NestedListLevListRefIdAdapter&); public: - NestedListLevListRefIdAdapter(UniversalId::Type type) - :mType(type) {} - - virtual ~NestedListLevListRefIdAdapter() {} - - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override + : mType(type) { - throw std::logic_error ("cannot add a row to a fixed table"); } - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override + virtual ~NestedListLevListRefIdAdapter() = default; + + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override { - throw std::logic_error ("cannot remove a row to a fixed table"); + throw std::logic_error("cannot add a row to a fixed table"); } - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("cannot remove a row to a fixed table"); } - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override { - throw std::logic_error ("table operation not supported"); + throw std::logic_error("table operation not supported"); } - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + throw std::logic_error("table operation not supported"); + } + + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override + { + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); if (mType == UniversalId::Type_CreatureLevelledList) { switch (subColIndex) { - case 0: return QVariant(); // disable the checkbox editor - case 1: return record.get().mFlags & ESM::CreatureLevList::AllLevels; - case 2: return static_cast (record.get().mChanceNone); + case 0: + return QVariant(); // disable the checkbox editor + case 1: + return record.get().mFlags & ESM::CreatureLevList::AllLevels; + case 2: + return static_cast(record.get().mChanceNone); default: throw std::runtime_error("Trying to access non-existing column in levelled creatues!"); } @@ -2192,30 +2217,34 @@ namespace CSMWorld { switch (subColIndex) { - case 0: return record.get().mFlags & ESM::ItemLevList::Each; - case 1: return record.get().mFlags & ESM::ItemLevList::AllLevels; - case 2: return static_cast (record.get().mChanceNone); + case 0: + return record.get().mFlags & ESM::ItemLevList::Each; + case 1: + return record.get().mFlags & ESM::ItemLevList::AllLevels; + case 2: + return static_cast(record.get().mChanceNone); default: throw std::runtime_error("Trying to access non-existing column in levelled items!"); } } } - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); ESXRecordT leveled = record.get(); if (mType == UniversalId::Type_CreatureLevelledList) { - switch(subColIndex) + switch (subColIndex) { - case 0: return; // return without saving + case 0: + return; // return without saving case 1: { - if(value.toBool()) + if (value.toBool()) { leveled.mFlags |= ESM::CreatureLevList::AllLevels; break; @@ -2226,18 +2255,20 @@ namespace CSMWorld break; } } - case 2: leveled.mChanceNone = static_cast(value.toInt()); break; + case 2: + leveled.mChanceNone = static_cast(value.toInt()); + break; default: throw std::runtime_error("Trying to set non-existing column in levelled creatures!"); } } else { - switch(subColIndex) + switch (subColIndex) { case 0: { - if(value.toBool()) + if (value.toBool()) { leveled.mFlags |= ESM::ItemLevList::Each; break; @@ -2250,7 +2281,7 @@ namespace CSMWorld } case 1: { - if(value.toBool()) + if (value.toBool()) { leveled.mFlags |= ESM::ItemLevList::AllLevels; break; @@ -2261,21 +2292,20 @@ namespace CSMWorld break; } } - case 2: leveled.mChanceNone = static_cast(value.toInt()); break; + case 2: + leveled.mChanceNone = static_cast(value.toInt()); + break; default: throw std::runtime_error("Trying to set non-existing column in levelled items!"); } } - record.setModified (leveled); + record.setModified(leveled); } - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override - { - return 3; - } + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 3; } - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override { return 1; // fixed at size 1 } @@ -2288,129 +2318,133 @@ namespace CSMWorld UniversalId::Type mType; // not implemented - NestedLevListRefIdAdapter (const NestedLevListRefIdAdapter&); - NestedLevListRefIdAdapter& operator= (const NestedLevListRefIdAdapter&); + NestedLevListRefIdAdapter(const NestedLevListRefIdAdapter&); + NestedLevListRefIdAdapter& operator=(const NestedLevListRefIdAdapter&); public: - - NestedLevListRefIdAdapter(UniversalId::Type type) :mType(type) {} - - virtual ~NestedLevListRefIdAdapter() {} - - void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const override + NestedLevListRefIdAdapter(UniversalId::Type type) + : mType(type) { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + } + + virtual ~NestedLevListRefIdAdapter() = default; + + void addNestedRow(const RefIdColumn* column, RefIdData& data, int index, int position) const override + { + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT leveled = record.get(); std::vector& list = leveled.mList; ESM::LevelledListBase::LevelItem newItem; - newItem.mId = ""; + newItem.mId = ESM::RefId(); newItem.mLevel = 0; if (position >= (int)list.size()) list.push_back(newItem); else - list.insert(list.begin()+position, newItem); + list.insert(list.begin() + position, newItem); - record.setModified (leveled); + record.setModified(leveled); } - void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const override + void removeNestedRow(const RefIdColumn* column, RefIdData& data, int index, int rowToRemove) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT leveled = record.get(); std::vector& list = leveled.mList; - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (rowToRemove < 0 || rowToRemove >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - list.erase (list.begin () + rowToRemove); + list.erase(list.begin() + rowToRemove); - record.setModified (leveled); + record.setModified(leveled); } - void setNestedTable (const RefIdColumn* column, - RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const override + void setNestedTable(const RefIdColumn* column, RefIdData& data, int index, + const NestedTableWrapperBase& nestedTable) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); ESXRecordT leveled = record.get(); - leveled.mList = - static_cast >&>(nestedTable).mNestedTable; + leveled.mList + = static_cast>&>( + nestedTable) + .mNestedTable; - record.setModified (leveled); + record.setModified(leveled); } - NestedTableWrapperBase* nestedTable (const RefIdColumn* column, - const RefIdData& data, int index) const override + NestedTableWrapperBase* nestedTable(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(record.get().mList); + return new NestedTableWrapper>(record.get().mList); } - QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const override + QVariant getNestedData(const RefIdColumn* column, const RefIdData& data, int index, int subRowIndex, + int subColIndex) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); const std::vector& list = record.get().mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); const ESM::LevelledListBase::LevelItem& content = list.at(subRowIndex); switch (subColIndex) { - case 0: return QString(content.mId.c_str()); - case 1: return content.mLevel; + case 0: + return QString(content.mId.getRefIdString().c_str()); + case 1: + return content.mLevel; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } } - void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const override + void setNestedData(const RefIdColumn* column, RefIdData& data, int row, const QVariant& value, int subRowIndex, + int subColIndex) const override { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, mType))); ESXRecordT leveled = record.get(); std::vector& list = leveled.mList; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + if (subRowIndex < 0 || subRowIndex >= static_cast(list.size())) + throw std::runtime_error("index out of range"); - switch(subColIndex) + switch (subColIndex) { - case 0: list.at(subRowIndex).mId = value.toString().toStdString(); break; - case 1: list.at(subRowIndex).mLevel = static_cast(value.toInt()); break; + case 0: + list.at(subRowIndex).mId = ESM::RefId::stringRefId(value.toString().toStdString()); + break; + case 1: + list.at(subRowIndex).mLevel = static_cast(value.toInt()); + break; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } - record.setModified (leveled); + record.setModified(leveled); } - int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const override - { - return 2; - } + int getNestedColumnsCount(const RefIdColumn* column, const RefIdData& data) const override { return 2; } - int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const override + int getNestedRowsCount(const RefIdColumn* column, const RefIdData& data, int index) const override { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + const Record& record + = static_cast&>(data.getRecord(RefIdData::LocalIndex(index, mType))); return static_cast(record.get().mList.size()); } diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index cd2dd89df..668ee810e 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -1,20 +1,42 @@ #include "refidcollection.hpp" -#include +#include #include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "columns.hpp" +#include "nestedcoladapterimp.hpp" +#include "nestedtablewrapper.hpp" #include "refidadapter.hpp" #include "refidadapterimp.hpp" -#include "columns.hpp" -#include "nestedtablewrapper.hpp" -#include "nestedcoladapterimp.hpp" -CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag, - bool editable, bool userEditable) - : NestableColumn (columnId, displayType, flag), mEditable (editable), mUserEditable (userEditable) -{} +CSMWorld::RefIdColumn::RefIdColumn(int columnId, Display displayType, int flag, bool editable, bool userEditable) + : NestableColumn(columnId, displayType, flag) + , mEditable(editable) + , mUserEditable(userEditable) +{ +} bool CSMWorld::RefIdColumn::isEditable() const { @@ -26,12 +48,12 @@ bool CSMWorld::RefIdColumn::isUserEditable() const return mUserEditable; } -const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdapter (UniversalId::Type type) const +const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdapter(UniversalId::Type type) const { - std::map::const_iterator iter = mAdapters.find (type); + std::map::const_iterator iter = mAdapters.find(type); - if (iter==mAdapters.end()) - throw std::logic_error ("unsupported type in RefIdCollection"); + if (iter == mAdapters.end()) + throw std::logic_error("unsupported type in RefIdCollection"); return *iter->second; } @@ -40,8 +62,8 @@ CSMWorld::RefIdCollection::RefIdCollection() { BaseColumns baseColumns; - mColumns.emplace_back(Columns::ColumnId_Id, ColumnBase::Display_Id, - ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false); + mColumns.emplace_back( + Columns::ColumnId_Id, ColumnBase::Display_Id, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false); baseColumns.mId = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Modification, ColumnBase::Display_RecordState, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true, false); @@ -49,20 +71,27 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.emplace_back(Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false); baseColumns.mType = &mColumns.back(); + mColumns.emplace_back(Columns::ColumnId_Blocked, ColumnBase::Display_Boolean, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh); + baseColumns.mBlocked = &mColumns.back(); - ModelColumns modelColumns (baseColumns); + ModelColumns modelColumns(baseColumns); + mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean); + modelColumns.mPersistence = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh); modelColumns.mModel = &mColumns.back(); - NameColumns nameColumns (modelColumns); + NameColumns nameColumns(modelColumns); - mColumns.emplace_back(Columns::ColumnId_Name, ColumnBase::Display_String); + // Only items that can be placed in a container have the 32 character limit, but enforce + // that for all referenceable types for now. + mColumns.emplace_back(Columns::ColumnId_Name, ColumnBase::Display_String32); nameColumns.mName = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Script, ColumnBase::Display_Script); nameColumns.mScript = &mColumns.back(); - InventoryColumns inventoryColumns (nameColumns); + InventoryColumns inventoryColumns(nameColumns); mColumns.emplace_back(Columns::ColumnId_Icon, ColumnBase::Display_Icon); inventoryColumns.mIcon = &mColumns.back(); @@ -71,62 +100,49 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.emplace_back(Columns::ColumnId_CoinValue, ColumnBase::Display_Integer); inventoryColumns.mValue = &mColumns.back(); - IngredientColumns ingredientColumns (inventoryColumns); - mColumns.emplace_back(Columns::ColumnId_EffectList, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + IngredientColumns ingredientColumns(inventoryColumns); + mColumns.emplace_back(Columns::ColumnId_EffectList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); ingredientColumns.mEffects = &mColumns.back(); std::map ingredientEffectsMap; - ingredientEffectsMap.insert(std::make_pair(UniversalId::Type_Ingredient, - new IngredEffectRefIdAdapter ())); + ingredientEffectsMap.insert(std::make_pair(UniversalId::Type_Ingredient, new IngredEffectRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), ingredientEffectsMap); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_IngredEffectId)); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_EffectId, ColumnBase::Display_IngredEffectId)); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); // nested table - PotionColumns potionColumns (inventoryColumns); - mColumns.emplace_back(Columns::ColumnId_EffectList, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + PotionColumns potionColumns(inventoryColumns); + mColumns.emplace_back(Columns::ColumnId_EffectList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); potionColumns.mEffects = &mColumns.back(); // see refidadapterimp.hpp std::map effectsMap; - effectsMap.insert(std::make_pair(UniversalId::Type_Potion, - new EffectsRefIdAdapter (UniversalId::Type_Potion))); + effectsMap.insert( + std::make_pair(UniversalId::Type_Potion, new EffectsRefIdAdapter(UniversalId::Type_Potion))); mNestedAdapters.emplace_back(&mColumns.back(), effectsMap); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_EffectArea, ColumnBase::Display_String)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_EffectArea, ColumnBase::Display_String)); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); + new NestedChildColumn(Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); + mColumns.back().addColumn(new NestedChildColumn(Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); - EnchantableColumns enchantableColumns (inventoryColumns); + EnchantableColumns enchantableColumns(inventoryColumns); mColumns.emplace_back(Columns::ColumnId_Enchantment, ColumnBase::Display_Enchantment); enchantableColumns.mEnchantment = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_EnchantmentPoints, ColumnBase::Display_Integer); enchantableColumns.mEnchantmentPoints = &mColumns.back(); - ToolColumns toolsColumns (inventoryColumns); + ToolColumns toolsColumns(inventoryColumns); mColumns.emplace_back(Columns::ColumnId_Quality, ColumnBase::Display_Float); toolsColumns.mQuality = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Charges, ColumnBase::Display_Integer); toolsColumns.mUses = &mColumns.back(); - ActorColumns actorsColumns (nameColumns); + ActorColumns actorsColumns(nameColumns); mColumns.emplace_back(Columns::ColumnId_AiHello, ColumnBase::Display_UnsignedInteger16); actorsColumns.mHello = &mColumns.back(); @@ -138,194 +154,152 @@ CSMWorld::RefIdCollection::RefIdCollection() actorsColumns.mAlarm = &mColumns.back(); // Nested table - mColumns.emplace_back(Columns::ColumnId_ActorInventory, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + mColumns.emplace_back( + Columns::ColumnId_ActorInventory, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); actorsColumns.mInventory = &mColumns.back(); std::map inventoryMap; - inventoryMap.insert(std::make_pair(UniversalId::Type_Npc, - new NestedInventoryRefIdAdapter (UniversalId::Type_Npc))); - inventoryMap.insert(std::make_pair(UniversalId::Type_Creature, - new NestedInventoryRefIdAdapter (UniversalId::Type_Creature))); + inventoryMap.insert( + std::make_pair(UniversalId::Type_Npc, new NestedInventoryRefIdAdapter(UniversalId::Type_Npc))); + inventoryMap.insert(std::make_pair( + UniversalId::Type_Creature, new NestedInventoryRefIdAdapter(UniversalId::Type_Creature))); mNestedAdapters.emplace_back(&mColumns.back(), inventoryMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_Referenceable)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_ItemCount, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn(Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_Referenceable)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_ItemCount, CSMWorld::ColumnBase::Display_Integer)); // Nested table - mColumns.emplace_back(Columns::ColumnId_SpellList, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + mColumns.emplace_back(Columns::ColumnId_SpellList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); actorsColumns.mSpells = &mColumns.back(); std::map spellsMap; - spellsMap.insert(std::make_pair(UniversalId::Type_Npc, - new NestedSpellRefIdAdapter (UniversalId::Type_Npc))); - spellsMap.insert(std::make_pair(UniversalId::Type_Creature, - new NestedSpellRefIdAdapter (UniversalId::Type_Creature))); + spellsMap.insert( + std::make_pair(UniversalId::Type_Npc, new NestedSpellRefIdAdapter(UniversalId::Type_Npc))); + spellsMap.insert(std::make_pair( + UniversalId::Type_Creature, new NestedSpellRefIdAdapter(UniversalId::Type_Creature))); mNestedAdapters.emplace_back(&mColumns.back(), spellsMap); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_Spell)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_Spell)); // Nested table - mColumns.emplace_back(Columns::ColumnId_NpcDestinations, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + mColumns.emplace_back( + Columns::ColumnId_NpcDestinations, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); actorsColumns.mDestinations = &mColumns.back(); std::map destMap; - destMap.insert(std::make_pair(UniversalId::Type_Npc, - new NestedTravelRefIdAdapter (UniversalId::Type_Npc))); - destMap.insert(std::make_pair(UniversalId::Type_Creature, - new NestedTravelRefIdAdapter (UniversalId::Type_Creature))); + destMap.insert( + std::make_pair(UniversalId::Type_Npc, new NestedTravelRefIdAdapter(UniversalId::Type_Npc))); + destMap.insert(std::make_pair( + UniversalId::Type_Creature, new NestedTravelRefIdAdapter(UniversalId::Type_Creature))); mNestedAdapters.emplace_back(&mColumns.back(), destMap); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_DestinationCell, CSMWorld::ColumnBase::Display_Cell)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PosX, CSMWorld::ColumnBase::Display_Float)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PosY, CSMWorld::ColumnBase::Display_Float)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_DestinationCell, CSMWorld::ColumnBase::Display_Cell)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_PosX, CSMWorld::ColumnBase::Display_Float)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_PosY, CSMWorld::ColumnBase::Display_Float)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double)); // Nested table - mColumns.emplace_back(Columns::ColumnId_AiPackageList, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + mColumns.emplace_back(Columns::ColumnId_AiPackageList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); actorsColumns.mAiPackages = &mColumns.back(); std::map aiMap; - aiMap.insert(std::make_pair(UniversalId::Type_Npc, - new ActorAiRefIdAdapter (UniversalId::Type_Npc))); - aiMap.insert(std::make_pair(UniversalId::Type_Creature, - new ActorAiRefIdAdapter (UniversalId::Type_Creature))); + aiMap.insert(std::make_pair(UniversalId::Type_Npc, new ActorAiRefIdAdapter(UniversalId::Type_Npc))); + aiMap.insert( + std::make_pair(UniversalId::Type_Creature, new ActorAiRefIdAdapter(UniversalId::Type_Creature))); mNestedAdapters.emplace_back(&mColumns.back(), aiMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiPackageType, CSMWorld::ColumnBase::Display_AiPackageType)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderDist, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn(Columns::ColumnId_AiPackageType, CSMWorld::ColumnBase::Display_AiPackageType)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderDist, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Idle5, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle5, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiTargetCell, CSMWorld::ColumnBase::Display_String)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PosX, CSMWorld::ColumnBase::Display_Float)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PosY, CSMWorld::ColumnBase::Display_Float)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); + new RefIdColumn(Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String32)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String32)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiTargetCell, CSMWorld::ColumnBase::Display_String)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_PosX, CSMWorld::ColumnBase::Display_Float)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_PosY, CSMWorld::ColumnBase::Display_Float)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float)); static const struct { int mName; unsigned int mFlag; - } sServiceTable[] = - { - { Columns::ColumnId_BuysWeapons, ESM::NPC::Weapon}, - { Columns::ColumnId_BuysArmor, ESM::NPC::Armor}, - { Columns::ColumnId_BuysClothing, ESM::NPC::Clothing}, - { Columns::ColumnId_BuysBooks, ESM::NPC::Books}, - { Columns::ColumnId_BuysIngredients, ESM::NPC::Ingredients}, - { Columns::ColumnId_BuysLockpicks, ESM::NPC::Picks}, - { Columns::ColumnId_BuysProbes, ESM::NPC::Probes}, - { Columns::ColumnId_BuysLights, ESM::NPC::Lights}, - { Columns::ColumnId_BuysApparati, ESM::NPC::Apparatus}, - { Columns::ColumnId_BuysRepairItems, ESM::NPC::RepairItem}, - { Columns::ColumnId_BuysMiscItems, ESM::NPC::Misc}, - { Columns::ColumnId_BuysPotions, ESM::NPC::Potions}, - { Columns::ColumnId_BuysMagicItems, ESM::NPC::MagicItems}, - { Columns::ColumnId_SellsSpells, ESM::NPC::Spells}, - { Columns::ColumnId_Trainer, ESM::NPC::Training}, - { Columns::ColumnId_Spellmaking, ESM::NPC::Spellmaking}, - { Columns::ColumnId_EnchantingService, ESM::NPC::Enchanting}, - { Columns::ColumnId_RepairService, ESM::NPC::Repair}, - { -1, 0 } - }; + } sServiceTable[] = { { Columns::ColumnId_BuysWeapons, ESM::NPC::Weapon }, + { Columns::ColumnId_BuysArmor, ESM::NPC::Armor }, { Columns::ColumnId_BuysClothing, ESM::NPC::Clothing }, + { Columns::ColumnId_BuysBooks, ESM::NPC::Books }, { Columns::ColumnId_BuysIngredients, ESM::NPC::Ingredients }, + { Columns::ColumnId_BuysLockpicks, ESM::NPC::Picks }, { Columns::ColumnId_BuysProbes, ESM::NPC::Probes }, + { Columns::ColumnId_BuysLights, ESM::NPC::Lights }, { Columns::ColumnId_BuysApparati, ESM::NPC::Apparatus }, + { Columns::ColumnId_BuysRepairItems, ESM::NPC::RepairItem }, + { Columns::ColumnId_BuysMiscItems, ESM::NPC::Misc }, { Columns::ColumnId_BuysPotions, ESM::NPC::Potions }, + { Columns::ColumnId_BuysMagicItems, ESM::NPC::MagicItems }, { Columns::ColumnId_SellsSpells, ESM::NPC::Spells }, + { Columns::ColumnId_Trainer, ESM::NPC::Training }, { Columns::ColumnId_Spellmaking, ESM::NPC::Spellmaking }, + { Columns::ColumnId_EnchantingService, ESM::NPC::Enchanting }, + { Columns::ColumnId_RepairService, ESM::NPC::Repair }, { -1, 0 } }; - for (int i=0; sServiceTable[i].mName!=-1; ++i) + for (int i = 0; sServiceTable[i].mName != -1; ++i) { mColumns.emplace_back(sServiceTable[i].mName, ColumnBase::Display_Boolean); - actorsColumns.mServices.insert (std::make_pair (&mColumns.back(), sServiceTable[i].mFlag)); + actorsColumns.mServices.insert(std::make_pair(&mColumns.back(), sServiceTable[i].mFlag)); } mColumns.emplace_back(Columns::ColumnId_AutoCalc, ColumnBase::Display_Boolean, - ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh); - const RefIdColumn *autoCalc = &mColumns.back(); + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh); + const RefIdColumn* autoCalc = &mColumns.back(); - mColumns.emplace_back(Columns::ColumnId_ApparatusType, - ColumnBase::Display_ApparatusType); - const RefIdColumn *apparatusType = &mColumns.back(); + mColumns.emplace_back(Columns::ColumnId_ApparatusType, ColumnBase::Display_ApparatusType); + const RefIdColumn* apparatusType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_ArmorType, ColumnBase::Display_ArmorType); - const RefIdColumn *armorType = &mColumns.back(); + const RefIdColumn* armorType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Health, ColumnBase::Display_Integer); - const RefIdColumn *health = &mColumns.back(); + const RefIdColumn* health = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_ArmorValue, ColumnBase::Display_Integer); - const RefIdColumn *armor = &mColumns.back(); + const RefIdColumn* armor = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_BookType, ColumnBase::Display_BookType); - const RefIdColumn *bookType = &mColumns.back(); + const RefIdColumn* bookType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Skill, ColumnBase::Display_SkillId); - const RefIdColumn *skill = &mColumns.back(); + const RefIdColumn* skill = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Text, ColumnBase::Display_LongString); - const RefIdColumn *text = &mColumns.back(); + const RefIdColumn* text = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_ClothingType, ColumnBase::Display_ClothingType); - const RefIdColumn *clothingType = &mColumns.back(); + const RefIdColumn* clothingType = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_WeightCapacity, ColumnBase::Display_Float); - const RefIdColumn *weightCapacity = &mColumns.back(); + const RefIdColumn* weightCapacity = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_OrganicContainer, ColumnBase::Display_Boolean); - const RefIdColumn *organic = &mColumns.back(); + const RefIdColumn* organic = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Respawn, ColumnBase::Display_Boolean); - const RefIdColumn *respawn = &mColumns.back(); + const RefIdColumn* respawn = &mColumns.back(); // Nested table - mColumns.emplace_back(Columns::ColumnId_ContainerContent, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); - const RefIdColumn *content = &mColumns.back(); + mColumns.emplace_back( + Columns::ColumnId_ContainerContent, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + const RefIdColumn* content = &mColumns.back(); std::map contMap; - contMap.insert(std::make_pair(UniversalId::Type_Container, - new NestedInventoryRefIdAdapter (UniversalId::Type_Container))); + contMap.insert(std::make_pair( + UniversalId::Type_Container, new NestedInventoryRefIdAdapter(UniversalId::Type_Container))); mNestedAdapters.emplace_back(&mColumns.back(), contMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_Referenceable)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_ItemCount, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn(Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_Referenceable)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_ItemCount, CSMWorld::ColumnBase::Display_Integer)); - CreatureColumns creatureColumns (actorsColumns); + CreatureColumns creatureColumns(actorsColumns); mColumns.emplace_back(Columns::ColumnId_CreatureType, ColumnBase::Display_CreatureType); creatureColumns.mType = &mColumns.back(); @@ -338,98 +312,82 @@ CSMWorld::RefIdCollection::RefIdCollection() { int mName; unsigned int mFlag; - } sCreatureFlagTable[] = - { - { Columns::ColumnId_Biped, ESM::Creature::Bipedal }, - { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon }, - { Columns::ColumnId_Swims, ESM::Creature::Swims }, - { Columns::ColumnId_Flies, ESM::Creature::Flies }, - { Columns::ColumnId_Walks, ESM::Creature::Walks }, - { Columns::ColumnId_Essential, ESM::Creature::Essential }, - { -1, 0 } - }; + } sCreatureFlagTable[] = { { Columns::ColumnId_Biped, ESM::Creature::Bipedal }, + { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon }, { Columns::ColumnId_Swims, ESM::Creature::Swims }, + { Columns::ColumnId_Flies, ESM::Creature::Flies }, { Columns::ColumnId_Walks, ESM::Creature::Walks }, + { Columns::ColumnId_Essential, ESM::Creature::Essential }, { -1, 0 } }; // for re-use in NPC records - const RefIdColumn *essential = nullptr; + const RefIdColumn* essential = nullptr; - for (int i=0; sCreatureFlagTable[i].mName!=-1; ++i) + for (int i = 0; sCreatureFlagTable[i].mName != -1; ++i) { mColumns.emplace_back(sCreatureFlagTable[i].mName, ColumnBase::Display_Boolean); - creatureColumns.mFlags.insert (std::make_pair (&mColumns.back(), sCreatureFlagTable[i].mFlag)); + creatureColumns.mFlags.insert(std::make_pair(&mColumns.back(), sCreatureFlagTable[i].mFlag)); switch (sCreatureFlagTable[i].mFlag) { - case ESM::Creature::Essential: essential = &mColumns.back(); break; + case ESM::Creature::Essential: + essential = &mColumns.back(); + break; } } mColumns.emplace_back(Columns::ColumnId_BloodType, ColumnBase::Display_BloodType); // For re-use in NPC records. - const RefIdColumn *bloodType = &mColumns.back(); + const RefIdColumn* bloodType = &mColumns.back(); creatureColumns.mBloodType = bloodType; - creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn)); + creatureColumns.mFlags.insert(std::make_pair(respawn, ESM::Creature::Respawn)); // Nested table - mColumns.emplace_back(Columns::ColumnId_CreatureAttributes, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + mColumns.emplace_back( + Columns::ColumnId_CreatureAttributes, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); creatureColumns.mAttributes = &mColumns.back(); std::map creaAttrMap; creaAttrMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttributesRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), creaAttrMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AttributeValue, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn(Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AttributeValue, CSMWorld::ColumnBase::Display_Integer)); // Nested table - mColumns.emplace_back(Columns::ColumnId_CreatureAttack, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + mColumns.emplace_back( + Columns::ColumnId_CreatureAttack, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); creatureColumns.mAttacks = &mColumns.back(); std::map attackMap; attackMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttackRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), attackMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_CreatureAttack, CSMWorld::ColumnBase::Display_Integer, false, false)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_MinAttack, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_MaxAttack, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn(Columns::ColumnId_CreatureAttack, CSMWorld::ColumnBase::Display_Integer, false, false)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_MinAttack, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_MaxAttack, CSMWorld::ColumnBase::Display_Integer)); // Nested list - mColumns.emplace_back(Columns::ColumnId_CreatureMisc, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); + mColumns.emplace_back(Columns::ColumnId_CreatureMisc, ColumnBase::Display_NestedHeader, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); creatureColumns.mMisc = &mColumns.back(); std::map creaMiscMap; creaMiscMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureMiscRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), creaMiscMap); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, - ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_SoulPoints, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_CombatState, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_MagicState, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_StealthState, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_SoulPoints, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_CombatState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_MagicState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_StealthState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); mColumns.emplace_back(Columns::ColumnId_OpenSound, ColumnBase::Display_Sound); - const RefIdColumn *openSound = &mColumns.back(); + const RefIdColumn* openSound = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_CloseSound, ColumnBase::Display_Sound); - const RefIdColumn *closeSound = &mColumns.back(); + const RefIdColumn* closeSound = &mColumns.back(); - LightColumns lightColumns (inventoryColumns); + LightColumns lightColumns(inventoryColumns); mColumns.emplace_back(Columns::ColumnId_Duration, ColumnBase::Display_Integer); lightColumns.mTime = &mColumns.back(); @@ -450,26 +408,21 @@ CSMWorld::RefIdCollection::RefIdCollection() { int mName; unsigned int mFlag; - } sLightFlagTable[] = - { - { Columns::ColumnId_Dynamic, ESM::Light::Dynamic }, - { Columns::ColumnId_Portable, ESM::Light::Carry }, - { Columns::ColumnId_NegativeLight, ESM::Light::Negative }, - { Columns::ColumnId_Fire, ESM::Light::Fire }, - { Columns::ColumnId_OffByDefault, ESM::Light::OffDefault }, - { -1, 0 } - }; + } sLightFlagTable[] + = { { Columns::ColumnId_Dynamic, ESM::Light::Dynamic }, { Columns::ColumnId_Portable, ESM::Light::Carry }, + { Columns::ColumnId_NegativeLight, ESM::Light::Negative }, { Columns::ColumnId_Fire, ESM::Light::Fire }, + { Columns::ColumnId_OffByDefault, ESM::Light::OffDefault }, { -1, 0 } }; - for (int i=0; sLightFlagTable[i].mName!=-1; ++i) + for (int i = 0; sLightFlagTable[i].mName != -1; ++i) { mColumns.emplace_back(sLightFlagTable[i].mName, ColumnBase::Display_Boolean); - lightColumns.mFlags.insert (std::make_pair (&mColumns.back(), sLightFlagTable[i].mFlag)); + lightColumns.mFlags.insert(std::make_pair(&mColumns.back(), sLightFlagTable[i].mFlag)); } mColumns.emplace_back(Columns::ColumnId_IsKey, ColumnBase::Display_Boolean); - const RefIdColumn *key = &mColumns.back(); + const RefIdColumn* key = &mColumns.back(); - NpcColumns npcColumns (actorsColumns); + NpcColumns npcColumns(actorsColumns); mColumns.emplace_back(Columns::ColumnId_Race, ColumnBase::Display_Race); npcColumns.mRace = &mColumns.back(); @@ -477,6 +430,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.emplace_back(Columns::ColumnId_Class, ColumnBase::Display_Class); npcColumns.mClass = &mColumns.back(); + // NAME32 enforced in IdCompletionDelegate::createEditor() mColumns.emplace_back(Columns::ColumnId_Faction, ColumnBase::Display_Faction); npcColumns.mFaction = &mColumns.back(); @@ -489,11 +443,11 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.emplace_back(Columns::ColumnId_Gender, ColumnBase::Display_GenderNpc); npcColumns.mGender = &mColumns.back(); - npcColumns.mFlags.insert (std::make_pair (essential, ESM::NPC::Essential)); + npcColumns.mFlags.insert(std::make_pair(essential, ESM::NPC::Essential)); - npcColumns.mFlags.insert (std::make_pair (respawn, ESM::NPC::Respawn)); + npcColumns.mFlags.insert(std::make_pair(respawn, ESM::NPC::Respawn)); - npcColumns.mFlags.insert (std::make_pair (autoCalc, ESM::NPC::Autocalc)); + npcColumns.mFlags.insert(std::make_pair(autoCalc, ESM::NPC::Autocalc)); // Re-used from Creature records. npcColumns.mBloodType = bloodType; @@ -503,56 +457,47 @@ CSMWorld::RefIdCollection::RefIdCollection() // These needs to be driven from the autocalculated setting. // Nested table - mColumns.emplace_back(Columns::ColumnId_NpcAttributes, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + mColumns.emplace_back(Columns::ColumnId_NpcAttributes, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); npcColumns.mAttributes = &mColumns.back(); std::map attrMap; attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), attrMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_UnsignedInteger8)); + new RefIdColumn(Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_UnsignedInteger8)); // Nested table - mColumns.emplace_back(Columns::ColumnId_NpcSkills, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + mColumns.emplace_back(Columns::ColumnId_NpcSkills, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); npcColumns.mSkills = &mColumns.back(); std::map skillsMap; skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), skillsMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Skill, CSMWorld::ColumnBase::Display_SkillId, false, false)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_UnsignedInteger8)); + new RefIdColumn(Columns::ColumnId_Skill, CSMWorld::ColumnBase::Display_SkillId, false, false)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_UnsignedInteger8)); // Nested list - mColumns.emplace_back(Columns::ColumnId_NpcMisc, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); + mColumns.emplace_back(Columns::ColumnId_NpcMisc, ColumnBase::Display_NestedHeader, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); npcColumns.mMisc = &mColumns.back(); std::map miscMap; miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter())); mNestedAdapters.emplace_back(&mColumns.back(), miscMap); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_SignedInteger16)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_SignedInteger16)); + new RefIdColumn(Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_UnsignedInteger16)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_UnsignedInteger16)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_UnsignedInteger16)); + new RefIdColumn(Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_UnsignedInteger16)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_UnsignedInteger16)); + new RefIdColumn(Columns::ColumnId_NpcDisposition, CSMWorld::ColumnBase::Display_UnsignedInteger8)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_UnsignedInteger16)); + new RefIdColumn(Columns::ColumnId_NpcReputation, CSMWorld::ColumnBase::Display_UnsignedInteger8)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcDisposition, CSMWorld::ColumnBase::Display_UnsignedInteger8)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcReputation, CSMWorld::ColumnBase::Display_UnsignedInteger8)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcRank, CSMWorld::ColumnBase::Display_UnsignedInteger8)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcPersistence, CSMWorld::ColumnBase::Display_Boolean)); + new RefIdColumn(Columns::ColumnId_NpcRank, CSMWorld::ColumnBase::Display_UnsignedInteger8)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); - WeaponColumns weaponColumns (enchantableColumns); + WeaponColumns weaponColumns(enchantableColumns); mColumns.emplace_back(Columns::ColumnId_WeaponType, ColumnBase::Display_WeaponType); weaponColumns.mType = &mColumns.back(); @@ -565,14 +510,14 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.emplace_back(Columns::ColumnId_WeaponReach, ColumnBase::Display_Float); weaponColumns.mReach = &mColumns.back(); - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) { - const RefIdColumn **column = - i==0 ? weaponColumns.mChop : (i==1 ? weaponColumns.mSlash : weaponColumns.mThrust); + const RefIdColumn** column + = i == 0 ? weaponColumns.mChop : (i == 1 ? weaponColumns.mSlash : weaponColumns.mThrust); - for (int j=0; j<2; ++j) + for (int j = 0; j < 2; ++j) { - mColumns.emplace_back(Columns::ColumnId_MinChop+i*2+j, ColumnBase::Display_Integer); + mColumns.emplace_back(Columns::ColumnId_MinChop + i * 2 + j, ColumnBase::Display_Integer); column[j] = &mColumns.back(); } } @@ -581,123 +526,119 @@ CSMWorld::RefIdCollection::RefIdCollection() { int mName; unsigned int mFlag; - } sWeaponFlagTable[] = - { - { Columns::ColumnId_Magical, ESM::Weapon::Magical }, - { Columns::ColumnId_Silver, ESM::Weapon::Silver }, - { -1, 0 } - }; + } sWeaponFlagTable[] = { { Columns::ColumnId_Magical, ESM::Weapon::Magical }, + { Columns::ColumnId_Silver, ESM::Weapon::Silver }, { -1, 0 } }; - for (int i=0; sWeaponFlagTable[i].mName!=-1; ++i) + for (int i = 0; sWeaponFlagTable[i].mName != -1; ++i) { mColumns.emplace_back(sWeaponFlagTable[i].mName, ColumnBase::Display_Boolean); - weaponColumns.mFlags.insert (std::make_pair (&mColumns.back(), sWeaponFlagTable[i].mFlag)); + weaponColumns.mFlags.insert(std::make_pair(&mColumns.back(), sWeaponFlagTable[i].mFlag)); } // Nested table mColumns.emplace_back(Columns::ColumnId_PartRefList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); - const RefIdColumn *partRef = &mColumns.back(); + const RefIdColumn* partRef = &mColumns.back(); std::map partMap; - partMap.insert(std::make_pair(UniversalId::Type_Armor, - new BodyPartRefIdAdapter (UniversalId::Type_Armor))); - partMap.insert(std::make_pair(UniversalId::Type_Clothing, - new BodyPartRefIdAdapter (UniversalId::Type_Clothing))); + partMap.insert( + std::make_pair(UniversalId::Type_Armor, new BodyPartRefIdAdapter(UniversalId::Type_Armor))); + partMap.insert(std::make_pair( + UniversalId::Type_Clothing, new BodyPartRefIdAdapter(UniversalId::Type_Clothing))); mNestedAdapters.emplace_back(&mColumns.back(), partMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PartRefType, CSMWorld::ColumnBase::Display_PartRefType)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PartRefMale, CSMWorld::ColumnBase::Display_BodyPart)); - mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PartRefFemale, CSMWorld::ColumnBase::Display_BodyPart)); + new RefIdColumn(Columns::ColumnId_PartRefType, CSMWorld::ColumnBase::Display_PartRefType)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_PartRefMale, CSMWorld::ColumnBase::Display_BodyPart)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_PartRefFemale, CSMWorld::ColumnBase::Display_BodyPart)); - LevListColumns levListColumns (baseColumns); + LevListColumns creatureLevListColumns(baseColumns); + LevListColumns itemLevListColumns(baseColumns); + std::map creatureLevListMap, itemLevListMap; + creatureLevListMap.insert(std::make_pair(UniversalId::Type_CreatureLevelledList, + new NestedLevListRefIdAdapter(UniversalId::Type_CreatureLevelledList))); + itemLevListMap.insert(std::make_pair(UniversalId::Type_ItemLevelledList, + new NestedLevListRefIdAdapter(UniversalId::Type_ItemLevelledList))); - // Nested table - mColumns.emplace_back(Columns::ColumnId_LevelledList, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); - levListColumns.mLevList = &mColumns.back(); - std::map levListMap; - levListMap.insert(std::make_pair(UniversalId::Type_CreatureLevelledList, - new NestedLevListRefIdAdapter (UniversalId::Type_CreatureLevelledList))); - levListMap.insert(std::make_pair(UniversalId::Type_ItemLevelledList, - new NestedLevListRefIdAdapter (UniversalId::Type_ItemLevelledList))); - mNestedAdapters.emplace_back(&mColumns.back(), levListMap); + // Levelled creature nested table + mColumns.emplace_back(Columns::ColumnId_LevelledList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + creatureLevListColumns.mLevList = &mColumns.back(); + mNestedAdapters.emplace_back(&mColumns.back(), creatureLevListMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_LevelledItemId, CSMWorld::ColumnBase::Display_Referenceable)); + new RefIdColumn(Columns::ColumnId_LevelledCreatureId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_LevelledItemLevel, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn(Columns::ColumnId_LevelledItemLevel, CSMWorld::ColumnBase::Display_Integer)); - // Nested list - mColumns.emplace_back(Columns::ColumnId_LevelledList, - ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); - levListColumns.mNestedListLevList = &mColumns.back(); + // Levelled item nested table + mColumns.emplace_back(Columns::ColumnId_LevelledList, ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue); + itemLevListColumns.mLevList = &mColumns.back(); + mNestedAdapters.emplace_back(&mColumns.back(), itemLevListMap); + mColumns.back().addColumn( + new RefIdColumn(Columns::ColumnId_LevelledItemId, CSMWorld::ColumnBase::Display_Referenceable)); + mColumns.back().addColumn( + new RefIdColumn(Columns::ColumnId_LevelledItemLevel, CSMWorld::ColumnBase::Display_Integer)); + + // Shared levelled list nested list + mColumns.emplace_back(Columns::ColumnId_LevelledList, ColumnBase::Display_NestedHeader, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List); + creatureLevListColumns.mNestedListLevList = &mColumns.back(); + itemLevListColumns.mNestedListLevList = &mColumns.back(); std::map nestedListLevListMap; nestedListLevListMap.insert(std::make_pair(UniversalId::Type_CreatureLevelledList, - new NestedListLevListRefIdAdapter (UniversalId::Type_CreatureLevelledList))); + new NestedListLevListRefIdAdapter(UniversalId::Type_CreatureLevelledList))); nestedListLevListMap.insert(std::make_pair(UniversalId::Type_ItemLevelledList, - new NestedListLevListRefIdAdapter (UniversalId::Type_ItemLevelledList))); + new NestedListLevListRefIdAdapter(UniversalId::Type_ItemLevelledList))); mNestedAdapters.emplace_back(&mColumns.back(), nestedListLevListMap); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_LevelledItemTypeEach, CSMWorld::ColumnBase::Display_Boolean)); + new RefIdColumn(Columns::ColumnId_LevelledItemTypeEach, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_LevelledItemType, CSMWorld::ColumnBase::Display_Boolean)); + new RefIdColumn(Columns::ColumnId_LevelledItemType, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_LevelledItemChanceNone, CSMWorld::ColumnBase::Display_UnsignedInteger8)); + new RefIdColumn(Columns::ColumnId_LevelledItemChanceNone, CSMWorld::ColumnBase::Display_UnsignedInteger8)); - mAdapters.insert (std::make_pair (UniversalId::Type_Activator, - new NameRefIdAdapter (UniversalId::Type_Activator, nameColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Potion, - new PotionRefIdAdapter (potionColumns, autoCalc))); - mAdapters.insert (std::make_pair (UniversalId::Type_Apparatus, - new ApparatusRefIdAdapter (inventoryColumns, apparatusType, toolsColumns.mQuality))); - mAdapters.insert (std::make_pair (UniversalId::Type_Armor, - new ArmorRefIdAdapter (enchantableColumns, armorType, health, armor, partRef))); - mAdapters.insert (std::make_pair (UniversalId::Type_Book, - new BookRefIdAdapter (enchantableColumns, bookType, skill, text))); - mAdapters.insert (std::make_pair (UniversalId::Type_Clothing, - new ClothingRefIdAdapter (enchantableColumns, clothingType, partRef))); - mAdapters.insert (std::make_pair (UniversalId::Type_Container, - new ContainerRefIdAdapter (nameColumns, weightCapacity, organic, respawn, content))); - mAdapters.insert (std::make_pair (UniversalId::Type_Creature, - new CreatureRefIdAdapter (creatureColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Door, - new DoorRefIdAdapter (nameColumns, openSound, closeSound))); - mAdapters.insert (std::make_pair (UniversalId::Type_Ingredient, - new IngredientRefIdAdapter (ingredientColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_CreatureLevelledList, - new LevelledListRefIdAdapter ( - UniversalId::Type_CreatureLevelledList, levListColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_ItemLevelledList, - new LevelledListRefIdAdapter (UniversalId::Type_ItemLevelledList, levListColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Light, - new LightRefIdAdapter (lightColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Lockpick, - new ToolRefIdAdapter (UniversalId::Type_Lockpick, toolsColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, - new MiscRefIdAdapter (inventoryColumns, key))); - mAdapters.insert (std::make_pair (UniversalId::Type_Npc, - new NpcRefIdAdapter (npcColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Probe, - new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Repair, - new ToolRefIdAdapter (UniversalId::Type_Repair, toolsColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Static, - new ModelRefIdAdapter (UniversalId::Type_Static, modelColumns))); - mAdapters.insert (std::make_pair (UniversalId::Type_Weapon, - new WeaponRefIdAdapter (weaponColumns))); + mAdapters.insert(std::make_pair( + UniversalId::Type_Activator, new NameRefIdAdapter(UniversalId::Type_Activator, nameColumns))); + mAdapters.insert(std::make_pair(UniversalId::Type_Potion, new PotionRefIdAdapter(potionColumns, autoCalc))); + mAdapters.insert(std::make_pair(UniversalId::Type_Apparatus, + new ApparatusRefIdAdapter(inventoryColumns, apparatusType, toolsColumns.mQuality))); + mAdapters.insert(std::make_pair( + UniversalId::Type_Armor, new ArmorRefIdAdapter(enchantableColumns, armorType, health, armor, partRef))); + mAdapters.insert( + std::make_pair(UniversalId::Type_Book, new BookRefIdAdapter(enchantableColumns, bookType, skill, text))); + mAdapters.insert(std::make_pair( + UniversalId::Type_Clothing, new ClothingRefIdAdapter(enchantableColumns, clothingType, partRef))); + mAdapters.insert(std::make_pair(UniversalId::Type_Container, + new ContainerRefIdAdapter(nameColumns, weightCapacity, organic, respawn, content))); + mAdapters.insert(std::make_pair(UniversalId::Type_Creature, new CreatureRefIdAdapter(creatureColumns))); + mAdapters.insert(std::make_pair(UniversalId::Type_Door, new DoorRefIdAdapter(nameColumns, openSound, closeSound))); + mAdapters.insert(std::make_pair(UniversalId::Type_Ingredient, new IngredientRefIdAdapter(ingredientColumns))); + mAdapters.insert(std::make_pair(UniversalId::Type_CreatureLevelledList, + new LevelledListRefIdAdapter( + UniversalId::Type_CreatureLevelledList, creatureLevListColumns))); + mAdapters.insert(std::make_pair(UniversalId::Type_ItemLevelledList, + new LevelledListRefIdAdapter(UniversalId::Type_ItemLevelledList, itemLevListColumns))); + mAdapters.insert(std::make_pair(UniversalId::Type_Light, new LightRefIdAdapter(lightColumns))); + mAdapters.insert(std::make_pair( + UniversalId::Type_Lockpick, new ToolRefIdAdapter(UniversalId::Type_Lockpick, toolsColumns))); + mAdapters.insert(std::make_pair(UniversalId::Type_Miscellaneous, new MiscRefIdAdapter(inventoryColumns, key))); + mAdapters.insert(std::make_pair(UniversalId::Type_Npc, new NpcRefIdAdapter(npcColumns))); + mAdapters.insert(std::make_pair( + UniversalId::Type_Probe, new ToolRefIdAdapter(UniversalId::Type_Probe, toolsColumns))); + mAdapters.insert(std::make_pair( + UniversalId::Type_Repair, new ToolRefIdAdapter(UniversalId::Type_Repair, toolsColumns))); + mAdapters.insert(std::make_pair( + UniversalId::Type_Static, new ModelRefIdAdapter(UniversalId::Type_Static, modelColumns))); + mAdapters.insert(std::make_pair(UniversalId::Type_Weapon, new WeaponRefIdAdapter(weaponColumns))); } CSMWorld::RefIdCollection::~RefIdCollection() { - for (std::map::iterator iter (mAdapters.begin()); - iter!=mAdapters.end(); ++iter) - delete iter->second; + for (std::map::iterator iter(mAdapters.begin()); iter != mAdapters.end(); ++iter) + delete iter->second; - for (std::vector > >::iterator iter (mNestedAdapters.begin()); - iter!=mNestedAdapters.end(); ++iter) + for (std::vector>>::iterator iter( + mNestedAdapters.begin()); + iter != mNestedAdapters.end(); ++iter) { - for (std::map::iterator it ((iter->second).begin()); - it!=(iter->second).end(); ++it) + for (std::map::iterator it((iter->second).begin()); + it != (iter->second).end(); ++it) delete it->second; } } @@ -707,17 +648,17 @@ int CSMWorld::RefIdCollection::getSize() const return mData.getSize(); } -std::string CSMWorld::RefIdCollection::getId (int index) const +ESM::RefId CSMWorld::RefIdCollection::getId(int index) const { - return getData (index, 0).toString().toUtf8().constData(); + return ESM::RefId::stringRefId(getData(index, 0).toString().toUtf8().constData()); } -int CSMWorld::RefIdCollection::getIndex (const std::string& id) const +int CSMWorld::RefIdCollection::getIndex(const ESM::RefId& id) const { - int index = searchId (id); + int index = searchId(id); - if (index==-1) - throw std::runtime_error ("invalid ID: " + id); + if (index == -1) + throw std::runtime_error("ID is not found in RefId collection: " + id.toDebugString()); return index; } @@ -727,140 +668,136 @@ int CSMWorld::RefIdCollection::getColumns() const return mColumns.size(); } -const CSMWorld::ColumnBase& CSMWorld::RefIdCollection::getColumn (int column) const +const CSMWorld::ColumnBase& CSMWorld::RefIdCollection::getColumn(int column) const { - return mColumns.at (column); + return mColumns.at(column); } -QVariant CSMWorld::RefIdCollection::getData (int index, int column) const +QVariant CSMWorld::RefIdCollection::getData(int index, int column) const { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(index); - const RefIdAdapter& adaptor = findAdapter (localIndex.second); + const RefIdAdapter& adaptor = findAdapter(localIndex.second); - return adaptor.getData (&mColumns.at (column), mData, localIndex.first); + return adaptor.getData(&mColumns.at(column), mData, localIndex.first); } -QVariant CSMWorld::RefIdCollection::getNestedData (int row, int column, int subRow, int subColumn) const +QVariant CSMWorld::RefIdCollection::getNestedData(int row, int column, int subRow, int subColumn) const { RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); - return nestedAdapter.getNestedData(&mColumns.at (column), mData, localIndex.first, subRow, subColumn); + return nestedAdapter.getNestedData(&mColumns.at(column), mData, localIndex.first, subRow, subColumn); } -void CSMWorld::RefIdCollection::setData (int index, int column, const QVariant& data) +void CSMWorld::RefIdCollection::setData(int index, int column, const QVariant& data) { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(index); - const RefIdAdapter& adaptor = findAdapter (localIndex.second); + const RefIdAdapter& adaptor = findAdapter(localIndex.second); - adaptor.setData (&mColumns.at (column), mData, localIndex.first, data); + adaptor.setData(&mColumns.at(column), mData, localIndex.first, data); } void CSMWorld::RefIdCollection::setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); - nestedAdapter.setNestedData(&mColumns.at (column), mData, localIndex.first, data, subRow, subColumn); - return; + nestedAdapter.setNestedData(&mColumns.at(column), mData, localIndex.first, data, subRow, subColumn); } -void CSMWorld::RefIdCollection::removeRows (int index, int count) +void CSMWorld::RefIdCollection::removeRows(int index, int count) { - mData.erase (index, count); + mData.erase(index, count); } void CSMWorld::RefIdCollection::removeNestedRows(int row, int column, int subRow) { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); - nestedAdapter.removeNestedRow(&mColumns.at (column), mData, localIndex.first, subRow); - return; + nestedAdapter.removeNestedRow(&mColumns.at(column), mData, localIndex.first, subRow); } -void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) +void CSMWorld::RefIdCollection::appendBlankRecord(const ESM::RefId& id, UniversalId::Type type) { - mData.appendRecord (type, id, false); + mData.appendRecord(type, id, false); } -int CSMWorld::RefIdCollection::searchId (const std::string& id) const +int CSMWorld::RefIdCollection::searchId(const ESM::RefId& id) const { - RefIdData::LocalIndex localIndex = mData.searchId (id); + const RefIdData::LocalIndex localIndex = mData.searchId(id); - if (localIndex.first==-1) + if (localIndex.first == -1) return -1; - return mData.localToGlobalIndex (localIndex); + return mData.localToGlobalIndex(localIndex); } -void CSMWorld::RefIdCollection::replace (int index, const RecordBase& record) +void CSMWorld::RefIdCollection::replace(int index, std::unique_ptr record) { - mData.getRecord (mData.globalToLocalIndex (index)).assign (record); + mData.getRecord(mData.globalToLocalIndex(index)).assign(*record.release()); } -void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin, - const std::string& destination, - const CSMWorld::UniversalId::Type type) +void CSMWorld::RefIdCollection::cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, const CSMWorld::UniversalId::Type type) { - std::unique_ptr newRecord(mData.getRecord(mData.searchId(origin)).modifiedCopy()); - mAdapters.find(type)->second->setId(*newRecord, destination); - mData.insertRecord(*newRecord, type, destination); + std::unique_ptr newRecord = mData.getRecord(mData.searchId(origin)).modifiedCopy(); + mAdapters.find(type)->second->setId(*newRecord, destination.getRefIdString()); + mData.insertRecord(std::move(newRecord), type, destination); } -bool CSMWorld::RefIdCollection::touchRecord(const std::string& id) +bool CSMWorld::RefIdCollection::touchRecord(const ESM::RefId& id) { throw std::runtime_error("RefIdCollection::touchRecord is unimplemented"); return false; } -void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record, - UniversalId::Type type) +void CSMWorld::RefIdCollection::appendRecord(std::unique_ptr record, UniversalId::Type type) { - std::string id = findAdapter (type).getId (record); + auto id = findAdapter(type).getId(*record.get()); - int index = mData.getAppendIndex (type); + int index = mData.getAppendIndex(type); - mData.appendRecord (type, id, false); + mData.appendRecord(type, id, false); - mData.getRecord (mData.globalToLocalIndex (index)).assign (record); + mData.getRecord(mData.globalToLocalIndex(index)).assign(*record.release()); } -const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (const std::string& id) const +const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord(const ESM::RefId& id) const { - return mData.getRecord (mData.searchId (id)); + return mData.getRecord(mData.searchId(id)); } -const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (int index) const +const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord(int index) const { - return mData.getRecord (mData.globalToLocalIndex (index)); + return mData.getRecord(mData.globalToLocalIndex(index)); } -void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, UniversalId::Type type) +void CSMWorld::RefIdCollection::load(ESM::ESMReader& reader, bool base, UniversalId::Type type) { mData.load(reader, base, type); } -int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const +int CSMWorld::RefIdCollection::getAppendIndex(const ESM::RefId& id, UniversalId::Type type) const { - return mData.getAppendIndex (type); + return mData.getAppendIndex(type); } -std::vector CSMWorld::RefIdCollection::getIds (bool listDeleted) const +std::vector CSMWorld::RefIdCollection::getIds(bool listDeleted) const { - return mData.getIds (listDeleted); + return mData.getIds(listDeleted); } -bool CSMWorld::RefIdCollection::reorderRows (int baseIndex, const std::vector& newOrder) +bool CSMWorld::RefIdCollection::reorderRows(int baseIndex, const std::vector& newOrder) { return false; } -void CSMWorld::RefIdCollection::save (int index, ESM::ESMWriter& writer) const +void CSMWorld::RefIdCollection::save(int index, ESM::ESMWriter& writer) const { - mData.save (index, writer); + mData.save(index, writer); } const CSMWorld::RefIdData& CSMWorld::RefIdCollection::getDataSet() const @@ -870,7 +807,7 @@ const CSMWorld::RefIdData& CSMWorld::RefIdCollection::getDataSet() const int CSMWorld::RefIdCollection::getNestedRowsCount(int row, int column) const { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); return nestedAdapter.getNestedRowsCount(&mColumns.at(column), mData, localIndex.first); @@ -878,52 +815,51 @@ int CSMWorld::RefIdCollection::getNestedRowsCount(int row, int column) const int CSMWorld::RefIdCollection::getNestedColumnsCount(int row, int column) const { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); return nestedAdapter.getNestedColumnsCount(&mColumns.at(column), mData); } -CSMWorld::NestableColumn *CSMWorld::RefIdCollection::getNestableColumn(int column) +CSMWorld::NestableColumn* CSMWorld::RefIdCollection::getNestableColumn(int column) { return &mColumns.at(column); } void CSMWorld::RefIdCollection::addNestedRow(int row, int col, int position) { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(col), localIndex.second); nestedAdapter.addNestedRow(&mColumns.at(col), mData, localIndex.first, position); - return; } void CSMWorld::RefIdCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); nestedAdapter.setNestedTable(&mColumns.at(column), mData, localIndex.first, nestedTable); - return; } CSMWorld::NestedTableWrapperBase* CSMWorld::RefIdCollection::nestedTable(int row, int column) const { - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (row); + RefIdData::LocalIndex localIndex = mData.globalToLocalIndex(row); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); return nestedAdapter.nestedTable(&mColumns.at(column), mData, localIndex.first); } -const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdapter(const CSMWorld::ColumnBase &column, UniversalId::Type type) const +const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdapter( + const CSMWorld::ColumnBase& column, UniversalId::Type type) const { - for (std::vector > >::const_iterator iter (mNestedAdapters.begin()); - iter!=mNestedAdapters.end(); ++iter) + for (std::vector>>::const_iterator + iter(mNestedAdapters.begin()); + iter != mNestedAdapters.end(); ++iter) { if ((iter->first) == &column) { - std::map::const_iterator it = - (iter->second).find(type); + std::map::const_iterator it = (iter->second).find(type); if (it == (iter->second).end()) throw std::runtime_error("No such type in the nestedadapters"); @@ -934,7 +870,7 @@ const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdap throw std::runtime_error("No such column in the nestedadapters"); } -void CSMWorld::RefIdCollection::copyTo (int index, RefIdCollection& target) const +void CSMWorld::RefIdCollection::copyTo(int index, RefIdCollection& target) const { - mData.copyTo (index, target.mData); + mData.copyTo(index, target.mData); } diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index e85263ac1..10892535c 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -1,146 +1,150 @@ #ifndef CSM_WOLRD_REFIDCOLLECTION_H #define CSM_WOLRD_REFIDCOLLECTION_H -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include -#include "columnbase.hpp" #include "collectionbase.hpp" +#include "columnbase.hpp" #include "nestedcollection.hpp" #include "refiddata.hpp" namespace ESM { + class ESMReader; class ESMWriter; } namespace CSMWorld { class RefIdAdapter; + struct RecordBase; struct NestedTableWrapperBase; class NestedRefIdAdapterBase; class RefIdColumn : public NestableColumn { - bool mEditable; - bool mUserEditable; + bool mEditable; + bool mUserEditable; - public: + public: + RefIdColumn(int columnId, Display displayType, int flag = Flag_Table | Flag_Dialogue, bool editable = true, + bool userEditable = true); - RefIdColumn (int columnId, Display displayType, - int flag = Flag_Table | Flag_Dialogue, bool editable = true, - bool userEditable = true); + bool isEditable() const override; - bool isEditable() const override; - - bool isUserEditable() const override; + bool isUserEditable() const override; }; class RefIdCollection : public CollectionBase, public NestedCollection { - private: + private: + RefIdData mData; + std::deque mColumns; + std::map mAdapters; - RefIdData mData; - std::deque mColumns; - std::map mAdapters; + std::vector>> mNestedAdapters; - std::vector > > mNestedAdapters; + private: + const RefIdAdapter& findAdapter(UniversalId::Type) const; + ///< Throws an exception if no adaptor for \a Type can be found. - private: + const NestedRefIdAdapterBase& getNestedAdapter(const ColumnBase& column, UniversalId::Type type) const; - const RefIdAdapter& findAdapter (UniversalId::Type) const; - ///< Throws an exception if no adaptor for \a Type can be found. + public: + RefIdCollection(); - const NestedRefIdAdapterBase& getNestedAdapter(const ColumnBase &column, UniversalId::Type type) const; + ~RefIdCollection() override; - public: + int getSize() const override; - RefIdCollection(); + ESM::RefId getId(int index) const override; - virtual ~RefIdCollection(); + int getIndex(const ESM::RefId& id) const override; - int getSize() const override; + int getColumns() const override; - std::string getId (int index) const override; + const ColumnBase& getColumn(int column) const override; - int getIndex (const std::string& id) const override; + QVariant getData(int index, int column) const override; - int getColumns() const override; + void setData(int index, int column, const QVariant& data) override; - const ColumnBase& getColumn (int column) const override; + void removeRows(int index, int count) override; - QVariant getData (int index, int column) const override; + void cloneRecord( + const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) override; - void setData (int index, int column, const QVariant& data) override; + bool touchRecord(const ESM::RefId& id) override; - void removeRows (int index, int count) override; + void appendBlankRecord(const ESM::RefId& id, UniversalId::Type type) override; + ///< \param type Will be ignored, unless the collection supports multiple record types - void cloneRecord(const std::string& origin, - const std::string& destination, - const UniversalId::Type type) override; + int searchId(const ESM::RefId& id) const override; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) - bool touchRecord(const std::string& id) override; + void replace(int index, std::unique_ptr record) override; + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. - void appendBlankRecord (const std::string& id, UniversalId::Type type) override; - ///< \param type Will be ignored, unless the collection supports multiple record types + void appendRecord(std::unique_ptr record, UniversalId::Type type) override; + ///< If the record type does not match, an exception is thrown. + /// + ///< \param type Will be ignored, unless the collection supports multiple record types - int searchId (const std::string& id) const override; - ////< Search record with \a id. - /// \return index of record (if found) or -1 (not found) + const RecordBase& getRecord(const ESM::RefId& id) const override; - void replace (int index, const RecordBase& record) override; - ///< If the record type does not match, an exception is thrown. - /// - /// \attention \a record must not change the ID. + const RecordBase& getRecord(int index) const override; - void appendRecord (const RecordBase& record, UniversalId::Type type) override; - ///< If the record type does not match, an exception is thrown. - /// - ///< \param type Will be ignored, unless the collection supports multiple record types + void load(ESM::ESMReader& reader, bool base, UniversalId::Type type); - const RecordBase& getRecord (const std::string& id) const override; + int getAppendIndex(const ESM::RefId& id, UniversalId::Type type) const override; + ///< \param type Will be ignored, unless the collection supports multiple record types - const RecordBase& getRecord (int index) const override; + std::vector getIds(bool listDeleted) const override; + ///< Return a sorted collection of all IDs + /// + /// \param listDeleted include deleted record in the list - void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); + bool reorderRows(int baseIndex, const std::vector& newOrder) override; + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? - int getAppendIndex (const std::string& id, UniversalId::Type type) const override; - ///< \param type Will be ignored, unless the collection supports multiple record types + QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; - std::vector getIds (bool listDeleted) const override; - ///< Return a sorted collection of all IDs - /// - /// \param listDeleted include deleted record in the list + NestedTableWrapperBase* nestedTable(int row, int column) const override; - bool reorderRows (int baseIndex, const std::vector& newOrder) override; - ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices - /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). - /// - /// \return Success? + void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; - QVariant getNestedData(int row, int column, int subRow, int subColumn) const override; + int getNestedRowsCount(int row, int column) const override; - NestedTableWrapperBase* nestedTable(int row, int column) const override; + int getNestedColumnsCount(int row, int column) const override; - void setNestedTable(int row, int column, const NestedTableWrapperBase& nestedTable) override; + NestableColumn* getNestableColumn(int column) override; - int getNestedRowsCount(int row, int column) const override; + void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; - int getNestedColumnsCount(int row, int column) const override; + void removeNestedRows(int row, int column, int subRow) override; - NestableColumn *getNestableColumn(int column) override; + void addNestedRow(int row, int col, int position) override; - void setNestedData(int row, int column, const QVariant& data, int subRow, int subColumn) override; + void save(int index, ESM::ESMWriter& writer) const; - void removeNestedRows(int row, int column, int subRow) override; - - void addNestedRow(int row, int col, int position) override; - - void save (int index, ESM::ESMWriter& writer) const; - - const RefIdData& getDataSet() const; //I can't figure out a better name for this one :( - void copyTo (int index, RefIdCollection& target) const; + const RefIdData& getDataSet() const; // I can't figure out a better name for this one :( + void copyTo(int index, RefIdCollection& target) const; }; } diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index e2ffbcca6..f73847962 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -1,183 +1,232 @@ #include "refiddata.hpp" -#include +#include +#include + +#include + #include +#include +#include -CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} - - -std::string CSMWorld::RefIdData::getRecordId(const CSMWorld::RefIdData::LocalIndex &index) const +namespace ESM { - std::map::const_iterator found = - mRecordContainers.find (index.second); + class ESMWriter; +} + +ESM::RefId CSMWorld::RefIdData::getRecordId(const CSMWorld::RefIdData::LocalIndex& index) const +{ + std::map::const_iterator found = mRecordContainers.find(index.second); if (found == mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + throw std::logic_error("invalid local index type"); return found->second->getId(index.first); } CSMWorld::RefIdData::RefIdData() { - mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Potion, &mPotions)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Apparatus, &mApparati)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Armor, &mArmors)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Book, &mBooks)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Clothing, &mClothing)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Container, &mContainers)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Creature, &mCreatures)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Door, &mDoors)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Ingredient, &mIngredients)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_CreatureLevelledList, - &mCreatureLevelledLists)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_ItemLevelledList, &mItemLevelledLists)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Light, &mLights)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Lockpick, &mLockpicks)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Miscellaneous, &mMiscellaneous)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Npc, &mNpcs)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Probe, &mProbes)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Repair, &mRepairs)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Static, &mStatics)); - mRecordContainers.insert (std::make_pair (UniversalId::Type_Weapon, &mWeapons)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Activator, &mActivators)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Potion, &mPotions)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Apparatus, &mApparati)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Armor, &mArmors)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Book, &mBooks)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Clothing, &mClothing)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Container, &mContainers)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Creature, &mCreatures)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Door, &mDoors)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Ingredient, &mIngredients)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_CreatureLevelledList, &mCreatureLevelledLists)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_ItemLevelledList, &mItemLevelledLists)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Light, &mLights)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Lockpick, &mLockpicks)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Miscellaneous, &mMiscellaneous)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Npc, &mNpcs)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Probe, &mProbes)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Repair, &mRepairs)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Static, &mStatics)); + mRecordContainers.insert(std::make_pair(UniversalId::Type_Weapon, &mWeapons)); } -CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::globalToLocalIndex (int index) const +CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::globalToLocalIndex(int index) const { - for (std::map::const_iterator iter ( - mRecordContainers.begin()); iter!=mRecordContainers.end(); ++iter) + for (std::map::const_iterator iter(mRecordContainers.begin()); + iter != mRecordContainers.end(); ++iter) { - if (indexsecond->getSize()) - return LocalIndex (index, iter->first); + if (index < iter->second->getSize()) + return LocalIndex(index, iter->first); index -= iter->second->getSize(); } - throw std::runtime_error ("RefIdData index out of range"); + throw std::runtime_error("RefIdData index out of range"); } -int CSMWorld::RefIdData::localToGlobalIndex (const LocalIndex& index) - const +int CSMWorld::RefIdData::localToGlobalIndex(const LocalIndex& index) const { - std::map::const_iterator end = - mRecordContainers.find (index.second); + std::map::const_iterator end = mRecordContainers.find(index.second); - if (end==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (end == mRecordContainers.end()) + throw std::logic_error("invalid local index type"); int globalIndex = index.first; - for (std::map::const_iterator iter ( - mRecordContainers.begin()); iter!=end; ++iter) + for (std::map::const_iterator iter(mRecordContainers.begin()); + iter != end; ++iter) globalIndex += iter->second->getSize(); return globalIndex; } -CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId ( - const std::string& id) const +CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId(const ESM::RefId& id) const { - std::string id2 = Misc::StringUtils::lowerCase (id); + auto iter = mIndex.find(id); - std::map >::const_iterator iter = mIndex.find (id2); - - if (iter==mIndex.end()) - return std::make_pair (-1, CSMWorld::UniversalId::Type_None); + if (iter == mIndex.end()) + return std::make_pair(-1, CSMWorld::UniversalId::Type_None); return iter->second; } -void CSMWorld::RefIdData::erase (int index, int count) +unsigned int CSMWorld::RefIdData::getRecordFlags(const ESM::RefId& id) const { - LocalIndex localIndex = globalToLocalIndex (index); + LocalIndex localIndex = searchId(id); - std::map::const_iterator iter = - mRecordContainers.find (localIndex.second); + switch (localIndex.second) + { + case UniversalId::Type_Activator: + return mActivators.getRecordFlags(localIndex.first); + case UniversalId::Type_Potion: + return mPotions.getRecordFlags(localIndex.first); + case UniversalId::Type_Apparatus: + return mApparati.getRecordFlags(localIndex.first); + case UniversalId::Type_Armor: + return mArmors.getRecordFlags(localIndex.first); + case UniversalId::Type_Book: + return mBooks.getRecordFlags(localIndex.first); + case UniversalId::Type_Clothing: + return mClothing.getRecordFlags(localIndex.first); + case UniversalId::Type_Container: + return mContainers.getRecordFlags(localIndex.first); + case UniversalId::Type_Creature: + return mCreatures.getRecordFlags(localIndex.first); + case UniversalId::Type_Door: + return mDoors.getRecordFlags(localIndex.first); + case UniversalId::Type_Ingredient: + return mIngredients.getRecordFlags(localIndex.first); + case UniversalId::Type_CreatureLevelledList: + return mCreatureLevelledLists.getRecordFlags(localIndex.first); + case UniversalId::Type_ItemLevelledList: + return mItemLevelledLists.getRecordFlags(localIndex.first); + case UniversalId::Type_Light: + return mLights.getRecordFlags(localIndex.first); + case UniversalId::Type_Lockpick: + return mLockpicks.getRecordFlags(localIndex.first); + case UniversalId::Type_Miscellaneous: + return mMiscellaneous.getRecordFlags(localIndex.first); + case UniversalId::Type_Npc: + return mNpcs.getRecordFlags(localIndex.first); + case UniversalId::Type_Probe: + return mProbes.getRecordFlags(localIndex.first); + case UniversalId::Type_Repair: + return mRepairs.getRecordFlags(localIndex.first); + case UniversalId::Type_Static: + return mStatics.getRecordFlags(localIndex.first); + case UniversalId::Type_Weapon: + return mWeapons.getRecordFlags(localIndex.first); + default: + break; + } - while (count>0 && iter!=mRecordContainers.end()) + return 0; +} + +void CSMWorld::RefIdData::erase(int index, int count) +{ + LocalIndex localIndex = globalToLocalIndex(index); + + std::map::const_iterator iter + = mRecordContainers.find(localIndex.second); + + while (count > 0 && iter != mRecordContainers.end()) { int size = iter->second->getSize(); - if (localIndex.first+count>size) + if (localIndex.first + count > size) { - erase (localIndex, size-localIndex.first); - count -= size-localIndex.first; + erase(localIndex, size - localIndex.first); + count -= size - localIndex.first; ++iter; - if (iter==mRecordContainers.end()) - throw std::runtime_error ("invalid count value for erase operation"); + if (iter == mRecordContainers.end()) + throw std::runtime_error("invalid count value for erase operation"); localIndex.first = 0; localIndex.second = iter->first; } else { - erase (localIndex, count); + erase(localIndex, count); count = 0; } } } -const CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord (const LocalIndex& index) const +const CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord(const LocalIndex& index) const { - std::map::const_iterator iter = - mRecordContainers.find (index.second); + std::map::const_iterator iter = mRecordContainers.find(index.second); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (iter == mRecordContainers.end()) + throw std::logic_error("invalid local index type"); - return iter->second->getRecord (index.first); + return iter->second->getRecord(index.first); } -CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord (const LocalIndex& index) +CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord(const LocalIndex& index) { - std::map::iterator iter = - mRecordContainers.find (index.second); + std::map::iterator iter = mRecordContainers.find(index.second); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (iter == mRecordContainers.end()) + throw std::logic_error("invalid local index type"); - return iter->second->getRecord (index.first); + return iter->second->getRecord(index.first); } -void CSMWorld::RefIdData::appendRecord (UniversalId::Type type, const std::string& id, bool base) +void CSMWorld::RefIdData::appendRecord(UniversalId::Type type, const ESM::RefId& id, bool base) { - std::map::iterator iter = - mRecordContainers.find (type); + std::map::iterator iter = mRecordContainers.find(type); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (iter == mRecordContainers.end()) + throw std::logic_error("invalid local index type"); - iter->second->appendRecord (id, base); + iter->second->appendRecord(id, base); - mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), - LocalIndex (iter->second->getSize()-1, type))); + mIndex.insert(std::make_pair(id, LocalIndex(iter->second->getSize() - 1, type))); } -int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const +int CSMWorld::RefIdData::getAppendIndex(UniversalId::Type type) const { int index = 0; - for (std::map::const_iterator iter ( - mRecordContainers.begin()); iter!=mRecordContainers.end(); ++iter) + for (std::map::const_iterator iter(mRecordContainers.begin()); + iter != mRecordContainers.end(); ++iter) { index += iter->second->getSize(); - if (type==iter->first) + if (type == iter->first) break; } return index; } -void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::UniversalId::Type type) +void CSMWorld::RefIdData::load(ESM::ESMReader& reader, bool base, CSMWorld::UniversalId::Type type) { - std::map::iterator found = - mRecordContainers.find (type); + std::map::iterator found = mRecordContainers.find(type); if (found == mRecordContainers.end()) - throw std::logic_error ("Invalid Referenceable ID type"); + throw std::logic_error("Invalid Referenceable ID type"); int index = found->second->load(reader, base); if (index != -1) @@ -189,25 +238,23 @@ void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::Uni } else { - mIndex[Misc::StringUtils::lowerCase(getRecordId(localIndex))] = localIndex; + mIndex[getRecordId(localIndex)] = localIndex; } } } -void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) +void CSMWorld::RefIdData::erase(const LocalIndex& index, int count) { - std::map::iterator iter = - mRecordContainers.find (index.second); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + std::map::iterator iter = mRecordContainers.find(index.second); + if (iter == mRecordContainers.end()) + throw std::logic_error("invalid local index type"); - for (int i=index.first; i::iterator result = - mIndex.find (Misc::StringUtils::lowerCase (iter->second->getId (i))); + auto result = mIndex.find(iter->second->getId(i)); - if (result!=mIndex.end()) - mIndex.erase (result); + if (result != mIndex.end()) + mIndex.erase(result); } // Adjust the local indexes to avoid gaps between them after removal of records @@ -215,8 +262,7 @@ void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) int recordCount = iter->second->getSize(); while (recordIndex < recordCount) { - std::map::iterator recordIndexFound = - mIndex.find(Misc::StringUtils::lowerCase(iter->second->getId(recordIndex))); + auto recordIndexFound = mIndex.find(iter->second->getId(recordIndex)); if (recordIndexFound != mIndex.end()) { recordIndexFound->second.first -= count; @@ -224,7 +270,7 @@ void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) ++recordIndex; } - iter->second->erase (index.first, count); + iter->second->erase(index.first, count); } int CSMWorld::RefIdData::getSize() const @@ -232,164 +278,159 @@ int CSMWorld::RefIdData::getSize() const return mIndex.size(); } -std::vector CSMWorld::RefIdData::getIds (bool listDeleted) const +std::vector CSMWorld::RefIdData::getIds(bool listDeleted) const { - std::vector ids; + std::vector ids; - for (std::map::const_iterator iter (mIndex.begin()); iter!=mIndex.end(); - ++iter) + for (auto iter(mIndex.begin()); iter != mIndex.end(); ++iter) { - if (listDeleted || !getRecord (iter->second).isDeleted()) + if (listDeleted || !getRecord(iter->second).isDeleted()) { - std::map::const_iterator container = - mRecordContainers.find (iter->second.second); + std::map::const_iterator container + = mRecordContainers.find(iter->second.second); - if (container==mRecordContainers.end()) - throw std::logic_error ("Invalid referenceable ID type"); + if (container == mRecordContainers.end()) + throw std::logic_error("Invalid referenceable ID type"); - ids.push_back (container->second->getId (iter->second.first)); + ids.push_back(container->second->getId(iter->second.first)); } } return ids; } -void CSMWorld::RefIdData::save (int index, ESM::ESMWriter& writer) const +void CSMWorld::RefIdData::save(int index, ESM::ESMWriter& writer) const { - LocalIndex localIndex = globalToLocalIndex (index); + LocalIndex localIndex = globalToLocalIndex(index); - std::map::const_iterator iter = - mRecordContainers.find (localIndex.second); + std::map::const_iterator iter + = mRecordContainers.find(localIndex.second); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (iter == mRecordContainers.end()) + throw std::logic_error("invalid local index type"); - iter->second->save (localIndex.first, writer); + iter->second->save(localIndex.first, writer); } -const CSMWorld::RefIdDataContainer< ESM::Book >& CSMWorld::RefIdData::getBooks() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getBooks() const { return mBooks; } -const CSMWorld::RefIdDataContainer< ESM::Activator >& CSMWorld::RefIdData::getActivators() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getActivators() const { return mActivators; } -const CSMWorld::RefIdDataContainer< ESM::Potion >& CSMWorld::RefIdData::getPotions() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getPotions() const { return mPotions; } -const CSMWorld::RefIdDataContainer< ESM::Apparatus >& CSMWorld::RefIdData::getApparati() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getApparati() const { return mApparati; } -const CSMWorld::RefIdDataContainer< ESM::Armor >& CSMWorld::RefIdData::getArmors() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getArmors() const { return mArmors; } -const CSMWorld::RefIdDataContainer< ESM::Clothing >& CSMWorld::RefIdData::getClothing() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getClothing() const { return mClothing; } -const CSMWorld::RefIdDataContainer< ESM::Container >& CSMWorld::RefIdData::getContainers() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getContainers() const { return mContainers; } -const CSMWorld::RefIdDataContainer< ESM::Creature >& CSMWorld::RefIdData::getCreatures() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getCreatures() const { return mCreatures; } -const CSMWorld::RefIdDataContainer< ESM::Door >& CSMWorld::RefIdData::getDoors() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getDoors() const { return mDoors; } -const CSMWorld::RefIdDataContainer< ESM::Ingredient >& CSMWorld::RefIdData::getIngredients() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getIngredients() const { return mIngredients; } -const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& CSMWorld::RefIdData::getCreatureLevelledLists() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getCreatureLevelledLists() const { return mCreatureLevelledLists; } -const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& CSMWorld::RefIdData::getItemLevelledList() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getItemLevelledList() const { return mItemLevelledLists; } -const CSMWorld::RefIdDataContainer< ESM::Light >& CSMWorld::RefIdData::getLights() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getLights() const { return mLights; } -const CSMWorld::RefIdDataContainer< ESM::Lockpick >& CSMWorld::RefIdData::getLocpicks() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getLocpicks() const { return mLockpicks; } -const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& CSMWorld::RefIdData::getMiscellaneous() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getMiscellaneous() const { return mMiscellaneous; } -const CSMWorld::RefIdDataContainer< ESM::NPC >& CSMWorld::RefIdData::getNPCs() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getNPCs() const { return mNpcs; } -const CSMWorld::RefIdDataContainer< ESM::Weapon >& CSMWorld::RefIdData::getWeapons() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getWeapons() const { return mWeapons; } -const CSMWorld::RefIdDataContainer< ESM::Probe >& CSMWorld::RefIdData::getProbes() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getProbes() const { return mProbes; } -const CSMWorld::RefIdDataContainer< ESM::Repair >& CSMWorld::RefIdData::getRepairs() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getRepairs() const { return mRepairs; } -const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStatics() const +const CSMWorld::RefIdDataContainer& CSMWorld::RefIdData::getStatics() const { return mStatics; } -void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) +void CSMWorld::RefIdData::insertRecord( + std::unique_ptr record, CSMWorld::UniversalId::Type type, const ESM::RefId& id) { - std::map::iterator iter = - mRecordContainers.find (type); + std::map::iterator iter = mRecordContainers.find(type); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (iter == mRecordContainers.end()) + throw std::logic_error("invalid local index type"); - iter->second->insertRecord(record); + iter->second->insertRecord(std::move(record)); - mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), - LocalIndex (iter->second->getSize()-1, type))); + mIndex.insert(std::make_pair(id, LocalIndex(iter->second->getSize() - 1, type))); } -void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const +void CSMWorld::RefIdData::copyTo(int index, RefIdData& target) const { - LocalIndex localIndex = globalToLocalIndex (index); + LocalIndex localIndex = globalToLocalIndex(index); - RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second; + RefIdDataContainerBase* source = mRecordContainers.find(localIndex.second)->second; - std::string id = source->getId (localIndex.first); - - std::unique_ptr newRecord (source->getRecord (localIndex.first).modifiedCopy()); - - target.insertRecord (*newRecord, localIndex.second, id); + target.insertRecord( + source->getRecord(localIndex.first).modifiedCopy(), localIndex.second, source->getId(localIndex.first)); } diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 1480bb71d..4870e8672 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -1,31 +1,38 @@ #ifndef CSM_WOLRD_REFIDDATA_H #define CSM_WOLRD_REFIDDATA_H -#include +#include +#include #include +#include +#include +#include +#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include #include "record.hpp" #include "universalid.hpp" @@ -39,94 +46,107 @@ namespace CSMWorld { struct RefIdDataContainerBase { - virtual ~RefIdDataContainerBase(); + virtual ~RefIdDataContainerBase() = default; virtual int getSize() const = 0; - virtual const RecordBase& getRecord (int index) const = 0; + virtual const RecordBase& getRecord(int index) const = 0; - virtual RecordBase& getRecord (int index)= 0; + virtual RecordBase& getRecord(int index) = 0; - virtual void appendRecord (const std::string& id, bool base) = 0; + virtual unsigned int getRecordFlags(int index) const = 0; - virtual void insertRecord (RecordBase& record) = 0; + virtual void appendRecord(const ESM::RefId& id, bool base) = 0; - virtual int load (ESM::ESMReader& reader, bool base) = 0; + virtual void insertRecord(std::unique_ptr record) = 0; + + virtual int load(ESM::ESMReader& reader, bool base) = 0; ///< \return index of a loaded record or -1 if no record was loaded - virtual void erase (int index, int count) = 0; + virtual void erase(int index, int count) = 0; - virtual std::string getId (int index) const = 0; + virtual ESM::RefId getId(int index) const = 0; - virtual void save (int index, ESM::ESMWriter& writer) const = 0; + virtual void save(int index, ESM::ESMWriter& writer) const = 0; }; - template + template struct RefIdDataContainer : public RefIdDataContainerBase { - std::vector > mContainer; + std::vector>> mContainer; int getSize() const override; - const RecordBase& getRecord (int index) const override; + const RecordBase& getRecord(int index) const override; - RecordBase& getRecord (int index) override; + RecordBase& getRecord(int index) override; - void appendRecord (const std::string& id, bool base) override; + unsigned int getRecordFlags(int index) const override; - void insertRecord (RecordBase& record) override; + void appendRecord(const ESM::RefId& id, bool base) override; - int load (ESM::ESMReader& reader, bool base) override; + void insertRecord(std::unique_ptr record) override; + + int load(ESM::ESMReader& reader, bool base) override; ///< \return index of a loaded record or -1 if no record was loaded - void erase (int index, int count) override; + void erase(int index, int count) override; - std::string getId (int index) const override; + ESM::RefId getId(int index) const override; - void save (int index, ESM::ESMWriter& writer) const override; + void save(int index, ESM::ESMWriter& writer) const override; }; - template - void RefIdDataContainer::insertRecord(RecordBase& record) + template + void RefIdDataContainer::insertRecord(std::unique_ptr record) { - Record& newRecord = dynamic_cast& >(record); - mContainer.push_back(newRecord); + assert(record != nullptr); + // convert base pointer to record type pointer + std::unique_ptr> typedRecord(&dynamic_cast&>(*record)); + record.release(); + mContainer.push_back(std::move(typedRecord)); } - template + template int RefIdDataContainer::getSize() const { - return static_cast (mContainer.size()); + return static_cast(mContainer.size()); } - template - const RecordBase& RefIdDataContainer::getRecord (int index) const + template + const RecordBase& RefIdDataContainer::getRecord(int index) const { - return mContainer.at (index); + return *mContainer.at(index); } - template - RecordBase& RefIdDataContainer::getRecord (int index) + template + RecordBase& RefIdDataContainer::getRecord(int index) { - return mContainer.at (index); + return *mContainer.at(index); } - template - void RefIdDataContainer::appendRecord (const std::string& id, bool base) + template + unsigned int RefIdDataContainer::getRecordFlags(int index) const { - Record record; - - record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - - record.mBase.mId = id; - record.mModified.mId = id; - (base ? record.mBase : record.mModified).blank(); - - mContainer.push_back (record); + return mContainer.at(index)->get().mRecordFlags; } - template - int RefIdDataContainer::load (ESM::ESMReader& reader, bool base) + template + void RefIdDataContainer::appendRecord(const ESM::RefId& id, bool base) + { + auto record = std::make_unique>(); + + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + + record->mBase.mId = id; + record->mModified.mId = id; + (base ? record->mBase : record->mModified).blank(); + + mContainer.push_back(std::move(record)); + } + + template + int RefIdDataContainer::load(ESM::ESMReader& reader, bool base) { RecordT record; bool isDeleted = false; @@ -137,7 +157,7 @@ namespace CSMWorld int numRecords = static_cast(mContainer.size()); for (; index < numRecords; ++index) { - if (Misc::StringUtils::ciEqual(mContainer[index].get().mId, record.mId)) + if ((mContainer[index]->get().mId == record.mId)) { break; } @@ -155,7 +175,7 @@ namespace CSMWorld // Flag the record as Deleted even for a base content file. // RefIdData is responsible for its erasure. - mContainer[index].mState = RecordBase::State_Deleted; + mContainer[index]->mState = RecordBase::State_Deleted; } else { @@ -164,153 +184,150 @@ namespace CSMWorld appendRecord(record.mId, base); if (base) { - mContainer.back().mBase = record; + mContainer.back()->mBase = record; } else { - mContainer.back().mModified = record; + mContainer.back()->mModified = record; } } else if (!base) { - mContainer[index].setModified(record); + mContainer[index]->setModified(record); } else { // Overwrite - mContainer[index].setModified(record); - mContainer[index].merge(); + mContainer[index]->setModified(record); + mContainer[index]->merge(); } } return index; } - template - void RefIdDataContainer::erase (int index, int count) + template + void RefIdDataContainer::erase(int index, int count) { - if (index<0 || index+count>getSize()) - throw std::runtime_error ("invalid RefIdDataContainer index"); + if (index < 0 || index + count > getSize()) + throw std::runtime_error("invalid RefIdDataContainer index"); - mContainer.erase (mContainer.begin()+index, mContainer.begin()+index+count); + mContainer.erase(mContainer.begin() + index, mContainer.begin() + index + count); } - template - std::string RefIdDataContainer::getId (int index) const + template + ESM::RefId RefIdDataContainer::getId(int index) const { - return mContainer.at (index).get().mId; + return mContainer.at(index)->get().mId; } - template - void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const + template + void RefIdDataContainer::save(int index, ESM::ESMWriter& writer) const { - Record record = mContainer.at(index); + const Record& record = *mContainer.at(index); if (record.isModified() || record.mState == RecordBase::State_Deleted) { RecordT esmRecord = record.get(); - writer.startRecord(esmRecord.sRecordId); + writer.startRecord(esmRecord.sRecordId, esmRecord.mRecordFlags); esmRecord.save(writer, record.mState == RecordBase::State_Deleted); writer.endRecord(esmRecord.sRecordId); } } - class RefIdData { - public: + public: + typedef std::pair LocalIndex; - typedef std::pair LocalIndex; + private: + RefIdDataContainer mActivators; + RefIdDataContainer mPotions; + RefIdDataContainer mApparati; + RefIdDataContainer mArmors; + RefIdDataContainer mBooks; + RefIdDataContainer mClothing; + RefIdDataContainer mContainers; + RefIdDataContainer mCreatures; + RefIdDataContainer mDoors; + RefIdDataContainer mIngredients; + RefIdDataContainer mCreatureLevelledLists; + RefIdDataContainer mItemLevelledLists; + RefIdDataContainer mLights; + RefIdDataContainer mLockpicks; + RefIdDataContainer mMiscellaneous; + RefIdDataContainer mNpcs; + RefIdDataContainer mProbes; + RefIdDataContainer mRepairs; + RefIdDataContainer mStatics; + RefIdDataContainer mWeapons; - private: + std::map mIndex; - RefIdDataContainer mActivators; - RefIdDataContainer mPotions; - RefIdDataContainer mApparati; - RefIdDataContainer mArmors; - RefIdDataContainer mBooks; - RefIdDataContainer mClothing; - RefIdDataContainer mContainers; - RefIdDataContainer mCreatures; - RefIdDataContainer mDoors; - RefIdDataContainer mIngredients; - RefIdDataContainer mCreatureLevelledLists; - RefIdDataContainer mItemLevelledLists; - RefIdDataContainer mLights; - RefIdDataContainer mLockpicks; - RefIdDataContainer mMiscellaneous; - RefIdDataContainer mNpcs; - RefIdDataContainer mProbes; - RefIdDataContainer mRepairs; - RefIdDataContainer mStatics; - RefIdDataContainer mWeapons; + std::map mRecordContainers; - std::map mIndex; + void erase(const LocalIndex& index, int count); + ///< Must not spill over into another type. - std::map mRecordContainers; + ESM::RefId getRecordId(const LocalIndex& index) const; - void erase (const LocalIndex& index, int count); - ///< Must not spill over into another type. + public: + RefIdData(); - std::string getRecordId(const LocalIndex &index) const; + LocalIndex globalToLocalIndex(int index) const; - public: + int localToGlobalIndex(const LocalIndex& index) const; - RefIdData(); + LocalIndex searchId(const ESM::RefId& id) const; - LocalIndex globalToLocalIndex (int index) const; + void erase(int index, int count); - int localToGlobalIndex (const LocalIndex& index) const; + void insertRecord(std::unique_ptr record, CSMWorld::UniversalId::Type type, const ESM::RefId& id); - LocalIndex searchId (const std::string& id) const; + const RecordBase& getRecord(const LocalIndex& index) const; - void erase (int index, int count); + RecordBase& getRecord(const LocalIndex& index); - void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, - const std::string& id); + unsigned int getRecordFlags(const ESM::RefId& id) const; - const RecordBase& getRecord (const LocalIndex& index) const; + void appendRecord(UniversalId::Type type, const ESM::RefId& id, bool base); - RecordBase& getRecord (const LocalIndex& index); + int getAppendIndex(UniversalId::Type type) const; - void appendRecord (UniversalId::Type type, const std::string& id, bool base); + void load(ESM::ESMReader& reader, bool base, UniversalId::Type type); - int getAppendIndex (UniversalId::Type type) const; + int getSize() const; - void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); + std::vector getIds(bool listDeleted = true) const; + ///< Return a sorted collection of all IDs + /// + /// \param listDeleted include deleted record in the list - int getSize() const; + void save(int index, ESM::ESMWriter& writer) const; - std::vector getIds (bool listDeleted = true) const; - ///< Return a sorted collection of all IDs - /// - /// \param listDeleted include deleted record in the list + // RECORD CONTAINERS ACCESS METHODS + const RefIdDataContainer& getBooks() const; + const RefIdDataContainer& getActivators() const; + const RefIdDataContainer& getPotions() const; + const RefIdDataContainer& getApparati() const; + const RefIdDataContainer& getArmors() const; + const RefIdDataContainer& getClothing() const; + const RefIdDataContainer& getContainers() const; + const RefIdDataContainer& getCreatures() const; + const RefIdDataContainer& getDoors() const; + const RefIdDataContainer& getIngredients() const; + const RefIdDataContainer& getCreatureLevelledLists() const; + const RefIdDataContainer& getItemLevelledList() const; + const RefIdDataContainer& getLights() const; + const RefIdDataContainer& getLocpicks() const; + const RefIdDataContainer& getMiscellaneous() const; + const RefIdDataContainer& getNPCs() const; + const RefIdDataContainer& getWeapons() const; + const RefIdDataContainer& getProbes() const; + const RefIdDataContainer& getRepairs() const; + const RefIdDataContainer& getStatics() const; - void save (int index, ESM::ESMWriter& writer) const; - - //RECORD CONTAINERS ACCESS METHODS - const RefIdDataContainer& getBooks() const; - const RefIdDataContainer& getActivators() const; - const RefIdDataContainer& getPotions() const; - const RefIdDataContainer& getApparati() const; - const RefIdDataContainer& getArmors() const; - const RefIdDataContainer& getClothing() const; - const RefIdDataContainer& getContainers() const; - const RefIdDataContainer& getCreatures() const; - const RefIdDataContainer& getDoors() const; - const RefIdDataContainer& getIngredients() const; - const RefIdDataContainer& getCreatureLevelledLists() const; - const RefIdDataContainer& getItemLevelledList() const; - const RefIdDataContainer& getLights() const; - const RefIdDataContainer& getLocpicks() const; - const RefIdDataContainer& getMiscellaneous() const; - const RefIdDataContainer& getNPCs() const; - const RefIdDataContainer& getWeapons() const; - const RefIdDataContainer& getProbes() const; - const RefIdDataContainer& getRepairs() const; - const RefIdDataContainer& getStatics() const; - - void copyTo (int index, RefIdData& target) const; + void copyTo(int index, RefIdData& target) const; }; } diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 6dbbac97f..5c22aedf4 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,23 +1,37 @@ #include "regionmap.hpp" -#include -#include - #include +#include +#include +#include -#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include #include "data.hpp" #include "universalid.hpp" -CSMWorld::RegionMap::CellDescription::CellDescription() : mDeleted (false) {} +CSMWorld::RegionMap::CellDescription::CellDescription() + : mDeleted(false) +{ +} -CSMWorld::RegionMap::CellDescription::CellDescription (const Record& cell) +CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell) { const Cell& cell2 = cell.get(); if (!cell2.isExterior()) - throw std::logic_error ("Interior cell in region map"); + throw std::logic_error("Interior cell in region map"); mDeleted = cell.isDeleted(); @@ -25,28 +39,28 @@ CSMWorld::RegionMap::CellDescription::CellDescription (const Record& cell) mName = cell2.mName; } -CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const QModelIndex& index) const +CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex(const QModelIndex& index) const { - return mMin.move (index.column(), mMax.getY()-mMin.getY() - index.row()-1); + return mMin.move(index.column(), mMax.getY() - mMin.getY() - index.row() - 1); } -QModelIndex CSMWorld::RegionMap::getIndex (const CellCoordinates& index) const +QModelIndex CSMWorld::RegionMap::getIndex(const CellCoordinates& index) const { // I hate you, Qt API naming scheme! - return QAbstractTableModel::index (mMax.getY()-mMin.getY() - (index.getY()-mMin.getY())-1, - index.getX()-mMin.getX()); + return QAbstractTableModel::index( + mMax.getY() - mMin.getY() - (index.getY() - mMin.getY()) - 1, index.getX() - mMin.getX()); } -CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const Cell& cell) const +CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex(const Cell& cell) const { - std::istringstream stream (cell.mId); + std::istringstream stream(cell.mId.getRefIdString()); char ignore; int x = 0; int y = 0; stream >> ignore >> x >> y; - return CellCoordinates (x, y); + return CellCoordinates(x, y); } void CSMWorld::RegionMap::buildRegions() @@ -55,13 +69,12 @@ void CSMWorld::RegionMap::buildRegions() int size = regions.getSize(); - for (int i=0; i& region = regions.getRecord (i); + const Record& region = regions.getRecord(i); if (!region.isDeleted()) - mColours.insert (std::make_pair (Misc::StringUtils::lowerCase (region.get().mId), - region.get().mMapColor)); + mColours.insert(std::make_pair(region.get().mId, region.get().mMapColor)); } } @@ -71,19 +84,19 @@ void CSMWorld::RegionMap::buildMap() int size = cells.getSize(); - for (int i=0; i& cell = cells.getRecord (i); + const Record& cell = cells.getRecord(i); const Cell& cell2 = cell.get(); if (cell2.isExterior()) { - CellDescription description (cell); + CellDescription description(cell); - CellCoordinates index = getIndex (cell2); + CellCoordinates index = getIndex(cell2); - mMap.insert (std::make_pair (index, description)); + mMap.insert(std::make_pair(index, description)); } } @@ -93,11 +106,11 @@ void CSMWorld::RegionMap::buildMap() mMax = mapSize.second; } -void CSMWorld::RegionMap::addCell (const CellCoordinates& index, const CellDescription& description) +void CSMWorld::RegionMap::addCell(const CellCoordinates& index, const CellDescription& description) { - std::map::iterator cell = mMap.find (index); + std::map::iterator cell = mMap.find(index); - if (cell!=mMap.end()) + if (cell != mMap.end()) { cell->second = description; } @@ -105,82 +118,78 @@ void CSMWorld::RegionMap::addCell (const CellCoordinates& index, const CellDescr { updateSize(); - mMap.insert (std::make_pair (index, description)); + mMap.insert(std::make_pair(index, description)); } - QModelIndex index2 = getIndex (index); + QModelIndex index2 = getIndex(index); - dataChanged (index2, index2); + dataChanged(index2, index2); } -void CSMWorld::RegionMap::addCells (int start, int end) +void CSMWorld::RegionMap::addCells(int start, int end) { const IdCollection& cells = mData.getCells(); - for (int i=start; i<=end; ++i) + for (int i = start; i <= end; ++i) { - const Record& cell = cells.getRecord (i); + const Record& cell = cells.getRecord(i); const Cell& cell2 = cell.get(); if (cell2.isExterior()) { - CellCoordinates index = getIndex (cell2); + CellCoordinates index = getIndex(cell2); - CellDescription description (cell); + CellDescription description(cell); - addCell (index, description); + addCell(index, description); } } } -void CSMWorld::RegionMap::removeCell (const CellCoordinates& index) +void CSMWorld::RegionMap::removeCell(const CellCoordinates& index) { - std::map::iterator cell = mMap.find (index); + std::map::iterator cell = mMap.find(index); - if (cell!=mMap.end()) + if (cell != mMap.end()) { - mMap.erase (cell); + mMap.erase(cell); - QModelIndex index2 = getIndex (index); + QModelIndex index2 = getIndex(index); - dataChanged (index2, index2); + dataChanged(index2, index2); updateSize(); } } -void CSMWorld::RegionMap::addRegion (const std::string& region, unsigned int colour) +void CSMWorld::RegionMap::addRegion(const ESM::RefId& region, unsigned int colour) { - mColours[Misc::StringUtils::lowerCase (region)] = colour; + mColours[region] = colour; } -void CSMWorld::RegionMap::removeRegion (const std::string& region) +void CSMWorld::RegionMap::removeRegion(const ESM::RefId& region) { - std::map::iterator iter ( - mColours.find (Misc::StringUtils::lowerCase (region))); + auto iter(mColours.find(region)); - if (iter!=mColours.end()) - mColours.erase (iter); + if (iter != mColours.end()) + mColours.erase(iter); } -void CSMWorld::RegionMap::updateRegions (const std::vector& regions) +void CSMWorld::RegionMap::updateRegions(const std::vector& regions) { - std::vector regions2 (regions); + std::vector regions2(regions); - std::for_each (regions2.begin(), regions2.end(), Misc::StringUtils::lowerCaseInPlace); - std::sort (regions2.begin(), regions2.end()); + std::sort(regions2.begin(), regions2.end()); - for (std::map::const_iterator iter (mMap.begin()); - iter!=mMap.end(); ++iter) + for (std::map::const_iterator iter(mMap.begin()); iter != mMap.end(); ++iter) { - if (!iter->second.mRegion.empty() && - std::find (regions2.begin(), regions2.end(), - Misc::StringUtils::lowerCase (iter->second.mRegion))!=regions2.end()) + if (!iter->second.mRegion.empty() + && std::find(regions2.begin(), regions2.end(), iter->second.mRegion) != regions2.end()) { - QModelIndex index = getIndex (iter->first); + QModelIndex index = getIndex(iter->first); - dataChanged (index, index); + dataChanged(index, index); } } } @@ -191,15 +200,15 @@ void CSMWorld::RegionMap::updateSize() if (int diff = size.first.getX() - mMin.getX()) { - beginInsertColumns (QModelIndex(), 0, std::abs (diff)-1); - mMin = CellCoordinates (size.first.getX(), mMin.getY()); + beginInsertColumns(QModelIndex(), 0, std::abs(diff) - 1); + mMin = CellCoordinates(size.first.getX(), mMin.getY()); endInsertColumns(); } if (int diff = size.first.getY() - mMin.getY()) { - beginInsertRows (QModelIndex(), 0, std::abs (diff)-1); - mMin = CellCoordinates (mMin.getX(), size.first.getY()); + beginInsertRows(QModelIndex(), 0, std::abs(diff) - 1); + mMin = CellCoordinates(mMin.getX(), size.first.getY()); endInsertRows(); } @@ -207,12 +216,12 @@ void CSMWorld::RegionMap::updateSize() { int columns = columnCount(); - if (diff>0) - beginInsertColumns (QModelIndex(), columns, columns+diff-1); + if (diff > 0) + beginInsertColumns(QModelIndex(), columns, columns + diff - 1); else - beginRemoveColumns (QModelIndex(), columns+diff, columns-1); + beginRemoveColumns(QModelIndex(), columns + diff, columns - 1); - mMax = CellCoordinates (size.second.getX(), mMax.getY()); + mMax = CellCoordinates(size.second.getX(), mMax.getY()); endInsertColumns(); } @@ -220,12 +229,12 @@ void CSMWorld::RegionMap::updateSize() { int rows = rowCount(); - if (diff>0) - beginInsertRows (QModelIndex(), rows, rows+diff-1); + if (diff > 0) + beginInsertRows(QModelIndex(), rows, rows + diff - 1); else - beginRemoveRows (QModelIndex(), rows+diff, rows-1); + beginRemoveRows(QModelIndex(), rows + diff, rows - 1); - mMax = CellCoordinates (mMax.getX(), size.second.getY()); + mMax = CellCoordinates(mMax.getX(), size.second.getY()); endInsertRows(); } } @@ -236,128 +245,118 @@ std::pair CSMWorld::Region int size = cells.getSize(); - CellCoordinates min (0, 0); - CellCoordinates max (0, 0); + CellCoordinates min(0, 0); + CellCoordinates max(0, 0); - for (int i=0; i& cell = cells.getRecord (i); + const Record& cell = cells.getRecord(i); const Cell& cell2 = cell.get(); if (cell2.isExterior()) { - CellCoordinates index = getIndex (cell2); + CellCoordinates index = getIndex(cell2); - if (min==max) + if (min == max) { min = index; - max = min.move (1, 1); + max = min.move(1, 1); } else { - if (index.getX()=max.getX()) - max = CellCoordinates (index.getX()+1, max.getY()); + if (index.getX() < min.getX()) + min = CellCoordinates(index.getX(), min.getY()); + else if (index.getX() >= max.getX()) + max = CellCoordinates(index.getX() + 1, max.getY()); - if (index.getY()=max.getY()) - max = CellCoordinates (max.getX(), index.getY() + 1); + if (index.getY() < min.getY()) + min = CellCoordinates(min.getX(), index.getY()); + else if (index.getY() >= max.getY()) + max = CellCoordinates(max.getX(), index.getY() + 1); } } } - return std::make_pair (min, max); + return std::make_pair(min, max); } -CSMWorld::RegionMap::RegionMap (Data& data) : mData (data) +CSMWorld::RegionMap::RegionMap(Data& data) + : mData(data) { buildRegions(); buildMap(); - QAbstractItemModel *regions = data.getTableModel (UniversalId (UniversalId::Type_Regions)); + QAbstractItemModel* regions = data.getTableModel(UniversalId(UniversalId::Type_Regions)); - connect (regions, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (regionsAboutToBeRemoved (const QModelIndex&, int, int))); - connect (regions, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (regionsInserted (const QModelIndex&, int, int))); - connect (regions, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (regionsChanged (const QModelIndex&, const QModelIndex&))); + connect(regions, &QAbstractItemModel::rowsAboutToBeRemoved, this, &RegionMap::regionsAboutToBeRemoved); + connect(regions, &QAbstractItemModel::rowsInserted, this, &RegionMap::regionsInserted); + connect(regions, &QAbstractItemModel::dataChanged, this, &RegionMap::regionsChanged); - QAbstractItemModel *cells = data.getTableModel (UniversalId (UniversalId::Type_Cells)); + QAbstractItemModel* cells = data.getTableModel(UniversalId(UniversalId::Type_Cells)); - connect (cells, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (cellsAboutToBeRemoved (const QModelIndex&, int, int))); - connect (cells, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (cellsInserted (const QModelIndex&, int, int))); - connect (cells, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (cellsChanged (const QModelIndex&, const QModelIndex&))); + connect(cells, &QAbstractItemModel::rowsAboutToBeRemoved, this, &RegionMap::cellsAboutToBeRemoved); + connect(cells, &QAbstractItemModel::rowsInserted, this, &RegionMap::cellsInserted); + connect(cells, &QAbstractItemModel::dataChanged, this, &RegionMap::cellsChanged); } -int CSMWorld::RegionMap::rowCount (const QModelIndex& parent) const +int CSMWorld::RegionMap::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; - return mMax.getY()-mMin.getY(); + return mMax.getY() - mMin.getY(); } -int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const +int CSMWorld::RegionMap::columnCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; - return mMax.getX()-mMin.getX(); + return mMax.getX() - mMin.getX(); } -QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const +QVariant CSMWorld::RegionMap::data(const QModelIndex& index, int role) const { - if (role==Qt::SizeHintRole) - return QSize (16, 16); + if (role == Qt::SizeHintRole) + return QSize(16, 16); - if (role==Qt::BackgroundRole) + if (role == Qt::BackgroundRole) { /// \todo GUI class in non-GUI code. Needs to be addressed eventually. - std::map::const_iterator cell = - mMap.find (getIndex (index)); + std::map::const_iterator cell = mMap.find(getIndex(index)); - if (cell!=mMap.end()) + if (cell != mMap.end()) { if (cell->second.mDeleted) - return QBrush (Qt::red, Qt::DiagCrossPattern); + return QBrush(Qt::red, Qt::DiagCrossPattern); - std::map::const_iterator iter = - mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion)); + auto iter = mColours.find(cell->second.mRegion); - if (iter!=mColours.end()) - return QBrush (QColor (iter->second & 0xff, - (iter->second >> 8) & 0xff, - (iter->second >> 16) & 0xff)); + if (iter != mColours.end()) + return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff)); if (cell->second.mRegion.empty()) - return QBrush (Qt::Dense6Pattern); // no region + return QBrush(Qt::Dense6Pattern); // no region - return QBrush (Qt::red, Qt::Dense6Pattern); // invalid region + return QBrush(Qt::red, Qt::Dense6Pattern); // invalid region } - return QBrush (Qt::DiagCrossPattern); + return QBrush(Qt::DiagCrossPattern); } - if (role==Qt::ToolTipRole) + if (role == Qt::ToolTipRole) { - CellCoordinates cellIndex = getIndex (index); + CellCoordinates cellIndex = getIndex(index); std::ostringstream stream; stream << cellIndex; - std::map::const_iterator cell = - mMap.find (cellIndex); + std::map::const_iterator cell = mMap.find(cellIndex); - if (cell!=mMap.end()) + if (cell != mMap.end()) { if (!cell->second.mName.empty()) stream << " " << cell->second.mName; @@ -369,10 +368,9 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const { stream << "
"; - std::map::const_iterator iter = - mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion)); + auto iter = mColours.find(cell->second.mRegion); - if (iter!=mColours.end()) + if (iter != mColours.end()) stream << cell->second.mRegion; else stream << "" << cell->second.mRegion << ""; @@ -381,125 +379,124 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const else stream << " (no cell)"; - return QString::fromUtf8 (stream.str().c_str()); + return QString::fromUtf8(stream.str().c_str()); } - if (role==Role_Region) + if (role == Role_Region) { - CellCoordinates cellIndex = getIndex (index); + CellCoordinates cellIndex = getIndex(index); - std::map::const_iterator cell = - mMap.find (cellIndex); + std::map::const_iterator cell = mMap.find(cellIndex); - if (cell!=mMap.end() && !cell->second.mRegion.empty()) - return QString::fromUtf8 (Misc::StringUtils::lowerCase (cell->second.mRegion).c_str()); + if (cell != mMap.end() && !cell->second.mRegion.empty()) + return QString::fromUtf8(cell->second.mRegion.getRefIdString().c_str()); } - if (role==Role_CellId) + if (role == Role_CellId) { - CellCoordinates cellIndex = getIndex (index); + CellCoordinates cellIndex = getIndex(index); std::ostringstream stream; stream << "#" << cellIndex.getX() << " " << cellIndex.getY(); - return QString::fromUtf8 (stream.str().c_str()); + return QString::fromUtf8(stream.str().c_str()); } return QVariant(); } -Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const +Qt::ItemFlags CSMWorld::RegionMap::flags(const QModelIndex& index) const { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } -void CSMWorld::RegionMap::regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSMWorld::RegionMap::regionsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - std::vector update; + std::vector update; const IdCollection& regions = mData.getRegions(); - for (int i=start; i<=end; ++i) + for (int i = start; i <= end; ++i) { - const Record& region = regions.getRecord (i); + const Record& region = regions.getRecord(i); - update.push_back (region.get().mId); + update.push_back(region.get().mId); - removeRegion (region.get().mId); + removeRegion(region.get().mId); } - updateRegions (update); + updateRegions(update); } -void CSMWorld::RegionMap::regionsInserted (const QModelIndex& parent, int start, int end) +void CSMWorld::RegionMap::regionsInserted(const QModelIndex& parent, int start, int end) { - std::vector update; + std::vector update; const IdCollection& regions = mData.getRegions(); - for (int i=start; i<=end; ++i) + for (int i = start; i <= end; ++i) { - const Record& region = regions.getRecord (i); + const Record& region = regions.getRecord(i); if (!region.isDeleted()) { - update.push_back (region.get().mId); + update.push_back(region.get().mId); - addRegion (region.get().mId, region.get().mMapColor); + addRegion(region.get().mId, region.get().mMapColor); } } - updateRegions (update); + updateRegions(update); } -void CSMWorld::RegionMap::regionsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSMWorld::RegionMap::regionsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { // Note: At this point an additional check could be inserted to see if there is any change to the // columns we are interested in. If not we can exit the function here and avoid all updating. - std::vector update; + std::vector update; const IdCollection& regions = mData.getRegions(); - for (int i=topLeft.row(); i<=bottomRight.column(); ++i) + for (int i = topLeft.row(); i <= bottomRight.column(); ++i) { - const Record& region = regions.getRecord (i); + const Record& region = regions.getRecord(i); - update.push_back (region.get().mId); + update.push_back(region.get().mId); if (!region.isDeleted()) - addRegion (region.get().mId, region.get().mMapColor); + addRegion(region.get().mId, region.get().mMapColor); else - removeRegion (region.get().mId); + removeRegion(region.get().mId); } - updateRegions (update); + updateRegions(update); } -void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSMWorld::RegionMap::cellsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { const IdCollection& cells = mData.getCells(); - for (int i=start; i<=end; ++i) + for (int i = start; i <= end; ++i) { - const Record& cell = cells.getRecord (i); + const Record& cell = cells.getRecord(i); const Cell& cell2 = cell.get(); if (cell2.isExterior()) - removeCell (getIndex (cell2)); + removeCell(getIndex(cell2)); } } -void CSMWorld::RegionMap::cellsInserted (const QModelIndex& parent, int start, int end) +void CSMWorld::RegionMap::cellsInserted(const QModelIndex& parent, int start, int end) { - addCells (start, end); + addCells(start, end); } -void CSMWorld::RegionMap::cellsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSMWorld::RegionMap::cellsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { // Note: At this point an additional check could be inserted to see if there is any change to the // columns we are interested in. If not we can exit the function here and avoid all updating. - addCells (topLeft.row(), bottomRight.row()); + addCells(topLeft.row(), bottomRight.row()); } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 1de7f1cdb..3f62c7b61 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -3,117 +3,122 @@ #include #include +#include #include #include +#include +#include -#include "record.hpp" -#include "cell.hpp" #include "cellcoordinates.hpp" +#include + +class QObject; namespace CSMWorld { class Data; + struct Cell; + + template + struct Record; /// \brief Model for the region map /// /// This class does not holds any record data (other than for the purpose of buffering). class RegionMap : public QAbstractTableModel { - Q_OBJECT + Q_OBJECT - public: + public: + enum Role + { + Role_Region = Qt::UserRole, + Role_CellId = Qt::UserRole + 1 + }; - enum Role - { - Role_Region = Qt::UserRole, - Role_CellId = Qt::UserRole+1 - }; + private: + struct CellDescription + { + bool mDeleted; + ESM::RefId mRegion; + std::string mName; - private: + CellDescription(); - struct CellDescription - { - bool mDeleted; - std::string mRegion; - std::string mName; + CellDescription(const Record& cell); + }; - CellDescription(); + Data& mData; + std::map mMap; + CellCoordinates mMin; ///< inclusive + CellCoordinates mMax; ///< exclusive + std::map mColours; ///< region ID, colour (RGBA) - CellDescription (const Record& cell); - }; + CellCoordinates getIndex(const QModelIndex& index) const; + ///< Translates a Qt model index into a cell index (which can contain negative components) - Data& mData; - std::map mMap; - CellCoordinates mMin; ///< inclusive - CellCoordinates mMax; ///< exclusive - std::map mColours; ///< region ID, colour (RGBA) + QModelIndex getIndex(const CellCoordinates& index) const; - CellCoordinates getIndex (const QModelIndex& index) const; - ///< Translates a Qt model index into a cell index (which can contain negative components) + CellCoordinates getIndex(const Cell& cell) const; - QModelIndex getIndex (const CellCoordinates& index) const; + void buildRegions(); - CellCoordinates getIndex (const Cell& cell) const; + void buildMap(); - void buildRegions(); + void addCell(const CellCoordinates& index, const CellDescription& description); + ///< May be called on a cell that is already in the map (in which case an update is + // performed) - void buildMap(); + void addCells(int start, int end); - void addCell (const CellCoordinates& index, const CellDescription& description); - ///< May be called on a cell that is already in the map (in which case an update is - // performed) + void removeCell(const CellCoordinates& index); + ///< May be called on a cell that is not in the map (in which case the call is ignored) - void addCells (int start, int end); + void addRegion(const ESM::RefId& region, unsigned int colour); + ///< May be called on a region that is already listed (in which case an update is + /// performed) + /// + /// \note This function does not update the region map. - void removeCell (const CellCoordinates& index); - ///< May be called on a cell that is not in the map (in which case the call is ignored) + void removeRegion(const ESM::RefId& region); + ///< May be called on a region that is not listed (in which case the call is ignored) + /// + /// \note This function does not update the region map. - void addRegion (const std::string& region, unsigned int colour); - ///< May be called on a region that is already listed (in which case an update is - /// performed) - /// - /// \note This function does not update the region map. + void updateRegions(const std::vector& regions); + ///< Update cells affected by the listed regions - void removeRegion (const std::string& region); - ///< May be called on a region that is not listed (in which case the call is ignored) - /// - /// \note This function does not update the region map. + void updateSize(); - void updateRegions (const std::vector& regions); - ///< Update cells affected by the listed regions + std::pair getSize() const; - void updateSize(); + public: + RegionMap(Data& data); - std::pair getSize() const; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; - public: + int columnCount(const QModelIndex& parent = QModelIndex()) const override; - RegionMap (Data& data); + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + ///< \note Calling this function with role==Role_CellId may return the ID of a cell + /// that does not exist. - int rowCount (const QModelIndex& parent = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; - int columnCount (const QModelIndex& parent = QModelIndex()) const override; + private slots: - QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const override; - ///< \note Calling this function with role==Role_CellId may return the ID of a cell - /// that does not exist. + void regionsAboutToBeRemoved(const QModelIndex& parent, int start, int end); - Qt::ItemFlags flags (const QModelIndex& index) const override; + void regionsInserted(const QModelIndex& parent, int start, int end); - private slots: + void regionsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void cellsAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void regionsInserted (const QModelIndex& parent, int start, int end); + void cellsInserted(const QModelIndex& parent, int start, int end); - void regionsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void cellsAboutToBeRemoved (const QModelIndex& parent, int start, int end); - - void cellsInserted (const QModelIndex& parent, int start, int end); - - void cellsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void cellsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); }; } diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index c3eb9762e..bfab0193b 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -1,60 +1,61 @@ #include "resources.hpp" -#include -#include #include +#include +#include +#include +#include +#include +#include + +#include #include -#include - -CSMWorld::Resources::Resources (const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, - const char * const *extensions) -: mBaseDirectory (baseDirectory), mType (type) +CSMWorld::Resources::Resources( + const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char* const* extensions) + : mBaseDirectory(baseDirectory) + , mType(type) { recreate(vfs, extensions); } -void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const *extensions) +void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* extensions) { mFiles.clear(); mIndex.clear(); size_t baseSize = mBaseDirectory.size(); - const std::map& index = vfs->getIndex(); - for (std::map::const_iterator it = index.begin(); it != index.end(); ++it) + for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) { - std::string filepath = it->first; - if (filepath.size() (mFiles.size())-1)); + std::string file = filepath.substr(baseSize + 1); + mFiles.push_back(file); + std::replace(file.begin(), file.end(), '\\', '/'); + mIndex.insert(std::make_pair(Misc::StringUtils::lowerCase(file), static_cast(mFiles.size()) - 1)); } } @@ -63,35 +64,35 @@ int CSMWorld::Resources::getSize() const return static_cast(mFiles.size()); } -std::string CSMWorld::Resources::getId (int index) const +std::string CSMWorld::Resources::getId(int index) const { - return mFiles.at (index); + return mFiles.at(index); } -int CSMWorld::Resources::getIndex (const std::string& id) const +int CSMWorld::Resources::getIndex(const std::string& id) const { - int index = searchId (id); + int index = searchId(id); - if (index==-1) + if (index == -1) { std::ostringstream stream; stream << "Invalid resource: " << mBaseDirectory << '/' << id; - throw std::runtime_error (stream.str()); + throw std::runtime_error(stream.str()); } return index; } -int CSMWorld::Resources::searchId (const std::string& id) const +int CSMWorld::Resources::searchId(std::string_view id) const { - std::string id2 = Misc::StringUtils::lowerCase (id); + std::string id2 = Misc::StringUtils::lowerCase(id); - std::replace (id2.begin(), id2.end(), '\\', '/'); + std::replace(id2.begin(), id2.end(), '\\', '/'); - std::map::const_iterator iter = mIndex.find (id2); + std::map::const_iterator iter = mIndex.find(id2); - if (iter==mIndex.end()) + if (iter == mIndex.end()) return -1; return iter->second; diff --git a/apps/opencs/model/world/resources.hpp b/apps/opencs/model/world/resources.hpp index c217b793d..9a3152daa 100644 --- a/apps/opencs/model/world/resources.hpp +++ b/apps/opencs/model/world/resources.hpp @@ -1,8 +1,9 @@ #ifndef CSM_WOLRD_RESOURCES_H #define CSM_WOLRD_RESOURCES_H -#include #include +#include +#include #include #include "universalid.hpp" @@ -16,28 +17,27 @@ namespace CSMWorld { class Resources { - std::map mIndex; - std::vector mFiles; - std::string mBaseDirectory; - UniversalId::Type mType; + std::map mIndex; + std::vector mFiles; + std::string mBaseDirectory; + UniversalId::Type mType; - public: + public: + /// \param type Type of resources in this table. + Resources(const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, + const char* const* extensions = nullptr); - /// \param type Type of resources in this table. - Resources (const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, - const char * const *extensions = nullptr); + void recreate(const VFS::Manager* vfs, const char* const* extensions = nullptr); - void recreate(const VFS::Manager* vfs, const char * const *extensions = nullptr); + int getSize() const; - int getSize() const; + std::string getId(int index) const; - std::string getId (int index) const; + int getIndex(const std::string& id) const; - int getIndex (const std::string& id) const; + int searchId(std::string_view id) const; - int searchId (const std::string& id) const; - - UniversalId::Type getType() const; + UniversalId::Type getType() const; }; } diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 378ba7c6b..2b6ed2f8f 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -1,37 +1,44 @@ #include "resourcesmanager.hpp" +#include "resources.hpp" + +#include + #include +#include +#include CSMWorld::ResourcesManager::ResourcesManager() : mVFS(nullptr) { } -void CSMWorld::ResourcesManager::addResources (const Resources& resources) +CSMWorld::ResourcesManager::~ResourcesManager() = default; + +void CSMWorld::ResourcesManager::addResources(const Resources& resources) { - mResources.insert (std::make_pair (resources.getType(), resources)); - mResources.insert (std::make_pair (UniversalId::getParentType (resources.getType()), - resources)); + mResources.insert(std::make_pair(resources.getType(), resources)); + mResources.insert(std::make_pair(UniversalId::getParentType(resources.getType()), resources)); } -const char * const * CSMWorld::ResourcesManager::getMeshExtensions() +const char* const* CSMWorld::ResourcesManager::getMeshExtensions() { // maybe we could go over the osgDB::Registry to list all supported node formats - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae", 0 }; + static const char* const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae", 0 }; return sMeshTypes; } -void CSMWorld::ResourcesManager::setVFS(const VFS::Manager *vfs) +void CSMWorld::ResourcesManager::setVFS(const VFS::Manager* vfs) { mVFS = vfs; mResources.clear(); - addResources (Resources (vfs, "meshes", UniversalId::Type_Mesh, getMeshExtensions())); - addResources (Resources (vfs, "icons", UniversalId::Type_Icon)); - addResources (Resources (vfs, "music", UniversalId::Type_Music)); - addResources (Resources (vfs, "sound", UniversalId::Type_SoundRes)); - addResources (Resources (vfs, "textures", UniversalId::Type_Texture)); - addResources (Resources (vfs, "video", UniversalId::Type_Video)); + addResources(Resources(vfs, "meshes", UniversalId::Type_Mesh, getMeshExtensions())); + addResources(Resources(vfs, "icons", UniversalId::Type_Icon)); + addResources(Resources(vfs, "music", UniversalId::Type_Music)); + addResources(Resources(vfs, "sound", UniversalId::Type_SoundRes)); + addResources(Resources(vfs, "textures", UniversalId::Type_Texture)); + addResources(Resources(vfs, "video", UniversalId::Type_Video)); } const VFS::Manager* CSMWorld::ResourcesManager::getVFS() const @@ -42,7 +49,7 @@ const VFS::Manager* CSMWorld::ResourcesManager::getVFS() const void CSMWorld::ResourcesManager::recreateResources() { std::map::iterator it = mResources.begin(); - for ( ; it != mResources.end(); ++it) + for (; it != mResources.end(); ++it) { if (it->first == UniversalId::Type_Mesh) it->second.recreate(mVFS, getMeshExtensions()); @@ -51,12 +58,12 @@ void CSMWorld::ResourcesManager::recreateResources() } } -const CSMWorld::Resources& CSMWorld::ResourcesManager::get (UniversalId::Type type) const +const CSMWorld::Resources& CSMWorld::ResourcesManager::get(UniversalId::Type type) const { - std::map::const_iterator iter = mResources.find (type); + std::map::const_iterator iter = mResources.find(type); - if (iter==mResources.end()) - throw std::logic_error ("Unknown resource type"); + if (iter == mResources.end()) + throw std::logic_error("Unknown resource type"); return iter->second; } diff --git a/apps/opencs/model/world/resourcesmanager.hpp b/apps/opencs/model/world/resourcesmanager.hpp index 0e8385300..0bc1c8641 100644 --- a/apps/opencs/model/world/resourcesmanager.hpp +++ b/apps/opencs/model/world/resourcesmanager.hpp @@ -3,8 +3,9 @@ #include +#include + #include "universalid.hpp" -#include "resources.hpp" namespace VFS { @@ -15,26 +16,25 @@ namespace CSMWorld { class ResourcesManager { - std::map mResources; - const VFS::Manager* mVFS; + std::map mResources; + const VFS::Manager* mVFS; - private: + private: + void addResources(const Resources& resources); - void addResources (const Resources& resources); + const char* const* getMeshExtensions(); - const char * const * getMeshExtensions(); + public: + ResourcesManager(); + ~ResourcesManager(); - public: + const VFS::Manager* getVFS() const; - ResourcesManager(); + void setVFS(const VFS::Manager* vfs); - const VFS::Manager* getVFS() const; + void recreateResources(); - void setVFS(const VFS::Manager* vfs); - - void recreateResources(); - - const Resources& get (UniversalId::Type type) const; + const Resources& get(UniversalId::Type type) const; }; } diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 0e7864e48..5c15ee90e 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -1,18 +1,21 @@ #include "resourcetable.hpp" #include +#include + +#include -#include "resources.hpp" #include "columnbase.hpp" +#include "resources.hpp" #include "universalid.hpp" -CSMWorld::ResourceTable::ResourceTable (const Resources *resources, unsigned int features) -: IdTableBase (features | Feature_Constant), mResources (resources) -{} +CSMWorld::ResourceTable::ResourceTable(const Resources* resources, unsigned int features) + : IdTableBase(features | Feature_Constant) + , mResources(resources) +{ +} -CSMWorld::ResourceTable::~ResourceTable() {} - -int CSMWorld::ResourceTable::rowCount (const QModelIndex & parent) const +int CSMWorld::ResourceTable::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; @@ -20,7 +23,7 @@ int CSMWorld::ResourceTable::rowCount (const QModelIndex & parent) const return mResources->getSize(); } -int CSMWorld::ResourceTable::columnCount (const QModelIndex & parent) const +int CSMWorld::ResourceTable::columnCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; @@ -28,47 +31,46 @@ int CSMWorld::ResourceTable::columnCount (const QModelIndex & parent) const return 2; // ID, type } -QVariant CSMWorld::ResourceTable::data (const QModelIndex & index, int role) const +QVariant CSMWorld::ResourceTable::data(const QModelIndex& index, int role) const { - if (role!=Qt::DisplayRole) + if (role != Qt::DisplayRole) return QVariant(); - if (index.column()==0) - return QString::fromUtf8 (mResources->getId (index.row()).c_str()); + if (index.column() == 0) + return QString::fromUtf8(mResources->getId(index.row()).c_str()); - if (index.column()==1) - return static_cast (mResources->getType()); + if (index.column() == 1) + return static_cast(mResources->getType()); - throw std::logic_error ("Invalid column in resource table"); + throw std::logic_error("Invalid column in resource table"); } -QVariant CSMWorld::ResourceTable::headerData (int section, Qt::Orientation orientation, - int role ) const +QVariant CSMWorld::ResourceTable::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation==Qt::Vertical) + if (orientation == Qt::Vertical) return QVariant(); - if (role==ColumnBase::Role_Flags) - return section==0 ? ColumnBase::Flag_Table : 0; + if (role == ColumnBase::Role_Flags) + return section == 0 ? ColumnBase::Flag_Table : 0; switch (section) { case 0: - if (role==Qt::DisplayRole) - return Columns::getName (Columns::ColumnId_Id).c_str(); + if (role == Qt::DisplayRole) + return Columns::getName(Columns::ColumnId_Id).c_str(); - if (role==ColumnBase::Role_Display) + if (role == ColumnBase::Role_Display) return ColumnBase::Display_Id; break; case 1: - if (role==Qt::DisplayRole) - return Columns::getName (Columns::ColumnId_RecordType).c_str(); + if (role == Qt::DisplayRole) + return Columns::getName(Columns::ColumnId_RecordType).c_str(); - if (role==ColumnBase::Role_Display) + if (role == ColumnBase::Role_Display) return ColumnBase::Display_Integer; break; @@ -77,83 +79,83 @@ QVariant CSMWorld::ResourceTable::headerData (int section, Qt::Orientation orien return QVariant(); } -bool CSMWorld::ResourceTable::setData ( const QModelIndex &index, const QVariant &value, - int role) +bool CSMWorld::ResourceTable::setData(const QModelIndex& index, const QVariant& value, int role) { return false; } -Qt::ItemFlags CSMWorld::ResourceTable::flags (const QModelIndex & index) const +Qt::ItemFlags CSMWorld::ResourceTable::flags(const QModelIndex& index) const { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } -QModelIndex CSMWorld::ResourceTable::index (int row, int column, const QModelIndex& parent) - const +QModelIndex CSMWorld::ResourceTable::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid()) return QModelIndex(); - if (row<0 || row>=mResources->getSize()) + if (row < 0 || row >= mResources->getSize()) return QModelIndex(); - if (column<0 || column>1) + if (column < 0 || column > 1) return QModelIndex(); - return createIndex (row, column); + return createIndex(row, column); } -QModelIndex CSMWorld::ResourceTable::parent (const QModelIndex& index) const +QModelIndex CSMWorld::ResourceTable::parent(const QModelIndex& index) const { return QModelIndex(); } -QModelIndex CSMWorld::ResourceTable::getModelIndex (const std::string& id, int column) const +QModelIndex CSMWorld::ResourceTable::getModelIndex(const std::string& id, int column) const { int row = mResources->searchId(id); if (row != -1) - return index (row, column); + return index(row, column); return QModelIndex(); } -int CSMWorld::ResourceTable::searchColumnIndex (Columns::ColumnId id) const +int CSMWorld::ResourceTable::searchColumnIndex(Columns::ColumnId id) const { - if (id==Columns::ColumnId_Id) + if (id == Columns::ColumnId_Id) return 0; - if (id==Columns::ColumnId_RecordType) + if (id == Columns::ColumnId_RecordType) return 1; return -1; } -int CSMWorld::ResourceTable::findColumnIndex (Columns::ColumnId id) const +int CSMWorld::ResourceTable::findColumnIndex(Columns::ColumnId id) const { - int index = searchColumnIndex (id); + int index = searchColumnIndex(id); - if (index==-1) - throw std::logic_error ("invalid column index"); + if (index == -1) + throw std::logic_error("invalid column index"); return index; } -std::pair CSMWorld::ResourceTable::view (int row) const +std::pair CSMWorld::ResourceTable::view(int row) const { - return std::make_pair (UniversalId::Type_None, ""); + return std::make_pair(UniversalId::Type_None, ""); } -bool CSMWorld::ResourceTable::isDeleted (const std::string& id) const +bool CSMWorld::ResourceTable::isDeleted(const std::string& id) const { return false; } -int CSMWorld::ResourceTable::getColumnId (int column) const +int CSMWorld::ResourceTable::getColumnId(int column) const { switch (column) { - case 0: return Columns::ColumnId_Id; - case 1: return Columns::ColumnId_RecordType; + case 0: + return Columns::ColumnId_Id; + case 1: + return Columns::ColumnId_RecordType; } return -1; diff --git a/apps/opencs/model/world/resourcetable.hpp b/apps/opencs/model/world/resourcetable.hpp index 8a3fab8a2..be3ff79f8 100644 --- a/apps/opencs/model/world/resourcetable.hpp +++ b/apps/opencs/model/world/resourcetable.hpp @@ -3,60 +3,68 @@ #include "idtablebase.hpp" +#include +#include + +#include +#include + +#include + namespace CSMWorld { class Resources; + class UniversalId; class ResourceTable : public IdTableBase { - const Resources *mResources; + const Resources* mResources; - public: + public: + /// \note The feature Feature_Constant will be added implicitly. + ResourceTable(const Resources* resources, unsigned int features = 0); - /// \note The feature Feature_Constant will be added implicitly. - ResourceTable (const Resources *resources, unsigned int features = 0); + ~ResourceTable() override = default; - virtual ~ResourceTable(); + int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount (const QModelIndex & parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount (const QModelIndex & parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; - bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex& index) const override; - Qt::ItemFlags flags (const QModelIndex & index) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; - QModelIndex parent (const QModelIndex& index) const override; + QModelIndex getModelIndex(const std::string& id, int column) const override; - QModelIndex getModelIndex (const std::string& id, int column) const override; + /// Return index of column with the given \a id. If no such column exists, -1 is + /// returned. + int searchColumnIndex(Columns::ColumnId id) const override; - /// Return index of column with the given \a id. If no such column exists, -1 is - /// returned. - int searchColumnIndex (Columns::ColumnId id) const override; + /// Return index of column with the given \a id. If no such column exists, an + /// exception is thrown. + int findColumnIndex(Columns::ColumnId id) const override; - /// Return index of column with the given \a id. If no such column exists, an - /// exception is thrown. - int findColumnIndex (Columns::ColumnId id) const override; + /// Return the UniversalId and the hint for viewing \a row. If viewing is not + /// supported by this table, return (UniversalId::Type_None, ""). + std::pair view(int row) const override; - /// Return the UniversalId and the hint for viewing \a row. If viewing is not - /// supported by this table, return (UniversalId::Type_None, ""). - std::pair view (int row) const override; + /// Is \a id flagged as deleted? + bool isDeleted(const std::string& id) const override; - /// Is \a id flagged as deleted? - bool isDeleted (const std::string& id) const override; + int getColumnId(int column) const override; - int getColumnId (int column) const override; - - /// Signal Qt that the data is about to change. - void beginReset(); - /// Signal Qt that the data has been changed. - void endReset(); + /// Signal Qt that the data is about to change. + void beginReset(); + /// Signal Qt that the data has been changed. + void endReset(); }; } diff --git a/apps/opencs/model/world/scope.cpp b/apps/opencs/model/world/scope.cpp index b026c34c3..175074e49 100644 --- a/apps/opencs/model/world/scope.cpp +++ b/apps/opencs/model/world/scope.cpp @@ -1,24 +1,44 @@ #include "scope.hpp" -#include +#include -#include +#include +#include -CSMWorld::Scope CSMWorld::getScopeFromId (const std::string& id) +namespace CSMWorld { - // get root namespace - std::string namespace_; + namespace + { + struct GetScope + { + Scope operator()(ESM::StringRefId v) const + { + std::string_view namespace_; - std::string::size_type i = id.find ("::"); + const std::string::size_type i = v.getValue().find("::"); - if (i!=std::string::npos) - namespace_ = Misc::StringUtils::lowerCase (id.substr (0, i)); + if (i != std::string::npos) + namespace_ = std::string_view(v.getValue()).substr(0, i); - if (namespace_=="project") - return Scope_Project; + if (Misc::StringUtils::ciEqual(namespace_, "project")) + return Scope_Project; - if (namespace_=="session") - return Scope_Session; + if (Misc::StringUtils::ciEqual(namespace_, "session")) + return Scope_Session; - return Scope_Content; + return Scope_Content; + } + + template + Scope operator()(const T& /*v*/) const + { + return Scope_Content; + } + }; + } +} + +CSMWorld::Scope CSMWorld::getScopeFromId(ESM::RefId id) +{ + return visit(GetScope{}, id); } diff --git a/apps/opencs/model/world/scope.hpp b/apps/opencs/model/world/scope.hpp index 3983d91f5..c679a78de 100644 --- a/apps/opencs/model/world/scope.hpp +++ b/apps/opencs/model/world/scope.hpp @@ -1,7 +1,10 @@ #ifndef CSM_WOLRD_SCOPE_H #define CSM_WOLRD_SCOPE_H -#include +namespace ESM +{ + class RefId; +} namespace CSMWorld { @@ -17,7 +20,7 @@ namespace CSMWorld Scope_Session = 4 }; - Scope getScopeFromId (const std::string& id); + Scope getScopeFromId(ESM::RefId id); } #endif diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index 344ae322e..ba70127ad 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -1,110 +1,120 @@ #include "scriptcontext.hpp" #include +#include +#include -#include +#include +#include +#include +#include -#include #include +#include #include +#include +#include +#include +#include #include "data.hpp" -CSMWorld::ScriptContext::ScriptContext (const Data& data) : mData (data), mIdsUpdated (false) {} +CSMWorld::ScriptContext::ScriptContext(const Data& data) + : mData(data) + , mIdsUpdated(false) +{ +} bool CSMWorld::ScriptContext::canDeclareLocals() const { return true; } -char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const +char CSMWorld::ScriptContext::getGlobalType(const std::string& name) const { - int index = mData.getGlobals().searchId (name); + const int index = mData.getGlobals().searchId(ESM::RefId::stringRefId(name)); - if (index!=-1) + if (index != -1) { - switch (mData.getGlobals().getRecord (index).get().mValue.getType()) + switch (mData.getGlobals().getRecord(index).get().mValue.getType()) { - case ESM::VT_Short: return 's'; - case ESM::VT_Long: return 'l'; - case ESM::VT_Float: return 'f'; + case ESM::VT_Short: + return 's'; + case ESM::VT_Long: + return 'l'; + case ESM::VT_Float: + return 'f'; - default: return ' '; + default: + return ' '; } } return ' '; } -std::pair CSMWorld::ScriptContext::getMemberType (const std::string& name, - const std::string& id) const +std::pair CSMWorld::ScriptContext::getMemberType(const std::string& name, const ESM::RefId& id) const { - std::string id2 = Misc::StringUtils::lowerCase (id); + ESM::RefId id2 = id; - int index = mData.getScripts().searchId (id2); + int index = mData.getScripts().searchId(id2); bool reference = false; - if (index==-1) + if (index == -1) { // ID is not a script ID. Search for a matching referenceable instead. - index = mData.getReferenceables().searchId (id2); + index = mData.getReferenceables().searchId(id2); - if (index!=-1) + if (index != -1) { // Referenceable found. - int columnIndex = mData.getReferenceables().findColumnIndex (Columns::ColumnId_Script); + int columnIndex = mData.getReferenceables().findColumnIndex(Columns::ColumnId_Script); - id2 = Misc::StringUtils::lowerCase (mData.getReferenceables(). - getData (index, columnIndex).toString().toUtf8().constData()); + id2 = ESM::RefId::stringRefId( + mData.getReferenceables().getData(index, columnIndex).toString().toUtf8().constData()); if (!id2.empty()) { // Referenceable has a script -> use it. - index = mData.getScripts().searchId (id2); + index = mData.getScripts().searchId(id2); reference = true; } } } - if (index==-1) - return std::make_pair (' ', false); + if (index == -1) + return std::make_pair(' ', false); - std::map::iterator iter = mLocals.find (id2); + auto iter = mLocals.find(id2); - if (iter==mLocals.end()) + if (iter == mLocals.end()) { Compiler::Locals locals; Compiler::NullErrorHandler errorHandler; - std::istringstream stream (mData.getScripts().getRecord (index).get().mScriptText); - Compiler::QuickFileParser parser (errorHandler, *this, locals); - Compiler::Scanner scanner (errorHandler, stream, getExtensions()); - scanner.scan (parser); + std::istringstream stream(mData.getScripts().getRecord(index).get().mScriptText); + Compiler::QuickFileParser parser(errorHandler, *this, locals); + Compiler::Scanner scanner(errorHandler, stream, getExtensions()); + scanner.scan(parser); - iter = mLocals.insert (std::make_pair (id2, locals)).first; + iter = mLocals.emplace(id2, std::move(locals)).first; } - return std::make_pair (iter->second.getType (Misc::StringUtils::lowerCase (name)), reference); + return std::make_pair(iter->second.getType(Misc::StringUtils::lowerCase(name)), reference); } -bool CSMWorld::ScriptContext::isId (const std::string& name) const +bool CSMWorld::ScriptContext::isId(const ESM::RefId& name) const { if (!mIdsUpdated) { mIds = mData.getIds(); - std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::lowerCaseInPlace); - std::sort (mIds.begin(), mIds.end()); + std::sort(mIds.begin(), mIds.end()); mIdsUpdated = true; } - return std::binary_search (mIds.begin(), mIds.end(), Misc::StringUtils::lowerCase (name)); -} - -bool CSMWorld::ScriptContext::isJournalId (const std::string& name) const -{ - return mData.getJournals().searchId (name)!=-1; + return std::binary_search(mIds.begin(), mIds.end(), name); } void CSMWorld::ScriptContext::invalidateIds() @@ -119,14 +129,13 @@ void CSMWorld::ScriptContext::clear() mLocals.clear(); } -bool CSMWorld::ScriptContext::clearLocals (const std::string& script) +bool CSMWorld::ScriptContext::clearLocals(const std::string& script) { - std::map::iterator iter = - mLocals.find (Misc::StringUtils::lowerCase (script)); + const auto iter = mLocals.find(script); - if (iter!=mLocals.end()) + if (iter != mLocals.end()) { - mLocals.erase (iter); + mLocals.erase(iter); mIdsUpdated = false; return true; } diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp index 8e1a5e57b..c2dbadfc6 100644 --- a/apps/opencs/model/world/scriptcontext.hpp +++ b/apps/opencs/model/world/scriptcontext.hpp @@ -1,12 +1,14 @@ #ifndef CSM_WORLD_SCRIPTCONTEXT_H #define CSM_WORLD_SCRIPTCONTEXT_H -#include -#include #include +#include +#include +#include #include #include +#include namespace CSMWorld { @@ -14,41 +16,36 @@ namespace CSMWorld class ScriptContext : public Compiler::Context { - const Data& mData; - mutable std::vector mIds; - mutable bool mIdsUpdated; - mutable std::map mLocals; + const Data& mData; + mutable std::vector mIds; + mutable bool mIdsUpdated; + mutable std::map> mLocals; - public: + public: + ScriptContext(const Data& data); - ScriptContext (const Data& data); + bool canDeclareLocals() const override; + ///< Is the compiler allowed to declare local variables? - bool canDeclareLocals() const override; - ///< Is the compiler allowed to declare local variables? + char getGlobalType(const std::string& name) const override; + ///< 'l: long, 's': short, 'f': float, ' ': does not exist. - char getGlobalType (const std::string& name) const override; - ///< 'l: long, 's': short, 'f': float, ' ': does not exist. + std::pair getMemberType(const std::string& name, const ESM::RefId& id) const override; + ///< Return type of member variable \a name in script \a id or in script of reference of + /// \a id + /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist. + /// second: true: script of reference - std::pair getMemberType (const std::string& name, - const std::string& id) const override; - ///< Return type of member variable \a name in script \a id or in script of reference of - /// \a id - /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist. - /// second: true: script of reference + bool isId(const ESM::RefId& name) const override; + ///< Does \a name match an ID, that can be referenced? - bool isId (const std::string& name) const override; - ///< Does \a name match an ID, that can be referenced? + void invalidateIds(); - bool isJournalId (const std::string& name) const override; - ///< Does \a name match a journal ID? + void clear(); + ///< Remove all cached data. - void invalidateIds(); - - void clear(); - ///< Remove all cached data. - - /// \return Were there any locals that needed clearing? - bool clearLocals (const std::string& script); + /// \return Were there any locals that needed clearing? + bool clearLocals(const std::string& script); }; } diff --git a/apps/opencs/model/world/subcellcollection.hpp b/apps/opencs/model/world/subcellcollection.hpp index a60929680..f7cb896ac 100644 --- a/apps/opencs/model/world/subcellcollection.hpp +++ b/apps/opencs/model/world/subcellcollection.hpp @@ -11,35 +11,30 @@ namespace ESM namespace CSMWorld { struct Cell; - template - class IdCollection; /// \brief Single type collection of top level records that are associated with cells - template > - class SubCellCollection : public NestedIdCollection + template + class SubCellCollection final : public NestedIdCollection { - const IdCollection& mCells; + const IdCollection& mCells; - void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted) override; + void loadRecord(ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted) override; - public: - - SubCellCollection (const IdCollection& cells); + public: + SubCellCollection(const IdCollection& cells); }; - template - void SubCellCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader, - bool& isDeleted) + template + void SubCellCollection::loadRecord(ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted) { - record.load (reader, isDeleted, mCells); + record.load(reader, isDeleted, mCells); } - template - SubCellCollection::SubCellCollection ( - const IdCollection& cells) - : mCells (cells) - {} + template + SubCellCollection::SubCellCollection(const IdCollection& cells) + : mCells(cells) + { + } } #endif diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index 1268a7389..1814e551c 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -1,25 +1,30 @@ #include "tablemimedata.hpp" +#include +#include +#include #include #include +#include -#include "universalid.hpp" #include "columnbase.hpp" +#include "universalid.hpp" -CSMWorld::TableMimeData::TableMimeData (UniversalId id, const CSMDoc::Document& document) : -mDocument(document) +CSMWorld::TableMimeData::TableMimeData(UniversalId id, const CSMDoc::Document& document) + : mDocument(document) { - mUniversalId.push_back (id); - mObjectsFormats << QString::fromUtf8 (("tabledata/" + id.getTypeName()).c_str()); + mUniversalId.push_back(id); + mObjectsFormats << QString::fromUtf8(("tabledata/" + id.getTypeName()).c_str()); } -CSMWorld::TableMimeData::TableMimeData (const std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) : - mUniversalId (id), mDocument(document) +CSMWorld::TableMimeData::TableMimeData(const std::vector& id, const CSMDoc::Document& document) + : mUniversalId(id) + , mDocument(document) { - for (std::vector::iterator it (mUniversalId.begin()); it != mUniversalId.end(); ++it) + for (std::vector::iterator it(mUniversalId.begin()); it != mUniversalId.end(); ++it) { - mObjectsFormats << QString::fromUtf8 (("tabledata/" + it->getTypeName()).c_str()); + mObjectsFormats << QString::fromUtf8(("tabledata/" + it->getTypeName()).c_str()); } } @@ -28,94 +33,73 @@ QStringList CSMWorld::TableMimeData::formats() const return mObjectsFormats; } -CSMWorld::TableMimeData::~TableMimeData() -{ -} - std::string CSMWorld::TableMimeData::getIcon() const { if (mUniversalId.empty()) { - qDebug()<<"TableMimeData object does not hold any records!"; //because throwing in the event loop tends to be problematic - throw std::runtime_error ("TableMimeData object does not hold any records!"); + qDebug() << "TableMimeData object does not hold any records!"; // because throwing in the event loop tends to be + // problematic + throw std::runtime_error("TableMimeData object does not hold any records!"); } std::string tmpIcon; bool firstIteration = true; - for (unsigned i = 0; i < mUniversalId.size(); ++i) + for (const auto& id : mUniversalId) { if (firstIteration) { firstIteration = false; - tmpIcon = mUniversalId[i].getIcon(); + tmpIcon = id.getIcon(); continue; } - if (tmpIcon != mUniversalId[i].getIcon()) + if (tmpIcon != id.getIcon()) { - return ":/multitype.png"; //icon stolen from gnome TODO: get new icon + return ":/multitype.png"; // icon stolen from gnome TODO: get new icon } - tmpIcon = mUniversalId[i].getIcon(); + tmpIcon = id.getIcon(); } - return mUniversalId.begin()->getIcon(); //All objects are of the same type; + return mUniversalId.begin()->getIcon(); // All objects are of the same type; } -std::vector< CSMWorld::UniversalId > CSMWorld::TableMimeData::getData() const +std::vector CSMWorld::TableMimeData::getData() const { return mUniversalId; } bool CSMWorld::TableMimeData::isReferencable(CSMWorld::ColumnBase::Display type) const { -return ( type == CSMWorld::ColumnBase::Display_Activator - || type == CSMWorld::ColumnBase::Display_Potion - || type == CSMWorld::ColumnBase::Display_Apparatus - || type == CSMWorld::ColumnBase::Display_Armor - || type == CSMWorld::ColumnBase::Display_Book - || type == CSMWorld::ColumnBase::Display_Clothing - || type == CSMWorld::ColumnBase::Display_Container - || type == CSMWorld::ColumnBase::Display_Creature - || type == CSMWorld::ColumnBase::Display_Door - || type == CSMWorld::ColumnBase::Display_Ingredient - || type == CSMWorld::ColumnBase::Display_CreatureLevelledList - || type == CSMWorld::ColumnBase::Display_ItemLevelledList - || type == CSMWorld::ColumnBase::Display_Light - || type == CSMWorld::ColumnBase::Display_Lockpick - || type == CSMWorld::ColumnBase::Display_Miscellaneous - || type == CSMWorld::ColumnBase::Display_Npc - || type == CSMWorld::ColumnBase::Display_Probe - || type == CSMWorld::ColumnBase::Display_Repair - || type == CSMWorld::ColumnBase::Display_Static - || type == CSMWorld::ColumnBase::Display_Weapon); + return (type == CSMWorld::ColumnBase::Display_Activator || type == CSMWorld::ColumnBase::Display_Potion + || type == CSMWorld::ColumnBase::Display_Apparatus || type == CSMWorld::ColumnBase::Display_Armor + || type == CSMWorld::ColumnBase::Display_Book || type == CSMWorld::ColumnBase::Display_Clothing + || type == CSMWorld::ColumnBase::Display_Container || type == CSMWorld::ColumnBase::Display_Creature + || type == CSMWorld::ColumnBase::Display_Door || type == CSMWorld::ColumnBase::Display_Ingredient + || type == CSMWorld::ColumnBase::Display_CreatureLevelledList + || type == CSMWorld::ColumnBase::Display_ItemLevelledList || type == CSMWorld::ColumnBase::Display_Light + || type == CSMWorld::ColumnBase::Display_Lockpick || type == CSMWorld::ColumnBase::Display_Miscellaneous + || type == CSMWorld::ColumnBase::Display_Npc || type == CSMWorld::ColumnBase::Display_Probe + || type == CSMWorld::ColumnBase::Display_Repair || type == CSMWorld::ColumnBase::Display_Static + || type == CSMWorld::ColumnBase::Display_Weapon); } bool CSMWorld::TableMimeData::isReferencable(CSMWorld::UniversalId::Type type) { - return ( type == CSMWorld::UniversalId::Type_Activator - || type == CSMWorld::UniversalId::Type_Potion - || type == CSMWorld::UniversalId::Type_Apparatus - || type == CSMWorld::UniversalId::Type_Armor - || type == CSMWorld::UniversalId::Type_Book - || type == CSMWorld::UniversalId::Type_Clothing - || type == CSMWorld::UniversalId::Type_Container - || type == CSMWorld::UniversalId::Type_Creature - || type == CSMWorld::UniversalId::Type_Door - || type == CSMWorld::UniversalId::Type_Ingredient - || type == CSMWorld::UniversalId::Type_CreatureLevelledList - || type == CSMWorld::UniversalId::Type_ItemLevelledList - || type == CSMWorld::UniversalId::Type_Light - || type == CSMWorld::UniversalId::Type_Lockpick - || type == CSMWorld::UniversalId::Type_Miscellaneous - || type == CSMWorld::UniversalId::Type_Npc - || type == CSMWorld::UniversalId::Type_Probe - || type == CSMWorld::UniversalId::Type_Repair - || type == CSMWorld::UniversalId::Type_Static - || type == CSMWorld::UniversalId::Type_Weapon); + return (type == CSMWorld::UniversalId::Type_Activator || type == CSMWorld::UniversalId::Type_Potion + || type == CSMWorld::UniversalId::Type_Apparatus || type == CSMWorld::UniversalId::Type_Armor + || type == CSMWorld::UniversalId::Type_Book || type == CSMWorld::UniversalId::Type_Clothing + || type == CSMWorld::UniversalId::Type_Container || type == CSMWorld::UniversalId::Type_Creature + || type == CSMWorld::UniversalId::Type_Door || type == CSMWorld::UniversalId::Type_Ingredient + || type == CSMWorld::UniversalId::Type_CreatureLevelledList + || type == CSMWorld::UniversalId::Type_ItemLevelledList || type == CSMWorld::UniversalId::Type_Light + || type == CSMWorld::UniversalId::Type_Lockpick || type == CSMWorld::UniversalId::Type_Miscellaneous + || type == CSMWorld::UniversalId::Type_Npc || type == CSMWorld::UniversalId::Type_Probe + || type == CSMWorld::UniversalId::Type_Repair || type == CSMWorld::UniversalId::Type_Static + || type == CSMWorld::UniversalId::Type_Weapon); } -bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const +bool CSMWorld::TableMimeData::holdsType(CSMWorld::UniversalId::Type type) const { bool referencable = (type == CSMWorld::UniversalId::Type_Referenceable); for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) @@ -126,8 +110,10 @@ bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const { return true; } - } else { - if (it->getType() == type) + } + else + { + if (it->getType() == type) { return true; } @@ -137,7 +123,7 @@ bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const return false; } -bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) const +bool CSMWorld::TableMimeData::holdsType(CSMWorld::ColumnBase::Display type) const { bool referencable = (type == CSMWorld::ColumnBase::Display_Referenceable); for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) @@ -148,8 +134,10 @@ bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) con { return true; } - } else { - if (it->getType() == convertEnums (type)) + } + else + { + if (it->getType() == convertEnums(type)) { return true; } @@ -159,7 +147,7 @@ bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) con return false; } -CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::UniversalId::Type type) const +CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching(CSMWorld::UniversalId::Type type) const { bool referencable = (type == CSMWorld::UniversalId::Type_Referenceable); for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) @@ -170,7 +158,8 @@ CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::Univers { return *it; } - } else + } + else { if (it->getType() == type) { @@ -179,10 +168,10 @@ CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::Univers } } - throw std::runtime_error ("TableMimeData object does not hold object of the sought type"); + throw std::runtime_error("TableMimeData object does not hold object of the sought type"); } -CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const +CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching(CSMWorld::ColumnBase::Display type) const { bool referencable = (type == CSMWorld::ColumnBase::Display_Referenceable); for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) @@ -193,18 +182,20 @@ CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnB { return *it; } - } else { - if (it->getType() == convertEnums (type)) + } + else + { + if (it->getType() == convertEnums(type)) { return *it; } } } - throw std::runtime_error ("TableMimeData object does not hold object of the sought type"); + throw std::runtime_error("TableMimeData object does not hold object of the sought type"); } -bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const +bool CSMWorld::TableMimeData::fromDocument(const CSMDoc::Document& document) const { return &document == &mDocument; } @@ -217,8 +208,7 @@ namespace CSMWorld::ColumnBase::Display mDisplayType; }; - const Mapping mapping[] = - { + const Mapping mapping[] = { { CSMWorld::UniversalId::Type_Race, CSMWorld::ColumnBase::Display_Race }, { CSMWorld::UniversalId::Type_Skill, CSMWorld::ColumnBase::Display_Skill }, { CSMWorld::UniversalId::Type_Class, CSMWorld::ColumnBase::Display_Class }, @@ -271,19 +261,19 @@ namespace }; } -CSMWorld::UniversalId::Type CSMWorld::TableMimeData::convertEnums (ColumnBase::Display type) +CSMWorld::UniversalId::Type CSMWorld::TableMimeData::convertEnums(ColumnBase::Display type) { - for (int i=0; mapping[i].mUniversalIdType!=CSMWorld::UniversalId::Type_None; ++i) - if (mapping[i].mDisplayType==type) + for (int i = 0; mapping[i].mUniversalIdType != CSMWorld::UniversalId::Type_None; ++i) + if (mapping[i].mDisplayType == type) return mapping[i].mUniversalIdType; return CSMWorld::UniversalId::Type_None; } -CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (UniversalId::Type type) +CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums(UniversalId::Type type) { - for (int i=0; mapping[i].mUniversalIdType!=CSMWorld::UniversalId::Type_None; ++i) - if (mapping[i].mUniversalIdType==type) + for (int i = 0; mapping[i].mUniversalIdType != CSMWorld::UniversalId::Type_None; ++i) + if (mapping[i].mUniversalIdType == type) return mapping[i].mDisplayType; return CSMWorld::ColumnBase::Display_None; diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 234524912..bcee8c0f1 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -1,67 +1,85 @@ #ifndef TABLEMIMEDATA_H #define TABLEMIMEDATA_H +#include #include -#include +#include #include +#include -#include "universalid.hpp" #include "columnbase.hpp" +#include "universalid.hpp" namespace CSMDoc { class Document; } +namespace CSVWorld +{ + class DragRecordTable; +} + namespace CSMWorld { -/// \brief Subclass of QmimeData, augmented to contain and transport UniversalIds. -/// -/// This class provides way to construct mimedata object holding the universalid copy -/// Universalid is used in the majority of the tables to store type, id, argument types. -/// This way universalid grants a way to retrieve record from the concrete table. -/// Please note, that tablemimedata object can hold multiple universalIds in the vector. + /// \brief Subclass of QmimeData, augmented to contain and transport UniversalIds. + /// + /// This class provides way to construct mimedata object holding the universalid copy + /// Universalid is used in the majority of the tables to store type, id, argument types. + /// This way universalid grants a way to retrieve record from the concrete table. + /// Please note, that tablemimedata object can hold multiple universalIds in the vector. class TableMimeData : public QMimeData { - std::vector mUniversalId; - QStringList mObjectsFormats; - const CSMDoc::Document& mDocument; - public: - TableMimeData(UniversalId id, const CSMDoc::Document& document); + std::vector mUniversalId; + QStringList mObjectsFormats; + const CSMDoc::Document& mDocument; + const CSVWorld::DragRecordTable* mTableOfDragStart; + QModelIndex mIndexAtDragStart; - TableMimeData(const std::vector& id, const CSMDoc::Document& document); + public: + TableMimeData(UniversalId id, const CSMDoc::Document& document); - ~TableMimeData(); + TableMimeData(const std::vector& id, const CSMDoc::Document& document); - QStringList formats() const override; + ~TableMimeData() override = default; - std::string getIcon() const; + QStringList formats() const override; - std::vector getData() const; + std::string getIcon() const; - bool holdsType(UniversalId::Type type) const; + std::vector getData() const; - bool holdsType(CSMWorld::ColumnBase::Display type) const; + bool holdsType(UniversalId::Type type) const; - bool fromDocument(const CSMDoc::Document& document) const; + bool holdsType(CSMWorld::ColumnBase::Display type) const; - UniversalId returnMatching(UniversalId::Type type) const; + bool fromDocument(const CSMDoc::Document& document) const; - const CSMDoc::Document* getDocumentPtr() const; + UniversalId returnMatching(UniversalId::Type type) const; - UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; + const CSMDoc::Document* getDocumentPtr() const; - static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); + UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; - static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); + void setIndexAtDragStart(const QModelIndex& index) { mIndexAtDragStart = index; } - static bool isReferencable(CSMWorld::UniversalId::Type type); - private: - bool isReferencable(CSMWorld::ColumnBase::Display type) const; + void setTableOfDragStart(const CSVWorld::DragRecordTable* const table) { mTableOfDragStart = table; } + const QModelIndex getIndexAtDragStart() const { return mIndexAtDragStart; } + + const CSVWorld::DragRecordTable* getTableOfDragStart() const { return mTableOfDragStart; } + + static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); + + static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); + + static bool isReferencable(CSMWorld::UniversalId::Type type); + + private: + bool isReferencable(CSMWorld::ColumnBase::Display type) const; }; } #endif // TABLEMIMEDATA_H diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 486f3770a..dec533b01 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -1,24 +1,29 @@ #include "universalid.hpp" -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include namespace { struct TypeData { - CSMWorld::UniversalId::Class mClass; - CSMWorld::UniversalId::Type mType; - const char *mName; - const char *mIcon; + CSMWorld::UniversalId::Class mClass; + CSMWorld::UniversalId::Type mType; + std::string_view mName; + std::string_view mIcon; }; - static const TypeData sNoArg[] = - { - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", ":./global-variable.png" }, + constexpr TypeData sNoArg[] = { + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", ":placeholder" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", + ":./global-variable.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":./gmst.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":./skill.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":./class.png" }, @@ -27,40 +32,62 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":./sound.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":./script.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":./region.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", ":./birthsign.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", + ":./birthsign.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":./spell.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", ":./dialogue-topics.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", ":./journal-topics.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", ":./dialogue-topic-infos.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", ":./journal-topic-infos.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", + ":./dialogue-topics.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", + ":./journal-topics.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", + ":./dialogue-topic-infos.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", + ":./journal-topic-infos.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":./cell.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments", ":./enchantment.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", ":./body-part.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", ":./object.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", ":./instance.png" }, - { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", ":./region-map.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments", + ":./enchantment.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", + ":./body-part.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", + ":./object.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", + ":./instance.png" }, + { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", + ":./region-map.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":./filter.png" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", ":./resources-mesh" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", + ":./resources-mesh" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":./resources-icon" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", ":./resources-music" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", ":resources-sound" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", ":./resources-texture" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", ":./resources-video" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", ":./debug-profile.png" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", + ":./resources-music" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", + ":resources-sound" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", + ":./resources-texture" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", + ":./resources-video" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", + ":./debug-profile.png" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", ":./sound-generator.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", ":./magic-effect.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", ":./land-heightmap.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Textures", ":./land-texture.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", ":./pathgrid.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", ":./start-script.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", ":./metadata.png" }, - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", + ":./sound-generator.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", + ":./magic-effect.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", + ":./land-heightmap.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Textures", + ":./land-texture.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", + ":./pathgrid.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", + ":./start-script.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", + ":./metadata.png" }, }; - static const TypeData sIdArg[] = - { - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", ":./global-variable.png" }, + constexpr TypeData sIdArg[] = { + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", + ":./global-variable.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./gmst.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":./skill.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":./class.png" }, @@ -72,168 +99,245 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":./dialogue-topics.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", ":./journal-topics.png" }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", ":./dialogue-topic-infos.png" }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", ":./journal-topic-infos.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", + ":./journal-topics.png" }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", + ":./dialogue-topic-infos.png" }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", + ":./journal-topic-infos.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":./object.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", + ":./activator.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":./apparatus.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", + ":./apparatus.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":./armor.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":./book.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":./clothing.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", ":./container.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", + ":./container.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":./creature.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":./ingredient.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", + ":./ingredient.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList, "Creature Levelled List", ":./levelled-creature.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, - "Item Levelled List", ":./levelled-item.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, "Item Levelled List", + ":./levelled-item.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":./lockpick.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, - "Miscellaneous", ":./miscellaneous.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, "Miscellaneous", + ":./miscellaneous.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":./npc.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":./probe.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":./repair.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", ":./instance.png" }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", + ":./instance.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":./scene.png" }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", ":./record-preview.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", ":./enchantment.png" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", + ":./record-preview.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", + ":./enchantment.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":./body-part.png" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":./resources-mesh"}, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":./resources-icon"}, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":./resources-mesh" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":./resources-icon" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":./resources-music" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", ":./resources-sound" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", ":./resources-texture" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", + ":./resources-sound" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", + ":./resources-texture" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":./resources-video" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", ":./debug-profile.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", ":./sound-generator.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", ":./magic-effect.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", + ":./debug-profile.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", + ":./sound-generator.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", + ":./magic-effect.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":./land-heightmap.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", ":./land-texture.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", + ":./land-texture.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":./pathgrid.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", ":./start-script.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", + ":./start-script.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":./metadata.png" }, - - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; - static const TypeData sIndexArg[] = + constexpr TypeData sIndexArg[] = { + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, + "Verification Results", ":./menu-verify.png" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", + ":./error-log.png" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", + ":./menu-search.png" }, + }; + + struct WriteToStream { - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", ":./menu-verify.png" }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", ":./error-log.png" }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", ":./menu-search.png" }, - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker + std::ostream& mStream; + + void operator()(std::monostate /*value*/) const {} + + template + void operator()(const T& value) const + { + mStream << ": " << value; + } }; + + struct GetTypeData + { + std::span operator()(std::monostate /*value*/) const { return sNoArg; } + + std::span operator()(int /*value*/) const { return sIndexArg; } + + template + std::span operator()(const T& /*value*/) const + { + return sIdArg; + } + }; + + std::string toString(CSMWorld::UniversalId::ArgumentType value) + { + switch (value) + { + case CSMWorld::UniversalId::ArgumentType_None: + return "None"; + case CSMWorld::UniversalId::ArgumentType_Id: + return "Id"; + case CSMWorld::UniversalId::ArgumentType_Index: + return "Index"; + case CSMWorld::UniversalId::ArgumentType_RefId: + return "RefId"; + } + + return std::to_string(value); + } } -CSMWorld::UniversalId::UniversalId (const std::string& universalId) -: mIndex(0) +CSMWorld::UniversalId::UniversalId(const std::string& universalId) + : mValue(std::monostate{}) { - std::string::size_type index = universalId.find (':'); + std::string::size_type index = universalId.find(':'); - if (index!=std::string::npos) + if (index != std::string::npos) { - std::string type = universalId.substr (0, index); + std::string type = universalId.substr(0, index); - for (int i=0; sIdArg[i].mName; ++i) - if (type==sIdArg[i].mName) + for (const TypeData& value : sIdArg) + if (type == value.mName) { - mArgumentType = ArgumentType_Id; - mType = sIdArg[i].mType; - mClass = sIdArg[i].mClass; - mId = universalId.substr (index+2); + mType = value.mType; + mClass = value.mClass; + mValue = universalId.substr(index + 2); return; } - for (int i=0; sIndexArg[i].mName; ++i) - if (type==sIndexArg[i].mName) + for (const TypeData& value : sIndexArg) + if (type == value.mName) { - mArgumentType = ArgumentType_Index; - mType = sIndexArg[i].mType; - mClass = sIndexArg[i].mClass; + mType = value.mType; + mClass = value.mClass; - std::istringstream stream (universalId.substr (index+2)); + std::istringstream stream(universalId.substr(index + 2)); - if (stream >> mIndex) + int index = 0; + if (stream >> index) + { + mValue = index; return; + } break; } } else { - for (int i=0; sNoArg[i].mName; ++i) - if (universalId==sNoArg[i].mName) + for (const TypeData& value : sIndexArg) + if (universalId == value.mName) { - mArgumentType = ArgumentType_None; - mType = sNoArg[i].mType; - mClass = sNoArg[i].mClass; + mType = value.mType; + mClass = value.mClass; return; } } - throw std::runtime_error ("invalid UniversalId: " + universalId); + throw std::runtime_error("invalid UniversalId: " + universalId); } -CSMWorld::UniversalId::UniversalId (Type type) : mArgumentType (ArgumentType_None), mType (type), mIndex (0) +CSMWorld::UniversalId::UniversalId(Type type) + : mType(type) + , mValue(std::monostate{}) { - for (int i=0; sNoArg[i].mName; ++i) - if (type==sNoArg[i].mType) + for (const TypeData& value : sNoArg) + if (type == value.mType) { - mClass = sNoArg[i].mClass; + mClass = value.mClass; return; } - for (int i=0; sIdArg[i].mName; ++i) - if (type==sIdArg[i].mType) + for (const TypeData& value : sIdArg) + if (type == value.mType) { - mArgumentType = ArgumentType_Id; - mClass = sIdArg[i].mClass; + mValue = std::string(); + mClass = value.mClass; return; } - for (int i=0; sIndexArg[i].mName; ++i) - if (type==sIndexArg[i].mType) + for (const TypeData& value : sIndexArg) + if (type == value.mType) { - mArgumentType = ArgumentType_Index; - mClass = sIndexArg[i].mClass; + mValue = int{}; + mClass = value.mClass; return; } - throw std::logic_error ("invalid argument-less UniversalId type"); + throw std::logic_error("invalid argument-less UniversalId type"); } -CSMWorld::UniversalId::UniversalId (Type type, const std::string& id) -: mArgumentType (ArgumentType_Id), mType (type), mId (id), mIndex (0) +CSMWorld::UniversalId::UniversalId(Type type, const std::string& id) + : mType(type) + , mValue(id) { - for (int i=0; sIdArg[i].mName; ++i) - if (type==sIdArg[i].mType) + for (const TypeData& value : sIdArg) + if (type == value.mType) { - mClass = sIdArg[i].mClass; + mClass = value.mClass; return; } - throw std::logic_error ("invalid ID argument UniversalId type"); + throw std::logic_error("invalid ID argument UniversalId type: " + std::to_string(type)); } -CSMWorld::UniversalId::UniversalId (Type type, int index) -: mArgumentType (ArgumentType_Index), mType (type), mIndex (index) +CSMWorld::UniversalId::UniversalId(Type type, ESM::RefId id) + : mType(type) + , mValue(id) { - for (int i=0; sIndexArg[i].mName; ++i) - if (type==sIndexArg[i].mType) + for (const TypeData& value : sIdArg) + if (type == value.mType) { - mClass = sIndexArg[i].mClass; + mClass = value.mClass; + return; + } + throw std::logic_error("invalid RefId argument UniversalId type: " + std::to_string(type)); +} + +CSMWorld::UniversalId::UniversalId(Type type, int index) + : mType(type) + , mValue(index) +{ + for (const TypeData& value : sIndexArg) + if (type == value.mType) + { + mClass = value.mClass; return; } - throw std::logic_error ("invalid index argument UniversalId type"); + throw std::logic_error("invalid index argument UniversalId type: " + std::to_string(type)); } CSMWorld::UniversalId::Class CSMWorld::UniversalId::getClass() const @@ -243,7 +347,7 @@ CSMWorld::UniversalId::Class CSMWorld::UniversalId::getClass() const CSMWorld::UniversalId::ArgumentType CSMWorld::UniversalId::getArgumentType() const { - return mArgumentType; + return static_cast(mValue.index()); } CSMWorld::UniversalId::Type CSMWorld::UniversalId::getType() const @@ -253,61 +357,37 @@ CSMWorld::UniversalId::Type CSMWorld::UniversalId::getType() const const std::string& CSMWorld::UniversalId::getId() const { - if (mArgumentType!=ArgumentType_Id) - throw std::logic_error ("invalid access to ID of non-ID UniversalId"); + if (const std::string* result = std::get_if(&mValue)) + return *result; - return mId; + throw std::logic_error("invalid access to ID of " + ::toString(getArgumentType()) + " UniversalId"); } int CSMWorld::UniversalId::getIndex() const { - if (mArgumentType!=ArgumentType_Index) - throw std::logic_error ("invalid access to index of non-index UniversalId"); + if (const int* result = std::get_if(&mValue)) + return *result; - return mIndex; + throw std::logic_error("invalid access to index of " + ::toString(getArgumentType()) + " UniversalId"); } -bool CSMWorld::UniversalId::isEqual (const UniversalId& universalId) const +ESM::RefId CSMWorld::UniversalId::getRefId() const { - if (mClass!=universalId.mClass || mArgumentType!=universalId.mArgumentType || mType!=universalId.mType) - return false; + if (const ESM::RefId* result = std::get_if(&mValue)) + return *result; - switch (mArgumentType) - { - case ArgumentType_Id: return mId==universalId.mId; - case ArgumentType_Index: return mIndex==universalId.mIndex; - - default: return true; - } -} - -bool CSMWorld::UniversalId::isLess (const UniversalId& universalId) const -{ - if (mTypeuniversalId.mType) - return false; - - switch (mArgumentType) - { - case ArgumentType_Id: return mId typeData = std::visit(GetTypeData{}, mValue); - for (int i=0; typeData[i].mName; ++i) - if (typeData[i].mType==mType) - return typeData[i].mName; + for (const TypeData& value : typeData) + if (value.mType == mType) + return std::string(value.mName); - throw std::logic_error ("failed to retrieve UniversalId type name"); + throw std::logic_error("failed to retrieve UniversalId type name"); } std::string CSMWorld::UniversalId::toString() const @@ -316,73 +396,66 @@ std::string CSMWorld::UniversalId::toString() const stream << getTypeName(); - switch (mArgumentType) - { - case ArgumentType_None: break; - case ArgumentType_Id: stream << ": " << mId; break; - case ArgumentType_Index: stream << ": " << mIndex; break; - } + std::visit(WriteToStream{ stream }, mValue); return stream.str(); } std::string CSMWorld::UniversalId::getIcon() const { - const TypeData *typeData = mArgumentType==ArgumentType_None ? sNoArg : - (mArgumentType==ArgumentType_Id ? sIdArg : sIndexArg); + const std::span typeData = std::visit(GetTypeData{}, mValue); - for (int i=0; typeData[i].mName; ++i) - if (typeData[i].mType==mType) - return typeData[i].mIcon ? typeData[i].mIcon : ":placeholder"; + for (const TypeData& value : typeData) + if (value.mType == mType) + return std::string(value.mIcon); - throw std::logic_error ("failed to retrieve UniversalId type icon"); + throw std::logic_error("failed to retrieve UniversalId type icon"); } std::vector CSMWorld::UniversalId::listReferenceableTypes() { std::vector list; - for (int i=0; sIdArg[i].mName; ++i) - if (sIdArg[i].mClass==Class_RefRecord) - list.push_back (sIdArg[i].mType); + for (const TypeData& value : sIdArg) + if (value.mClass == Class_RefRecord) + list.push_back(value.mType); return list; } -std::vector CSMWorld::UniversalId::listTypes (int classes) +std::vector CSMWorld::UniversalId::listTypes(int classes) { std::vector list; - for (int i=0; sNoArg[i].mName; ++i) - if (sNoArg[i].mClass & classes) - list.push_back (sNoArg[i].mType); + for (const TypeData& value : sNoArg) + if (value.mClass & classes) + list.push_back(value.mType); - for (int i=0; sIdArg[i].mName; ++i) - if (sIdArg[i].mClass & classes) - list.push_back (sIdArg[i].mType); + for (const TypeData& value : sIdArg) + if (value.mClass & classes) + list.push_back(value.mType); - for (int i=0; sIndexArg[i].mName; ++i) - if (sIndexArg[i].mClass & classes) - list.push_back (sIndexArg[i].mType); + for (const TypeData& value : sIndexArg) + if (value.mClass & classes) + list.push_back(value.mType); return list; } -CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) +CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType(Type type) { - for (int i=0; sIdArg[i].mType; ++i) - if (type==sIdArg[i].mType) + for (const TypeData& value : sIdArg) + if (type == value.mType) { - if (sIdArg[i].mClass==Class_RefRecord) + if (value.mClass == Class_RefRecord) return Type_Referenceables; - if (sIdArg[i].mClass==Class_SubRecord || sIdArg[i].mClass==Class_Record || - sIdArg[i].mClass==Class_Resource) + if (value.mClass == Class_SubRecord || value.mClass == Class_Record || value.mClass == Class_Resource) { - if (type==Type_Cell_Missing) + if (type == Type_Cell_Missing) return Type_Cells; - return static_cast (type-1); + return static_cast(type - 1); } break; @@ -391,22 +464,12 @@ CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) return Type_None; } -bool CSMWorld::operator== (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right) +bool CSMWorld::operator==(const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right) { - return left.isEqual (right); + return std::tie(left.mClass, left.mType, left.mValue) == std::tie(right.mClass, right.mType, right.mValue); } -bool CSMWorld::operator!= (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right) +bool CSMWorld::operator<(const UniversalId& left, const UniversalId& right) { - return !left.isEqual (right); -} - -bool CSMWorld::operator< (const UniversalId& left, const UniversalId& right) -{ - return left.isLess (right); -} - -std::ostream& CSMWorld::operator< (std::ostream& stream, const CSMWorld::UniversalId& universalId) -{ - return stream << universalId.toString(); + return std::tie(left.mClass, left.mType, left.mValue) < std::tie(right.mClass, right.mType, right.mValue); } diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index accd1b78d..2d3385bcb 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -2,207 +2,207 @@ #define CSM_WOLRD_UNIVERSALID_H #include -#include +#include #include #include +#include namespace CSMWorld { class UniversalId { - public: + public: + enum Class + { + Class_None = 0, + Class_Record = 1, + Class_RefRecord = 2, // referenceable record + Class_SubRecord = 4, + Class_RecordList = 8, + Class_Collection = 16, // multiple types of records combined + Class_Transient = 32, // not part of the world data or the project data + Class_NonRecord = 64, // record like data that is not part of the world + Class_Resource = 128, ///< \attention Resource IDs are unique only within the + /// respective collection + Class_ResourceList = 256 + }; - enum Class - { - Class_None = 0, - Class_Record = 1, - Class_RefRecord = 2, // referenceable record - Class_SubRecord = 4, - Class_RecordList = 8, - Class_Collection = 16, // multiple types of records combined - Class_Transient = 32, // not part of the world data or the project data - Class_NonRecord = 64, // record like data that is not part of the world - Class_Resource = 128, ///< \attention Resource IDs are unique only within the - /// respective collection - Class_ResourceList = 256 - }; + enum ArgumentType + { + ArgumentType_None, + ArgumentType_Id, + ArgumentType_Index, + ArgumentType_RefId, + }; - enum ArgumentType - { - ArgumentType_None, - ArgumentType_Id, - ArgumentType_Index - }; + /// \note A record list type must always be immediately followed by the matching + /// record type, if this type is of class SubRecord or Record. + enum Type + { + Type_None = 0, + Type_Globals, + Type_Global, + Type_VerificationResults, + Type_Gmsts, + Type_Gmst, + Type_Skills, + Type_Skill, + Type_Classes, + Type_Class, + Type_Factions, + Type_Faction, + Type_Races, + Type_Race, + Type_Sounds, + Type_Sound, + Type_Scripts, + Type_Script, + Type_Regions, + Type_Region, + Type_Birthsigns, + Type_Birthsign, + Type_Spells, + Type_Spell, + Type_Cells, + Type_Cell, + Type_Cell_Missing, // For cells that does not exist yet. + Type_Referenceables, + Type_Referenceable, + Type_Activator, + Type_Potion, + Type_Apparatus, + Type_Armor, + Type_Book, + Type_Clothing, + Type_Container, + Type_Creature, + Type_Door, + Type_Ingredient, + Type_CreatureLevelledList, + Type_ItemLevelledList, + Type_Light, + Type_Lockpick, + Type_Miscellaneous, + Type_Npc, + Type_Probe, + Type_Repair, + Type_Static, + Type_Weapon, + Type_References, + Type_Reference, + Type_RegionMap, + Type_Filters, + Type_Filter, + Type_Topics, + Type_Topic, + Type_Journals, + Type_Journal, + Type_TopicInfos, + Type_TopicInfo, + Type_JournalInfos, + Type_JournalInfo, + Type_Scene, + Type_Preview, + Type_LoadErrorLog, + Type_Enchantments, + Type_Enchantment, + Type_BodyParts, + Type_BodyPart, + Type_Meshes, + Type_Mesh, + Type_Icons, + Type_Icon, + Type_Musics, + Type_Music, + Type_SoundsRes, + Type_SoundRes, + Type_Textures, + Type_Texture, + Type_Videos, + Type_Video, + Type_DebugProfiles, + Type_DebugProfile, + Type_SoundGens, + Type_SoundGen, + Type_MagicEffects, + Type_MagicEffect, + Type_Lands, + Type_Land, + Type_LandTextures, + Type_LandTexture, + Type_Pathgrids, + Type_Pathgrid, + Type_StartScripts, + Type_StartScript, + Type_Search, + Type_MetaDatas, + Type_MetaData, + Type_RunLog + }; - /// \note A record list type must always be immediately followed by the matching - /// record type, if this type is of class SubRecord or Record. - enum Type - { - Type_None = 0, - Type_Globals, - Type_Global, - Type_VerificationResults, - Type_Gmsts, - Type_Gmst, - Type_Skills, - Type_Skill, - Type_Classes, - Type_Class, - Type_Factions, - Type_Faction, - Type_Races, - Type_Race, - Type_Sounds, - Type_Sound, - Type_Scripts, - Type_Script, - Type_Regions, - Type_Region, - Type_Birthsigns, - Type_Birthsign, - Type_Spells, - Type_Spell, - Type_Cells, - Type_Cell, - Type_Cell_Missing, //For cells that does not exist yet. - Type_Referenceables, - Type_Referenceable, - Type_Activator, - Type_Potion, - Type_Apparatus, - Type_Armor, - Type_Book, - Type_Clothing, - Type_Container, - Type_Creature, - Type_Door, - Type_Ingredient, - Type_CreatureLevelledList, - Type_ItemLevelledList, - Type_Light, - Type_Lockpick, - Type_Miscellaneous, - Type_Npc, - Type_Probe, - Type_Repair, - Type_Static, - Type_Weapon, - Type_References, - Type_Reference, - Type_RegionMap, - Type_Filters, - Type_Filter, - Type_Topics, - Type_Topic, - Type_Journals, - Type_Journal, - Type_TopicInfos, - Type_TopicInfo, - Type_JournalInfos, - Type_JournalInfo, - Type_Scene, - Type_Preview, - Type_LoadErrorLog, - Type_Enchantments, - Type_Enchantment, - Type_BodyParts, - Type_BodyPart, - Type_Meshes, - Type_Mesh, - Type_Icons, - Type_Icon, - Type_Musics, - Type_Music, - Type_SoundsRes, - Type_SoundRes, - Type_Textures, - Type_Texture, - Type_Videos, - Type_Video, - Type_DebugProfiles, - Type_DebugProfile, - Type_SoundGens, - Type_SoundGen, - Type_MagicEffects, - Type_MagicEffect, - Type_Lands, - Type_Land, - Type_LandTextures, - Type_LandTexture, - Type_Pathgrids, - Type_Pathgrid, - Type_StartScripts, - Type_StartScript, - Type_Search, - Type_MetaDatas, - Type_MetaData, - Type_RunLog - }; + enum + { + NumberOfTypes = Type_RunLog + 1 + }; - enum { NumberOfTypes = Type_RunLog+1 }; + UniversalId(const std::string& universalId); - private: + UniversalId(Type type = Type_None); - Class mClass; - ArgumentType mArgumentType; - Type mType; - std::string mId; - int mIndex; + UniversalId(Type type, const std::string& id); + ///< Using a type for a non-ID-argument UniversalId will throw an exception. - public: + UniversalId(Type type, ESM::RefId id); - UniversalId (const std::string& universalId); + UniversalId(Type type, int index); + ///< Using a type for a non-index-argument UniversalId will throw an exception. - UniversalId (Type type = Type_None); + Class getClass() const; - UniversalId (Type type, const std::string& id); - ///< Using a type for a non-ID-argument UniversalId will throw an exception. + ArgumentType getArgumentType() const; - UniversalId (Type type, int index); - ///< Using a type for a non-index-argument UniversalId will throw an exception. + Type getType() const; - Class getClass() const; + const std::string& getId() const; + ///< Calling this function for a non-ID type will throw an exception. - ArgumentType getArgumentType() const; + int getIndex() const; + ///< Calling this function for a non-index type will throw an exception. - Type getType() const; + ESM::RefId getRefId() const; - const std::string& getId() const; - ///< Calling this function for a non-ID type will throw an exception. + std::string getTypeName() const; - int getIndex() const; - ///< Calling this function for a non-index type will throw an exception. + std::string toString() const; - bool isEqual (const UniversalId& universalId) const; + std::string getIcon() const; + ///< Will return an empty string, if no icon is available. - bool isLess (const UniversalId& universalId) const; + static std::vector listReferenceableTypes(); - std::string getTypeName() const; + static std::vector listTypes(int classes); - std::string toString() const; + /// If \a type is a SubRecord, RefRecord or Record type return the type of the table + /// that contains records of type \a type. + /// Otherwise return Type_None. + static Type getParentType(Type type); - std::string getIcon() const; - ///< Will return an empty string, if no icon is available. + private: + Class mClass; + Type mType; + std::variant mValue; - static std::vector listReferenceableTypes(); + friend bool operator==(const UniversalId& left, const UniversalId& right); - static std::vector listTypes (int classes); - - /// If \a type is a SubRecord, RefRecord or Record type return the type of the table - /// that contains records of type \a type. - /// Otherwise return Type_None. - static Type getParentType (Type type); + friend bool operator<(const UniversalId& left, const UniversalId& right); }; - bool operator== (const UniversalId& left, const UniversalId& right); - bool operator!= (const UniversalId& left, const UniversalId& right); + bool operator==(const UniversalId& left, const UniversalId& right); - bool operator< (const UniversalId& left, const UniversalId& right); - - std::ostream& operator< (std::ostream& stream, const UniversalId& universalId); + bool operator<(const UniversalId& left, const UniversalId& right); } -Q_DECLARE_METATYPE (CSMWorld::UniversalId) +Q_DECLARE_METATYPE(CSMWorld::UniversalId) #endif diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 509e656c3..d4cfdc6d3 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -1,47 +1,52 @@ #include "adjusterwidget.hpp" -#include - -#include - #include #include #include -CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) - : QWidget (parent), mValid (false), mAction (ContentAction_Undefined) +#include +#include +#include + +#include +#include + +CSVDoc::AdjusterWidget::AdjusterWidget(QWidget* parent) + : QWidget(parent) + , mValid(false) + , mAction(ContentAction_Undefined) { - QHBoxLayout *layout = new QHBoxLayout (this); + QHBoxLayout* layout = new QHBoxLayout(this); - mIcon = new QLabel (this); + mIcon = new QLabel(this); - layout->addWidget (mIcon, 0); + layout->addWidget(mIcon, 0); - mMessage = new QLabel (this); - mMessage->setWordWrap (true); - mMessage->setSizePolicy (QSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum)); + mMessage = new QLabel(this); + mMessage->setWordWrap(true); + mMessage->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); - layout->addWidget (mMessage, 1); + layout->addWidget(mMessage, 1); - setName ("", false); + setName("", false); - setLayout (layout); + setLayout(layout); } -void CSVDoc::AdjusterWidget::setAction (ContentAction action) +void CSVDoc::AdjusterWidget::setAction(ContentAction action) { mAction = action; } -void CSVDoc::AdjusterWidget::setLocalData (const boost::filesystem::path& localData) +void CSVDoc::AdjusterWidget::setLocalData(const std::filesystem::path& localData) { mLocalData = localData; } -boost::filesystem::path CSVDoc::AdjusterWidget::getPath() const +std::filesystem::path CSVDoc::AdjusterWidget::getPath() const { if (!mValid) - throw std::logic_error ("invalid content file path"); + throw std::logic_error("invalid content file path"); return mResultPath; } @@ -51,12 +56,12 @@ bool CSVDoc::AdjusterWidget::isValid() const return mValid; } -void CSVDoc::AdjusterWidget::setFilenameCheck (bool doCheck) +void CSVDoc::AdjusterWidget::setFilenameCheck(bool doCheck) { mDoFilenameCheck = doCheck; } -void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) +void CSVDoc::AdjusterWidget::setName(const QString& name, bool addon) { QString message; @@ -69,37 +74,36 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) } else { - boost::filesystem::path path (name.toUtf8().data()); + auto path = Files::pathFromQString(name); - std::string extension = Misc::StringUtils::lowerCase(path.extension().string()); + const auto extension = Misc::StringUtils::lowerCase(path.extension().u8string()); - bool isLegacyPath = (extension == ".esm" || - extension == ".esp"); + bool isLegacyPath = (extension == u8".esm" || extension == u8".esp"); - bool isFilePathChanged = (path.parent_path().string() != mLocalData.string()); + bool isFilePathChanged = (path.parent_path() != mLocalData); if (isLegacyPath) - path.replace_extension (addon ? ".omwaddon" : ".omwgame"); + path.replace_extension(addon ? ".omwaddon" : ".omwgame"); - //if the file came from data-local and is not a legacy file to be converted, - //don't worry about doing a file check. + // if the file came from data-local and is not a legacy file to be converted, + // don't worry about doing a file check. if (!isFilePathChanged && !isLegacyPath) { // path already points to the local data directory - message = QString::fromUtf8 (("Will be saved as: " + path.string()).c_str()); + message = "Will be saved as: " + Files::pathToQString(path); mResultPath = path; } - //in all other cases, ensure the path points to data-local and do an existing file check + // in all other cases, ensure the path points to data-local and do an existing file check else { // path points somewhere else or is a leaf name. if (isFilePathChanged) path = mLocalData / path.filename(); - message = QString::fromUtf8 (("Will be saved as: " + path.string()).c_str()); + message = "Will be saved as: " + Files::pathToQString(path); mResultPath = path; - if (boost::filesystem::exists (path)) + if (std::filesystem::exists(path)) { /// \todo add an user setting to make this an error. message += "

A file with the same name already exists. If you continue, it will be overwritten."; @@ -108,10 +112,12 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) } } - mMessage->setText (message); - mIcon->setPixmap (style()->standardIcon ( - mValid ? (warning ? QStyle::SP_MessageBoxWarning : QStyle::SP_MessageBoxInformation) : QStyle::SP_MessageBoxCritical). - pixmap (QSize (16, 16))); + mMessage->setText(message); + mIcon->setPixmap( + style() + ->standardIcon(mValid ? (warning ? QStyle::SP_MessageBoxWarning : QStyle::SP_MessageBoxInformation) + : QStyle::SP_MessageBoxCritical) + .pixmap(QSize(16, 16))); - emit stateChanged (mValid); + emit stateChanged(mValid); } diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp index cec9ca229..53192d841 100644 --- a/apps/opencs/view/doc/adjusterwidget.hpp +++ b/apps/opencs/view/doc/adjusterwidget.hpp @@ -1,10 +1,10 @@ #ifndef CSV_DOC_ADJUSTERWIDGET_H #define CSV_DOC_ADJUSTERWIDGET_H -#include - #include +#include + class QLabel; namespace CSVDoc @@ -18,38 +18,36 @@ namespace CSVDoc class AdjusterWidget : public QWidget { - Q_OBJECT + Q_OBJECT - public: + public: + std::filesystem::path mLocalData; + QLabel* mMessage; + QLabel* mIcon; + bool mValid; + std::filesystem::path mResultPath; + ContentAction mAction; + bool mDoFilenameCheck; - boost::filesystem::path mLocalData; - QLabel *mMessage; - QLabel *mIcon; - bool mValid; - boost::filesystem::path mResultPath; - ContentAction mAction; - bool mDoFilenameCheck; + public: + AdjusterWidget(QWidget* parent = nullptr); - public: + void setLocalData(const std::filesystem::path& localData); + void setAction(ContentAction action); - AdjusterWidget (QWidget *parent = nullptr); + void setFilenameCheck(bool doCheck); + bool isValid() const; - void setLocalData (const boost::filesystem::path& localData); - void setAction (ContentAction action); + std::filesystem::path getPath() const; + ///< This function must not be called if there is no valid path. - void setFilenameCheck (bool doCheck); - bool isValid() const; + public slots: - boost::filesystem::path getPath() const; - ///< This function must not be called if there is no valid path. + void setName(const QString& name, bool addon); - public slots: + signals: - void setName (const QString& name, bool addon); - - signals: - - void stateChanged (bool valid); + void stateChanged(bool valid); }; } diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 69490edca..1b502c9d6 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -1,39 +1,45 @@ #include "filedialog.hpp" -#include -#include #include -#include -#include -#include -#include #include -#include -#include -#include "components/contentselector/model/esmfile.hpp" -#include "components/contentselector/view/contentselector.hpp" +#include +#include +#include +#include + +#include -#include "filewidget.hpp" #include "adjusterwidget.hpp" +#include "filewidget.hpp" -CSVDoc::FileDialog::FileDialog(QWidget *parent) : - QDialog(parent), mSelector (nullptr), mAction(ContentAction_Undefined), mFileWidget (nullptr), mAdjusterWidget (nullptr), mDialogBuilt(false) +CSVDoc::FileDialog::FileDialog(QWidget* parent) + : QDialog(parent) + , mSelector(nullptr) + , mAction(ContentAction_Undefined) + , mFileWidget(nullptr) + , mAdjusterWidget(nullptr) + , mDialogBuilt(false) { - ui.setupUi (this); + ui.setupUi(this); resize(400, 400); - setObjectName ("FileDialog"); - mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); - mAdjusterWidget = new AdjusterWidget (this); + setObjectName("FileDialog"); + mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/false); + mAdjusterWidget = new AdjusterWidget(this); } -void CSVDoc::FileDialog::addFiles(const QString &path) +void CSVDoc::FileDialog::addFiles(const std::vector& dataDirs) { - mSelector->addFiles(path); + for (auto iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) + { + QString path = Files::pathToQString(*iter); + mSelector->addFiles(path); + } + mSelector->sortFiles(); } -void CSVDoc::FileDialog::setEncoding(const QString &encoding) +void CSVDoc::FileDialog::setEncoding(const QString& encoding) { mSelector->setEncoding(encoding); } @@ -47,46 +53,46 @@ QStringList CSVDoc::FileDialog::selectedFilePaths() { QStringList filePaths; - for (ContentSelectorModel::EsmFile *file : mSelector->selectedFiles() ) + for (ContentSelectorModel::EsmFile* file : mSelector->selectedFiles()) filePaths.append(file->filePath()); return filePaths; } -void CSVDoc::FileDialog::setLocalData (const boost::filesystem::path& localData) +void CSVDoc::FileDialog::setLocalData(const std::filesystem::path& localData) { - mAdjusterWidget->setLocalData (localData); + mAdjusterWidget->setLocalData(localData); } -void CSVDoc::FileDialog::showDialog (ContentAction action) +void CSVDoc::FileDialog::showDialog(ContentAction action) { mAction = action; - ui.projectGroupBoxLayout->insertWidget (0, mAdjusterWidget); + ui.projectGroupBoxLayout->insertWidget(0, mAdjusterWidget); switch (mAction) { - case ContentAction_New: - buildNewFileView(); - break; + case ContentAction_New: + buildNewFileView(); + break; - case ContentAction_Edit: - buildOpenFileView(); - break; + case ContentAction_Edit: + buildOpenFileView(); + break; - default: - break; + default: + break; } - mAdjusterWidget->setFilenameCheck (mAction == ContentAction_New); + mAdjusterWidget->setFilenameCheck(mAction == ContentAction_New); - if(!mDialogBuilt) + if (!mDialogBuilt) { - //connections common to both dialog view flavors - connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), - this, SLOT (slotUpdateAcceptButton (int))); + // connections common to both dialog view flavors + connect(mSelector, &ContentSelectorView::ContentSelector::signalCurrentGamefileIndexChanged, this, + qOverload(&FileDialog::slotUpdateAcceptButton)); - connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); + connect(ui.projectButtonBox, &QDialogButtonBox::rejected, this, &FileDialog::slotRejected); mDialogBuilt = true; } @@ -99,47 +105,47 @@ void CSVDoc::FileDialog::buildNewFileView() { setWindowTitle(tr("Create a new addon")); - QPushButton* createButton = ui.projectButtonBox->button (QDialogButtonBox::Ok); - createButton->setText ("Create"); - createButton->setEnabled (false); + QPushButton* createButton = ui.projectButtonBox->button(QDialogButtonBox::Ok); + createButton->setText("Create"); + createButton->setEnabled(false); - if(!mFileWidget) + if (!mFileWidget) { - mFileWidget = new FileWidget (this); + mFileWidget = new FileWidget(this); - mFileWidget->setType (true); + mFileWidget->setType(true); mFileWidget->extensionLabelIsVisible(true); - connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), - mAdjusterWidget, SLOT (setName (const QString&, bool))); + connect(mFileWidget, &FileWidget::nameChanged, mAdjusterWidget, &AdjusterWidget::setName); - connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), - this, SLOT (slotUpdateAcceptButton(const QString &, bool))); + connect(mFileWidget, &FileWidget::nameChanged, this, + qOverload(&FileDialog::slotUpdateAcceptButton)); } - ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); + ui.projectGroupBoxLayout->insertWidget(0, mFileWidget); - connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); + connect(ui.projectButtonBox, &QDialogButtonBox::accepted, this, &FileDialog::slotNewFile); } void CSVDoc::FileDialog::buildOpenFileView() { setWindowTitle(tr("Open")); - ui.projectGroupBox->setTitle (QString("")); - ui.projectButtonBox->button(QDialogButtonBox::Ok)->setText ("Open"); - if(mSelector->isGamefileSelected()) - ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (true); + ui.projectGroupBox->setTitle(QString("")); + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setText("Open"); + if (mSelector->isGamefileSelected()) + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled(true); else - ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - if(!mDialogBuilt) + if (!mDialogBuilt) { - connect (mSelector, SIGNAL (signalAddonDataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (slotAddonDataChanged(const QModelIndex&, const QModelIndex&))); + connect(mSelector, &ContentSelectorView::ContentSelector::signalAddonDataChanged, this, + &FileDialog::slotAddonDataChanged); } - connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); + connect(ui.projectButtonBox, &QDialogButtonBox::accepted, this, &FileDialog::slotOpenFile); } -void CSVDoc::FileDialog::slotAddonDataChanged(const QModelIndex &topleft, const QModelIndex &bottomright) +void CSVDoc::FileDialog::slotAddonDataChanged(const QModelIndex& topleft, const QModelIndex& bottomright) { slotUpdateAcceptButton(0); } @@ -151,26 +157,26 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(int) if (mFileWidget && mAction == ContentAction_New) name = mFileWidget->getName(); - slotUpdateAcceptButton (name, true); + slotUpdateAcceptButton(name, true); } -void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) +void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString& name, bool) { bool success = !mSelector->selectedFiles().empty(); bool isNew = (mAction == ContentAction_New); if (isNew) - success = success && !(name.isEmpty()); + success = !name.isEmpty(); else if (success) { - ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); - mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); + ContentSelectorModel::EsmFile* file = mSelector->selectedFiles().back(); + mAdjusterWidget->setName(file->filePath(), !file->isGameFile()); } else - mAdjusterWidget->setName ("", true); + mAdjusterWidget->setName("", true); - ui.projectButtonBox->button (QDialogButtonBox::Ok)->setEnabled (success); + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled(success); } QString CSVDoc::FileDialog::filename() const @@ -184,9 +190,9 @@ QString CSVDoc::FileDialog::filename() const void CSVDoc::FileDialog::slotRejected() { emit rejected(); - disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); - disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); - if(mFileWidget) + disconnect(ui.projectButtonBox, &QDialogButtonBox::accepted, this, &FileDialog::slotNewFile); + disconnect(ui.projectButtonBox, &QDialogButtonBox::accepted, this, &FileDialog::slotOpenFile); + if (mFileWidget) { delete mFileWidget; mFileWidget = nullptr; @@ -196,23 +202,23 @@ void CSVDoc::FileDialog::slotRejected() void CSVDoc::FileDialog::slotNewFile() { - emit signalCreateNewFile (mAdjusterWidget->getPath()); - if(mFileWidget) + emit signalCreateNewFile(mAdjusterWidget->getPath()); + if (mFileWidget) { delete mFileWidget; mFileWidget = nullptr; } - disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); + disconnect(ui.projectButtonBox, &QDialogButtonBox::accepted, this, &FileDialog::slotNewFile); close(); } void CSVDoc::FileDialog::slotOpenFile() { - ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); + ContentSelectorModel::EsmFile* file = mSelector->selectedFiles().back(); - mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); + mAdjusterWidget->setName(file->filePath(), !file->isGameFile()); - emit signalOpenFiles (mAdjusterWidget->getPath()); - disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); + emit signalOpenFiles(mAdjusterWidget->getPath()); + disconnect(ui.projectButtonBox, &QDialogButtonBox::accepted, this, &FileDialog::slotOpenFile); close(); } diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 6c48fa78b..d660f22f7 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -2,22 +2,25 @@ #define FILEDIALOG_HPP #include -#include #ifndef Q_MOC_RUN -#include #include "adjusterwidget.hpp" -#ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED -#define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED -Q_DECLARE_METATYPE (boost::filesystem::path) +#ifndef CS_QT_STD_FILESYSTEM_PATH_DECLARED +#define CS_QT_STD_FILESYSTEM_PATH_DECLARED +Q_DECLARE_METATYPE(std::filesystem::path) #endif #endif #include "ui_filedialog.h" +#include +#include + +class QModelIndex; + namespace ContentSelectorView { class ContentSelector; @@ -32,46 +35,43 @@ namespace CSVDoc Q_OBJECT private: - - ContentSelectorView::ContentSelector *mSelector; + ContentSelectorView::ContentSelector* mSelector; Ui::FileDialog ui; ContentAction mAction; - FileWidget *mFileWidget; - AdjusterWidget *mAdjusterWidget; + FileWidget* mFileWidget; + AdjusterWidget* mAdjusterWidget; bool mDialogBuilt; public: + explicit FileDialog(QWidget* parent = nullptr); + void showDialog(ContentAction action); - explicit FileDialog(QWidget *parent = nullptr); - void showDialog (ContentAction action); - - void addFiles (const QString &path); - void setEncoding (const QString &encoding); - void clearFiles (); + void addFiles(const std::vector& dataDirs); + void setEncoding(const QString& encoding); + void clearFiles(); QString filename() const; QStringList selectedFilePaths(); - void setLocalData (const boost::filesystem::path& localData); + void setLocalData(const std::filesystem::path& localData); private: - void buildNewFileView(); void buildOpenFileView(); signals: - void signalOpenFiles (const boost::filesystem::path &path); - void signalCreateNewFile (const boost::filesystem::path &path); + void signalOpenFiles(const std::filesystem::path& path); + void signalCreateNewFile(const std::filesystem::path& path); - void signalUpdateAcceptButton (bool, int); + void signalUpdateAcceptButton(bool, int); private slots: void slotNewFile(); void slotOpenFile(); - void slotUpdateAcceptButton (int); - void slotUpdateAcceptButton (const QString &, bool); + void slotUpdateAcceptButton(int); + void slotUpdateAcceptButton(const QString&, bool); void slotRejected(); void slotAddonDataChanged(const QModelIndex& topleft, const QModelIndex& bottomright); }; diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index 9e9acdfbe..26cdef011 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -1,38 +1,38 @@ #include "filewidget.hpp" #include -#include #include -#include -#include +#include QString CSVDoc::FileWidget::getExtension() const { return mAddon ? ".omwaddon" : ".omwgame"; } -CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (false) +CSVDoc::FileWidget::FileWidget(QWidget* parent) + : QWidget(parent) + , mAddon(false) { - QHBoxLayout *layout = new QHBoxLayout (this); + QHBoxLayout* layout = new QHBoxLayout(this); - mInput = new QLineEdit (this); + mInput = new QLineEdit(this); - layout->addWidget (mInput, 1); + layout->addWidget(mInput, 1); - mType = new QLabel (this); + mType = new QLabel(this); - layout ->addWidget (mType); + layout->addWidget(mType); - connect (mInput, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + connect(mInput, &QLineEdit::textChanged, this, &FileWidget::textChanged); - setLayout (layout); + setLayout(layout); } -void CSVDoc::FileWidget::setType (bool addon) +void CSVDoc::FileWidget::setType(bool addon) { mAddon = addon; - mType->setText (getExtension()); + mType->setText(getExtension()); } QString CSVDoc::FileWidget::getName() const @@ -45,9 +45,9 @@ QString CSVDoc::FileWidget::getName() const return text + getExtension(); } -void CSVDoc::FileWidget::textChanged (const QString& text) +void CSVDoc::FileWidget::textChanged(const QString& text) { - emit nameChanged (getName(), mAddon); + emit nameChanged(getName(), mAddon); } void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) @@ -55,10 +55,10 @@ void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) mType->setVisible(visible); } -void CSVDoc::FileWidget::setName (const std::string& text) +void CSVDoc::FileWidget::setName(const std::string& text) { - QString text2 = QString::fromUtf8 (text.c_str()); + QString text2 = QString::fromUtf8(text.c_str()); - mInput->setText (text2); - textChanged (text2); + mInput->setText(text2); + textChanged(text2); } diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp index 626b8d77d..1d90afd36 100644 --- a/apps/opencs/view/doc/filewidget.hpp +++ b/apps/opencs/view/doc/filewidget.hpp @@ -1,45 +1,44 @@ #ifndef CSV_DOC_FILEWIDGET_H #define CSV_DOC_FILEWIDGET_H +#include #include #include class QLabel; -class QString; class QLineEdit; namespace CSVDoc { class FileWidget : public QWidget { - Q_OBJECT + Q_OBJECT - bool mAddon; - QLineEdit *mInput; - QLabel *mType; + bool mAddon; + QLineEdit* mInput; + QLabel* mType; - QString getExtension() const; + QString getExtension() const; - public: + public: + FileWidget(QWidget* parent = nullptr); - FileWidget (QWidget *parent = nullptr); + void setType(bool addon); - void setType (bool addon); + QString getName() const; - QString getName() const; + void extensionLabelIsVisible(bool visible); - void extensionLabelIsVisible(bool visible); + void setName(const std::string& text); - void setName (const std::string& text); + private slots: - private slots: + void textChanged(const QString& text); - void textChanged (const QString& text); + signals: - signals: - - void nameChanged (const QString& file, bool addon); + void nameChanged(const QString& file, bool addon); }; } diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.cpp b/apps/opencs/view/doc/globaldebugprofilemenu.cpp index c898b819c..87b148c5e 100644 --- a/apps/opencs/view/doc/globaldebugprofilemenu.cpp +++ b/apps/opencs/view/doc/globaldebugprofilemenu.cpp @@ -1,13 +1,20 @@ #include "globaldebugprofilemenu.hpp" -#include #include +#include +#include #include +#include +#include + +#include #include "../../model/world/idtable.hpp" #include "../../model/world/record.hpp" +class QWidget; + void CSVDoc::GlobalDebugProfileMenu::rebuild() { clear(); @@ -15,78 +22,72 @@ void CSVDoc::GlobalDebugProfileMenu::rebuild() delete mActions; mActions = nullptr; - int idColumn = mDebugProfiles->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - int stateColumn = mDebugProfiles->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - int globalColumn = mDebugProfiles->findColumnIndex ( - CSMWorld::Columns::ColumnId_GlobalProfile); + int idColumn = mDebugProfiles->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + int stateColumn = mDebugProfiles->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + int globalColumn = mDebugProfiles->findColumnIndex(CSMWorld::Columns::ColumnId_GlobalProfile); int size = mDebugProfiles->rowCount(); std::vector ids; - for (int i=0; idata (mDebugProfiles->index (i, stateColumn)).toInt(); + int state = mDebugProfiles->data(mDebugProfiles->index(i, stateColumn)).toInt(); - bool global = mDebugProfiles->data (mDebugProfiles->index (i, globalColumn)).toInt(); + bool global = mDebugProfiles->data(mDebugProfiles->index(i, globalColumn)).toInt(); - if (state!=CSMWorld::RecordBase::State_Deleted && global) - ids.push_back ( - mDebugProfiles->data (mDebugProfiles->index (i, idColumn)).toString()); + if (state != CSMWorld::RecordBase::State_Deleted && global) + ids.push_back(mDebugProfiles->data(mDebugProfiles->index(i, idColumn)).toString()); } - mActions = new QActionGroup (this); - connect (mActions, SIGNAL (triggered (QAction *)), this, SLOT (actionTriggered (QAction *))); + mActions = new QActionGroup(this); + connect(mActions, &QActionGroup::triggered, this, &GlobalDebugProfileMenu::actionTriggered); - std::sort (ids.begin(), ids.end()); + std::sort(ids.begin(), ids.end()); - for (std::vector::const_iterator iter (ids.begin()); iter!=ids.end(); ++iter) + for (std::vector::const_iterator iter(ids.begin()); iter != ids.end(); ++iter) { - mActions->addAction (addAction (*iter)); + mActions->addAction(addAction(*iter)); } } -CSVDoc::GlobalDebugProfileMenu::GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, - QWidget *parent) -: QMenu (parent), mDebugProfiles (debugProfiles), mActions (nullptr) +CSVDoc::GlobalDebugProfileMenu::GlobalDebugProfileMenu(CSMWorld::IdTable* debugProfiles, QWidget* parent) + : QMenu(parent) + , mDebugProfiles(debugProfiles) + , mActions(nullptr) { rebuild(); - connect (mDebugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (profileAboutToBeRemoved (const QModelIndex&, int, int))); + connect(mDebugProfiles, &CSMWorld::IdTable::rowsAboutToBeRemoved, this, + &GlobalDebugProfileMenu::profileAboutToBeRemoved); - connect (mDebugProfiles, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (profileInserted (const QModelIndex&, int, int))); + connect(mDebugProfiles, &CSMWorld::IdTable::rowsInserted, this, &GlobalDebugProfileMenu::profileInserted); - connect (mDebugProfiles, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (profileChanged (const QModelIndex&, const QModelIndex&))); + connect(mDebugProfiles, &CSMWorld::IdTable::dataChanged, this, &GlobalDebugProfileMenu::profileChanged); } -void CSVDoc::GlobalDebugProfileMenu::updateActions (bool running) +void CSVDoc::GlobalDebugProfileMenu::updateActions(bool running) { if (mActions) - mActions->setEnabled (!running); + mActions->setEnabled(!running); } -void CSVDoc::GlobalDebugProfileMenu::profileAboutToBeRemoved (const QModelIndex& parent, - int start, int end) +void CSVDoc::GlobalDebugProfileMenu::profileAboutToBeRemoved(const QModelIndex& parent, int start, int end) { rebuild(); } -void CSVDoc::GlobalDebugProfileMenu::profileInserted (const QModelIndex& parent, int start, - int end) +void CSVDoc::GlobalDebugProfileMenu::profileInserted(const QModelIndex& parent, int start, int end) { rebuild(); } -void CSVDoc::GlobalDebugProfileMenu::profileChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVDoc::GlobalDebugProfileMenu::profileChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { rebuild(); } -void CSVDoc::GlobalDebugProfileMenu::actionTriggered (QAction *action) +void CSVDoc::GlobalDebugProfileMenu::actionTriggered(QAction* action) { - emit triggered (std::string (action->text().toUtf8().constData())); + emit triggered(std::string(action->text().toUtf8().constData())); } diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.hpp b/apps/opencs/view/doc/globaldebugprofilemenu.hpp index e12ee306a..797fda3e8 100644 --- a/apps/opencs/view/doc/globaldebugprofilemenu.hpp +++ b/apps/opencs/view/doc/globaldebugprofilemenu.hpp @@ -3,8 +3,13 @@ #include -class QModelIndex; +#include + +class QAction; class QActionGroup; +class QModelIndex; +class QObject; +class QWidget; namespace CSMWorld { @@ -15,34 +20,32 @@ namespace CSVDoc { class GlobalDebugProfileMenu : public QMenu { - Q_OBJECT + Q_OBJECT - CSMWorld::IdTable *mDebugProfiles; - QActionGroup *mActions; + CSMWorld::IdTable* mDebugProfiles; + QActionGroup* mActions; - private: + private: + void rebuild(); - void rebuild(); + public: + GlobalDebugProfileMenu(CSMWorld::IdTable* debugProfiles, QWidget* parent = nullptr); - public: + void updateActions(bool running); - GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, QWidget *parent = nullptr); + private slots: - void updateActions (bool running); + void profileAboutToBeRemoved(const QModelIndex& parent, int start, int end); - private slots: + void profileInserted(const QModelIndex& parent, int start, int end); - void profileAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void profileChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void profileInserted (const QModelIndex& parent, int start, int end); + void actionTriggered(QAction* action); - void profileChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + signals: - void actionTriggered (QAction *action); - - signals: - - void triggered (const std::string& profile); + void triggered(const std::string& profile); }; } diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 1cdfc0173..9d98635ef 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -1,201 +1,214 @@ #include "loader.hpp" -#include -#include -#include +#include #include #include -#include +#include #include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include #include "../../model/doc/document.hpp" -void CSVDoc::LoadingDocument::closeEvent (QCloseEvent *event) +void CSVDoc::LoadingDocument::closeEvent(QCloseEvent* event) { event->ignore(); cancel(); } -CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) -: mDocument (document), mAborted (false), mMessages (nullptr), mTotalRecords (0) +CSVDoc::LoadingDocument::LoadingDocument(CSMDoc::Document* document) + : mDocument(document) + , mTotalRecordsLabel(0) + , mRecordsLabel(0) + , mAborted(false) + , mMessages(nullptr) + , mRecords(0) { - setWindowTitle (QString::fromUtf8((std::string("Opening ") + document->getSavePath().filename().string()).c_str())); + setWindowTitle("Opening " + Files::pathToQString(document->getSavePath().filename())); - setMinimumWidth (400); + setMinimumWidth(400); - mLayout = new QVBoxLayout (this); + mLayout = new QVBoxLayout(this); - // file progress - mFile = new QLabel (this); + // total progress + mTotalRecordsLabel = new QLabel(this); - mLayout->addWidget (mFile); + mLayout->addWidget(mTotalRecordsLabel); - mFileProgress = new QProgressBar (this); + mTotalProgress = new QProgressBar(this); - mLayout->addWidget (mFileProgress); + mLayout->addWidget(mTotalProgress); - int size = static_cast (document->getContentFiles().size())+1; - if (document->isNew()) - --size; + mTotalProgress->setMinimum(0); + mTotalProgress->setMaximum(document->getData().getTotalRecords(document->getContentFiles())); + mTotalProgress->setTextVisible(true); + mTotalProgress->setValue(0); + mTotalRecords = 0; - mFileProgress->setMinimum (0); - mFileProgress->setMaximum (size); - mFileProgress->setTextVisible (true); - mFileProgress->setValue (0); + mFilesLoaded = 0; // record progress - mLayout->addWidget (mRecords = new QLabel ("Records", this)); + mLayout->addWidget(mRecordsLabel = new QLabel("Records", this)); - mRecordProgress = new QProgressBar (this); + mRecordProgress = new QProgressBar(this); - mLayout->addWidget (mRecordProgress); + mLayout->addWidget(mRecordProgress); - mRecordProgress->setMinimum (0); - mRecordProgress->setTextVisible (true); - mRecordProgress->setValue (0); + mRecordProgress->setMinimum(0); + mRecordProgress->setTextVisible(true); + mRecordProgress->setValue(0); // error message - mError = new QLabel (this); - mError->setWordWrap (true); + mError = new QLabel(this); + mError->setWordWrap(true); + mError->setTextInteractionFlags(Qt::TextSelectableByMouse); - mLayout->addWidget (mError); + mLayout->addWidget(mError); // buttons - mButtons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); + mButtons = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, this); - mLayout->addWidget (mButtons); + mLayout->addWidget(mButtons); - setLayout (mLayout); + setLayout(mLayout); - move (QCursor::pos()); + move(QCursor::pos()); show(); - connect (mButtons, SIGNAL (rejected()), this, SLOT (cancel())); + connect(mButtons, &QDialogButtonBox::rejected, this, qOverload<>(&LoadingDocument::cancel)); } -void CSVDoc::LoadingDocument::nextStage (const std::string& name, int totalRecords) +void CSVDoc::LoadingDocument::nextStage(const std::string& name, int fileRecords) { - mFile->setText (QString::fromUtf8 (("Loading: " + name).c_str())); + ++mFilesLoaded; + size_t numFiles = mDocument->getContentFiles().size(); - mFileProgress->setValue (mFileProgress->value()+1); + mTotalRecordsLabel->setText(QString::fromUtf8( + ("Loading: " + name + " (" + std::to_string(mFilesLoaded) + " of " + std::to_string((numFiles)) + ")") + .c_str())); - mRecordProgress->setValue (0); - mRecordProgress->setMaximum (totalRecords>0 ? totalRecords : 1); + mTotalRecords = mTotalProgress->value(); - mTotalRecords = totalRecords; + mRecordProgress->setValue(0); + mRecordProgress->setMaximum(fileRecords > 0 ? fileRecords : 1); + + mRecords = fileRecords; } -void CSVDoc::LoadingDocument::nextRecord (int records) +void CSVDoc::LoadingDocument::nextRecord(int records) { - if (records<=mTotalRecords) + if (records <= mRecords) { - mRecordProgress->setValue (records); + mTotalProgress->setValue(mTotalRecords + records); - std::ostringstream stream; + mRecordProgress->setValue(records); - stream << "Records: " << records << " of " << mTotalRecords; - - mRecords->setText (QString::fromUtf8 (stream.str().c_str())); + mRecordsLabel->setText("Records: " + QString::number(records) + " of " + QString::number(mRecords)); } } -void CSVDoc::LoadingDocument::abort (const std::string& error) +void CSVDoc::LoadingDocument::abort(const std::string& error) { mAborted = true; - mError->setText (QString::fromUtf8 (("Loading failed: " + error + "").c_str())); - mButtons->setStandardButtons (QDialogButtonBox::Close); + mError->setText(QString::fromUtf8(("Loading failed: " + error + "").c_str())); + Log(Debug::Error) << "Loading failed: " << error; + mButtons->setStandardButtons(QDialogButtonBox::Close); } -void CSVDoc::LoadingDocument::addMessage (const std::string& message) +void CSVDoc::LoadingDocument::addMessage(const std::string& message) { if (!mMessages) { - mMessages = new QListWidget (this); - mLayout->insertWidget (4, mMessages); + mMessages = new QListWidget(this); + mLayout->insertWidget(4, mMessages); } - new QListWidgetItem (QString::fromUtf8 (message.c_str()), mMessages); + new QListWidgetItem(QString::fromUtf8(message.c_str()), mMessages); } void CSVDoc::LoadingDocument::cancel() { if (!mAborted) - emit cancel (mDocument); + emit cancel(mDocument); else { - emit close (mDocument); + emit close(mDocument); deleteLater(); } } - -CSVDoc::Loader::Loader() {} - CSVDoc::Loader::~Loader() { - for (std::map::iterator iter (mDocuments.begin()); - iter!=mDocuments.end(); ++iter) + for (std::map::iterator iter(mDocuments.begin()); iter != mDocuments.end(); + ++iter) delete iter->second; } -void CSVDoc::Loader::add (CSMDoc::Document *document) +void CSVDoc::Loader::add(CSMDoc::Document* document) { - LoadingDocument *loading = new LoadingDocument (document); - mDocuments.insert (std::make_pair (document, loading)); + LoadingDocument* loading = new LoadingDocument(document); + mDocuments.insert(std::make_pair(document, loading)); - connect (loading, SIGNAL (cancel (CSMDoc::Document *)), - this, SIGNAL (cancel (CSMDoc::Document *))); - connect (loading, SIGNAL (close (CSMDoc::Document *)), - this, SIGNAL (close (CSMDoc::Document *))); + connect(loading, qOverload(&LoadingDocument::cancel), this, &Loader::cancel); + connect(loading, &LoadingDocument::close, this, &Loader::close); } -void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed, - const std::string& error) +void CSVDoc::Loader::loadingStopped(CSMDoc::Document* document, bool completed, const std::string& error) { - std::map::iterator iter = mDocuments.begin(); + std::map::iterator iter = mDocuments.begin(); - for (; iter!=mDocuments.end(); ++iter) - if (iter->first==document) + for (; iter != mDocuments.end(); ++iter) + if (iter->first == document) break; - if (iter==mDocuments.end()) + if (iter == mDocuments.end()) return; if (completed || error.empty()) { delete iter->second; - mDocuments.erase (iter); + mDocuments.erase(iter); } else { - iter->second->abort (error); + iter->second->abort(error); // Leave the window open for now (wait for the user to close it) - mDocuments.erase (iter); + mDocuments.erase(iter); } } -void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, - int totalRecords) +void CSVDoc::Loader::nextStage(CSMDoc::Document* document, const std::string& name, int fileRecords) { - std::map::iterator iter = mDocuments.find (document); + std::map::iterator iter = mDocuments.find(document); - if (iter!=mDocuments.end()) - iter->second->nextStage (name, totalRecords); + if (iter != mDocuments.end()) + iter->second->nextStage(name, fileRecords); } -void CSVDoc::Loader::nextRecord (CSMDoc::Document *document, int records) +void CSVDoc::Loader::nextRecord(CSMDoc::Document* document, int records) { - std::map::iterator iter = mDocuments.find (document); + std::map::iterator iter = mDocuments.find(document); - if (iter!=mDocuments.end()) - iter->second->nextRecord (records); + if (iter != mDocuments.end()) + iter->second->nextRecord(records); } -void CSVDoc::Loader::loadMessage (CSMDoc::Document *document, const std::string& message) +void CSVDoc::Loader::loadMessage(CSMDoc::Document* document, const std::string& message) { - std::map::iterator iter = mDocuments.find (document); + std::map::iterator iter = mDocuments.find(document); - if (iter!=mDocuments.end()) - iter->second->addMessage (message); + if (iter != mDocuments.end()) + iter->second->addMessage(message); } diff --git a/apps/opencs/view/doc/loader.hpp b/apps/opencs/view/doc/loader.hpp index 24cbee788..223d77ce3 100644 --- a/apps/opencs/view/doc/loader.hpp +++ b/apps/opencs/view/doc/loader.hpp @@ -2,11 +2,12 @@ #define CSV_DOC_LOADER_H #include +#include #include #include -#include +class QCloseEvent; class QLabel; class QProgressBar; class QDialogButtonBox; @@ -22,79 +23,77 @@ namespace CSVDoc { class LoadingDocument : public QWidget { - Q_OBJECT + Q_OBJECT - CSMDoc::Document *mDocument; - QLabel *mFile; - QLabel *mRecords; - QProgressBar *mFileProgress; - QProgressBar *mRecordProgress; - bool mAborted; - QDialogButtonBox *mButtons; - QLabel *mError; - QListWidget *mMessages; - QVBoxLayout *mLayout; - int mTotalRecords; + CSMDoc::Document* mDocument; + QLabel* mTotalRecordsLabel; + QLabel* mRecordsLabel; + QProgressBar* mTotalProgress; + QProgressBar* mRecordProgress; + bool mAborted; + QDialogButtonBox* mButtons; + QLabel* mError; + QListWidget* mMessages; + QVBoxLayout* mLayout; + int mRecords; + int mTotalRecords; + int mFilesLoaded; - private: + private: + void closeEvent(QCloseEvent* event) override; - void closeEvent (QCloseEvent *event) override; + public: + LoadingDocument(CSMDoc::Document* document); - public: + void nextStage(const std::string& name, int totalRecords); - LoadingDocument (CSMDoc::Document *document); + void nextRecord(int records); - void nextStage (const std::string& name, int totalRecords); + void abort(const std::string& error); - void nextRecord (int records); + void addMessage(const std::string& message); - void abort (const std::string& error); + private slots: - void addMessage (const std::string& message); + void cancel(); - private slots: + signals: - void cancel(); + void cancel(CSMDoc::Document* document); + ///< Stop loading process. - signals: - - void cancel (CSMDoc::Document *document); - ///< Stop loading process. - - void close (CSMDoc::Document *document); - ///< Close stopped loading process. + void close(CSMDoc::Document* document); + ///< Close stopped loading process. }; class Loader : public QObject { - Q_OBJECT + Q_OBJECT - std::map mDocuments; + std::map mDocuments; - public: + public: + Loader() = default; - Loader(); + ~Loader() override; - virtual ~Loader(); + signals: - signals: + void cancel(CSMDoc::Document* document); - void cancel (CSMDoc::Document *document); + void close(CSMDoc::Document* document); - void close (CSMDoc::Document *document); + public slots: - public slots: + void add(CSMDoc::Document* document); - void add (CSMDoc::Document *document); + void loadingStopped(CSMDoc::Document* document, bool completed, const std::string& error); - void loadingStopped (CSMDoc::Document *document, bool completed, - const std::string& error); + void nextStage(CSMDoc::Document* document, const std::string& name, int totalRecords); - void nextStage (CSMDoc::Document *document, const std::string& name, int totalRecords); + void nextRecord(CSMDoc::Document* document, int records); - void nextRecord (CSMDoc::Document *document, int records); - - void loadMessage (CSMDoc::Document *document, const std::string& message); + void loadMessage(CSMDoc::Document* document, const std::string& message); }; } diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp index 7b247652e..99ee189e7 100644 --- a/apps/opencs/view/doc/newgame.cpp +++ b/apps/opencs/view/doc/newgame.cpp @@ -1,74 +1,72 @@ #include "newgame.hpp" -#include -#include -#include #include +#include #include #include +#include -#include "filewidget.hpp" #include "adjusterwidget.hpp" +#include "filewidget.hpp" CSVDoc::NewGameDialogue::NewGameDialogue() { - setWindowTitle ("Create New Game"); + setWindowTitle("Create New Game"); - QVBoxLayout *layout = new QVBoxLayout (this); + QVBoxLayout* layout = new QVBoxLayout(this); - mFileWidget = new FileWidget (this); - mFileWidget->setType (false); + mFileWidget = new FileWidget(this); + mFileWidget->setType(false); - layout->addWidget (mFileWidget, 1); + layout->addWidget(mFileWidget, 1); - mAdjusterWidget = new AdjusterWidget (this); + mAdjusterWidget = new AdjusterWidget(this); - layout->addWidget (mAdjusterWidget, 1); + layout->addWidget(mAdjusterWidget, 1); - QDialogButtonBox *buttons = new QDialogButtonBox (this); + QDialogButtonBox* buttons = new QDialogButtonBox(this); - mCreate = new QPushButton ("Create", this); - mCreate->setDefault (true); - mCreate->setEnabled (false); + mCreate = new QPushButton("Create", this); + mCreate->setDefault(true); + mCreate->setEnabled(false); - buttons->addButton (mCreate, QDialogButtonBox::AcceptRole); + buttons->addButton(mCreate, QDialogButtonBox::AcceptRole); - QPushButton *cancel = new QPushButton ("Cancel", this); + QPushButton* cancel = new QPushButton("Cancel", this); - buttons->addButton (cancel, QDialogButtonBox::RejectRole); + buttons->addButton(cancel, QDialogButtonBox::RejectRole); - layout->addWidget (buttons); + layout->addWidget(buttons); - setLayout (layout); + setLayout(layout); - connect (mAdjusterWidget, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); - connect (mCreate, SIGNAL (clicked()), this, SLOT (create())); - connect (cancel, SIGNAL (clicked()), this, SLOT (reject())); - connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), - mAdjusterWidget, SLOT (setName (const QString&, bool))); + connect(mAdjusterWidget, &AdjusterWidget::stateChanged, this, &NewGameDialogue::stateChanged); + connect(mCreate, &QPushButton::clicked, this, &NewGameDialogue::create); + connect(cancel, &QPushButton::clicked, this, &NewGameDialogue::reject); + connect(mFileWidget, &FileWidget::nameChanged, mAdjusterWidget, &AdjusterWidget::setName); QRect scr = QGuiApplication::primaryScreen()->geometry(); QRect rect = geometry(); - move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); + move(scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); } -void CSVDoc::NewGameDialogue::setLocalData (const boost::filesystem::path& localData) +void CSVDoc::NewGameDialogue::setLocalData(const std::filesystem::path& localData) { - mAdjusterWidget->setLocalData (localData); + mAdjusterWidget->setLocalData(localData); } -void CSVDoc::NewGameDialogue::stateChanged (bool valid) +void CSVDoc::NewGameDialogue::stateChanged(bool valid) { - mCreate->setEnabled (valid); + mCreate->setEnabled(valid); } void CSVDoc::NewGameDialogue::create() { - emit createRequest (mAdjusterWidget->getPath()); + emit createRequest(mAdjusterWidget->getPath()); } void CSVDoc::NewGameDialogue::reject() { - emit cancelCreateGame (); + emit cancelCreateGame(); QDialog::reject(); } diff --git a/apps/opencs/view/doc/newgame.hpp b/apps/opencs/view/doc/newgame.hpp index e3c6f53ca..4e8154e22 100644 --- a/apps/opencs/view/doc/newgame.hpp +++ b/apps/opencs/view/doc/newgame.hpp @@ -1,14 +1,14 @@ #ifndef CSV_DOC_NEWGAME_H #define CSV_DOC_NEWGAME_H -#include - #include #include -#ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED -#define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED -Q_DECLARE_METATYPE (boost::filesystem::path) +#include + +#ifndef CS_QT_STD_FILESYSTEM_PATH_DECLARED +#define CS_QT_STD_FILESYSTEM_PATH_DECLARED +Q_DECLARE_METATYPE(std::filesystem::path) #endif class QPushButton; @@ -20,31 +20,30 @@ namespace CSVDoc class NewGameDialogue : public QDialog { - Q_OBJECT + Q_OBJECT - QPushButton *mCreate; - FileWidget *mFileWidget; - AdjusterWidget *mAdjusterWidget; + QPushButton* mCreate; + FileWidget* mFileWidget; + AdjusterWidget* mAdjusterWidget; - public: + public: + NewGameDialogue(); - NewGameDialogue(); + void setLocalData(const std::filesystem::path& localData); - void setLocalData (const boost::filesystem::path& localData); + signals: - signals: + void createRequest(const std::filesystem::path& file); - void createRequest (const boost::filesystem::path& file); + void cancelCreateGame(); - void cancelCreateGame (); + private slots: - private slots: + void stateChanged(bool valid); - void stateChanged (bool valid); + void create(); - void create(); - - void reject() override; + void reject() override; }; } diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp index e5c1e7b89..4825f87ff 100644 --- a/apps/opencs/view/doc/operation.cpp +++ b/apps/opencs/view/doc/operation.cpp @@ -1,30 +1,39 @@ #include "operation.hpp" #include +#include +#include #include #include -#include -#include "../../model/doc/document.hpp" +#include "../../model/doc/state.hpp" -void CSVDoc::Operation::updateLabel (int threads) +void CSVDoc::Operation::updateLabel(int threads) { - if (threads==-1 || ((threads==0)!=mStalling)) + if (threads == -1 || ((threads == 0) != mStalling)) { - std::string name ("unknown operation"); + std::string name("unknown operation"); switch (mType) { - case CSMDoc::State_Saving: name = "saving"; break; - case CSMDoc::State_Verifying: name = "verifying"; break; - case CSMDoc::State_Searching: name = "searching"; break; - case CSMDoc::State_Merging: name = "merging"; break; + case CSMDoc::State_Saving: + name = "saving"; + break; + case CSMDoc::State_Verifying: + name = "verifying"; + break; + case CSMDoc::State_Searching: + name = "searching"; + break; + case CSMDoc::State_Merging: + name = "merging"; + break; } std::ostringstream stream; - if ((mStalling = (threads<=0))) + if ((mStalling = (threads <= 0))) { stream << name << " (waiting for a free worker thread)"; } @@ -33,15 +42,17 @@ void CSVDoc::Operation::updateLabel (int threads) stream << name << " (%p%)"; } - mProgressBar->setFormat (stream.str().c_str()); + mProgressBar->setFormat(stream.str().c_str()); } } -CSVDoc::Operation::Operation (int type, QWidget* parent) : mType (type), mStalling (false) +CSVDoc::Operation::Operation(int type, QWidget* parent) + : mType(type) + , mStalling(false) { /// \todo Add a cancel button or a pop up menu with a cancel item initWidgets(); - setBarColor( type); + setBarColor(type); updateLabel(); /// \todo assign different progress bar colours to allow the user to distinguish easily between operation types @@ -56,21 +67,21 @@ CSVDoc::Operation::~Operation() void CSVDoc::Operation::initWidgets() { - mProgressBar = new QProgressBar (); + mProgressBar = new QProgressBar(); mAbortButton = new QPushButton("Abort"); mLayout = new QHBoxLayout(); - mLayout->addWidget (mProgressBar); - mLayout->addWidget (mAbortButton); + mLayout->addWidget(mProgressBar); + mLayout->addWidget(mAbortButton); - connect (mAbortButton, SIGNAL (clicked()), this, SLOT (abortOperation())); + connect(mAbortButton, &QPushButton::clicked, this, qOverload<>(&Operation::abortOperation)); } -void CSVDoc::Operation::setProgress (int current, int max, int threads) +void CSVDoc::Operation::setProgress(int current, int max, int threads) { - updateLabel (threads); - mProgressBar->setRange (0, max); - mProgressBar->setValue (current); + updateLabel(threads); + mProgressBar->setRange(0, max); + mProgressBar->setValue(current); } int CSVDoc::Operation::getType() const @@ -78,76 +89,77 @@ int CSVDoc::Operation::getType() const return mType; } -void CSVDoc::Operation::setBarColor (int type) +void CSVDoc::Operation::setBarColor(int type) { - QString style ="QProgressBar {" - "text-align: center;" - "}" + QString style + = "QProgressBar {" + "text-align: center;" + "}" "QProgressBar::chunk {" - "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:.50 %2 stop: .51 %3 stop:1 %4);" - "text-align: center;" - "margin: 2px 1px 1p 2px;" + "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:.50 %2 stop: .51 %3 stop:1 %4);" + "text-align: center;" + "margin: 2px 1px 1p 2px;" "}"; QString topColor = "#F2F6F8"; QString bottomColor = "#E0EFF9"; QString midTopColor = "#D8E1E7"; - QString midBottomColor = "#B5C6D0"; // default gray gloss + QString midBottomColor = "#B5C6D0"; // default gray gloss // colors inspired by samples from: // http://www.colorzilla.com/gradient-editor/ switch (type) { - case CSMDoc::State_Saving: + case CSMDoc::State_Saving: - topColor = "#FECCB1"; - midTopColor = "#F17432"; - midBottomColor = "#EA5507"; - bottomColor = "#FB955E"; // red gloss #2 - break; + topColor = "#FECCB1"; + midTopColor = "#F17432"; + midBottomColor = "#EA5507"; + bottomColor = "#FB955E"; // red gloss #2 + break; - case CSMDoc::State_Searching: + case CSMDoc::State_Searching: - topColor = "#EBF1F6"; - midTopColor = "#ABD3EE"; - midBottomColor = "#89C3EB"; - bottomColor = "#D5EBFB"; //blue gloss #4 - break; + topColor = "#EBF1F6"; + midTopColor = "#ABD3EE"; + midBottomColor = "#89C3EB"; + bottomColor = "#D5EBFB"; // blue gloss #4 + break; - case CSMDoc::State_Verifying: + case CSMDoc::State_Verifying: - topColor = "#BFD255"; - midTopColor = "#8EB92A"; - midBottomColor = "#72AA00"; - bottomColor = "#9ECB2D"; //green gloss - break; + topColor = "#BFD255"; + midTopColor = "#8EB92A"; + midBottomColor = "#72AA00"; + bottomColor = "#9ECB2D"; // green gloss + break; - case CSMDoc::State_Merging: + case CSMDoc::State_Merging: - topColor = "#F3E2C7"; - midTopColor = "#C19E67"; - midBottomColor = "#B68D4C"; - bottomColor = "#E9D4B3"; //l Brown 3D - break; + topColor = "#F3E2C7"; + midTopColor = "#C19E67"; + midBottomColor = "#B68D4C"; + bottomColor = "#E9D4B3"; // l Brown 3D + break; - default: + default: - topColor = "#F2F6F8"; - bottomColor = "#E0EFF9"; - midTopColor = "#D8E1E7"; - midBottomColor = "#B5C6D0"; // gray gloss for undefined ops + topColor = "#F2F6F8"; + bottomColor = "#E0EFF9"; + midTopColor = "#D8E1E7"; + midBottomColor = "#B5C6D0"; // gray gloss for undefined ops } mProgressBar->setStyleSheet(style.arg(topColor).arg(midTopColor).arg(midBottomColor).arg(bottomColor)); } -QHBoxLayout *CSVDoc::Operation::getLayout() const +QHBoxLayout* CSVDoc::Operation::getLayout() const { return mLayout; } void CSVDoc::Operation::abortOperation() { - emit abortOperation (mType); + emit abortOperation(mType); } diff --git a/apps/opencs/view/doc/operation.hpp b/apps/opencs/view/doc/operation.hpp index 48839fada..b50833060 100644 --- a/apps/opencs/view/doc/operation.hpp +++ b/apps/opencs/view/doc/operation.hpp @@ -6,47 +6,46 @@ class QHBoxLayout; class QPushButton; class QProgressBar; +class QWidget; namespace CSVDoc { class Operation : public QObject { - Q_OBJECT + Q_OBJECT - int mType; - bool mStalling; - QProgressBar *mProgressBar; - QPushButton *mAbortButton; - QHBoxLayout *mLayout; + int mType; + bool mStalling; + QProgressBar* mProgressBar; + QPushButton* mAbortButton; + QHBoxLayout* mLayout; - // not implemented - Operation (const Operation&); - Operation& operator= (const Operation&); + // not implemented + Operation(const Operation&); + Operation& operator=(const Operation&); - void updateLabel (int threads = -1); + void updateLabel(int threads = -1); - public: + public: + Operation(int type, QWidget* parent); + ~Operation() override; - Operation (int type, QWidget *parent); - ~Operation(); + void setProgress(int current, int max, int threads); - void setProgress (int current, int max, int threads); + int getType() const; + QHBoxLayout* getLayout() const; - int getType() const; - QHBoxLayout *getLayout() const; + private: + void setBarColor(int type); + void initWidgets(); - private: + signals: - void setBarColor (int type); - void initWidgets(); + void abortOperation(int type); - signals: + private slots: - void abortOperation (int type); - - private slots: - - void abortOperation(); + void abortOperation(); }; } diff --git a/apps/opencs/view/doc/operations.cpp b/apps/opencs/view/doc/operations.cpp index 9ed77ff9c..4fe53d629 100644 --- a/apps/opencs/view/doc/operations.cpp +++ b/apps/opencs/view/doc/operations.cpp @@ -1,7 +1,10 @@ #include "operations.hpp" +#include + +#include #include -#include +#include #include "operation.hpp" @@ -9,60 +12,60 @@ CSVDoc::Operations::Operations() { /// \todo make widget height fixed (exactly the height required to display all operations) - setFeatures (QDockWidget::NoDockWidgetFeatures); + setFeatures(QDockWidget::NoDockWidgetFeatures); - QWidget *widgetContainer = new QWidget (this); + QWidget* widgetContainer = new QWidget(this); mLayout = new QVBoxLayout; - widgetContainer->setLayout (mLayout); - setWidget (widgetContainer); - setVisible (false); - setFixedHeight (widgetContainer->height()); - setTitleBarWidget (new QWidget (this)); + widgetContainer->setLayout(mLayout); + setWidget(widgetContainer); + setVisible(false); + setFixedHeight(widgetContainer->height()); + setTitleBarWidget(new QWidget(this)); } -void CSVDoc::Operations::setProgress (int current, int max, int type, int threads) +void CSVDoc::Operations::setProgress(int current, int max, int type, int threads) { - for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) - if ((*iter)->getType()==type) + for (std::vector::iterator iter(mOperations.begin()); iter != mOperations.end(); ++iter) + if ((*iter)->getType() == type) { - (*iter)->setProgress (current, max, threads); + (*iter)->setProgress(current, max, threads); return; } int oldCount = static_cast(mOperations.size()); int newCount = oldCount + 1; - Operation *operation = new Operation (type, this); - connect (operation, SIGNAL (abortOperation (int)), this, SIGNAL (abortOperation (int))); + Operation* operation = new Operation(type, this); + connect(operation, qOverload(&Operation::abortOperation), this, &Operations::abortOperation); - mLayout->addLayout (operation->getLayout()); - mOperations.push_back (operation); - operation->setProgress (current, max, threads); + mLayout->addLayout(operation->getLayout()); + mOperations.push_back(operation); + operation->setProgress(current, max, threads); - if ( oldCount > 0) - setFixedHeight (height()/oldCount * newCount); + if (oldCount > 0) + setFixedHeight(height() / oldCount * newCount); - setVisible (true); + setVisible(true); } -void CSVDoc::Operations::quitOperation (int type) +void CSVDoc::Operations::quitOperation(int type) { - for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) - if ((*iter)->getType()==type) + for (std::vector::iterator iter(mOperations.begin()); iter != mOperations.end(); ++iter) + if ((*iter)->getType() == type) { int oldCount = static_cast(mOperations.size()); int newCount = oldCount - 1; - mLayout->removeItem ((*iter)->getLayout()); + mLayout->removeItem((*iter)->getLayout()); (*iter)->deleteLater(); - mOperations.erase (iter); + mOperations.erase(iter); if (oldCount > 1) - setFixedHeight (height() / oldCount * newCount); + setFixedHeight(height() / oldCount * newCount); else - setVisible (false); + setVisible(false); break; } diff --git a/apps/opencs/view/doc/operations.hpp b/apps/opencs/view/doc/operations.hpp index 71c595f66..110ed3f72 100644 --- a/apps/opencs/view/doc/operations.hpp +++ b/apps/opencs/view/doc/operations.hpp @@ -13,28 +13,27 @@ namespace CSVDoc class Operations : public QDockWidget { - Q_OBJECT + Q_OBJECT - QVBoxLayout *mLayout; - std::vector mOperations; + QVBoxLayout* mLayout; + std::vector mOperations; - // not implemented - Operations (const Operations&); - Operations& operator= (const Operations&); + // not implemented + Operations(const Operations&); + Operations& operator=(const Operations&); - public: + public: + Operations(); - Operations(); + void setProgress(int current, int max, int type, int threads); + ///< Implicitly starts the operation, if it is not running already. - void setProgress (int current, int max, int type, int threads); - ///< Implicitly starts the operation, if it is not running already. + void quitOperation(int type); + ///< Calling this function for an operation that is not running is a no-op. - void quitOperation (int type); - ///< Calling this function for an operation that is not running is a no-op. + signals: - signals: - - void abortOperation (int type); + void abortOperation(int type); }; } diff --git a/apps/opencs/view/doc/runlogsubview.cpp b/apps/opencs/view/doc/runlogsubview.cpp index 2b7182228..7affd7381 100644 --- a/apps/opencs/view/doc/runlogsubview.cpp +++ b/apps/opencs/view/doc/runlogsubview.cpp @@ -1,19 +1,22 @@ #include "runlogsubview.hpp" +#include "../../model/doc/document.hpp" + +#include + #include -CSVDoc::RunLogSubView::RunLogSubView (const CSMWorld::UniversalId& id, - CSMDoc::Document& document) -: SubView (id) +CSVDoc::RunLogSubView::RunLogSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) + : SubView(id) { - QTextEdit *edit = new QTextEdit (this); - edit->setDocument (document.getRunLog()); - edit->setReadOnly (true); + QTextEdit* edit = new QTextEdit(this); + edit->setDocument(document.getRunLog()); + edit->setReadOnly(true); - setWidget (edit); + setWidget(edit); } -void CSVDoc::RunLogSubView::setEditLock (bool locked) +void CSVDoc::RunLogSubView::setEditLock(bool locked) { // ignored since this SubView does not have editing } diff --git a/apps/opencs/view/doc/runlogsubview.hpp b/apps/opencs/view/doc/runlogsubview.hpp index e7b490fff..8ff9cd7a5 100644 --- a/apps/opencs/view/doc/runlogsubview.hpp +++ b/apps/opencs/view/doc/runlogsubview.hpp @@ -3,17 +3,23 @@ #include "subview.hpp" +#include + +namespace CSMDoc +{ + class Document; +} + namespace CSVDoc { class RunLogSubView : public SubView { - Q_OBJECT + Q_OBJECT - public: + public: + RunLogSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document); - RunLogSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); - - void setEditLock (bool locked) override; + void setEditLock(bool locked) override; }; } diff --git a/apps/opencs/view/doc/sizehint.cpp b/apps/opencs/view/doc/sizehint.cpp index 038bd9e4d..8b907ead0 100644 --- a/apps/opencs/view/doc/sizehint.cpp +++ b/apps/opencs/view/doc/sizehint.cpp @@ -1,17 +1,16 @@ #include "sizehint.hpp" -CSVDoc::SizeHintWidget::SizeHintWidget(QWidget *parent) : QWidget(parent) -{} - -CSVDoc::SizeHintWidget::~SizeHintWidget() -{} +CSVDoc::SizeHintWidget::SizeHintWidget(QWidget* parent) + : QWidget(parent) +{ +} QSize CSVDoc::SizeHintWidget::sizeHint() const { return mSize; } -void CSVDoc::SizeHintWidget::setSizeHint(const QSize &size) +void CSVDoc::SizeHintWidget::setSizeHint(const QSize& size) { mSize = size; } diff --git a/apps/opencs/view/doc/sizehint.hpp b/apps/opencs/view/doc/sizehint.hpp index 14ec7b186..a0296a51e 100644 --- a/apps/opencs/view/doc/sizehint.hpp +++ b/apps/opencs/view/doc/sizehint.hpp @@ -1,21 +1,21 @@ #ifndef CSV_DOC_SIZEHINT_H #define CSV_DOC_SIZEHINT_H -#include #include +#include namespace CSVDoc { class SizeHintWidget : public QWidget { - QSize mSize; + QSize mSize; - public: - SizeHintWidget(QWidget *parent = nullptr); - ~SizeHintWidget(); + public: + SizeHintWidget(QWidget* parent = nullptr); + ~SizeHintWidget() override = default; - QSize sizeHint() const override; - void setSizeHint(const QSize &size); + QSize sizeHint() const override; + void setSizeHint(const QSize& size); }; } diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 3a1950a6e..27463b045 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -1,126 +1,129 @@ #include "startup.hpp" -#include -#include -#include -#include -#include #include -#include +#include +#include #include +#include #include +#include #include +#include -QPushButton *CSVDoc::StartupDialogue::addButton (const QString& label, const QIcon& icon) +QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QIcon& icon) { int column = mColumn--; - QPushButton *button = new QPushButton (this); + QPushButton* button = new QPushButton(this); - button->setIcon (QIcon (icon)); + button->setIcon(QIcon(icon)); - button->setSizePolicy (QSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred)); + button->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); - mLayout->addWidget (button, 0, column); + mLayout->addWidget(button, 0, column); - mLayout->addWidget (new QLabel (label, this), 1, column, Qt::AlignCenter); + mLayout->addWidget(new QLabel(label, this), 1, column, Qt::AlignCenter); - int width = mLayout->itemAtPosition (1, column)->widget()->sizeHint().width(); + int width = mLayout->itemAtPosition(1, column)->widget()->sizeHint().width(); - if (width>mWidth) + if (width > mWidth) mWidth = width; return button; } - -QWidget *CSVDoc::StartupDialogue::createButtons() +QWidget* CSVDoc::StartupDialogue::createButtons() { - QWidget *widget = new QWidget (this); + QWidget* widget = new QWidget(this); - mLayout = new QGridLayout (widget); + mLayout = new QGridLayout(widget); /// \todo add icons - QPushButton *loadDocument = addButton ("Edit A Content File", QIcon (":startup/edit-content")); - connect (loadDocument, SIGNAL (clicked()), this, SIGNAL (loadDocument())); + QPushButton* loadDocument = addButton("Edit A Content File", QIcon(":startup/edit-content")); + connect(loadDocument, &QPushButton::clicked, this, &StartupDialogue::loadDocument); - QPushButton *createAddon = addButton ("Create A New Addon", QIcon (":startup/create-addon")); - connect (createAddon, SIGNAL (clicked()), this, SIGNAL (createAddon())); + QPushButton* createAddon = addButton("Create A New Addon", QIcon(":startup/create-addon")); + connect(createAddon, &QPushButton::clicked, this, &StartupDialogue::createAddon); - QPushButton *createGame = addButton ("Create A New Game", QIcon (":startup/create-game")); - connect (createGame, SIGNAL (clicked()), this, SIGNAL (createGame())); + QPushButton* createGame = addButton("Create A New Game", QIcon(":startup/create-game")); + connect(createGame, &QPushButton::clicked, this, &StartupDialogue::createGame); - for (int i=0; i<3; ++i) - mLayout->setColumnMinimumWidth (i, mWidth); + for (int i = 0; i < 3; ++i) + mLayout->setColumnMinimumWidth(i, mWidth); - mLayout->setRowMinimumHeight (0, mWidth); + mLayout->setRowMinimumHeight(0, mWidth); - mLayout->setSizeConstraint (QLayout::SetMinimumSize); - mLayout->setHorizontalSpacing (32); + mLayout->setSizeConstraint(QLayout::SetMinimumSize); + mLayout->setHorizontalSpacing(32); - mLayout->setContentsMargins (16, 16, 16, 8); + mLayout->setContentsMargins(16, 16, 16, 8); - loadDocument->setIconSize (QSize (mWidth, mWidth)); - createGame->setIconSize (QSize (mWidth, mWidth)); - createAddon->setIconSize (QSize (mWidth, mWidth)); + loadDocument->setIconSize(QSize(mWidth, mWidth)); + createGame->setIconSize(QSize(mWidth, mWidth)); + createAddon->setIconSize(QSize(mWidth, mWidth)); - widget->setLayout (mLayout); + widget->setLayout(mLayout); return widget; } -QWidget *CSVDoc::StartupDialogue::createTools() +QWidget* CSVDoc::StartupDialogue::createTools() { - QWidget *widget = new QWidget (this); + QWidget* widget = new QWidget(this); - QHBoxLayout *layout = new QHBoxLayout (widget); - layout->setDirection (QBoxLayout::RightToLeft); - layout->setContentsMargins (4, 4, 4, 4); + QHBoxLayout* layout = new QHBoxLayout(widget); + layout->setDirection(QBoxLayout::RightToLeft); + layout->setContentsMargins(4, 4, 4, 4); - QPushButton *config = new QPushButton (widget); + QPushButton* config = new QPushButton(widget); - config->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); - config->setIcon (QIcon (":startup/configure")); - config->setToolTip ("Open user settings"); + config->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + config->setIcon(QIcon(":startup/configure")); + config->setToolTip("Open user settings"); - layout->addWidget (config); + layout->addWidget(config); - layout->addWidget (new QWidget, 1); // dummy widget; stops buttons from taking all the space + layout->addWidget(new QWidget, 1); // dummy widget; stops buttons from taking all the space - widget->setLayout (layout); + widget->setLayout(layout); - connect (config, SIGNAL (clicked()), this, SIGNAL (editConfig())); + connect(config, &QPushButton::clicked, this, &StartupDialogue::editConfig); return widget; } -CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) +CSVDoc::StartupDialogue::StartupDialogue() + : mWidth(0) + , mColumn(2) { - setWindowTitle ("OpenMW-CS"); + setWindowTitle("OpenMW-CS"); - QVBoxLayout *layout = new QVBoxLayout (this); + QVBoxLayout* layout = new QVBoxLayout(this); - layout->setContentsMargins (0, 0, 0, 0); + layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget (createButtons()); - layout->addWidget (createTools()); + layout->addWidget(createButtons()); + layout->addWidget(createTools()); /// \todo remove this label once we are feature complete and convinced that this thing is /// working properly. - QLabel *warning = new QLabel ("WARNING: OpenMW-CS is in alpha stage.

The editor is not feature complete and not sufficiently tested.
In theory your data should be safe. But we strongly advise to make backups regularly if you are working with live data.
"); + QLabel* warning = new QLabel( + "WARNING: OpenMW-CS is in alpha stage.

The editor is not feature complete and not " + "sufficiently tested.
In theory your data should be safe. But we strongly advise to make backups regularly " + "if you are working with live data.
"); QFont font; - font.setPointSize (12); - font.setBold (true); + font.setPointSize(12); + font.setBold(true); - warning->setFont (font); - warning->setWordWrap (true); + warning->setFont(font); + warning->setWordWrap(true); - layout->addWidget (warning, 1); + layout->addWidget(warning, 1); - setLayout (layout); + setLayout(layout); QRect scr = QGuiApplication::primaryScreen()->geometry(); QRect rect = geometry(); - move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); + move(scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); } diff --git a/apps/opencs/view/doc/startup.hpp b/apps/opencs/view/doc/startup.hpp index f059a44e5..061b91b2d 100644 --- a/apps/opencs/view/doc/startup.hpp +++ b/apps/opencs/view/doc/startup.hpp @@ -15,31 +15,29 @@ namespace CSVDoc { Q_OBJECT - private: + private: + int mWidth; + int mColumn; + QGridLayout* mLayout; - int mWidth; - int mColumn; - QGridLayout *mLayout; + QPushButton* addButton(const QString& label, const QIcon& icon); - QPushButton *addButton (const QString& label, const QIcon& icon); + QWidget* createButtons(); - QWidget *createButtons(); + QWidget* createTools(); - QWidget *createTools(); + public: + StartupDialogue(); - public: + signals: - StartupDialogue(); + void createGame(); - signals: + void createAddon(); - void createGame(); + void loadDocument(); - void createAddon(); - - void loadDocument(); - - void editConfig(); + void editConfig(); }; } diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 82cbe835e..c61930291 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -1,31 +1,30 @@ #include "subview.hpp" -#include "view.hpp" - -#include #include #include -bool CSVDoc::SubView::event (QEvent *event) -{ - if (event->type()==QEvent::ShortcutOverride) - { - QKeyEvent *keyEvent = static_cast (event); +#include - if (keyEvent->key()==Qt::Key_W && keyEvent->modifiers()==(Qt::ShiftModifier | Qt::ControlModifier)) +bool CSVDoc::SubView::event(QEvent* event) +{ + if (event->type() == QEvent::ShortcutOverride) + { + QKeyEvent* keyEvent = static_cast(event); + + if (keyEvent->key() == Qt::Key_W && keyEvent->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) emit closeRequest(); - return true; + return true; } - return QDockWidget::event (event); + return QDockWidget::event(event); } -CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) - : mUniversalId (id) +CSVDoc::SubView::SubView(const CSMWorld::UniversalId& id) + : mUniversalId(id) { /// \todo add a button to the title bar that clones this sub view - setWindowTitle (QString::fromUtf8 (mUniversalId.toString().c_str())); + setWindowTitle(QString::fromUtf8(mUniversalId.toString().c_str())); setAttribute(Qt::WA_DeleteOnClose); } @@ -34,20 +33,20 @@ CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const return mUniversalId; } -void CSVDoc::SubView::setStatusBar (bool show) {} +void CSVDoc::SubView::setStatusBar(bool show) {} -void CSVDoc::SubView::useHint (const std::string& hint) {} +void CSVDoc::SubView::useHint(const std::string& hint) {} -void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) +void CSVDoc::SubView::setUniversalId(const CSMWorld::UniversalId& id) { mUniversalId = id; - setWindowTitle (QString::fromUtf8(mUniversalId.toString().c_str())); - emit universalIdChanged (mUniversalId); + setWindowTitle(QString::fromUtf8(mUniversalId.toString().c_str())); + emit universalIdChanged(mUniversalId); } -void CSVDoc::SubView::closeEvent (QCloseEvent *event) +void CSVDoc::SubView::closeEvent(QCloseEvent* event) { - emit updateSubViewIndices (this); + emit updateSubViewIndices(this); } std::string CSVDoc::SubView::getTitle() const @@ -57,5 +56,5 @@ std::string CSVDoc::SubView::getTitle() const void CSVDoc::SubView::closeRequest() { - emit closeRequest (this); + emit closeRequest(this); } diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index ca9ca8225..d8b30cce6 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -1,76 +1,66 @@ #ifndef CSV_DOC_SUBVIEW_H #define CSV_DOC_SUBVIEW_H -#include "../../model/doc/document.hpp" - #include "../../model/world/universalid.hpp" -#include "subviewfactory.hpp" - #include -class QUndoStack; +#include -namespace CSMWorld -{ - class Data; -} +class QCloseEvent; +class QEvent; +class QObject; namespace CSVDoc { - class View; - class SubView : public QDockWidget { - Q_OBJECT + Q_OBJECT - CSMWorld::UniversalId mUniversalId; + CSMWorld::UniversalId mUniversalId; - // not implemented - SubView (const SubView&); - SubView& operator= (SubView&); + // not implemented + SubView(const SubView&); + SubView& operator=(SubView&); - protected: + protected: + void setUniversalId(const CSMWorld::UniversalId& id); - void setUniversalId(const CSMWorld::UniversalId& id); + bool event(QEvent* event) override; - bool event (QEvent *event) override; + public: + SubView(const CSMWorld::UniversalId& id); - public: + CSMWorld::UniversalId getUniversalId() const; - SubView (const CSMWorld::UniversalId& id); + virtual void setEditLock(bool locked) = 0; - CSMWorld::UniversalId getUniversalId() const; + virtual void setStatusBar(bool show); + ///< Default implementation: ignored - virtual void setEditLock (bool locked) = 0; + virtual void useHint(const std::string& hint); + ///< Default implementation: ignored - virtual void setStatusBar (bool show); - ///< Default implementation: ignored + virtual std::string getTitle() const; - virtual void useHint (const std::string& hint); - ///< Default implementation: ignored + private: + void closeEvent(QCloseEvent* event) override; - virtual std::string getTitle() const; + signals: - private: + void focusId(const CSMWorld::UniversalId& universalId, const std::string& hint); - void closeEvent (QCloseEvent *event) override; + void closeRequest(SubView* subView); - signals: + void updateTitle(); - void focusId (const CSMWorld::UniversalId& universalId, const std::string& hint); + void updateSubViewIndices(SubView* view = nullptr); - void closeRequest (SubView *subView); + void universalIdChanged(const CSMWorld::UniversalId& universalId); - void updateTitle(); + protected slots: - void updateSubViewIndices (SubView *view = nullptr); - - void universalIdChanged (const CSMWorld::UniversalId& universalId); - - protected slots: - - void closeRequest(); + void closeRequest(); }; } diff --git a/apps/opencs/view/doc/subviewfactory.cpp b/apps/opencs/view/doc/subviewfactory.cpp index 82a8aeb05..8330a2c86 100644 --- a/apps/opencs/view/doc/subviewfactory.cpp +++ b/apps/opencs/view/doc/subviewfactory.cpp @@ -1,37 +1,33 @@ #include "subviewfactory.hpp" #include - #include +#include +#include +#include -CSVDoc::SubViewFactoryBase::SubViewFactoryBase() {} - -CSVDoc::SubViewFactoryBase::~SubViewFactoryBase() {} - - -CSVDoc::SubViewFactoryManager::SubViewFactoryManager() {} +#include CSVDoc::SubViewFactoryManager::~SubViewFactoryManager() { - for (std::map::iterator iter (mSubViewFactories.begin()); - iter!=mSubViewFactories.end(); ++iter) + for (std::map::iterator iter(mSubViewFactories.begin()); + iter != mSubViewFactories.end(); ++iter) delete iter->second; } -void CSVDoc::SubViewFactoryManager::add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory) +void CSVDoc::SubViewFactoryManager::add(const CSMWorld::UniversalId::Type& id, SubViewFactoryBase* factory) { - assert (mSubViewFactories.find (id)==mSubViewFactories.end()); + assert(mSubViewFactories.find(id) == mSubViewFactories.end()); - mSubViewFactories.insert (std::make_pair (id, factory)); + mSubViewFactories.insert(std::make_pair(id, factory)); } -CSVDoc::SubView *CSVDoc::SubViewFactoryManager::makeSubView (const CSMWorld::UniversalId& id, - CSMDoc::Document& document) +CSVDoc::SubView* CSVDoc::SubViewFactoryManager::makeSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) { - std::map::iterator iter = mSubViewFactories.find (id.getType()); + std::map::iterator iter = mSubViewFactories.find(id.getType()); - if (iter==mSubViewFactories.end()) - throw std::runtime_error ("Failed to create a sub view for: " + id.toString()); + if (iter == mSubViewFactories.end()) + throw std::runtime_error("Failed to create a sub view for: " + id.toString()); - return iter->second->makeSubView (id, document); + return iter->second->makeSubView(id, document); } diff --git a/apps/opencs/view/doc/subviewfactory.hpp b/apps/opencs/view/doc/subviewfactory.hpp index 1f7c15480..d3630c5e9 100644 --- a/apps/opencs/view/doc/subviewfactory.hpp +++ b/apps/opencs/view/doc/subviewfactory.hpp @@ -16,39 +16,31 @@ namespace CSVDoc class SubViewFactoryBase { - // not implemented - SubViewFactoryBase (const SubViewFactoryBase&); - SubViewFactoryBase& operator= (const SubViewFactoryBase&); + public: + SubViewFactoryBase() = default; + SubViewFactoryBase(const SubViewFactoryBase&) = delete; + SubViewFactoryBase& operator=(const SubViewFactoryBase&) = delete; + virtual ~SubViewFactoryBase() = default; - public: - - SubViewFactoryBase(); - - virtual ~SubViewFactoryBase(); - - virtual SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) = 0; - ///< The ownership of the returned sub view is not transferred. + virtual SubView* makeSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) = 0; + ///< The ownership of the returned sub view is not transferred. }; class SubViewFactoryManager { - std::map mSubViewFactories; + std::map mSubViewFactories; - // not implemented - SubViewFactoryManager (const SubViewFactoryManager&); - SubViewFactoryManager& operator= (const SubViewFactoryManager&); + public: + SubViewFactoryManager() = default; + SubViewFactoryManager(const SubViewFactoryManager&) = delete; + SubViewFactoryManager& operator=(const SubViewFactoryManager&) = delete; + ~SubViewFactoryManager(); - public: + void add(const CSMWorld::UniversalId::Type& id, SubViewFactoryBase* factory); + ///< The ownership of \a factory is transferred to this. - SubViewFactoryManager(); - - ~SubViewFactoryManager(); - - void add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory); - ///< The ownership of \a factory is transferred to this. - - SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); - ///< The ownership of the returned sub view is not transferred. + SubView* makeSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document); + ///< The ownership of the returned sub view is not transferred. }; } diff --git a/apps/opencs/view/doc/subviewfactoryimp.hpp b/apps/opencs/view/doc/subviewfactoryimp.hpp index 152b443a3..40990779a 100644 --- a/apps/opencs/view/doc/subviewfactoryimp.hpp +++ b/apps/opencs/view/doc/subviewfactoryimp.hpp @@ -7,44 +7,41 @@ namespace CSVDoc { - template + template class SubViewFactory : public SubViewFactoryBase { - public: - - CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) override; + public: + CSVDoc::SubView* makeSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) override; }; - template - CSVDoc::SubView *SubViewFactory::makeSubView (const CSMWorld::UniversalId& id, - CSMDoc::Document& document) + template + CSVDoc::SubView* SubViewFactory::makeSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) { - return new SubViewT (id, document); + return new SubViewT(id, document); } - - template + template class SubViewFactoryWithCreator : public SubViewFactoryBase { - bool mSorting; + bool mSorting; - public: + public: + SubViewFactoryWithCreator(bool sorting = true); - SubViewFactoryWithCreator (bool sorting = true); - - CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) override; + CSVDoc::SubView* makeSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) override; }; - template - SubViewFactoryWithCreator::SubViewFactoryWithCreator (bool sorting) - : mSorting (sorting) - {} + template + SubViewFactoryWithCreator::SubViewFactoryWithCreator(bool sorting) + : mSorting(sorting) + { + } - template - CSVDoc::SubView *SubViewFactoryWithCreator::makeSubView ( + template + CSVDoc::SubView* SubViewFactoryWithCreator::makeSubView( const CSMWorld::UniversalId& id, CSMDoc::Document& document) { - return new SubViewT (id, document, CreatorFactoryT(), mSorting); + return new SubViewT(id, document, CreatorFactoryT(), mSorting); } } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 4514cfb58..08db4efa5 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -1,46 +1,64 @@ #include "view.hpp" -#include -#include - +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include + +#include +#include +#include #include "../../model/doc/document.hpp" -#include "../../model/prefs/state.hpp" +#include "../../model/doc/state.hpp" + #include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/state.hpp" #include "../../model/world/idtable.hpp" -#include "../world/subviews.hpp" +#include "../world/dialoguesubview.hpp" #include "../world/scenesubview.hpp" +#include "../world/scriptsubview.hpp" +#include "../world/subviews.hpp" #include "../world/tablesubview.hpp" #include "../tools/subviews.hpp" +#include +#include +#include +#include +#include + +#include #include +#include #include -#include "viewmanager.hpp" -#include "operations.hpp" -#include "subview.hpp" #include "globaldebugprofilemenu.hpp" +#include "operations.hpp" #include "runlogsubview.hpp" +#include "subview.hpp" #include "subviewfactoryimp.hpp" +#include "viewmanager.hpp" -void CSVDoc::View::closeEvent (QCloseEvent *event) +QRect desktopRect() { - if (!mViewManager.closeRequest (this)) + QRegion virtualGeometry; + for (auto screen : QGuiApplication::screens()) + { + virtualGeometry += screen->geometry(); + } + return virtualGeometry.boundingRect(); +} + +void CSVDoc::View::closeEvent(QCloseEvent* event) +{ + if (!mViewManager.closeRequest(this)) event->ignore(); else { @@ -51,48 +69,52 @@ void CSVDoc::View::closeEvent (QCloseEvent *event) void CSVDoc::View::setupFileMenu() { - QMenu *file = menuBar()->addMenu (tr ("File")); + QMenu* file = menuBar()->addMenu(tr("File")); QAction* newGame = createMenuEntry("New Game", ":./menu-new-game.png", file, "document-file-newgame"); - connect (newGame, SIGNAL (triggered()), this, SIGNAL (newGameRequest())); + connect(newGame, &QAction::triggered, this, &View::newGameRequest); QAction* newAddon = createMenuEntry("New Addon", ":./menu-new-addon.png", file, "document-file-newaddon"); - connect (newAddon, SIGNAL (triggered()), this, SIGNAL (newAddonRequest())); + connect(newAddon, &QAction::triggered, this, &View::newAddonRequest); QAction* open = createMenuEntry("Open", ":./menu-open.png", file, "document-file-open"); - connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest())); + connect(open, &QAction::triggered, this, &View::loadDocumentRequest); QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); - connect (save, SIGNAL (triggered()), this, SLOT (save())); + connect(save, &QAction::triggered, this, &View::save); mSave = save; + file->addSeparator(); + QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); - connect (verify, SIGNAL (triggered()), this, SLOT (verify())); + connect(verify, &QAction::triggered, this, &View::verify); mVerify = verify; QAction* merge = createMenuEntry("Merge", ":./menu-merge.png", file, "document-file-merge"); - connect (merge, SIGNAL (triggered()), this, SLOT (merge())); + connect(merge, &QAction::triggered, this, &View::merge); mMerge = merge; QAction* loadErrors = createMenuEntry("Error Log", ":./error-log.png", file, "document-file-errorlog"); - connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); + connect(loadErrors, &QAction::triggered, this, &View::loadErrorLog); QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); - connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); + connect(meta, &QAction::triggered, this, &View::addMetaDataSubView); + + file->addSeparator(); QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); - connect (close, SIGNAL (triggered()), this, SLOT (close())); + connect(close, &QAction::triggered, this, &View::close); QAction* exit = createMenuEntry("Exit", ":./menu-exit.png", file, "document-file-exit"); - connect (exit, SIGNAL (triggered()), this, SLOT (exit())); + connect(exit, &QAction::triggered, this, &View::exit); - connect (this, SIGNAL(exitApplicationRequest(CSVDoc::View *)), &mViewManager, SLOT(exitApplication(CSVDoc::View *))); + connect(this, &View::exitApplicationRequest, &mViewManager, &ViewManager::exitApplication); } namespace { - void updateUndoRedoAction(QAction *action, const std::string &settingsKey) + void updateUndoRedoAction(QAction* action, const std::string& settingsKey) { QKeySequence seq; CSMPrefs::State::get().getShortcutManager().getSequence(settingsKey, seq); @@ -113,241 +135,262 @@ void CSVDoc::View::redoActionChanged() void CSVDoc::View::setupEditMenu() { - QMenu *edit = menuBar()->addMenu (tr ("Edit")); + QMenu* edit = menuBar()->addMenu(tr("Edit")); - mUndo = mDocument->getUndoStack().createUndoAction (this, tr("Undo")); + mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo")); setupShortcut("document-edit-undo", mUndo); - connect(mUndo, SIGNAL (changed ()), this, SLOT (undoActionChanged ())); + connect(mUndo, &QAction::changed, this, &View::undoActionChanged); mUndo->setIcon(QIcon(QString::fromStdString(":./menu-undo.png"))); - edit->addAction (mUndo); + edit->addAction(mUndo); - mRedo = mDocument->getUndoStack().createRedoAction (this, tr("Redo")); - connect(mRedo, SIGNAL (changed ()), this, SLOT (redoActionChanged ())); + mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo")); + connect(mRedo, &QAction::changed, this, &View::redoActionChanged); setupShortcut("document-edit-redo", mRedo); mRedo->setIcon(QIcon(QString::fromStdString(":./menu-redo.png"))); - edit->addAction (mRedo); + edit->addAction(mRedo); - QAction* userSettings = createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences"); - connect (userSettings, SIGNAL (triggered()), this, SIGNAL (editSettingsRequest())); + QAction* userSettings + = createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences"); + connect(userSettings, &QAction::triggered, this, &View::editSettingsRequest); QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search"); - connect (search, SIGNAL (triggered()), this, SLOT (addSearchSubView())); + connect(search, &QAction::triggered, this, &View::addSearchSubView); } void CSVDoc::View::setupViewMenu() { - QMenu *view = menuBar()->addMenu (tr ("View")); + QMenu* view = menuBar()->addMenu(tr("View")); - QAction *newWindow = createMenuEntry("New View", ":./menu-new-window.png", view, "document-view-newview"); - connect (newWindow, SIGNAL (triggered()), this, SLOT (newView())); + QAction* newWindow = createMenuEntry("New View", ":./menu-new-window.png", view, "document-view-newview"); + connect(newWindow, &QAction::triggered, this, &View::newView); mShowStatusBar = createMenuEntry("Toggle Status Bar", ":./menu-status-bar.png", view, "document-view-statusbar"); - connect (mShowStatusBar, SIGNAL (toggled (bool)), this, SLOT (toggleShowStatusBar (bool))); - mShowStatusBar->setCheckable (true); - mShowStatusBar->setChecked (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); + connect(mShowStatusBar, &QAction::toggled, this, &View::toggleShowStatusBar); + mShowStatusBar->setCheckable(true); + mShowStatusBar->setChecked(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); - view->addAction (mShowStatusBar); + view->addAction(mShowStatusBar); - QAction *filters = createMenuEntry(CSMWorld::UniversalId::Type_Filters, view, "document-mechanics-filters"); - connect (filters, SIGNAL (triggered()), this, SLOT (addFiltersSubView())); + QAction* filters = createMenuEntry(CSMWorld::UniversalId::Type_Filters, view, "document-mechanics-filters"); + connect(filters, &QAction::triggered, this, &View::addFiltersSubView); } void CSVDoc::View::setupWorldMenu() { - QMenu *world = menuBar()->addMenu (tr ("World")); + QMenu* world = menuBar()->addMenu(tr("World")); - QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); - connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); - - QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); - connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); - - QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables"); - connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView())); + QAction* referenceables + = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables"); + connect(referenceables, &QAction::triggered, this, &View::addReferenceablesSubView); QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references"); - connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView())); + connect(references, &QAction::triggered, this, &View::addReferencesSubView); - QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands"); - connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView())); + world->addSeparator(); - QAction *landTextures = createMenuEntry(CSMWorld::UniversalId::Type_LandTextures, world, "document-world-landtextures"); - connect (landTextures, SIGNAL (triggered()), this, SLOT (addLandTexturesSubView())); + QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); + connect(cells, &QAction::triggered, this, &View::addCellsSubView); - QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid"); - connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); + QAction* lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands"); + connect(lands, &QAction::triggered, this, &View::addLandsSubView); - world->addSeparator(); // items that don't represent single record lists follow here + QAction* landTextures + = createMenuEntry(CSMWorld::UniversalId::Type_LandTextures, world, "document-world-landtextures"); + connect(landTextures, &QAction::triggered, this, &View::addLandTexturesSubView); - QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap"); - connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); + QAction* grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid"); + connect(grid, &QAction::triggered, this, &View::addPathgridSubView); + + world->addSeparator(); + + QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); + connect(regions, &QAction::triggered, this, &View::addRegionsSubView); + + QAction* regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap"); + connect(regionMap, &QAction::triggered, this, &View::addRegionMapSubView); } void CSVDoc::View::setupMechanicsMenu() { - QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics")); - - QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals"); - connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); - - QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings"); - connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); + QMenu* mechanics = menuBar()->addMenu(tr("Mechanics")); QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); - connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); + connect(scripts, &QAction::triggered, this, &View::addScriptsSubView); + + QAction* startScripts + = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); + connect(startScripts, &QAction::triggered, this, &View::addStartScriptsSubView); + + QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals"); + connect(globals, &QAction::triggered, this, &View::addGlobalsSubView); + + QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings"); + connect(gmsts, &QAction::triggered, this, &View::addGmstsSubView); + + mechanics->addSeparator(); QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells"); - connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); + connect(spells, &QAction::triggered, this, &View::addSpellsSubView); - QAction* enchantments = createMenuEntry(CSMWorld::UniversalId::Type_Enchantments, mechanics, "document-mechanics-enchantments"); - connect (enchantments, SIGNAL (triggered()), this, SLOT (addEnchantmentsSubView())); + QAction* enchantments + = createMenuEntry(CSMWorld::UniversalId::Type_Enchantments, mechanics, "document-mechanics-enchantments"); + connect(enchantments, &QAction::triggered, this, &View::addEnchantmentsSubView); - QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects"); - connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView())); - - QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); - connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); + QAction* magicEffects + = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects"); + connect(magicEffects, &QAction::triggered, this, &View::addMagicEffectsSubView); } void CSVDoc::View::setupCharacterMenu() { - QMenu *characters = menuBar()->addMenu (tr ("Characters")); + QMenu* characters = menuBar()->addMenu(tr("Characters")); QAction* skills = createMenuEntry(CSMWorld::UniversalId::Type_Skills, characters, "document-character-skills"); - connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView())); + connect(skills, &QAction::triggered, this, &View::addSkillsSubView); QAction* classes = createMenuEntry(CSMWorld::UniversalId::Type_Classes, characters, "document-character-classes"); - connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView())); + connect(classes, &QAction::triggered, this, &View::addClassesSubView); QAction* factions = createMenuEntry(CSMWorld::UniversalId::Type_Faction, characters, "document-character-factions"); - connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView())); + connect(factions, &QAction::triggered, this, &View::addFactionsSubView); QAction* races = createMenuEntry(CSMWorld::UniversalId::Type_Races, characters, "document-character-races"); - connect (races, SIGNAL (triggered()), this, SLOT (addRacesSubView())); + connect(races, &QAction::triggered, this, &View::addRacesSubView); - QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns"); - connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); + QAction* birthsigns + = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns"); + connect(birthsigns, &QAction::triggered, this, &View::addBirthsignsSubView); + + QAction* bodyParts + = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); + connect(bodyParts, &QAction::triggered, this, &View::addBodyPartsSubView); + + characters->addSeparator(); QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics"); - connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); + connect(topics, &QAction::triggered, this, &View::addTopicsSubView); - QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); - connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); + QAction* topicInfos + = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); + connect(topicInfos, &QAction::triggered, this, &View::addTopicInfosSubView); - QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); - connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); + characters->addSeparator(); - QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); - connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); + QAction* journals + = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); + connect(journals, &QAction::triggered, this, &View::addJournalsSubView); - QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); - connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); + QAction* journalInfos + = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); + connect(journalInfos, &QAction::triggered, this, &View::addJournalInfosSubView); } void CSVDoc::View::setupAssetsMenu() { - QMenu *assets = menuBar()->addMenu (tr ("Assets")); + QMenu* assets = menuBar()->addMenu(tr("Assets")); QAction* reload = createMenuEntry("Reload", ":./menu-reload.png", assets, "document-assets-reload"); - connect (reload, SIGNAL (triggered()), &mDocument->getData(), SLOT (assetsChanged())); + connect(reload, &QAction::triggered, &mDocument->getData(), &CSMWorld::Data::assetsChanged); assets->addSeparator(); QAction* sounds = createMenuEntry(CSMWorld::UniversalId::Type_Sounds, assets, "document-assets-sounds"); - connect (sounds, SIGNAL (triggered()), this, SLOT (addSoundsSubView())); + connect(sounds, &QAction::triggered, this, &View::addSoundsSubView); QAction* soundGens = createMenuEntry(CSMWorld::UniversalId::Type_SoundGens, assets, "document-assets-soundgens"); - connect (soundGens, SIGNAL (triggered()), this, SLOT (addSoundGensSubView())); + connect(soundGens, &QAction::triggered, this, &View::addSoundGensSubView); assets->addSeparator(); // resources follow here QAction* meshes = createMenuEntry(CSMWorld::UniversalId::Type_Meshes, assets, "document-assets-meshes"); - connect (meshes, SIGNAL (triggered()), this, SLOT (addMeshesSubView())); + connect(meshes, &QAction::triggered, this, &View::addMeshesSubView); QAction* icons = createMenuEntry(CSMWorld::UniversalId::Type_Icons, assets, "document-assets-icons"); - connect (icons, SIGNAL (triggered()), this, SLOT (addIconsSubView())); + connect(icons, &QAction::triggered, this, &View::addIconsSubView); QAction* musics = createMenuEntry(CSMWorld::UniversalId::Type_Musics, assets, "document-assets-musics"); - connect (musics, SIGNAL (triggered()), this, SLOT (addMusicsSubView())); + connect(musics, &QAction::triggered, this, &View::addMusicsSubView); QAction* soundFiles = createMenuEntry(CSMWorld::UniversalId::Type_SoundsRes, assets, "document-assets-soundres"); - connect (soundFiles, SIGNAL (triggered()), this, SLOT (addSoundsResSubView())); + connect(soundFiles, &QAction::triggered, this, &View::addSoundsResSubView); QAction* textures = createMenuEntry(CSMWorld::UniversalId::Type_Textures, assets, "document-assets-textures"); - connect (textures, SIGNAL (triggered()), this, SLOT (addTexturesSubView())); + connect(textures, &QAction::triggered, this, &View::addTexturesSubView); QAction* videos = createMenuEntry(CSMWorld::UniversalId::Type_Videos, assets, "document-assets-videos"); - connect (videos, SIGNAL (triggered()), this, SLOT (addVideosSubView())); + connect(videos, &QAction::triggered, this, &View::addVideosSubView); } void CSVDoc::View::setupDebugMenu() { - QMenu *debug = menuBar()->addMenu (tr ("Debug")); + QMenu* debug = menuBar()->addMenu(tr("Debug")); QAction* profiles = createMenuEntry(CSMWorld::UniversalId::Type_DebugProfiles, debug, "document-debug-profiles"); - connect (profiles, SIGNAL (triggered()), this, SLOT (addDebugProfilesSubView())); + connect(profiles, &QAction::triggered, this, &View::addDebugProfilesSubView); debug->addSeparator(); - mGlobalDebugProfileMenu = new GlobalDebugProfileMenu ( - &dynamic_cast (*mDocument->getData().getTableModel ( - CSMWorld::UniversalId::Type_DebugProfiles)), this); + mGlobalDebugProfileMenu = new GlobalDebugProfileMenu( + &dynamic_cast( + *mDocument->getData().getTableModel(CSMWorld::UniversalId::Type_DebugProfiles)), + this); - connect (mGlobalDebugProfileMenu, SIGNAL (triggered (const std::string&)), - this, SLOT (run (const std::string&))); + connect(mGlobalDebugProfileMenu, &GlobalDebugProfileMenu::triggered, this, + [this](const std::string& profile) { this->run(profile, ""); }); - QAction *runDebug = debug->addMenu (mGlobalDebugProfileMenu); - runDebug->setText (tr ("Run OpenMW")); + QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu); + runDebug->setText(tr("Run OpenMW")); setupShortcut("document-debug-run", runDebug); runDebug->setIcon(QIcon(QString::fromStdString(":./run-openmw.png"))); QAction* stopDebug = createMenuEntry("Stop OpenMW", ":./stop-openmw.png", debug, "document-debug-shutdown"); - connect (stopDebug, SIGNAL (triggered()), this, SLOT (stop())); + connect(stopDebug, &QAction::triggered, this, &View::stop); mStopDebug = stopDebug; QAction* runLog = createMenuEntry(CSMWorld::UniversalId::Type_RunLog, debug, "document-debug-runlog"); - connect (runLog, SIGNAL (triggered()), this, SLOT (addRunLogSubView())); + connect(runLog, &QAction::triggered, this, &View::addRunLogSubView); } void CSVDoc::View::setupHelpMenu() { - QMenu *help = menuBar()->addMenu (tr ("Help")); + QMenu* help = menuBar()->addMenu(tr("Help")); QAction* helpInfo = createMenuEntry("Help", ":/info.png", help, "document-help-help"); - connect (helpInfo, SIGNAL (triggered()), this, SLOT (openHelp())); + connect(helpInfo, &QAction::triggered, this, &View::openHelp); QAction* tutorial = createMenuEntry("Tutorial", ":/info.png", help, "document-help-tutorial"); - connect (tutorial, SIGNAL (triggered()), this, SLOT (tutorial())); + connect(tutorial, &QAction::triggered, this, &View::tutorial); QAction* about = createMenuEntry("About OpenMW-CS", ":./info.png", help, "document-help-about"); - connect (about, SIGNAL (triggered()), this, SLOT (infoAbout())); + connect(about, &QAction::triggered, this, &View::infoAbout); QAction* aboutQt = createMenuEntry("About Qt", ":./qt.png", help, "document-help-qt"); - connect (aboutQt, SIGNAL (triggered()), this, SLOT (infoAboutQt())); + connect(aboutQt, &QAction::triggered, this, &View::infoAboutQt); } QAction* CSVDoc::View::createMenuEntry(CSMWorld::UniversalId::Type type, QMenu* menu, const char* shortcutName) { - const std::string title = CSMWorld::UniversalId (type).getTypeName(); - QAction *entry = new QAction(QString::fromStdString(title), this); + const std::string title = CSMWorld::UniversalId(type).getTypeName(); + QAction* entry = new QAction(QString::fromStdString(title), this); setupShortcut(shortcutName, entry); - const std::string iconName = CSMWorld::UniversalId (type).getIcon(); + const std::string iconName = CSMWorld::UniversalId(type).getIcon(); if (!iconName.empty() && iconName != ":placeholder") entry->setIcon(QIcon(QString::fromStdString(iconName))); - menu->addAction (entry); + menu->addAction(entry); return entry; } -QAction* CSVDoc::View::createMenuEntry(const std::string& title, const std::string& iconName, QMenu* menu, const char* shortcutName) +QAction* CSVDoc::View::createMenuEntry( + const std::string& title, const std::string& iconName, QMenu* menu, const char* shortcutName) { - QAction *entry = new QAction(QString::fromStdString(title), this); + QAction* entry = new QAction(QString::fromStdString(title), this); setupShortcut(shortcutName, entry); if (!iconName.empty() && iconName != ":placeholder") entry->setIcon(QIcon(QString::fromStdString(iconName))); - menu->addAction (entry); + menu->addAction(entry); return entry; } @@ -375,30 +418,29 @@ void CSVDoc::View::updateTitle() { std::ostringstream stream; - stream << mDocument->getSavePath().filename().string(); + stream << Files::pathToUnicodeString(mDocument->getSavePath().filename()); if (mDocument->getState() & CSMDoc::State_Modified) - stream << " *"; + stream << " *"; - if (mViewTotal>1) - stream << " [" << (mViewIndex+1) << "/" << mViewTotal << "]"; + if (mViewTotal > 1) + stream << " [" << (mViewIndex + 1) << "/" << mViewTotal << "]"; CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - bool hideTitle = windows["hide-subview"].isTrue() && - mSubViews.size()==1 && !mSubViews.at (0)->isFloating(); + bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size() == 1 && !mSubViews.at(0)->isFloating(); if (hideTitle) - stream << " - " << mSubViews.at (0)->getTitle(); + stream << " - " << mSubViews.at(0)->getTitle(); - setWindowTitle (QString::fromUtf8(stream.str().c_str())); + setWindowTitle(QString::fromUtf8(stream.str().c_str())); } -void CSVDoc::View::updateSubViewIndices(SubView *view) +void CSVDoc::View::updateSubViewIndices(SubView* view) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - if(view && mSubViews.contains(view)) + if (view && mSubViews.contains(view)) { mSubViews.removeOne(view); @@ -407,24 +449,23 @@ void CSVDoc::View::updateSubViewIndices(SubView *view) updateScrollbar(); } - bool hideTitle = windows["hide-subview"].isTrue() && - mSubViews.size()==1 && !mSubViews.at (0)->isFloating(); + bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size() == 1 && !mSubViews.at(0)->isFloating(); updateTitle(); - for (SubView *subView : mSubViews) + for (SubView* subView : mSubViews) { if (!subView->isFloating()) { if (hideTitle) { - subView->setTitleBarWidget (new QWidget (this)); - subView->setWindowTitle (QString::fromUtf8 (subView->getTitle().c_str())); + subView->setTitleBarWidget(new QWidget(this)); + subView->setWindowTitle(QString::fromUtf8(subView->getTitle().c_str())); } else { delete subView->titleBarWidget(); - subView->setTitleBarWidget (nullptr); + subView->setTitleBarWidget(nullptr); } } } @@ -435,38 +476,41 @@ void CSVDoc::View::updateActions() bool editing = !(mDocument->getState() & CSMDoc::State_Locked); bool running = mDocument->getState() & CSMDoc::State_Running; - for (std::vector::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter) - (*iter)->setEnabled (editing); + for (std::vector::iterator iter(mEditingActions.begin()); iter != mEditingActions.end(); ++iter) + (*iter)->setEnabled(editing); - mUndo->setEnabled (editing && mDocument->getUndoStack().canUndo()); - mRedo->setEnabled (editing && mDocument->getUndoStack().canRedo()); + mUndo->setEnabled(editing && mDocument->getUndoStack().canUndo()); + mRedo->setEnabled(editing && mDocument->getUndoStack().canRedo()); - mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running); - mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying)); + mSave->setEnabled(!(mDocument->getState() & CSMDoc::State_Saving) && !running); + mVerify->setEnabled(!(mDocument->getState() & CSMDoc::State_Verifying)); - mGlobalDebugProfileMenu->updateActions (running); - mStopDebug->setEnabled (running); + mGlobalDebugProfileMenu->updateActions(running); + mStopDebug->setEnabled(running); - mMerge->setEnabled (mDocument->getContentFiles().size()>1 && - !(mDocument->getState() & CSMDoc::State_Merging)); + mMerge->setEnabled(mDocument->getContentFiles().size() > 1 && !(mDocument->getState() & CSMDoc::State_Merging)); } -CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) - : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), - mViewTotal (totalViews), mScroll(nullptr), mScrollbarOnly(false) +CSVDoc::View::View(ViewManager& viewManager, CSMDoc::Document* document, int totalViews) + : mViewManager(viewManager) + , mDocument(document) + , mViewIndex(totalViews - 1) + , mViewTotal(totalViews) + , mScroll(nullptr) + , mScrollbarOnly(false) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - int width = std::max (windows["default-width"].toInt(), 300); - int height = std::max (windows["default-height"].toInt(), 300); + int width = std::max(windows["default-width"].toInt(), 300); + int height = std::max(windows["default-height"].toInt(), 300); - resize (width, height); + resize(width, height); - mSubViewWindow.setDockOptions (QMainWindow::AllowNestedDocks); + mSubViewWindow.setDockOptions(QMainWindow::AllowNestedDocks); if (windows["mainwindow-scrollbar"].toString() == "Grow Only") { - setCentralWidget (&mSubViewWindow); + setCentralWidget(&mSubViewWindow); } else { @@ -474,7 +518,7 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to } mOperations = new Operations; - addDockWidget (Qt::BottomDockWidgetArea, mOperations); + addDockWidget(Qt::BottomDockWidgetArea, mOperations); setContextMenuPolicy(Qt::NoContextMenu); @@ -484,32 +528,27 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to updateActions(); - CSVWorld::addSubViewFactories (mSubViewFactory); - CSVTools::addSubViewFactories (mSubViewFactory); + CSVWorld::addSubViewFactories(mSubViewFactory); + CSVTools::addSubViewFactories(mSubViewFactory); - mSubViewFactory.add (CSMWorld::UniversalId::Type_RunLog, new SubViewFactory); + mSubViewFactory.add(CSMWorld::UniversalId::Type_RunLog, new SubViewFactory); - connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int))); + connect(mOperations, &Operations::abortOperation, this, &View::abortOperation); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &View::settingChanged); } -CSVDoc::View::~View() +const CSMDoc::Document* CSVDoc::View::getDocument() const { + return mDocument; } -const CSMDoc::Document *CSVDoc::View::getDocument() const +CSMDoc::Document* CSVDoc::View::getDocument() { - return mDocument; + return mDocument; } -CSMDoc::Document *CSVDoc::View::getDocument() -{ - return mDocument; -} - -void CSVDoc::View::setIndex (int viewIndex, int totalViews) +void CSVDoc::View::setIndex(int viewIndex, int totalViews) { mViewIndex = viewIndex; mViewTotal = totalViews; @@ -521,31 +560,33 @@ void CSVDoc::View::updateDocumentState() updateTitle(); updateActions(); - static const int operations[] = - { - CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching, + static const int operations[] = { + CSMDoc::State_Saving, + CSMDoc::State_Verifying, + CSMDoc::State_Searching, CSMDoc::State_Merging, - -1 // end marker + // end marker + -1, }; - int state = mDocument->getState() ; + int state = mDocument->getState(); - for (int i=0; operations[i]!=-1; ++i) + for (int i = 0; operations[i] != -1; ++i) if (!(state & operations[i])) - mOperations->quitOperation (operations[i]); + mOperations->quitOperation(operations[i]); - QList subViews = findChildren(); + QList subViews = findChildren(); - for (QList::iterator iter (subViews.begin()); iter!=subViews.end(); ++iter) - (*iter)->setEditLock (state & CSMDoc::State_Locked); + for (QList::iterator iter(subViews.begin()); iter != subViews.end(); ++iter) + (*iter)->setEditLock(state & CSMDoc::State_Locked); } -void CSVDoc::View::updateProgress (int current, int max, int type, int threads) +void CSVDoc::View::updateProgress(int current, int max, int type, int threads) { - mOperations->setProgress (current, max, type, threads); + mOperations->setProgress(current, max, type, threads); } -void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::string& hint) +void CSVDoc::View::addSubView(const CSMWorld::UniversalId& id, const std::string& hint) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; @@ -554,57 +595,55 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // User setting to reuse sub views (on a per top level view basis) if (windows["reuse"].isTrue()) { - for (SubView *sb : mSubViews) + for (SubView* sb : mSubViews) { - bool isSubViewReferenceable = - sb->getUniversalId().getType() == CSMWorld::UniversalId::Type_Referenceable; + bool isSubViewReferenceable = sb->getUniversalId().getType() == CSMWorld::UniversalId::Type_Referenceable; - if((isReferenceable && isSubViewReferenceable && id.getId() == sb->getUniversalId().getId()) - || - (!isReferenceable && id == sb->getUniversalId())) + if ((isReferenceable && isSubViewReferenceable && id.getId() == sb->getUniversalId().getId()) + || (!isReferenceable && id == sb->getUniversalId())) { sb->setFocus(); if (!hint.empty()) - sb->useHint (hint); + sb->useHint(hint); return; } } } if (mScroll) - QObject::connect(mScroll->horizontalScrollBar(), - SIGNAL(rangeChanged(int,int)), this, SLOT(moveScrollBarToEnd(int,int))); + QObject::connect(mScroll->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &View::moveScrollBarToEnd); // User setting for limiting the number of sub views per top level view. // Automatically open a new top level view if this number is exceeded // // If the sub view limit setting is one, the sub view title bar is hidden and the // text in the main title bar is adjusted accordingly - if(mSubViews.size() >= windows["max-subviews"].toInt()) // create a new top level view + if (mSubViews.size() >= windows["max-subviews"].toInt()) // create a new top level view { mViewManager.addView(mDocument, id, hint); return; } - SubView *view = nullptr; - if(isReferenceable) + SubView* view = nullptr; + if (isReferenceable) { - view = mSubViewFactory.makeSubView (CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id.getId()), *mDocument); + view = mSubViewFactory.makeSubView( + CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id.getId()), *mDocument); } else { - view = mSubViewFactory.makeSubView (id, *mDocument); + view = mSubViewFactory.makeSubView(id, *mDocument); } assert(view); view->setParent(this); - view->setEditLock (mDocument->getState() & CSMDoc::State_Locked); + view->setEditLock(mDocument->getState() & CSMDoc::State_Locked); mSubViews.append(view); // only after assert int minWidth = windows["minimum-width"].toInt(); - view->setMinimumWidth (minWidth); + view->setMinimumWidth(minWidth); - view->setStatusBar (mShowStatusBar->isChecked()); + view->setStatusBar(mShowStatusBar->isChecked()); // Work out how to deal with additional subviews // @@ -619,40 +658,51 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only"; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) updateWidth(windows["grow-limit"].isTrue(), minWidth); +#else + updateWidth(true, minWidth); +#endif - mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); + mSubViewWindow.addDockWidget(Qt::TopDockWidgetArea, view); updateSubViewIndices(); - connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)), this, - SLOT (addSubView (const CSMWorld::UniversalId&, const std::string&))); + connect(view, &SubView::focusId, this, &View::addSubView); - connect (view, SIGNAL (closeRequest (SubView *)), this, SLOT (closeRequest (SubView *))); + connect(view, qOverload(&SubView::closeRequest), this, &View::closeRequest); - connect (view, SIGNAL (updateTitle()), this, SLOT (updateTitle())); + connect(view, &SubView::updateTitle, this, &View::updateTitle); - connect (view, SIGNAL (updateSubViewIndices (SubView *)), - this, SLOT (updateSubViewIndices (SubView *))); + connect(view, &SubView::updateSubViewIndices, this, &View::updateSubViewIndices); CSVWorld::TableSubView* tableView = dynamic_cast(view); if (tableView) { - connect (this, SIGNAL (requestFocus (const std::string&)), - tableView, SLOT (requestFocus (const std::string&))); + connect(this, &View::requestFocus, tableView, &CSVWorld::TableSubView::requestFocus); } CSVWorld::SceneSubView* sceneView = dynamic_cast(view); if (sceneView) { - connect(sceneView, SIGNAL(requestFocus(const std::string&)), - this, SLOT(onRequestFocus(const std::string&))); + connect(sceneView, &CSVWorld::SceneSubView::requestFocus, this, &View::onRequestFocus); + } + + if (CSMPrefs::State::get()["ID Tables"]["subview-new-window"].isTrue()) + { + CSVWorld::DialogueSubView* dialogueView = dynamic_cast(view); + if (dialogueView) + dialogueView->setFloating(true); + + CSVWorld::ScriptSubView* scriptView = dynamic_cast(view); + if (scriptView) + scriptView->setFloating(true); } view->show(); if (!hint.empty()) - view->useHint (hint); + view->useHint(hint); } void CSVDoc::View::moveScrollBarToEnd(int min, int max) @@ -661,22 +711,21 @@ void CSVDoc::View::moveScrollBarToEnd(int min, int max) { mScroll->horizontalScrollBar()->setValue(max); - QObject::disconnect(mScroll->horizontalScrollBar(), - SIGNAL(rangeChanged(int,int)), this, SLOT(moveScrollBarToEnd(int,int))); + QObject::disconnect(mScroll->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &View::moveScrollBarToEnd); } } -void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) +void CSVDoc::View::settingChanged(const CSMPrefs::Setting* setting) { - if (*setting=="Windows/hide-subview") - updateSubViewIndices (nullptr); - else if (*setting=="Windows/mainwindow-scrollbar") + if (*setting == "Windows/hide-subview") + updateSubViewIndices(nullptr); + else if (*setting == "Windows/mainwindow-scrollbar") { - if (setting->toString()!="Grow Only") + if (setting->toString() != "Grow Only") { if (mScroll) { - if (setting->toString()=="Scrollbar Only") + if (setting->toString() == "Scrollbar Only") { mScrollbarOnly = true; mSubViewWindow.setMinimumWidth(0); @@ -692,10 +741,10 @@ void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) createScrollArea(); } } - else if (mScroll) + else if (mScroll) { mScroll->takeWidget(); - setCentralWidget (&mSubViewWindow); + setCentralWidget(&mSubViewWindow); mScroll->deleteLater(); mScroll = nullptr; } @@ -704,7 +753,7 @@ void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) void CSVDoc::View::newView() { - mViewManager.addView (mDocument); + mViewManager.addView(mDocument); } void CSVDoc::View::save() @@ -725,40 +774,37 @@ void CSVDoc::View::tutorial() void CSVDoc::View::infoAbout() { // Get current OpenMW version - QString versionInfo = (Version::getOpenmwVersionDescription(mDocument->getResourceDir().string())+ + QString versionInfo = (Version::getOpenmwVersionDescription(mDocument->getResourceDir()) + #if defined(__x86_64__) || defined(_M_X64) - " (64-bit)").c_str(); + " (64-bit)") + .c_str(); #else - " (32-bit)").c_str(); + " (32-bit)") + .c_str(); #endif // Get current year - time_t now = time(nullptr); - struct tm tstruct; - char copyrightInfo[40]; - tstruct = *localtime(&now); - strftime(copyrightInfo, sizeof(copyrightInfo), "Copyright © 2008-%Y OpenMW Team", &tstruct); + const auto copyrightInfo = Misc::timeToString(std::chrono::system_clock::now(), "Copyright © 2008-%Y OpenMW Team"); QString aboutText = QString( - "

" - "

OpenMW Construction Set

" - "%1\n\n" - "%2\n\n" - "%3\n\n" - "" - "" - "" - "" - "" - "
%4https://openmw.org
%5https://forum.openmw.org
%6https://gitlab.com/OpenMW/openmw/issues
%7ircs://irc.libera.chat/#openmw
" - "

") - .arg(versionInfo - , tr("OpenMW-CS is a content file editor for OpenMW, a modern, free and open source game engine.") - , tr(copyrightInfo) - , tr("Home Page:") - , tr("Forum:") - , tr("Bug Tracker:") - , tr("IRC:")); + "

" + "

OpenMW Construction Set

" + "%1\n\n" + "%2\n\n" + "%3\n\n" + "" + "" + "" + "" + "" + "
%4https://openmw.org
%5https://forum.openmw.org
%6https://gitlab.com/OpenMW/openmw/issues
%7ircs://irc.libera.chat/#openmw
" + "

") + .arg(versionInfo, + tr("OpenMW-CS is a content file editor for OpenMW, a modern, free and open source game " + "engine."), + tr(copyrightInfo.c_str()), tr("Home Page:"), tr("Forum:"), tr("Bug Tracker:"), + tr("IRC:")); QMessageBox::about(this, "About OpenMW-CS", aboutText); } @@ -770,233 +816,233 @@ void CSVDoc::View::infoAboutQt() void CSVDoc::View::verify() { - addSubView (mDocument->verify()); + addSubView(mDocument->verify()); } void CSVDoc::View::addGlobalsSubView() { - addSubView (CSMWorld::UniversalId::Type_Globals); + addSubView(CSMWorld::UniversalId::Type_Globals); } void CSVDoc::View::addGmstsSubView() { - addSubView (CSMWorld::UniversalId::Type_Gmsts); + addSubView(CSMWorld::UniversalId::Type_Gmsts); } void CSVDoc::View::addSkillsSubView() { - addSubView (CSMWorld::UniversalId::Type_Skills); + addSubView(CSMWorld::UniversalId::Type_Skills); } void CSVDoc::View::addClassesSubView() { - addSubView (CSMWorld::UniversalId::Type_Classes); + addSubView(CSMWorld::UniversalId::Type_Classes); } void CSVDoc::View::addFactionsSubView() { - addSubView (CSMWorld::UniversalId::Type_Factions); + addSubView(CSMWorld::UniversalId::Type_Factions); } void CSVDoc::View::addRacesSubView() { - addSubView (CSMWorld::UniversalId::Type_Races); + addSubView(CSMWorld::UniversalId::Type_Races); } void CSVDoc::View::addSoundsSubView() { - addSubView (CSMWorld::UniversalId::Type_Sounds); + addSubView(CSMWorld::UniversalId::Type_Sounds); } void CSVDoc::View::addScriptsSubView() { - addSubView (CSMWorld::UniversalId::Type_Scripts); + addSubView(CSMWorld::UniversalId::Type_Scripts); } void CSVDoc::View::addRegionsSubView() { - addSubView (CSMWorld::UniversalId::Type_Regions); + addSubView(CSMWorld::UniversalId::Type_Regions); } void CSVDoc::View::addBirthsignsSubView() { - addSubView (CSMWorld::UniversalId::Type_Birthsigns); + addSubView(CSMWorld::UniversalId::Type_Birthsigns); } void CSVDoc::View::addSpellsSubView() { - addSubView (CSMWorld::UniversalId::Type_Spells); + addSubView(CSMWorld::UniversalId::Type_Spells); } void CSVDoc::View::addCellsSubView() { - addSubView (CSMWorld::UniversalId::Type_Cells); + addSubView(CSMWorld::UniversalId::Type_Cells); } void CSVDoc::View::addReferenceablesSubView() { - addSubView (CSMWorld::UniversalId::Type_Referenceables); + addSubView(CSMWorld::UniversalId::Type_Referenceables); } void CSVDoc::View::addReferencesSubView() { - addSubView (CSMWorld::UniversalId::Type_References); + addSubView(CSMWorld::UniversalId::Type_References); } void CSVDoc::View::addRegionMapSubView() { - addSubView (CSMWorld::UniversalId::Type_RegionMap); + addSubView(CSMWorld::UniversalId::Type_RegionMap); } void CSVDoc::View::addFiltersSubView() { - addSubView (CSMWorld::UniversalId::Type_Filters); + addSubView(CSMWorld::UniversalId::Type_Filters); } void CSVDoc::View::addTopicsSubView() { - addSubView (CSMWorld::UniversalId::Type_Topics); + addSubView(CSMWorld::UniversalId::Type_Topics); } void CSVDoc::View::addJournalsSubView() { - addSubView (CSMWorld::UniversalId::Type_Journals); + addSubView(CSMWorld::UniversalId::Type_Journals); } void CSVDoc::View::addTopicInfosSubView() { - addSubView (CSMWorld::UniversalId::Type_TopicInfos); + addSubView(CSMWorld::UniversalId::Type_TopicInfos); } void CSVDoc::View::addJournalInfosSubView() { - addSubView (CSMWorld::UniversalId::Type_JournalInfos); + addSubView(CSMWorld::UniversalId::Type_JournalInfos); } void CSVDoc::View::addEnchantmentsSubView() { - addSubView (CSMWorld::UniversalId::Type_Enchantments); + addSubView(CSMWorld::UniversalId::Type_Enchantments); } void CSVDoc::View::addBodyPartsSubView() { - addSubView (CSMWorld::UniversalId::Type_BodyParts); + addSubView(CSMWorld::UniversalId::Type_BodyParts); } void CSVDoc::View::addSoundGensSubView() { - addSubView (CSMWorld::UniversalId::Type_SoundGens); + addSubView(CSMWorld::UniversalId::Type_SoundGens); } void CSVDoc::View::addMeshesSubView() { - addSubView (CSMWorld::UniversalId::Type_Meshes); + addSubView(CSMWorld::UniversalId::Type_Meshes); } void CSVDoc::View::addIconsSubView() { - addSubView (CSMWorld::UniversalId::Type_Icons); + addSubView(CSMWorld::UniversalId::Type_Icons); } void CSVDoc::View::addMusicsSubView() { - addSubView (CSMWorld::UniversalId::Type_Musics); + addSubView(CSMWorld::UniversalId::Type_Musics); } void CSVDoc::View::addSoundsResSubView() { - addSubView (CSMWorld::UniversalId::Type_SoundsRes); + addSubView(CSMWorld::UniversalId::Type_SoundsRes); } void CSVDoc::View::addMagicEffectsSubView() { - addSubView (CSMWorld::UniversalId::Type_MagicEffects); + addSubView(CSMWorld::UniversalId::Type_MagicEffects); } void CSVDoc::View::addTexturesSubView() { - addSubView (CSMWorld::UniversalId::Type_Textures); + addSubView(CSMWorld::UniversalId::Type_Textures); } void CSVDoc::View::addVideosSubView() { - addSubView (CSMWorld::UniversalId::Type_Videos); + addSubView(CSMWorld::UniversalId::Type_Videos); } void CSVDoc::View::addDebugProfilesSubView() { - addSubView (CSMWorld::UniversalId::Type_DebugProfiles); + addSubView(CSMWorld::UniversalId::Type_DebugProfiles); } void CSVDoc::View::addRunLogSubView() { - addSubView (CSMWorld::UniversalId::Type_RunLog); + addSubView(CSMWorld::UniversalId::Type_RunLog); } void CSVDoc::View::addLandsSubView() { - addSubView (CSMWorld::UniversalId::Type_Lands); + addSubView(CSMWorld::UniversalId::Type_Lands); } void CSVDoc::View::addLandTexturesSubView() { - addSubView (CSMWorld::UniversalId::Type_LandTextures); + addSubView(CSMWorld::UniversalId::Type_LandTextures); } void CSVDoc::View::addPathgridSubView() { - addSubView (CSMWorld::UniversalId::Type_Pathgrids); + addSubView(CSMWorld::UniversalId::Type_Pathgrids); } void CSVDoc::View::addStartScriptsSubView() { - addSubView (CSMWorld::UniversalId::Type_StartScripts); + addSubView(CSMWorld::UniversalId::Type_StartScripts); } void CSVDoc::View::addSearchSubView() { - addSubView (mDocument->newSearch()); + addSubView(mDocument->newSearch()); } void CSVDoc::View::addMetaDataSubView() { - addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_MetaData, "sys::meta")); + addSubView(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_MetaData, "sys::meta")); } -void CSVDoc::View::abortOperation (int type) +void CSVDoc::View::abortOperation(int type) { - mDocument->abortOperation (type); + mDocument->abortOperation(type); updateActions(); } -CSVDoc::Operations *CSVDoc::View::getOperations() const +CSVDoc::Operations* CSVDoc::View::getOperations() const { return mOperations; } void CSVDoc::View::exit() { - emit exitApplicationRequest (this); + emit exitApplicationRequest(this); } -void CSVDoc::View::resizeViewWidth (int width) +void CSVDoc::View::resizeViewWidth(int width) { if (width >= 0) - resize (width, geometry().height()); + resize(width, geometry().height()); } -void CSVDoc::View::resizeViewHeight (int height) +void CSVDoc::View::resizeViewHeight(int height) { if (height >= 0) - resize (geometry().width(), height); + resize(geometry().width(), height); } -void CSVDoc::View::toggleShowStatusBar (bool show) +void CSVDoc::View::toggleShowStatusBar(bool show) { - for (QObject *view : mSubViewWindow.children()) + for (QObject* view : mSubViewWindow.children()) { - if (CSVDoc::SubView *subView = dynamic_cast (view)) - subView->setStatusBar (show); + if (CSVDoc::SubView* subView = dynamic_cast(view)) + subView->setStatusBar(show); } } @@ -1007,12 +1053,12 @@ void CSVDoc::View::toggleStatusBar(bool checked) void CSVDoc::View::loadErrorLog() { - addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_LoadErrorLog, 0)); + addSubView(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_LoadErrorLog, 0)); } -void CSVDoc::View::run (const std::string& profile, const std::string& startupInstruction) +void CSVDoc::View::run(const std::string& profile, const std::string& startupInstruction) { - mDocument->startRunning (profile, startupInstruction); + mDocument->startRunning(profile, startupInstruction); } void CSVDoc::View::stop() @@ -1020,23 +1066,23 @@ void CSVDoc::View::stop() mDocument->stopRunning(); } -void CSVDoc::View::closeRequest (SubView *subView) +void CSVDoc::View::closeRequest(SubView* subView) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - if (mSubViews.size()>1 || mViewTotal<=1 || !windows["hide-subview"].isTrue()) + if (mSubViews.size() > 1 || mViewTotal <= 1 || !windows["hide-subview"].isTrue()) { subView->deleteLater(); - mSubViews.removeOne (subView); + mSubViews.removeOne(subView); } - else if (mViewManager.closeRequest (this)) - mViewManager.removeDocAndView (mDocument); + else if (mViewManager.closeRequest(this)) + mViewManager.removeDocAndView(mDocument); } void CSVDoc::View::updateScrollbar() { QRect rect; - QWidget *topLevel = QApplication::topLevelAt(pos()); + QWidget* topLevel = QApplication::topLevelAt(pos()); if (topLevel) rect = topLevel->rect(); else @@ -1050,7 +1096,7 @@ void CSVDoc::View::updateScrollbar() int frameWidth = frameGeometry().width() - width(); - if ((newWidth+frameWidth) >= rect.width()) + if ((newWidth + frameWidth) >= rect.width()) mSubViewWindow.setMinimumWidth(newWidth); else mSubViewWindow.setMinimumWidth(0); @@ -1058,34 +1104,33 @@ void CSVDoc::View::updateScrollbar() void CSVDoc::View::merge() { - emit mergeDocument (mDocument); + emit mergeDocument(mDocument); } void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth) { - QDesktopWidget *dw = QApplication::desktop(); QRect rect; if (isGrowLimit) - rect = dw->screenGeometry(this); + rect = QApplication::screenAt(pos())->geometry(); else - rect = QGuiApplication::screens().at(dw->screenNumber(this))->geometry(); + rect = desktopRect(); if (!mScrollbarOnly && mScroll && mSubViews.size() > 1) { - int newWidth = width()+minSubViewWidth; + int newWidth = width() + minSubViewWidth; int frameWidth = frameGeometry().width() - width(); - if (newWidth+frameWidth <= rect.width()) + if (newWidth + frameWidth <= rect.width()) { resize(newWidth, height()); // WARNING: below code assumes that new subviews are added to the right - if (x() > rect.width()-(newWidth+frameWidth)) - move(rect.width()-(newWidth+frameWidth), y()); // shift left to stay within the screen + if (x() > rect.width() - (newWidth + frameWidth)) + move(rect.width() - (newWidth + frameWidth), y()); // shift left to stay within the screen } else { // full width - resize(rect.width()-frameWidth, height()); - mSubViewWindow.setMinimumWidth(mSubViewWindow.width()+minSubViewWidth); + resize(rect.width() - frameWidth, height()); + mSubViewWindow.setMinimumWidth(mSubViewWindow.width() + minSubViewWidth); move(0, y()); } } @@ -1099,15 +1144,15 @@ void CSVDoc::View::createScrollArea() setCentralWidget(mScroll); } -void CSVDoc::View::onRequestFocus (const std::string& id) +void CSVDoc::View::onRequestFocus(const std::string& id) { - if(CSMPrefs::get()["3D Scene Editing"]["open-list-view"].isTrue()) + if (CSMPrefs::get()["3D Scene Editing"]["open-list-view"].isTrue()) { addReferencesSubView(); emit requestFocus(id); } else { - addSubView(CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Reference, id)); + addSubView(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Reference, id)); } } diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 322bcdfb7..165cb0da8 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -1,15 +1,20 @@ #ifndef CSV_DOC_VIEW_H #define CSV_DOC_VIEW_H +#include #include -#include +#include #include +#include + #include "subviewfactory.hpp" class QAction; -class QDockWidget; +class QCloseEvent; +class QMenu; +class QObject; class QScrollArea; namespace CSMDoc @@ -17,11 +22,6 @@ namespace CSMDoc class Document; } -namespace CSMWorld -{ - class UniversalId; -} - namespace CSMPrefs { class Setting; @@ -30,246 +30,242 @@ namespace CSMPrefs namespace CSVDoc { class ViewManager; + class SubView; class Operations; class GlobalDebugProfileMenu; class View : public QMainWindow { - Q_OBJECT + Q_OBJECT - ViewManager& mViewManager; - CSMDoc::Document *mDocument; - int mViewIndex; - int mViewTotal; - QList mSubViews; - QAction *mUndo; - QAction *mRedo; - QAction *mSave; - QAction *mVerify; - QAction *mShowStatusBar; - QAction *mStopDebug; - QAction *mMerge; - std::vector mEditingActions; - Operations *mOperations; - SubViewFactoryManager mSubViewFactory; - QMainWindow mSubViewWindow; - GlobalDebugProfileMenu *mGlobalDebugProfileMenu; - QScrollArea *mScroll; - bool mScrollbarOnly; + ViewManager& mViewManager; + CSMDoc::Document* mDocument; + int mViewIndex; + int mViewTotal; + QList mSubViews; + QAction* mUndo; + QAction* mRedo; + QAction* mSave; + QAction* mVerify; + QAction* mShowStatusBar; + QAction* mStopDebug; + QAction* mMerge; + std::vector mEditingActions; + Operations* mOperations; + SubViewFactoryManager mSubViewFactory; + QMainWindow mSubViewWindow; + GlobalDebugProfileMenu* mGlobalDebugProfileMenu; + QScrollArea* mScroll; + bool mScrollbarOnly; + private: + void closeEvent(QCloseEvent* event) override; - // not implemented - View (const View&); - View& operator= (const View&); + QAction* createMenuEntry(CSMWorld::UniversalId::Type type, QMenu* menu, const char* shortcutName); + QAction* createMenuEntry( + const std::string& title, const std::string& iconName, QMenu* menu, const char* shortcutName); - private: + void setupFileMenu(); - void closeEvent (QCloseEvent *event) override; + void setupEditMenu(); - QAction* createMenuEntry(CSMWorld::UniversalId::Type type, QMenu* menu, const char* shortcutName); - QAction* createMenuEntry(const std::string& title, const std::string& iconName, QMenu* menu, const char* shortcutName); + void setupViewMenu(); - void setupFileMenu(); + void setupWorldMenu(); - void setupEditMenu(); + void setupMechanicsMenu(); - void setupViewMenu(); + void setupCharacterMenu(); - void setupWorldMenu(); + void setupAssetsMenu(); - void setupMechanicsMenu(); + void setupDebugMenu(); - void setupCharacterMenu(); + void setupHelpMenu(); - void setupAssetsMenu(); + void setupUi(); - void setupDebugMenu(); + void setupShortcut(const char* name, QAction* action); - void setupHelpMenu(); + void updateActions(); - void setupUi(); + void exitApplication(); - void setupShortcut(const char* name, QAction* action); + /// User preference function + void resizeViewWidth(int width); - void updateActions(); + /// User preference function + void resizeViewHeight(int height); - void exitApplication(); + void updateScrollbar(); + void updateWidth(bool isGrowLimit, int minSubViewWidth); + void createScrollArea(); - /// User preference function - void resizeViewWidth (int width); + public: + View(ViewManager& viewManager, CSMDoc::Document* document, int totalViews); + ///< The ownership of \a document is not transferred to *this. + View(const View&) = delete; + View& operator=(const View&) = delete; + ~View() override = default; - /// User preference function - void resizeViewHeight (int height); + const CSMDoc::Document* getDocument() const; - void updateScrollbar(); - void updateWidth(bool isGrowLimit, int minSubViewWidth); - void createScrollArea(); - public: + CSMDoc::Document* getDocument(); - View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews); + void setIndex(int viewIndex, int totalViews); - ///< The ownership of \a document is not transferred to *this. + void updateDocumentState(); - virtual ~View(); + void updateProgress(int current, int max, int type, int threads); - const CSMDoc::Document *getDocument() const; + void toggleStatusBar(bool checked); - CSMDoc::Document *getDocument(); + Operations* getOperations() const; - void setIndex (int viewIndex, int totalViews); + signals: - void updateDocumentState(); + void newGameRequest(); - void updateProgress (int current, int max, int type, int threads); + void newAddonRequest(); - void toggleStatusBar(bool checked); + void loadDocumentRequest(); - Operations *getOperations() const; + void exitApplicationRequest(CSVDoc::View* view); - signals: + void editSettingsRequest(); - void newGameRequest(); + void mergeDocument(CSMDoc::Document* document); - void newAddonRequest(); + void requestFocus(const std::string& id); - void loadDocumentRequest(); + public slots: - void exitApplicationRequest (CSVDoc::View *view); + void addSubView(const CSMWorld::UniversalId& id, const std::string& hint = ""); + ///< \param hint Suggested view point (e.g. coordinates in a 3D scene or a line number + /// in a script). - void editSettingsRequest(); + void abortOperation(int type); - void mergeDocument (CSMDoc::Document *document); + void updateTitle(); - void requestFocus (const std::string& id); + // called when subviews are added or removed + void updateSubViewIndices(SubView* view = nullptr); - public slots: + private slots: - void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = ""); - ///< \param hint Suggested view point (e.g. coordinates in a 3D scene or a line number - /// in a script). + void settingChanged(const CSMPrefs::Setting* setting); - void abortOperation (int type); + void undoActionChanged(); - void updateTitle(); + void redoActionChanged(); - // called when subviews are added or removed - void updateSubViewIndices (SubView *view = nullptr); + void newView(); - private slots: + void save(); - void settingChanged (const CSMPrefs::Setting *setting); + void exit(); - void undoActionChanged(); + static void openHelp(); - void redoActionChanged(); + static void tutorial(); - void newView(); + void infoAbout(); - void save(); + void infoAboutQt(); - void exit(); + void verify(); - static void openHelp(); + void addGlobalsSubView(); - static void tutorial(); + void addGmstsSubView(); - void infoAbout(); + void addSkillsSubView(); - void infoAboutQt(); + void addClassesSubView(); - void verify(); + void addFactionsSubView(); - void addGlobalsSubView(); + void addRacesSubView(); - void addGmstsSubView(); + void addSoundsSubView(); - void addSkillsSubView(); + void addScriptsSubView(); - void addClassesSubView(); + void addRegionsSubView(); - void addFactionsSubView(); + void addBirthsignsSubView(); - void addRacesSubView(); + void addSpellsSubView(); - void addSoundsSubView(); + void addCellsSubView(); - void addScriptsSubView(); + void addReferenceablesSubView(); - void addRegionsSubView(); + void addReferencesSubView(); - void addBirthsignsSubView(); + void addRegionMapSubView(); - void addSpellsSubView(); + void addFiltersSubView(); - void addCellsSubView(); + void addTopicsSubView(); - void addReferenceablesSubView(); + void addJournalsSubView(); - void addReferencesSubView(); + void addTopicInfosSubView(); - void addRegionMapSubView(); + void addJournalInfosSubView(); - void addFiltersSubView(); + void addEnchantmentsSubView(); - void addTopicsSubView(); + void addBodyPartsSubView(); - void addJournalsSubView(); + void addSoundGensSubView(); - void addTopicInfosSubView(); + void addMagicEffectsSubView(); - void addJournalInfosSubView(); + void addMeshesSubView(); - void addEnchantmentsSubView(); + void addIconsSubView(); - void addBodyPartsSubView(); + void addMusicsSubView(); - void addSoundGensSubView(); + void addSoundsResSubView(); - void addMagicEffectsSubView(); + void addTexturesSubView(); - void addMeshesSubView(); + void addVideosSubView(); - void addIconsSubView(); + void addDebugProfilesSubView(); - void addMusicsSubView(); + void addRunLogSubView(); - void addSoundsResSubView(); + void addLandsSubView(); - void addTexturesSubView(); + void addLandTexturesSubView(); - void addVideosSubView(); + void addPathgridSubView(); - void addDebugProfilesSubView(); + void addStartScriptsSubView(); - void addRunLogSubView(); + void addSearchSubView(); - void addLandsSubView(); + void addMetaDataSubView(); - void addLandTexturesSubView(); + void toggleShowStatusBar(bool show); - void addPathgridSubView(); + void loadErrorLog(); - void addStartScriptsSubView(); + void run(const std::string& profile, const std::string& startupInstruction = ""); - void addSearchSubView(); + void stop(); - void addMetaDataSubView(); + void closeRequest(SubView* subView); - void toggleShowStatusBar (bool show); + void moveScrollBarToEnd(int min, int max); - void loadErrorLog(); + void merge(); - void run (const std::string& profile, const std::string& startupInstruction = ""); - - void stop(); - - void closeRequest (SubView *subView); - - void moveScrollBarToEnd(int min, int max); - - void merge(); - - void onRequestFocus (const std::string& id); + void onRequestFocus(const std::string& id); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 95f9f606d..dff4426ba 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -1,72 +1,80 @@ #include "viewmanager.hpp" -#include +#include +#include +#include #include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include #include -#include #include #include -#include "../../model/doc/documentmanager.hpp" #include "../../model/doc/document.hpp" -#include "../../model/world/columns.hpp" -#include "../../model/world/idcompletionmanager.hpp" +#include "../../model/doc/documentmanager.hpp" #include "../../model/prefs/state.hpp" -#include "../world/util.hpp" -#include "../world/enumdelegate.hpp" -#include "../world/vartypedelegate.hpp" -#include "../world/recordstatusdelegate.hpp" -#include "../world/idtypedelegate.hpp" -#include "../world/idcompletiondelegate.hpp" #include "../world/colordelegate.hpp" +#include "../world/enumdelegate.hpp" +#include "../world/idcompletiondelegate.hpp" +#include "../world/idtypedelegate.hpp" +#include "../world/recordstatusdelegate.hpp" +#include "../world/vartypedelegate.hpp" #include "view.hpp" void CSVDoc::ViewManager::updateIndices() { - std::map > documents; + std::map> documents; - for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + for (std::vector::const_iterator iter(mViews.begin()); iter != mViews.end(); ++iter) { - std::map >::iterator document = documents.find ((*iter)->getDocument()); + std::map>::iterator document = documents.find((*iter)->getDocument()); - if (document==documents.end()) - document = - documents.insert ( - std::make_pair ((*iter)->getDocument(), std::make_pair (0, countViews ((*iter)->getDocument())))). - first; + if (document == documents.end()) + document = documents + .insert(std::make_pair( + (*iter)->getDocument(), std::make_pair(0, countViews((*iter)->getDocument())))) + .first; - (*iter)->setIndex (document->second.first++, document->second.second); + (*iter)->setIndex(document->second.first++, document->second.second); } } -CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) - : mDocumentManager (documentManager), mExitOnSaveStateChange(false), mUserWarned(false) +CSVDoc::ViewManager::ViewManager(CSMDoc::DocumentManager& documentManager) + : mDocumentManager(documentManager) + , mExitOnSaveStateChange(false) + , mUserWarned(false) { mDelegateFactories = new CSVWorld::CommandDelegateFactoryCollection; - mDelegateFactories->add (CSMWorld::ColumnBase::Display_GmstVarType, - new CSVWorld::VarTypeDelegateFactory (ESM::VT_None, ESM::VT_String, ESM::VT_Int, ESM::VT_Float)); + mDelegateFactories->add(CSMWorld::ColumnBase::Display_GmstVarType, + new CSVWorld::VarTypeDelegateFactory(ESM::VT_None, ESM::VT_String, ESM::VT_Int, ESM::VT_Float)); - mDelegateFactories->add (CSMWorld::ColumnBase::Display_GlobalVarType, - new CSVWorld::VarTypeDelegateFactory (ESM::VT_Short, ESM::VT_Long, ESM::VT_Float)); + mDelegateFactories->add(CSMWorld::ColumnBase::Display_GlobalVarType, + new CSVWorld::VarTypeDelegateFactory(ESM::VT_Short, ESM::VT_Long, ESM::VT_Float)); - mDelegateFactories->add (CSMWorld::ColumnBase::Display_RecordState, - new CSVWorld::RecordStatusDelegateFactory()); + mDelegateFactories->add(CSMWorld::ColumnBase::Display_RecordState, new CSVWorld::RecordStatusDelegateFactory()); - mDelegateFactories->add (CSMWorld::ColumnBase::Display_RefRecordType, - new CSVWorld::IdTypeDelegateFactory()); + mDelegateFactories->add(CSMWorld::ColumnBase::Display_RefRecordType, new CSVWorld::IdTypeDelegateFactory()); - mDelegateFactories->add (CSMWorld::ColumnBase::Display_Colour, - new CSVWorld::ColorDelegateFactory()); + mDelegateFactories->add(CSMWorld::ColumnBase::Display_Colour, new CSVWorld::ColorDelegateFactory()); std::vector idCompletionColumns = CSMWorld::IdCompletionManager::getDisplayTypes(); for (std::vector::const_iterator current = idCompletionColumns.begin(); - current != idCompletionColumns.end(); - ++current) + current != idCompletionColumns.end(); ++current) { mDelegateFactories->add(*current, new CSVWorld::IdCompletionDelegateFactory()); } @@ -78,8 +86,7 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) bool mAllowNone; }; - static const Mapping sMapping[] = - { + static const Mapping sMapping[] = { { CSMWorld::ColumnBase::Display_Specialisation, CSMWorld::Columns::ColumnId_Specialisation, false }, { CSMWorld::ColumnBase::Display_Attribute, CSMWorld::Columns::ColumnId_Attribute, true }, { CSMWorld::ColumnBase::Display_SpellType, CSMWorld::Columns::ColumnId_SpellType, false }, @@ -109,80 +116,68 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_BookType, CSMWorld::Columns::ColumnId_BookType, false }, { CSMWorld::ColumnBase::Display_BloodType, CSMWorld::Columns::ColumnId_BloodType, false }, { CSMWorld::ColumnBase::Display_EmitterType, CSMWorld::Columns::ColumnId_EmitterType, false }, - { CSMWorld::ColumnBase::Display_GenderNpc, CSMWorld::Columns::ColumnId_Gender, false } + { CSMWorld::ColumnBase::Display_GenderNpc, CSMWorld::Columns::ColumnId_Gender, false }, }; - for (std::size_t i=0; iadd (sMapping[i].mDisplay, new CSVWorld::EnumDelegateFactory ( - CSMWorld::Columns::getEnums (sMapping[i].mColumnId), sMapping[i].mAllowNone)); + for (std::size_t i = 0; i < sizeof(sMapping) / sizeof(Mapping); ++i) + mDelegateFactories->add(sMapping[i].mDisplay, + new CSVWorld::EnumDelegateFactory( + CSMWorld::Columns::getEnums(sMapping[i].mColumnId), sMapping[i].mAllowNone)); - connect (&mDocumentManager, SIGNAL (loadRequest (CSMDoc::Document *)), - &mLoader, SLOT (add (CSMDoc::Document *))); + connect(&mDocumentManager, &CSMDoc::DocumentManager::loadRequest, &mLoader, &Loader::add); - connect ( - &mDocumentManager, SIGNAL (loadingStopped (CSMDoc::Document *, bool, const std::string&)), - &mLoader, SLOT (loadingStopped (CSMDoc::Document *, bool, const std::string&))); + connect(&mDocumentManager, &CSMDoc::DocumentManager::loadingStopped, &mLoader, &Loader::loadingStopped); - connect ( - &mDocumentManager, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int)), - &mLoader, SLOT (nextStage (CSMDoc::Document *, const std::string&, int))); + connect(&mDocumentManager, &CSMDoc::DocumentManager::nextStage, &mLoader, &Loader::nextStage); - connect ( - &mDocumentManager, SIGNAL (nextRecord (CSMDoc::Document *, int)), - &mLoader, SLOT (nextRecord (CSMDoc::Document *, int))); + connect(&mDocumentManager, &CSMDoc::DocumentManager::nextRecord, &mLoader, &Loader::nextRecord); - connect ( - &mDocumentManager, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), - &mLoader, SLOT (loadMessage (CSMDoc::Document *, const std::string&))); + connect(&mDocumentManager, &CSMDoc::DocumentManager::loadMessage, &mLoader, &Loader::loadMessage); - connect ( - &mLoader, SIGNAL (cancel (CSMDoc::Document *)), - &mDocumentManager, SIGNAL (cancelLoading (CSMDoc::Document *))); + connect(&mLoader, &Loader::cancel, &mDocumentManager, &CSMDoc::DocumentManager::cancelLoading); - connect ( - &mLoader, SIGNAL (close (CSMDoc::Document *)), - &mDocumentManager, SLOT (removeDocument (CSMDoc::Document *))); + connect(&mLoader, &Loader::close, &mDocumentManager, &CSMDoc::DocumentManager::removeDocument); } CSVDoc::ViewManager::~ViewManager() { delete mDelegateFactories; - for (std::vector::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + for (std::vector::iterator iter(mViews.begin()); iter != mViews.end(); ++iter) delete *iter; } -CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) +CSVDoc::View* CSVDoc::ViewManager::addView(CSMDoc::Document* document) { - if (countViews (document)==0) + if (countViews(document) == 0) { // new document - connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), - this, SLOT (documentStateChanged (int, CSMDoc::Document *))); + connect(document, &CSMDoc::Document::stateChanged, this, &ViewManager::documentStateChanged); - connect (document, SIGNAL (progress (int, int, int, int, CSMDoc::Document *)), - this, SLOT (progress (int, int, int, int, CSMDoc::Document *))); + connect(document, qOverload(&CSMDoc::Document::progress), this, + &ViewManager::progress); } - View *view = new View (*this, document, countViews (document)+1); + View* view = new View(*this, document, countViews(document) + 1); - mViews.push_back (view); + mViews.push_back(view); - view->toggleStatusBar (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); + view->toggleStatusBar(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); view->show(); - connect (view, SIGNAL (newGameRequest ()), this, SIGNAL (newGameRequest())); - connect (view, SIGNAL (newAddonRequest ()), this, SIGNAL (newAddonRequest())); - connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); - connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest())); - connect (view, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SIGNAL (mergeDocument (CSMDoc::Document *))); + connect(view, &View::newGameRequest, this, &ViewManager::newGameRequest); + connect(view, &View::newAddonRequest, this, &ViewManager::newAddonRequest); + connect(view, &View::loadDocumentRequest, this, &ViewManager::loadDocumentRequest); + connect(view, &View::editSettingsRequest, this, &ViewManager::editSettingsRequest); + connect(view, &View::mergeDocument, this, &ViewManager::mergeDocument); updateIndices(); return view; } -CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document, const CSMWorld::UniversalId& id, const std::string& hint) +CSVDoc::View* CSVDoc::ViewManager::addView( + CSMDoc::Document* document, const CSMWorld::UniversalId& id, const std::string& hint) { View* view = addView(document); view->addSubView(id, hint); @@ -190,33 +185,33 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document, const CS return view; } -int CSVDoc::ViewManager::countViews (const CSMDoc::Document *document) const +int CSVDoc::ViewManager::countViews(const CSMDoc::Document* document) const { int count = 0; - for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) - if ((*iter)->getDocument()==document) + for (std::vector::const_iterator iter(mViews.begin()); iter != mViews.end(); ++iter) + if ((*iter)->getDocument() == document) ++count; return count; } -bool CSVDoc::ViewManager::closeRequest (View *view) +bool CSVDoc::ViewManager::closeRequest(View* view) { - std::vector::iterator iter = std::find (mViews.begin(), mViews.end(), view); + std::vector::iterator iter = std::find(mViews.begin(), mViews.end(), view); bool continueWithClose = false; - if (iter!=mViews.end()) + if (iter != mViews.end()) { - bool last = countViews (view->getDocument())<=1; + bool last = countViews(view->getDocument()) <= 1; if (last) - continueWithClose = notifySaveOnClose (view); + continueWithClose = notifySaveOnClose(view); else { (*iter)->deleteLater(); - mViews.erase (iter); + mViews.erase(iter); updateIndices(); } @@ -226,16 +221,16 @@ bool CSVDoc::ViewManager::closeRequest (View *view) } // NOTE: This method assumes that it is called only if the last document -void CSVDoc::ViewManager::removeDocAndView (CSMDoc::Document *document) +void CSVDoc::ViewManager::removeDocAndView(CSMDoc::Document* document) { - for (std::vector::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) + for (std::vector::iterator iter(mViews.begin()); iter != mViews.end(); ++iter) { // the first match should also be the only match - if((*iter)->getDocument() == document) + if ((*iter)->getDocument() == document) { mDocumentManager.removeDocument(document); (*iter)->deleteLater(); - mViews.erase (iter); + mViews.erase(iter); updateIndices(); return; @@ -243,43 +238,43 @@ void CSVDoc::ViewManager::removeDocAndView (CSMDoc::Document *document) } } -bool CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view) +bool CSVDoc::ViewManager::notifySaveOnClose(CSVDoc::View* view) { bool result = true; - CSMDoc::Document *document = view->getDocument(); + CSMDoc::Document* document = view->getDocument(); - //notify user of saving in progress - if ( (document->getState() & CSMDoc::State_Saving) ) - result = showSaveInProgressMessageBox (view); + // notify user of saving in progress + if ((document->getState() & CSMDoc::State_Saving)) + result = showSaveInProgressMessageBox(view); - //notify user of unsaved changes and process response - else if ( document->getState() & CSMDoc::State_Modified) - result = showModifiedDocumentMessageBox (view); + // notify user of unsaved changes and process response + else if (document->getState() & CSMDoc::State_Modified) + result = showModifiedDocumentMessageBox(view); return result; } -bool CSVDoc::ViewManager::showModifiedDocumentMessageBox (CSVDoc::View *view) +bool CSVDoc::ViewManager::showModifiedDocumentMessageBox(CSVDoc::View* view) { emit closeMessageBox(); QMessageBox messageBox(view); - CSMDoc::Document *document = view->getDocument(); + CSMDoc::Document* document = view->getDocument(); - messageBox.setWindowTitle (QString::fromUtf8(document->getSavePath().filename().string().c_str())); - messageBox.setText ("The document has been modified."); - messageBox.setInformativeText ("Do you want to save your changes?"); - messageBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); - messageBox.setDefaultButton (QMessageBox::Save); - messageBox.setWindowModality (Qt::NonModal); + messageBox.setWindowTitle(Files::pathToQString(document->getSavePath().filename())); + messageBox.setText("The document has been modified."); + messageBox.setInformativeText("Do you want to save your changes?"); + messageBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + messageBox.setDefaultButton(QMessageBox::Save); + messageBox.setWindowModality(Qt::NonModal); messageBox.hide(); messageBox.show(); bool retVal = true; - connect (this, SIGNAL (closeMessageBox()), &messageBox, SLOT (close())); + connect(this, &ViewManager::closeMessageBox, &messageBox, &QMessageBox::close); - connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); + connect(document, &CSMDoc::Document::stateChanged, this, &ViewManager::onExitWarningHandler); mUserWarned = true; int response = messageBox.exec(); @@ -292,65 +287,64 @@ bool CSVDoc::ViewManager::showModifiedDocumentMessageBox (CSVDoc::View *view) document->save(); mExitOnSaveStateChange = true; retVal = false; - break; + break; case QMessageBox::Discard: - disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); - break; + disconnect(document, &CSMDoc::Document::stateChanged, this, &ViewManager::onExitWarningHandler); + break; case QMessageBox::Cancel: - //disconnect to prevent unintended view closures - disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); + // disconnect to prevent unintended view closures + disconnect(document, &CSMDoc::Document::stateChanged, this, &ViewManager::onExitWarningHandler); retVal = false; - break; + break; default: - break; - + break; } return retVal; } -bool CSVDoc::ViewManager::showSaveInProgressMessageBox (CSVDoc::View *view) +bool CSVDoc::ViewManager::showSaveInProgressMessageBox(CSVDoc::View* view) { QMessageBox messageBox; - CSMDoc::Document *document = view->getDocument(); + CSMDoc::Document* document = view->getDocument(); - messageBox.setText ("The document is currently being saved."); + messageBox.setText("The document is currently being saved."); messageBox.setInformativeText("Do you want to close now and abort saving, or wait until saving has completed?"); - QPushButton* waitButton = messageBox.addButton (tr("Wait"), QMessageBox::YesRole); - QPushButton* closeButton = messageBox.addButton (tr("Close Now"), QMessageBox::RejectRole); - QPushButton* cancelButton = messageBox.addButton (tr("Cancel"), QMessageBox::NoRole); + QPushButton* waitButton = messageBox.addButton(tr("Wait"), QMessageBox::YesRole); + QPushButton* closeButton = messageBox.addButton(tr("Close Now"), QMessageBox::RejectRole); + QPushButton* cancelButton = messageBox.addButton(tr("Cancel"), QMessageBox::NoRole); - messageBox.setDefaultButton (waitButton); + messageBox.setDefaultButton(waitButton); bool retVal = true; - //Connections shut down message box if operation ends before user makes a decision. - connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); - connect (this, SIGNAL (closeMessageBox()), &messageBox, SLOT (close())); + // Connections shut down message box if operation ends before user makes a decision. + connect(document, &CSMDoc::Document::stateChanged, this, &ViewManager::onExitWarningHandler); + connect(this, &ViewManager::closeMessageBox, &messageBox, &QMessageBox::close); - //set / clear the user warned flag to indicate whether or not the message box is currently active. + // set / clear the user warned flag to indicate whether or not the message box is currently active. mUserWarned = true; messageBox.exec(); mUserWarned = false; - //if closed by the warning handler, defaults to the RejectRole button (closeButton) + // if closed by the warning handler, defaults to the RejectRole button (closeButton) if (messageBox.clickedButton() == waitButton) { - //save the View iterator for shutdown after the save operation ends + // save the View iterator for shutdown after the save operation ends mExitOnSaveStateChange = true; retVal = false; } else if (messageBox.clickedButton() == closeButton) { - //disconnect to avoid segmentation fault - disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); + // disconnect to avoid segmentation fault + disconnect(document, &CSMDoc::Document::stateChanged, this, &ViewManager::onExitWarningHandler); view->abortOperation(CSMDoc::State_Saving); mExitOnSaveStateChange = true; @@ -358,59 +352,59 @@ bool CSVDoc::ViewManager::showSaveInProgressMessageBox (CSVDoc::View *view) else if (messageBox.clickedButton() == cancelButton) { - //abort shutdown, allow save to complete - //disconnection to prevent unintended view closures + // abort shutdown, allow save to complete + // disconnection to prevent unintended view closures mExitOnSaveStateChange = false; - disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *))); + disconnect(document, &CSMDoc::Document::stateChanged, this, &ViewManager::onExitWarningHandler); retVal = false; } return retVal; } -void CSVDoc::ViewManager::documentStateChanged (int state, CSMDoc::Document *document) +void CSVDoc::ViewManager::documentStateChanged(int state, CSMDoc::Document* document) { - for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) - if ((*iter)->getDocument()==document) - (*iter)->updateDocumentState(); + for (std::vector::const_iterator iter(mViews.begin()); iter != mViews.end(); ++iter) + if ((*iter)->getDocument() == document) + (*iter)->updateDocumentState(); } -void CSVDoc::ViewManager::progress (int current, int max, int type, int threads, CSMDoc::Document *document) +void CSVDoc::ViewManager::progress(int current, int max, int type, int threads, CSMDoc::Document* document) { - for (std::vector::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter) - if ((*iter)->getDocument()==document) - (*iter)->updateProgress (current, max, type, threads); + for (std::vector::const_iterator iter(mViews.begin()); iter != mViews.end(); ++iter) + if ((*iter)->getDocument() == document) + (*iter)->updateProgress(current, max, type, threads); } -void CSVDoc::ViewManager::onExitWarningHandler (int state, CSMDoc::Document *document) +void CSVDoc::ViewManager::onExitWarningHandler(int state, CSMDoc::Document* document) { - if ( !(state & CSMDoc::State_Saving) ) + if (!(state & CSMDoc::State_Saving)) { - //if the user is being warned (message box is active), shut down the message box, - //as there is no save operation currently running - if ( mUserWarned ) + // if the user is being warned (message box is active), shut down the message box, + // as there is no save operation currently running + if (mUserWarned) emit closeMessageBox(); - //otherwise, the user has closed the message box before the save operation ended. - //exit the application + // otherwise, the user has closed the message box before the save operation ended. + // exit the application else if (mExitOnSaveStateChange) QApplication::instance()->exit(); } } -bool CSVDoc::ViewManager::removeDocument (CSVDoc::View *view) +bool CSVDoc::ViewManager::removeDocument(CSVDoc::View* view) { - if(!notifySaveOnClose(view)) + if (!notifySaveOnClose(view)) return false; else { // don't bother closing views or updating indicies, but remove from mViews - CSMDoc::Document * document = view->getDocument(); - std::vector remainingViews; - std::vector::const_iterator iter = mViews.begin(); - for (; iter!=mViews.end(); ++iter) + CSMDoc::Document* document = view->getDocument(); + std::vector remainingViews; + std::vector::const_iterator iter = mViews.begin(); + for (; iter != mViews.end(); ++iter) { - if(document == (*iter)->getDocument()) + if (document == (*iter)->getDocument()) (*iter)->setVisible(false); else remainingViews.push_back(*iter); @@ -421,16 +415,16 @@ bool CSVDoc::ViewManager::removeDocument (CSVDoc::View *view) return true; } -void CSVDoc::ViewManager::exitApplication (CSVDoc::View *view) +void CSVDoc::ViewManager::exitApplication(CSVDoc::View* view) { - if(!removeDocument(view)) // close the current document first + if (!removeDocument(view)) // close the current document first return; - while(!mViews.empty()) // attempt to close all other documents + while (!mViews.empty()) // attempt to close all other documents { mViews.back()->activateWindow(); mViews.back()->raise(); // raise the window to alert the user - if(!removeDocument(mViews.back())) + if (!removeDocument(mViews.back())) return; } // Editor exits (via a signal) when the last document is deleted diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index 8424be6f8..779d059ba 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -1,6 +1,7 @@ #ifndef CSV_DOC_VIEWMANAGER_H #define CSV_DOC_VIEWMANAGER_H +#include #include #include @@ -29,67 +30,66 @@ namespace CSVDoc class ViewManager : public QObject { - Q_OBJECT + Q_OBJECT - CSMDoc::DocumentManager& mDocumentManager; - std::vector mViews; - CSVWorld::CommandDelegateFactoryCollection *mDelegateFactories; - bool mExitOnSaveStateChange; - bool mUserWarned; - Loader mLoader; + CSMDoc::DocumentManager& mDocumentManager; + std::vector mViews; + CSVWorld::CommandDelegateFactoryCollection* mDelegateFactories; + bool mExitOnSaveStateChange; + bool mUserWarned; + Loader mLoader; - // not implemented - ViewManager (const ViewManager&); - ViewManager& operator= (const ViewManager&); + // not implemented + ViewManager(const ViewManager&); + ViewManager& operator=(const ViewManager&); - void updateIndices(); - bool notifySaveOnClose (View *view = nullptr); - bool showModifiedDocumentMessageBox (View *view); - bool showSaveInProgressMessageBox (View *view); - bool removeDocument(View *view); + void updateIndices(); + bool notifySaveOnClose(View* view = nullptr); + bool showModifiedDocumentMessageBox(View* view); + bool showSaveInProgressMessageBox(View* view); + bool removeDocument(View* view); - public: + public: + ViewManager(CSMDoc::DocumentManager& documentManager); - ViewManager (CSMDoc::DocumentManager& documentManager); + ~ViewManager() override; - virtual ~ViewManager(); + View* addView(CSMDoc::Document* document); + ///< The ownership of the returned view is not transferred. - View *addView (CSMDoc::Document *document); - ///< The ownership of the returned view is not transferred. + View* addView(CSMDoc::Document* document, const CSMWorld::UniversalId& id, const std::string& hint); - View *addView (CSMDoc::Document *document, const CSMWorld::UniversalId& id, const std::string& hint); + int countViews(const CSMDoc::Document* document) const; + ///< Return number of views for \a document. - int countViews (const CSMDoc::Document *document) const; - ///< Return number of views for \a document. + bool closeRequest(View* view); + void removeDocAndView(CSMDoc::Document* document); - bool closeRequest (View *view); - void removeDocAndView (CSMDoc::Document *document); + signals: - signals: + void newGameRequest(); - void newGameRequest(); + void newAddonRequest(); - void newAddonRequest(); + void loadDocumentRequest(); - void loadDocumentRequest(); + void closeMessageBox(); - void closeMessageBox(); + void editSettingsRequest(); - void editSettingsRequest(); + void mergeDocument(CSMDoc::Document* document); - void mergeDocument (CSMDoc::Document *document); + public slots: - public slots: + void exitApplication(CSVDoc::View* view); - void exitApplication (CSVDoc::View *view); + private slots: - private slots: + void documentStateChanged(int state, CSMDoc::Document* document); - void documentStateChanged (int state, CSMDoc::Document *document); + void progress(int current, int max, int type, int threads, CSMDoc::Document* document); - void progress (int current, int max, int type, int threads, CSMDoc::Document *document); - - void onExitWarningHandler(int state, CSMDoc::Document* document); + void onExitWarningHandler(int state, CSMDoc::Document* document); }; } diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 6b585591f..50735bf70 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -1,106 +1,128 @@ #include "editwidget.hpp" -#include +#include +#include + #include +#include #include #include +#include #include -#include +#include +#include "filterdata.hpp" + +#include +#include + +#include #include +#include "../../model/prefs/shortcut.hpp" +#include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtablebase.hpp" -#include "../../model/world/columns.hpp" -#include "../../model/prefs/shortcut.hpp" -CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) -: QLineEdit (parent), mParser (data), mIsEmpty(true) +CSVFilter::EditWidget::EditWidget(CSMWorld::Data& data, QWidget* parent) + : QLineEdit(parent) + , mParser(data) + , mIsEmpty(true) { mPalette = palette(); - connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + connect(this, &QLineEdit::textChanged, this, &EditWidget::textChanged); - const CSMWorld::IdTableBase *model = - static_cast (data.getTableModel (CSMWorld::UniversalId::Type_Filters)); + const CSMWorld::IdTableBase* model + = static_cast(data.getTableModel(CSMWorld::UniversalId::Type_Filters)); - connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), - this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), - Qt::QueuedConnection); - connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (filterRowsRemoved (const QModelIndex&, int, int)), - Qt::QueuedConnection); - connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), - Qt::QueuedConnection); + connect(model, &CSMWorld::IdTableBase::dataChanged, this, &EditWidget::filterDataChanged, Qt::QueuedConnection); + connect(model, &CSMWorld::IdTableBase::rowsRemoved, this, &EditWidget::filterRowsRemoved, Qt::QueuedConnection); + connect(model, &CSMWorld::IdTableBase::rowsInserted, this, &EditWidget::filterRowsInserted, Qt::QueuedConnection); - mStateColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); - mDescColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Description); + mStateColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + mDescColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Description); - mHelpAction = new QAction (tr ("Help"), this); - connect (mHelpAction, SIGNAL (triggered()), this, SLOT (openHelp())); + mHelpAction = new QAction(tr("Help"), this); + connect(mHelpAction, &QAction::triggered, this, &EditWidget::openHelp); mHelpAction->setIcon(QIcon(":/info.png")); - addAction (mHelpAction); + addAction(mHelpAction); auto* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); + + setText("!string(\"ID\", \".*\")"); } -void CSVFilter::EditWidget::textChanged (const QString& text) +void CSVFilter::EditWidget::textChanged(const QString& text) { - //no need to parse and apply filter if it was empty and now is empty too. - //e.g. - we modifiing content of filter with already opened some other (big) tables. - if (text.length() == 0){ + // no need to parse and apply filter if it was empty and now is empty too. + // e.g. - we modifiing content of filter with already opened some other (big) tables. + if (text.length() == 0) + { if (mIsEmpty) return; else mIsEmpty = true; - }else + } + else mIsEmpty = false; - if (mParser.parse (text.toUtf8().constData())) + if (mParser.parse(text.toUtf8().constData())) { - setPalette (mPalette); - emit filterChanged (mParser.getFilter()); + setPalette(mPalette); + emit filterChanged(mParser.getFilter()); } else { - QPalette palette (mPalette); - palette.setColor (QPalette::Text, Qt::red); - setPalette (palette); + QPalette palette(mPalette); + palette.setColor(QPalette::Text, Qt::red); + setPalette(palette); /// \todo improve error reporting; mark only the faulty part } } -void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVFilter::EditWidget::filterDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int i = topLeft.column(); i <= bottomRight.column(); ++i) if (i != mStateColumnIndex && i != mDescColumnIndex) - textChanged (text()); + textChanged(text()); } -void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) +void CSVFilter::EditWidget::filterRowsRemoved(const QModelIndex& parent, int start, int end) { - textChanged (text()); + textChanged(text()); } -void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int start, int end) +void CSVFilter::EditWidget::filterRowsInserted(const QModelIndex& parent, int start, int end) { - textChanged (text()); + textChanged(text()); } -void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, - Qt::DropAction action) +void CSVFilter::EditWidget::createFilterRequest(const std::vector& sourceFilter, Qt::DropAction action) { - const unsigned count = filterSource.size(); + FilterType filterType = FilterType::String; + std::vector newFilter; + + for (auto filterData : sourceFilter) + { + FilterData newFilterData; + std::pair pair = std::visit(FilterVisitor(), filterData.searchData); + std::string searchString = pair.first; + filterType = pair.second; + newFilterData.searchData = searchString; + newFilterData.columns = filterData.columns; + newFilter.emplace_back(newFilterData); + } + + const unsigned count = newFilter.size(); bool multipleElements = false; - switch (count) //setting multipleElements; + switch (count) // setting multipleElements; { - case 0: //empty - return; //nothing to do here + case 0: // empty + return; // nothing to do here - case 1: //only single + case 1: // only single multipleElements = false; break; @@ -110,12 +132,12 @@ void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::st } Qt::KeyboardModifiers key = QApplication::keyboardModifiers(); - QString oldContent (text()); + QString oldContent(text()); bool replaceMode = false; std::string orAnd; - switch (key) //setting replaceMode and string used to glue expressions + switch (key) // setting replaceMode and string used to glue expressions { case Qt::ShiftModifier: orAnd = "!or("; @@ -132,14 +154,17 @@ void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::st break; } - if (oldContent.isEmpty() || !oldContent.contains (QRegExp ("^!.*$", Qt::CaseInsensitive))) //if line edit is empty or it does not contain one shot filter go into replace mode + if (oldContent.isEmpty() + || !oldContent.contains(QRegularExpression("^!.*$", + QRegularExpression::CaseInsensitiveOption))) // if line edit is empty or it does not contain one shot filter + // go into replace mode { replaceMode = true; } if (!replaceMode) { - oldContent.remove ('!'); + oldContent.remove('!'); } std::stringstream ss; @@ -148,86 +173,106 @@ void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::st { if (replaceMode) { - ss<<"!or("; - } else { + ss << "!or("; + } + else + { ss << orAnd << oldContent.toUtf8().constData() << ','; } for (unsigned i = 0; i < count; ++i) { - ss<4) + if (ss.str().length() > 4) { clear(); - insert (QString::fromUtf8(ss.str().c_str())); + insert(QString::fromUtf8(ss.str().c_str())); } } -std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::vector< std::string > >& seekedString) const +std::string CSVFilter::EditWidget::generateFilter(const FilterData& filterData, FilterType filterType) const { - const unsigned columns = seekedString.second.size(); + const unsigned columns = filterData.columns.size(); bool multipleColumns = false; switch (columns) { - case 0: //empty - return ""; //no column to filter + case 0: // empty + return ""; // no column to filter - case 1: //one column to look for - multipleColumns = false; - break; + case 1: // one column to look for + multipleColumns = false; + break; - default: - multipleColumns = true; - break; + default: + multipleColumns = true; + break; } + std::string quotesResolved; + if (std::get_if(&filterData.searchData)) + quotesResolved = std::get(filterData.searchData); + else + { + Log(Debug::Warning) << "Generating record filter failed."; + return ""; + } + if (filterType == FilterType::String) + quotesResolved = '"' + quotesResolved + '"'; + std::stringstream ss; + if (multipleColumns) { - ss<<"or("; + ss << "or("; for (unsigned i = 0; i < columns; ++i) { - ss<<"string("<<'"'<addAction(mHelpAction); menu->exec(event->globalPos()); delete menu; @@ -237,4 +282,3 @@ void CSVFilter::EditWidget::openHelp() { Misc::HelpViewer::openHelp("manuals/openmw-cs/record-filters.html"); } - diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index f933ec92e..f2ecd5f64 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -3,12 +3,28 @@ #include #include -#include +#include + +#include +#include +#include +#include +#include + +#include "filterdata.hpp" #include "../../model/filter/parser.hpp" -#include "../../model/filter/node.hpp" class QModelIndex; +class QAction; +class QContextMenuEvent; +class QObject; +class QWidget; + +namespace CSMFilter +{ + class Node; +} namespace CSMWorld { @@ -17,45 +33,80 @@ namespace CSMWorld namespace CSVFilter { + enum class FilterType + { + String, + Value + }; + + struct FilterVisitor + { + std::pair operator()(const std::string& stringData) + { + FilterType filterType = FilterType::String; + return std::make_pair(stringData, filterType); + } + + std::pair operator()(const QVariant& variantData) + { + FilterType filterType = FilterType::String; + QMetaType::Type dataType = static_cast(variantData.type()); + if (dataType == QMetaType::QString || dataType == QMetaType::Bool || dataType == QMetaType::Int) + filterType = FilterType::String; + if (dataType == QMetaType::Int || dataType == QMetaType::Float) + filterType = FilterType::Value; + return std::make_pair(variantData.toString().toStdString(), filterType); + } + }; + class EditWidget : public QLineEdit { - Q_OBJECT + Q_OBJECT - CSMFilter::Parser mParser; - QPalette mPalette; - bool mIsEmpty; - int mStateColumnIndex; - int mDescColumnIndex; - QAction *mHelpAction; + CSMFilter::Parser mParser; + QPalette mPalette; + bool mIsEmpty; + int mStateColumnIndex; + int mDescColumnIndex; + QAction* mHelpAction; - public: + public: + EditWidget(CSMWorld::Data& data, QWidget* parent = nullptr); - EditWidget (CSMWorld::Data& data, QWidget *parent = nullptr); + void createFilterRequest(const std::vector& sourceFilter, Qt::DropAction action); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); + signals: - signals: - - void filterChanged (std::shared_ptr filter); + void filterChanged(std::shared_ptr filter); private: - std::string generateFilter(std::pair >& seekedString) const; - void contextMenuEvent (QContextMenuEvent *event) override; + std::string generateFilter(const FilterData& filterData, FilterType filterType) const; - private slots: + void contextMenuEvent(QContextMenuEvent* event) override; - void textChanged (const QString& text); + constexpr std::string_view filterTypeName(const FilterType& type) const + { + switch (type) + { + case FilterType::String: + return "string"; + case FilterType::Value: + return "value"; + } + return "unknown type"; + } - void filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + private slots: - void filterRowsRemoved (const QModelIndex& parent, int start, int end); + void textChanged(const QString& text); - void filterRowsInserted (const QModelIndex& parent, int start, int end); + void filterDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - static void openHelp(); + void filterRowsRemoved(const QModelIndex& parent, int start, int end); + void filterRowsInserted(const QModelIndex& parent, int start, int end); + static void openHelp(); }; } diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index 461b131db..e79b83ed5 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -1,60 +1,76 @@ #include "filterbox.hpp" -#include -#include +#include +#include +#include +#include +#include +#include +#include + +#include "filterdata.hpp" #include "recordfilterbox.hpp" #include +#include +#include -CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) -: QWidget (parent) +CSVFilter::FilterBox::FilterBox(CSMWorld::Data& data, QWidget* parent) + : QWidget(parent) { - QHBoxLayout *layout = new QHBoxLayout (this); + QHBoxLayout* layout = new QHBoxLayout(this); - layout->setContentsMargins (0, 0, 0, 0); + layout->setContentsMargins(0, 0, 0, 0); - mRecordFilterBox = new RecordFilterBox (data, this); + mRecordFilterBox = new RecordFilterBox(data, this); - layout->addWidget (mRecordFilterBox); + layout->addWidget(mRecordFilterBox); - setLayout (layout); + setLayout(layout); - connect (mRecordFilterBox, - SIGNAL (filterChanged (std::shared_ptr)), - this, SIGNAL (recordFilterChanged (std::shared_ptr))); + connect(mRecordFilterBox, &RecordFilterBox::filterChanged, this, &FilterBox::recordFilterChanged); setAcceptDrops(true); } -void CSVFilter::FilterBox::setRecordFilter (const std::string& filter) +void CSVFilter::FilterBox::setRecordFilter(const std::string& filter) { - mRecordFilterBox->setFilter (filter); + mRecordFilterBox->setFilter(filter); } -void CSVFilter::FilterBox::dropEvent (QDropEvent* event) +void CSVFilter::FilterBox::dropEvent(QDropEvent* event) { - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; std::vector universalIdData = mime->getData(); + QModelIndex index = mime->getIndexAtDragStart(); + const CSVWorld::DragRecordTable* dragTable = mime->getTableOfDragStart(); - emit recordDropped(universalIdData, event->proposedAction()); + QVariant qData; + std::string searchColumn; + if (index.isValid() && dragTable) + { + qData = dragTable->model()->data(index); + searchColumn = dragTable->model()->headerData(index.column(), Qt::Horizontal).toString().toStdString(); + } + + emit recordDropped(universalIdData, std::make_pair(qData, searchColumn), event->proposedAction()); } -void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event) +void CSVFilter::FilterBox::dragEnterEvent(QDragEnterEvent* event) { event->acceptProposedAction(); } -void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event) +void CSVFilter::FilterBox::dragMoveEvent(QDragMoveEvent* event) { event->accept(); } -void CSVFilter::FilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, - Qt::DropAction action) +void CSVFilter::FilterBox::createFilterRequest(const std::vector& sourceFilter, Qt::DropAction action) { - mRecordFilterBox->createFilterRequest(filterSource, action); + mRecordFilterBox->createFilterRequest(sourceFilter, action); } diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index 94b5fced3..bb53b9455 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -1,17 +1,30 @@ #ifndef CSV_FILTER_FILTERBOX_H #define CSV_FILTER_FILTERBOX_H +#include +#include +#include +#include #include +#include #include -#include -#include "../../model/filter/node.hpp" -#include "../../model/world/universalid.hpp" +#include "filterdata.hpp" +class QDragEnterEvent; +class QDragMoveEvent; +class QDropEvent; +class QObject; + +namespace CSMFilter +{ + class Node; +} namespace CSMWorld { class Data; + class UniversalId; } namespace CSVFilter @@ -20,32 +33,30 @@ namespace CSVFilter class FilterBox : public QWidget { - Q_OBJECT + Q_OBJECT - RecordFilterBox *mRecordFilterBox; + RecordFilterBox* mRecordFilterBox; - public: - FilterBox (CSMWorld::Data& data, QWidget *parent = nullptr); + public: + FilterBox(CSMWorld::Data& data, QWidget* parent = nullptr); - void setRecordFilter (const std::string& filter); + void setRecordFilter(const std::string& filter); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); + void createFilterRequest(const std::vector& sourceFilter, Qt::DropAction action); + private: + void dragEnterEvent(QDragEnterEvent* event) override; - private: - void dragEnterEvent (QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; - void dropEvent (QDropEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - - signals: - void recordFilterChanged (std::shared_ptr filter); - void recordDropped (std::vector& types, Qt::DropAction action); + signals: + void recordFilterChanged(std::shared_ptr filter); + void recordDropped(std::vector& types, + const std::pair& columnSearchData, Qt::DropAction action); }; } #endif - diff --git a/apps/opencs/view/filter/filterdata.hpp b/apps/opencs/view/filter/filterdata.hpp new file mode 100644 index 000000000..b99363712 --- /dev/null +++ b/apps/opencs/view/filter/filterdata.hpp @@ -0,0 +1,19 @@ +#ifndef CSV_FILTER_FILTERDATA_H +#define CSV_FILTER_FILTERDATA_H + +#include +#include +#include + +#include + +namespace CSVFilter +{ + struct FilterData + { + std::variant searchData; + std::vector columns; + }; +} + +#endif diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index d24465897..deed76bfb 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -1,40 +1,41 @@ #include "recordfilterbox.hpp" +#include + #include #include +#include #include "editwidget.hpp" +#include "filterdata.hpp" -CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *parent) -: QWidget (parent) +CSVFilter::RecordFilterBox::RecordFilterBox(CSMWorld::Data& data, QWidget* parent) + : QWidget(parent) { - QHBoxLayout *layout = new QHBoxLayout (this); + QHBoxLayout* layout = new QHBoxLayout(this); - layout->setContentsMargins (0, 6, 5, 0); + layout->setContentsMargins(0, 6, 5, 0); - QLabel *label = new QLabel("Record Filter", this); + QLabel* label = new QLabel("Record Filter", this); label->setIndent(2); - layout->addWidget (label); + layout->addWidget(label); - mEdit = new EditWidget (data, this); + mEdit = new EditWidget(data, this); - layout->addWidget (mEdit); + layout->addWidget(mEdit); - setLayout (layout); + setLayout(layout); - connect ( - mEdit, SIGNAL (filterChanged (std::shared_ptr)), - this, SIGNAL (filterChanged (std::shared_ptr))); + connect(mEdit, &EditWidget::filterChanged, this, &RecordFilterBox::filterChanged); } -void CSVFilter::RecordFilterBox::setFilter (const std::string& filter) +void CSVFilter::RecordFilterBox::setFilter(const std::string& filter) { mEdit->clear(); - mEdit->setText (QString::fromUtf8 (filter.c_str())); + mEdit->setText(QString::fromUtf8(filter.c_str())); } -void CSVFilter::RecordFilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, - Qt::DropAction action) +void CSVFilter::RecordFilterBox::createFilterRequest(const std::vector& sourceFilter, Qt::DropAction action) { - mEdit->createFilterRequest(filterSource, action); + mEdit->createFilterRequest(sourceFilter, action); } diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index 3bcd7c57b..d7fdb5cba 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -1,12 +1,21 @@ #ifndef CSV_FILTER_RECORDFILTERBOX_H #define CSV_FILTER_RECORDFILTERBOX_H +#include +#include +#include +#include +#include + +#include #include -#include -#include +#include "filterdata.hpp" -#include "../../model/filter/node.hpp" +namespace CSMFilter +{ + class Node; +} namespace CSMWorld { @@ -19,24 +28,22 @@ namespace CSVFilter class RecordFilterBox : public QWidget { - Q_OBJECT + Q_OBJECT - EditWidget *mEdit; + EditWidget* mEdit; - public: + public: + RecordFilterBox(CSMWorld::Data& data, QWidget* parent = nullptr); - RecordFilterBox (CSMWorld::Data& data, QWidget *parent = nullptr); + void setFilter(const std::string& filter); - void setFilter (const std::string& filter); + void useFilterRequest(const std::string& idOfFilter); - void useFilterRequest(const std::string& idOfFilter); + void createFilterRequest(const std::vector& sourceFilter, Qt::DropAction action); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); + signals: - signals: - - void filterChanged (std::shared_ptr filter); + void filterChanged(std::shared_ptr filter); }; } diff --git a/apps/opencs/view/prefs/contextmenulist.cpp b/apps/opencs/view/prefs/contextmenulist.cpp index 8115c3ecc..bf7a5f289 100644 --- a/apps/opencs/view/prefs/contextmenulist.cpp +++ b/apps/opencs/view/prefs/contextmenulist.cpp @@ -1,13 +1,13 @@ #include "contextmenulist.hpp" -#include #include +#include #include #include "../../model/prefs/state.hpp" CSVPrefs::ContextMenuList::ContextMenuList(QWidget* parent) - :QListWidget(parent) + : QListWidget(parent) { } diff --git a/apps/opencs/view/prefs/contextmenulist.hpp b/apps/opencs/view/prefs/contextmenulist.hpp index f527057d2..42063cea1 100644 --- a/apps/opencs/view/prefs/contextmenulist.hpp +++ b/apps/opencs/view/prefs/contextmenulist.hpp @@ -10,24 +10,22 @@ namespace CSVPrefs { class ContextMenuList : public QListWidget { - Q_OBJECT - - public: - - ContextMenuList(QWidget* parent = nullptr); - - protected: - - void contextMenuEvent(QContextMenuEvent* e) override; + Q_OBJECT - void mousePressEvent(QMouseEvent* e) override; - - private slots: - - void resetCategory(); - - void resetAll(); - }; + public: + ContextMenuList(QWidget* parent = nullptr); + + protected: + void contextMenuEvent(QContextMenuEvent* e) override; + + void mousePressEvent(QMouseEvent* e) override; + + private slots: + + void resetCategory(); + + void resetAll(); + }; } #endif diff --git a/apps/opencs/view/prefs/dialogue.cpp b/apps/opencs/view/prefs/dialogue.cpp index 7e41fcf82..26cb89478 100644 --- a/apps/opencs/view/prefs/dialogue.cpp +++ b/apps/opencs/view/prefs/dialogue.cpp @@ -1,81 +1,86 @@ #include "dialogue.hpp" +#include +#include +#include + #include -#include -#include -#include -#include #include #include +#include +#include #include +#include +#include + #include "../../model/prefs/state.hpp" -#include "page.hpp" -#include "keybindingpage.hpp" #include "contextmenulist.hpp" +#include "keybindingpage.hpp" +#include "page.hpp" -void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main) +void CSVPrefs::Dialogue::buildCategorySelector(QSplitter* main) { - CSVPrefs::ContextMenuList* list = new CSVPrefs::ContextMenuList (main); - list->setMinimumWidth (50); - list->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); + CSVPrefs::ContextMenuList* list = new CSVPrefs::ContextMenuList(main); + list->setMinimumWidth(50); + list->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - list->setSelectionBehavior (QAbstractItemView::SelectItems); + list->setSelectionBehavior(QAbstractItemView::SelectItems); - main->addWidget (list); + main->addWidget(list); - QFontMetrics metrics (QApplication::font(list)); + QFontMetrics metrics(QApplication::font(list)); int maxWidth = 1; - for (CSMPrefs::State::Iterator iter = CSMPrefs::get().begin(); iter!=CSMPrefs::get().end(); - ++iter) + for (CSMPrefs::State::Iterator iter = CSMPrefs::get().begin(); iter != CSMPrefs::get().end(); ++iter) { - QString label = QString::fromUtf8 (iter->second.getKey().c_str()); + QString label = QString::fromUtf8(iter->second.getKey().c_str()); - maxWidth = std::max (maxWidth, metrics.horizontalAdvance (label)); + maxWidth = std::max(maxWidth, metrics.horizontalAdvance(label)); - list->addItem (label); + list->addItem(label); } - list->setMaximumWidth (maxWidth + 10); + list->setMaximumWidth(maxWidth + 50); - connect (list, SIGNAL (currentItemChanged (QListWidgetItem *, QListWidgetItem *)), - this, SLOT (selectionChanged (QListWidgetItem *, QListWidgetItem *))); + connect(list, &ContextMenuList::currentItemChanged, this, &Dialogue::selectionChanged); } -void CSVPrefs::Dialogue::buildContentArea (QSplitter *main) +void CSVPrefs::Dialogue::buildContentArea(QSplitter* main) { - mContent = new QStackedWidget (main); - mContent->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding); + mContent = new QStackedWidget(main); + mContent->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - main->addWidget (mContent); + main->addWidget(mContent); } -CSVPrefs::PageBase *CSVPrefs::Dialogue::makePage (const std::string& key) +CSVPrefs::PageBase* CSVPrefs::Dialogue::makePage(const std::string& key) { // special case page code goes here if (key == "Key Bindings") return new KeyBindingPage(CSMPrefs::get()[key], mContent); else - return new Page (CSMPrefs::get()[key], mContent); + return new Page(CSMPrefs::get()[key], mContent); } CSVPrefs::Dialogue::Dialogue() { - setWindowTitle ("User Settings"); + setWindowTitle("User Settings"); - setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - setMinimumSize (600, 400); + setMinimumSize(600, 400); - QSplitter *main = new QSplitter (this); + resize(810, 680); - setCentralWidget (main); - buildCategorySelector (main); - buildContentArea (main); + QSplitter* main = new QSplitter(this); + + setCentralWidget(main); + buildCategorySelector(main); + buildContentArea(main); } CSVPrefs::Dialogue::~Dialogue() @@ -85,26 +90,26 @@ CSVPrefs::Dialogue::~Dialogue() if (isVisible()) CSMPrefs::State::get().save(); } - catch(const std::exception& e) + catch (const std::exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } -void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event) +void CSVPrefs::Dialogue::closeEvent(QCloseEvent* event) { - QMainWindow::closeEvent (event); + QMainWindow::closeEvent(event); CSMPrefs::State::get().save(); } void CSVPrefs::Dialogue::show() { - if (QWidget *active = QApplication::activeWindow()) + if (QWidget* active = QApplication::activeWindow()) { // place at the centre of the window with focus QSize size = active->size(); - move (active->geometry().x()+(size.width() - frameGeometry().width())/2, - active->geometry().y()+(size.height() - frameGeometry().height())/2); + move(active->geometry().x() + (size.width() - frameGeometry().width()) / 2, + active->geometry().y() + (size.height() - frameGeometry().height()) / 2); } else { @@ -113,30 +118,30 @@ void CSVPrefs::Dialogue::show() // otherwise place at the centre of the screen QPoint screenCenter = scr.center(); - move (screenCenter - QPoint(frameGeometry().width()/2, frameGeometry().height()/2)); + move(screenCenter - QPoint(frameGeometry().width() / 2, frameGeometry().height() / 2)); } QWidget::show(); } -void CSVPrefs::Dialogue::selectionChanged (QListWidgetItem *current, QListWidgetItem *previous) +void CSVPrefs::Dialogue::selectionChanged(QListWidgetItem* current, QListWidgetItem* previous) { if (current) { std::string key = current->text().toUtf8().data(); - for (int i=0; icount(); ++i) + for (int i = 0; i < mContent->count(); ++i) { - PageBase& page = dynamic_cast (*mContent->widget (i)); + PageBase& page = dynamic_cast(*mContent->widget(i)); - if (page.getCategory().getKey()==key) + if (page.getCategory().getKey() == key) { - mContent->setCurrentIndex (i); + mContent->setCurrentIndex(i); return; } } - PageBase *page = makePage (key); - mContent->setCurrentIndex (mContent->addWidget (page)); + PageBase* page = makePage(key); + mContent->setCurrentIndex(mContent->addWidget(page)); } } diff --git a/apps/opencs/view/prefs/dialogue.hpp b/apps/opencs/view/prefs/dialogue.hpp index 2e0975649..dab25d303 100644 --- a/apps/opencs/view/prefs/dialogue.hpp +++ b/apps/opencs/view/prefs/dialogue.hpp @@ -4,7 +4,6 @@ #include class QSplitter; -class QListWidget; class QStackedWidget; class QListWidgetItem; @@ -14,35 +13,32 @@ namespace CSVPrefs class Dialogue : public QMainWindow { - Q_OBJECT + Q_OBJECT - QStackedWidget *mContent; + QStackedWidget* mContent; - private: + private: + void buildCategorySelector(QSplitter* main); - void buildCategorySelector (QSplitter *main); + void buildContentArea(QSplitter* main); - void buildContentArea (QSplitter *main); + PageBase* makePage(const std::string& key); - PageBase *makePage (const std::string& key); + public: + Dialogue(); - public: + ~Dialogue() override; - Dialogue(); + protected: + void closeEvent(QCloseEvent* event) override; - virtual ~Dialogue(); + public slots: - protected: + void show(); - void closeEvent (QCloseEvent *event) override; + private slots: - public slots: - - void show(); - - private slots: - - void selectionChanged (QListWidgetItem *current, QListWidgetItem *previous); + void selectionChanged(QListWidgetItem* current, QListWidgetItem* previous); }; } diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index 39c9f78ec..d3cc1ff88 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -1,6 +1,9 @@ #include "keybindingpage.hpp" #include +#include +#include +#include #include #include @@ -8,8 +11,10 @@ #include #include -#include "../../model/prefs/setting.hpp" +#include + #include "../../model/prefs/category.hpp" +#include "../../model/prefs/setting.hpp" #include "../../model/prefs/state.hpp" namespace CSVPrefs @@ -29,15 +34,16 @@ namespace CSVPrefs mStackedLayout = new QStackedLayout(stackedWidget); mPageSelector = new QComboBox(); - connect(mPageSelector, SIGNAL(currentIndexChanged(int)), mStackedLayout, SLOT(setCurrentIndex(int))); + connect(mPageSelector, qOverload(&QComboBox::currentIndexChanged), mStackedLayout, + &QStackedLayout::setCurrentIndex); QFrame* lineSeparator = new QFrame(topWidget); lineSeparator->setFrameShape(QFrame::HLine); lineSeparator->setFrameShadow(QFrame::Sunken); // Reset key bindings button - QPushButton* resetButton = new QPushButton ("Reset to Defaults", topWidget); - connect(resetButton, SIGNAL(clicked()), this, SLOT(resetKeyBindings())); + QPushButton* resetButton = new QPushButton("Reset to Defaults", topWidget); + connect(resetButton, &QPushButton::clicked, this, &KeyBindingPage::resetKeyBindings); topLayout->addWidget(mPageSelector); topLayout->addWidget(stackedWidget); @@ -46,16 +52,16 @@ namespace CSVPrefs topLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); // Add each option - for (CSMPrefs::Category::Iterator iter = category.begin(); iter!=category.end(); ++iter) - addSetting (*iter); + for (CSMPrefs::Category::Iterator iter = category.begin(); iter != category.end(); ++iter) + addSetting(*iter); setWidgetResizable(true); setWidget(topWidget); } - void KeyBindingPage::addSetting(CSMPrefs::Setting *setting) + void KeyBindingPage::addSetting(CSMPrefs::Setting* setting) { - std::pair widgets = setting->makeWidgets (this); + std::pair widgets = setting->makeWidgets(this); if (widgets.first) { diff --git a/apps/opencs/view/prefs/keybindingpage.hpp b/apps/opencs/view/prefs/keybindingpage.hpp index 05c4b22db..d674e965b 100644 --- a/apps/opencs/view/prefs/keybindingpage.hpp +++ b/apps/opencs/view/prefs/keybindingpage.hpp @@ -6,33 +6,33 @@ class QComboBox; class QGridLayout; class QStackedLayout; +class QWidget; namespace CSMPrefs { class Setting; + class Category; } namespace CSVPrefs { class KeyBindingPage : public PageBase { - Q_OBJECT + Q_OBJECT - public: + public: + KeyBindingPage(CSMPrefs::Category& category, QWidget* parent); - KeyBindingPage(CSMPrefs::Category& category, QWidget* parent); + void addSetting(CSMPrefs::Setting* setting); - void addSetting(CSMPrefs::Setting* setting); + private: + QStackedLayout* mStackedLayout; + QGridLayout* mPageLayout; + QComboBox* mPageSelector; - private: + private slots: - QStackedLayout* mStackedLayout; - QGridLayout* mPageLayout; - QComboBox* mPageSelector; - - private slots: - - void resetKeyBindings(); + void resetKeyBindings(); }; } diff --git a/apps/opencs/view/prefs/page.cpp b/apps/opencs/view/prefs/page.cpp index c23e9f64f..4f04a39f0 100644 --- a/apps/opencs/view/prefs/page.cpp +++ b/apps/opencs/view/prefs/page.cpp @@ -1,40 +1,44 @@ - #include "page.hpp" +#include +#include + +#include + #include -#include "../../model/prefs/setting.hpp" #include "../../model/prefs/category.hpp" +#include "../../model/prefs/setting.hpp" -CSVPrefs::Page::Page (CSMPrefs::Category& category, QWidget *parent) -: PageBase (category, parent) +CSVPrefs::Page::Page(CSMPrefs::Category& category, QWidget* parent) + : PageBase(category, parent) { - QWidget *widget = new QWidget (parent); - mGrid = new QGridLayout (widget); + QWidget* widget = new QWidget(parent); + mGrid = new QGridLayout(widget); - for (CSMPrefs::Category::Iterator iter = category.begin(); iter!=category.end(); ++iter) - addSetting (*iter); + for (CSMPrefs::Category::Iterator iter = category.begin(); iter != category.end(); ++iter) + addSetting(*iter); - setWidget (widget); + setWidget(widget); } -void CSVPrefs::Page::addSetting (CSMPrefs::Setting *setting) +void CSVPrefs::Page::addSetting(CSMPrefs::Setting* setting) { - std::pair widgets = setting->makeWidgets (this); + std::pair widgets = setting->makeWidgets(this); int next = mGrid->rowCount(); if (widgets.first) { - mGrid->addWidget (widgets.first, next, 0); - mGrid->addWidget (widgets.second, next, 1); + mGrid->addWidget(widgets.first, next, 0); + mGrid->addWidget(widgets.second, next, 1); } else if (widgets.second) { - mGrid->addWidget (widgets.second, next, 0, 1, 2); + mGrid->addWidget(widgets.second, next, 0, 1, 2); } else { - mGrid->addWidget (new QWidget (this), next, 0); + mGrid->addWidget(new QWidget(this), next, 0); } } diff --git a/apps/opencs/view/prefs/page.hpp b/apps/opencs/view/prefs/page.hpp index ce13e5d9b..8968b3d59 100644 --- a/apps/opencs/view/prefs/page.hpp +++ b/apps/opencs/view/prefs/page.hpp @@ -4,9 +4,12 @@ #include "pagebase.hpp" class QGridLayout; +class QWidget; +class QObject; namespace CSMPrefs { + class Category; class Setting; } @@ -14,15 +17,14 @@ namespace CSVPrefs { class Page : public PageBase { - Q_OBJECT + Q_OBJECT - QGridLayout *mGrid; + QGridLayout* mGrid; - public: + public: + Page(CSMPrefs::Category& category, QWidget* parent); - Page (CSMPrefs::Category& category, QWidget *parent); - - void addSetting (CSMPrefs::Setting *setting); + void addSetting(CSMPrefs::Setting* setting); }; } diff --git a/apps/opencs/view/prefs/pagebase.cpp b/apps/opencs/view/prefs/pagebase.cpp index 15535b785..ea9af3256 100644 --- a/apps/opencs/view/prefs/pagebase.cpp +++ b/apps/opencs/view/prefs/pagebase.cpp @@ -1,15 +1,17 @@ #include "pagebase.hpp" -#include #include +#include #include "../../model/prefs/category.hpp" #include "../../model/prefs/state.hpp" -CSVPrefs::PageBase::PageBase (CSMPrefs::Category& category, QWidget *parent) -: QScrollArea (parent), mCategory (category) -{} +CSVPrefs::PageBase::PageBase(CSMPrefs::Category& category, QWidget* parent) + : QScrollArea(parent) + , mCategory(category) +{ +} CSMPrefs::Category& CSVPrefs::PageBase::getCategory() { diff --git a/apps/opencs/view/prefs/pagebase.hpp b/apps/opencs/view/prefs/pagebase.hpp index ce5b378b3..b5e0836dd 100644 --- a/apps/opencs/view/prefs/pagebase.hpp +++ b/apps/opencs/view/prefs/pagebase.hpp @@ -14,25 +14,23 @@ namespace CSVPrefs { class PageBase : public QScrollArea { - Q_OBJECT + Q_OBJECT - CSMPrefs::Category& mCategory; + CSMPrefs::Category& mCategory; - public: + public: + PageBase(CSMPrefs::Category& category, QWidget* parent); - PageBase (CSMPrefs::Category& category, QWidget *parent); + CSMPrefs::Category& getCategory(); - CSMPrefs::Category& getCategory(); + protected: + void contextMenuEvent(QContextMenuEvent*) override; - protected: + private slots: - void contextMenuEvent(QContextMenuEvent*) override; + void resetCategory(); - private slots: - - void resetCategory(); - - void resetAll(); + void resetAll(); }; } diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index d6077a65a..d1bfac0ec 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -1,11 +1,21 @@ #include "actor.hpp" +#include +#include +#include + #include +#include #include -#include +#include +#include +#include + +#include +#include #include -#include +#include #include #include #include @@ -16,15 +26,14 @@ namespace CSVRender { const std::string Actor::MeshPrefix = "meshes\\"; - Actor::Actor(const std::string& id, CSMWorld::Data& data) + Actor::Actor(const ESM::RefId& id, CSMWorld::Data& data) : mId(id) , mData(data) , mBaseNode(new osg::Group()) , mSkeleton(nullptr) { mActorData = mData.getActorAdapter()->getActorData(mId); - connect(mData.getActorAdapter(), SIGNAL(actorChanged(const std::string&)), - this, SLOT(handleActorChanged(const std::string&))); + connect(mData.getActorAdapter(), &CSMWorld::ActorAdapter::actorChanged, this, &Actor::handleActorChanged); } osg::Group* Actor::getBaseNode() @@ -38,7 +47,8 @@ namespace CSVRender // Load skeleton std::string skeletonModel = mActorData->getSkeleton(); - skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + skeletonModel + = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); loadSkeleton(skeletonModel); if (!mActorData->isCreature()) @@ -63,7 +73,7 @@ namespace CSVRender mSkeleton->setActive(SceneUtil::Skeleton::Active); } - void Actor::handleActorChanged(const std::string& refId) + void Actor::handleActorChanged(const ESM::RefId& refId) { if (mId == refId) { @@ -88,16 +98,14 @@ namespace CSVRender mNodeMap.clear(); SceneUtil::NodeMapVisitor nmVisitor(mNodeMap); mSkeleton->accept(nmVisitor); - } void Actor::loadBodyParts() { for (int i = 0; i < ESM::PRT_Count; ++i) { - auto type = (ESM::PartReferenceType) i; - std::string partId = mActorData->getPart(type); - attachBodyPart(type, getBodyPartMesh(partId)); + const auto type = static_cast(i); + attachBodyPart(type, getBodyPartMesh(mActorData->getPart(type))); } } @@ -111,15 +119,15 @@ namespace CSVRender if (!mesh.empty() && node != mNodeMap.end()) { auto instance = sceneMgr->getInstance(mesh); - SceneUtil::attach(instance, mSkeleton, boneName, node->second); + SceneUtil::attach(instance, mSkeleton, boneName, node->second, sceneMgr); } } - std::string Actor::getBodyPartMesh(const std::string& bodyPartId) + std::string Actor::getBodyPartMesh(const ESM::RefId& bodyPartId) { const auto& bodyParts = mData.getBodyParts(); - int index = bodyParts.searchId(bodyPartId); + const int index = bodyParts.searchId(bodyPartId); if (index != -1 && !bodyParts.getRecord(index).isDeleted()) return MeshPrefix + bodyParts.getRecord(index).get().mModel; else diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 2f19454f7..86c7e7ff2 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -2,21 +2,18 @@ #define OPENCS_VIEW_RENDER_ACTOR_H #include +#include +#include #include #include -#include +#include #include #include "../../model/world/actoradapter.hpp" -namespace osg -{ - class Group; -} - namespace CSMWorld { class Data; @@ -38,7 +35,7 @@ namespace CSVRender /// \param id The referenceable id /// \param type The record type /// \param data The data store - Actor(const std::string& id, CSMWorld::Data& data); + Actor(const ESM::RefId& id, CSMWorld::Data& data); /// Retrieves the base node that meshes are attached to osg::Group* getBaseNode(); @@ -47,18 +44,18 @@ namespace CSVRender void update(); private slots: - void handleActorChanged(const std::string& refId); + void handleActorChanged(const ESM::RefId& refId); private: void loadSkeleton(const std::string& model); void loadBodyParts(); void attachBodyPart(ESM::PartReferenceType, const std::string& mesh); - std::string getBodyPartMesh(const std::string& bodyPartId); + std::string getBodyPartMesh(const ESM::RefId& bodyPartId); static const std::string MeshPrefix; - std::string mId; + ESM::RefId mId; CSMWorld::Data& mData; CSMWorld::ActorAdapter::ActorDataPtr mActorData; diff --git a/apps/opencs/view/render/brushdraw.cpp b/apps/opencs/view/render/brushdraw.cpp index 255a13a12..0d4500939 100644 --- a/apps/opencs/view/render/brushdraw.cpp +++ b/apps/opencs/view/render/brushdraw.cpp @@ -1,23 +1,37 @@ #include "brushdraw.hpp" #include +#include +#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + #include "../../model/world/cellcoordinates.hpp" #include "../widget/brushshapes.hpp" #include "mask.hpp" -CSVRender::BrushDraw::BrushDraw(osg::ref_ptr parentNode, bool textureMode) : - mParentNode(parentNode), mTextureMode(textureMode) +CSVRender::BrushDraw::BrushDraw(osg::ref_ptr parentNode, bool textureMode) + : mParentNode(parentNode) + , mTextureMode(textureMode) { mBrushDrawNode = new osg::Group(); mGeometry = new osg::Geometry(); mBrushDrawNode->addChild(mGeometry); + mBrushDrawNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mBrushDrawNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin"); mParentNode->addChild(mBrushDrawNode); if (mTextureMode) mLandSizeFactor = static_cast(ESM::Land::REAL_SIZE) / static_cast(ESM::Land::LAND_TEXTURE_SIZE); @@ -31,7 +45,7 @@ CSVRender::BrushDraw::~BrushDraw() mParentNode->removeChild(mBrushDrawNode); } -float CSVRender::BrushDraw::getIntersectionHeight (const osg::Vec3d& point) +float CSVRender::BrushDraw::getIntersectionHeight(const osg::Vec3d& point) { osg::Vec3d start = point; osg::Vec3d end = point; @@ -40,8 +54,8 @@ float CSVRender::BrushDraw::getIntersectionHeight (const osg::Vec3d& point) osg::Vec3d direction = end - start; // Get intersection - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( - osgUtil::Intersector::MODEL, start, end) ); + osg::ref_ptr intersector( + new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, start, end)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); @@ -67,44 +81,28 @@ float CSVRender::BrushDraw::getIntersectionHeight (const osg::Vec3d& point) void CSVRender::BrushDraw::buildPointGeometry(const osg::Vec3d& point) { - osg::ref_ptr geom (new osg::Geometry()); - osg::ref_ptr vertices (new osg::Vec3Array()); - osg::ref_ptr colors (new osg::Vec4Array()); - const float brushOutlineHeight (1.0f); - const float crossHeadSize (8.0f); + osg::ref_ptr geom(new osg::Geometry()); + osg::ref_ptr vertices(new osg::Vec3Array()); + osg::ref_ptr colors(new osg::Vec4Array()); + const float brushOutlineHeight(1.0f); + const float crossHeadSize(8.0f); osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); - vertices->push_back(osg::Vec3d( - point.x() - crossHeadSize, - point.y() - crossHeadSize, - getIntersectionHeight(osg::Vec3d( - point.x() - crossHeadSize, - point.y() - crossHeadSize, - point.z()) ) + brushOutlineHeight)); + vertices->push_back(osg::Vec3d(point.x() - crossHeadSize, point.y() - crossHeadSize, + getIntersectionHeight(osg::Vec3d(point.x() - crossHeadSize, point.y() - crossHeadSize, point.z())) + + brushOutlineHeight)); colors->push_back(lineColor); - vertices->push_back(osg::Vec3d( - point.x() + crossHeadSize, - point.y() + crossHeadSize, - getIntersectionHeight(osg::Vec3d( - point.x() + crossHeadSize, - point.y() + crossHeadSize, - point.z()) ) + brushOutlineHeight)); + vertices->push_back(osg::Vec3d(point.x() + crossHeadSize, point.y() + crossHeadSize, + getIntersectionHeight(osg::Vec3d(point.x() + crossHeadSize, point.y() + crossHeadSize, point.z())) + + brushOutlineHeight)); colors->push_back(lineColor); - vertices->push_back(osg::Vec3d( - point.x() + crossHeadSize, - point.y() - crossHeadSize, - getIntersectionHeight(osg::Vec3d( - point.x() + crossHeadSize, - point.y() - crossHeadSize, - point.z()) ) + brushOutlineHeight)); + vertices->push_back(osg::Vec3d(point.x() + crossHeadSize, point.y() - crossHeadSize, + getIntersectionHeight(osg::Vec3d(point.x() + crossHeadSize, point.y() - crossHeadSize, point.z())) + + brushOutlineHeight)); colors->push_back(lineColor); - vertices->push_back(osg::Vec3d( - point.x() - crossHeadSize, - point.y() + crossHeadSize, - getIntersectionHeight(osg::Vec3d( - point.x() - crossHeadSize, - point.y() + crossHeadSize, - point.z()) ) + brushOutlineHeight)); + vertices->push_back(osg::Vec3d(point.x() - crossHeadSize, point.y() + crossHeadSize, + getIntersectionHeight(osg::Vec3d(point.x() - crossHeadSize, point.y() + crossHeadSize, point.z())) + + brushOutlineHeight)); colors->push_back(lineColor); geom->setVertexArray(vertices); @@ -115,14 +113,21 @@ void CSVRender::BrushDraw::buildPointGeometry(const osg::Vec3d& point) void CSVRender::BrushDraw::buildSquareGeometry(const float& radius, const osg::Vec3d& point) { - osg::ref_ptr geom (new osg::Geometry()); - osg::ref_ptr vertices (new osg::Vec3Array()); - osg::ref_ptr colors (new osg::Vec4Array()); + osg::ref_ptr geom(new osg::Geometry()); + osg::ref_ptr vertices(new osg::Vec3Array()); + osg::ref_ptr colors(new osg::Vec4Array()); - const float brushOutlineHeight (1.0f); + const float brushOutlineHeight(1.0f); float diameter = radius * 2; - int resolution = static_cast(2.f * diameter / mLandSizeFactor); //half a vertex resolution - float resAdjustedLandSizeFactor = mLandSizeFactor / 2; + int resolution = static_cast(2.f * diameter / mLandSizeFactor); // half a vertex resolution + float resAdjustedLandSizeFactor = mLandSizeFactor / 2; // 128 + + if (resolution > 128) // limit accuracy for performance + { + resolution = 128; + resAdjustedLandSizeFactor = diameter / resolution; + } + osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); for (int i = 0; i < resolution; i++) @@ -130,62 +135,30 @@ void CSVRender::BrushDraw::buildSquareGeometry(const float& radius, const osg::V int step = i * resAdjustedLandSizeFactor; int step2 = (i + 1) * resAdjustedLandSizeFactor; - osg::Vec3d upHorizontalLinePoint1( - point.x() - radius + step, - point.y() - radius, - getIntersectionHeight(osg::Vec3d( - point.x() - radius + step, - point.y() - radius, - point.z())) + brushOutlineHeight); - osg::Vec3d upHorizontalLinePoint2( - point.x() - radius + step2, - point.y() - radius, - getIntersectionHeight(osg::Vec3d( - point.x() - radius + step2, - point.y() - radius, - point.z())) + brushOutlineHeight); - osg::Vec3d upVerticalLinePoint1( - point.x() - radius, - point.y() - radius + step, - getIntersectionHeight(osg::Vec3d( - point.x() - radius, - point.y() - radius + step, - point.z())) + brushOutlineHeight); - osg::Vec3d upVerticalLinePoint2( - point.x() - radius, - point.y() - radius + step2, - getIntersectionHeight(osg::Vec3d( - point.x() - radius, - point.y() - radius + step2, - point.z())) + brushOutlineHeight); - osg::Vec3d downHorizontalLinePoint1( - point.x() + radius - step, - point.y() + radius, - getIntersectionHeight(osg::Vec3d( - point.x() + radius - step, - point.y() + radius, - point.z())) + brushOutlineHeight); - osg::Vec3d downHorizontalLinePoint2( - point.x() + radius - step2, - point.y() + radius, - getIntersectionHeight(osg::Vec3d( - point.x() + radius - step2, - point.y() + radius, - point.z())) + brushOutlineHeight); - osg::Vec3d downVerticalLinePoint1( - point.x() + radius, - point.y() + radius - step, - getIntersectionHeight(osg::Vec3d( - point.x() + radius, - point.y() + radius - step, - point.z())) + brushOutlineHeight); - osg::Vec3d downVerticalLinePoint2( - point.x() + radius, - point.y() + radius - step2, - getIntersectionHeight(osg::Vec3d( - point.x() + radius, - point.y() + radius - step2, - point.z())) + brushOutlineHeight); + osg::Vec3d upHorizontalLinePoint1(point.x() - radius + step, point.y() - radius, + getIntersectionHeight(osg::Vec3d(point.x() - radius + step, point.y() - radius, point.z())) + + brushOutlineHeight); + osg::Vec3d upHorizontalLinePoint2(point.x() - radius + step2, point.y() - radius, + getIntersectionHeight(osg::Vec3d(point.x() - radius + step2, point.y() - radius, point.z())) + + brushOutlineHeight); + osg::Vec3d upVerticalLinePoint1(point.x() - radius, point.y() - radius + step, + getIntersectionHeight(osg::Vec3d(point.x() - radius, point.y() - radius + step, point.z())) + + brushOutlineHeight); + osg::Vec3d upVerticalLinePoint2(point.x() - radius, point.y() - radius + step2, + getIntersectionHeight(osg::Vec3d(point.x() - radius, point.y() - radius + step2, point.z())) + + brushOutlineHeight); + osg::Vec3d downHorizontalLinePoint1(point.x() + radius - step, point.y() + radius, + getIntersectionHeight(osg::Vec3d(point.x() + radius - step, point.y() + radius, point.z())) + + brushOutlineHeight); + osg::Vec3d downHorizontalLinePoint2(point.x() + radius - step2, point.y() + radius, + getIntersectionHeight(osg::Vec3d(point.x() + radius - step2, point.y() + radius, point.z())) + + brushOutlineHeight); + osg::Vec3d downVerticalLinePoint1(point.x() + radius, point.y() + radius - step, + getIntersectionHeight(osg::Vec3d(point.x() + radius, point.y() + radius - step, point.z())) + + brushOutlineHeight); + osg::Vec3d downVerticalLinePoint2(point.x() + radius, point.y() + radius - step2, + getIntersectionHeight(osg::Vec3d(point.x() + radius, point.y() + radius - step2, point.z())) + + brushOutlineHeight); vertices->push_back(upHorizontalLinePoint1); colors->push_back(lineColor); vertices->push_back(upHorizontalLinePoint2); @@ -212,33 +185,28 @@ void CSVRender::BrushDraw::buildSquareGeometry(const float& radius, const osg::V void CSVRender::BrushDraw::buildCircleGeometry(const float& radius, const osg::Vec3d& point) { - osg::ref_ptr geom (new osg::Geometry()); - osg::ref_ptr vertices (new osg::Vec3Array()); - osg::ref_ptr colors (new osg::Vec4Array()); - const int amountOfPoints = (osg::PI * 2.0f) * radius / 20; - const float step ((osg::PI * 2.0f) / static_cast(amountOfPoints)); - const float brushOutlineHeight (1.0f); + osg::ref_ptr geom(new osg::Geometry()); + osg::ref_ptr vertices(new osg::Vec3Array()); + osg::ref_ptr colors(new osg::Vec4Array()); + + const int amountOfPoints = 128; + const float step((osg::PI * 2.0f) / static_cast(amountOfPoints)); + const float brushOutlineHeight(1.0f); osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); for (int i = 0; i < amountOfPoints + 2; i++) { - float angle (i * step); - vertices->push_back(osg::Vec3d( - point.x() + radius * cosf(angle), - point.y() + radius * sinf(angle), - getIntersectionHeight(osg::Vec3d( - point.x() + radius * cosf(angle), - point.y() + radius * sinf(angle), - point.z()) ) + brushOutlineHeight)); + float angle(i * step); + vertices->push_back(osg::Vec3d(point.x() + radius * cosf(angle), point.y() + radius * sinf(angle), + getIntersectionHeight( + osg::Vec3d(point.x() + radius * cosf(angle), point.y() + radius * sinf(angle), point.z())) + + brushOutlineHeight)); colors->push_back(lineColor); angle = static_cast(i + 1) * step; - vertices->push_back(osg::Vec3d( - point.x() + radius * cosf(angle), - point.y() + radius * sinf(angle), - getIntersectionHeight(osg::Vec3d( - point.x() + radius * cosf(angle), - point.y() + radius * sinf(angle), - point.z()) ) + brushOutlineHeight)); + vertices->push_back(osg::Vec3d(point.x() + radius * cosf(angle), point.y() + radius * sinf(angle), + getIntersectionHeight( + osg::Vec3d(point.x() + radius * cosf(angle), point.y() + radius * sinf(angle), point.z())) + + brushOutlineHeight)); colors->push_back(lineColor); } @@ -262,36 +230,32 @@ void CSVRender::BrushDraw::update(osg::Vec3d point, int brushSize, CSVWidget::Br if (mTextureMode) { std::pair snapToGridXY = CSMWorld::CellCoordinates::toTextureCoords(point); - float offsetToMiddle = mLandSizeFactor * 0.5f; + float offsetToMiddle = mLandSizeFactor * 0.5f; snapToGridPoint = osg::Vec3d( CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(snapToGridXY.first) + offsetToMiddle, - CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(snapToGridXY.second) + offsetToMiddle, - point.z()); + CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(snapToGridXY.second) + offsetToMiddle, point.z()); } else { std::pair snapToGridXY = CSMWorld::CellCoordinates::toVertexCoords(point); - snapToGridPoint = osg::Vec3d( - CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(snapToGridXY.first), - CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(snapToGridXY.second), - point.z()); + snapToGridPoint = osg::Vec3d(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(snapToGridXY.first), + CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(snapToGridXY.second), point.z()); } - switch (toolShape) { - case (CSVWidget::BrushShape_Point) : + case (CSVWidget::BrushShape_Point): buildPointGeometry(snapToGridPoint); break; - case (CSVWidget::BrushShape_Square) : + case (CSVWidget::BrushShape_Square): buildSquareGeometry(radius, snapToGridPoint); break; - case (CSVWidget::BrushShape_Circle) : + case (CSVWidget::BrushShape_Circle): buildCircleGeometry(radius, snapToGridPoint); break; - case (CSVWidget::BrushShape_Custom) : + case (CSVWidget::BrushShape_Custom): buildSquareGeometry(1, snapToGridPoint); - //buildCustomGeometry + // buildCustomGeometry break; } diff --git a/apps/opencs/view/render/brushdraw.hpp b/apps/opencs/view/render/brushdraw.hpp index 0551631cd..27a276c7c 100644 --- a/apps/opencs/view/render/brushdraw.hpp +++ b/apps/opencs/view/render/brushdraw.hpp @@ -1,35 +1,40 @@ #ifndef CSV_RENDER_BRUSHDRAW_H #define CSV_RENDER_BRUSHDRAW_H -#include -#include +#include +#include -#include #include "../widget/brushshapes.hpp" +namespace osg +{ + class Geometry; + class Group; +} + namespace CSVRender { class BrushDraw { - public: - BrushDraw(osg::ref_ptr parentNode, bool textureMode = false); - ~BrushDraw(); + public: + BrushDraw(osg::ref_ptr parentNode, bool textureMode = false); + ~BrushDraw(); - void update(osg::Vec3d point, int brushSize, CSVWidget::BrushShape toolShape); - void hide(); + void update(osg::Vec3d point, int brushSize, CSVWidget::BrushShape toolShape); + void hide(); - private: - void buildPointGeometry(const osg::Vec3d& point); - void buildSquareGeometry(const float& radius, const osg::Vec3d& point); - void buildCircleGeometry(const float& radius, const osg::Vec3d& point); - void buildCustomGeometry(const float& radius, const osg::Vec3d& point); - float getIntersectionHeight (const osg::Vec3d& point); + private: + void buildPointGeometry(const osg::Vec3d& point); + void buildSquareGeometry(const float& radius, const osg::Vec3d& point); + void buildCircleGeometry(const float& radius, const osg::Vec3d& point); + void buildCustomGeometry(const float& radius, const osg::Vec3d& point); + float getIntersectionHeight(const osg::Vec3d& point); - osg::ref_ptr mParentNode; - osg::ref_ptr mBrushDrawNode; - osg::ref_ptr mGeometry; - bool mTextureMode; - float mLandSizeFactor; + osg::ref_ptr mParentNode; + osg::ref_ptr mBrushDrawNode; + osg::ref_ptr mGeometry; + bool mTextureMode; + float mLandSizeFactor; }; } diff --git a/apps/opencs/view/render/cameracontroller.cpp b/apps/opencs/view/render/cameracontroller.cpp index f21224d73..10033cb51 100644 --- a/apps/opencs/view/render/cameracontroller.cpp +++ b/apps/opencs/view/render/cameracontroller.cpp @@ -1,23 +1,26 @@ #include "cameracontroller.hpp" +#include #include +#include #include #include #include #include -#include #include +#include #include #include +#include +#include +#include #include #include "../../model/prefs/shortcut.hpp" -#include "scenewidget.hpp" - namespace CSVRender { @@ -35,17 +38,13 @@ namespace CSVRender : QObject(parent) , mActive(false) , mInverted(false) - , mCameraSensitivity(1/650.f) + , mCameraSensitivity(1 / 650.f) , mSecondaryMoveMult(50) , mWheelMoveMult(8) , mCamera(nullptr) { } - CameraController::~CameraController() - { - } - bool CameraController::isActive() const { return mActive; @@ -179,57 +178,63 @@ namespace CSVRender { CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget); naviPrimaryShortcut->enable(false); - connect(naviPrimaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviPrimary(bool))); + connect(naviPrimaryShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, + &FreeCameraController::naviPrimary); addShortcut(naviPrimaryShortcut); CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget); naviSecondaryShortcut->enable(false); - connect(naviSecondaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviSecondary(bool))); + connect(naviSecondaryShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, + &FreeCameraController::naviSecondary); addShortcut(naviSecondaryShortcut); - CSMPrefs::Shortcut* forwardShortcut = new CSMPrefs::Shortcut("free-forward", "scene-speed-modifier", - CSMPrefs::Shortcut::SM_Detach, widget); + CSMPrefs::Shortcut* forwardShortcut + = new CSMPrefs::Shortcut("free-forward", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, widget); forwardShortcut->enable(false); - connect(forwardShortcut, SIGNAL(activated(bool)), this, SLOT(forward(bool))); - connect(forwardShortcut, SIGNAL(secondary(bool)), this, SLOT(alternateFast(bool))); + connect(forwardShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::forward); + connect(forwardShortcut, qOverload(&CSMPrefs::Shortcut::secondary), this, + &FreeCameraController::alternateFast); addShortcut(forwardShortcut); CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("free-left", widget); leftShortcut->enable(false); - connect(leftShortcut, SIGNAL(activated(bool)), this, SLOT(left(bool))); + connect(leftShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::left); addShortcut(leftShortcut); CSMPrefs::Shortcut* backShortcut = new CSMPrefs::Shortcut("free-backward", widget); backShortcut->enable(false); - connect(backShortcut, SIGNAL(activated(bool)), this, SLOT(backward(bool))); + connect(backShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::backward); addShortcut(backShortcut); CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("free-right", widget); rightShortcut->enable(false); - connect(rightShortcut, SIGNAL(activated(bool)), this, SLOT(right(bool))); + connect(rightShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::right); addShortcut(rightShortcut); CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("free-roll-left", widget); rollLeftShortcut->enable(false); - connect(rollLeftShortcut, SIGNAL(activated(bool)), this, SLOT(rollLeft(bool))); + connect( + rollLeftShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::rollLeft); addShortcut(rollLeftShortcut); CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("free-roll-right", widget); rollRightShortcut->enable(false); - connect(rollRightShortcut, SIGNAL(activated(bool)), this, SLOT(rollRight(bool))); + connect( + rollRightShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::rollRight); addShortcut(rollRightShortcut); CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("free-speed-mode", widget); speedModeShortcut->enable(false); - connect(speedModeShortcut, SIGNAL(activated()), this, SLOT(swapSpeedMode())); + connect( + speedModeShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::swapSpeedMode); addShortcut(speedModeShortcut); } @@ -332,7 +337,7 @@ namespace CSVRender if (mRollRight) roll(rotDist); } - else if(mModified) + else if (mModified) { stabilize(); mModified = false; @@ -459,7 +464,7 @@ namespace CSVRender , mRollLeft(false) , mRollRight(false) , mPickingMask(~0u) - , mCenter(0,0,0) + , mCenter(0, 0, 0) , mDistance(0) , mOrbitSpeed(osg::PI / 4) , mOrbitSpeedMult(4) @@ -467,57 +472,63 @@ namespace CSVRender { CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget); naviPrimaryShortcut->enable(false); - connect(naviPrimaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviPrimary(bool))); + connect(naviPrimaryShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, + &OrbitCameraController::naviPrimary); addShortcut(naviPrimaryShortcut); CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget); naviSecondaryShortcut->enable(false); - connect(naviSecondaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviSecondary(bool))); + connect(naviSecondaryShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, + &OrbitCameraController::naviSecondary); addShortcut(naviSecondaryShortcut); - CSMPrefs::Shortcut* upShortcut = new CSMPrefs::Shortcut("orbit-up", "scene-speed-modifier", - CSMPrefs::Shortcut::SM_Detach, widget); + CSMPrefs::Shortcut* upShortcut + = new CSMPrefs::Shortcut("orbit-up", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, widget); upShortcut->enable(false); - connect(upShortcut, SIGNAL(activated(bool)), this, SLOT(up(bool))); - connect(upShortcut, SIGNAL(secondary(bool)), this, SLOT(alternateFast(bool))); + connect(upShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::up); + connect( + upShortcut, qOverload(&CSMPrefs::Shortcut::secondary), this, &OrbitCameraController::alternateFast); addShortcut(upShortcut); CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("orbit-left", widget); leftShortcut->enable(false); - connect(leftShortcut, SIGNAL(activated(bool)), this, SLOT(left(bool))); + connect(leftShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::left); addShortcut(leftShortcut); CSMPrefs::Shortcut* downShortcut = new CSMPrefs::Shortcut("orbit-down", widget); downShortcut->enable(false); - connect(downShortcut, SIGNAL(activated(bool)), this, SLOT(down(bool))); + connect(downShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::down); addShortcut(downShortcut); CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("orbit-right", widget); rightShortcut->enable(false); - connect(rightShortcut, SIGNAL(activated(bool)), this, SLOT(right(bool))); + connect(rightShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::right); addShortcut(rightShortcut); CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("orbit-roll-left", widget); rollLeftShortcut->enable(false); - connect(rollLeftShortcut, SIGNAL(activated(bool)), this, SLOT(rollLeft(bool))); + connect( + rollLeftShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::rollLeft); addShortcut(rollLeftShortcut); CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("orbit-roll-right", widget); rollRightShortcut->enable(false); - connect(rollRightShortcut, SIGNAL(activated(bool)), this, SLOT(rollRight(bool))); + connect(rollRightShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, + &OrbitCameraController::rollRight); addShortcut(rollRightShortcut); CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("orbit-speed-mode", widget); speedModeShortcut->enable(false); - connect(speedModeShortcut, SIGNAL(activated()), this, SLOT(swapSpeedMode())); + connect(speedModeShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &OrbitCameraController::swapSpeedMode); addShortcut(speedModeShortcut); } @@ -643,8 +654,8 @@ namespace CSVRender static const int DefaultStartDistance = 10000.f; // Try to intelligently pick focus object - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( - osgUtil::Intersector::PROJECTION, osg::Vec3d(0, 0, 0), LocalForward)); + osg::ref_ptr intersector( + new osgUtil::LineSegmentIntersector(osgUtil::Intersector::PROJECTION, osg::Vec3d(0, 0, 0), LocalForward)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); osgUtil::IntersectionVisitor visitor(intersector); @@ -669,7 +680,7 @@ namespace CSVRender mInitialized = true; } - + void OrbitCameraController::setConstRoll(bool enabled) { mConstRoll = enabled; @@ -679,7 +690,7 @@ namespace CSVRender { osg::Vec3d eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); - osg::Vec3d absoluteUp = osg::Vec3(0,0,1); + osg::Vec3d absoluteUp = osg::Vec3(0, 0, 1); osg::Quat rotation = osg::Quat(value, mConstRoll ? absoluteUp : up); osg::Vec3d oldOffset = eye - mCenter; @@ -699,10 +710,10 @@ namespace CSVRender osg::Vec3d forward = center - eye; osg::Vec3d axis = up ^ forward; - osg::Quat rotation = osg::Quat(value,axis); + osg::Quat rotation = osg::Quat(value, axis); osg::Vec3d oldOffset = eye - mCenter; osg::Vec3d newOffset = rotation * oldOffset; - + if (mConstRoll) up = rotation * up; diff --git a/apps/opencs/view/render/cameracontroller.hpp b/apps/opencs/view/render/cameracontroller.hpp index dff0f212e..a026087f6 100644 --- a/apps/opencs/view/render/cameracontroller.hpp +++ b/apps/opencs/view/render/cameracontroller.hpp @@ -1,12 +1,10 @@ #ifndef OPENCS_VIEW_CAMERACONTROLLER_H #define OPENCS_VIEW_CAMERACONTROLLER_H -#include #include #include -#include #include namespace osg @@ -22,181 +20,172 @@ namespace CSMPrefs namespace CSVRender { - class SceneWidget; - class CameraController : public QObject { - Q_OBJECT + Q_OBJECT - public: + public: + static const osg::Vec3d WorldUp; - static const osg::Vec3d WorldUp; + static const osg::Vec3d LocalUp; + static const osg::Vec3d LocalLeft; + static const osg::Vec3d LocalForward; - static const osg::Vec3d LocalUp; - static const osg::Vec3d LocalLeft; - static const osg::Vec3d LocalForward; + CameraController(QObject* parent); + ~CameraController() override = default; - CameraController(QObject* parent); - virtual ~CameraController(); + bool isActive() const; - bool isActive() const; + osg::Camera* getCamera() const; + double getCameraSensitivity() const; + bool getInverted() const; + double getSecondaryMovementMultiplier() const; + double getWheelMovementMultiplier() const; - osg::Camera* getCamera() const; - double getCameraSensitivity() const; - bool getInverted() const; - double getSecondaryMovementMultiplier() const; - double getWheelMovementMultiplier() const; + void setCamera(osg::Camera*); + void setCameraSensitivity(double value); + void setInverted(bool value); + void setSecondaryMovementMultiplier(double value); + void setWheelMovementMultiplier(double value); - void setCamera(osg::Camera*); - void setCameraSensitivity(double value); - void setInverted(bool value); - void setSecondaryMovementMultiplier(double value); - void setWheelMovementMultiplier(double value); + // moves the camera to an intelligent position + void setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up); - // moves the camera to an intelligent position - void setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up); + virtual void handleMouseMoveEvent(int x, int y) = 0; + virtual void handleMouseScrollEvent(int x) = 0; - virtual void handleMouseMoveEvent(int x, int y) = 0; - virtual void handleMouseScrollEvent(int x) = 0; + virtual void update(double dt) = 0; - virtual void update(double dt) = 0; + protected: + void addShortcut(CSMPrefs::Shortcut* shortcut); - protected: + private: + bool mActive, mInverted; + double mCameraSensitivity; + double mSecondaryMoveMult; + double mWheelMoveMult; - void addShortcut(CSMPrefs::Shortcut* shortcut); + osg::Camera* mCamera; - private: - - bool mActive, mInverted; - double mCameraSensitivity; - double mSecondaryMoveMult; - double mWheelMoveMult; - - osg::Camera* mCamera; - - std::vector mShortcuts; + std::vector mShortcuts; }; class FreeCameraController : public CameraController { - Q_OBJECT + Q_OBJECT - public: + public: + FreeCameraController(QWidget* parent); - FreeCameraController(QWidget* parent); + double getLinearSpeed() const; + double getRotationalSpeed() const; + double getSpeedMultiplier() const; - double getLinearSpeed() const; - double getRotationalSpeed() const; - double getSpeedMultiplier() const; + void setLinearSpeed(double value); + void setRotationalSpeed(double value); + void setSpeedMultiplier(double value); - void setLinearSpeed(double value); - void setRotationalSpeed(double value); - void setSpeedMultiplier(double value); + void fixUpAxis(const osg::Vec3d& up); + void unfixUpAxis(); - void fixUpAxis(const osg::Vec3d& up); - void unfixUpAxis(); + void handleMouseMoveEvent(int x, int y) override; + void handleMouseScrollEvent(int x) override; - void handleMouseMoveEvent(int x, int y) override; - void handleMouseScrollEvent(int x) override; + void update(double dt) override; - void update(double dt) override; + private: + void yaw(double value); + void pitch(double value); + void roll(double value); + void translate(const osg::Vec3d& offset); - private: + void stabilize(); - void yaw(double value); - void pitch(double value); - void roll(double value); - void translate(const osg::Vec3d& offset); + bool mLockUpright, mModified; + bool mNaviPrimary, mNaviSecondary; + bool mFast, mFastAlternate; + bool mLeft, mRight, mForward, mBackward, mRollLeft, mRollRight; + osg::Vec3d mUp; - void stabilize(); + double mLinSpeed; + double mRotSpeed; + double mSpeedMult; - bool mLockUpright, mModified; - bool mNaviPrimary, mNaviSecondary; - bool mFast, mFastAlternate; - bool mLeft, mRight, mForward, mBackward, mRollLeft, mRollRight; - osg::Vec3d mUp; + private slots: - double mLinSpeed; - double mRotSpeed; - double mSpeedMult; - - private slots: - - void naviPrimary(bool active); - void naviSecondary(bool active); - void forward(bool active); - void left(bool active); - void backward(bool active); - void right(bool active); - void rollLeft(bool active); - void rollRight(bool active); - void alternateFast(bool active); - void swapSpeedMode(); + void naviPrimary(bool active); + void naviSecondary(bool active); + void forward(bool active); + void left(bool active); + void backward(bool active); + void right(bool active); + void rollLeft(bool active); + void rollRight(bool active); + void alternateFast(bool active); + void swapSpeedMode(); }; class OrbitCameraController : public CameraController { - Q_OBJECT + Q_OBJECT - public: + public: + OrbitCameraController(QWidget* parent); - OrbitCameraController(QWidget* parent); + osg::Vec3d getCenter() const; + double getOrbitSpeed() const; + double getOrbitSpeedMultiplier() const; + unsigned int getPickingMask() const; - osg::Vec3d getCenter() const; - double getOrbitSpeed() const; - double getOrbitSpeedMultiplier() const; - unsigned int getPickingMask() const; + void setCenter(const osg::Vec3d& center); + void setOrbitSpeed(double value); + void setOrbitSpeedMultiplier(double value); + void setPickingMask(unsigned int value); - void setCenter(const osg::Vec3d& center); - void setOrbitSpeed(double value); - void setOrbitSpeedMultiplier(double value); - void setPickingMask(unsigned int value); + void handleMouseMoveEvent(int x, int y) override; + void handleMouseScrollEvent(int x) override; - void handleMouseMoveEvent(int x, int y) override; - void handleMouseScrollEvent(int x) override; + void update(double dt) override; - void update(double dt) override; + /// \brief Flag controller to be re-initialized. + void reset(); - /// \brief Flag controller to be re-initialized. - void reset(); + void setConstRoll(bool enable); - void setConstRoll(bool enable); + private: + void initialize(); - private: + void rotateHorizontal(double value); + void rotateVertical(double value); + void roll(double value); + void translate(const osg::Vec3d& offset); + void zoom(double value); - void initialize(); + bool mInitialized; + bool mNaviPrimary, mNaviSecondary; + bool mFast, mFastAlternate; + bool mLeft, mRight, mUp, mDown, mRollLeft, mRollRight; + unsigned int mPickingMask; + osg::Vec3d mCenter; + double mDistance; - void rotateHorizontal(double value); - void rotateVertical(double value); - void roll(double value); - void translate(const osg::Vec3d& offset); - void zoom(double value); + double mOrbitSpeed; + double mOrbitSpeedMult; - bool mInitialized; - bool mNaviPrimary, mNaviSecondary; - bool mFast, mFastAlternate; - bool mLeft, mRight, mUp, mDown, mRollLeft, mRollRight; - unsigned int mPickingMask; - osg::Vec3d mCenter; - double mDistance; + bool mConstRoll; - double mOrbitSpeed; - double mOrbitSpeedMult; + private slots: - bool mConstRoll; - - private slots: - - void naviPrimary(bool active); - void naviSecondary(bool active); - void up(bool active); - void left(bool active); - void down(bool active); - void right(bool active); - void rollLeft(bool active); - void rollRight(bool active); - void alternateFast(bool active); - void swapSpeedMode(); + void naviPrimary(bool active); + void naviSecondary(bool active); + void up(bool active); + void left(bool active); + void down(bool active); + void right(bool active); + void rollLeft(bool active); + void rollRight(bool active); + void alternateFast(bool active); + void swapSpeedMode(); }; } diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 2502dc1fd..6238b40e7 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,104 +1,114 @@ #include "cell.hpp" -#include +#include +#include +#include -#include -#include -#include +#include #include +#include +#include +#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include "../../model/world/idtable.hpp" -#include "../../model/world/columns.hpp" -#include "../../model/world/data.hpp" -#include "../../model/world/refcollection.hpp" -#include "../../model/world/cellcoordinates.hpp" -#include "cellwater.hpp" -#include "cellborder.hpp" #include "cellarrow.hpp" +#include "cellborder.hpp" #include "cellmarker.hpp" +#include "cellwater.hpp" +#include "instancedragmodes.hpp" #include "mask.hpp" +#include "object.hpp" #include "pathgrid.hpp" #include "terrainstorage.hpp" -#include "object.hpp" -#include "instancedragmodes.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace CSVRender { class CellNodeContainer : public osg::Referenced { - public: + public: + CellNodeContainer(Cell* cell) + : mCell(cell) + { + } - CellNodeContainer(Cell* cell) : mCell(cell) {} + Cell* getCell() { return mCell; } - Cell* getCell(){ return mCell; } - - private: - - Cell* mCell; + private: + Cell* mCell; }; class CellNodeCallback : public osg::NodeCallback { - public: - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - traverse(node, nv); - CellNodeContainer* container = static_cast(node->getUserData()); - container->getCell()->updateLand(); - } + public: + void operator()(osg::Node* node, osg::NodeVisitor* nv) override + { + traverse(node, nv); + CellNodeContainer* container = static_cast(node->getUserData()); + container->getCell()->updateLand(); + } }; } -bool CSVRender::Cell::removeObject (const std::string& id) +bool CSVRender::Cell::removeObject(const std::string& id) { - std::map::iterator iter = - mObjects.find (Misc::StringUtils::lowerCase (id)); + std::map::iterator iter = mObjects.find(Misc::StringUtils::lowerCase(id)); - if (iter==mObjects.end()) + if (iter == mObjects.end()) return false; - removeObject (iter); + removeObject(iter); return true; } -std::map::iterator CSVRender::Cell::removeObject ( - std::map::iterator iter) +std::map::iterator CSVRender::Cell::removeObject( + std::map::iterator iter) { delete iter->second; - mObjects.erase (iter++); + mObjects.erase(iter++); return iter; } -bool CSVRender::Cell::addObjects (int start, int end) +bool CSVRender::Cell::addObjects(int start, int end) { bool modified = false; const CSMWorld::RefCollection& collection = mData.getReferences(); - for (int i=start; i<=end; ++i) + for (int i = start; i <= end; ++i) { - std::string cell = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mCell); + const auto& cellId = ESM::RefId::stringRefId(collection.getRecord(i).get().mCell.toString()); - CSMWorld::RecordBase::State state = collection.getRecord (i).mState; + CSMWorld::RecordBase::State state = collection.getRecord(i).mState; - if (cell==mId && state!=CSMWorld::RecordBase::State_Deleted) + if (cellId == mId && state != CSMWorld::RecordBase::State_Deleted) { - std::string id = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mId); + const std::string& id = collection.getRecord(i).get().mId.getRefIdString(); - std::unique_ptr object (new Object (mData, mCellNode, id, false)); + auto object = std::make_unique(mData, mCellNode, id, false); if (mSubModeElementMask & Mask_Reference) - object->setSubMode (mSubMode); + object->setSubMode(mSubMode); - mObjects.insert (std::make_pair (id, object.release())); + mObjects.insert(std::make_pair(id, object.release())); modified = true; } } @@ -127,7 +137,7 @@ void CSVRender::Cell::updateLand() { const ESM::Land& esmLand = land.getRecord(mId).get(); - if (esmLand.getLandData (ESM::Land::DATA_VHGT)) + if (esmLand.getLandData(ESM::Land::DATA_VHGT)) { if (mTerrain) { @@ -136,14 +146,14 @@ void CSVRender::Cell::updateLand() } else { - mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, - mData.getResourceSystem().get(), mTerrainStorage, Mask_Terrain)); + mTerrain = std::make_unique(mCellNode, mCellNode, mData.getResourceSystem().get(), + mTerrainStorage, Mask_Terrain, ESM::Cell::sDefaultWorldspaceId); } mTerrain->loadCell(esmLand.mX, esmLand.mY); if (!mCellBorder) - mCellBorder.reset(new CellBorder(mCellNode, mCoordinates)); + mCellBorder = std::make_unique(mCellNode, mCoordinates); mCellBorder->buildShape(esmLand); @@ -155,7 +165,7 @@ void CSVRender::Cell::updateLand() unloadLand(); } -void CSVRender::Cell::unloadLand() +void CSVRender::Cell::unloadLand() { if (mTerrain) mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); @@ -164,12 +174,16 @@ void CSVRender::Cell::unloadLand() mCellBorder.reset(); } -CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, - bool deleted) -: mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted), mSubMode (0), - mSubModeElementMask (0), mUpdateLand(true), mLandDeleted(false) +CSVRender::Cell::Cell(CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted) + : mData(data) + , mId(ESM::RefId::stringRefId(id)) + , mDeleted(deleted) + , mSubMode(0) + , mSubModeElementMask(0) + , mUpdateLand(true) + , mLandDeleted(false) { - std::pair result = CSMWorld::CellCoordinates::fromId (id); + std::pair result = CSMWorld::CellCoordinates::fromId(id); mTerrainStorage = new TerrainStorage(mData); @@ -185,24 +199,23 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st if (!mDeleted) { - CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& references + = dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_References)); int rows = references.rowCount(); - addObjects (0, rows-1); + addObjects(0, rows - 1); updateLand(); - mPathgrid.reset(new Pathgrid(mData, mCellNode, mId, mCoordinates)); - mCellWater.reset(new CellWater(mData, mCellNode, mId, mCoordinates)); + mPathgrid = std::make_unique(mData, mCellNode, mId.getRefIdString(), mCoordinates); + mCellWater = std::make_unique(mData, mCellNode, mId.getRefIdString(), mCoordinates); } } CSVRender::Cell::~Cell() { - for (std::map::iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + for (std::map::iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) delete iter->second; mCellNode->getParent(0)->removeChild(mCellNode); @@ -213,86 +226,81 @@ CSVRender::Pathgrid* CSVRender::Cell::getPathgrid() const return mPathgrid.get(); } -bool CSVRender::Cell::referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +bool CSVRender::Cell::referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { bool modified = false; - for (std::map::iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) - if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + for (std::map::iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) + if (iter->second->referenceableDataChanged(topLeft, bottomRight)) modified = true; return modified; } -bool CSVRender::Cell::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, - int end) +bool CSVRender::Cell::referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end) { if (parent.isValid()) return false; bool modified = false; - for (std::map::iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) - if (iter->second->referenceableAboutToBeRemoved (parent, start, end)) + for (std::map::iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) + if (iter->second->referenceableAboutToBeRemoved(parent, start, end)) modified = true; return modified; } -bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +bool CSVRender::Cell::referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mDeleted) return false; - CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& references + = dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_References)); - int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); - int cellColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); - int stateColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + int idColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Id); + int cellColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); + int stateColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Modification); // list IDs in cell std::map ids; // id, deleted state - for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { - std::string cell = Misc::StringUtils::lowerCase (references.data ( - references.index (i, cellColumn)).toString().toUtf8().constData()); + auto cell + = ESM::RefId::stringRefId(references.data(references.index(i, cellColumn)).toString().toUtf8().constData()); - if (cell==mId) + if (cell == mId) { - std::string id = Misc::StringUtils::lowerCase (references.data ( - references.index (i, idColumn)).toString().toUtf8().constData()); + std::string id = Misc::StringUtils::lowerCase( + references.data(references.index(i, idColumn)).toString().toUtf8().constData()); - int state = references.data (references.index (i, stateColumn)).toInt(); + int state = references.data(references.index(i, stateColumn)).toInt(); - ids.insert (std::make_pair (id, state==CSMWorld::RecordBase::State_Deleted)); + ids.insert(std::make_pair(id, state == CSMWorld::RecordBase::State_Deleted)); } } // perform update and remove where needed bool modified = false; - std::map::iterator iter = mObjects.begin(); - while (iter!=mObjects.end()) + std::map::iterator iter = mObjects.begin(); + while (iter != mObjects.end()) { - if (iter->second->referenceDataChanged (topLeft, bottomRight)) + if (iter->second->referenceDataChanged(topLeft, bottomRight)) modified = true; - std::map::iterator iter2 = ids.find (iter->first); + std::map::iterator iter2 = ids.find(iter->first); - if (iter2!=ids.end()) + if (iter2 != ids.end()) { bool deleted = iter2->second; - ids.erase (iter2); + ids.erase(iter2); if (deleted) { - iter = removeObject (iter); + iter = removeObject(iter); modified = true; continue; } @@ -302,12 +310,11 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, } // add new objects - for (std::map::iterator mapIter (ids.begin()); mapIter!=ids.end(); ++mapIter) + for (std::map::iterator mapIter(ids.begin()); mapIter != ids.end(); ++mapIter) { if (!mapIter->second) { - mObjects.insert (std::make_pair ( - mapIter->first, new Object (mData, mCellNode, mapIter->first, false))); + mObjects.insert(std::make_pair(mapIter->first, new Object(mData, mCellNode, mapIter->first, false))); modified = true; } @@ -316,8 +323,7 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, return modified; } -bool CSVRender::Cell::referenceAboutToBeRemoved (const QModelIndex& parent, int start, - int end) +bool CSVRender::Cell::referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end) { if (parent.isValid()) return false; @@ -325,22 +331,21 @@ bool CSVRender::Cell::referenceAboutToBeRemoved (const QModelIndex& parent, int if (mDeleted) return false; - CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& references + = dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_References)); - int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int idColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Id); bool modified = false; - for (int row = start; row<=end; ++row) - if (removeObject (references.data ( - references.index (row, idColumn)).toString().toUtf8().constData())) + for (int row = start; row <= end; ++row) + if (removeObject(references.data(references.index(row, idColumn)).toString().toUtf8().constData())) modified = true; return modified; } -bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int end) +bool CSVRender::Cell::referenceAdded(const QModelIndex& parent, int start, int end) { if (parent.isValid()) return false; @@ -348,7 +353,7 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int if (mDeleted) return false; - return addObjects (start, end); + return addObjects(start, end); } void CSVRender::Cell::setAlteredHeight(int inCellX, int inCellY, float height) @@ -385,42 +390,41 @@ void CSVRender::Cell::pathgridRemoved() mPathgrid->removeGeometry(); } -void CSVRender::Cell::landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSVRender::Cell::landDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { mUpdateLand = true; } -void CSVRender::Cell::landAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSVRender::Cell::landAboutToBeRemoved(const QModelIndex& parent, int start, int end) { mLandDeleted = true; unloadLand(); } -void CSVRender::Cell::landAdded (const QModelIndex& parent, int start, int end) +void CSVRender::Cell::landAdded(const QModelIndex& parent, int start, int end) { mUpdateLand = true; mLandDeleted = false; } -void CSVRender::Cell::landTextureChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSVRender::Cell::landTextureChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { mUpdateLand = true; } -void CSVRender::Cell::landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSVRender::Cell::landTextureAboutToBeRemoved(const QModelIndex& parent, int start, int end) { mUpdateLand = true; } -void CSVRender::Cell::landTextureAdded (const QModelIndex& parent, int start, int end) +void CSVRender::Cell::landTextureAdded(const QModelIndex& parent, int start, int end) { mUpdateLand = true; } void CSVRender::Cell::reloadAssets() { - for (std::map::const_iterator iter (mObjects.begin()); - iter != mObjects.end(); ++iter) + for (std::map::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) { iter->second->reloadAssets(); } @@ -435,23 +439,28 @@ void CSVRender::Cell::reloadAssets() mCellWater->reloadAssets(); } -void CSVRender::Cell::setSelection (int elementMask, Selection mode) +void CSVRender::Cell::setSelection(int elementMask, Selection mode) { if (elementMask & Mask_Reference) { - for (std::map::const_iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + for (std::map::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) { bool selected = false; switch (mode) { - case Selection_Clear: selected = false; break; - case Selection_All: selected = true; break; - case Selection_Invert: selected = !iter->second->getSelected(); break; + case Selection_Clear: + selected = false; + break; + case Selection_All: + selected = true; + break; + case Selection_Invert: + selected = !iter->second->getSelected(); + break; } - iter->second->setSelected (selected); + iter->second->setSelected(selected); } } if (mPathgrid && elementMask & Mask_Pathgrid) @@ -477,24 +486,21 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode) } } -void CSVRender::Cell::selectAllWithSameParentId (int elementMask) +void CSVRender::Cell::selectAllWithSameParentId(int elementMask) { std::set ids; - for (std::map::const_iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + for (std::map::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) { if (iter->second->getSelected()) - ids.insert (iter->second->getReferenceableId()); + ids.insert(iter->second->getReferenceableId()); } - for (std::map::const_iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + for (std::map::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) { - if (!iter->second->getSelected() && - ids.find (iter->second->getReferenceableId())!=ids.end()) + if (!iter->second->getSelected() && ids.find(iter->second->getReferenceableId()) != ids.end()) { - iter->second->setSelected (true); + iter->second->setSelected(true); } } } @@ -508,26 +514,27 @@ void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode) object->setSelected(false); else if (dragMode == DragMode_Select_Invert) - object->setSelected (!object->getSelected()); + object->setSelected(!object->getSelected()); } void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) { for (auto& object : mObjects) { - if (dragMode == DragMode_Select_Only) object.second->setSelected (false); + if (dragMode == DragMode_Select_Only) + object.second->setSelected(false); - if ( ( object.second->getPosition().pos[0] > pointA[0] && object.second->getPosition().pos[0] < pointB[0] ) || - ( object.second->getPosition().pos[0] > pointB[0] && object.second->getPosition().pos[0] < pointA[0] )) + if ((object.second->getPosition().pos[0] > pointA[0] && object.second->getPosition().pos[0] < pointB[0]) + || (object.second->getPosition().pos[0] > pointB[0] && object.second->getPosition().pos[0] < pointA[0])) { - if ( ( object.second->getPosition().pos[1] > pointA[1] && object.second->getPosition().pos[1] < pointB[1] ) || - ( object.second->getPosition().pos[1] > pointB[1] && object.second->getPosition().pos[1] < pointA[1] )) + if ((object.second->getPosition().pos[1] > pointA[1] && object.second->getPosition().pos[1] < pointB[1]) + || (object.second->getPosition().pos[1] > pointB[1] && object.second->getPosition().pos[1] < pointA[1])) { - if ( ( object.second->getPosition().pos[2] > pointA[2] && object.second->getPosition().pos[2] < pointB[2] ) || - ( object.second->getPosition().pos[2] > pointB[2] && object.second->getPosition().pos[2] < pointA[2] )) + if ((object.second->getPosition().pos[2] > pointA[2] && object.second->getPosition().pos[2] < pointB[2]) + || (object.second->getPosition().pos[2] > pointB[2] + && object.second->getPosition().pos[2] < pointA[2])) handleSelectDrag(object.second, dragMode); } - } } } @@ -536,27 +543,29 @@ void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distan { for (auto& object : mObjects) { - if (dragMode == DragMode_Select_Only) object.second->setSelected (false); + if (dragMode == DragMode_Select_Only) + object.second->setSelected(false); float distanceFromObject = (point - object.second->getPosition().asVec3()).length(); - if (distanceFromObject < distance) handleSelectDrag(object.second, dragMode); + if (distanceFromObject < distance) + handleSelectDrag(object.second, dragMode); } } -void CSVRender::Cell::setCellArrows (int mask) +void CSVRender::Cell::setCellArrows(int mask) { - for (int i=0; i<4; ++i) + for (int i = 0; i < 4; ++i) { - CellArrow::Direction direction = static_cast (1<(1 << i); bool enable = mask & direction; - if (enable!=(mCellArrows[i].get()!=nullptr)) + if (enable != (mCellArrows[i].get() != nullptr)) { if (enable) - mCellArrows[i].reset (new CellArrow (mCellNode, direction, mCoordinates)); + mCellArrows[i] = std::make_unique(mCellNode, direction, mCoordinates); else - mCellArrows[i].reset (nullptr); + mCellArrows[i].reset(nullptr); } } } @@ -574,8 +583,9 @@ void CSVRender::Cell::setCellMarker() isInteriorCell = cellRecord.get().mData.mFlags & ESM::Cell::Interior; } - if (!isInteriorCell) { - mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); + if (!isInteriorCell) + { + mCellMarker = std::make_unique(mCellNode, mCoordinates, cellExists); } } @@ -589,15 +599,26 @@ bool CSVRender::Cell::isDeleted() const return mDeleted; } -std::vector > CSVRender::Cell::getSelection (unsigned int elementMask) const +osg::ref_ptr CSVRender::Cell::getSnapTarget(unsigned int elementMask) const { - std::vector > result; + osg::ref_ptr result; if (elementMask & Mask_Reference) - for (std::map::const_iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + for (auto& obj : mObjects) + if (obj.second->getSnapTarget()) + return obj.second->getTag(); + + return result; +} + +std::vector> CSVRender::Cell::getSelection(unsigned int elementMask) const +{ + std::vector> result; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) if (iter->second->getSelected()) - result.push_back (iter->second->getTag()); + result.push_back(iter->second->getTag()); if (mPathgrid && elementMask & Mask_Pathgrid) if (mPathgrid->isSelected()) result.emplace_back(mPathgrid->getTag()); @@ -605,35 +626,32 @@ std::vector > CSVRender::Cell::getSelection (un return result; } -std::vector > CSVRender::Cell::getEdited (unsigned int elementMask) const +std::vector> CSVRender::Cell::getEdited(unsigned int elementMask) const { - std::vector > result; + std::vector> result; if (elementMask & Mask_Reference) - for (std::map::const_iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + for (std::map::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) if (iter->second->isEdited()) - result.push_back (iter->second->getTag()); + result.push_back(iter->second->getTag()); return result; } -void CSVRender::Cell::setSubMode (int subMode, unsigned int elementMask) +void CSVRender::Cell::setSubMode(int subMode, unsigned int elementMask) { mSubMode = subMode; mSubModeElementMask = elementMask; if (elementMask & Mask_Reference) - for (std::map::const_iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) - iter->second->setSubMode (subMode); + for (std::map::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) + iter->second->setSubMode(subMode); } -void CSVRender::Cell::reset (unsigned int elementMask) +void CSVRender::Cell::reset(unsigned int elementMask) { if (elementMask & Mask_Reference) - for (std::map::const_iterator iter (mObjects.begin()); - iter!=mObjects.end(); ++iter) + for (std::map::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter) iter->second->reset(); if (mPathgrid && elementMask & Mask_Pathgrid) mPathgrid->resetIndicators(); diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 5998a4ee6..cf50604c2 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -1,24 +1,24 @@ #ifndef OPENCS_VIEW_CELL_H #define OPENCS_VIEW_CELL_H -#include #include #include +#include #include +#include #include #include "../../model/world/cellcoordinates.hpp" -#include "terrainstorage.hpp" #include "instancedragmodes.hpp" +#include +#include class QModelIndex; namespace osg { class Group; - class Geometry; - class Geode; } namespace CSMWorld @@ -36,150 +36,147 @@ namespace CSVRender class CellWater; class Pathgrid; class TagBase; + class TerrainStorage; class Object; class CellArrow; class CellBorder; class CellMarker; - class CellWater; class Cell { - CSMWorld::Data& mData; - std::string mId; - osg::ref_ptr mCellNode; - std::map mObjects; - std::unique_ptr mTerrain; - CSMWorld::CellCoordinates mCoordinates; - std::unique_ptr mCellArrows[4]; - std::unique_ptr mCellMarker; - std::unique_ptr mCellBorder; - std::unique_ptr mCellWater; - std::unique_ptr mPathgrid; - bool mDeleted; - int mSubMode; - unsigned int mSubModeElementMask; - bool mUpdateLand, mLandDeleted; - TerrainStorage *mTerrainStorage; + CSMWorld::Data& mData; + ESM::RefId mId; + osg::ref_ptr mCellNode; + std::map mObjects; + std::unique_ptr mTerrain; + CSMWorld::CellCoordinates mCoordinates; + std::unique_ptr mCellArrows[4]; + std::unique_ptr mCellMarker; + std::unique_ptr mCellBorder; + std::unique_ptr mCellWater; + std::unique_ptr mPathgrid; + bool mDeleted; + int mSubMode; + unsigned int mSubModeElementMask; + bool mUpdateLand, mLandDeleted; + TerrainStorage* mTerrainStorage; - /// Ignored if cell does not have an object with the given ID. - /// - /// \return Was the object deleted? - bool removeObject (const std::string& id); + /// Ignored if cell does not have an object with the given ID. + /// + /// \return Was the object deleted? + bool removeObject(const std::string& id); - // Remove object and return iterator to next object. - std::map::iterator removeObject ( - std::map::iterator iter); + // Remove object and return iterator to next object. + std::map::iterator removeObject(std::map::iterator iter); - /// Add objects from reference table that are within this cell. - /// - /// \return Have any objects been added? - bool addObjects (int start, int end); + /// Add objects from reference table that are within this cell. + /// + /// \return Have any objects been added? + bool addObjects(int start, int end); - void updateLand(); - void unloadLand(); + void updateLand(); + void unloadLand(); - public: + public: + enum Selection + { + Selection_Clear, + Selection_All, + Selection_Invert + }; - enum Selection - { - Selection_Clear, - Selection_All, - Selection_Invert - }; + public: + /// \note Deleted covers both cells that are deleted and cells that don't exist in + /// the first place. + Cell(CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted = false); - public: + ~Cell(); - /// \note Deleted covers both cells that are deleted and cells that don't exist in - /// the first place. - Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, - bool deleted = false); + /// \note Returns the pathgrid representation which will exist as long as the cell exists + Pathgrid* getPathgrid() const; - ~Cell(); + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - /// \note Returns the pathgrid representation which will exist as long as the cell exists - Pathgrid* getPathgrid() const; + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end); - /// \return Did this call result in a modification of the visual representation of - /// this cell? - bool referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight); + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - /// \return Did this call result in a modification of the visual representation of - /// this cell? - bool referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end); - /// \return Did this call result in a modification of the visual representation of - /// this cell? - bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceAdded(const QModelIndex& parent, int start, int end); - /// \return Did this call result in a modification of the visual representation of - /// this cell? - bool referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void setAlteredHeight(int inCellX, int inCellY, float height); - /// \return Did this call result in a modification of the visual representation of - /// this cell? - bool referenceAdded (const QModelIndex& parent, int start, int end); + float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); - void setAlteredHeight(int inCellX, int inCellY, float height); + float* getAlteredHeight(int inCellX, int inCellY); - float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); + void resetAlteredHeights(); - float* getAlteredHeight(int inCellX, int inCellY); + void pathgridModified(); - void resetAlteredHeights(); + void pathgridRemoved(); - void pathgridModified(); + void landDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void pathgridRemoved(); + void landAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void landAdded(const QModelIndex& parent, int start, int end); - void landAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void landTextureChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void landAdded (const QModelIndex& parent, int start, int end); + void landTextureAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void landTextureChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void landTextureAdded(const QModelIndex& parent, int start, int end); - void landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void reloadAssets(); - void landTextureAdded (const QModelIndex& parent, int start, int end); + void setSelection(int elementMask, Selection mode); - void reloadAssets(); + // Select everything that references the same ID as at least one of the elements + // already selected + void selectAllWithSameParentId(int elementMask); - void setSelection (int elementMask, Selection mode); + void handleSelectDrag(Object* object, DragMode dragMode); - // Select everything that references the same ID as at least one of the elements - // already selected - void selectAllWithSameParentId (int elementMask); + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); - void handleSelectDrag(Object* object, DragMode dragMode); + void selectWithinDistance(const osg::Vec3d& pointA, float distance, DragMode dragMode); - void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); + void setCellArrows(int mask); - void selectWithinDistance(const osg::Vec3d& pointA, float distance, DragMode dragMode); + /// \brief Set marker for this cell. + void setCellMarker(); - void setCellArrows (int mask); + /// Returns 0, 0 in case of an unpaged cell. + CSMWorld::CellCoordinates getCoordinates() const; - /// \brief Set marker for this cell. - void setCellMarker(); + bool isDeleted() const; - /// Returns 0, 0 in case of an unpaged cell. - CSMWorld::CellCoordinates getCoordinates() const; + osg::ref_ptr getSnapTarget(unsigned int elementMask) const; - bool isDeleted() const; + std::vector> getSelection(unsigned int elementMask) const; - std::vector > getSelection (unsigned int elementMask) const; + std::vector> getEdited(unsigned int elementMask) const; - std::vector > getEdited (unsigned int elementMask) const; + void setSubMode(int subMode, unsigned int elementMask); - void setSubMode (int subMode, unsigned int elementMask); + /// Erase all overrides and restore the visual representation of the cell to its + /// true state. + void reset(unsigned int elementMask); - /// Erase all overrides and restore the visual representation of the cell to its - /// true state. - void reset (unsigned int elementMask); - - friend class CellNodeCallback; + friend class CellNodeCallback; }; } diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index 4d6155123..d31df99a1 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -1,38 +1,62 @@ - #include "cellarrow.hpp" -#include -#include -#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include #include "../../model/prefs/state.hpp" -#include "../../model/prefs/shortcutmanager.hpp" + +#include +#include +#include #include #include "mask.hpp" -CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) -: TagBase (Mask_CellArrow), mArrow (arrow) -{} +namespace CSVRender +{ + struct WorldspaceHitResult; +} -CSVRender::CellArrow *CSVRender::CellArrowTag::getCellArrow() const +CSVRender::CellArrowTag::CellArrowTag(CellArrow* arrow) + : TagBase(Mask_CellArrow) + , mArrow(arrow) +{ +} + +CSVRender::CellArrow* CSVRender::CellArrowTag::getCellArrow() const { return mArrow; } -QString CSVRender::CellArrowTag::getToolTip (bool hideBasics) const +QString CSVRender::CellArrowTag::getToolTip(bool hideBasics, const WorldspaceHitResult& /*hit*/) const { - QString text ("Direction: "); + QString text("Direction: "); switch (mArrow->getDirection()) { - case CellArrow::Direction_North: text += "North"; break; - case CellArrow::Direction_West: text += "West"; break; - case CellArrow::Direction_South: text += "South"; break; - case CellArrow::Direction_East: text += "East"; break; + case CellArrow::Direction_North: + text += "North"; + break; + case CellArrow::Direction_West: + text += "West"; + break; + case CellArrow::Direction_South: + text += "South"; + break; + case CellArrow::Direction_East: + text += "East"; + break; } if (!hideBasics) @@ -55,127 +79,138 @@ QString CSVRender::CellArrowTag::getToolTip (bool hideBasics) const return CSMPrefs::State::get().getShortcutManager().processToolTip(text); } - void CSVRender::CellArrow::adjustTransform() { // position const int cellSize = Constants::CellSizeInUnits; - const int offset = cellSize / 2 + 800; + const int offset = cellSize / 2 + 600; - int x = mCoordinates.getX()*cellSize + cellSize/2; - int y = mCoordinates.getY()*cellSize + cellSize/2; + int x = mCoordinates.getX() * cellSize + cellSize / 2; + int y = mCoordinates.getY() * cellSize + cellSize / 2; float xr = 0; float yr = 0; float zr = 0; - float angle = osg::DegreesToRadians (90.0f); + float angle = osg::DegreesToRadians(90.0f); switch (mDirection) { - case Direction_North: y += offset; xr = -angle; zr = angle; break; - case Direction_West: x -= offset; yr = -angle; break; - case Direction_South: y -= offset; xr = angle; zr = angle; break; - case Direction_East: x += offset; yr = angle; break; + case Direction_North: + y += offset; + xr = -angle; + zr = angle; + break; + case Direction_West: + x -= offset; + yr = -angle; + break; + case Direction_South: + y -= offset; + xr = angle; + zr = angle; + break; + case Direction_East: + x += offset; + yr = angle; + break; }; - mBaseNode->setPosition (osg::Vec3f (x, y, 0)); + mBaseNode->setPosition(osg::Vec3f(x, y, 0)); // orientation - osg::Quat xr2 (xr, osg::Vec3f (1,0,0)); - osg::Quat yr2 (yr, osg::Vec3f (0,1,0)); - osg::Quat zr2 (zr, osg::Vec3f (0,0,1)); - mBaseNode->setAttitude (zr2*yr2*xr2); + osg::Quat xr2(xr, osg::Vec3f(1, 0, 0)); + osg::Quat yr2(yr, osg::Vec3f(0, 1, 0)); + osg::Quat zr2(zr, osg::Vec3f(0, 0, 1)); + mBaseNode->setAttitude(zr2 * yr2 * xr2); } void CSVRender::CellArrow::buildShape() { - osg::ref_ptr geometry (new osg::Geometry); + osg::ref_ptr geometry(new osg::Geometry); - const int arrowWidth = 4000; - const int arrowLength = 1500; - const int arrowHeight = 500; + const int arrowWidth = 2700; + const int arrowLength = 1350; + const int arrowHeight = 300; - osg::Vec3Array *vertices = new osg::Vec3Array; - for (int i2=0; i2<2; ++i2) - for (int i=0; i<2; ++i) + osg::Vec3Array* vertices = new osg::Vec3Array; + for (int i2 = 0; i2 < 2; ++i2) + for (int i = 0; i < 2; ++i) { - float height = i ? -arrowHeight/2 : arrowHeight/2; - vertices->push_back (osg::Vec3f (height, -arrowWidth/2, 0)); - vertices->push_back (osg::Vec3f (height, arrowWidth/2, 0)); - vertices->push_back (osg::Vec3f (height, 0, arrowLength)); + float height = i ? -arrowHeight / 2 : arrowHeight / 2; + vertices->push_back(osg::Vec3f(height, -arrowWidth / 2, 0)); + vertices->push_back(osg::Vec3f(height, arrowWidth / 2, 0)); + vertices->push_back(osg::Vec3f(height, 0, arrowLength)); } - geometry->setVertexArray (vertices); + geometry->setVertexArray(vertices); - osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0); // top - primitives->push_back (0); - primitives->push_back (1); - primitives->push_back (2); + primitives->push_back(0); + primitives->push_back(1); + primitives->push_back(2); // bottom - primitives->push_back (5); - primitives->push_back (4); - primitives->push_back (3); + primitives->push_back(5); + primitives->push_back(4); + primitives->push_back(3); // back - primitives->push_back (3+6); - primitives->push_back (4+6); - primitives->push_back (1+6); + primitives->push_back(3 + 6); + primitives->push_back(4 + 6); + primitives->push_back(1 + 6); - primitives->push_back (3+6); - primitives->push_back (1+6); - primitives->push_back (0+6); + primitives->push_back(3 + 6); + primitives->push_back(1 + 6); + primitives->push_back(0 + 6); // sides - primitives->push_back (0+6); - primitives->push_back (2+6); - primitives->push_back (5+6); + primitives->push_back(0 + 6); + primitives->push_back(2 + 6); + primitives->push_back(5 + 6); - primitives->push_back (0+6); - primitives->push_back (5+6); - primitives->push_back (3+6); + primitives->push_back(0 + 6); + primitives->push_back(5 + 6); + primitives->push_back(3 + 6); - primitives->push_back (4+6); - primitives->push_back (5+6); - primitives->push_back (2+6); + primitives->push_back(4 + 6); + primitives->push_back(5 + 6); + primitives->push_back(2 + 6); - primitives->push_back (4+6); - primitives->push_back (2+6); - primitives->push_back (1+6); + primitives->push_back(4 + 6); + primitives->push_back(2 + 6); + primitives->push_back(1 + 6); - geometry->addPrimitiveSet (primitives); + geometry->addPrimitiveSet(primitives); - osg::Vec4Array *colours = new osg::Vec4Array; + osg::Vec4Array* colours = new osg::Vec4Array; - for (int i=0; i<6; ++i) - colours->push_back (osg::Vec4f (0.11f, 0.6f, 0.95f, 1.0f)); - for (int i=0; i<6; ++i) - colours->push_back (osg::Vec4f (0.08f, 0.44f, 0.7f, 1.0f)); + for (int i = 0; i < 6; ++i) + colours->push_back(osg::Vec4f(0.11f, 0.6f, 0.95f, 1.0f)); + for (int i = 0; i < 6; ++i) + colours->push_back(osg::Vec4f(0.08f, 0.44f, 0.7f, 1.0f)); - geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable (geometry); - - mBaseNode->addChild (geode); + mBaseNode->addChild(geometry); } -CSVRender::CellArrow::CellArrow (osg::Group *cellNode, Direction direction, - const CSMWorld::CellCoordinates& coordinates) -: mDirection (direction), mParentNode (cellNode), mCoordinates (coordinates) +CSVRender::CellArrow::CellArrow(osg::Group* cellNode, Direction direction, const CSMWorld::CellCoordinates& coordinates) + : mDirection(direction) + , mParentNode(cellNode) + , mCoordinates(coordinates) { mBaseNode = new osg::PositionAttitudeTransform; - mBaseNode->setUserData (new CellArrowTag (this)); + mBaseNode->setUserData(new CellArrowTag(this)); - mParentNode->addChild (mBaseNode); + mParentNode->addChild(mBaseNode); - mBaseNode->setNodeMask (Mask_CellArrow); + mBaseNode->setNodeMask(Mask_CellArrow); adjustTransform(); buildShape(); @@ -183,7 +218,7 @@ CSVRender::CellArrow::CellArrow (osg::Group *cellNode, Direction direction, CSVRender::CellArrow::~CellArrow() { - mParentNode->removeChild (mBaseNode); + mParentNode->removeChild(mBaseNode); } CSMWorld::CellCoordinates CSVRender::CellArrow::getCoordinates() const diff --git a/apps/opencs/view/render/cellarrow.hpp b/apps/opencs/view/render/cellarrow.hpp index 9a49b80db..7bb39e02d 100644 --- a/apps/opencs/view/render/cellarrow.hpp +++ b/apps/opencs/view/render/cellarrow.hpp @@ -3,6 +3,8 @@ #include "tagbase.hpp" +#include + #include #include "../../model/world/cellcoordinates.hpp" @@ -16,58 +18,53 @@ namespace osg namespace CSVRender { class CellArrow; + struct WorldspaceHitResult; class CellArrowTag : public TagBase { - CellArrow *mArrow; + CellArrow* mArrow; - public: + public: + CellArrowTag(CellArrow* arrow); - CellArrowTag (CellArrow *arrow); + CellArrow* getCellArrow() const; - CellArrow *getCellArrow() const; - - QString getToolTip (bool hideBasics) const override; + QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override; }; - class CellArrow { - public: + public: + enum Direction + { + Direction_North = 1, + Direction_West = 2, + Direction_South = 4, + Direction_East = 8 + }; - enum Direction - { - Direction_North = 1, - Direction_West = 2, - Direction_South = 4, - Direction_East = 8 - }; + private: + // not implemented + CellArrow(const CellArrow&); + CellArrow& operator=(const CellArrow&); - private: + Direction mDirection; + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; + CSMWorld::CellCoordinates mCoordinates; - // not implemented - CellArrow (const CellArrow&); - CellArrow& operator= (const CellArrow&); + void adjustTransform(); - Direction mDirection; - osg::Group* mParentNode; - osg::ref_ptr mBaseNode; - CSMWorld::CellCoordinates mCoordinates; + void buildShape(); - void adjustTransform(); + public: + CellArrow(osg::Group* cellNode, Direction direction, const CSMWorld::CellCoordinates& coordinates); - void buildShape(); + ~CellArrow(); - public: + CSMWorld::CellCoordinates getCoordinates() const; - CellArrow (osg::Group *cellNode, Direction direction, - const CSMWorld::CellCoordinates& coordinates); - - ~CellArrow(); - - CSMWorld::CellCoordinates getCoordinates() const; - - Direction getDirection() const; + Direction getDirection() const; }; } diff --git a/apps/opencs/view/render/cellborder.cpp b/apps/opencs/view/render/cellborder.cpp index d8ff63801..f63814dcb 100644 --- a/apps/opencs/view/render/cellborder.cpp +++ b/apps/opencs/view/render/cellborder.cpp @@ -1,11 +1,17 @@ #include "cellborder.hpp" +#include +#include +#include #include #include -#include #include +#include +#include +#include +#include -#include +#include #include "mask.hpp" @@ -19,12 +25,11 @@ const int CSVRender::CellBorder::CellSize = ESM::Land::REAL_SIZE; */ const int CSVRender::CellBorder::VertexCount = (ESM::Land::LAND_SIZE * 4) - 4; - CSVRender::CellBorder::CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords) : mParentNode(cellNode) { mBorderGeometry = new osg::Geometry(); - + mBaseNode = new osg::PositionAttitudeTransform(); mBaseNode->setNodeMask(Mask_CellBorder); mBaseNode->setPosition(osg::Vec3f(coords.getX() * CellSize, coords.getY() * CellSize, 10)); @@ -79,8 +84,8 @@ void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand) mBorderGeometry->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET); - osg::ref_ptr primitives = - new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount + 1); + osg::ref_ptr primitives + = new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount + 1); // Assign one primitive to each vertex. for (size_t i = 0; i < VertexCount; ++i) diff --git a/apps/opencs/view/render/cellborder.hpp b/apps/opencs/view/render/cellborder.hpp index be2e18eee..e1201a1f3 100644 --- a/apps/opencs/view/render/cellborder.hpp +++ b/apps/opencs/view/render/cellborder.hpp @@ -28,14 +28,12 @@ namespace CSVRender class CellBorder { public: - CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords); ~CellBorder(); void buildShape(const ESM::Land& esmLand); private: - static const int CellSize; static const int VertexCount; diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp index 3de96ab02..5b116ec04 100644 --- a/apps/opencs/view/render/cellmarker.cpp +++ b/apps/opencs/view/render/cellmarker.cpp @@ -1,17 +1,31 @@ #include "cellmarker.hpp" +#include + #include +#include +#include #include -#include +#include +#include +#include +#include #include +#include #include -CSVRender::CellMarkerTag::CellMarkerTag(CellMarker *marker) -: TagBase(Mask_CellMarker), mMarker(marker) -{} +#include +#include +#include -CSVRender::CellMarker *CSVRender::CellMarkerTag::getCellMarker() const +CSVRender::CellMarkerTag::CellMarkerTag(CellMarker* marker) + : TagBase(Mask_CellMarker) + , mMarker(marker) +{ +} + +CSVRender::CellMarker* CSVRender::CellMarkerTag::getCellMarker() const { return mMarker; } @@ -21,7 +35,7 @@ void CSVRender::CellMarker::buildMarker() const int characterSize = 20; // Set up attributes of marker text. - osg::ref_ptr markerText (new osgText::Text); + osg::ref_ptr markerText(new osgText::Text); markerText->setLayout(osgText::Text::LEFT_TO_RIGHT); markerText->setCharacterSize(characterSize); markerText->setAlignment(osgText::Text::CENTER_CENTER); @@ -38,15 +52,11 @@ void CSVRender::CellMarker::buildMarker() } // Add text containing cell's coordinates. - std::string coordinatesText = - std::to_string(mCoordinates.getX()) + "," + - std::to_string(mCoordinates.getY()); + std::string coordinatesText = std::to_string(mCoordinates.getX()) + "," + std::to_string(mCoordinates.getY()); markerText->setText(coordinatesText); // Add text to marker node. - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(markerText); - mMarkerNode->addChild(geode); + mMarkerNode->addChild(markerText); } void CSVRender::CellMarker::positionMarker() @@ -61,12 +71,10 @@ void CSVRender::CellMarker::positionMarker() } CSVRender::CellMarker::CellMarker( - osg::Group *cellNode, - const CSMWorld::CellCoordinates& coordinates, - const bool cellExists -) : mCellNode(cellNode), - mCoordinates(coordinates), - mExists(cellExists) + osg::Group* cellNode, const CSMWorld::CellCoordinates& coordinates, const bool cellExists) + : mCellNode(cellNode) + , mCoordinates(coordinates) + , mExists(cellExists) { // Set up node for cell marker. mMarkerNode = new osg::AutoTransform(); diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp index 4246b20b8..c623a2960 100644 --- a/apps/opencs/view/render/cellmarker.hpp +++ b/apps/opencs/view/render/cellmarker.hpp @@ -19,49 +19,42 @@ namespace CSVRender class CellMarkerTag : public TagBase { - private: + private: + CellMarker* mMarker; - CellMarker *mMarker; + public: + CellMarkerTag(CellMarker* marker); - public: - - CellMarkerTag(CellMarker *marker); - - CellMarker *getCellMarker() const; + CellMarker* getCellMarker() const; }; /// \brief Marker to display cell coordinates. class CellMarker { - private: + private: + osg::Group* mCellNode; + osg::ref_ptr mMarkerNode; + CSMWorld::CellCoordinates mCoordinates; + bool mExists; - osg::Group* mCellNode; - osg::ref_ptr mMarkerNode; - CSMWorld::CellCoordinates mCoordinates; - bool mExists; + // Not implemented. + CellMarker(const CellMarker&); + CellMarker& operator=(const CellMarker&); - // Not implemented. - CellMarker(const CellMarker&); - CellMarker& operator=(const CellMarker&); + /// \brief Build marker containing cell's coordinates. + void buildMarker(); - /// \brief Build marker containing cell's coordinates. - void buildMarker(); + /// \brief Position marker at center of cell. + void positionMarker(); - /// \brief Position marker at center of cell. - void positionMarker(); + public: + /// \brief Constructor. + /// \param cellNode Cell to create marker for. + /// \param coordinates Coordinates of cell. + /// \param cellExists Whether or not cell exists. + CellMarker(osg::Group* cellNode, const CSMWorld::CellCoordinates& coordinates, const bool cellExists); - public: - - /// \brief Constructor. - /// \param cellNode Cell to create marker for. - /// \param coordinates Coordinates of cell. - /// \param cellExists Whether or not cell exists. - CellMarker( - osg::Group *cellNode, - const CSMWorld::CellCoordinates& coordinates, - const bool cellExists); - - ~CellMarker(); + ~CellMarker(); }; } diff --git a/apps/opencs/view/render/cellwater.cpp b/apps/opencs/view/render/cellwater.cpp index f8857c3af..7589f2c1f 100644 --- a/apps/opencs/view/render/cellwater.cpp +++ b/apps/opencs/view/render/cellwater.cpp @@ -1,13 +1,26 @@ #include "cellwater.hpp" -#include +#include +#include + #include #include #include +#include +#include +#include +#include +#include +#include -#include +#include +#include +#include +#include + +#include #include -#include +#include #include #include #include @@ -22,29 +35,29 @@ namespace CSVRender { const int CellWater::CellSize = ESM::Land::REAL_SIZE; - CellWater::CellWater(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, - const CSMWorld::CellCoordinates& cellCoords) + CellWater::CellWater( + CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, const CSMWorld::CellCoordinates& cellCoords) : mData(data) , mId(id) , mParentNode(cellNode) , mWaterTransform(nullptr) - , mWaterNode(nullptr) + , mWaterGroup(nullptr) , mWaterGeometry(nullptr) , mDeleted(false) , mExterior(false) , mHasWater(false) { mWaterTransform = new osg::PositionAttitudeTransform(); - mWaterTransform->setPosition(osg::Vec3f(cellCoords.getX() * CellSize + CellSize / 2.f, - cellCoords.getY() * CellSize + CellSize / 2.f, 0)); + mWaterTransform->setPosition(osg::Vec3f( + cellCoords.getX() * CellSize + CellSize / 2.f, cellCoords.getY() * CellSize + CellSize / 2.f, 0)); mWaterTransform->setNodeMask(Mask_Water); mParentNode->addChild(mWaterTransform); - mWaterNode = new osg::Geode(); - mWaterTransform->addChild(mWaterNode); + mWaterGroup = new osg::Group(); + mWaterTransform->addChild(mWaterGroup); - int cellIndex = mData.getCells().searchId(mId); + const int cellIndex = mData.getCells().searchId(ESM::RefId::stringRefId(mId)); if (cellIndex > -1) { updateCellData(mData.getCells().getRecord(cellIndex)); @@ -52,8 +65,7 @@ namespace CSVRender // Keep water existence/height up to date QAbstractItemModel* cells = mData.getTableModel(CSMWorld::UniversalId::Type_Cells); - connect(cells, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), - this, SLOT(cellDataChanged(const QModelIndex&, const QModelIndex&))); + connect(cells, &QAbstractItemModel::dataChanged, this, &CellWater::cellDataChanged); } CellWater::~CellWater() @@ -119,7 +131,7 @@ namespace CSVRender { const CSMWorld::Record& cellRecord = cells.getRecord(row); - if (Misc::StringUtils::lowerCase(cellRecord.get().mId) == mId) + if (cellRecord.get().mId == ESM::RefId::stringRefId(mId)) updateCellData(cellRecord); } } @@ -136,7 +148,7 @@ namespace CSVRender if (mWaterGeometry) { - mWaterNode->removeDrawable(mWaterGeometry); + mWaterGroup->removeChild(mWaterGeometry); mWaterGeometry = nullptr; } @@ -164,8 +176,9 @@ namespace CSVRender mWaterGeometry->setStateSet(SceneUtil::createSimpleWaterStateSet(Alpha, RenderBin)); // Add water texture - std::string textureName = Fallback::Map::getString("Water_SurfaceTexture"); - textureName = "textures/water/" + textureName + "00.dds"; + std::string textureName = "textures/water/"; + textureName += Fallback::Map::getString("Water_SurfaceTexture"); + textureName += "00.dds"; Resource::ImageManager* imageManager = mData.getResourceSystem()->getImageManager(); @@ -176,7 +189,6 @@ namespace CSVRender mWaterGeometry->getStateSet()->setTextureAttributeAndModes(0, waterTexture, osg::StateAttribute::ON); - - mWaterNode->addDrawable(mWaterGeometry); + mWaterGroup->addChild(mWaterGeometry); } } diff --git a/apps/opencs/view/render/cellwater.hpp b/apps/opencs/view/render/cellwater.hpp index 47e586707..249849d78 100644 --- a/apps/opencs/view/render/cellwater.hpp +++ b/apps/opencs/view/render/cellwater.hpp @@ -5,14 +5,13 @@ #include -#include #include +#include -#include "../../model/world/record.hpp" +class QModelIndex; namespace osg { - class Geode; class Geometry; class Group; class PositionAttitudeTransform; @@ -23,6 +22,9 @@ namespace CSMWorld struct Cell; class CellCoordinates; class Data; + + template + struct Record; } namespace CSVRender @@ -31,41 +33,39 @@ namespace CSVRender /// adds a large patch of water much larger than the typical size of a cell. class CellWater : public QObject { - Q_OBJECT + Q_OBJECT - public: + public: + CellWater(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, + const CSMWorld::CellCoordinates& cellCoords); - CellWater(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, - const CSMWorld::CellCoordinates& cellCoords); + ~CellWater(); - ~CellWater(); + void updateCellData(const CSMWorld::Record& cellRecord); - void updateCellData(const CSMWorld::Record& cellRecord); + void reloadAssets(); - void reloadAssets(); + private slots: - private slots: + void cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + private: + void recreate(); - private: + static const int CellSize; - void recreate(); + CSMWorld::Data& mData; + std::string mId; - static const int CellSize; + osg::Group* mParentNode; - CSMWorld::Data& mData; - std::string mId; + osg::ref_ptr mWaterTransform; + osg::ref_ptr mWaterGroup; + osg::ref_ptr mWaterGeometry; - osg::Group* mParentNode; - - osg::ref_ptr mWaterTransform; - osg::ref_ptr mWaterNode; - osg::ref_ptr mWaterGeometry; - - bool mDeleted; - bool mExterior; - bool mHasWater; + bool mDeleted; + bool mExterior; + bool mHasWater; }; } diff --git a/apps/opencs/view/render/commands.cpp b/apps/opencs/view/render/commands.cpp index 699bf5d01..68e72778a 100644 --- a/apps/opencs/view/render/commands.cpp +++ b/apps/opencs/view/render/commands.cpp @@ -1,19 +1,20 @@ #include "commands.hpp" -#include +#include + +#include +#include #include -#include -#include "editmode.hpp" -#include "terrainselection.hpp" #include "terrainshapemode.hpp" -#include "terraintexturemode.hpp" #include "worldspacewidget.hpp" -CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent) +CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand( + WorldspaceWidget* worldspaceWidget, QUndoCommand* parent) : mWorldspaceWidget(worldspaceWidget) -{ } +{ +} void CSVRender::DrawTerrainSelectionCommand::redo() { diff --git a/apps/opencs/view/render/commands.hpp b/apps/opencs/view/render/commands.hpp index 62b7fbfdc..50e2a29ea 100644 --- a/apps/opencs/view/render/commands.hpp +++ b/apps/opencs/view/render/commands.hpp @@ -2,14 +2,13 @@ #define CSV_RENDER_COMMANDS_HPP #include - #include #include "worldspacewidget.hpp" namespace CSVRender { - class TerrainSelection; + class WorldspaceWidget; /* Current solution to force a redrawing of the terrain-selection grid diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index ca4aa0fd5..6f40f8446 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -1,79 +1,90 @@ #include "editmode.hpp" -#include "tagbase.hpp" +#include + #include "worldspacewidget.hpp" +class QMouseEvent; +class QWidget; + +namespace CSVWidget +{ + class SceneToolbar; +} + CSVRender::WorldspaceWidget& CSVRender::EditMode::getWorldspaceWidget() { return *mWorldspaceWidget; } -CSVRender::EditMode::EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, - unsigned int mask, const QString& tooltip, QWidget *parent) -: ModeButton (icon, tooltip, parent), mWorldspaceWidget (worldspaceWidget), mMask (mask) -{} +CSVRender::EditMode::EditMode( + WorldspaceWidget* worldspaceWidget, const QIcon& icon, unsigned int mask, const QString& tooltip, QWidget* parent) + : ModeButton(icon, tooltip, parent) + , mWorldspaceWidget(worldspaceWidget) + , mMask(mask) +{ +} unsigned int CSVRender::EditMode::getInteractionMask() const { return mMask; } -void CSVRender::EditMode::activate (CSVWidget::SceneToolbar *toolbar) +void CSVRender::EditMode::activate(CSVWidget::SceneToolbar* toolbar) { - mWorldspaceWidget->setInteractionMask (mMask); - mWorldspaceWidget->clearSelection (~mMask); + mWorldspaceWidget->setInteractionMask(mMask); + mWorldspaceWidget->clearSelection(~mMask); } -void CSVRender::EditMode::setEditLock (bool locked) -{ +void CSVRender::EditMode::setEditLock(bool locked) {} -} +void CSVRender::EditMode::primaryOpenPressed(const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::primaryOpenPressed (const WorldspaceHitResult& hit) {} +void CSVRender::EditMode::primaryEditPressed(const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::primaryEditPressed (const WorldspaceHitResult& hit) {} +void CSVRender::EditMode::secondaryEditPressed(const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::secondaryEditPressed (const WorldspaceHitResult& hit) {} +void CSVRender::EditMode::primarySelectPressed(const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::primarySelectPressed (const WorldspaceHitResult& hit) {} +void CSVRender::EditMode::secondarySelectPressed(const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::secondarySelectPressed (const WorldspaceHitResult& hit) {} +void CSVRender::EditMode::tertiarySelectPressed(const WorldspaceHitResult& hit) {} -bool CSVRender::EditMode::primaryEditStartDrag (const QPoint& pos) +bool CSVRender::EditMode::primaryEditStartDrag(const QPoint& pos) { return false; } -bool CSVRender::EditMode::secondaryEditStartDrag (const QPoint& pos) +bool CSVRender::EditMode::secondaryEditStartDrag(const QPoint& pos) { return false; } -bool CSVRender::EditMode::primarySelectStartDrag (const QPoint& pos) +bool CSVRender::EditMode::primarySelectStartDrag(const QPoint& pos) { return false; } -bool CSVRender::EditMode::secondarySelectStartDrag (const QPoint& pos) +bool CSVRender::EditMode::secondarySelectStartDrag(const QPoint& pos) { return false; } -void CSVRender::EditMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) {} +void CSVRender::EditMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) {} void CSVRender::EditMode::dragCompleted(const QPoint& pos) {} void CSVRender::EditMode::dragAborted() {} -void CSVRender::EditMode::dragWheel (int diff, double speedFactor) {} +void CSVRender::EditMode::dragWheel(int diff, double speedFactor) {} -void CSVRender::EditMode::dragEnterEvent (QDragEnterEvent *event) {} +void CSVRender::EditMode::dragEnterEvent(QDragEnterEvent* event) {} -void CSVRender::EditMode::dropEvent (QDropEvent *event) {} +void CSVRender::EditMode::dropEvent(QDropEvent* event) {} -void CSVRender::EditMode::dragMoveEvent (QDragMoveEvent *event) {} +void CSVRender::EditMode::dragMoveEvent(QDragMoveEvent* event) {} -void CSVRender::EditMode::mouseMoveEvent (QMouseEvent *event) {} +void CSVRender::EditMode::mouseMoveEvent(QMouseEvent* event) {} int CSVRender::EditMode::getSubMode() const { diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp index 52c35811d..3492dbe0e 100644 --- a/apps/opencs/view/render/editmode.hpp +++ b/apps/opencs/view/render/editmode.hpp @@ -1,107 +1,113 @@ #ifndef CSV_RENDER_EDITMODE_H #define CSV_RENDER_EDITMODE_H -#include - #include "../widget/modebutton.hpp" class QDragEnterEvent; class QDropEvent; class QDragMoveEvent; class QPoint; +class QMouseEvent; +class QObject; +class QWidget; + +namespace CSVWidget +{ + class SceneToolbar; +} namespace CSVRender { class WorldspaceWidget; struct WorldspaceHitResult; - class TagBase; class EditMode : public CSVWidget::ModeButton { - Q_OBJECT + Q_OBJECT - WorldspaceWidget *mWorldspaceWidget; - unsigned int mMask; + WorldspaceWidget* mWorldspaceWidget; + unsigned int mMask; - protected: + protected: + WorldspaceWidget& getWorldspaceWidget(); - WorldspaceWidget& getWorldspaceWidget(); + public: + EditMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, unsigned int mask, const QString& tooltip = "", + QWidget* parent = nullptr); - public: + unsigned int getInteractionMask() const; - EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, unsigned int mask, - const QString& tooltip = "", QWidget *parent = nullptr); + void activate(CSVWidget::SceneToolbar* toolbar) override; - unsigned int getInteractionMask() const; + /// Default-implementation: Ignored. + virtual void setEditLock(bool locked); - void activate (CSVWidget::SceneToolbar *toolbar) override; + /// Default-implementation: Ignored. + virtual void primaryOpenPressed(const WorldspaceHitResult& hit); - /// Default-implementation: Ignored. - virtual void setEditLock (bool locked); + /// Default-implementation: Ignored. + virtual void primaryEditPressed(const WorldspaceHitResult& hit); - /// Default-implementation: Ignored. - virtual void primaryOpenPressed (const WorldspaceHitResult& hit); + /// Default-implementation: Ignored. + virtual void secondaryEditPressed(const WorldspaceHitResult& hit); - /// Default-implementation: Ignored. - virtual void primaryEditPressed (const WorldspaceHitResult& hit); + /// Default-implementation: Ignored. + virtual void primarySelectPressed(const WorldspaceHitResult& hit); - /// Default-implementation: Ignored. - virtual void secondaryEditPressed (const WorldspaceHitResult& hit); + /// Default-implementation: Ignored. + virtual void secondarySelectPressed(const WorldspaceHitResult& hit); - /// Default-implementation: Ignored. - virtual void primarySelectPressed (const WorldspaceHitResult& hit); + /// Default-implementation: Ignored. + virtual void tertiarySelectPressed(const WorldspaceHitResult& hit); - /// Default-implementation: Ignored. - virtual void secondarySelectPressed (const WorldspaceHitResult& hit); + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool primaryEditStartDrag(const QPoint& pos); - /// Default-implementation: ignore and return false - /// - /// \return Drag accepted? - virtual bool primaryEditStartDrag (const QPoint& pos); + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool secondaryEditStartDrag(const QPoint& pos); - /// Default-implementation: ignore and return false - /// - /// \return Drag accepted? - virtual bool secondaryEditStartDrag (const QPoint& pos); + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool primarySelectStartDrag(const QPoint& pos); - /// Default-implementation: ignore and return false - /// - /// \return Drag accepted? - virtual bool primarySelectStartDrag (const QPoint& pos); + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool secondarySelectStartDrag(const QPoint& pos); - /// Default-implementation: ignore and return false - /// - /// \return Drag accepted? - virtual bool secondarySelectStartDrag (const QPoint& pos); + /// Default-implementation: ignored + virtual void drag(const QPoint& pos, int diffX, int diffY, double speedFactor); - /// Default-implementation: ignored - virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + /// Default-implementation: ignored + virtual void dragCompleted(const QPoint& pos); - /// Default-implementation: ignored - virtual void dragCompleted(const QPoint& pos); + /// Default-implementation: ignored + /// + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + virtual void dragAborted(); - /// Default-implementation: ignored - /// - /// \note dragAborted will not be called, if the drag is aborted via changing - /// editing mode - virtual void dragAborted(); + /// Default-implementation: ignored + virtual void dragWheel(int diff, double speedFactor); - /// Default-implementation: ignored - virtual void dragWheel (int diff, double speedFactor); + /// Default-implementation: ignored + void dragEnterEvent(QDragEnterEvent* event) override; - /// Default-implementation: ignored - void dragEnterEvent (QDragEnterEvent *event) override; + /// Default-implementation: ignored + void dropEvent(QDropEvent* event) override; - /// Default-implementation: ignored - void dropEvent (QDropEvent *event) override; + /// Default-implementation: ignored + void dragMoveEvent(QDragMoveEvent* event) override; - /// Default-implementation: ignored - void dragMoveEvent (QDragMoveEvent *event) override; + void mouseMoveEvent(QMouseEvent* event) override; - void mouseMoveEvent (QMouseEvent *event) override; - - /// Default: return -1 - virtual int getSubMode() const; + /// Default: return -1 + virtual int getSubMode() const; }; } diff --git a/apps/opencs/view/render/instancedragmodes.hpp b/apps/opencs/view/render/instancedragmodes.hpp index 01547545a..2629a9d2f 100644 --- a/apps/opencs/view/render/instancedragmodes.hpp +++ b/apps/opencs/view/render/instancedragmodes.hpp @@ -12,7 +12,10 @@ namespace CSVRender DragMode_Select_Only, DragMode_Select_Add, DragMode_Select_Remove, - DragMode_Select_Invert + DragMode_Select_Invert, + DragMode_Move_Snap, + DragMode_Rotate_Snap, + DragMode_Scale_Snap }; } #endif diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 99ddce7f7..e0f05c2fa 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -1,37 +1,67 @@ - #include "instancemode.hpp" #include #include #include +#include +#include +#include +#include +#include +#include + #include "../../model/prefs/state.hpp" +#include +#include #include #include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../model/prefs/shortcut.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/commandmacro.hpp" -#include "../../model/prefs/shortcut.hpp" +#include "../../model/world/tablemimedata.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" #include "mask.hpp" -#include "object.hpp" -#include "worldspacewidget.hpp" -#include "pagedworldspacewidget.hpp" -#include "instanceselectionmode.hpp" #include "instancemovemode.hpp" +#include "instanceselectionmode.hpp" +#include "object.hpp" +#include "pagedworldspacewidget.hpp" +#include "worldspacewidget.hpp" -int CSVRender::InstanceMode::getSubModeFromId (const std::string& id) const +int CSVRender::InstanceMode::getSubModeFromId(const std::string& id) const { - return id=="move" ? 0 : (id=="rotate" ? 1 : 2); + return id == "move" ? 0 : (id == "rotate" ? 1 : 2); } osg::Vec3f CSVRender::InstanceMode::quatToEuler(const osg::Quat& rot) const @@ -57,21 +87,54 @@ osg::Vec3f CSVRender::InstanceMode::quatToEuler(const osg::Quat& rot) const osg::Quat CSVRender::InstanceMode::eulerToQuat(const osg::Vec3f& euler) const { - osg::Quat xr = osg::Quat(-euler[0], osg::Vec3f(1,0,0)); - osg::Quat yr = osg::Quat(-euler[1], osg::Vec3f(0,1,0)); - osg::Quat zr = osg::Quat(-euler[2], osg::Vec3f(0,0,1)); + osg::Quat xr = osg::Quat(-euler[0], osg::Vec3f(1, 0, 0)); + osg::Quat yr = osg::Quat(-euler[1], osg::Vec3f(0, 1, 0)); + osg::Quat zr = osg::Quat(-euler[2], osg::Vec3f(0, 0, 1)); return zr * yr * xr; } -osg::Vec3f CSVRender::InstanceMode::getSelectionCenter(const std::vector >& selection) const +float CSVRender::InstanceMode::roundFloatToMult(const float val, const double mult) const +{ + if (mult == 0) + return val; + return round(val / mult) * mult; +} + +osg::Vec3 CSVRender::InstanceMode::calculateSnapPositionRelativeToTarget(osg::Vec3 initalPosition, + osg::Vec3 targetPosition, osg::Vec3 targetRotation, osg::Vec3 translation, double snap) const +{ + auto quatTargetRotation + = osg::Quat(targetRotation[0], osg::X_AXIS, targetRotation[1], osg::Y_AXIS, targetRotation[2], osg::Z_AXIS); + + // Break object world coords into snap target space + auto localWorld = osg::Matrix::translate(initalPosition) + * osg::Matrix::inverse(osg::Matrix::translate(targetPosition)) * osg::Matrix::rotate(quatTargetRotation); + + osg::Vec3 localPosition = localWorld.getTrans(); + + osg::Vec3 newTranslation; + newTranslation[0] = CSVRender::InstanceMode::roundFloatToMult(localPosition[0] + translation[0], snap); + newTranslation[1] = CSVRender::InstanceMode::roundFloatToMult(localPosition[1] + translation[1], snap); + newTranslation[2] = CSVRender::InstanceMode::roundFloatToMult(localPosition[2] + translation[2], snap); + + // rebuild object's world coordinates (note: inverse operations from local construction) + auto newObjectWorld = osg::Matrix::translate(newTranslation) + * osg::Matrix::inverse(osg::Matrix::rotate(quatTargetRotation)) * osg::Matrix::translate(targetPosition); + + osg::Vec3 newObjectPosition = newObjectWorld.getTrans(); + + return newObjectPosition; +} + +osg::Vec3f CSVRender::InstanceMode::getSelectionCenter(const std::vector>& selection) const { osg::Vec3f center = osg::Vec3f(0, 0, 0); int objectCount = 0; - for (std::vector >::const_iterator iter (selection.begin()); iter!=selection.end(); ++iter) + for (std::vector>::const_iterator iter(selection.begin()); iter != selection.end(); ++iter) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { const ESM::Position& position = objectTag->mObject->getPosition(); center += osg::Vec3f(position.pos[0], position.pos[1], position.pos[2]); @@ -123,93 +186,107 @@ osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, con return mousePlanePoint; } -CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent) -: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", - parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None), - mDragAxis (-1), mLocked (false), mUnitScaleDist(1), mParentNode (parentNode) +CSVRender::InstanceMode::InstanceMode( + WorldspaceWidget* worldspaceWidget, osg::ref_ptr parentNode, QWidget* parent) + : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, + "Instance editing", parent) + , mSubMode(nullptr) + , mSubModeId("move") + , mSelectionMode(nullptr) + , mDragMode(DragMode_None) + , mDragAxis(-1) + , mLocked(false) + , mUnitScaleDist(1) + , mParentNode(parentNode) { - connect(this, SIGNAL(requestFocus(const std::string&)), - worldspaceWidget, SIGNAL(requestFocus(const std::string&))); + connect(this, &InstanceMode::requestFocus, worldspaceWidget, &WorldspaceWidget::requestFocus); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); - connect(deleteShortcut, SIGNAL(activated(bool)), this, SLOT(deleteSelectedInstances(bool))); + connect( + deleteShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &InstanceMode::deleteSelectedInstances); - // Following classes could be simplified by using QSignalMapper, which is obsolete in Qt5.10, but not in Qt4.8 and Qt5.14 - CSMPrefs::Shortcut* dropToCollisionShortcut = new CSMPrefs::Shortcut("scene-instance-drop-collision", worldspaceWidget); - connect(dropToCollisionShortcut, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToCollision())); - CSMPrefs::Shortcut* dropToTerrainLevelShortcut = new CSMPrefs::Shortcut("scene-instance-drop-terrain", worldspaceWidget); - connect(dropToTerrainLevelShortcut, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToTerrain())); - CSMPrefs::Shortcut* dropToCollisionShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-collision-separately", worldspaceWidget); - connect(dropToCollisionShortcut2, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToCollisionSeparately())); - CSMPrefs::Shortcut* dropToTerrainLevelShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget); - connect(dropToTerrainLevelShortcut2, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToTerrainSeparately())); + // Following classes could be simplified by using QSignalMapper, which is obsolete in Qt5.10, but not in Qt4.8 and + // Qt5.14 + CSMPrefs::Shortcut* dropToCollisionShortcut + = new CSMPrefs::Shortcut("scene-instance-drop-collision", worldspaceWidget); + connect(dropToCollisionShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &InstanceMode::dropSelectedInstancesToCollision); + CSMPrefs::Shortcut* dropToTerrainLevelShortcut + = new CSMPrefs::Shortcut("scene-instance-drop-terrain", worldspaceWidget); + connect(dropToTerrainLevelShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &InstanceMode::dropSelectedInstancesToTerrain); + CSMPrefs::Shortcut* dropToCollisionShortcut2 + = new CSMPrefs::Shortcut("scene-instance-drop-collision-separately", worldspaceWidget); + connect(dropToCollisionShortcut2, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &InstanceMode::dropSelectedInstancesToCollisionSeparately); + CSMPrefs::Shortcut* dropToTerrainLevelShortcut2 + = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget); + connect(dropToTerrainLevelShortcut2, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &InstanceMode::dropSelectedInstancesToTerrainSeparately); } -void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) +void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar) { if (!mSubMode) { - mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); - mSubMode->addButton (new InstanceMoveMode (this), "move"); - mSubMode->addButton (":scenetoolbar/transform-rotate", "rotate", + mSubMode = new CSVWidget::SceneToolMode(toolbar, "Edit Sub-Mode"); + mSubMode->addButton(new InstanceMoveMode(this), "move"); + mSubMode->addButton(":scenetoolbar/transform-rotate", "rotate", "Rotate selected instances" "
  • Use {scene-edit-primary} to rotate instances freely
  • " "
  • Use {scene-edit-secondary} to rotate instances within the grid
  • " "
  • The center of the view acts as the axis of rotation
  • " - "
" - "Grid rotate not implemented yet"); - mSubMode->addButton (":scenetoolbar/transform-scale", "scale", + ""); + mSubMode->addButton(":scenetoolbar/transform-scale", "scale", "Scale selected instances" "
  • Use {scene-edit-primary} to scale instances freely
  • " "
  • Use {scene-edit-secondary} to scale instances along the grid
  • " "
  • The scaling rate is based on how close the start of a drag is to the center of the screen
  • " - "
" - "Grid scale not implemented yet"); + ""); - mSubMode->setButton (mSubModeId); + mSubMode->setButton(mSubModeId); - connect (mSubMode, SIGNAL (modeChanged (const std::string&)), - this, SLOT (subModeChanged (const std::string&))); + connect(mSubMode, &CSVWidget::SceneToolMode::modeChanged, this, &InstanceMode::subModeChanged); } if (!mSelectionMode) - mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget(), mParentNode); + mSelectionMode = new InstanceSelectionMode(toolbar, getWorldspaceWidget(), mParentNode); mDragMode = DragMode_None; - EditMode::activate (toolbar); + EditMode::activate(toolbar); - toolbar->addTool (mSubMode); - toolbar->addTool (mSelectionMode); + toolbar->addTool(mSubMode); + toolbar->addTool(mSelectionMode); std::string subMode = mSubMode->getCurrentId(); - getWorldspaceWidget().setSubMode (getSubModeFromId (subMode), Mask_Reference); + getWorldspaceWidget().setSubMode(getSubModeFromId(subMode), Mask_Reference); } -void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) +void CSVRender::InstanceMode::deactivate(CSVWidget::SceneToolbar* toolbar) { mDragMode = DragMode_None; - getWorldspaceWidget().reset (Mask_Reference); + getWorldspaceWidget().reset(Mask_Reference); if (mSelectionMode) { - toolbar->removeTool (mSelectionMode); + toolbar->removeTool(mSelectionMode); delete mSelectionMode; mSelectionMode = nullptr; } if (mSubMode) { - toolbar->removeTool (mSubMode); + toolbar->removeTool(mSubMode); delete mSubMode; mSubMode = nullptr; } - EditMode::deactivate (toolbar); + EditMode::deactivate(toolbar); } -void CSVRender::InstanceMode::setEditLock (bool locked) +void CSVRender::InstanceMode::setEditLock(bool locked) { mLocked = locked; @@ -217,17 +294,17 @@ void CSVRender::InstanceMode::setEditLock (bool locked) getWorldspaceWidget().abortDrag(); } -void CSVRender::InstanceMode::primaryEditPressed (const WorldspaceHitResult& hit) +void CSVRender::InstanceMode::primaryEditPressed(const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) - primarySelectPressed (hit); + primarySelectPressed(hit); } -void CSVRender::InstanceMode::primaryOpenPressed (const WorldspaceHitResult& hit) +void CSVRender::InstanceMode::primaryOpenPressed(const WorldspaceHitResult& hit) { - if(hit.tag) + if (hit.tag) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { const std::string refId = objectTag->mObject->getReferenceId(); emit requestFocus(refId); @@ -235,90 +312,118 @@ void CSVRender::InstanceMode::primaryOpenPressed (const WorldspaceHitResult& hit } } -void CSVRender::InstanceMode::secondaryEditPressed (const WorldspaceHitResult& hit) +void CSVRender::InstanceMode::secondaryEditPressed(const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) - secondarySelectPressed (hit); + secondarySelectPressed(hit); } -void CSVRender::InstanceMode::primarySelectPressed (const WorldspaceHitResult& hit) +void CSVRender::InstanceMode::primarySelectPressed(const WorldspaceHitResult& hit) { - getWorldspaceWidget().clearSelection (Mask_Reference); + getWorldspaceWidget().clearSelection(Mask_Reference); if (hit.tag) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { // hit an Object, select it CSVRender::Object* object = objectTag->mObject; - object->setSelected (true); + object->setSelected(true); return; } } } -void CSVRender::InstanceMode::secondarySelectPressed (const WorldspaceHitResult& hit) +void CSVRender::InstanceMode::secondarySelectPressed(const WorldspaceHitResult& hit) { if (hit.tag) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { // hit an Object, toggle its selection state CSVRender::Object* object = objectTag->mObject; - object->setSelected (!object->getSelected()); + object->setSelected(!object->getSelected()); return; } } } -bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) +void CSVRender::InstanceMode::tertiarySelectPressed(const WorldspaceHitResult& hit) { - if (mDragMode!=DragMode_None || mLocked) + auto* snapTarget = dynamic_cast(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); + + if (snapTarget) + { + snapTarget->mObject->setSnapTarget(false); + } + + if (hit.tag) + { + if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) + { + // hit an Object, toggle its selection state + CSVRender::Object* object = objectTag->mObject; + object->setSnapTarget(!object->getSnapTarget()); + return; + } + } +} + +bool CSVRender::InstanceMode::primaryEditStartDrag(const QPoint& pos) +{ + if (mDragMode != DragMode_None || mLocked) return false; - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) { // Only change selection at the start of drag if no object is already selected if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) { - getWorldspaceWidget().clearSelection (Mask_Reference); - if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) + getWorldspaceWidget().clearSelection(Mask_Reference); + if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) { CSVRender::Object* object = objectTag->mObject; - object->setSelected (true); + object->setSelected(true); } } - selection = getWorldspaceWidget().getSelection (Mask_Reference); + selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) return false; } - for (std::vector >::iterator iter (selection.begin()); - iter!=selection.end(); ++iter) + mObjectsAtDragStart.clear(); + + for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { if (mSubModeId == "move") { - objectTag->mObject->setEdited (Object::Override_Position); + objectTag->mObject->setEdited(Object::Override_Position); + float x = objectTag->mObject->getPosition().pos[0]; + float y = objectTag->mObject->getPosition().pos[1]; + float z = objectTag->mObject->getPosition().pos[2]; + osg::Vec3f thisPoint(x, y, z); + mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); + mObjectsAtDragStart.emplace_back(thisPoint); mDragMode = DragMode_Move; } else if (mSubModeId == "rotate") { - objectTag->mObject->setEdited (Object::Override_Rotation); + objectTag->mObject->setEdited(Object::Override_Rotation); mDragMode = DragMode_Rotate; } else if (mSubModeId == "scale") { - objectTag->mObject->setEdited (Object::Override_Scale); + objectTag->mObject->setEdited(Object::Override_Scale); mDragMode = DragMode_Scale; // Calculate scale factor - std::vector > editedSelection = getWorldspaceWidget().getEdited (Mask_Reference); + std::vector> editedSelection = getWorldspaceWidget().getEdited(Mask_Reference); osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); int widgetHeight = getWorldspaceWidget().height(); @@ -331,7 +436,7 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) } } - if (CSVRender::ObjectMarkerTag *objectTag = dynamic_cast (hit.tag.get())) + if (CSVRender::ObjectMarkerTag* objectTag = dynamic_cast(hit.tag.get())) { mDragAxis = objectTag->mAxis; } @@ -341,84 +446,143 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) return true; } -bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) +bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos) { - if (mLocked) + if (mDragMode != DragMode_None || mLocked) return false; - return false; + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); + if (selection.empty()) + { + // Only change selection at the start of drag if no object is already selected + if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + { + getWorldspaceWidget().clearSelection(Mask_Reference); + if (CSVRender::ObjectTag* objectTag = dynamic_cast(hit.tag.get())) + { + CSVRender::Object* object = objectTag->mObject; + object->setSelected(true); + } + } + + selection = getWorldspaceWidget().getSelection(Mask_Reference); + if (selection.empty()) + return false; + } + + mObjectsAtDragStart.clear(); + + for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter) + { + if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) + { + if (mSubModeId == "move") + { + objectTag->mObject->setEdited(Object::Override_Position); + float x = objectTag->mObject->getPosition().pos[0]; + float y = objectTag->mObject->getPosition().pos[1]; + float z = objectTag->mObject->getPosition().pos[2]; + osg::Vec3f thisPoint(x, y, z); + + mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); + mObjectsAtDragStart.emplace_back(thisPoint); + mDragMode = DragMode_Move_Snap; + } + else if (mSubModeId == "rotate") + { + objectTag->mObject->setEdited(Object::Override_Rotation); + mDragMode = DragMode_Rotate_Snap; + } + else if (mSubModeId == "scale") + { + objectTag->mObject->setEdited(Object::Override_Scale); + mDragMode = DragMode_Scale_Snap; + + // Calculate scale factor + std::vector> editedSelection = getWorldspaceWidget().getEdited(Mask_Reference); + osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); + + int widgetHeight = getWorldspaceWidget().height(); + + float dx = pos.x() - center.x(); + float dy = (widgetHeight - pos.y()) - center.y(); + + mUnitScaleDist = std::sqrt(dx * dx + dy * dy); + } + } + } + + if (CSVRender::ObjectMarkerTag* objectTag = dynamic_cast(hit.tag.get())) + { + mDragAxis = objectTag->mAxis; + } + else + mDragAxis = -1; + + return true; } -bool CSVRender::InstanceMode::primarySelectStartDrag (const QPoint& pos) +bool CSVRender::InstanceMode::primarySelectStartDrag(const QPoint& pos) { - if (mDragMode!=DragMode_None || mLocked) + if (mDragMode != DragMode_None || mLocked) return false; std::string primarySelectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); - if ( primarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; - else if ( primarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; - else if ( primarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; - else if ( primarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; + if (primarySelectAction == "Select only") + mDragMode = DragMode_Select_Only; + else if (primarySelectAction == "Add to selection") + mDragMode = DragMode_Select_Add; + else if (primarySelectAction == "Remove from selection") + mDragMode = DragMode_Select_Remove; + else if (primarySelectAction == "Invert selection") + mDragMode = DragMode_Select_Invert; - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mSelectionMode->setDragStart(hit.worldPos); return true; } -bool CSVRender::InstanceMode::secondarySelectStartDrag (const QPoint& pos) +bool CSVRender::InstanceMode::secondarySelectStartDrag(const QPoint& pos) { - if (mDragMode!=DragMode_None || mLocked) + if (mDragMode != DragMode_None || mLocked) return false; std::string secondarySelectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); - if ( secondarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; - else if ( secondarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; - else if ( secondarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; - else if ( secondarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; + if (secondarySelectAction == "Select only") + mDragMode = DragMode_Select_Only; + else if (secondarySelectAction == "Add to selection") + mDragMode = DragMode_Select_Add; + else if (secondarySelectAction == "Remove from selection") + mDragMode = DragMode_Select_Remove; + else if (secondarySelectAction == "Invert selection") + mDragMode = DragMode_Select_Invert; - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mSelectionMode->setDragStart(hit.worldPos); return true; } -void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) +void CSVRender::InstanceMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) { osg::Vec3f offset; osg::Quat rotation; - std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); + std::vector> selection = getWorldspaceWidget().getEdited(Mask_Reference); + auto* snapTarget = dynamic_cast(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); - if (mDragMode == DragMode_Move) + if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) { - osg::Vec3f eye, centre, up; - getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); - - if (diffY) - { - offset += up * diffY * speedFactor; - } - if (diffX) - { - offset += ((centre-eye) ^ up) * diffX * speedFactor; - } - - if (mDragAxis!=-1) - { - for (int i=0; i<3; ++i) - { - if (i!=mDragAxis) - offset[i] = 0; - } - } } - else if (mDragMode == DragMode_Rotate) + else if (mDragMode == DragMode_Rotate || mDragMode == DragMode_Rotate_Snap) { osg::Vec3f eye, centre, up; - getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt(eye, centre, up); float angle; osg::Vec3f axis; @@ -434,7 +598,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou osg::Vec3f screenDir = cameraRotation * osg::Vec3f(diffX, diffY, 0); screenDir.normalize(); - angle = std::sqrt(diffX*diffX + diffY*diffY) * rotationFactor; + angle = std::sqrt(diffX * diffX + diffY * diffY) * rotationFactor; axis = screenDir ^ camForward; } else @@ -479,7 +643,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou rotation = osg::Quat(angle, axis); } - else if (mDragMode == DragMode_Scale) + else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) { osg::Vec3f center = getScreenCoords(getSelectionCenter(selection)); @@ -498,38 +662,77 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou else if (mSelectionMode->getCurrentId() == "cube-centre") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); - mSelectionMode->drawSelectionCubeCentre (mousePlanePoint); + mSelectionMode->drawSelectionCubeCentre(mousePlanePoint); return; } else if (mSelectionMode->getCurrentId() == "cube-corner") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); - mSelectionMode->drawSelectionCubeCorner (mousePlanePoint); + mSelectionMode->drawSelectionCubeCorner(mousePlanePoint); return; } else if (mSelectionMode->getCurrentId() == "sphere") { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); - mSelectionMode->drawSelectionSphere (mousePlanePoint); + mSelectionMode->drawSelectionSphere(mousePlanePoint); return; } + int i = 0; + // Apply - for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) + for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter, i++) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { - if (mDragMode == DragMode_Move) + if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) { ESM::Position position = objectTag->mObject->getPosition(); - for (int i=0; i<3; ++i) + osg::Vec3f mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); + float addToX = mousePos.x() - mDragStart.x(); + float addToY = mousePos.y() - mDragStart.y(); + float addToZ = mousePos.z() - mDragStart.z(); + position.pos[0] = mObjectsAtDragStart[i].x() + addToX; + position.pos[1] = mObjectsAtDragStart[i].y() + addToY; + position.pos[2] = mObjectsAtDragStart[i].z() + addToZ; + + if (mDragMode == DragMode_Move_Snap) { - position.pos[i] += offset[i]; + double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble(); + + if (snapTarget) + { + osg::Vec3 translation(addToX, addToY, addToZ); + + auto snapTargetPosition = snapTarget->mObject->getPosition(); + auto newPosition = calculateSnapPositionRelativeToTarget(mObjectsAtDragStart[i], + snapTargetPosition.asVec3(), snapTargetPosition.asRotationVec3(), translation, snap); + + position.pos[0] = newPosition[0]; + position.pos[1] = newPosition[1]; + position.pos[2] = newPosition[2]; + } + else + { + position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], snap); + position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], snap); + position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], snap); + } + } + + // XYZ-locking + if (mDragAxis != -1) + { + for (int j = 0; j < 3; ++j) + { + if (j != mDragAxis) + position.pos[j] = mObjectsAtDragStart[i][j]; + } } objectTag->mObject->setPosition(position.pos); } - else if (mDragMode == DragMode_Rotate) + else if (mDragMode == DragMode_Rotate || mDragMode == DragMode_Rotate_Snap) { ESM::Position position = objectTag->mObject->getPosition(); @@ -547,7 +750,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou objectTag->mObject->setRotation(position.rot); } - else if (mDragMode == DragMode_Scale) + else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) { // Reset scale objectTag->mObject->setEdited(0); @@ -556,7 +759,13 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou float scale = objectTag->mObject->getScale(); scale *= offset.x(); - objectTag->mObject->setScale (scale); + if (mDragMode == DragMode_Scale_Snap) + { + scale = CSVRender::InstanceMode::roundFloatToMult( + scale, CSMPrefs::get()["3D Scene Editing"]["gridsnap-scale"].toDouble()); + } + + objectTag->mObject->setScale(scale); } } } @@ -564,8 +773,9 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) { - std::vector > selection = - getWorldspaceWidget().getEdited (Mask_Reference); + std::vector> selection = getWorldspaceWidget().getEdited(Mask_Reference); + + auto* snapTarget = dynamic_cast(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); @@ -573,189 +783,263 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) switch (mDragMode) { - case DragMode_Move: description = "Move Instances"; break; - case DragMode_Rotate: description = "Rotate Instances"; break; - case DragMode_Scale: description = "Scale Instances"; break; - case DragMode_Select_Only : + case DragMode_Move: + description = "Move Instances"; + break; + case DragMode_Rotate: + description = "Rotate Instances"; + break; + case DragMode_Scale: + description = "Scale Instances"; + break; + case DragMode_Select_Only: handleSelectDrag(pos); return; break; - case DragMode_Select_Add : + case DragMode_Select_Add: handleSelectDrag(pos); return; break; - case DragMode_Select_Remove : + case DragMode_Select_Remove: handleSelectDrag(pos); return; break; - case DragMode_Select_Invert : + case DragMode_Select_Invert: handleSelectDrag(pos); return; break; - - case DragMode_None: break; + case DragMode_Move_Snap: + description = "Move Instances"; + break; + case DragMode_Rotate_Snap: + description = "Rotate Instances"; + break; + case DragMode_Scale_Snap: + description = "Scale Instances"; + break; + case DragMode_None: + break; } + CSMWorld::CommandMacro macro(undoStack, description); - CSMWorld::CommandMacro macro (undoStack, description); - - for (std::vector >::iterator iter (selection.begin()); - iter!=selection.end(); ++iter) + // Is this even supposed to be here? + for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { - objectTag->mObject->apply (macro); + if (mDragMode == DragMode_Rotate_Snap) + { + ESM::Position position = objectTag->mObject->getPosition(); + double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble(); + + float xOffset = 0; + float yOffset = 0; + float zOffset = 0; + + if (snapTarget) + { + auto snapTargetPosition = snapTarget->mObject->getPosition(); + auto rotation = snapTargetPosition.rot; + if (rotation) + { + xOffset = remainder(rotation[0], osg::DegreesToRadians(snap)); + yOffset = remainder(rotation[1], osg::DegreesToRadians(snap)); + zOffset = remainder(rotation[2], osg::DegreesToRadians(snap)); + } + } + + position.rot[0] + = CSVRender::InstanceMode::roundFloatToMult(position.rot[0], osg::DegreesToRadians(snap)) + xOffset; + position.rot[1] + = CSVRender::InstanceMode::roundFloatToMult(position.rot[1], osg::DegreesToRadians(snap)) + yOffset; + position.rot[2] + = CSVRender::InstanceMode::roundFloatToMult(position.rot[2], osg::DegreesToRadians(snap)) + zOffset; + + objectTag->mObject->setRotation(position.rot); + } + + objectTag->mObject->apply(macro); } } + mObjectsAtDragStart.clear(); mDragMode = DragMode_None; } void CSVRender::InstanceMode::dragAborted() { - getWorldspaceWidget().reset (Mask_Reference); + getWorldspaceWidget().reset(Mask_Reference); mDragMode = DragMode_None; } -void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) +void CSVRender::InstanceMode::dragWheel(int diff, double speedFactor) { - if (mDragMode==DragMode_Move) + if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) { osg::Vec3f eye; osg::Vec3f centre; osg::Vec3f up; - getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt(eye, centre, up); osg::Vec3f offset = centre - eye; offset.normalize(); offset *= diff * speedFactor; - std::vector > selection = - getWorldspaceWidget().getEdited (Mask_Reference); + std::vector> selection = getWorldspaceWidget().getEdited(Mask_Reference); + auto snapTarget + = dynamic_cast(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()); - for (std::vector >::iterator iter (selection.begin()); - iter!=selection.end(); ++iter) + int j = 0; + + for (std::vector>::iterator iter(selection.begin()); iter != selection.end(); ++iter, j++) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(iter->get())) { ESM::Position position = objectTag->mObject->getPosition(); - for (int i=0; i<3; ++i) + auto preMovedObjectPosition = position.asVec3(); + for (int i = 0; i < 3; ++i) position.pos[i] += offset[i]; - objectTag->mObject->setPosition (position.pos); + + if (mDragMode == DragMode_Move_Snap) + { + double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble(); + + if (snapTarget) + { + osg::Vec3 translation(snap, snap, snap); + + auto snapTargetPosition = snapTarget->mObject->getPosition(); + auto newPosition = calculateSnapPositionRelativeToTarget(preMovedObjectPosition, + snapTargetPosition.asVec3(), snapTargetPosition.asRotationVec3(), translation, snap); + + position.pos[0] = newPosition[0]; + position.pos[1] = newPosition[1]; + position.pos[2] = newPosition[2]; + } + else + { + position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], snap); + position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], snap); + position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], snap); + } + } + + objectTag->mObject->setPosition(position.pos); + osg::Vec3f thisPoint(position.pos[0], position.pos[1], position.pos[2]); + mDragStart = getMousePlaneCoords( + getWorldspaceWidget().mapFromGlobal(QCursor::pos()), getProjectionSpaceCoords(thisPoint)); + mObjectsAtDragStart[j] = thisPoint; } } } } -void CSVRender::InstanceMode::dragEnterEvent (QDragEnterEvent *event) +void CSVRender::InstanceMode::dragEnterEvent(QDragEnterEvent* event) { - if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) + if (const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData())) { - if (!mime->fromDocument (getWorldspaceWidget().getDocument())) + if (!mime->fromDocument(getWorldspaceWidget().getDocument())) return; - if (mime->holdsType (CSMWorld::UniversalId::Type_Referenceable)) + if (mime->holdsType(CSMWorld::UniversalId::Type_Referenceable)) event->accept(); } } -void CSVRender::InstanceMode::dropEvent (QDropEvent* event) +void CSVRender::InstanceMode::dropEvent(QDropEvent* event) { - if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) + if (const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData())) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - if (!mime->fromDocument (document)) + if (!mime->fromDocument(document)) return; - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (event->pos(), getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit + = getWorldspaceWidget().mousePick(event->pos(), getWorldspaceWidget().getInteractionMask()); - std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); + std::string cellId = getWorldspaceWidget().getCellId(hit.worldPos); - CSMWorld::IdTree& cellTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + CSMWorld::IdTree& cellTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); - bool noCell = document.getData().getCells().searchId (cellId)==-1; + const bool noCell = document.getData().getCells().searchId(ESM::RefId::stringRefId(cellId)) == -1; if (noCell) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-drop"].toString(); // target cell does not exist - if (mode=="Discard") + if (mode == "Discard") return; - if (mode=="Create cell and insert") + if (mode == "Create cell and insert") { - std::unique_ptr createCommand ( - new CSMWorld::CreateCommand (cellTable, cellId)); + std::unique_ptr createCommand(new CSMWorld::CreateCommand(cellTable, cellId)); - int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); - int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); - createCommand->addNestedValue (parentIndex, index, false); + int parentIndex = cellTable.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); + int index = cellTable.findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); + createCommand->addNestedValue(parentIndex, index, false); - document.getUndoStack().push (createCommand.release()); + document.getUndoStack().push(createCommand.release()); - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); - selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); - paged->setCellSelection (selection); + selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); + paged->setCellSelection(selection); } } } - else if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + else if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); - if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) + if (!selection.has(CSMWorld::CellCoordinates::fromId(cellId).first)) { // target cell exists, but is not shown - std::string mode = - CSMPrefs::get()["3D Scene Editing"]["outside-visible-drop"].toString(); + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-visible-drop"].toString(); - if (mode=="Discard") + if (mode == "Discard") return; - if (mode=="Show cell and insert") + if (mode == "Show cell and insert") { - selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); - paged->setCellSelection (selection); + selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); + paged->setCellSelection(selection); } } } - CSMWorld::IdTable& referencesTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& referencesTable = dynamic_cast( + *document.getData().getTableModel(CSMWorld::UniversalId::Type_References)); bool dropped = false; std::vector ids = mime->getData(); - for (std::vector::const_iterator iter (ids.begin()); - iter!=ids.end(); ++iter) - if (mime->isReferencable (iter->getType())) + for (std::vector::const_iterator iter(ids.begin()); iter != ids.end(); ++iter) + if (mime->isReferencable(iter->getType())) { // create reference - std::unique_ptr createCommand ( - new CSMWorld::CreateCommand ( - referencesTable, document.getData().getReferences().getNewId())); + std::unique_ptr createCommand( + new CSMWorld::CreateCommand(referencesTable, document.getData().getReferences().getNewId())); - createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_Cell), QString::fromUtf8 (cellId.c_str())); - createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionXPos), hit.worldPos.x()); - createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionYPos), hit.worldPos.y()); - createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionZPos), hit.worldPos.z()); - createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_ReferenceableId), - QString::fromUtf8 (iter->getId().c_str())); + createCommand->addValue(referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_Cell), + QString::fromUtf8(cellId.c_str())); + createCommand->addValue( + referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_PositionXPos), hit.worldPos.x()); + createCommand->addValue( + referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_PositionYPos), hit.worldPos.y()); + createCommand->addValue( + referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_PositionZPos), hit.worldPos.z()); + createCommand->addValue(referencesTable.findColumnIndex(CSMWorld::Columns::ColumnId_ReferenceableId), + QString::fromUtf8(iter->getId().c_str())); - document.getUndoStack().push (createCommand.release()); + document.getUndoStack().push(createCommand.release()); dropped = true; } @@ -767,39 +1051,40 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) int CSVRender::InstanceMode::getSubMode() const { - return mSubMode ? getSubModeFromId (mSubMode->getCurrentId()) : 0; + return mSubMode ? getSubModeFromId(mSubMode->getCurrentId()) : 0; } -void CSVRender::InstanceMode::subModeChanged (const std::string& id) +void CSVRender::InstanceMode::subModeChanged(const std::string& id) { mSubModeId = id; getWorldspaceWidget().abortDrag(); - getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference); + getWorldspaceWidget().setSubMode(getSubModeFromId(id), Mask_Reference); } void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos) { osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); - mSelectionMode->dragEnded (mousePlanePoint, mDragMode); + mSelectionMode->dragEnded(mousePlanePoint, mDragMode); mDragMode = DragMode_None; } void CSVRender::InstanceMode::deleteSelectedInstances(bool active) { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); - if (selection.empty()) return; + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); + if (selection.empty()) + return; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& referencesTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& referencesTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_References)); QUndoStack& undoStack = document.getUndoStack(); - CSMWorld::CommandMacro macro (undoStack, "Delete Instances"); - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + CSMWorld::CommandMacro macro(undoStack, "Delete Instances"); + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) macro.push(new CSMWorld::DeleteCommand(referencesTable, objectTag->mObject->getReferenceId())); - getWorldspaceWidget().clearSelection (Mask_Reference); + getWorldspaceWidget().clearSelection(Mask_Reference); } void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) @@ -819,8 +1104,8 @@ float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender: osg::Vec3d end = point; end.z() = std::numeric_limits::lowest(); - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( - osgUtil::Intersector::MODEL, start, end) ); + osg::ref_ptr intersector( + new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, start, end)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); @@ -864,18 +1149,18 @@ void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately() void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg) { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) return; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); - CSMWorld::CommandMacro macro (undoStack, commandMsg); + CSMWorld::CommandMacro macro(undoStack, commandMsg); DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget()); - if(dropMode & Separate) + if (dropMode & Separate) { int counter = 0; for (osg::ref_ptr tag : selection) @@ -913,10 +1198,10 @@ void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString comman CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget) : mWorldspaceWidget(worldspacewidget) { - std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); - for(osg::ref_ptr tag: selection) + std::vector> selection = mWorldspaceWidget->getSelection(Mask_Reference); + for (osg::ref_ptr tag : selection) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { osg::ref_ptr objectNodeWithGUI = objectTag->mObject->getRootNode(); osg::ref_ptr objectNodeWithoutGUI = objectTag->mObject->getBaseNode(); @@ -939,11 +1224,11 @@ CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* wo CSVRender::DropObjectHeightHandler::~DropObjectHeightHandler() { - std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); + std::vector> selection = mWorldspaceWidget->getSelection(Mask_Reference); int counter = 0; - for(osg::ref_ptr tag: selection) + for (osg::ref_ptr tag : selection) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) { osg::ref_ptr objectNodeWithGUI = objectTag->mObject->getRootNode(); objectNodeWithGUI->setNodeMask(mOldMasks[counter]); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 73b7fff12..5055d08d5 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -3,17 +3,28 @@ #include -#include +#include +#include + #include +#include #include -#include +#include +#include #include "editmode.hpp" #include "instancedragmodes.hpp" +class QDragEnterEvent; +class QDropEvent; +class QObject; +class QPoint; +class QWidget; + namespace CSVWidget { class SceneToolMode; + class SceneToolbar; } namespace CSVRender @@ -21,114 +32,124 @@ namespace CSVRender class TagBase; class InstanceSelectionMode; class Object; + class WorldspaceWidget; + struct WorldspaceHitResult; class InstanceMode : public EditMode { - Q_OBJECT + Q_OBJECT - enum DropMode - { - Separate = 0b1, + enum DropMode + { + Separate = 0b1, - Collision = 0b10, - Terrain = 0b100, + Collision = 0b10, + Terrain = 0b100, - CollisionSep = Collision | Separate, - TerrainSep = Terrain | Separate, - }; + CollisionSep = Collision | Separate, + TerrainSep = Terrain | Separate, + }; - CSVWidget::SceneToolMode *mSubMode; - std::string mSubModeId; - InstanceSelectionMode *mSelectionMode; - DragMode mDragMode; - int mDragAxis; - bool mLocked; - float mUnitScaleDist; - osg::ref_ptr mParentNode; + CSVWidget::SceneToolMode* mSubMode; + std::string mSubModeId; + InstanceSelectionMode* mSelectionMode; + DragMode mDragMode; + int mDragAxis; + bool mLocked; + float mUnitScaleDist; + osg::ref_ptr mParentNode; + osg::Vec3 mDragStart; + std::vector mObjectsAtDragStart; - int getSubModeFromId (const std::string& id) const; + int getSubModeFromId(const std::string& id) const; - osg::Vec3f quatToEuler(const osg::Quat& quat) const; - osg::Quat eulerToQuat(const osg::Vec3f& euler) const; + osg::Vec3 quatToEuler(const osg::Quat& quat) const; + osg::Quat eulerToQuat(const osg::Vec3& euler) const; - osg::Vec3f getSelectionCenter(const std::vector >& selection) const; - osg::Vec3f getScreenCoords(const osg::Vec3f& pos); - osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); - osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); - void handleSelectDrag(const QPoint& pos); - void dropInstance(CSVRender::Object* object, float dropHeight); - float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); + float roundFloatToMult(const float val, const double mult) const; - public: + osg::Vec3 getSelectionCenter(const std::vector>& selection) const; + osg::Vec3 getScreenCoords(const osg::Vec3& pos); + osg::Vec3 getProjectionSpaceCoords(const osg::Vec3& pos); + osg::Vec3 getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); + void handleSelectDrag(const QPoint& pos); + void dropInstance(CSVRender::Object* object, float dropHeight); + float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); + osg::Vec3 calculateSnapPositionRelativeToTarget(osg::Vec3 initalPosition, osg::Vec3 targetPosition, + osg::Vec3 targetRotation, osg::Vec3 translation, double snap) const; - InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent = nullptr); + public: + InstanceMode( + WorldspaceWidget* worldspaceWidget, osg::ref_ptr parentNode, QWidget* parent = nullptr); - void activate (CSVWidget::SceneToolbar *toolbar) override; + void activate(CSVWidget::SceneToolbar* toolbar) override; - void deactivate (CSVWidget::SceneToolbar *toolbar) override; + void deactivate(CSVWidget::SceneToolbar* toolbar) override; - void setEditLock (bool locked) override; + void setEditLock(bool locked) override; - void primaryOpenPressed (const WorldspaceHitResult& hit) override; + void primaryOpenPressed(const WorldspaceHitResult& hit) override; - void primaryEditPressed (const WorldspaceHitResult& hit) override; + void primaryEditPressed(const WorldspaceHitResult& hit) override; - void secondaryEditPressed (const WorldspaceHitResult& hit) override; + void secondaryEditPressed(const WorldspaceHitResult& hit) override; - void primarySelectPressed (const WorldspaceHitResult& hit) override; + void primarySelectPressed(const WorldspaceHitResult& hit) override; - void secondarySelectPressed (const WorldspaceHitResult& hit) override; + void secondarySelectPressed(const WorldspaceHitResult& hit) override; - bool primaryEditStartDrag (const QPoint& pos) override; + void tertiarySelectPressed(const WorldspaceHitResult& hit) override; - bool secondaryEditStartDrag (const QPoint& pos) override; + bool primaryEditStartDrag(const QPoint& pos) override; - bool primarySelectStartDrag(const QPoint& pos) override; + bool secondaryEditStartDrag(const QPoint& pos) override; - bool secondarySelectStartDrag(const QPoint& pos) override; + bool primarySelectStartDrag(const QPoint& pos) override; - void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; + bool secondarySelectStartDrag(const QPoint& pos) override; - void dragCompleted(const QPoint& pos) override; + void drag(const QPoint& pos, int diffX, int diffY, double speedFactor) override; - /// \note dragAborted will not be called, if the drag is aborted via changing - /// editing mode - void dragAborted() override; + void dragCompleted(const QPoint& pos) override; - void dragWheel (int diff, double speedFactor) override; + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + void dragAborted() override; - void dragEnterEvent (QDragEnterEvent *event) override; + void dragWheel(int diff, double speedFactor) override; - void dropEvent (QDropEvent *event) override; + void dragEnterEvent(QDragEnterEvent* event) override; - int getSubMode() const override; + void dropEvent(QDropEvent* event) override; - signals: + int getSubMode() const override; - void requestFocus (const std::string& id); + signals: - private slots: + void requestFocus(const std::string& id); - void subModeChanged (const std::string& id); - void deleteSelectedInstances(bool active); - void dropSelectedInstancesToCollision(); - void dropSelectedInstancesToTerrain(); - void dropSelectedInstancesToCollisionSeparately(); - void dropSelectedInstancesToTerrainSeparately(); - void handleDropMethod(DropMode dropMode, QString commandMsg); + private slots: + + void subModeChanged(const std::string& id); + void deleteSelectedInstances(bool active); + void dropSelectedInstancesToCollision(); + void dropSelectedInstancesToTerrain(); + void dropSelectedInstancesToCollisionSeparately(); + void dropSelectedInstancesToTerrainSeparately(); + void handleDropMethod(DropMode dropMode, QString commandMsg); }; /// \brief Helper class to handle object mask data in safe way class DropObjectHeightHandler { - public: - DropObjectHeightHandler(WorldspaceWidget* worldspacewidget); - ~DropObjectHeightHandler(); - std::vector mObjectHeights; + public: + DropObjectHeightHandler(WorldspaceWidget* worldspacewidget); + ~DropObjectHeightHandler(); + std::vector mObjectHeights; - private: - WorldspaceWidget* mWorldspaceWidget; - std::vector mOldMasks; + private: + WorldspaceWidget* mWorldspaceWidget; + std::vector mOldMasks; }; } diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp index 723af811d..e4004a153 100644 --- a/apps/opencs/view/render/instancemovemode.cpp +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -1,12 +1,18 @@ - #include "instancemovemode.hpp" -CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent) -: ModeButton (QIcon (QPixmap (":scenetoolbar/transform-move")), - "Move selected instances" - "
  • Use {scene-edit-primary} to move instances around freely
  • " - "
  • Use {scene-edit-secondary} to move instances around within the grid
  • " - "
" - "Grid move not implemented yet", - parent) -{} +#include +#include + +#include + +class QWidget; + +CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent) + : ModeButton(QIcon(QPixmap(":scenetoolbar/transform-move")), + "Move selected instances" + "
  • Use {scene-edit-primary} to move instances around freely
  • " + "
  • Use {scene-edit-secondary} to move instances around within the grid
  • " + "
", + parent) +{ +} diff --git a/apps/opencs/view/render/instancemovemode.hpp b/apps/opencs/view/render/instancemovemode.hpp index 62e6b6a1f..fd7ed9d3f 100644 --- a/apps/opencs/view/render/instancemovemode.hpp +++ b/apps/opencs/view/render/instancemovemode.hpp @@ -7,11 +7,10 @@ namespace CSVRender { class InstanceMoveMode : public CSVWidget::ModeButton { - Q_OBJECT + Q_OBJECT - public: - - InstanceMoveMode (QWidget *parent = nullptr); + public: + InstanceMoveMode(QWidget* parent = nullptr); }; } diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index 9b5fb759c..fa8998747 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -1,31 +1,59 @@ #include "instanceselectionmode.hpp" -#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include +#include #include -#include -#include -#include -#include - -#include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" +#include "../../model/world/idtable.hpp" #include "instancedragmodes.hpp" -#include "worldspacewidget.hpp" #include "object.hpp" +#include "worldspacewidget.hpp" + +namespace CSVWidget +{ + class SceneToolbar; +} namespace CSVRender { - InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode) - : SelectionMode(parent, worldspaceWidget, Mask_Reference), mParentNode(cellNode) + class TagBase; + + InstanceSelectionMode::InstanceSelectionMode( + CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group* cellNode) + : SelectionMode(parent, worldspaceWidget, Mask_Reference) + , mParentNode(cellNode) { mSelectSame = new QAction("Extend selection to instances with same object ID", this); mDeleteSelection = new QAction("Delete selected instances", this); - connect(mSelectSame, SIGNAL(triggered()), this, SLOT(selectSame())); - connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection())); + connect(mSelectSame, &QAction::triggered, this, &InstanceSelectionMode::selectSame); + connect(mDeleteSelection, &QAction::triggered, this, &InstanceSelectionMode::deleteSelection); } InstanceSelectionMode::~InstanceSelectionMode() @@ -46,7 +74,8 @@ namespace CSVRender void InstanceSelectionMode::dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode) { float dragDistance = (mDragStart - dragEndPoint).length(); - if (mBaseNode) mParentNode->removeChild (mBaseNode); + if (mBaseNode) + mParentNode->removeChild(mBaseNode); if (getCurrentId() == "cube-centre") { osg::Vec3d pointA(mDragStart[0] - dragDistance, mDragStart[1] - dragDistance, mDragStart[2] - dragDistance); @@ -76,196 +105,198 @@ namespace CSVRender void InstanceSelectionMode::drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB) { - if (mBaseNode) mParentNode->removeChild (mBaseNode); + if (mBaseNode) + mParentNode->removeChild(mBaseNode); mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->setPosition(pointA); - osg::ref_ptr geometry (new osg::Geometry); + osg::ref_ptr geometry(new osg::Geometry); - osg::Vec3Array *vertices = new osg::Vec3Array; - vertices->push_back (osg::Vec3f (0.0f, 0.0f, 0.0f)); - vertices->push_back (osg::Vec3f (0.0f, 0.0f, pointB[2] - pointA[2])); - vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], 0.0f)); - vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], pointB[2] - pointA[2])); + osg::Vec3Array* vertices = new osg::Vec3Array; + vertices->push_back(osg::Vec3f(0.0f, 0.0f, 0.0f)); + vertices->push_back(osg::Vec3f(0.0f, 0.0f, pointB[2] - pointA[2])); + vertices->push_back(osg::Vec3f(0.0f, pointB[1] - pointA[1], 0.0f)); + vertices->push_back(osg::Vec3f(0.0f, pointB[1] - pointA[1], pointB[2] - pointA[2])); - vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, 0.0f)); - vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, pointB[2] - pointA[2])); - vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], 0.0f)); - vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2])); + vertices->push_back(osg::Vec3f(pointB[0] - pointA[0], 0.0f, 0.0f)); + vertices->push_back(osg::Vec3f(pointB[0] - pointA[0], 0.0f, pointB[2] - pointA[2])); + vertices->push_back(osg::Vec3f(pointB[0] - pointA[0], pointB[1] - pointA[1], 0.0f)); + vertices->push_back(osg::Vec3f(pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2])); - geometry->setVertexArray (vertices); + geometry->setVertexArray(vertices); - osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0); // top - primitives->push_back (2); - primitives->push_back (1); - primitives->push_back (0); + primitives->push_back(2); + primitives->push_back(1); + primitives->push_back(0); - primitives->push_back (3); - primitives->push_back (1); - primitives->push_back (2); + primitives->push_back(3); + primitives->push_back(1); + primitives->push_back(2); // bottom - primitives->push_back (4); - primitives->push_back (5); - primitives->push_back (6); + primitives->push_back(4); + primitives->push_back(5); + primitives->push_back(6); - primitives->push_back (6); - primitives->push_back (5); - primitives->push_back (7); + primitives->push_back(6); + primitives->push_back(5); + primitives->push_back(7); // sides - primitives->push_back (1); - primitives->push_back (4); - primitives->push_back (0); + primitives->push_back(1); + primitives->push_back(4); + primitives->push_back(0); - primitives->push_back (4); - primitives->push_back (1); - primitives->push_back (5); + primitives->push_back(4); + primitives->push_back(1); + primitives->push_back(5); - primitives->push_back (4); - primitives->push_back (2); - primitives->push_back (0); + primitives->push_back(4); + primitives->push_back(2); + primitives->push_back(0); - primitives->push_back (6); - primitives->push_back (2); - primitives->push_back (4); + primitives->push_back(6); + primitives->push_back(2); + primitives->push_back(4); - primitives->push_back (6); - primitives->push_back (3); - primitives->push_back (2); + primitives->push_back(6); + primitives->push_back(3); + primitives->push_back(2); - primitives->push_back (7); - primitives->push_back (3); - primitives->push_back (6); + primitives->push_back(7); + primitives->push_back(3); + primitives->push_back(6); - primitives->push_back (1); - primitives->push_back (3); - primitives->push_back (5); + primitives->push_back(1); + primitives->push_back(3); + primitives->push_back(5); - primitives->push_back (5); - primitives->push_back (3); - primitives->push_back (7); + primitives->push_back(5); + primitives->push_back(3); + primitives->push_back(7); - geometry->addPrimitiveSet (primitives); + geometry->addPrimitiveSet(primitives); - osg::Vec4Array *colours = new osg::Vec4Array; + osg::Vec4Array* colours = new osg::Vec4Array; - colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); - colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); - colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); - colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); - colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); - colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); - colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); - colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back(osg::Vec4f(0.3f, 0.3f, 0.5f, 0.2f)); + colours->push_back(osg::Vec4f(0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back(osg::Vec4f(0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back(osg::Vec4f(0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back(osg::Vec4f(0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back(osg::Vec4f(0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back(osg::Vec4f(0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back(osg::Vec4f(0.9f, 0.9f, 1.0f, 0.2f)); - geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); - geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - mBaseNode->addChild (geometry); + mBaseNode->addChild(geometry); mParentNode->addChild(mBaseNode); } void InstanceSelectionMode::drawSelectionCube(const osg::Vec3d& point, float radius) { - if (mBaseNode) mParentNode->removeChild (mBaseNode); + if (mBaseNode) + mParentNode->removeChild(mBaseNode); mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->setPosition(point); - osg::ref_ptr geometry (new osg::Geometry); + osg::ref_ptr geometry(new osg::Geometry); - osg::Vec3Array *vertices = new osg::Vec3Array; + osg::Vec3Array* vertices = new osg::Vec3Array; for (int i = 0; i < 2; ++i) { float height = i ? -radius : radius; - vertices->push_back (osg::Vec3f (height, -radius, -radius)); - vertices->push_back (osg::Vec3f (height, -radius, radius)); - vertices->push_back (osg::Vec3f (height, radius, -radius)); - vertices->push_back (osg::Vec3f (height, radius, radius)); + vertices->push_back(osg::Vec3f(height, -radius, -radius)); + vertices->push_back(osg::Vec3f(height, -radius, radius)); + vertices->push_back(osg::Vec3f(height, radius, -radius)); + vertices->push_back(osg::Vec3f(height, radius, radius)); } - geometry->setVertexArray (vertices); + geometry->setVertexArray(vertices); - osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0); // top - primitives->push_back (2); - primitives->push_back (1); - primitives->push_back (0); + primitives->push_back(2); + primitives->push_back(1); + primitives->push_back(0); - primitives->push_back (3); - primitives->push_back (1); - primitives->push_back (2); + primitives->push_back(3); + primitives->push_back(1); + primitives->push_back(2); // bottom - primitives->push_back (4); - primitives->push_back (5); - primitives->push_back (6); + primitives->push_back(4); + primitives->push_back(5); + primitives->push_back(6); - primitives->push_back (6); - primitives->push_back (5); - primitives->push_back (7); + primitives->push_back(6); + primitives->push_back(5); + primitives->push_back(7); // sides - primitives->push_back (1); - primitives->push_back (4); - primitives->push_back (0); + primitives->push_back(1); + primitives->push_back(4); + primitives->push_back(0); - primitives->push_back (4); - primitives->push_back (1); - primitives->push_back (5); + primitives->push_back(4); + primitives->push_back(1); + primitives->push_back(5); - primitives->push_back (4); - primitives->push_back (2); - primitives->push_back (0); + primitives->push_back(4); + primitives->push_back(2); + primitives->push_back(0); - primitives->push_back (6); - primitives->push_back (2); - primitives->push_back (4); + primitives->push_back(6); + primitives->push_back(2); + primitives->push_back(4); - primitives->push_back (6); - primitives->push_back (3); - primitives->push_back (2); + primitives->push_back(6); + primitives->push_back(3); + primitives->push_back(2); - primitives->push_back (7); - primitives->push_back (3); - primitives->push_back (6); + primitives->push_back(7); + primitives->push_back(3); + primitives->push_back(6); - primitives->push_back (1); - primitives->push_back (3); - primitives->push_back (5); + primitives->push_back(1); + primitives->push_back(3); + primitives->push_back(5); - primitives->push_back (5); - primitives->push_back (3); - primitives->push_back (7); + primitives->push_back(5); + primitives->push_back(3); + primitives->push_back(7); - geometry->addPrimitiveSet (primitives); + geometry->addPrimitiveSet(primitives); - osg::Vec4Array *colours = new osg::Vec4Array; + osg::Vec4Array* colours = new osg::Vec4Array; - colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); - colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); - colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); - colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); - colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); - colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); - colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); - colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back(osg::Vec4f(0.3f, 0.3f, 0.5f, 0.2f)); + colours->push_back(osg::Vec4f(0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back(osg::Vec4f(0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back(osg::Vec4f(0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back(osg::Vec4f(0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back(osg::Vec4f(0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back(osg::Vec4f(0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back(osg::Vec4f(0.9f, 0.9f, 1.0f, 0.2f)); - geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); - geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - mBaseNode->addChild (geometry); + mBaseNode->addChild(geometry); mParentNode->addChild(mBaseNode); } @@ -277,32 +308,33 @@ namespace CSVRender void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3d& point, float radius) { - if (mBaseNode) mParentNode->removeChild (mBaseNode); + if (mBaseNode) + mParentNode->removeChild(mBaseNode); mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->setPosition(point); - osg::ref_ptr geometry (new osg::Geometry); + osg::ref_ptr geometry(new osg::Geometry); - osg::Vec3Array *vertices = new osg::Vec3Array; - int resolution = 32; + osg::Vec3Array* vertices = new osg::Vec3Array; + constexpr int resolution = 32; float radiusPerResolution = radius / resolution; float reciprocalResolution = 1.0f / resolution; float doubleReciprocalRes = reciprocalResolution * 2; - osg::Vec4Array *colours = new osg::Vec4Array; + osg::Vec4Array* colours = new osg::Vec4Array; - for (float i = 0.0; i <= resolution; i += 2) + for (int i = 0; i <= resolution; i += 2) { float iShifted = (static_cast(i) - resolution / 2.0f); // i - 16 = -16 ... 16 float xPercentile = iShifted * doubleReciprocalRes; float x = xPercentile * radius; - float thisRadius = sqrt (radius * radius - x * x); + float thisRadius = sqrt(radius * radius - x * x); - //the next row + // the next row float iShifted2 = (static_cast(i + 1) - resolution / 2.0f); float xPercentile2 = iShifted2 * doubleReciprocalRes; float x2 = xPercentile2 * radius; - float thisRadius2 = sqrt (radius * radius - x2 * x2); + float thisRadius2 = sqrt(radius * radius - x2 * x2); for (int j = 0; j < resolution; ++j) { @@ -310,57 +342,62 @@ namespace CSVRender float vertexY = i * radiusPerResolution * 2 - radius; float vertexZ = thisRadius * cos(j * reciprocalResolution * osg::PI * 2); float heightPercentage = (vertexZ + radius) / (radius * 2); - vertices->push_back (osg::Vec3f (vertexX, vertexY, vertexZ)); - colours->push_back (osg::Vec4f (heightPercentage, heightPercentage, heightPercentage, 0.3f)); + vertices->push_back(osg::Vec3f(vertexX, vertexY, vertexZ)); + colours->push_back(osg::Vec4f(heightPercentage, heightPercentage, heightPercentage, 0.3f)); float vertexNextRowX = thisRadius2 * sin(j * reciprocalResolution * osg::PI * 2); float vertexNextRowY = (i + 1) * radiusPerResolution * 2 - radius; float vertexNextRowZ = thisRadius2 * cos(j * reciprocalResolution * osg::PI * 2); float heightPercentageNextRow = (vertexZ + radius) / (radius * 2); - vertices->push_back (osg::Vec3f (vertexNextRowX, vertexNextRowY, vertexNextRowZ)); - colours->push_back (osg::Vec4f (heightPercentageNextRow, heightPercentageNextRow, heightPercentageNextRow, 0.3f)); + vertices->push_back(osg::Vec3f(vertexNextRowX, vertexNextRowY, vertexNextRowZ)); + colours->push_back( + osg::Vec4f(heightPercentageNextRow, heightPercentageNextRow, heightPercentageNextRow, 0.3f)); } } - geometry->setVertexArray (vertices); + geometry->setVertexArray(vertices); - osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLE_STRIP, 0); + osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, 0); for (int i = 0; i < resolution; ++i) { - //Even + // Even for (int j = 0; j < resolution * 2; ++j) { - if (i * resolution * 2 + j > static_cast(vertices->size()) - 1) continue; - primitives->push_back (i * resolution * 2 + j); + if (i * resolution * 2 + j > static_cast(vertices->size()) - 1) + continue; + primitives->push_back(i * resolution * 2 + j); } - if (i * resolution * 2 > static_cast(vertices->size()) - 1) continue; - primitives->push_back (i * resolution * 2); - primitives->push_back (i * resolution * 2 + 1); + if (i * resolution * 2 > static_cast(vertices->size()) - 1) + continue; + primitives->push_back(i * resolution * 2); + primitives->push_back(i * resolution * 2 + 1); - //Odd + // Odd for (int j = 1; j < resolution * 2 - 2; j += 2) { - if ((i + 1) * resolution * 2 + j - 1 > static_cast(vertices->size()) - 1) continue; - primitives->push_back ((i + 1) * resolution * 2 + j - 1); - primitives->push_back (i * resolution * 2 + j + 2); + if ((i + 1) * resolution * 2 + j - 1 > static_cast(vertices->size()) - 1) + continue; + primitives->push_back((i + 1) * resolution * 2 + j - 1); + primitives->push_back(i * resolution * 2 + j + 2); } - if ((i + 2) * resolution * 2 - 2 > static_cast(vertices->size()) - 1) continue; - primitives->push_back ((i + 2) * resolution * 2 - 2); - primitives->push_back (i * resolution * 2 + 1); - primitives->push_back ((i + 1) * resolution * 2); + if ((i + 2) * resolution * 2 - 2 > static_cast(vertices->size()) - 1) + continue; + primitives->push_back((i + 2) * resolution * 2 - 2); + primitives->push_back(i * resolution * 2 + 1); + primitives->push_back((i + 1) * resolution * 2); } - geometry->addPrimitiveSet (primitives); + geometry->addPrimitiveSet(primitives); - geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); - geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); - geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - mBaseNode->addChild (geometry); + mBaseNode->addChild(geometry); mParentNode->addChild(mBaseNode); } @@ -384,15 +421,15 @@ namespace CSVRender void InstanceSelectionMode::deleteSelection() { - std::vector > selection = getWorldspaceWidget().getSelection(Mask_Reference); + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); CSMWorld::IdTable& referencesTable = dynamic_cast( *getWorldspaceWidget().getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_References)); - for (std::vector >::iterator iter = selection.begin(); iter != selection.end(); ++iter) + for (std::vector>::iterator iter = selection.begin(); iter != selection.end(); ++iter) { - CSMWorld::DeleteCommand* command = new CSMWorld::DeleteCommand(referencesTable, - static_cast(iter->get())->mObject->getReferenceId()); + CSMWorld::DeleteCommand* command = new CSMWorld::DeleteCommand( + referencesTable, static_cast(iter->get())->mObject->getReferenceId()); getWorldspaceWidget().getDocument().getUndoStack().push(command); } diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index 81795d5d3..9551f964d 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -1,67 +1,82 @@ #ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H #define CSV_RENDER_INSTANCE_SELECTION_MODE_H -#include - -#include #include +#include + +class QAction; +class QMenu; +class QObject; +class QPoint; + +namespace CSVWidget +{ + class SceneToolbar; +} + +namespace osg +{ + class PositionAttitudeTransform; + class Group; + class Vec3f; +} -#include "selectionmode.hpp" #include "instancedragmodes.hpp" +#include "selectionmode.hpp" namespace CSVRender { + class WorldspaceWidget; class InstanceSelectionMode : public SelectionMode { - Q_OBJECT + Q_OBJECT - public: + public: + InstanceSelectionMode( + CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group* cellNode); - InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode); + ~InstanceSelectionMode(); - ~InstanceSelectionMode(); + /// Store the worldspace-coordinate when drag begins + void setDragStart(const osg::Vec3d& dragStart); - /// Store the worldspace-coordinate when drag begins - void setDragStart(const osg::Vec3d& dragStart); + /// Store the worldspace-coordinate when drag begins + const osg::Vec3d& getDragStart(); - /// Store the worldspace-coordinate when drag begins - const osg::Vec3d& getDragStart(); + /// Store the screen-coordinate when drag begins + void setScreenDragStart(const QPoint& dragStartPoint); - /// Store the screen-coordinate when drag begins - void setScreenDragStart(const QPoint& dragStartPoint); + /// Apply instance selection changes + void dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode); - /// Apply instance selection changes - void dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode); + void drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint); + void drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint); + void drawSelectionSphere(const osg::Vec3f& mousePlanePoint); - void drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint ); - void drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint ); - void drawSelectionSphere(const osg::Vec3f& mousePlanePoint ); - protected: + protected: + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + bool createContextMenu(QMenu* menu) override; - /// Add context menu items to \a menu. - /// - /// \attention menu can be a 0-pointer - /// - /// \return Have there been any menu items to be added (if menu is 0 and there - /// items to be added, the function must return true anyway. - bool createContextMenu(QMenu* menu) override; + private: + void drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB); + void drawSelectionCube(const osg::Vec3d& point, float radius); + void drawSelectionSphere(const osg::Vec3d& point, float radius); - private: + QAction* mDeleteSelection; + QAction* mSelectSame; + osg::Vec3d mDragStart; + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; - void drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB); - void drawSelectionCube(const osg::Vec3d& point, float radius); - void drawSelectionSphere(const osg::Vec3d& point, float radius); + private slots: - QAction* mDeleteSelection; - QAction* mSelectSame; - osg::Vec3d mDragStart; - osg::Group* mParentNode; - osg::ref_ptr mBaseNode; - - private slots: - - void deleteSelection(); - void selectSame(); + void deleteSelection(); + void selectSame(); }; } diff --git a/apps/opencs/view/render/lighting.cpp b/apps/opencs/view/render/lighting.cpp index 82ad43e6a..94b3f02ea 100644 --- a/apps/opencs/view/render/lighting.cpp +++ b/apps/opencs/view/render/lighting.cpp @@ -1,23 +1,59 @@ #include "lighting.hpp" +#include + +#include #include #include +#include #include +#include + +#include +#include #include +#include "../../model/prefs/state.hpp" + class DayNightSwitchVisitor : public osg::NodeVisitor { public: DayNightSwitchVisitor(int index) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mIndex(index) - { } - - void apply(osg::Switch &switchNode) override { - if (switchNode.getName() == Constants::NightDayLabel) - switchNode.setSingleChildOn(mIndex); + } + + void apply(osg::Switch& switchNode) override + { + constexpr int NoIndex = -1; + + int initialIndex = NoIndex; + if (!switchNode.getUserValue("initialIndex", initialIndex)) + { + for (size_t i = 0; i < switchNode.getValueList().size(); ++i) + { + if (switchNode.getValueList()[i]) + { + initialIndex = i; + break; + } + } + + if (initialIndex != NoIndex) + switchNode.setUserValue("initialIndex", initialIndex); + } + + if (CSMPrefs::get()["Rendering"]["scene-day-night-switch-nodes"].isTrue()) + { + if (switchNode.getName() == Constants::NightDayLabel) + switchNode.setSingleChildOn(mIndex); + } + else if (initialIndex != NoIndex) + { + switchNode.setSingleChildOn(initialIndex); + } traverse(switchNode); } @@ -26,8 +62,6 @@ private: int mIndex; }; -CSVRender::Lighting::~Lighting() {} - void CSVRender::Lighting::updateDayNightMode(int index) { if (mRootNode == nullptr) diff --git a/apps/opencs/view/render/lighting.hpp b/apps/opencs/view/render/lighting.hpp index d9d90767f..88d9c710c 100644 --- a/apps/opencs/view/render/lighting.hpp +++ b/apps/opencs/view/render/lighting.hpp @@ -14,23 +14,24 @@ namespace CSVRender { class Lighting { - public: + public: + Lighting() + : mRootNode(nullptr) + { + } + virtual ~Lighting() = default; - Lighting() : mRootNode(nullptr) {} - virtual ~Lighting(); + virtual void activate(osg::Group* rootNode, bool isExterior) = 0; - virtual void activate (osg::Group* rootNode, bool isExterior) = 0; + virtual void deactivate() = 0; - virtual void deactivate() = 0; + virtual osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) = 0; - virtual osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) = 0; + protected: + void updateDayNightMode(int index); - protected: - - void updateDayNightMode(int index); - - osg::ref_ptr mLightSource; - osg::Group* mRootNode; + osg::ref_ptr mLightSource; + osg::Group* mRootNode; }; } diff --git a/apps/opencs/view/render/lightingbright.cpp b/apps/opencs/view/render/lightingbright.cpp index d76823fb3..7acd24fc6 100644 --- a/apps/opencs/view/render/lightingbright.cpp +++ b/apps/opencs/view/render/lightingbright.cpp @@ -1,16 +1,19 @@ #include "lightingbright.hpp" +#include +#include #include +#include CSVRender::LightingBright::LightingBright() {} -void CSVRender::LightingBright::activate (osg::Group* rootNode, bool /*isExterior*/) +void CSVRender::LightingBright::activate(osg::Group* rootNode, bool /*isExterior*/) { mRootNode = rootNode; mLightSource = (new osg::LightSource); - osg::ref_ptr light (new osg::Light); + osg::ref_ptr light(new osg::Light); light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); light->setDiffuse(osg::Vec4f(1.f, 1.f, 1.f, 1.f)); diff --git a/apps/opencs/view/render/lightingbright.hpp b/apps/opencs/view/render/lightingbright.hpp index aa1492752..8ee570e2a 100644 --- a/apps/opencs/view/render/lightingbright.hpp +++ b/apps/opencs/view/render/lightingbright.hpp @@ -3,9 +3,10 @@ #include "lighting.hpp" +#include + namespace osg { - class Light; class Group; } @@ -13,15 +14,14 @@ namespace CSVRender { class LightingBright : public Lighting { - public: + public: + LightingBright(); - LightingBright(); + void activate(osg::Group* rootNode, bool /*isExterior*/) override; - void activate (osg::Group* rootNode, bool /*isExterior*/) override; + void deactivate() override; - void deactivate() override; - - osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) override; + osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) override; }; } diff --git a/apps/opencs/view/render/lightingday.cpp b/apps/opencs/view/render/lightingday.cpp index dc4592b21..50aef93f1 100644 --- a/apps/opencs/view/render/lightingday.cpp +++ b/apps/opencs/view/render/lightingday.cpp @@ -1,16 +1,17 @@ #include "lightingday.hpp" +#include +#include #include +#include -CSVRender::LightingDay::LightingDay(){} - -void CSVRender::LightingDay::activate (osg::Group* rootNode, bool /*isExterior*/) +void CSVRender::LightingDay::activate(osg::Group* rootNode, bool /*isExterior*/) { mRootNode = rootNode; mLightSource = new osg::LightSource; - osg::ref_ptr light (new osg::Light); + osg::ref_ptr light(new osg::Light); light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); light->setDiffuse(osg::Vec4f(1.f, 1.f, 1.f, 1.f)); @@ -29,7 +30,7 @@ void CSVRender::LightingDay::deactivate() mRootNode->removeChild(mLightSource); } -osg::Vec4f CSVRender::LightingDay::getAmbientColour(osg::Vec4f *defaultAmbient) +osg::Vec4f CSVRender::LightingDay::getAmbientColour(osg::Vec4f* defaultAmbient) { if (defaultAmbient) return *defaultAmbient; diff --git a/apps/opencs/view/render/lightingday.hpp b/apps/opencs/view/render/lightingday.hpp index eafc6b8e8..e68d496b2 100644 --- a/apps/opencs/view/render/lightingday.hpp +++ b/apps/opencs/view/render/lightingday.hpp @@ -3,19 +3,25 @@ #include "lighting.hpp" +#include + +namespace osg +{ + class Group; +} + namespace CSVRender { class LightingDay : public Lighting { - public: + public: + LightingDay() = default; - LightingDay(); + void activate(osg::Group* rootNode, bool /*isExterior*/) override; - void activate (osg::Group* rootNode, bool /*isExterior*/) override; + void deactivate() override; - void deactivate() override; - - osg::Vec4f getAmbientColour(osg::Vec4f *defaultAmbient) override; + osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) override; }; } diff --git a/apps/opencs/view/render/lightingnight.cpp b/apps/opencs/view/render/lightingnight.cpp index fbebb46a1..6628a4983 100644 --- a/apps/opencs/view/render/lightingnight.cpp +++ b/apps/opencs/view/render/lightingnight.cpp @@ -1,16 +1,17 @@ #include "lightingnight.hpp" +#include +#include #include +#include -CSVRender::LightingNight::LightingNight() {} - -void CSVRender::LightingNight::activate (osg::Group* rootNode, bool isExterior) +void CSVRender::LightingNight::activate(osg::Group* rootNode, bool isExterior) { mRootNode = rootNode; mLightSource = new osg::LightSource; - osg::ref_ptr light (new osg::Light); + osg::ref_ptr light(new osg::Light); light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); light->setDiffuse(osg::Vec4f(0.2f, 0.2f, 0.2f, 1.f)); @@ -30,7 +31,7 @@ void CSVRender::LightingNight::deactivate() mRootNode->removeChild(mLightSource); } -osg::Vec4f CSVRender::LightingNight::getAmbientColour(osg::Vec4f *defaultAmbient) +osg::Vec4f CSVRender::LightingNight::getAmbientColour(osg::Vec4f* defaultAmbient) { if (defaultAmbient) return *defaultAmbient; diff --git a/apps/opencs/view/render/lightingnight.hpp b/apps/opencs/view/render/lightingnight.hpp index bfa94ce97..1a813bd54 100644 --- a/apps/opencs/view/render/lightingnight.hpp +++ b/apps/opencs/view/render/lightingnight.hpp @@ -3,18 +3,24 @@ #include "lighting.hpp" +#include + +namespace osg +{ + class Group; +} + namespace CSVRender { class LightingNight : public Lighting { - public: + public: + LightingNight() = default; - LightingNight(); + void activate(osg::Group* rootNode, bool isExterior) override; + void deactivate() override; - void activate (osg::Group* rootNode, bool isExterior) override; - void deactivate() override; - - osg::Vec4f getAmbientColour(osg::Vec4f *defaultAmbient) override; + osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) override; }; } diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 2bb537d74..7782ce36c 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -1,90 +1,118 @@ #include "object.hpp" +#include +#include +#include +#include #include #include -#include +#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include -#include "../../model/world/data.hpp" -#include "../../model/world/ref.hpp" -#include "../../model/world/refidcollection.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/universalid.hpp" -#include "../../model/world/commandmacro.hpp" -#include "../../model/world/cellcoordinates.hpp" #include "../../model/prefs/state.hpp" +#include "../../model/world/cellcoordinates.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" #include +#include +#include #include -#include +#include #include +#include #include "actor.hpp" #include "mask.hpp" +namespace CSVRender +{ + struct WorldspaceHitResult; +} + +namespace ESM +{ + struct Light; +} const float CSVRender::Object::MarkerShaftWidth = 30; const float CSVRender::Object::MarkerShaftBaseLength = 70; const float CSVRender::Object::MarkerHeadWidth = 50; const float CSVRender::Object::MarkerHeadLength = 50; - namespace { - osg::ref_ptr createErrorCube() + osg::ref_ptr createErrorCube() { - osg::ref_ptr shape(new osg::Box(osg::Vec3f(0,0,0), 50.f)); + osg::ref_ptr shape(new osg::Box(osg::Vec3f(0, 0, 0), 50.f)); osg::ref_ptr shapedrawable(new osg::ShapeDrawable); shapedrawable->setShape(shape); - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(shapedrawable); - return geode; + osg::ref_ptr group(new osg::Group); + group->addChild(shapedrawable); + return group; } } - -CSVRender::ObjectTag::ObjectTag (Object* object) -: TagBase (Mask_Reference), mObject (object) -{} - -QString CSVRender::ObjectTag::getToolTip (bool hideBasics) const -{ - return QString::fromUtf8 (mObject->getReferenceableId().c_str()); -} - - -CSVRender::ObjectMarkerTag::ObjectMarkerTag (Object* object, int axis) -: ObjectTag (object), mAxis (axis) -{} - - -void CSVRender::Object::clear() +CSVRender::ObjectTag::ObjectTag(Object* object) + : TagBase(Mask_Reference) + , mObject(object) { } +QString CSVRender::ObjectTag::getToolTip(bool /*hideBasics*/, const WorldspaceHitResult& /*hit*/) const +{ + return QString::fromUtf8(mObject->getReferenceableId().c_str()); +} + +CSVRender::ObjectMarkerTag::ObjectMarkerTag(Object* object, int axis) + : ObjectTag(object) + , mAxis(axis) +{ +} + +void CSVRender::Object::clear() {} + void CSVRender::Object::update() { clear(); const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); const int TypeIndex = referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - const int ModelIndex = referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model); + const int ModelIndex = referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_Model); - int index = referenceables.searchId (mReferenceableId); + int index = referenceables.searchId(mReferenceableId); const ESM::Light* light = nullptr; mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); @@ -102,7 +130,7 @@ void CSVRender::Object::update() if (recordType == CSMWorld::UniversalId::Type_Light) { - light = &dynamic_cast& >(referenceables.getRecord(index)).get(); + light = &dynamic_cast&>(referenceables.getRecord(index)).get(); if (model.empty()) model = "marker_light.nif"; } @@ -117,7 +145,8 @@ void CSVRender::Object::update() { if (recordType == CSMWorld::UniversalId::Type_Npc || recordType == CSMWorld::UniversalId::Type_Creature) { - if (!mActor) mActor.reset(new Actor(mReferenceableId, mData)); + if (!mActor) + mActor = std::make_unique(mReferenceableId, mData); mActor->update(); mBaseNode->addChild(mActor->getBaseNode()); } @@ -128,7 +157,7 @@ void CSVRender::Object::update() } else { - throw std::runtime_error(mReferenceableId + " has no model"); + throw std::runtime_error(mReferenceableId.getRefIdString() + " has no model"); } } catch (std::exception& e) @@ -140,7 +169,7 @@ void CSVRender::Object::update() if (light) { bool isExterior = false; // FIXME - SceneUtil::addLight(mBaseNode, light, Mask_ParticleSystem, Mask_Lighting, isExterior); + SceneUtil::addLight(mBaseNode, SceneUtil::LightCommon(*light), Mask_Lighting, isExterior); } } @@ -152,13 +181,14 @@ void CSVRender::Object::adjustTransform() ESM::Position position = getPosition(); // position - mRootNode->setPosition(mForceBaseToZero ? osg::Vec3() : osg::Vec3f(position.pos[0], position.pos[1], position.pos[2])); + mRootNode->setPosition( + mForceBaseToZero ? osg::Vec3() : osg::Vec3f(position.pos[0], position.pos[1], position.pos[2])); // orientation - osg::Quat xr (-position.rot[0], osg::Vec3f(1,0,0)); - osg::Quat yr (-position.rot[1], osg::Vec3f(0,1,0)); - osg::Quat zr (-position.rot[2], osg::Vec3f(0,0,1)); - mBaseNode->setAttitude(zr*yr*xr); + osg::Quat xr(-position.rot[0], osg::Vec3f(1, 0, 0)); + osg::Quat yr(-position.rot[1], osg::Vec3f(0, 1, 0)); + osg::Quat zr(-position.rot[2], osg::Vec3f(0, 0, 1)); + mBaseNode->setAttitude(zr * yr * xr); float scale = getScale(); @@ -168,147 +198,147 @@ void CSVRender::Object::adjustTransform() const CSMWorld::CellRef& CSVRender::Object::getReference() const { if (mReferenceId.empty()) - throw std::logic_error ("object does not represent a reference"); + throw std::logic_error("object does not represent a reference"); - return mData.getReferences().getRecord (mReferenceId).get(); + return mData.getReferences().getRecord(mReferenceId).get(); } void CSVRender::Object::updateMarker() { - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) { if (mMarker[i]) { - mRootNode->removeChild (mMarker[i]); + mRootNode->removeChild(mMarker[i]); mMarker[i] = osg::ref_ptr(); } if (mSelected) { - if (mSubMode==0) + if (mSubMode == 0) { - mMarker[i] = makeMoveOrScaleMarker (i); - mMarker[i]->setUserData(new ObjectMarkerTag (this, i)); + mMarker[i] = makeMoveOrScaleMarker(i); + mMarker[i]->setUserData(new ObjectMarkerTag(this, i)); - mRootNode->addChild (mMarker[i]); + mRootNode->addChild(mMarker[i]); } - else if (mSubMode==1) + else if (mSubMode == 1) { - mMarker[i] = makeRotateMarker (i); - mMarker[i]->setUserData(new ObjectMarkerTag (this, i)); + mMarker[i] = makeRotateMarker(i); + mMarker[i]->setUserData(new ObjectMarkerTag(this, i)); - mRootNode->addChild (mMarker[i]); + mRootNode->addChild(mMarker[i]); } - else if (mSubMode==2) + else if (mSubMode == 2) { - mMarker[i] = makeMoveOrScaleMarker (i); - mMarker[i]->setUserData(new ObjectMarkerTag (this, i)); + mMarker[i] = makeMoveOrScaleMarker(i); + mMarker[i]->setUserData(new ObjectMarkerTag(this, i)); - mRootNode->addChild (mMarker[i]); + mRootNode->addChild(mMarker[i]); } } } } -osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) +osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker(int axis) { - osg::ref_ptr geometry (new osg::Geometry); + osg::ref_ptr geometry(new osg::Geometry); float shaftLength = MarkerShaftBaseLength + mBaseNode->getBound().radius(); // shaft - osg::Vec3Array *vertices = new osg::Vec3Array; + osg::Vec3Array* vertices = new osg::Vec3Array; - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { 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)); - vertices->push_back (getMarkerPosition (MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis)); - vertices->push_back (getMarkerPosition (MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis)); + vertices->push_back(getMarkerPosition(-MarkerShaftWidth / 2, -MarkerShaftWidth / 2, length, axis)); + vertices->push_back(getMarkerPosition(-MarkerShaftWidth / 2, MarkerShaftWidth / 2, length, axis)); + vertices->push_back(getMarkerPosition(MarkerShaftWidth / 2, MarkerShaftWidth / 2, length, axis)); + vertices->push_back(getMarkerPosition(MarkerShaftWidth / 2, -MarkerShaftWidth / 2, length, axis)); } // head backside - vertices->push_back (getMarkerPosition (-MarkerHeadWidth/2, -MarkerHeadWidth/2, shaftLength, axis)); - vertices->push_back (getMarkerPosition (-MarkerHeadWidth/2, MarkerHeadWidth/2, shaftLength, axis)); - vertices->push_back (getMarkerPosition (MarkerHeadWidth/2, MarkerHeadWidth/2, shaftLength, axis)); - vertices->push_back (getMarkerPosition (MarkerHeadWidth/2, -MarkerHeadWidth/2, shaftLength, axis)); + vertices->push_back(getMarkerPosition(-MarkerHeadWidth / 2, -MarkerHeadWidth / 2, shaftLength, axis)); + vertices->push_back(getMarkerPosition(-MarkerHeadWidth / 2, MarkerHeadWidth / 2, shaftLength, axis)); + vertices->push_back(getMarkerPosition(MarkerHeadWidth / 2, MarkerHeadWidth / 2, shaftLength, axis)); + vertices->push_back(getMarkerPosition(MarkerHeadWidth / 2, -MarkerHeadWidth / 2, shaftLength, axis)); // head - vertices->push_back (getMarkerPosition (0, 0, shaftLength+MarkerHeadLength, axis)); + vertices->push_back(getMarkerPosition(0, 0, shaftLength + MarkerHeadLength, axis)); - geometry->setVertexArray (vertices); + geometry->setVertexArray(vertices); - osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0); // shaft - for (int i=0; i<4; ++i) + for (int i = 0; i < 4; ++i) { - int i2 = i==3 ? 0 : i+1; - primitives->push_back (i); - primitives->push_back (4+i); - primitives->push_back (i2); + int i2 = i == 3 ? 0 : i + 1; + primitives->push_back(i); + primitives->push_back(4 + i); + primitives->push_back(i2); - primitives->push_back (4+i); - primitives->push_back (4+i2); - primitives->push_back (i2); + primitives->push_back(4 + i); + primitives->push_back(4 + i2); + primitives->push_back(i2); } // cap - primitives->push_back (0); - primitives->push_back (1); - primitives->push_back (2); + primitives->push_back(0); + primitives->push_back(1); + primitives->push_back(2); - primitives->push_back (2); - primitives->push_back (3); - primitives->push_back (0); + primitives->push_back(2); + primitives->push_back(3); + primitives->push_back(0); // head, backside - primitives->push_back (0+8); - primitives->push_back (1+8); - primitives->push_back (2+8); + primitives->push_back(0 + 8); + primitives->push_back(1 + 8); + primitives->push_back(2 + 8); - primitives->push_back (2+8); - primitives->push_back (3+8); - primitives->push_back (0+8); + primitives->push_back(2 + 8); + primitives->push_back(3 + 8); + primitives->push_back(0 + 8); - for (int i=0; i<4; ++i) + for (int i = 0; i < 4; ++i) { - primitives->push_back (12); - primitives->push_back (8+(i==3 ? 0 : i+1)); - primitives->push_back (8+i); + primitives->push_back(12); + primitives->push_back(8 + (i == 3 ? 0 : i + 1)); + primitives->push_back(8 + i); } - geometry->addPrimitiveSet (primitives); + geometry->addPrimitiveSet(primitives); - osg::Vec4Array *colours = new osg::Vec4Array; + osg::Vec4Array* colours = new osg::Vec4Array; - for (int i=0; i<8; ++i) - colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, - axis==2 ? 1.0f : 0.2f, mMarkerTransparency)); + 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, 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, 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, mMarkerTransparency)); - geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); setupCommonMarkerState(geometry); - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable (geometry); + osg::ref_ptr group(new osg::Group); + group->addChild(geometry); - return geode; + return group; } -osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) +osg::ref_ptr CSVRender::Object::makeRotateMarker(int axis) { 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(24, (int)(OuterRadius * 2 * osg::PI / SegmentDistance))); + const size_t SegmentCount = std::clamp(OuterRadius * 2 * osg::PI / SegmentDistance, 24, 64); const size_t VerticesPerSegment = 4; const size_t IndicesPerSegment = 24; @@ -317,24 +347,18 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) const float Angle = 2 * osg::PI / SegmentCount; - const unsigned short IndexPattern[IndicesPerSegment] = - { - 0, 4, 5, 0, 5, 1, - 2, 6, 4, 2, 4, 0, - 3, 7, 6, 3, 6, 2, - 1, 5, 7, 1, 7, 3 - }; - + const unsigned short IndexPattern[IndicesPerSegment] + = { 0, 4, 5, 0, 5, 1, 2, 6, 4, 2, 4, 0, 3, 7, 6, 3, 6, 2, 1, 5, 7, 1, 7, 3 }; osg::ref_ptr geometry = new osg::Geometry(); osg::ref_ptr vertices = new osg::Vec3Array(VertexCount); osg::ref_ptr colors = new osg::Vec4Array(1); - osg::ref_ptr primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, - IndexCount); + 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); + osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth / 4, 0, axis); for (size_t i = 0; i < SegmentCount; ++i) { @@ -346,17 +370,14 @@ 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) + offset; + 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; 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, - mMarkerTransparency); + 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) { @@ -382,10 +403,10 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) setupCommonMarkerState(geometry); - osg::ref_ptr geode = new osg::Geode(); - geode->addDrawable (geometry); + osg::ref_ptr group = new osg::Group(); + group->addChild(geometry); - return geode; + return group; } void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr geometry) @@ -397,24 +418,35 @@ void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr geome state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); } -osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis) +osg::Vec3f CSVRender::Object::getMarkerPosition(float x, float y, float z, int axis) { switch (axis) { - case 2: return osg::Vec3f (x, y, z); - case 0: return osg::Vec3f (z, x, y); - case 1: return osg::Vec3f (y, z, x); + case 2: + return osg::Vec3f(x, y, z); + case 0: + return osg::Vec3f(z, x, y); + case 1: + return osg::Vec3f(y, z, x); default: - throw std::logic_error ("invalid axis for marker geometry"); + throw std::logic_error("invalid axis for marker geometry"); } } -CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, - const std::string& id, bool referenceable, bool forceBaseToZero) -: mData (data), mBaseNode(nullptr), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), - mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f) +CSVRender::Object::Object( + CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) + : mData(data) + , mBaseNode(nullptr) + , mSelected(false) + , mParentNode(parentNode) + , mResourceSystem(data.getResourceSystem().get()) + , mForceBaseToZero(forceBaseToZero) + , mScaleOverride(1) + , mOverrideFlags(0) + , mSubMode(-1) + , mMarkerTransparency(0.5f) { mRootNode = new osg::PositionAttitudeTransform; @@ -425,19 +457,19 @@ CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, mBaseNode->setUserData(new ObjectTag(this)); - mRootNode->addChild (mBaseNode); + mRootNode->addChild(mBaseNode); - parentNode->addChild (mRootNode); + parentNode->addChild(mRootNode); mRootNode->setNodeMask(Mask_Reference); - + ESM::RefId refId = ESM::RefId::stringRefId(id); if (referenceable) { - mReferenceableId = id; + mReferenceableId = refId; } else { - mReferenceId = id; + mReferenceId = refId; mReferenceableId = getReference().mRefID; } @@ -450,18 +482,24 @@ CSVRender::Object::~Object() { clear(); - mParentNode->removeChild (mRootNode); + mParentNode->removeChild(mRootNode); } void CSVRender::Object::setSelected(bool selected) { mSelected = selected; + if (mSnapTarget) + { + setSnapTarget(false); + } + mOutline->removeChild(mBaseNode); mRootNode->removeChild(mOutline); mRootNode->removeChild(mBaseNode); if (selected) { + mOutline->setWireframeColor(osg::Vec4f(1, 1, 1, 1)); mOutline->addChild(mBaseNode); mRootNode->addChild(mOutline); } @@ -477,6 +515,36 @@ bool CSVRender::Object::getSelected() const return mSelected; } +void CSVRender::Object::setSnapTarget(bool isSnapTarget) +{ + mSnapTarget = isSnapTarget; + + if (mSelected) + { + setSelected(false); + } + + mOutline->removeChild(mBaseNode); + mRootNode->removeChild(mOutline); + mRootNode->removeChild(mBaseNode); + if (isSnapTarget) + { + mOutline->setWireframeColor(osg::Vec4f(1, 1, 0, 1)); + mOutline->addChild(mBaseNode); + mRootNode->addChild(mOutline); + } + else + mRootNode->addChild(mBaseNode); + + mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble(); + updateMarker(); +} + +bool CSVRender::Object::getSnapTarget() const +{ + return mSnapTarget; +} + osg::ref_ptr CSVRender::Object::getRootNode() { return mRootNode; @@ -487,14 +555,13 @@ osg::ref_ptr CSVRender::Object::getBaseNode() return mBaseNode; } -bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +bool CSVRender::Object::referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); - int index = referenceables.searchId (mReferenceableId); + int index = referenceables.searchId(mReferenceableId); - if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) + if (index != -1 && index >= topLeft.row() && index <= bottomRight.row()) { adjustTransform(); update(); @@ -505,14 +572,13 @@ bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, return false; } -bool CSVRender::Object::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, - int end) +bool CSVRender::Object::referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end) { const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); - int index = referenceables.searchId (mReferenceableId); + int index = referenceables.searchId(mReferenceableId); - if (index!=-1 && index>=start && index<=end) + if (index != -1 && index >= start && index <= end) { // Deletion of referenceable-type objects is handled outside of Object. if (!mReferenceId.empty()) @@ -526,27 +592,25 @@ bool CSVRender::Object::referenceableAboutToBeRemoved (const QModelIndex& parent return false; } -bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mReferenceId.empty()) return false; const CSMWorld::RefCollection& references = mData.getReferences(); - int index = references.searchId (mReferenceId); + int index = references.searchId(mReferenceId); - if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) + if (index != -1 && index >= topLeft.row() && index <= bottomRight.row()) { - int columnIndex = - references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); + int columnIndex = references.findColumnIndex(CSMWorld::Columns::ColumnId_ReferenceableId); adjustTransform(); - if (columnIndex>=topLeft.column() && columnIndex<=bottomRight.row()) + if (columnIndex >= topLeft.column() && columnIndex <= bottomRight.row()) { - mReferenceableId = - references.getData (index, columnIndex).toString().toUtf8().constData(); + mReferenceableId + = ESM::RefId::stringRefId(references.getData(index, columnIndex).toString().toUtf8().constData()); update(); updateMarker(); @@ -566,17 +630,17 @@ void CSVRender::Object::reloadAssets() std::string CSVRender::Object::getReferenceId() const { - return mReferenceId; + return mReferenceId.getRefIdString(); } std::string CSVRender::Object::getReferenceableId() const { - return mReferenceableId; + return mReferenceableId.getRefIdString(); } osg::ref_ptr CSVRender::Object::getTag() const { - return static_cast (mBaseNode->getUserData()); + return static_cast(mBaseNode->getUserData()); } bool CSVRender::Object::isEdited() const @@ -584,7 +648,7 @@ bool CSVRender::Object::isEdited() const return mOverrideFlags; } -void CSVRender::Object::setEdited (int flags) +void CSVRender::Object::setEdited(int flags) { bool discard = mOverrideFlags & ~flags; int added = flags & ~mOverrideFlags; @@ -592,11 +656,11 @@ void CSVRender::Object::setEdited (int flags) mOverrideFlags = flags; if (added & Override_Position) - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) mPositionOverride.pos[i] = getReference().mPos.pos[i]; if (added & Override_Rotation) - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) mPositionOverride.rot[i] = getReference().mPos.rot[i]; if (added & Override_Scale) @@ -611,11 +675,11 @@ ESM::Position CSVRender::Object::getPosition() const ESM::Position position = getReference().mPos; if (mOverrideFlags & Override_Position) - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) position.pos[i] = mPositionOverride.pos[i]; if (mOverrideFlags & Override_Rotation) - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) position.rot[i] = mPositionOverride.rot[i]; return position; @@ -626,27 +690,27 @@ float CSVRender::Object::getScale() const return (mOverrideFlags & Override_Scale) ? mScaleOverride : getReference().mScale; } -void CSVRender::Object::setPosition (const float position[3]) +void CSVRender::Object::setPosition(const float position[3]) { mOverrideFlags |= Override_Position; - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) mPositionOverride.pos[i] = position[i]; adjustTransform(); } -void CSVRender::Object::setRotation (const float rotation[3]) +void CSVRender::Object::setRotation(const float rotation[3]) { mOverrideFlags |= Override_Rotation; - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) mPositionOverride.rot[i] = rotation[i]; adjustTransform(); } -void CSVRender::Object::setScale (float scale) +void CSVRender::Object::setScale(float scale) { mOverrideFlags |= Override_Scale; @@ -661,78 +725,79 @@ void CSVRender::Object::setMarkerTransparency(float value) updateMarker(); } -void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) +void CSVRender::Object::apply(CSMWorld::CommandMacro& commands) { const CSMWorld::RefCollection& collection = mData.getReferences(); - QAbstractItemModel *model = mData.getTableModel (CSMWorld::UniversalId::Type_References); + QAbstractItemModel* model = mData.getTableModel(CSMWorld::UniversalId::Type_References); - int recordIndex = collection.getIndex (mReferenceId); + int recordIndex = collection.getIndex(mReferenceId); if (mOverrideFlags & Override_Position) { - //Do cell check first so positions can be compared + // Do cell check first so positions can be compared const CSMWorld::CellRef& ref = collection.getRecord(recordIndex).get(); if (CSMWorld::CellCoordinates::isExteriorCell(ref.mCell)) { // Find cell index at new position - std::pair cellIndex = CSMWorld::CellCoordinates::coordinatesToCellIndex( - mPositionOverride.pos[0], mPositionOverride.pos[1]); + std::pair cellIndex + = CSMWorld::CellCoordinates::coordinatesToCellIndex(mPositionOverride.pos[0], mPositionOverride.pos[1]); std::pair originalIndex = ref.getCellIndex(); - int cellColumn = collection.findColumnIndex (static_cast ( - CSMWorld::Columns::ColumnId_Cell)); - int refNumColumn = collection.findColumnIndex (static_cast ( - CSMWorld::Columns::ColumnId_RefNum)); + int cellColumn = collection.findColumnIndex( + static_cast(CSMWorld::Columns::ColumnId_Cell)); + int origCellColumn = collection.findColumnIndex( + static_cast(CSMWorld::Columns::ColumnId_OriginalCell)); if (cellIndex != originalIndex) { /// \todo figure out worldspace (not important until multiple worldspaces are supported) - std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); + std::string origCellId = CSMWorld::CellCoordinates(originalIndex).getId(""); + std::string cellId = CSMWorld::CellCoordinates(cellIndex).getId(""); - commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str()))); - commands.push (new CSMWorld::ModifyCommand( *model, - model->index (recordIndex, refNumColumn), 0)); + commands.push(new CSMWorld::ModifyCommand( + *model, model->index(recordIndex, origCellColumn), QString::fromUtf8(origCellId.c_str()))); + commands.push(new CSMWorld::ModifyCommand( + *model, model->index(recordIndex, cellColumn), QString::fromUtf8(cellId.c_str()))); + // NOTE: refnum is not modified for moving a reference to another cell } } - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) { - int column = collection.findColumnIndex (static_cast ( - CSMWorld::Columns::ColumnId_PositionXPos+i)); + int column = collection.findColumnIndex( + static_cast(CSMWorld::Columns::ColumnId_PositionXPos + i)); - commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, column), mPositionOverride.pos[i])); + commands.push( + new CSMWorld::ModifyCommand(*model, model->index(recordIndex, column), mPositionOverride.pos[i])); } } if (mOverrideFlags & Override_Rotation) { - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) { - int column = collection.findColumnIndex (static_cast ( - CSMWorld::Columns::ColumnId_PositionXRot+i)); + int column = collection.findColumnIndex( + static_cast(CSMWorld::Columns::ColumnId_PositionXRot + i)); - commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, column), osg::RadiansToDegrees(mPositionOverride.rot[i]))); + commands.push(new CSMWorld::ModifyCommand( + *model, model->index(recordIndex, column), osg::RadiansToDegrees(mPositionOverride.rot[i]))); } } if (mOverrideFlags & Override_Scale) { - int column = collection.findColumnIndex (CSMWorld::Columns::ColumnId_Scale); + int column = collection.findColumnIndex(CSMWorld::Columns::ColumnId_Scale); - commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, column), mScaleOverride)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(recordIndex, column), mScaleOverride)); } mOverrideFlags = 0; } -void CSVRender::Object::setSubMode (int subMode) +void CSVRender::Object::setSubMode(int subMode) { - if (subMode!=mSubMode) + if (subMode != mSubMode) { mSubMode = subMode; updateMarker(); diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index a19d64223..436c410c8 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -4,23 +4,22 @@ #include #include +#include #include -#include -#include #include +#include #include "tagbase.hpp" class QModelIndex; -class QUndoStack; namespace osg { class PositionAttitudeTransform; + class Geometry; class Group; class Node; - class Geode; } namespace osgFX @@ -44,165 +43,166 @@ namespace CSVRender { class Actor; class Object; + struct WorldspaceHitResult; - // An object to attach as user data to the osg::Node, allows us to get an Object back from a Node when we are doing a ray query + // An object to attach as user data to the osg::Node, allows us to get an Object back from a Node when we are doing + // a ray query class ObjectTag : public TagBase { - public: + public: + ObjectTag(Object* object); - ObjectTag (Object* object); + Object* mObject; - Object* mObject; - - QString getToolTip (bool hideBasics) const override; + QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override; }; class ObjectMarkerTag : public ObjectTag { - public: + public: + ObjectMarkerTag(Object* object, int axis); - ObjectMarkerTag (Object* object, int axis); - - int mAxis; + int mAxis; }; class Object { - public: + public: + enum OverrideFlags + { + Override_Position = 1, + Override_Rotation = 2, + Override_Scale = 4 + }; - enum OverrideFlags - { - Override_Position = 1, - Override_Rotation = 2, - Override_Scale = 4 - }; + private: + static const float MarkerShaftWidth; + static const float MarkerShaftBaseLength; + static const float MarkerHeadWidth; + static const float MarkerHeadLength; - private: + CSMWorld::Data& mData; + ESM::RefId mReferenceId; + ESM::RefId mReferenceableId; + osg::ref_ptr mRootNode; + osg::ref_ptr mBaseNode; + osg::ref_ptr mOutline; + bool mSelected; + bool mSnapTarget; + osg::Group* mParentNode; + Resource::ResourceSystem* mResourceSystem; + bool mForceBaseToZero; + ESM::Position mPositionOverride; + float mScaleOverride; + int mOverrideFlags; + osg::ref_ptr mMarker[3]; + int mSubMode; + float mMarkerTransparency; + std::unique_ptr mActor; - static const float MarkerShaftWidth; - static const float MarkerShaftBaseLength; - static const float MarkerHeadWidth; - static const float MarkerHeadLength; + /// Not implemented + Object(const Object&); - CSMWorld::Data& mData; - std::string mReferenceId; - std::string mReferenceableId; - osg::ref_ptr mRootNode; - osg::ref_ptr mBaseNode; - osg::ref_ptr mOutline; - bool mSelected; - osg::Group* mParentNode; - Resource::ResourceSystem* mResourceSystem; - bool mForceBaseToZero; - ESM::Position mPositionOverride; - float mScaleOverride; - int mOverrideFlags; - osg::ref_ptr mMarker[3]; - int mSubMode; - float mMarkerTransparency; - std::unique_ptr mActor; + /// Not implemented + Object& operator=(const Object&); - /// Not implemented - Object (const Object&); + /// Remove object from node (includes deleting) + void clear(); - /// Not implemented - Object& operator= (const Object&); + /// Update model + /// @note Make sure adjustTransform() was called first so world space particles get positioned correctly + void update(); - /// Remove object from node (includes deleting) - void clear(); + /// Adjust position, orientation and scale + void adjustTransform(); - /// Update model - /// @note Make sure adjustTransform() was called first so world space particles get positioned correctly - void update(); + /// Throws an exception if *this was constructed with referenceable + const CSMWorld::CellRef& getReference() const; - /// Adjust position, orientation and scale - void adjustTransform(); + void updateMarker(); - /// Throws an exception if *this was constructed with referenceable - const CSMWorld::CellRef& getReference() const; + osg::ref_ptr makeMoveOrScaleMarker(int axis); + osg::ref_ptr makeRotateMarker(int axis); - void updateMarker(); + /// Sets up a stateset with properties common to all marker types. + void setupCommonMarkerState(osg::ref_ptr geometry); - osg::ref_ptr makeMoveOrScaleMarker (int axis); - osg::ref_ptr makeRotateMarker (int axis); + osg::Vec3f getMarkerPosition(float x, float y, float z, int axis); - /// Sets up a stateset with properties common to all marker types. - void setupCommonMarkerState(osg::ref_ptr geometry); + public: + Object(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, bool referenceable, + bool forceBaseToZero = false); + /// \param forceBaseToZero If this is a reference ignore the coordinates and place + /// it at 0, 0, 0 instead. - osg::Vec3f getMarkerPosition (float x, float y, float z, int axis); + ~Object(); - public: + /// Mark the object as selected, selected objects show an outline effect + void setSelected(bool selected); - Object (CSMWorld::Data& data, osg::Group *cellNode, - const std::string& id, bool referenceable, - bool forceBaseToZero = false); - /// \param forceBaseToZero If this is a reference ignore the coordinates and place - /// it at 0, 0, 0 instead. + bool getSelected() const; - ~Object(); + /// Mark Object as "snap target" + void setSnapTarget(bool isSnapTarget); - /// Mark the object as selected, selected objects show an outline effect - void setSelected(bool selected); + bool getSnapTarget() const; - bool getSelected() const; + /// Get object node with GUI graphics + osg::ref_ptr getRootNode(); - /// Get object node with GUI graphics - osg::ref_ptr getRootNode(); + /// Get object node without GUI graphics + osg::ref_ptr getBaseNode(); - /// Get object node without GUI graphics - osg::ref_ptr getBaseNode(); + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - /// \return Did this call result in a modification of the visual representation of - /// this object? - bool referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight); + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end); - /// \return Did this call result in a modification of the visual representation of - /// this object? - bool referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - /// \return Did this call result in a modification of the visual representation of - /// this object? - bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + /// Reloads the underlying asset + void reloadAssets(); - /// Reloads the underlying asset - void reloadAssets(); + /// Returns an empty string if this is a refereceable-type object. + std::string getReferenceId() const; - /// Returns an empty string if this is a refereceable-type object. - std::string getReferenceId() const; + std::string getReferenceableId() const; - std::string getReferenceableId() const; + osg::ref_ptr getTag() const; - osg::ref_ptr getTag() const; + /// Is there currently an editing operation running on this object? + bool isEdited() const; - /// Is there currently an editing operation running on this object? - bool isEdited() const; + void setEdited(int flags); - void setEdited (int flags); + ESM::Position getPosition() const; - ESM::Position getPosition() const; + float getScale() const; - float getScale() const; + /// Set override position. + void setPosition(const float position[3]); - /// Set override position. - void setPosition (const float position[3]); + /// Set override rotation + void setRotation(const float rotation[3]); - /// Set override rotation - void setRotation (const float rotation[3]); + /// Set override scale + void setScale(float scale); - /// Set override scale - void setScale (float scale); + void setMarkerTransparency(float value); - void setMarkerTransparency(float value); + /// Apply override changes via command and end edit mode + void apply(CSMWorld::CommandMacro& commands); - /// Apply override changes via command and end edit mode - void apply (CSMWorld::CommandMacro& commands); + void setSubMode(int subMode); - void setSubMode (int subMode); - - /// Erase all overrides and restore the visual representation of the object to its - /// true state. - void reset(); + /// Erase all overrides and restore the visual representation of the object to its + /// true state. + void reset(); }; } diff --git a/apps/opencs/view/render/orbitcameramode.cpp b/apps/opencs/view/render/orbitcameramode.cpp index c81402ed1..26fc015cf 100644 --- a/apps/opencs/view/render/orbitcameramode.cpp +++ b/apps/opencs/view/render/orbitcameramode.cpp @@ -2,32 +2,37 @@ #include +#include + #include "../../model/prefs/shortcut.hpp" +#include + #include "worldspacewidget.hpp" +namespace CSVWidget +{ + class SceneToolbar; +} + namespace CSVRender { - OrbitCameraMode::OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip, - QWidget* parent) + OrbitCameraMode::OrbitCameraMode( + WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip, QWidget* parent) : ModeButton(icon, tooltip, parent) , mWorldspaceWidget(worldspaceWidget) , mCenterOnSelection(nullptr) { mCenterShortcut = new CSMPrefs::Shortcut("orbit-center-selection", worldspaceWidget); mCenterShortcut->enable(false); - connect(mCenterShortcut, SIGNAL(activated()), this, SLOT(centerSelection())); - } - - OrbitCameraMode::~OrbitCameraMode() - { + connect(mCenterShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &OrbitCameraMode::centerSelection); } void OrbitCameraMode::activate(CSVWidget::SceneToolbar* toolbar) { mCenterOnSelection = new QAction("Center on selected object", this); mCenterShortcut->associateAction(mCenterOnSelection); - connect(mCenterOnSelection, SIGNAL(triggered()), this, SLOT(centerSelection())); + connect(mCenterOnSelection, &QAction::triggered, this, &OrbitCameraMode::centerSelection); mCenterShortcut->enable(true); } diff --git a/apps/opencs/view/render/orbitcameramode.hpp b/apps/opencs/view/render/orbitcameramode.hpp index 10bc97b0f..30a92fdb4 100644 --- a/apps/opencs/view/render/orbitcameramode.hpp +++ b/apps/opencs/view/render/orbitcameramode.hpp @@ -1,10 +1,18 @@ #ifndef CSV_RENDER_ORBITCAMERAPICKMODE_H #define CSV_RENDER_ORBITCAMERAPICKMODE_H -#include - #include "../widget/modebutton.hpp" +class QAction; +class QMenu; +class QObject; +class QWidget; + +namespace CSVWidget +{ + class SceneToolbar; +} + namespace CSMPrefs { class Shortcut; @@ -16,27 +24,25 @@ namespace CSVRender class OrbitCameraMode : public CSVWidget::ModeButton { - Q_OBJECT + Q_OBJECT - public: + public: + OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip = "", + QWidget* parent = nullptr); + ~OrbitCameraMode() override = default; - OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip = "", - QWidget* parent = nullptr); - ~OrbitCameraMode(); + void activate(CSVWidget::SceneToolbar* toolbar) override; + void deactivate(CSVWidget::SceneToolbar* toolbar) override; + bool createContextMenu(QMenu* menu) override; - void activate(CSVWidget::SceneToolbar* toolbar) override; - void deactivate(CSVWidget::SceneToolbar* toolbar) override; - bool createContextMenu(QMenu* menu) override; + private: + WorldspaceWidget* mWorldspaceWidget; + QAction* mCenterOnSelection; + CSMPrefs::Shortcut* mCenterShortcut; - private: + private slots: - WorldspaceWidget* mWorldspaceWidget; - QAction* mCenterOnSelection; - CSMPrefs::Shortcut* mCenterShortcut; - - private slots: - - void centerSelection(); + void centerSelection(); }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index a0b4de979..00d519ecc 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -1,27 +1,58 @@ #include "pagedworldspacewidget.hpp" +#include +#include #include #include #include +#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include + #include "../../model/prefs/shortcut.hpp" #include "../../model/world/idtable.hpp" -#include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolmode.hpp" +#include "../widget/scenetooltoggle2.hpp" +#include "cellarrow.hpp" #include "editmode.hpp" #include "mask.hpp" -#include "cameracontroller.hpp" -#include "cellarrow.hpp" -#include "terraintexturemode.hpp" #include "terrainshapemode.hpp" +#include "terraintexturemode.hpp" + +class QWidget; + +namespace CSMWorld +{ + struct Cell; +} + +namespace CSVWidget +{ + class SceneToolbar; +} bool CSVRender::PagedWorldspaceWidget::adjustCells() { @@ -31,32 +62,31 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() { // remove/update - std::map::iterator iter (mCells.begin()); + std::map::iterator iter(mCells.begin()); - while (iter!=mCells.end()) + while (iter != mCells.end()) { - if (!mSelection.has (iter->first)) + if (!mSelection.has(iter->first)) { // remove delete iter->second; - mCells.erase (iter++); + mCells.erase(iter++); modified = true; } else { // update - int index = cells.searchId (iter->first.getId (mWorldspace)); + const int index = cells.searchId(ESM::RefId::stringRefId(iter->first.getId(mWorldspace))); - bool deleted = index==-1 || - cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; + bool deleted = index == -1 || cells.getRecord(index).mState == CSMWorld::RecordBase::State_Deleted; - if (deleted!=iter->second->isDeleted()) + if (deleted != iter->second->isDeleted()) { modified = true; - std::unique_ptr cell (new Cell (mDocument.getData(), mRootNode, - iter->first.getId (mWorldspace), deleted)); + auto cell = std::make_unique( + mDocument.getData(), mRootNode, iter->first.getId(mWorldspace), deleted); delete iter->second; iter->second = cell.release(); @@ -67,8 +97,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() // TODO check if name or region field has changed (cell marker) // FIXME: config setting - //std::string name = cells.getRecord(index).get().mName; - //std::string region = cells.getRecord(index).get().mRegion; + // std::string name = cells.getRecord(index).get().mName; + // std::string region = cells.getRecord(index).get().mRegion; modified = true; } @@ -79,79 +109,79 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() } // add - for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); - ++iter) + for (CSMWorld::CellSelection::Iterator iter(mSelection.begin()); iter != mSelection.end(); ++iter) { - if (mCells.find (*iter)==mCells.end()) + if (mCells.find(*iter) == mCells.end()) { - addCellToScene (*iter); + addCellToScene(*iter); modified = true; } } if (modified) { - for (std::map::const_iterator iter (mCells.begin()); - iter!=mCells.end(); ++iter) + for (std::map::const_iterator iter(mCells.begin()); iter != mCells.end(); + ++iter) { int mask = 0; - for (int i=CellArrow::Direction_North; i<=CellArrow::Direction_East; i *= 2) + for (int i = CellArrow::Direction_North; i <= CellArrow::Direction_East; i *= 2) { - CSMWorld::CellCoordinates coordinates (iter->second->getCoordinates()); + CSMWorld::CellCoordinates coordinates(iter->second->getCoordinates()); switch (i) { - case CellArrow::Direction_North: coordinates = coordinates.move (0, 1); break; - case CellArrow::Direction_West: coordinates = coordinates.move (-1, 0); break; - case CellArrow::Direction_South: coordinates = coordinates.move (0, -1); break; - case CellArrow::Direction_East: coordinates = coordinates.move (1, 0); break; + case CellArrow::Direction_North: + coordinates = coordinates.move(0, 1); + break; + case CellArrow::Direction_West: + coordinates = coordinates.move(-1, 0); + break; + case CellArrow::Direction_South: + coordinates = coordinates.move(0, -1); + break; + case CellArrow::Direction_East: + coordinates = coordinates.move(1, 0); + break; } - if (!mSelection.has (coordinates)) + if (!mSelection.has(coordinates)) mask |= i; } - iter->second->setCellArrows (mask); + iter->second->setCellArrows(mask); } } return modified; } -void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons ( - CSVWidget::SceneToolToggle2 *tool) +void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) { - WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (Button_Terrain, Mask_Terrain, "Terrain"); - tool->addButton (Button_Fog, Mask_Fog, "Fog", "", true); + WorldspaceWidget::addVisibilitySelectorButtons(tool); + tool->addButton(Button_Terrain, Mask_Terrain, "Terrain"); + tool->addButton(Button_Fog, Mask_Fog, "Fog", "", true); } -void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( - CSVWidget::SceneToolMode *tool) +void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool) { - WorldspaceWidget::addEditModeSelectorButtons (tool); + WorldspaceWidget::addEditModeSelectorButtons(tool); /// \todo replace EditMode with suitable subclasses - tool->addButton ( - new TerrainShapeMode (this, mRootNode, tool), "terrain-shape"); - tool->addButton ( - new TerrainTextureMode (this, mRootNode, tool), "terrain-texture"); - tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), - "terrain-vertex"); - tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain movement"), - "terrain-move"); + tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape"); + tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture"); + tool->addButton( + new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); + tool->addButton(new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain movement"), "terrain-move"); } -void CSVRender::PagedWorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) +void CSVRender::PagedWorldspaceWidget::handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type) { - if (hit.tag && hit.tag->getMask()==Mask_CellArrow) + if (hit.tag && hit.tag->getMask() == Mask_CellArrow) { - if (CellArrowTag *cellArrowTag = dynamic_cast (hit.tag.get())) + if (CellArrowTag* cellArrowTag = dynamic_cast(hit.tag.get())) { - CellArrow *arrow = cellArrowTag->getCellArrow(); + CellArrow* arrow = cellArrowTag->getCellArrow(); CSMWorld::CellCoordinates coordinates = arrow->getCoordinates(); @@ -162,41 +192,49 @@ void CSVRender::PagedWorldspaceWidget::handleInteractionPress (const WorldspaceH switch (direction) { - case CellArrow::Direction_North: y = 1; break; - case CellArrow::Direction_West: x = -1; break; - case CellArrow::Direction_South: y = -1; break; - case CellArrow::Direction_East: x = 1; break; + case CellArrow::Direction_North: + y = 1; + break; + case CellArrow::Direction_West: + x = -1; + break; + case CellArrow::Direction_South: + y = -1; + break; + case CellArrow::Direction_East: + x = 1; + break; } bool modified = false; if (type == InteractionType_PrimarySelect) { - addCellSelection (x, y); + addCellSelection(x, y); modified = true; } else if (type == InteractionType_SecondarySelect) { - moveCellSelection (x, y); + moveCellSelection(x, y); modified = true; } else // Primary/SecondaryEdit { - CSMWorld::CellCoordinates newCoordinates = coordinates.move (x, y); + CSMWorld::CellCoordinates newCoordinates = coordinates.move(x, y); - if (mCells.find (newCoordinates)==mCells.end()) + if (mCells.find(newCoordinates) == mCells.end()) { - addCellToScene (newCoordinates); - mSelection.add (newCoordinates); + addCellToScene(newCoordinates); + mSelection.add(newCoordinates); modified = true; } if (type == InteractionType_SecondaryEdit) { - if (mCells.find (coordinates)!=mCells.end()) + if (mCells.find(coordinates) != mCells.end()) { - removeCellFromScene (coordinates); - mSelection.remove (coordinates); + removeCellFromScene(coordinates); + mSelection.remove(coordinates); modified = true; } } @@ -209,73 +247,61 @@ void CSVRender::PagedWorldspaceWidget::handleInteractionPress (const WorldspaceH } } - WorldspaceWidget::handleInteractionPress (hit, type); + WorldspaceWidget::handleInteractionPress(hit, type); } -void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::PagedWorldspaceWidget::referenceableDataChanged( + const QModelIndex& topLeft, const QModelIndex& bottomRight) { - for (std::map::iterator iter (mCells.begin()); - iter!=mCells.end(); ++iter) - if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + for (std::map::iterator iter(mCells.begin()); iter != mCells.end(); ++iter) + if (iter->second->referenceableDataChanged(topLeft, bottomRight)) flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::referenceableAboutToBeRemoved ( - const QModelIndex& parent, int start, int end) +void CSVRender::PagedWorldspaceWidget::referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - for (std::map::iterator iter (mCells.begin()); - iter!=mCells.end(); ++iter) - if (iter->second->referenceableAboutToBeRemoved (parent, start, end)) + for (std::map::iterator iter(mCells.begin()); iter != mCells.end(); ++iter) + if (iter->second->referenceableAboutToBeRemoved(parent, start, end)) flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::referenceableAdded (const QModelIndex& parent, - int start, int end) +void CSVRender::PagedWorldspaceWidget::referenceableAdded(const QModelIndex& parent, int start, int end) { - CSMWorld::IdTable& referenceables = dynamic_cast ( - *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + CSMWorld::IdTable& referenceables = dynamic_cast( + *mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Referenceables)); - for (std::map::iterator iter (mCells.begin()); - iter!=mCells.end(); ++iter) + for (std::map::iterator iter(mCells.begin()); iter != mCells.end(); ++iter) { - QModelIndex topLeft = referenceables.index (start, 0); - QModelIndex bottomRight = - referenceables.index (end, referenceables.columnCount()); + QModelIndex topLeft = referenceables.index(start, 0); + QModelIndex bottomRight = referenceables.index(end, referenceables.columnCount()); - if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + if (iter->second->referenceableDataChanged(topLeft, bottomRight)) flagAsModified(); } } -void CSVRender::PagedWorldspaceWidget::referenceDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::PagedWorldspaceWidget::referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - for (std::map::iterator iter (mCells.begin()); - iter!=mCells.end(); ++iter) - if (iter->second->referenceDataChanged (topLeft, bottomRight)) + for (std::map::iterator iter(mCells.begin()); iter != mCells.end(); ++iter) + if (iter->second->referenceDataChanged(topLeft, bottomRight)) flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::referenceAboutToBeRemoved (const QModelIndex& parent, - int start, int end) +void CSVRender::PagedWorldspaceWidget::referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - for (std::map::iterator iter (mCells.begin()); - iter!=mCells.end(); ++iter) - if (iter->second->referenceAboutToBeRemoved (parent, start, end)) + for (std::map::iterator iter(mCells.begin()); iter != mCells.end(); ++iter) + if (iter->second->referenceAboutToBeRemoved(parent, start, end)) flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent, int start, - int end) +void CSVRender::PagedWorldspaceWidget::referenceAdded(const QModelIndex& parent, int start, int end) { - for (std::map::iterator iter (mCells.begin()); - iter!=mCells.end(); ++iter) - if (iter->second->referenceAdded (parent, start, end)) + for (std::map::iterator iter(mCells.begin()); iter != mCells.end(); ++iter) + if (iter->second->referenceAdded(parent, start, end)) flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSVRender::PagedWorldspaceWidget::pathgridDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); @@ -307,7 +333,7 @@ void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& t } } -void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved(const QModelIndex& parent, int start, int end) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); @@ -331,7 +357,7 @@ void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelInd void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, int start, int end) { - const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); if (!parent.isValid()) { @@ -350,13 +376,13 @@ void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, } } -void CSVRender::PagedWorldspaceWidget::landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSVRender::PagedWorldspaceWidget::landDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int r = topLeft.row(); r <= bottomRight.row(); ++r) { - std::string id = mDocument.getData().getLand().getId(r); + const auto& id = mDocument.getData().getLand().getId(r); - auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); + auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id.getRefIdString()).first); if (cellIt != mCells.end()) { cellIt->second->landDataChanged(topLeft, bottomRight); @@ -365,13 +391,13 @@ void CSVRender::PagedWorldspaceWidget::landDataChanged (const QModelIndex& topLe } } -void CSVRender::PagedWorldspaceWidget::landAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSVRender::PagedWorldspaceWidget::landAboutToBeRemoved(const QModelIndex& parent, int start, int end) { for (int r = start; r <= end; ++r) { - std::string id = mDocument.getData().getLand().getId(r); + const auto& id = mDocument.getData().getLand().getId(r); - auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); + auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id.getRefIdString()).first); if (cellIt != mCells.end()) { cellIt->second->landAboutToBeRemoved(parent, start, end); @@ -380,13 +406,13 @@ void CSVRender::PagedWorldspaceWidget::landAboutToBeRemoved (const QModelIndex& } } -void CSVRender::PagedWorldspaceWidget::landAdded (const QModelIndex& parent, int start, int end) +void CSVRender::PagedWorldspaceWidget::landAdded(const QModelIndex& parent, int start, int end) { for (int r = start; r <= end; ++r) { - std::string id = mDocument.getData().getLand().getId(r); + const auto& id = mDocument.getData().getLand().getId(r); - auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first); + auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id.getRefIdString()).first); if (cellIt != mCells.end()) { cellIt->second->landAdded(parent, start, end); @@ -395,28 +421,28 @@ void CSVRender::PagedWorldspaceWidget::landAdded (const QModelIndex& parent, int } } -void CSVRender::PagedWorldspaceWidget::landTextureDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSVRender::PagedWorldspaceWidget::landTextureDataChanged( + const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (auto cellIt : mCells) cellIt.second->landTextureChanged(topLeft, bottomRight); flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSVRender::PagedWorldspaceWidget::landTextureAboutToBeRemoved(const QModelIndex& parent, int start, int end) { for (auto cellIt : mCells) cellIt.second->landTextureAboutToBeRemoved(parent, start, end); flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::landTextureAdded (const QModelIndex& parent, int start, int end) +void CSVRender::PagedWorldspaceWidget::landTextureAdded(const QModelIndex& parent, int start, int end) { for (auto cellIt : mCells) cellIt.second->landTextureAdded(parent, start, end); flagAsModified(); } - std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { osg::Vec3d eye, center, up; @@ -425,84 +451,73 @@ std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() std::ostringstream stream; - stream - << "player->position " - << position.x() << ", " << position.y() << ", " << position.z() - << ", 0"; + stream << "player->position " << position.x() << ", " << position.y() << ", " << position.z() << ", 0"; return stream.str(); } -void CSVRender::PagedWorldspaceWidget::addCellToScene ( - const CSMWorld::CellCoordinates& coordinates) +void CSVRender::PagedWorldspaceWidget::addCellToScene(const CSMWorld::CellCoordinates& coordinates) { const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); - int index = cells.searchId (coordinates.getId (mWorldspace)); + const int index = cells.searchId(ESM::RefId::stringRefId(coordinates.getId(mWorldspace))); - bool deleted = index==-1 || - cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; + bool deleted = index == -1 || cells.getRecord(index).mState == CSMWorld::RecordBase::State_Deleted; - std::unique_ptr cell ( - new Cell (mDocument.getData(), mRootNode, coordinates.getId (mWorldspace), - deleted)); - EditMode *editMode = getEditMode(); - cell->setSubMode (editMode->getSubMode(), editMode->getInteractionMask()); + auto cell = std::make_unique(mDocument.getData(), mRootNode, coordinates.getId(mWorldspace), deleted); + EditMode* editMode = getEditMode(); + cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask()); - mCells.insert (std::make_pair (coordinates, cell.release())); + mCells.insert(std::make_pair(coordinates, cell.release())); } -void CSVRender::PagedWorldspaceWidget::removeCellFromScene ( - const CSMWorld::CellCoordinates& coordinates) +void CSVRender::PagedWorldspaceWidget::removeCellFromScene(const CSMWorld::CellCoordinates& coordinates) { - std::map::iterator iter = mCells.find (coordinates); + std::map::iterator iter = mCells.find(coordinates); - if (iter!=mCells.end()) + if (iter != mCells.end()) { delete iter->second; - mCells.erase (iter); + mCells.erase(iter); } } -void CSVRender::PagedWorldspaceWidget::addCellSelection (int x, int y) +void CSVRender::PagedWorldspaceWidget::addCellSelection(int x, int y) { CSMWorld::CellSelection newSelection = mSelection; - newSelection.move (x, y); + newSelection.move(x, y); - for (CSMWorld::CellSelection::Iterator iter (newSelection.begin()); iter!=newSelection.end(); - ++iter) + for (CSMWorld::CellSelection::Iterator iter(newSelection.begin()); iter != newSelection.end(); ++iter) { - if (mCells.find (*iter)==mCells.end()) + if (mCells.find(*iter) == mCells.end()) { - addCellToScene (*iter); - mSelection.add (*iter); + addCellToScene(*iter); + mSelection.add(*iter); } } } -void CSVRender::PagedWorldspaceWidget::moveCellSelection (int x, int y) +void CSVRender::PagedWorldspaceWidget::moveCellSelection(int x, int y) { CSMWorld::CellSelection newSelection = mSelection; - newSelection.move (x, y); + newSelection.move(x, y); - for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); - ++iter) + for (CSMWorld::CellSelection::Iterator iter(mSelection.begin()); iter != mSelection.end(); ++iter) { - if (!newSelection.has (*iter)) - removeCellFromScene (*iter); + if (!newSelection.has(*iter)) + removeCellFromScene(*iter); } - for (CSMWorld::CellSelection::Iterator iter (newSelection.begin()); iter!=newSelection.end(); - ++iter) + for (CSMWorld::CellSelection::Iterator iter(newSelection.begin()); iter != newSelection.end(); ++iter) { - if (!mSelection.has (*iter)) - addCellToScene (*iter); + if (!mSelection.has(*iter)) + addCellToScene(*iter); } mSelection = newSelection; } -void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera (int offsetX, int offsetY) +void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera(int offsetX, int offsetY) { osg::Vec3f eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); @@ -521,79 +536,76 @@ void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera (int offsetX, in } } -CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) -: WorldspaceWidget (document, parent), mDocument (document), mWorldspace ("std::default"), - mControlElements(nullptr), mDisplayCellCoord(true) +CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget(QWidget* parent, CSMDoc::Document& document) + : WorldspaceWidget(document, parent) + , mDocument(document) + , mWorldspace("std::default") + , mControlElements(nullptr) + , mDisplayCellCoord(true) { - QAbstractItemModel *cells = - document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells); + QAbstractItemModel* cells = document.getData().getTableModel(CSMWorld::UniversalId::Type_Cells); - connect (cells, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (cellDataChanged (const QModelIndex&, const QModelIndex&))); - connect (cells, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (cellRemoved (const QModelIndex&, int, int))); - connect (cells, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (cellAdded (const QModelIndex&, int, int))); + connect(cells, &QAbstractItemModel::dataChanged, this, &PagedWorldspaceWidget::cellDataChanged); + connect(cells, &QAbstractItemModel::rowsRemoved, this, &PagedWorldspaceWidget::cellRemoved); + connect(cells, &QAbstractItemModel::rowsInserted, this, &PagedWorldspaceWidget::cellAdded); - connect (&document.getData(), SIGNAL (assetTablesChanged ()), - this, SLOT (assetTablesChanged ())); + connect(&document.getData(), &CSMWorld::Data::assetTablesChanged, this, &PagedWorldspaceWidget::assetTablesChanged); - QAbstractItemModel *lands = document.getData().getTableModel (CSMWorld::UniversalId::Type_Lands); + QAbstractItemModel* lands = document.getData().getTableModel(CSMWorld::UniversalId::Type_Lands); - connect (lands, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (landDataChanged (const QModelIndex&, const QModelIndex&))); - connect (lands, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (landAboutToBeRemoved (const QModelIndex&, int, int))); - connect (lands, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (landAdded (const QModelIndex&, int, int))); + connect(lands, &QAbstractItemModel::dataChanged, this, &PagedWorldspaceWidget::landDataChanged); + connect(lands, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PagedWorldspaceWidget::landAboutToBeRemoved); + connect(lands, &QAbstractItemModel::rowsInserted, this, &PagedWorldspaceWidget::landAdded); - QAbstractItemModel *ltexs = document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures); + QAbstractItemModel* ltexs = document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures); - connect (ltexs, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (landTextureDataChanged (const QModelIndex&, const QModelIndex&))); - connect (ltexs, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (landTextureAboutToBeRemoved (const QModelIndex&, int, int))); - connect (ltexs, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (landTextureAdded (const QModelIndex&, int, int))); + connect(ltexs, &QAbstractItemModel::dataChanged, this, &PagedWorldspaceWidget::landTextureDataChanged); + connect( + ltexs, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PagedWorldspaceWidget::landTextureAboutToBeRemoved); + connect(ltexs, &QAbstractItemModel::rowsInserted, this, &PagedWorldspaceWidget::landTextureAdded); // Shortcuts CSMPrefs::Shortcut* loadCameraCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-cell", this); - connect(loadCameraCellShortcut, SIGNAL(activated()), this, SLOT(loadCameraCell())); + connect(loadCameraCellShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &PagedWorldspaceWidget::loadCameraCell); CSMPrefs::Shortcut* loadCameraEastCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-eastcell", this); - connect(loadCameraEastCellShortcut, SIGNAL(activated()), this, SLOT(loadEastCell())); + connect(loadCameraEastCellShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &PagedWorldspaceWidget::loadEastCell); CSMPrefs::Shortcut* loadCameraNorthCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-northcell", this); - connect(loadCameraNorthCellShortcut, SIGNAL(activated()), this, SLOT(loadNorthCell())); + connect(loadCameraNorthCellShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &PagedWorldspaceWidget::loadNorthCell); CSMPrefs::Shortcut* loadCameraWestCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-westcell", this); - connect(loadCameraWestCellShortcut, SIGNAL(activated()), this, SLOT(loadWestCell())); + connect(loadCameraWestCellShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &PagedWorldspaceWidget::loadWestCell); CSMPrefs::Shortcut* loadCameraSouthCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-southcell", this); - connect(loadCameraSouthCellShortcut, SIGNAL(activated()), this, SLOT(loadSouthCell())); + connect(loadCameraSouthCellShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, + &PagedWorldspaceWidget::loadSouthCell); } CSVRender::PagedWorldspaceWidget::~PagedWorldspaceWidget() { - for (std::map::iterator iter (mCells.begin()); - iter!=mCells.end(); ++iter) + for (std::map::iterator iter(mCells.begin()); iter != mCells.end(); ++iter) { delete iter->second; } } -void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) +void CSVRender::PagedWorldspaceWidget::useViewHint(const std::string& hint) { if (!hint.empty()) { CSMWorld::CellSelection selection; - if (hint[0]=='c') + if (hint[0] == 'c') { // syntax: c:#x1 y1; #x2 y2 (number of coordinate pairs can be 0 or larger) char ignore; - std::istringstream stream (hint.c_str()); + std::istringstream stream(hint.c_str()); if (stream >> ignore) { char ignore1; // : or ; @@ -603,61 +615,63 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) // Loop through all the coordinates to add them to selection while (stream >> ignore1 >> ignore2 >> x >> y) - selection.add (CSMWorld::CellCoordinates (x, y)); + selection.add(CSMWorld::CellCoordinates(x, y)); // Mark that camera needs setup - mCamPositionSet=false; + mCamPositionSet = false; } } - else if (hint[0]=='r') + else if (hint[0] == 'r') { // syntax r:ref#number (e.g. r:ref#100) char ignore; - std::istringstream stream (hint.c_str()); + std::istringstream stream(hint.c_str()); if (stream >> ignore) // ignore r { char ignore1; // : or ; std::string refCode; // ref#number (e.g. ref#100) - while (stream >> ignore1 >> refCode) {} + while (stream >> ignore1 >> refCode) + { + } - //Find out cell coordinate - CSMWorld::IdTable& references = dynamic_cast ( - *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_References)); + // Find out cell coordinate + CSMWorld::IdTable& references = dynamic_cast( + *mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_References)); int cellColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); QVariant cell = references.data(references.getModelIndex(refCode, cellColumn)).value(); QString cellqs = cell.toString(); - std::istringstream streamCellCoord (cellqs.toStdString().c_str()); + std::istringstream streamCellCoord(cellqs.toStdString().c_str()); - if (streamCellCoord >> ignore) //ignore # + if (streamCellCoord >> ignore) // ignore # { // Current coordinate int x, y; // Loop through all the coordinates to add them to selection while (streamCellCoord >> x >> y) - selection.add (CSMWorld::CellCoordinates (x, y)); + selection.add(CSMWorld::CellCoordinates(x, y)); // Mark that camera needs setup - mCamPositionSet=false; + mCamPositionSet = false; } } } - setCellSelection (selection); + setCellSelection(selection); } } -void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSelection& selection) +void CSVRender::PagedWorldspaceWidget::setCellSelection(const CSMWorld::CellSelection& selection) { mSelection = selection; if (adjustCells()) flagAsModified(); - emit cellSelectionChanged (mSelection); + emit cellSelectionChanged(mSelection); } const CSMWorld::CellSelection& CSVRender::PagedWorldspaceWidget::getCellSelection() const @@ -665,28 +679,28 @@ const CSMWorld::CellSelection& CSVRender::PagedWorldspaceWidget::getCellSelectio return mSelection; } -std::pair< int, int > CSVRender::PagedWorldspaceWidget::getCoordinatesFromId (const std::string& record) const +std::pair CSVRender::PagedWorldspaceWidget::getCoordinatesFromId(const std::string& record) const { - std::istringstream stream (record.c_str()); + std::istringstream stream(record.c_str()); char ignore; int x, y; stream >> ignore >> x >> y; return std::make_pair(x, y); } -bool CSVRender::PagedWorldspaceWidget::handleDrop ( - const std::vector< CSMWorld::UniversalId >& universalIdData, DropType type) +bool CSVRender::PagedWorldspaceWidget::handleDrop( + const std::vector& universalIdData, DropType type) { - if (WorldspaceWidget::handleDrop (universalIdData, type)) + if (WorldspaceWidget::handleDrop(universalIdData, type)) return true; - if (type!=Type_CellsExterior) + if (type != Type_CellsExterior) return false; bool selectionChanged = false; - for (unsigned i = 0; i < universalIdData.size(); ++i) + for (const auto& id : universalIdData) { - std::pair coordinates(getCoordinatesFromId(universalIdData[i].getId())); + std::pair coordinates(getCoordinatesFromId(id.getId())); if (mSelection.add(CSMWorld::CellCoordinates(coordinates.first, coordinates.second))) { selectionChanged = true; @@ -703,11 +717,12 @@ bool CSVRender::PagedWorldspaceWidget::handleDrop ( return true; } -CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::DropType type) const +CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::getDropRequirements( + CSVRender::WorldspaceWidget::DropType type) const { - dropRequirments requirements = WorldspaceWidget::getDropRequirements (type); + dropRequirments requirements = WorldspaceWidget::getDropRequirements(type); - if (requirements!=ignored) + if (requirements != ignored) return requirements; switch (type) @@ -728,47 +743,44 @@ unsigned int CSVRender::PagedWorldspaceWidget::getVisibilityMask() const return WorldspaceWidget::getVisibilityMask() | mControlElements->getSelectionMask(); } -void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) +void CSVRender::PagedWorldspaceWidget::clearSelection(int elementMask) { - for (std::map::iterator iter = mCells.begin(); - iter!=mCells.end(); ++iter) - iter->second->setSelection (elementMask, Cell::Selection_Clear); + for (std::map::iterator iter = mCells.begin(); iter != mCells.end(); ++iter) + iter->second->setSelection(elementMask, Cell::Selection_Clear); flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::invertSelection (int elementMask) +void CSVRender::PagedWorldspaceWidget::invertSelection(int elementMask) { - for (std::map::iterator iter = mCells.begin(); - iter!=mCells.end(); ++iter) - iter->second->setSelection (elementMask, Cell::Selection_Invert); + for (std::map::iterator iter = mCells.begin(); iter != mCells.end(); ++iter) + iter->second->setSelection(elementMask, Cell::Selection_Invert); flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::selectAll (int elementMask) +void CSVRender::PagedWorldspaceWidget::selectAll(int elementMask) { - for (std::map::iterator iter = mCells.begin(); - iter!=mCells.end(); ++iter) - iter->second->setSelection (elementMask, Cell::Selection_All); + for (std::map::iterator iter = mCells.begin(); iter != mCells.end(); ++iter) + iter->second->setSelection(elementMask, Cell::Selection_All); flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMask) +void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId(int elementMask) { - for (std::map::iterator iter = mCells.begin(); - iter!=mCells.end(); ++iter) - iter->second->selectAllWithSameParentId (elementMask); + for (std::map::iterator iter = mCells.begin(); iter != mCells.end(); ++iter) + iter->second->selectAllWithSameParentId(elementMask); flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +void CSVRender::PagedWorldspaceWidget::selectInsideCube( + const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) { for (auto& cell : mCells) { - cell.second->selectInsideCube (pointA, pointB, dragMode); + cell.second->selectInsideCube(pointA, pointB, dragMode); } } @@ -776,24 +788,22 @@ void CSVRender::PagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& po { for (auto& cell : mCells) { - cell.second->selectWithinDistance (point, distance, dragMode); + cell.second->selectWithinDistance(point, distance, dragMode); } } -std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const +std::string CSVRender::PagedWorldspaceWidget::getCellId(const osg::Vec3f& point) const { - CSMWorld::CellCoordinates cellCoordinates ( - static_cast (std::floor (point.x() / Constants::CellSizeInUnits)), - static_cast (std::floor (point.y() / Constants::CellSizeInUnits))); + CSMWorld::CellCoordinates cellCoordinates(static_cast(std::floor(point.x() / Constants::CellSizeInUnits)), + static_cast(std::floor(point.y() / Constants::CellSizeInUnits))); - return cellCoordinates.getId (mWorldspace); + return cellCoordinates.getId(mWorldspace); } CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& point) const { - CSMWorld::CellCoordinates coords( - static_cast (std::floor (point.x() / Constants::CellSizeInUnits)), - static_cast (std::floor (point.y() / Constants::CellSizeInUnits))); + CSMWorld::CellCoordinates coords(static_cast(std::floor(point.x() / Constants::CellSizeInUnits)), + static_cast(std::floor(point.y() / Constants::CellSizeInUnits))); std::map::const_iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) @@ -811,14 +821,16 @@ CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const CSMWorld::CellC return nullptr; } -void CSVRender::PagedWorldspaceWidget::setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height) +void CSVRender::PagedWorldspaceWidget::setCellAlteredHeight( + const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height) { std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) searchResult->second->setAlteredHeight(inCellX, inCellY, height); } -float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY) +float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeight( + const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY) { std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) @@ -832,89 +844,96 @@ void CSVRender::PagedWorldspaceWidget::resetAllAlteredHeights() cell.second->resetAlteredHeights(); } -std::vector > CSVRender::PagedWorldspaceWidget::getSelection ( - unsigned int elementMask) const +osg::ref_ptr CSVRender::PagedWorldspaceWidget::getSnapTarget(unsigned int elementMask) const { - std::vector > result; + osg::ref_ptr result; - for (std::map::const_iterator iter = mCells.begin(); - iter!=mCells.end(); ++iter) + for (auto& [coords, cell] : mCells) { - std::vector > cellResult = - iter->second->getSelection (elementMask); - - result.insert (result.end(), cellResult.begin(), cellResult.end()); + auto snapTarget = cell->getSnapTarget(elementMask); + if (snapTarget) + { + return snapTarget; + } } return result; } -std::vector > CSVRender::PagedWorldspaceWidget::getEdited ( +std::vector> CSVRender::PagedWorldspaceWidget::getSelection( unsigned int elementMask) const { - std::vector > result; + std::vector> result; - for (std::map::const_iterator iter = mCells.begin(); - iter!=mCells.end(); ++iter) + for (std::map::const_iterator iter = mCells.begin(); iter != mCells.end(); ++iter) { - std::vector > cellResult = - iter->second->getEdited (elementMask); + std::vector> cellResult = iter->second->getSelection(elementMask); - result.insert (result.end(), cellResult.begin(), cellResult.end()); + result.insert(result.end(), cellResult.begin(), cellResult.end()); } return result; } -void CSVRender::PagedWorldspaceWidget::setSubMode (int subMode, unsigned int elementMask) +std::vector> CSVRender::PagedWorldspaceWidget::getEdited( + unsigned int elementMask) const { - for (std::map::const_iterator iter = mCells.begin(); - iter!=mCells.end(); ++iter) - iter->second->setSubMode (subMode, elementMask); + std::vector> result; + + for (std::map::const_iterator iter = mCells.begin(); iter != mCells.end(); ++iter) + { + std::vector> cellResult = iter->second->getEdited(elementMask); + + result.insert(result.end(), cellResult.begin(), cellResult.end()); + } + + return result; } -void CSVRender::PagedWorldspaceWidget::reset (unsigned int elementMask) +void CSVRender::PagedWorldspaceWidget::setSubMode(int subMode, unsigned int elementMask) { - for (std::map::const_iterator iter = mCells.begin(); - iter!=mCells.end(); ++iter) - iter->second->reset (elementMask); + for (std::map::const_iterator iter = mCells.begin(); iter != mCells.end(); ++iter) + iter->second->setSubMode(subMode, elementMask); } -CSVWidget::SceneToolToggle2 *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( - CSVWidget::SceneToolbar *parent) +void CSVRender::PagedWorldspaceWidget::reset(unsigned int elementMask) { - mControlElements = new CSVWidget::SceneToolToggle2 (parent, - "Controls & Guides Visibility", ":scenetoolbar/scene-view-marker-c", ":scenetoolbar/scene-view-marker-"); + for (std::map::const_iterator iter = mCells.begin(); iter != mCells.end(); ++iter) + iter->second->reset(elementMask); +} - mControlElements->addButton (1, Mask_CellMarker, "Cell Marker"); - mControlElements->addButton (2, Mask_CellArrow, "Cell Arrows"); - mControlElements->addButton (4, Mask_CellBorder, "Cell Border"); +CSVWidget::SceneToolToggle2* CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector( + CSVWidget::SceneToolbar* parent) +{ + mControlElements = new CSVWidget::SceneToolToggle2(parent, "Controls & Guides Visibility", + ":scenetoolbar/scene-view-marker-c", ":scenetoolbar/scene-view-marker-"); - mControlElements->setSelectionMask (0xffffffff); + mControlElements->addButton(1, Mask_CellMarker, "Cell Marker"); + mControlElements->addButton(2, Mask_CellArrow, "Cell Arrows"); + mControlElements->addButton(4, Mask_CellBorder, "Cell Border"); - connect (mControlElements, SIGNAL (selectionChanged()), - this, SLOT (elementSelectionChanged())); + mControlElements->setSelectionMask(0xffffffff); + + connect(mControlElements, &CSVWidget::SceneToolToggle2::selectionChanged, this, + &PagedWorldspaceWidget::elementSelectionChanged); return mControlElements; } -void CSVRender::PagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::PagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { /// \todo check if no selected cell is affected and do not update, if that is the case if (adjustCells()) flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::cellRemoved (const QModelIndex& parent, int start, - int end) +void CSVRender::PagedWorldspaceWidget::cellRemoved(const QModelIndex& parent, int start, int end) { if (adjustCells()) flagAsModified(); } -void CSVRender::PagedWorldspaceWidget::cellAdded (const QModelIndex& index, int start, - int end) +void CSVRender::PagedWorldspaceWidget::cellAdded(const QModelIndex& index, int start, int end) { /// \todo check if no selected cell is affected and do not update, if that is the case if (adjustCells()) @@ -923,8 +942,8 @@ void CSVRender::PagedWorldspaceWidget::cellAdded (const QModelIndex& index, int void CSVRender::PagedWorldspaceWidget::assetTablesChanged() { - std::map::iterator iter = mCells.begin(); - for ( ; iter != mCells.end(); ++iter) + std::map::iterator iter = mCells.begin(); + for (; iter != mCells.end(); ++iter) { iter->second->reloadAssets(); } diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index beab0c575..9ba8911c7 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -2,192 +2,212 @@ #define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #include +#include +#include +#include + +#include #include "../../model/world/cellselection.hpp" -#include "worldspacewidget.hpp" #include "cell.hpp" #include "instancedragmodes.hpp" +#include "worldspacewidget.hpp" + +class QModelIndex; +class QObject; +class QWidget; + +namespace osg +{ + class Vec3f; + template + class ref_ptr; +} + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class UniversalId; +} namespace CSVWidget { - class SceneToolToggle; - class SceneToolToggle2; + class SceneToolToggle2; + class SceneToolMode; + class SceneToolBar; } namespace CSVRender { - class TextOverlay; - class OverlayMask; + class Cell; + class TagBase; class PagedWorldspaceWidget : public WorldspaceWidget { - Q_OBJECT + Q_OBJECT - CSMDoc::Document& mDocument; - CSMWorld::CellSelection mSelection; - std::map mCells; - std::string mWorldspace; - CSVWidget::SceneToolToggle2 *mControlElements; - bool mDisplayCellCoord; + CSMDoc::Document& mDocument; + CSMWorld::CellSelection mSelection; + std::map mCells; + std::string mWorldspace; + CSVWidget::SceneToolToggle2* mControlElements; + bool mDisplayCellCoord; - private: + private: + std::pair getCoordinatesFromId(const std::string& record) const; - std::pair getCoordinatesFromId(const std::string& record) const; + /// Bring mCells into sync with mSelection again. + /// + /// \return Any cells added or removed? + bool adjustCells(); - /// Bring mCells into sync with mSelection again. - /// - /// \return Any cells added or removed? - bool adjustCells(); + void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override; - void referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) override; + void referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; - void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; + void referenceableAdded(const QModelIndex& index, int start, int end) override; - void referenceableAdded (const QModelIndex& index, int start, int end) override; + void referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override; - void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; + void referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; - void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; + void referenceAdded(const QModelIndex& index, int start, int end) override; - void referenceAdded (const QModelIndex& index, int start, int end) override; + void pathgridDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override; - void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; + void pathgridAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; - void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; + void pathgridAdded(const QModelIndex& parent, int start, int end) override; - void pathgridAdded (const QModelIndex& parent, int start, int end) override; + std::string getStartupInstruction() override; - std::string getStartupInstruction() override; + /// \note Does not update the view or any cell marker + void addCellToScene(const CSMWorld::CellCoordinates& coordinates); - /// \note Does not update the view or any cell marker - void addCellToScene (const CSMWorld::CellCoordinates& coordinates); + /// \note Does not update the view or any cell marker + /// + /// \note Calling this function for a cell that is not in the selection is a no-op. + void removeCellFromScene(const CSMWorld::CellCoordinates& coordinates); - /// \note Does not update the view or any cell marker - /// - /// \note Calling this function for a cell that is not in the selection is a no-op. - void removeCellFromScene (const CSMWorld::CellCoordinates& coordinates); + /// \note Does not update the view or any cell marker + void addCellSelection(int x, int y); - /// \note Does not update the view or any cell marker - void addCellSelection (int x, int y); + /// \note Does not update the view or any cell marker + void moveCellSelection(int x, int y); - /// \note Does not update the view or any cell marker - void moveCellSelection (int x, int y); + void addCellToSceneFromCamera(int offsetX, int offsetY); - void addCellToSceneFromCamera (int offsetX, int offsetY); + public: + PagedWorldspaceWidget(QWidget* parent, CSMDoc::Document& document); + ///< \note Sets the cell area selection to an invalid value to indicate that currently + /// no cells are displayed. The cells to be displayed will be specified later through + /// hint system. - public: + virtual ~PagedWorldspaceWidget(); - PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); - ///< \note Sets the cell area selection to an invalid value to indicate that currently - /// no cells are displayed. The cells to be displayed will be specified later through - /// hint system. + /// Decodes the the hint string to set of cell that are rendered. + void useViewHint(const std::string& hint) override; - virtual ~PagedWorldspaceWidget(); + void setCellSelection(const CSMWorld::CellSelection& selection); - /// Decodes the the hint string to set of cell that are rendered. - void useViewHint (const std::string& hint) override; + const CSMWorld::CellSelection& getCellSelection() const; - void setCellSelection(const CSMWorld::CellSelection& selection); + /// \return Drop handled? + bool handleDrop(const std::vector& data, DropType type) override; - const CSMWorld::CellSelection& getCellSelection() const; + dropRequirments getDropRequirements(DropType type) const override; - /// \return Drop handled? - bool handleDrop (const std::vector& data, - DropType type) override; + /// \attention The created tool is not added to the toolbar (via addTool). Doing + /// that is the responsibility of the calling function. + virtual CSVWidget::SceneToolToggle2* makeControlVisibilitySelector(CSVWidget::SceneToolbar* parent); - dropRequirments getDropRequirements(DropType type) const override; + unsigned int getVisibilityMask() const override; - /// \attention The created tool is not added to the toolbar (via addTool). Doing - /// that is the responsibility of the calling function. - virtual CSVWidget::SceneToolToggle2 *makeControlVisibilitySelector ( - CSVWidget::SceneToolbar *parent); + /// \param elementMask Elements to be affected by the clear operation + void clearSelection(int elementMask) override; - unsigned int getVisibilityMask() const override; + /// \param elementMask Elements to be affected by the select operation + void invertSelection(int elementMask) override; - /// \param elementMask Elements to be affected by the clear operation - void clearSelection (int elementMask) override; + /// \param elementMask Elements to be affected by the select operation + void selectAll(int elementMask) override; - /// \param elementMask Elements to be affected by the select operation - void invertSelection (int elementMask) override; + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + void selectAllWithSameParentId(int elementMask) override; - /// \param elementMask Elements to be affected by the select operation - void selectAll (int elementMask) override; + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; - // Select everything that references the same ID as at least one of the elements - // already selected - // - /// \param elementMask Elements to be affected by the select operation - void selectAllWithSameParentId (int elementMask) override; + void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; - void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; + std::string getCellId(const osg::Vec3f& point) const override; - void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; + Cell* getCell(const osg::Vec3d& point) const override; - std::string getCellId (const osg::Vec3f& point) const override; + Cell* getCell(const CSMWorld::CellCoordinates& coords) const override; - Cell* getCell(const osg::Vec3d& point) const override; + void setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height); - Cell* getCell(const CSMWorld::CellCoordinates& coords) const override; + float* getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY); - void setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height); + void resetAllAlteredHeights(); - float* getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY); + osg::ref_ptr getSnapTarget(unsigned int elementMask) const override; - void resetAllAlteredHeights(); + std::vector> getSelection(unsigned int elementMask) const override; - std::vector > getSelection (unsigned int elementMask) - const override; + std::vector> getEdited(unsigned int elementMask) const override; - std::vector > getEdited (unsigned int elementMask) - const override; + void setSubMode(int subMode, unsigned int elementMask) override; - void setSubMode (int subMode, unsigned int elementMask) override; + /// Erase all overrides and restore the visual representation to its true state. + void reset(unsigned int elementMask) override; - /// Erase all overrides and restore the visual representation to its true state. - void reset (unsigned int elementMask) override; + protected: + void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) override; - protected: + void addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool) override; - void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool) override; + void handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type) override; - void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) override; + signals: - void handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) override; + void cellSelectionChanged(const CSMWorld::CellSelection& selection); - signals: + private slots: - void cellSelectionChanged (const CSMWorld::CellSelection& selection); + virtual void cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - private slots: + virtual void cellRemoved(const QModelIndex& parent, int start, int end); - virtual void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + virtual void cellAdded(const QModelIndex& index, int start, int end); - virtual void cellRemoved (const QModelIndex& parent, int start, int end); + virtual void landDataChanged(const QModelIndex& topLeft, const QModelIndex& botomRight); + virtual void landAboutToBeRemoved(const QModelIndex& parent, int start, int end); + virtual void landAdded(const QModelIndex& parent, int start, int end); - virtual void cellAdded (const QModelIndex& index, int start, int end); + virtual void landTextureDataChanged(const QModelIndex& topLeft, const QModelIndex& botomRight); + virtual void landTextureAboutToBeRemoved(const QModelIndex& parent, int start, int end); + virtual void landTextureAdded(const QModelIndex& parent, int start, int end); - virtual void landDataChanged (const QModelIndex& topLeft, const QModelIndex& botomRight); - virtual void landAboutToBeRemoved (const QModelIndex& parent, int start, int end); - virtual void landAdded (const QModelIndex& parent, int start, int end); + void assetTablesChanged(); - virtual void landTextureDataChanged (const QModelIndex& topLeft, const QModelIndex& botomRight); - virtual void landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end); - virtual void landTextureAdded (const QModelIndex& parent, int start, int end); + void loadCameraCell(); - void assetTablesChanged (); + void loadEastCell(); - void loadCameraCell(); + void loadNorthCell(); - void loadEastCell(); - - void loadNorthCell(); - - void loadWestCell(); - - void loadSouthCell(); + void loadWestCell(); + void loadSouthCell(); }; } diff --git a/apps/opencs/view/render/pathgrid.cpp b/apps/opencs/view/render/pathgrid.cpp index 470a3d092..44e85e719 100644 --- a/apps/opencs/view/render/pathgrid.cpp +++ b/apps/opencs/view/render/pathgrid.cpp @@ -1,37 +1,67 @@ #include "pathgrid.hpp" #include +#include +#include +#include +#include #include -#include +#include +#include #include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include -#include "../../model/world/cell.hpp" -#include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" +#include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtree.hpp" +#include "worldspacewidget.hpp" + +namespace osg +{ + class NodeVisitor; +} namespace CSVRender { class PathgridNodeCallback : public osg::NodeCallback { - public: - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - PathgridTag* tag = static_cast(node->getUserData()); - tag->getPathgrid()->update(); - } + public: + void operator()(osg::Node* node, osg::NodeVisitor* nv) override + { + PathgridTag* tag = static_cast(node->getUserData()); + tag->getPathgrid()->update(); + } }; PathgridTag::PathgridTag(Pathgrid* pathgrid) - : TagBase(Mask_Pathgrid), mPathgrid(pathgrid) + : TagBase(Mask_Pathgrid) + , mPathgrid(pathgrid) { } @@ -40,10 +70,13 @@ namespace CSVRender return mPathgrid; } - QString PathgridTag::getToolTip(bool hideBasics) const + QString PathgridTag::getToolTip(bool /*hideBasics*/, const WorldspaceHitResult& hit) const { QString text("Pathgrid: "); text += mPathgrid->getId().c_str(); + text += " ("; + text += QString::number(SceneUtil::getPathgridNode(static_cast(hit.index0))); + text += ")"; return text; } @@ -52,7 +85,7 @@ namespace CSVRender const CSMWorld::CellCoordinates& coordinates) : mData(data) , mPathgridCollection(mData.getPathgrids()) - , mId(pathgridId) + , mId(ESM::RefId::stringRefId(pathgridId)) , mCoords(coordinates) , mInterior(false) , mDragOrigin(0) @@ -66,15 +99,15 @@ namespace CSVRender { const float CoordScalar = ESM::Land::REAL_SIZE; - mBaseNode = new osg::PositionAttitudeTransform (); + mBaseNode = new osg::PositionAttitudeTransform(); mBaseNode->setPosition(osg::Vec3f(mCoords.getX() * CoordScalar, mCoords.getY() * CoordScalar, 0.f)); mBaseNode->setUserData(mTag); mBaseNode->setUpdateCallback(new PathgridNodeCallback()); mBaseNode->setNodeMask(Mask_Pathgrid); mParent->addChild(mBaseNode); - mPathgridGeode = new osg::Geode(); - mBaseNode->addChild(mPathgridGeode); + mPathgridGroup = new osg::Group(); + mBaseNode->addChild(mPathgridGroup); recreateGeometry(); @@ -98,7 +131,7 @@ namespace CSVRender const std::string& Pathgrid::getId() const { - return mId; + return mId.getRefIdString(); } bool Pathgrid::isSelected() const @@ -218,14 +251,15 @@ namespace CSVRender mUseOffset = false; mMoveOffset.set(0, 0, 0); - mPathgridGeode->removeDrawable(mDragGeometry); + mPathgridGroup->removeChild(mDragGeometry); mDragGeometry = nullptr; } void Pathgrid::applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos) { - CSMWorld::IdTree* model = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); - + CSMWorld::IdTree* model + = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); + const std::string& idString = mId.getRefIdString(); const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { @@ -235,23 +269,23 @@ namespace CSVRender int posY = clampToCell(static_cast(localCoords.y())); int posZ = clampToCell(static_cast(localCoords.z())); - int recordIndex = mPathgridCollection.getIndex (mId); + int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); - int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridPosX); + int posXColumn + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosX); - int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridPosY); + int posYColumn + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosY); - int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridPosZ); + int posZColumn + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosZ); QModelIndex parent = model->index(recordIndex, parentColumn); int row = static_cast(source->mPoints.size()); // Add node to end of list - commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::AddNestedCommand(*model, idString, row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), posX)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), posY)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), posZ)); @@ -262,25 +296,25 @@ namespace CSVRender if (index == -1) { // Does not exist - commands.push(new CSMWorld::CreatePathgridCommand(*model, mId)); + commands.push(new CSMWorld::CreatePathgridCommand(*model, idString)); } else { source = &mPathgridCollection.getRecord(index).get(); // Deleted, so revert and remove all data - commands.push(new CSMWorld::RevertCommand(*model, mId)); + commands.push(new CSMWorld::RevertCommand(*model, idString)); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); for (int row = source->mPoints.size() - 1; row >= 0; --row) { - commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::DeleteNestedCommand(*model, idString, row, parentColumn)); } parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); for (int row = source->mEdges.size() - 1; row >= 0; --row) { - commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::DeleteNestedCommand(*model, idString, row, parentColumn)); } } } @@ -302,30 +336,30 @@ namespace CSVRender int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); - int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridPosX); + int posXColumn + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosX); - int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridPosY); + int posYColumn + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosY); - int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridPosZ); + int posZColumn + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosZ); QModelIndex parent = model->index(recordIndex, parentColumn); - for (size_t i = 0; i < mSelected.size(); ++i) + for (const auto& selected : mSelected) { - const CSMWorld::Pathgrid::Point& point = source->mPoints[mSelected[i]]; - int row = static_cast(mSelected[i]); + const CSMWorld::Pathgrid::Point& point = source->mPoints[selected]; + int row = static_cast(selected); - commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), - clampToCell(point.mX + offsetX))); + commands.push(new CSMWorld::ModifyCommand( + *model, model->index(row, posXColumn, parent), clampToCell(point.mX + offsetX))); - commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), - clampToCell(point.mY + offsetY))); + commands.push(new CSMWorld::ModifyCommand( + *model, model->index(row, posYColumn, parent), clampToCell(point.mY + offsetY))); - commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), - clampToCell(point.mZ + offsetZ))); + commands.push(new CSMWorld::ModifyCommand( + *model, model->index(row, posZColumn, parent), clampToCell(point.mZ + offsetZ))); } } } @@ -344,9 +378,9 @@ namespace CSVRender const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { - for (size_t i = 0; i < mSelected.size(); ++i) + for (const auto& selected : mSelected) { - addEdge(commands, *source, node, mSelected[i]); + addEdge(commands, *source, node, selected); } } } @@ -356,7 +390,8 @@ namespace CSVRender const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { - CSMWorld::IdTree* model = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); + CSMWorld::IdTree* model + = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); // Want to remove nodes from end of list first std::sort(mSelected.begin(), mSelected.end(), std::greater()); @@ -366,19 +401,20 @@ namespace CSVRender for (std::vector::iterator row = mSelected.begin(); row != mSelected.end(); ++row) { - commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, static_cast(*row), parentColumn)); + commands.push(new CSMWorld::DeleteNestedCommand( + *model, mId.getRefIdString(), static_cast(*row), parentColumn)); } // Fix/remove edges - std::set > edgeRowsToRemove; + std::set> edgeRowsToRemove; parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); - int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridEdge0); + int edge0Column + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge0); - int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridEdge1); + int edge1Column + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge1); QModelIndex parent = model->index(recordIndex, parentColumn); @@ -388,9 +424,9 @@ namespace CSVRender int adjustment1 = 0; // Determine necessary adjustment - for (std::vector::iterator point = mSelected.begin(); point != mSelected.end(); ++point) + for (const auto point : mSelected) { - if (source->mEdges[edge].mV0 == *point || source->mEdges[edge].mV1 == *point) + if (source->mEdges[edge].mV0 == point || source->mEdges[edge].mV1 == point) { edgeRowsToRemove.insert(static_cast(edge)); @@ -399,32 +435,31 @@ namespace CSVRender break; } - if (source->mEdges[edge].mV0 > *point) + if (source->mEdges[edge].mV0 > point) --adjustment0; - if (source->mEdges[edge].mV1 > *point) + if (source->mEdges[edge].mV1 > point) --adjustment1; } if (adjustment0 != 0) { int adjustedEdge = source->mEdges[edge].mV0 + adjustment0; - commands.push(new CSMWorld::ModifyCommand(*model, model->index(edge, edge0Column, parent), - adjustedEdge)); + commands.push( + new CSMWorld::ModifyCommand(*model, model->index(edge, edge0Column, parent), adjustedEdge)); } if (adjustment1 != 0) { int adjustedEdge = source->mEdges[edge].mV1 + adjustment1; - commands.push(new CSMWorld::ModifyCommand(*model, model->index(edge, edge1Column, parent), - adjustedEdge)); + commands.push( + new CSMWorld::ModifyCommand(*model, model->index(edge, edge1Column, parent), adjustedEdge)); } } - std::set >::iterator row; - for (row = edgeRowsToRemove.begin(); row != edgeRowsToRemove.end(); ++row) + for (const auto row : edgeRowsToRemove) { - commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId.getRefIdString(), row, parentColumn)); } } @@ -437,7 +472,7 @@ namespace CSVRender if (source) { // Want to remove from end of row first - std::set > rowsToRemove; + std::set> rowsToRemove; for (size_t i = 0; i <= mSelected.size(); ++i) { for (size_t j = i + 1; j < mSelected.size(); ++j) @@ -456,13 +491,14 @@ namespace CSVRender } } - CSMWorld::IdTree* model = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); + CSMWorld::IdTree* model + = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); - std::set >::iterator row; + std::set>::iterator row; for (row = rowsToRemove.begin(); row != rowsToRemove.end(); ++row) { - commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId.getRefIdString(), *row, parentColumn)); } } } @@ -520,7 +556,7 @@ namespace CSVRender removePathgridGeometry(); mPathgridGeometry = SceneUtil::createPathgridGeometry(*source); - mPathgridGeode->addDrawable(mPathgridGeometry); + mPathgridGroup->addChild(mPathgridGeometry); createSelectedGeometry(*source); } @@ -549,14 +585,14 @@ namespace CSVRender removeSelectedGeometry(); mSelectedGeometry = SceneUtil::createPathgridSelectedWireframe(source, mSelected); - mPathgridGeode->addDrawable(mSelectedGeometry); + mPathgridGroup->addChild(mSelectedGeometry); } void Pathgrid::removePathgridGeometry() { if (mPathgridGeometry) { - mPathgridGeode->removeDrawable(mPathgridGeometry); + mPathgridGroup->removeChild(mPathgridGeometry); mPathgridGeometry = nullptr; } } @@ -565,7 +601,7 @@ namespace CSVRender { if (mSelectedGeometry) { - mPathgridGeode->removeDrawable(mSelectedGeometry); + mPathgridGroup->removeChild(mSelectedGeometry); mSelectedGeometry = nullptr; } } @@ -573,7 +609,7 @@ namespace CSVRender void Pathgrid::createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid) { if (mDragGeometry) - mPathgridGeode->removeDrawable(mDragGeometry); + mPathgridGroup->removeChild(mDragGeometry); mDragGeometry = new osg::Geometry(); @@ -601,7 +637,7 @@ namespace CSVRender mDragGeometry->addPrimitiveSet(indices); mDragGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - mPathgridGeode->addDrawable(mDragGeometry); + mPathgridGroup->addChild(mDragGeometry); } const CSMWorld::Pathgrid* Pathgrid::getPathgridSource() @@ -626,26 +662,27 @@ namespace CSVRender return -1; } - void Pathgrid::addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, - unsigned short node2) + void Pathgrid::addEdge( + CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2) { - CSMWorld::IdTree* model = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); + CSMWorld::IdTree* model + = &dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); - int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridEdge0); + int edge0Column + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge0); - int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, - CSMWorld::Columns::ColumnId_PathgridEdge1); + int edge1Column + = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge1); QModelIndex parent = model->index(recordIndex, parentColumn); int row = static_cast(source.mEdges.size()); if (edgeExists(source, node1, node2) == -1) { - commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::AddNestedCommand(*model, mId.getRefIdString(), row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node1)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node2)); ++row; @@ -653,7 +690,7 @@ namespace CSVRender if (edgeExists(source, node2, node1) == -1) { - commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::AddNestedCommand(*model, mId.getRefIdString(), row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node2)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node1)); } diff --git a/apps/opencs/view/render/pathgrid.hpp b/apps/opencs/view/render/pathgrid.hpp index 8f5d45a48..4c37a7fa7 100644 --- a/apps/opencs/view/render/pathgrid.hpp +++ b/apps/opencs/view/render/pathgrid.hpp @@ -1,21 +1,22 @@ #ifndef CSV_RENDER_PATHGRID_H #define CSV_RENDER_PATHGRID_H +#include +#include #include #include -#include #include +#include #include "../../model/world/cellcoordinates.hpp" -#include "../../model/world/idcollection.hpp" #include "../../model/world/subcellcollection.hpp" - #include "tagbase.hpp" +#include namespace osg { - class Geode; + class Vec3f; class Geometry; class Group; class PositionAttitudeTransform; @@ -32,105 +33,103 @@ namespace CSVRender { class Pathgrid; + struct WorldspaceHitResult; + class PathgridTag : public TagBase { - public: + public: + PathgridTag(Pathgrid* pathgrid); - PathgridTag (Pathgrid* pathgrid); + Pathgrid* getPathgrid() const; - Pathgrid* getPathgrid () const; + QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override; - QString getToolTip (bool hideBasics) const override; - - private: - - Pathgrid* mPathgrid; + private: + Pathgrid* mPathgrid; }; class Pathgrid { - public: + public: + typedef std::vector NodeList; - typedef std::vector NodeList; + Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, + const CSMWorld::CellCoordinates& coordinates); - Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, - const CSMWorld::CellCoordinates& coordinates); + ~Pathgrid(); - ~Pathgrid(); + const CSMWorld::CellCoordinates& getCoordinates() const; + const std::string& getId() const; - const CSMWorld::CellCoordinates& getCoordinates() const; - const std::string& getId() const; + bool isSelected() const; + const NodeList& getSelected() const; + void selectAll(); + void toggleSelected(unsigned short node); // Adds to end of vector + void invertSelected(); + void clearSelected(); - bool isSelected() const; - const NodeList& getSelected() const; - void selectAll(); - void toggleSelected(unsigned short node); // Adds to end of vector - void invertSelected(); - void clearSelected(); + void moveSelected(const osg::Vec3d& offset); + void setDragOrigin(unsigned short node); + void setDragEndpoint(unsigned short node); + void setDragEndpoint(const osg::Vec3d& pos); - void moveSelected(const osg::Vec3d& offset); - void setDragOrigin(unsigned short node); - void setDragEndpoint(unsigned short node); - void setDragEndpoint(const osg::Vec3d& pos); + void resetIndicators(); - void resetIndicators(); + void applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos); + void applyPosition(CSMWorld::CommandMacro& commands); + void applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2); + void applyEdges(CSMWorld::CommandMacro& commands, unsigned short node); + void applyRemoveNodes(CSMWorld::CommandMacro& commands); + void applyRemoveEdges(CSMWorld::CommandMacro& commands); - void applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos); - void applyPosition(CSMWorld::CommandMacro& commands); - void applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2); - void applyEdges(CSMWorld::CommandMacro& commands, unsigned short node); - void applyRemoveNodes(CSMWorld::CommandMacro& commands); - void applyRemoveEdges(CSMWorld::CommandMacro& commands); + osg::ref_ptr getTag() const; - osg::ref_ptr getTag() const; + void recreateGeometry(); + void removeGeometry(); - void recreateGeometry(); - void removeGeometry(); + void update(); - void update(); + private: + CSMWorld::Data& mData; + CSMWorld::SubCellCollection& mPathgridCollection; + ESM::RefId mId; + CSMWorld::CellCoordinates mCoords; + bool mInterior; - private: + NodeList mSelected; + osg::Vec3d mMoveOffset; + unsigned short mDragOrigin; - CSMWorld::Data& mData; - CSMWorld::SubCellCollection& mPathgridCollection; - std::string mId; - CSMWorld::CellCoordinates mCoords; - bool mInterior; + bool mChangeGeometry; + bool mRemoveGeometry; + bool mUseOffset; - NodeList mSelected; - osg::Vec3d mMoveOffset; - unsigned short mDragOrigin; + osg::Group* mParent; + osg::ref_ptr mBaseNode; + osg::ref_ptr mPathgridGroup; + osg::ref_ptr mPathgridGeometry; + osg::ref_ptr mSelectedGeometry; + osg::ref_ptr mDragGeometry; - bool mChangeGeometry; - bool mRemoveGeometry; - bool mUseOffset; + osg::ref_ptr mTag; - osg::Group* mParent; - osg::ref_ptr mBaseNode; - osg::ref_ptr mPathgridGeode; - osg::ref_ptr mPathgridGeometry; - osg::ref_ptr mSelectedGeometry; - osg::ref_ptr mDragGeometry; + void createGeometry(); + void createSelectedGeometry(); + void createSelectedGeometry(const CSMWorld::Pathgrid& source); + void removePathgridGeometry(); + void removeSelectedGeometry(); - osg::ref_ptr mTag; + void createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid); - void createGeometry(); - void createSelectedGeometry(); - void createSelectedGeometry(const CSMWorld::Pathgrid& source); - void removePathgridGeometry(); - void removeSelectedGeometry(); + const CSMWorld::Pathgrid* getPathgridSource(); - void createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid); + int edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2); + void addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2); + void removeEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2); - const CSMWorld::Pathgrid* getPathgridSource(); - - int edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2); - void addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, - unsigned short node2); - void removeEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, - unsigned short node2); - - int clampToCell(int v); + int clampToCell(int v); }; } diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 193cb664d..5c45e2b31 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -1,13 +1,11 @@ #include "pathgridmode.hpp" -#include -#include +#include #include #include "../../model/prefs/state.hpp" -#include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" #include "../widget/scenetoolbar.hpp" @@ -18,11 +16,28 @@ #include "pathgridselectionmode.hpp" #include "worldspacewidget.hpp" +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +class QPoint; +class QUndoStack; +class QWidget; + namespace CSVRender { PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) - : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, - getTooltip(), parent) + : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), + parent) , mDragMode(DragMode_None) , mFromNode(0) , mSelectionMode(nullptr) @@ -55,24 +70,22 @@ namespace CSVRender { if (mSelectionMode) { - toolbar->removeTool (mSelectionMode); + toolbar->removeTool(mSelectionMode); delete mSelectionMode; mSelectionMode = nullptr; } } - void PathgridMode::primaryOpenPressed(const WorldspaceHitResult& hitResult) - { - } + void PathgridMode::primaryOpenPressed(const WorldspaceHitResult& hitResult) {} void PathgridMode::primaryEditPressed(const WorldspaceHitResult& hitResult) { - if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue() && - dynamic_cast(hitResult.tag.get())) + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue() + && dynamic_cast(hitResult.tag.get())) { primarySelectPressed(hitResult); } - else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos)) + else if (Cell* cell = getWorldspaceWidget().getCell(hitResult.worldPos)) { if (cell->getPathgrid()) { @@ -145,16 +158,16 @@ namespace CSVRender bool PathgridMode::primaryEditStartDrag(const QPoint& pos) { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); if (dynamic_cast(hit.tag.get())) { primarySelectPressed(hit); - selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); } } @@ -169,7 +182,7 @@ namespace CSVRender bool PathgridMode::secondaryEditStartDrag(const QPoint& pos) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); if (hit.tag) { if (PathgridTag* tag = dynamic_cast(hit.tag.get())) @@ -190,14 +203,14 @@ namespace CSVRender { if (mDragMode == DragMode_Move) { - std::vector > selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); - for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + for (std::vector>::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) { osg::Vec3d eye, center, up, offset; - getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, center, up); + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt(eye, center, up); offset = (up * diffY * speedFactor) + (((center - eye) ^ up) * diffX * speedFactor); @@ -207,13 +220,14 @@ namespace CSVRender } else if (mDragMode == DragMode_Edge) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); if (cell && cell->getPathgrid()) { PathgridTag* tag = nullptr; - if (hit.tag && (tag = dynamic_cast(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) + if (hit.tag && (tag = dynamic_cast(hit.tag.get())) + && tag->getPathgrid()->getId() == mEdgeId) { unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); cell->getPathgrid()->setDragEndpoint(node); @@ -222,7 +236,6 @@ namespace CSVRender { cell->getPathgrid()->setDragEndpoint(hit.worldPos); } - } } } @@ -231,8 +244,8 @@ namespace CSVRender { if (mDragMode == DragMode_Move) { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); - for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); + for (std::vector>::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) { diff --git a/apps/opencs/view/render/pathgridmode.hpp b/apps/opencs/view/render/pathgridmode.hpp index cc61dfe9b..39ba5158d 100644 --- a/apps/opencs/view/render/pathgridmode.hpp +++ b/apps/opencs/view/render/pathgridmode.hpp @@ -5,60 +5,65 @@ #include "editmode.hpp" +namespace CSVWidget +{ + class SceneToolbar; +} + namespace CSVRender { class PathgridSelectionMode; + class WorldspaceWidget; + struct WorldspaceHitResult; class PathgridMode : public EditMode { - Q_OBJECT + Q_OBJECT - public: + public: + PathgridMode(WorldspaceWidget* worldspace, QWidget* parent = nullptr); - PathgridMode(WorldspaceWidget* worldspace, QWidget* parent=nullptr); + void activate(CSVWidget::SceneToolbar* toolbar) override; - void activate(CSVWidget::SceneToolbar* toolbar) override; + void deactivate(CSVWidget::SceneToolbar* toolbar) override; - void deactivate(CSVWidget::SceneToolbar* toolbar) override; + void primaryOpenPressed(const WorldspaceHitResult& hit) override; - void primaryOpenPressed(const WorldspaceHitResult& hit) override; + void primaryEditPressed(const WorldspaceHitResult& hit) override; - void primaryEditPressed(const WorldspaceHitResult& hit) override; + void secondaryEditPressed(const WorldspaceHitResult& hit) override; - void secondaryEditPressed(const WorldspaceHitResult& hit) override; + void primarySelectPressed(const WorldspaceHitResult& hit) override; - void primarySelectPressed(const WorldspaceHitResult& hit) override; + void secondarySelectPressed(const WorldspaceHitResult& hit) override; - void secondarySelectPressed(const WorldspaceHitResult& hit) override; + bool primaryEditStartDrag(const QPoint& pos) override; - bool primaryEditStartDrag (const QPoint& pos) override; + bool secondaryEditStartDrag(const QPoint& pos) override; - bool secondaryEditStartDrag (const QPoint& pos) override; + void drag(const QPoint& pos, int diffX, int diffY, double speedFactor) override; - void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; + void dragCompleted(const QPoint& pos) override; - void dragCompleted(const QPoint& pos) override; + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + void dragAborted() override; - /// \note dragAborted will not be called, if the drag is aborted via changing - /// editing mode - void dragAborted() override; + private: + enum DragMode + { + DragMode_None, + DragMode_Move, + DragMode_Edge + }; - private: + DragMode mDragMode; + std::string mLastId, mEdgeId; + unsigned short mFromNode; - enum DragMode - { - DragMode_None, - DragMode_Move, - DragMode_Edge - }; + PathgridSelectionMode* mSelectionMode; - DragMode mDragMode; - std::string mLastId, mEdgeId; - unsigned short mFromNode; - - PathgridSelectionMode* mSelectionMode; - - QString getTooltip(); + QString getTooltip(); }; } diff --git a/apps/opencs/view/render/pathgridselectionmode.cpp b/apps/opencs/view/render/pathgridselectionmode.cpp index db41faf50..1e154197a 100644 --- a/apps/opencs/view/render/pathgridselectionmode.cpp +++ b/apps/opencs/view/render/pathgridselectionmode.cpp @@ -1,14 +1,26 @@ #include "pathgridselectionmode.hpp" -#include #include +#include + +#include + +#include -#include "../../model/world/idtable.hpp" -#include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" -#include "worldspacewidget.hpp" +#include +#include +#include +#include + #include "pathgrid.hpp" +#include "worldspacewidget.hpp" + +namespace CSVWidget +{ + class SceneToolbar; +} namespace CSVRender { @@ -18,8 +30,8 @@ namespace CSVRender mRemoveSelectedNodes = new QAction("Remove selected nodes", this); mRemoveSelectedEdges = new QAction("Remove edges between selected nodes", this); - connect(mRemoveSelectedNodes, SIGNAL(triggered()), this, SLOT(removeSelectedNodes())); - connect(mRemoveSelectedEdges, SIGNAL(triggered()), this, SLOT(removeSelectedEdges())); + connect(mRemoveSelectedNodes, &QAction::triggered, this, &PathgridSelectionMode::removeSelectedNodes); + connect(mRemoveSelectedEdges, &QAction::triggered, this, &PathgridSelectionMode::removeSelectedEdges); } bool PathgridSelectionMode::createContextMenu(QMenu* menu) @@ -37,9 +49,9 @@ namespace CSVRender void PathgridSelectionMode::removeSelectedNodes() { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); - for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + for (std::vector>::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) { @@ -54,9 +66,9 @@ namespace CSVRender void PathgridSelectionMode::removeSelectedEdges() { - std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); - for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + for (std::vector>::iterator it = selection.begin(); it != selection.end(); ++it) { if (PathgridTag* tag = dynamic_cast(it->get())) { diff --git a/apps/opencs/view/render/pathgridselectionmode.hpp b/apps/opencs/view/render/pathgridselectionmode.hpp index 19bfca803..8c13ba495 100644 --- a/apps/opencs/view/render/pathgridselectionmode.hpp +++ b/apps/opencs/view/render/pathgridselectionmode.hpp @@ -3,35 +3,42 @@ #include "selectionmode.hpp" +namespace CSVRender +{ + class WorldspaceWidget; +} + +namespace CSVWidget +{ + class SceneToolbar; +} + namespace CSVRender { class PathgridSelectionMode : public SelectionMode { - Q_OBJECT + Q_OBJECT - public: + public: + PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); - PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + protected: + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + bool createContextMenu(QMenu* menu) override; - protected: + private: + QAction* mRemoveSelectedNodes; + QAction* mRemoveSelectedEdges; - /// Add context menu items to \a menu. - /// - /// \attention menu can be a 0-pointer - /// - /// \return Have there been any menu items to be added (if menu is 0 and there - /// items to be added, the function must return true anyway. - bool createContextMenu(QMenu* menu) override; + private slots: - private: - - QAction* mRemoveSelectedNodes; - QAction* mRemoveSelectedEdges; - - private slots: - - void removeSelectedNodes(); - void removeSelectedEdges(); + void removeSelectedNodes(); + void removeSelectedEdges(); }; } diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index 522534adb..0f9064aaa 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -1,73 +1,75 @@ #include "previewwidget.hpp" +#include +#include +#include +#include +#include + #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" -CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, - const std::string& id, bool referenceable, QWidget *parent) -: SceneWidget (data.getResourceSystem(), parent), mData (data), mObject(data, mRootNode, id, referenceable) +class QWidget; + +CSVRender::PreviewWidget::PreviewWidget( + CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget* parent) + : SceneWidget(data.getResourceSystem(), parent) + , mData(data) + , mObject(data, mRootNode, id, referenceable) { selectNavigationMode("orbit"); - QAbstractItemModel *referenceables = - mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); + QAbstractItemModel* referenceables = mData.getTableModel(CSMWorld::UniversalId::Type_Referenceables); - connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); - connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); + connect(referenceables, &QAbstractItemModel::dataChanged, this, &PreviewWidget::referenceableDataChanged); + connect( + referenceables, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PreviewWidget::referenceableAboutToBeRemoved); - connect (&mData, SIGNAL (assetTablesChanged ()), - this, SLOT (assetTablesChanged ())); + connect(&mData, &CSMWorld::Data::assetTablesChanged, this, &PreviewWidget::assetTablesChanged); setExterior(false); if (!referenceable) { - QAbstractItemModel *references = - mData.getTableModel (CSMWorld::UniversalId::Type_References); + QAbstractItemModel* references = mData.getTableModel(CSMWorld::UniversalId::Type_References); - connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); - connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); + connect(references, &QAbstractItemModel::dataChanged, this, &PreviewWidget::referenceDataChanged); + connect(references, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PreviewWidget::referenceAboutToBeRemoved); } } -void CSVRender::PreviewWidget::referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::PreviewWidget::referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mObject.referenceableDataChanged (topLeft, bottomRight)) + if (mObject.referenceableDataChanged(topLeft, bottomRight)) flagAsModified(); if (mObject.getReferenceId().empty()) { - CSMWorld::IdTable& referenceables = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + CSMWorld::IdTable& referenceables + = dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Referenceables)); - QModelIndex index = referenceables.getModelIndex (mObject.getReferenceableId(), - referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + QModelIndex index = referenceables.getModelIndex( + mObject.getReferenceableId(), referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_Modification)); - if (referenceables.data (index).toInt()==CSMWorld::RecordBase::State_Deleted) + if (referenceables.data(index).toInt() == CSMWorld::RecordBase::State_Deleted) emit closeRequest(); } } -void CSVRender::PreviewWidget::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, - int end) +void CSVRender::PreviewWidget::referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - if (mObject.referenceableAboutToBeRemoved (parent, start, end)) + if (mObject.referenceableAboutToBeRemoved(parent, start, end)) flagAsModified(); if (mObject.getReferenceableId().empty()) return; - CSMWorld::IdTable& referenceables = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + CSMWorld::IdTable& referenceables + = dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_Referenceables)); - QModelIndex index = referenceables.getModelIndex (mObject.getReferenceableId(), 0); + QModelIndex index = referenceables.getModelIndex(mObject.getReferenceableId(), 0); - if (index.row()>=start && index.row()<=end) + if (index.row() >= start && index.row() <= end) { if (mObject.getReferenceId().empty()) { @@ -77,55 +79,53 @@ void CSVRender::PreviewWidget::referenceableAboutToBeRemoved (const QModelIndex& } } -void CSVRender::PreviewWidget::referenceDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::PreviewWidget::referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mObject.referenceDataChanged (topLeft, bottomRight)) + if (mObject.referenceDataChanged(topLeft, bottomRight)) flagAsModified(); if (mObject.getReferenceId().empty()) return; - CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& references + = dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_References)); // check for deleted state { - QModelIndex index = references.getModelIndex (mObject.getReferenceId(), - references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + QModelIndex index = references.getModelIndex( + mObject.getReferenceId(), references.findColumnIndex(CSMWorld::Columns::ColumnId_Modification)); - if (references.data (index).toInt()==CSMWorld::RecordBase::State_Deleted) + if (references.data(index).toInt() == CSMWorld::RecordBase::State_Deleted) { emit closeRequest(); return; } } - int columnIndex = references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); + int columnIndex = references.findColumnIndex(CSMWorld::Columns::ColumnId_ReferenceableId); - QModelIndex index = references.getModelIndex (mObject.getReferenceId(), columnIndex); + QModelIndex index = references.getModelIndex(mObject.getReferenceId(), columnIndex); - if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) - if (index.column()>=topLeft.column() && index.column()<=bottomRight.row()) - emit referenceableIdChanged (mObject.getReferenceableId()); + if (index.row() >= topLeft.row() && index.row() <= bottomRight.row()) + if (index.column() >= topLeft.column() && index.column() <= bottomRight.row()) + emit referenceableIdChanged(mObject.getReferenceableId()); } -void CSVRender::PreviewWidget::referenceAboutToBeRemoved (const QModelIndex& parent, int start, - int end) +void CSVRender::PreviewWidget::referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end) { if (mObject.getReferenceId().empty()) return; - CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& references + = dynamic_cast(*mData.getTableModel(CSMWorld::UniversalId::Type_References)); - QModelIndex index = references.getModelIndex (mObject.getReferenceId(), 0); + QModelIndex index = references.getModelIndex(mObject.getReferenceId(), 0); - if (index.row()>=start && index.row()<=end) + if (index.row() >= start && index.row() <= end) emit closeRequest(); } -void CSVRender::PreviewWidget::assetTablesChanged () +void CSVRender::PreviewWidget::assetTablesChanged() { mObject.reloadAssets(); } diff --git a/apps/opencs/view/render/previewwidget.hpp b/apps/opencs/view/render/previewwidget.hpp index a8d73729a..9f926d464 100644 --- a/apps/opencs/view/render/previewwidget.hpp +++ b/apps/opencs/view/render/previewwidget.hpp @@ -3,14 +3,13 @@ #include "scenewidget.hpp" +#include + #include "object.hpp" class QModelIndex; - -namespace VFS -{ - class Manager; -} +class QObject; +class QWidget; namespace CSMWorld { @@ -21,34 +20,31 @@ namespace CSVRender { class PreviewWidget : public SceneWidget { - Q_OBJECT + Q_OBJECT - CSMWorld::Data& mData; - CSVRender::Object mObject; + CSMWorld::Data& mData; + CSVRender::Object mObject; - public: + public: + PreviewWidget(CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget* parent = nullptr); - PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, - QWidget *parent = nullptr); + signals: - signals: + void closeRequest(); - void closeRequest(); + void referenceableIdChanged(const std::string& id); - void referenceableIdChanged (const std::string& id); + private slots: - private slots: + void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight); + void referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); - - void assetTablesChanged (); + void assetTablesChanged(); }; } diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index dbed1ba97..6961a9d1a 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -1,604 +1,561 @@ #include "scenewidget.hpp" #include +#include #include -#include -#include -#include #include +#include +#include -#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include #include -#include -#include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include #include -#include #include +#include #include #include "../widget/scenetoolmode.hpp" -#include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/state.hpp" +#include "cameracontroller.hpp" #include "lighting.hpp" #include "mask.hpp" -#include "cameracontroller.hpp" namespace CSVRender { -RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) - : QWidget(parent, f) - , mRootNode(nullptr) -{ - - osgViewer::CompositeViewer& viewer = CompositeViewer::get(); - - osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); - //ds->setNumMultiSamples(8); - - osg::ref_ptr traits = new osg::GraphicsContext::Traits; - traits->windowName = ""; - traits->windowDecoration = true; - traits->x = 0; - traits->y = 0; - traits->width = width(); - traits->height = height(); - traits->doubleBuffer = true; - traits->alpha = ds->getMinimumNumAlphaBits(); - traits->stencil = ds->getMinimumNumStencilBits(); - traits->sampleBuffers = ds->getMultiSamples(); - traits->samples = ds->getNumMultiSamples(); - // Doesn't make much sense as we're running on demand updates, and there seems to be a bug with the refresh rate when running multiple QGLWidgets - 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); - layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(window->getGLWidget()); - setLayout(layout); - - mView->getCamera()->setGraphicsContext(window); - mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); - - SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; - lightMgr->setStartLight(1); - lightMgr->setLightingMask(Mask_Lighting); - mRootNode = lightMgr; - - mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); - mView->getCamera()->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - osg::ref_ptr defaultMat (new osg::Material); - defaultMat->setColorMode(osg::Material::OFF); - defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mView->getCamera()->getOrCreateStateSet()->setAttribute(defaultMat); - - mView->setSceneData(mRootNode); - - // Add ability to signal osg to show its statistics for debugging purposes - mView->addEventHandler(new osgViewer::StatsHandler); - - viewer.addView(mView); - viewer.setDone(false); - viewer.realize(); -} - -RenderWidget::~RenderWidget() -{ - try + RenderWidget::RenderWidget(QWidget* parent, Qt::WindowFlags f) + : QWidget(parent, f) + , mRootNode(nullptr) { - CompositeViewer::get().removeView(mView); + mView = new osgViewer::View; + updateCameraParameters(width() / static_cast(height())); -#if OSG_VERSION_LESS_THAN(3,6,5) - // before OSG 3.6.4, the default font was a static object, and if it wasn't attached to the scene when a graphics context was destroyed, it's program wouldn't be released. - // 3.6.4 moved it into the object cache, which meant it usually got released, but not here. - // 3.6.5 improved cleanup with osgViewer::CompositeViewer::removeView so it more reliably released associated state for objects in the object cache. - osg::ref_ptr graphicsContext = mView->getCamera()->getGraphicsContext(); - osgText::Font::getDefaultFont()->releaseGLObjects(graphicsContext->getState()); -#endif + mWidget = new osgQOpenGLWidget(this); + + mRenderer = mWidget->getCompositeViewer(); + osg::ref_ptr window + = new osgViewer::GraphicsWindowEmbedded(0, 0, width(), height()); + mWidget->setGraphicsWindowEmbedded(window); + + int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); + mRenderer->setRunMaxFrameRate(frameRateLimit); + mRenderer->setUseConfigureAffinity(false); + + QLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(mWidget); + setLayout(layout); + + mView->getCamera()->setGraphicsContext(window); + + SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; + lightMgr->setStartLight(1); + lightMgr->setLightingMask(Mask_Lighting); + mRootNode = lightMgr; + + mView->getCamera()->setViewport(new osg::Viewport(0, 0, width(), height())); + + mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + mView->getCamera()->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + osg::ref_ptr defaultMat(new osg::Material); + defaultMat->setColorMode(osg::Material::OFF); + defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); + defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); + defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mView->getCamera()->getOrCreateStateSet()->setAttribute(defaultMat); + + mView->setSceneData(mRootNode); + + // Add ability to signal osg to show its statistics for debugging purposes + mView->addEventHandler(new osgViewer::StatsHandler); + + mRenderer->addView(mView); + mRenderer->setDone(false); } - catch(const std::exception& e) + + RenderWidget::~RenderWidget() { - Log(Debug::Error) << "Error in the destructor: " << e.what(); + try + { + mRenderer->removeView(mView); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Error in the destructor: " << e.what(); + } + delete mWidget; } -} -void RenderWidget::flagAsModified() -{ - mView->requestRedraw(); -} - -void RenderWidget::setVisibilityMask(unsigned int mask) -{ - mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting); -} - -osg::Camera *RenderWidget::getCamera() -{ - return mView->getCamera(); -} - -void RenderWidget::toggleRenderStats() -{ - osgViewer::GraphicsWindow* window = - static_cast(mView->getCamera()->getGraphicsContext()); - - window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S); - window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S); -} - - -// -------------------------------------------------- - -CompositeViewer::CompositeViewer() - : mSimulationTime(0.0) -{ - // TODO: Upgrade osgQt to support osgViewer::ViewerBase::DrawThreadPerContext - // https://gitlab.com/OpenMW/openmw/-/issues/5481 - setThreadingModel(osgViewer::ViewerBase::SingleThreaded); - -#if OSG_VERSION_GREATER_OR_EQUAL(3,5,5) - setUseConfigureAffinity(false); -#endif - - // disable the default setting of viewer.done() by pressing Escape. - setKeyEventSetsDone(0); - - // Only render when the camera position changed, or content flagged dirty - //setRunFrameScheme(osgViewer::ViewerBase::ON_DEMAND); - setRunFrameScheme(osgViewer::ViewerBase::CONTINUOUS); - - connect( &mTimer, SIGNAL(timeout()), this, SLOT(update()) ); - mTimer.start( 10 ); - - int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); - setRunMaxFrameRate(frameRateLimit); -} - -CompositeViewer &CompositeViewer::get() -{ - static CompositeViewer sThis; - return sThis; -} - -void CompositeViewer::update() -{ - double dt = mFrameTimer.time_s(); - mFrameTimer.setStartTick(); - - emit simulationUpdated(dt); - - mSimulationTime += dt; - frame(mSimulationTime); - - double minFrameTime = _runMaxFrameRate > 0.0 ? 1.0 / _runMaxFrameRate : 0.0; - if (dt < minFrameTime) + void RenderWidget::flagAsModified() { - std::this_thread::sleep_for(std::chrono::duration(minFrameTime - dt)); - } -} - -// --------------------------------------------------- - -SceneWidget::SceneWidget(std::shared_ptr resourceSystem, QWidget *parent, Qt::WindowFlags f, - bool retrieveInput) - : RenderWidget(parent, f) - , mResourceSystem(resourceSystem) - , mLighting(nullptr) - , mHasDefaultAmbient(false) - , mIsExterior(true) - , mPrevMouseX(0) - , mPrevMouseY(0) - , mCamPositionSet(false) -{ - mFreeCamControl = new FreeCameraController(this); - mOrbitCamControl = new OrbitCameraController(this); - mCurrentCamControl = mFreeCamControl; - - mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); - - mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() ); - - // set up gradient view or configured clear color - QColor bgColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); - - if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) { - QColor gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); - mGradientCamera = createGradientCamera(bgColour, gradientColour); - - mView->getCamera()->setClearMask(0); - mView->getCamera()->addChild(mGradientCamera.get()); - } - else { - mView->getCamera()->setClearColor(osg::Vec4( - bgColour.redF(), - bgColour.greenF(), - bgColour.blueF(), - 1.0f - )); + mView->requestRedraw(); } - // we handle lighting manually - mView->setLightingMode(osgViewer::View::NO_LIGHT); - - setLighting(&mLightingDay); - - mResourceSystem->getSceneManager()->setParticleSystemMask(Mask_ParticleSystem); - - // Recieve mouse move event even if mouse button is not pressed - setMouseTracking(true); - setFocusPolicy(Qt::ClickFocus); - - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); - - // TODO update this outside of the constructor where virtual methods can be used - if (retrieveInput) + void RenderWidget::setVisibilityMask(unsigned int mask) { - CSMPrefs::get()["3D Scene Input"].update(); - CSMPrefs::get()["Tooltips"].update(); + mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting); } - connect (&CompositeViewer::get(), SIGNAL (simulationUpdated(double)), this, SLOT (update(double))); - - // Shortcuts - CSMPrefs::Shortcut* focusToolbarShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this); - connect(focusToolbarShortcut, SIGNAL(activated()), this, SIGNAL(focusToolbarRequest())); - - CSMPrefs::Shortcut* renderStatsShortcut = new CSMPrefs::Shortcut("scene-render-stats", this); - connect(renderStatsShortcut, SIGNAL(activated()), this, SLOT(toggleRenderStats())); -} - -SceneWidget::~SceneWidget() -{ - // Since we're holding on to the resources past the existence of this graphics context, we'll need to manually release the created objects - mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); -} - - -osg::ref_ptr SceneWidget::createGradientRectangle(QColor bgColour, QColor gradientColour) -{ - osg::ref_ptr geometry = new osg::Geometry; - - osg::ref_ptr vertices = new osg::Vec3Array; - - vertices->push_back(osg::Vec3(0.0f, 0.0f, -1.0f)); - vertices->push_back(osg::Vec3(1.0f, 0.0f, -1.0f)); - vertices->push_back(osg::Vec3(0.0f, 1.0f, -1.0f)); - vertices->push_back(osg::Vec3(1.0f, 1.0f, -1.0f)); - - geometry->setVertexArray(vertices); - - osg::ref_ptr primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); - - // triangle 1 - primitives->push_back (0); - primitives->push_back (1); - primitives->push_back (2); - - // triangle 2 - primitives->push_back (2); - primitives->push_back (1); - primitives->push_back (3); - - geometry->addPrimitiveSet(primitives); - - osg::ref_ptr colours = new osg::Vec4ubArray; - colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); - colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); - colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); - colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); - - geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); - - geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - geometry->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - - return geometry; -} - - -osg::ref_ptr SceneWidget::createGradientCamera(QColor bgColour, QColor gradientColour) -{ - osg::ref_ptr camera = new osg::Camera(); - camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); - camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1.0f, 0, 1.0f)); - camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); - camera->setViewMatrix(osg::Matrix::identity()); - - camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - camera->setAllowEventFocus(false); - - // draw subgraph before main camera view. - camera->setRenderOrder(osg::Camera::PRE_RENDER); - - camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - - osg::ref_ptr gradientQuad = createGradientRectangle(bgColour, gradientColour); - - camera->addChild(gradientQuad); - return camera; -} - - -void SceneWidget::updateGradientCamera(QColor bgColour, QColor gradientColour) -{ - osg::ref_ptr gradientRect = createGradientRectangle(bgColour, gradientColour); - // Replaces previous rectangle - mGradientCamera->setChild(0, gradientRect.get()); -} - -void SceneWidget::setLighting(Lighting *lighting) -{ - if (mLighting) - mLighting->deactivate(); - - mLighting = lighting; - mLighting->activate (mRootNode, mIsExterior); - - osg::Vec4f ambient = mLighting->getAmbientColour(mHasDefaultAmbient ? &mDefaultAmbient : nullptr); - setAmbient(ambient); - - flagAsModified(); -} - -void SceneWidget::setAmbient(const osg::Vec4f& ambient) -{ - osg::ref_ptr stateset = new osg::StateSet; - osg::ref_ptr lightmodel = new osg::LightModel; - lightmodel->setAmbientIntensity(ambient); - stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); - stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON); - stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); - mRootNode->setStateSet(stateset); -} - -void SceneWidget::selectLightingMode (const std::string& mode) -{ - QColor backgroundColour; - QColor gradientColour; - if (mode == "day") + osg::Camera* RenderWidget::getCamera() { - backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); - gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); - setLighting(&mLightingDay); + return mView->getCamera(); } - else if (mode == "night") - { - backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor(); - gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor(); - setLighting(&mLightingNight); - } - else if (mode == "bright") - { - backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor(); - gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor(); - setLighting(&mLightingBright); - } - if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) { - if (mGradientCamera.get() != nullptr) { - // we can go ahead and update since this camera still exists - updateGradientCamera(backgroundColour, gradientColour); - if (!mView->getCamera()->containsNode(mGradientCamera.get())) - { - // need to re-attach the gradient camera - mView->getCamera()->setClearMask(0); - mView->getCamera()->addChild(mGradientCamera.get()); - } - } - else { - // need to create the gradient camera - mGradientCamera = createGradientCamera(backgroundColour, gradientColour); + void RenderWidget::toggleRenderStats() + { + osgViewer::GraphicsWindow* window + = static_cast(mView->getCamera()->getGraphicsContext()); + + window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S); + window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S); + } + + // --------------------------------------------------- + + SceneWidget::SceneWidget(std::shared_ptr resourceSystem, QWidget* parent, + Qt::WindowFlags f, bool retrieveInput) + : RenderWidget(parent, f) + , mResourceSystem(resourceSystem) + , mLighting(nullptr) + , mHasDefaultAmbient(false) + , mIsExterior(true) + , mPrevMouseX(0) + , mPrevMouseY(0) + , mCamPositionSet(false) + { + mFreeCamControl = new FreeCameraController(this); + mOrbitCamControl = new OrbitCameraController(this); + mCurrentCamControl = mFreeCamControl; + + mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); + + mOrbitCamControl->setConstRoll(CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue()); + + // set up gradient view or configured clear color + QColor bgColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); + + if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) + { + QColor gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); + mGradientCamera = createGradientCamera(bgColour, gradientColour); + mView->getCamera()->setClearMask(0); mView->getCamera()->addChild(mGradientCamera.get()); } + else + { + mView->getCamera()->setClearColor(osg::Vec4(bgColour.redF(), bgColour.greenF(), bgColour.blueF(), 1.0f)); + } + + // we handle lighting manually + mView->setLightingMode(osgViewer::View::NO_LIGHT); + + setLighting(&mLightingDay); + + mResourceSystem->getSceneManager()->setParticleSystemMask(Mask_ParticleSystem); + + // Recieve mouse move event even if mouse button is not pressed + setMouseTracking(true); + setFocusPolicy(Qt::ClickFocus); + + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &SceneWidget::settingChanged); + + // TODO update this outside of the constructor where virtual methods can be used + if (retrieveInput) + { + CSMPrefs::get()["3D Scene Input"].update(); + CSMPrefs::get()["Tooltips"].update(); + } + + connect(mRenderer, &CompositeOsgRenderer::simulationUpdated, this, &SceneWidget::update); + + // Shortcuts + CSMPrefs::Shortcut* focusToolbarShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this); + connect( + focusToolbarShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &SceneWidget::focusToolbarRequest); + + CSMPrefs::Shortcut* renderStatsShortcut = new CSMPrefs::Shortcut("scene-render-stats", this); + connect( + renderStatsShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &SceneWidget::toggleRenderStats); } - else { - // Fall back to using the clear color for the camera - mView->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - mView->getCamera()->setClearColor(osg::Vec4( - backgroundColour.redF(), - backgroundColour.greenF(), - backgroundColour.blueF(), - 1.0f - )); - if (mGradientCamera.get() != nullptr && mView->getCamera()->containsNode(mGradientCamera.get())) { - // Remove the child to prevent the gradient from rendering - mView->getCamera()->removeChild(mGradientCamera.get()); + + SceneWidget::~SceneWidget() + { + // Since we're holding on to the resources past the existence of this graphics context, we'll need to manually + // release the created objects + mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); + } + + osg::ref_ptr SceneWidget::createGradientRectangle(QColor bgColour, QColor gradientColour) + { + osg::ref_ptr geometry = new osg::Geometry; + + osg::ref_ptr vertices = new osg::Vec3Array; + + vertices->push_back(osg::Vec3(0.0f, 0.0f, -1.0f)); + vertices->push_back(osg::Vec3(1.0f, 0.0f, -1.0f)); + vertices->push_back(osg::Vec3(0.0f, 1.0f, -1.0f)); + vertices->push_back(osg::Vec3(1.0f, 1.0f, -1.0f)); + + geometry->setVertexArray(vertices); + + osg::ref_ptr primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0); + + // triangle 1 + primitives->push_back(0); + primitives->push_back(1); + primitives->push_back(2); + + // triangle 2 + primitives->push_back(2); + primitives->push_back(1); + primitives->push_back(3); + + geometry->addPrimitiveSet(primitives); + + osg::ref_ptr colours = new osg::Vec4ubArray; + colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); + + geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + return geometry; + } + + osg::ref_ptr SceneWidget::createGradientCamera(QColor bgColour, QColor gradientColour) + { + osg::ref_ptr camera = new osg::Camera(); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1.0f, 0, 1.0f)); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setViewMatrix(osg::Matrix::identity()); + + camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + camera->setAllowEventFocus(false); + + // draw subgraph before main camera view. + camera->setRenderOrder(osg::Camera::PRE_RENDER); + + camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + osg::ref_ptr gradientQuad = createGradientRectangle(bgColour, gradientColour); + + camera->addChild(gradientQuad); + return camera; + } + + void SceneWidget::updateGradientCamera(QColor bgColour, QColor gradientColour) + { + osg::ref_ptr gradientRect = createGradientRectangle(bgColour, gradientColour); + // Replaces previous rectangle + mGradientCamera->setChild(0, gradientRect.get()); + } + + void SceneWidget::setLighting(Lighting* lighting) + { + if (mLighting) + mLighting->deactivate(); + + mLighting = lighting; + mLighting->activate(mRootNode, mIsExterior); + + osg::Vec4f ambient = mLighting->getAmbientColour(mHasDefaultAmbient ? &mDefaultAmbient : nullptr); + setAmbient(ambient); + + flagAsModified(); + } + + void SceneWidget::setAmbient(const osg::Vec4f& ambient) + { + osg::ref_ptr stateset = new osg::StateSet; + osg::ref_ptr lightmodel = new osg::LightModel; + lightmodel->setAmbientIntensity(ambient); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); + stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON); + stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); + mRootNode->setStateSet(stateset); + } + + void SceneWidget::selectLightingMode(const std::string& mode) + { + QColor backgroundColour; + QColor gradientColour; + if (mode == "day") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); + setLighting(&mLightingDay); + } + else if (mode == "night") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor(); + setLighting(&mLightingNight); + } + else if (mode == "bright") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor(); + setLighting(&mLightingBright); + } + if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) + { + if (mGradientCamera.get() != nullptr) + { + // we can go ahead and update since this camera still exists + updateGradientCamera(backgroundColour, gradientColour); + + if (!mView->getCamera()->containsNode(mGradientCamera.get())) + { + // need to re-attach the gradient camera + mView->getCamera()->setClearMask(0); + mView->getCamera()->addChild(mGradientCamera.get()); + } + } + else + { + // need to create the gradient camera + mGradientCamera = createGradientCamera(backgroundColour, gradientColour); + mView->getCamera()->setClearMask(0); + mView->getCamera()->addChild(mGradientCamera.get()); + } + } + else + { + // Fall back to using the clear color for the camera + mView->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + mView->getCamera()->setClearColor( + osg::Vec4(backgroundColour.redF(), backgroundColour.greenF(), backgroundColour.blueF(), 1.0f)); + if (mGradientCamera.get() != nullptr && mView->getCamera()->containsNode(mGradientCamera.get())) + { + // Remove the child to prevent the gradient from rendering + mView->getCamera()->removeChild(mGradientCamera.get()); + } } } -} -CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent) -{ - CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Lighting Mode"); + CSVWidget::SceneToolMode* SceneWidget::makeLightingSelector(CSVWidget::SceneToolbar* parent) + { + CSVWidget::SceneToolMode* tool = new CSVWidget::SceneToolMode(parent, "Lighting Mode"); - /// \todo replace icons - tool->addButton (":scenetoolbar/day", "day", - "Day" - "
  • Cell specific ambient in interiors
  • " - "
  • Low ambient in exteriors
  • " - "
  • Strong directional light source
  • " - "
  • This mode closely resembles day time in-game
"); - tool->addButton (":scenetoolbar/night", "night", - "Night" - "
  • Cell specific ambient in interiors
  • " - "
  • Low ambient in exteriors
  • " - "
  • Weak directional light source
  • " - "
  • This mode closely resembles night time in-game
"); - tool->addButton (":scenetoolbar/bright", "bright", - "Bright" - "
  • Maximum ambient
  • " - "
  • Strong directional light source
"); + /// \todo replace icons + tool->addButton(":scenetoolbar/day", "day", + "Day" + "
  • Cell specific ambient in interiors
  • " + "
  • Low ambient in exteriors
  • " + "
  • Strong directional light source
  • " + "
  • This mode closely resembles day time in-game
"); + tool->addButton(":scenetoolbar/night", "night", + "Night" + "
  • Cell specific ambient in interiors
  • " + "
  • Low ambient in exteriors
  • " + "
  • Weak directional light source
  • " + "
  • This mode closely resembles night time in-game
"); + tool->addButton(":scenetoolbar/bright", "bright", + "Bright" + "
  • Maximum ambient
  • " + "
  • Strong directional light source
"); - connect (tool, SIGNAL (modeChanged (const std::string&)), - this, SLOT (selectLightingMode (const std::string&))); + connect(tool, &CSVWidget::SceneToolMode::modeChanged, this, &SceneWidget::selectLightingMode); - return tool; -} + return tool; + } -void SceneWidget::setDefaultAmbient (const osg::Vec4f& colour) -{ - mDefaultAmbient = colour; - mHasDefaultAmbient = true; + void SceneWidget::setDefaultAmbient(const osg::Vec4f& colour) + { + mDefaultAmbient = colour; + mHasDefaultAmbient = true; - setAmbient(mLighting->getAmbientColour(&mDefaultAmbient)); -} + setAmbient(mLighting->getAmbientColour(&mDefaultAmbient)); + } -void SceneWidget::setExterior (bool isExterior) -{ - mIsExterior = isExterior; -} + void SceneWidget::setExterior(bool isExterior) + { + mIsExterior = isExterior; + } -void SceneWidget::mouseMoveEvent (QMouseEvent *event) -{ - mCurrentCamControl->handleMouseMoveEvent(event->x() - mPrevMouseX, event->y() - mPrevMouseY); + void SceneWidget::mouseMoveEvent(QMouseEvent* event) + { + mCurrentCamControl->handleMouseMoveEvent(event->x() - mPrevMouseX, event->y() - mPrevMouseY); - mPrevMouseX = event->x(); - mPrevMouseY = event->y(); -} + mPrevMouseX = event->x(); + mPrevMouseY = event->y(); + } -void SceneWidget::wheelEvent(QWheelEvent *event) -{ - mCurrentCamControl->handleMouseScrollEvent(event->angleDelta().y()); -} + void SceneWidget::wheelEvent(QWheelEvent* event) + { + mCurrentCamControl->handleMouseScrollEvent(event->angleDelta().y()); + } -void SceneWidget::update(double dt) -{ - if (mCamPositionSet) + void SceneWidget::update(double dt) { - mCurrentCamControl->update(dt); + if (mCamPositionSet) + { + mCurrentCamControl->update(dt); + } + else + { + mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp); + mCamPositionSet = true; + } } - else - { - mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp); - mCamPositionSet = true; - } -} -void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) -{ - if (*setting=="3D Scene Input/p-navi-free-sensitivity") + void SceneWidget::settingChanged(const CSMPrefs::Setting* setting) { - mFreeCamControl->setCameraSensitivity(setting->toDouble()); + if (*setting == "3D Scene Input/p-navi-free-sensitivity") + { + mFreeCamControl->setCameraSensitivity(setting->toDouble()); + } + else if (*setting == "3D Scene Input/p-navi-orbit-sensitivity") + { + mOrbitCamControl->setCameraSensitivity(setting->toDouble()); + } + else if (*setting == "3D Scene Input/p-navi-free-invert") + { + mFreeCamControl->setInverted(setting->isTrue()); + } + else if (*setting == "3D Scene Input/p-navi-orbit-invert") + { + mOrbitCamControl->setInverted(setting->isTrue()); + } + else if (*setting == "3D Scene Input/s-navi-sensitivity") + { + mFreeCamControl->setSecondaryMovementMultiplier(setting->toDouble()); + mOrbitCamControl->setSecondaryMovementMultiplier(setting->toDouble()); + } + else if (*setting == "3D Scene Input/navi-wheel-factor") + { + mFreeCamControl->setWheelMovementMultiplier(setting->toDouble()); + mOrbitCamControl->setWheelMovementMultiplier(setting->toDouble()); + } + else if (*setting == "3D Scene Input/navi-free-lin-speed") + { + mFreeCamControl->setLinearSpeed(setting->toDouble()); + } + else if (*setting == "3D Scene Input/navi-free-rot-speed") + { + mFreeCamControl->setRotationalSpeed(setting->toDouble()); + } + else if (*setting == "3D Scene Input/navi-free-speed-mult") + { + mFreeCamControl->setSpeedMultiplier(setting->toDouble()); + } + else if (*setting == "3D Scene Input/navi-orbit-rot-speed") + { + mOrbitCamControl->setOrbitSpeed(setting->toDouble()); + } + else if (*setting == "3D Scene Input/navi-orbit-speed-mult") + { + mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble()); + } + else if (*setting == "3D Scene Input/navi-orbit-const-roll") + { + mOrbitCamControl->setConstRoll(setting->isTrue()); + } + else if (*setting == "Rendering/framerate-limit") + { + mRenderer->setRunMaxFrameRate(setting->toInt()); + } + else if (*setting == "Rendering/camera-fov" || *setting == "Rendering/camera-ortho" + || *setting == "Rendering/camera-ortho-size") + { + updateCameraParameters(); + } + else if (*setting == "Rendering/scene-day-night-switch-nodes") + { + if (mLighting) + setLighting(mLighting); + } } - else if (*setting=="3D Scene Input/p-navi-orbit-sensitivity") - { - mOrbitCamControl->setCameraSensitivity(setting->toDouble()); - } - else if (*setting=="3D Scene Input/p-navi-free-invert") - { - mFreeCamControl->setInverted(setting->isTrue()); - } - else if (*setting=="3D Scene Input/p-navi-orbit-invert") - { - mOrbitCamControl->setInverted(setting->isTrue()); - } - else if (*setting=="3D Scene Input/s-navi-sensitivity") - { - mFreeCamControl->setSecondaryMovementMultiplier(setting->toDouble()); - mOrbitCamControl->setSecondaryMovementMultiplier(setting->toDouble()); - } - else if (*setting=="3D Scene Input/navi-wheel-factor") - { - mFreeCamControl->setWheelMovementMultiplier(setting->toDouble()); - mOrbitCamControl->setWheelMovementMultiplier(setting->toDouble()); - } - else if (*setting=="3D Scene Input/navi-free-lin-speed") - { - mFreeCamControl->setLinearSpeed(setting->toDouble()); - } - else if (*setting=="3D Scene Input/navi-free-rot-speed") - { - mFreeCamControl->setRotationalSpeed(setting->toDouble()); - } - else if (*setting=="3D Scene Input/navi-free-speed-mult") - { - mFreeCamControl->setSpeedMultiplier(setting->toDouble()); - } - else if (*setting=="3D Scene Input/navi-orbit-rot-speed") - { - mOrbitCamControl->setOrbitSpeed(setting->toDouble()); - } - else if (*setting=="3D Scene Input/navi-orbit-speed-mult") - { - mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble()); - } - else if (*setting=="3D Scene Input/navi-orbit-const-roll") - { - mOrbitCamControl->setConstRoll(setting->isTrue()); - } - else if (*setting=="Rendering/framerate-limit") - { - CompositeViewer::get().setRunMaxFrameRate(setting->toInt()); - } - 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; + 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; + 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); + 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); + } } - 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) -{ - if (mode=="1st") + void SceneWidget::selectNavigationMode(const std::string& mode) { - mCurrentCamControl->setCamera(nullptr); - mCurrentCamControl = mFreeCamControl; - mFreeCamControl->setCamera(getCamera()); - mFreeCamControl->fixUpAxis(CameraController::WorldUp); + if (mode == "1st") + { + mCurrentCamControl->setCamera(nullptr); + mCurrentCamControl = mFreeCamControl; + mFreeCamControl->setCamera(getCamera()); + mFreeCamControl->fixUpAxis(CameraController::WorldUp); + } + else if (mode == "free") + { + mCurrentCamControl->setCamera(nullptr); + mCurrentCamControl = mFreeCamControl; + mFreeCamControl->setCamera(getCamera()); + mFreeCamControl->unfixUpAxis(); + } + else if (mode == "orbit") + { + mCurrentCamControl->setCamera(nullptr); + mCurrentCamControl = mOrbitCamControl; + mOrbitCamControl->setCamera(getCamera()); + mOrbitCamControl->reset(); + } } - else if (mode=="free") - { - mCurrentCamControl->setCamera(nullptr); - mCurrentCamControl = mFreeCamControl; - mFreeCamControl->setCamera(getCamera()); - mFreeCamControl->unfixUpAxis(); - } - else if (mode=="orbit") - { - mCurrentCamControl->setCamera(nullptr); - mCurrentCamControl = mOrbitCamControl; - mOrbitCamControl->setCamera(getCamera()); - mOrbitCamControl->reset(); - } -} } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 922776e9f..a6b052178 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -1,19 +1,28 @@ #ifndef OPENCS_VIEW_SCENEWIDGET_H #define OPENCS_VIEW_SCENEWIDGET_H -#include #include +#include -#include +#include +#include #include +#include + +#include +#include -#include #include +#include "lightingbright.hpp" #include "lightingday.hpp" #include "lightingnight.hpp" -#include "lightingbright.hpp" +class QMouseEvent; +class QWheelEvent; + +class osgQOpenGLWidget; +class CompositeOsgRenderer; namespace Resource { @@ -24,6 +33,12 @@ namespace osg { class Group; class Camera; + class Geometry; +} + +namespace osg +{ + class View; } namespace CSVWidget @@ -46,126 +61,101 @@ namespace CSVRender class RenderWidget : public QWidget { - Q_OBJECT + Q_OBJECT - public: - RenderWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); - virtual ~RenderWidget(); + public: + RenderWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + virtual ~RenderWidget(); - /// Initiates a request to redraw the view - void flagAsModified(); + /// Initiates a request to redraw the view + void flagAsModified(); - void setVisibilityMask(unsigned int mask); + void setVisibilityMask(unsigned int mask); - osg::Camera *getCamera(); + osg::Camera* getCamera(); - protected: + protected: + osgQOpenGLWidget* mWidget; + CompositeOsgRenderer* mRenderer; + osg::ref_ptr mView; + osg::ref_ptr mRootNode; - osg::ref_ptr mView; - osg::ref_ptr mRootNode; + void updateCameraParameters(double overrideAspect = -1.0); - void updateCameraParameters(double overrideAspect = -1.0); + protected slots: - QTimer mTimer; - - protected slots: - - void toggleRenderStats(); + void toggleRenderStats(); }; /// Extension of RenderWidget to support lighting mode selection & toolbar class SceneWidget : public RenderWidget { - Q_OBJECT - public: - SceneWidget(std::shared_ptr resourceSystem, QWidget* parent = nullptr, - Qt::WindowFlags f = Qt::WindowFlags(), bool retrieveInput = true); - virtual ~SceneWidget(); + Q_OBJECT + public: + SceneWidget(std::shared_ptr resourceSystem, QWidget* parent = nullptr, + Qt::WindowFlags f = Qt::WindowFlags(), bool retrieveInput = true); + virtual ~SceneWidget(); - CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); - ///< \attention The created tool is not added to the toolbar (via addTool). Doing that - /// is the responsibility of the calling function. + CSVWidget::SceneToolMode* makeLightingSelector(CSVWidget::SceneToolbar* parent); + ///< \attention The created tool is not added to the toolbar (via addTool). Doing that + /// is the responsibility of the calling function. - void setDefaultAmbient (const osg::Vec4f& colour); - ///< \note The actual ambient colour may differ based on lighting settings. + void setDefaultAmbient(const osg::Vec4f& colour); + ///< \note The actual ambient colour may differ based on lighting settings. - void setExterior (bool isExterior); + void setExterior(bool isExterior); - protected: - void setLighting (Lighting *lighting); - ///< \attention The ownership of \a lighting is not transferred to *this. + protected: + void setLighting(Lighting* lighting); + ///< \attention The ownership of \a lighting is not transferred to *this. - void setAmbient(const osg::Vec4f& ambient); + void setAmbient(const osg::Vec4f& ambient); - void mouseMoveEvent (QMouseEvent *event) override; - void wheelEvent (QWheelEvent *event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void wheelEvent(QWheelEvent* event) override; - osg::ref_ptr createGradientRectangle(QColor bgColour, QColor gradientColour); - osg::ref_ptr createGradientCamera(QColor bgColour, QColor gradientColour); - void updateGradientCamera(QColor bgColour, QColor gradientColour); + osg::ref_ptr createGradientRectangle(QColor bgColour, QColor gradientColour); + osg::ref_ptr createGradientCamera(QColor bgColour, QColor gradientColour); + void updateGradientCamera(QColor bgColour, QColor gradientColour); - std::shared_ptr mResourceSystem; + std::shared_ptr mResourceSystem; - Lighting* mLighting; - - osg::ref_ptr mGradientCamera; - osg::Vec4f mDefaultAmbient; - bool mHasDefaultAmbient; - bool mIsExterior; - LightingDay mLightingDay; - LightingNight mLightingNight; - LightingBright mLightingBright; + Lighting* mLighting; - int mPrevMouseX, mPrevMouseY; - - /// Tells update that camera isn't set - bool mCamPositionSet; + osg::ref_ptr mGradientCamera; + osg::Vec4f mDefaultAmbient; + bool mHasDefaultAmbient; + bool mIsExterior; + LightingDay mLightingDay; + LightingNight mLightingNight; + LightingBright mLightingBright; - FreeCameraController* mFreeCamControl; - OrbitCameraController* mOrbitCamControl; - CameraController* mCurrentCamControl; + int mPrevMouseX, mPrevMouseY; - public slots: - void update(double dt); + /// Tells update that camera isn't set + bool mCamPositionSet; - protected slots: + FreeCameraController* mFreeCamControl; + OrbitCameraController* mOrbitCamControl; + CameraController* mCurrentCamControl; - virtual void settingChanged (const CSMPrefs::Setting *setting); + public slots: + void update(double dt); - void selectNavigationMode (const std::string& mode); + protected slots: - private slots: + virtual void settingChanged(const CSMPrefs::Setting* setting); - void selectLightingMode (const std::string& mode); + void selectNavigationMode(const std::string& mode); - signals: + private slots: - void focusToolbarRequest(); + void selectLightingMode(const std::string& mode); + + signals: + + void focusToolbarRequest(); }; - - - // There are rendering glitches when using multiple Viewer instances, work around using CompositeViewer with multiple views - class CompositeViewer : public QObject, public osgViewer::CompositeViewer - { - Q_OBJECT - public: - CompositeViewer(); - - static CompositeViewer& get(); - - QTimer mTimer; - - private: - osg::Timer mFrameTimer; - double mSimulationTime; - - public slots: - void update(); - - signals: - void simulationUpdated(double dt); - }; - } #endif diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp index e7e7d47b5..1dabaed35 100644 --- a/apps/opencs/view/render/selectionmode.cpp +++ b/apps/opencs/view/render/selectionmode.cpp @@ -1,49 +1,61 @@ #include "selectionmode.hpp" -#include #include +#include + +#include + +#include #include "worldspacewidget.hpp" +namespace CSVWidget +{ + class SceneToolbar; +} + namespace CSVRender { - SelectionMode::SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, - unsigned int interactionMask) + SelectionMode::SelectionMode( + CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, unsigned int interactionMask) : SceneToolMode(parent, "Selection mode") , mWorldspaceWidget(worldspaceWidget) , mInteractionMask(interactionMask) { addButton(":scenetoolbar/selection-mode-cube", "cube-centre", "Centred cube" - "
  • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " - "from the centre of the selection cube outwards.
  • " + "
    • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary " + "select " + "from the centre of the selection cube outwards.
    • " "
    • The selection cube is aligned to the word space axis
    • " "
    • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " - "starting on an instance will have the same effect
    • " + "starting on an instance will have the same effect" "
    "); addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner", "Cube corner to corner" - "
    • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " - "from one corner of the selection cube to the opposite corner
    • " + "
      • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary " + "select " + "from one corner of the selection cube to the opposite corner
      • " "
      • The selection cube is aligned to the word space axis
      • " "
      • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " - "starting on an instance will have the same effect
      • " + "starting on an instance will have the same effect" "
      "); addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere", "Centred sphere" - "
      • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " - "from the centre of the selection sphere outwards
      • " + "
        • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary " + "select " + "from the centre of the selection sphere outwards
        • " "
        • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " - "starting on an instance will have the same effect
        • " + "starting on an instance will have the same effect" "
        "); mSelectAll = new QAction("Select all", this); mDeselectAll = new QAction("Clear selection", this); mInvertSelection = new QAction("Invert selection", this); - connect(mSelectAll, SIGNAL(triggered()), this, SLOT(selectAll())); - connect(mDeselectAll, SIGNAL(triggered()), this, SLOT(clearSelection())); - connect(mInvertSelection, SIGNAL(triggered()), this, SLOT(invertSelection())); + connect(mSelectAll, &QAction::triggered, this, &SelectionMode::selectAll); + connect(mDeselectAll, &QAction::triggered, this, &SelectionMode::clearSelection); + connect(mInvertSelection, &QAction::triggered, this, &SelectionMode::invertSelection); } WorldspaceWidget& SelectionMode::getWorldspaceWidget() @@ -51,7 +63,7 @@ namespace CSVRender return mWorldspaceWidget; } - bool SelectionMode::createContextMenu (QMenu* menu) + bool SelectionMode::createContextMenu(QMenu* menu) { if (menu) { diff --git a/apps/opencs/view/render/selectionmode.hpp b/apps/opencs/view/render/selectionmode.hpp index 95f6de41b..34d186c94 100644 --- a/apps/opencs/view/render/selectionmode.hpp +++ b/apps/opencs/view/render/selectionmode.hpp @@ -3,48 +3,48 @@ #include "../widget/scenetoolmode.hpp" -#include "mask.hpp" - class QAction; +namespace CSVWidget +{ + class SceneToolbar; +} + namespace CSVRender { class WorldspaceWidget; class SelectionMode : public CSVWidget::SceneToolMode { - Q_OBJECT + Q_OBJECT - public: + public: + SelectionMode( + CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, unsigned int interactionMask); - SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, - unsigned int interactionMask); + protected: + WorldspaceWidget& getWorldspaceWidget(); - protected: + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + bool createContextMenu(QMenu* menu) override; - WorldspaceWidget& getWorldspaceWidget(); + private: + WorldspaceWidget& mWorldspaceWidget; + unsigned int mInteractionMask; + QAction* mSelectAll; + QAction* mDeselectAll; + QAction* mInvertSelection; - /// Add context menu items to \a menu. - /// - /// \attention menu can be a 0-pointer - /// - /// \return Have there been any menu items to be added (if menu is 0 and there - /// items to be added, the function must return true anyway. - bool createContextMenu (QMenu* menu) override; + protected slots: - private: - - WorldspaceWidget& mWorldspaceWidget; - unsigned int mInteractionMask; - QAction* mSelectAll; - QAction* mDeselectAll; - QAction* mInvertSelection; - - protected slots: - - virtual void selectAll(); - virtual void clearSelection(); - virtual void invertSelection(); + virtual void selectAll(); + virtual void clearSelection(); + virtual void invertSelection(); }; } diff --git a/apps/opencs/view/render/tagbase.cpp b/apps/opencs/view/render/tagbase.cpp index 3ddd68690..b52a553c9 100644 --- a/apps/opencs/view/render/tagbase.cpp +++ b/apps/opencs/view/render/tagbase.cpp @@ -1,14 +1,18 @@ - #include "tagbase.hpp" -CSVRender::TagBase::TagBase (Mask mask) : mMask (mask) {} +#include + +CSVRender::TagBase::TagBase(Mask mask) + : mMask(mask) +{ +} CSVRender::Mask CSVRender::TagBase::getMask() const { return mMask; } -QString CSVRender::TagBase::getToolTip (bool hideBasics) const +QString CSVRender::TagBase::getToolTip(bool hideBasics, const WorldspaceHitResult& /*hit*/) const { return ""; } diff --git a/apps/opencs/view/render/tagbase.hpp b/apps/opencs/view/render/tagbase.hpp index d1ecd2cfd..a93267b6f 100644 --- a/apps/opencs/view/render/tagbase.hpp +++ b/apps/opencs/view/render/tagbase.hpp @@ -9,18 +9,18 @@ namespace CSVRender { + struct WorldspaceHitResult; + class TagBase : public osg::Referenced { - Mask mMask; + Mask mMask; - public: + public: + TagBase(Mask mask); - TagBase (Mask mask); - - Mask getMask() const; - - virtual QString getToolTip (bool hideBasics) const; + Mask getMask() const; + virtual QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const; }; } diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 0593917e0..bdeb1a095 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -1,26 +1,45 @@ #include "terrainselection.hpp" #include +#include -#include +#include #include +#include #include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include -#include "../../model/world/cellcoordinates.hpp" -#include "../../model/world/columnimp.hpp" -#include "../../model/world/idtable.hpp" +#include #include "cell.hpp" #include "worldspacewidget.hpp" -CSVRender::TerrainSelection::TerrainSelection(osg::Group* parentNode, WorldspaceWidget *worldspaceWidget, TerrainSelectionType type): -mParentNode(parentNode), mWorldspaceWidget (worldspaceWidget), mDraggedOperationFlag(false), mSelectionType(type) +namespace CSMWorld +{ + struct Cell; +} + +CSVRender::TerrainSelection::TerrainSelection( + osg::Group* parentNode, WorldspaceWidget* worldspaceWidget, TerrainSelectionType type) + : mParentNode(parentNode) + , mWorldspaceWidget(worldspaceWidget) + , mSelectionType(type) { mGeometry = new osg::Geometry(); mSelectionNode = new osg::Group(); + mSelectionNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mSelectionNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin"); mSelectionNode->addChild(mGeometry); activate(); @@ -36,31 +55,37 @@ std::vector> CSVRender::TerrainSelection::getTerrainSelectio return mSelection; } -void CSVRender::TerrainSelection::onlySelect(const std::vector> &localPositions) +void CSVRender::TerrainSelection::onlySelect(const std::vector>& localPositions) { mSelection = localPositions; update(); } -void CSVRender::TerrainSelection::addSelect(const std::vector>& localPositions, bool toggleInProgress) +void CSVRender::TerrainSelection::addSelect(const std::vector>& localPositions) { - handleSelection(localPositions, toggleInProgress, SelectionMethod::AddSelect); + handleSelection(localPositions, SelectionMethod::AddSelect); } -void CSVRender::TerrainSelection::removeSelect(const std::vector>& localPositions, bool toggleInProgress) +void CSVRender::TerrainSelection::removeSelect(const std::vector>& localPositions) { - handleSelection(localPositions, toggleInProgress, SelectionMethod::RemoveSelect); + handleSelection(localPositions, SelectionMethod::RemoveSelect); } -void CSVRender::TerrainSelection::toggleSelect(const std::vector>& localPositions, bool toggleInProgress) +void CSVRender::TerrainSelection::toggleSelect(const std::vector>& localPositions) { - handleSelection(localPositions, toggleInProgress, SelectionMethod::ToggleSelect); + handleSelection(localPositions, SelectionMethod::ToggleSelect); +} + +void CSVRender::TerrainSelection::clearTemporarySelection() +{ + mTemporarySelection.clear(); } void CSVRender::TerrainSelection::activate() { - if (!mParentNode->containsNode(mSelectionNode)) mParentNode->addChild(mSelectionNode); + if (!mParentNode->containsNode(mSelectionNode)) + mParentNode->addChild(mSelectionNode); } void CSVRender::TerrainSelection::deactivate() @@ -73,20 +98,23 @@ void CSVRender::TerrainSelection::update() mSelectionNode->removeChild(mGeometry); mGeometry = new osg::Geometry(); - const osg::ref_ptr vertices (new osg::Vec3Array); + const osg::ref_ptr vertices(new osg::Vec3Array); switch (mSelectionType) { - case TerrainSelectionType::Texture : drawTextureSelection(vertices); - break; - case TerrainSelectionType::Shape : drawShapeSelection(vertices); - break; + case TerrainSelectionType::Texture: + drawTextureSelection(vertices); + break; + case TerrainSelectionType::Shape: + drawShapeSelection(vertices); + break; } mGeometry->setVertexArray(vertices); osg::ref_ptr drawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); drawArrays->setCount(vertices->size()); - if (vertices->size() != 0) mGeometry->addPrimitiveSet(drawArrays); + if (vertices->size() != 0) + mGeometry->addPrimitiveSet(drawArrays); mSelectionNode->addChild(mGeometry); } @@ -94,34 +122,28 @@ void CSVRender::TerrainSelection::drawShapeSelection(const osg::ref_ptr &localPos : mSelection) + for (std::pair& localPos : mSelection) { - int x (localPos.first); - int y (localPos.second); + int x(localPos.first); + int y(localPos.second); float xWorldCoord(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x)); float yWorldCoord(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y)); - osg::Vec3f pointXY(xWorldCoord, yWorldCoord, calculateLandHeight(x, y) + 2); + osg::Vec3f pointXY(xWorldCoord, yWorldCoord, calculateLandHeight(x, y)); vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y - 1), calculateLandHeight(x, y - 1) + 2)); + vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y - 1), + calculateLandHeight(x, y - 1))); vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x - 1), yWorldCoord, calculateLandHeight(x - 1, y) + 2)); - - const auto north = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y + 1)); - if (north == mSelection.end()) - { - vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), calculateLandHeight(x, y + 1) + 2)); - } - - const auto east = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x + 1, y)); - if (east == mSelection.end()) - { - vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, calculateLandHeight(x + 1, y) + 2)); - } + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x - 1), yWorldCoord, + calculateLandHeight(x - 1, y))); + vertices->push_back(pointXY); + vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), + calculateLandHeight(x, y + 1))); + vertices->push_back(pointXY); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, + calculateLandHeight(x + 1, y))); } } } @@ -130,14 +152,15 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptr &localPos : mSelection) + for (std::pair& localPos : mSelection) { - int x (localPos.first); - int y (localPos.second); + int x(localPos.first); + int y(localPos.second); // convert texture selection to global vertex coordinates at selection box corners int x1 = x * textureSizeToLandSizeModifier + landHeightsNudge; @@ -150,84 +173,104 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptrpush_back(osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+(i-1), y2)+2)); - vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+i, y2)+2)); + float drawPreviousX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + float drawCurrentX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + vertices->push_back( + osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), + calculateLandHeight(x1 + (i - 1), y2))); + vertices->push_back( + osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), + calculateLandHeight(x1 + i, y2))); } } const auto south = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y - 1)); if (south == mSelection.end()) { - for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) + for (int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) { - float drawPreviousX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + (i - 1) *(ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); - float drawCurrentX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); - vertices->push_back(osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+(i-1), y1)+2)); - vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+i, y1)+2)); + float drawPreviousX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + float drawCurrentX = CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x) + + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + vertices->push_back( + osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), + calculateLandHeight(x1 + (i - 1), y1))); + vertices->push_back(osg::Vec3f(drawCurrentX, + CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1 + i, y1))); } } const auto east = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x + 1, y)); if (east == mSelection.end()) { - for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) + for (int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) { - float drawPreviousY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); - float drawCurrentY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawPreviousY, calculateLandHeight(x2, y1+(i-1))+2)); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawCurrentY, calculateLandHeight(x2, y1+i)+2)); + float drawPreviousY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + float drawCurrentY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), + drawPreviousY, calculateLandHeight(x2, y1 + (i - 1)))); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), + drawCurrentY, calculateLandHeight(x2, y1 + i))); } } const auto west = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x - 1, y)); if (west == mSelection.end()) { - for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) + for (int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) { - float drawPreviousY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); - float drawCurrentY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawPreviousY, calculateLandHeight(x1, y1+(i-1))+2)); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawCurrentY, calculateLandHeight(x1, y1+i)+2)); + float drawPreviousY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + float drawCurrentY = CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y) + + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), + drawPreviousY, calculateLandHeight(x1, y1 + (i - 1)))); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), + drawCurrentY, calculateLandHeight(x1, y1 + i))); } } } } } -void CSVRender::TerrainSelection::handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod) +void CSVRender::TerrainSelection::handleSelection( + const std::vector>& localPositions, SelectionMethod selectionMethod) { - if (toggleInProgress) + for (auto const& localPos : localPositions) { - for (auto const& localPos : localPositions) + const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); + + switch (selectionMethod) { - auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); - mDraggedOperationFlag = true; + case SelectionMethod::OnlySelect: + break; - if (iterTemp == mTemporarySelection.end()) - { - auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); - - switch (selectionMethod) + case SelectionMethod::AddSelect: + if (iter == mSelection.end()) { - case SelectionMethod::AddSelect: - if (iter == mSelection.end()) - { - mSelection.emplace_back(localPos); - } + mSelection.emplace_back(localPos); + } + break; - break; - case SelectionMethod::RemoveSelect: - if (iter != mSelection.end()) - { - mSelection.erase(iter); - } + case SelectionMethod::RemoveSelect: + if (iter != mSelection.end()) + { + mSelection.erase(iter); + } + break; - break; - case SelectionMethod::ToggleSelect: + case SelectionMethod::ToggleSelect: + { + const auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); + if (iterTemp == mTemporarySelection.end()) + { if (iter == mSelection.end()) { mSelection.emplace_back(localPos); @@ -236,58 +279,15 @@ void CSVRender::TerrainSelection::handleSelection(const std::vectorgetDocument(); const CSMWorld::IdCollection& cellCollection = document.getData().getCells(); - return cellCollection.searchId (cellId) == -1; + return cellCollection.searchId(ESM::RefId::stringRefId(cellId)) == -1; } bool CSVRender::TerrainSelection::noLand(const std::string& cellId) { CSMDoc::Document& document = mWorldspaceWidget->getDocument(); const CSMWorld::IdCollection& landCollection = document.getData().getLand(); - return landCollection.searchId (cellId) == -1; + return landCollection.searchId(ESM::RefId::stringRefId(cellId)) == -1; } bool CSVRender::TerrainSelection::noLandLoaded(const std::string& cellId) { CSMDoc::Document& document = mWorldspaceWidget->getDocument(); const CSMWorld::IdCollection& landCollection = document.getData().getLand(); - return !landCollection.getRecord(cellId).get().isDataLoaded(ESM::Land::DATA_VNML); + return !landCollection.getRecord(ESM::RefId::stringRefId(cellId)).get().isDataLoaded(ESM::Land::DATA_VNML); } bool CSVRender::TerrainSelection::isLandLoaded(const std::string& cellId) { - if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) return true; + if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) + return true; return false; } @@ -326,7 +327,7 @@ int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global ver int localX = x - cellX * (ESM::Land::LAND_SIZE - 1); int localY = y - cellY * (ESM::Land::LAND_SIZE - 1); - CSMWorld::CellCoordinates coords (cellX, cellY); + CSMWorld::CellCoordinates coords(cellX, cellY); float landHeight = 0.f; if (CSVRender::Cell* cell = dynamic_cast(mWorldspaceWidget->getCell(coords))) @@ -336,11 +337,13 @@ int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global ver else if (isLandLoaded(CSMWorld::CellCoordinates::generateId(cellX, cellY))) { CSMDoc::Document& document = mWorldspaceWidget->getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); - int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - const CSMWorld::LandHeightsColumn::DataType mPointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - return mPointer[localY*ESM::Land::LAND_SIZE + localX]; + const ESM::Land::LandData* landData = document.getData() + .getLand() + .getRecord(ESM::RefId::stringRefId(cellId)) + .get() + .getLandData(ESM::Land::DATA_VHGT); + return landData->mHeights[localY * ESM::Land::LAND_SIZE + localX]; } return landHeight; diff --git a/apps/opencs/view/render/terrainselection.hpp b/apps/opencs/view/render/terrainselection.hpp index 18622ad13..b451a007b 100644 --- a/apps/opencs/view/render/terrainselection.hpp +++ b/apps/opencs/view/render/terrainselection.hpp @@ -1,24 +1,22 @@ #ifndef CSV_RENDER_TERRAINSELECTION_H #define CSV_RENDER_TERRAINSELECTION_H +#include #include #include -#include +#include #include -#include - -#include -#include "../../model/world/cellcoordinates.hpp" namespace osg { class Group; + class Geometry; + class PositionAttitudeTransform; } namespace CSVRender { - struct WorldspaceHitResult; class WorldspaceWidget; enum class TerrainSelectionType @@ -38,51 +36,50 @@ namespace CSVRender /// \brief Class handling the terrain selection data and rendering class TerrainSelection { - public: + public: + TerrainSelection(osg::Group* parentNode, WorldspaceWidget* worldspaceWidget, TerrainSelectionType type); + ~TerrainSelection(); - TerrainSelection(osg::Group* parentNode, WorldspaceWidget *worldspaceWidget, TerrainSelectionType type); - ~TerrainSelection(); + void onlySelect(const std::vector>& localPositions); + void addSelect(const std::vector>& localPositions); + void removeSelect(const std::vector>& localPositions); + void toggleSelect(const std::vector>& localPositions); + void clearTemporarySelection(); - void onlySelect(const std::vector> &localPositions); - void addSelect(const std::vector>& localPositions, bool toggleInProgress); - void removeSelect(const std::vector>& localPositions, bool toggleInProgress); - void toggleSelect(const std::vector> &localPositions, bool toggleInProgress); + void activate(); + void deactivate(); - void activate(); - void deactivate(); + std::vector> getTerrainSelection() const; - std::vector> getTerrainSelection() const; + void update(); - void update(); + protected: + void drawShapeSelection(const osg::ref_ptr vertices); + void drawTextureSelection(const osg::ref_ptr vertices); - protected: + int calculateLandHeight(int x, int y); - void drawShapeSelection(const osg::ref_ptr vertices); - void drawTextureSelection(const osg::ref_ptr vertices); + private: + void handleSelection(const std::vector>& localPositions, SelectionMethod selectionMethod); - int calculateLandHeight(int x, int y); + bool noCell(const std::string& cellId); - private: + bool noLand(const std::string& cellId); - void handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod); + bool noLandLoaded(const std::string& cellId); - bool noCell(const std::string& cellId); + bool isLandLoaded(const std::string& cellId); - bool noLand(const std::string& cellId); - - bool noLandLoaded(const std::string& cellId); - - bool isLandLoaded(const std::string& cellId); - - osg::Group* mParentNode; - WorldspaceWidget *mWorldspaceWidget; - osg::ref_ptr mBaseNode; - osg::ref_ptr mGeometry; - osg::ref_ptr mSelectionNode; - std::vector> mSelection; // Global terrain selection coordinate in either vertex or texture units - std::vector> mTemporarySelection; // Used during toggle to compare the most recent drag operation - bool mDraggedOperationFlag; //true during drag operation, false when click-operation - TerrainSelectionType mSelectionType; + osg::Group* mParentNode; + WorldspaceWidget* mWorldspaceWidget; + osg::ref_ptr mBaseNode; + osg::ref_ptr mGeometry; + osg::ref_ptr mSelectionNode; + std::vector> + mSelection; // Global terrain selection coordinate in either vertex or texture units + std::vector> + mTemporarySelection; // Used during toggle to compare the most recent drag operation + TerrainSelectionType mSelectionType; }; } diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 050494495..6e1a21681 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -1,50 +1,70 @@ #include "terrainshapemode.hpp" #include -#include -#include +#include #include +#include -#include -#include -#include +#include #include -#include -#include +#include +#include +#include -#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include -#include "../widget/brushshapes.hpp" -#include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolshapebrush.hpp" -#include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" -#include "../../model/world/data.hpp" -#include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" -#include "../../model/world/land.hpp" -#include "../../model/world/tablemimedata.hpp" -#include "../../model/world/universalid.hpp" #include "brushdraw.hpp" #include "commands.hpp" #include "editmode.hpp" -#include "pagedworldspacewidget.hpp" #include "mask.hpp" -#include "tagbase.hpp" +#include "pagedworldspacewidget.hpp" #include "terrainselection.hpp" #include "worldspacewidget.hpp" -CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) -: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain, "Terrain land editing", parent), - mParentNode(parentNode) +class QPoint; +class QWidget; + +namespace CSMWorld +{ + struct Cell; +} + +namespace osg +{ + class Group; +} + +CSVRender::TerrainShapeMode::TerrainShapeMode( + WorldspaceWidget* worldspaceWidget, osg::Group* parentNode, QWidget* parent) + : EditMode( + worldspaceWidget, QIcon{ ":scenetoolbar/editing-terrain-shape" }, Mask_Terrain, "Terrain land editing", parent) + , mParentNode(parentNode) { } @@ -52,32 +72,40 @@ void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar) { if (!mTerrainShapeSelection) { - mTerrainShapeSelection.reset(new TerrainSelection(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Shape)); + mTerrainShapeSelection + = std::make_shared(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Shape); } - if(!mShapeBrushScenetool) + if (!mShapeBrushScenetool) { - mShapeBrushScenetool = new CSVWidget::SceneToolShapeBrush (toolbar, "scenetoolshapebrush", getWorldspaceWidget().getDocument()); - connect(mShapeBrushScenetool, SIGNAL (clicked()), mShapeBrushScenetool, SLOT (activate())); - connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int))); - connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setBrushShape(CSVWidget::BrushShape))); - connect(mShapeBrushScenetool->mShapeBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int))); - connect(mShapeBrushScenetool->mShapeBrushWindow->mToolSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(setShapeEditTool(int))); - connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, SIGNAL(valueChanged(int)), this, SLOT(setShapeEditToolStrength(int))); + mShapeBrushScenetool + = new CSVWidget::SceneToolShapeBrush(toolbar, "scenetoolshapebrush", getWorldspaceWidget().getDocument()); + connect(mShapeBrushScenetool, &CSVWidget::SceneTool::clicked, mShapeBrushScenetool, + &CSVWidget::SceneToolShapeBrush::activate); + connect(mShapeBrushScenetool->mShapeBrushWindow, &CSVWidget::ShapeBrushWindow::passBrushSize, this, + &TerrainShapeMode::setBrushSize); + connect(mShapeBrushScenetool->mShapeBrushWindow, &CSVWidget::ShapeBrushWindow::passBrushShape, this, + &TerrainShapeMode::setBrushShape); + connect(mShapeBrushScenetool->mShapeBrushWindow->mSizeSliders->mBrushSizeSlider, &QSlider::valueChanged, this, + &TerrainShapeMode::setBrushSize); + connect(mShapeBrushScenetool->mShapeBrushWindow->mToolSelector, qOverload(&QComboBox::currentIndexChanged), + this, &TerrainShapeMode::setShapeEditTool); + connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, &QSlider::valueChanged, this, + &TerrainShapeMode::setShapeEditToolStrength); } if (!mBrushDraw) - mBrushDraw.reset(new BrushDraw(mParentNode)); + mBrushDraw = std::make_unique(mParentNode); EditMode::activate(toolbar); - toolbar->addTool (mShapeBrushScenetool); + toolbar->addTool(mShapeBrushScenetool); } void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar) { - if(mShapeBrushScenetool) + if (mShapeBrushScenetool) { - toolbar->removeTool (mShapeBrushScenetool); + toolbar->removeTool(mShapeBrushScenetool); } if (mTerrainShapeSelection) @@ -91,7 +119,7 @@ void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar) EditMode::deactivate(toolbar); } -void CSVRender::TerrainShapeMode::primaryOpenPressed (const WorldspaceHitResult& hit) // Apply changes here +void CSVRender::TerrainShapeMode::primaryOpenPressed(const WorldspaceHitResult& hit) // Apply changes here { } @@ -106,39 +134,31 @@ void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); applyTerrainEditChanges(); } - - if (mDragMode == InteractionType_PrimarySelect) - { - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); - } - - if (mDragMode == InteractionType_SecondarySelect) - { - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); - } } clearTransientEdits(); } void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit) { - if(hit.hit && hit.tag == nullptr) + if (hit.hit && hit.tag == nullptr) { - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, false); + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); + mTerrainShapeSelection->clearTemporarySelection(); } } void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit) { - if(hit.hit && hit.tag == nullptr) + if (hit.hit && hit.tag == nullptr) { - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, false); + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); + mTerrainShapeSelection->clearTemporarySelection(); } } -bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) +bool CSVRender::TerrainShapeMode::primaryEditStartDrag(const QPoint& pos) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimaryEdit; @@ -153,60 +173,64 @@ bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) return true; } -bool CSVRender::TerrainShapeMode::secondaryEditStartDrag (const QPoint& pos) +bool CSVRender::TerrainShapeMode::secondaryEditStartDrag(const QPoint& pos) { return false; } -bool CSVRender::TerrainShapeMode::primarySelectStartDrag (const QPoint& pos) +bool CSVRender::TerrainShapeMode::primarySelectStartDrag(const QPoint& pos) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); - return false; + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); + return true; } -bool CSVRender::TerrainShapeMode::secondarySelectStartDrag (const QPoint& pos) +bool CSVRender::TerrainShapeMode::secondarySelectStartDrag(const QPoint& pos) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_SecondarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); - return false; + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); + return true; } -void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) +void CSVRender::TerrainShapeMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) { if (mDragMode == InteractionType_PrimaryEdit) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mTotalDiffY += diffY; if (mIsEditing) { - if (mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); - else editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + if (mShapeEditTool == ShapeEditTool_Drag) + editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); + else + editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); } } if (mDragMode == InteractionType_PrimarySelect) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == nullptr) + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); } if (mDragMode == InteractionType_SecondarySelect) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == nullptr) + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); } } @@ -217,17 +241,19 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) applyTerrainEditChanges(); clearTransientEdits(); } + if (mDragMode == InteractionType_PrimarySelect || mDragMode == InteractionType_SecondarySelect) + { + mTerrainShapeSelection->clearTemporarySelection(); + } } - void CSVRender::TerrainShapeMode::dragAborted() { - clearTransientEdits(); + clearTransientEdits(); + mDragMode = InteractionType_None; } -void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) -{ -} +void CSVRender::TerrainShapeMode::dragWheel(int diff, double speedFactor) {} void CSVRender::TerrainShapeMode::sortAndLimitAlteredCells() { @@ -240,19 +266,23 @@ void CSVRender::TerrainShapeMode::sortAndLimitAlteredCells() while (!passing) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously { passing = true; - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + for (CSMWorld::CellCoordinates cellCoordinates : mAlteredCells) { limitAlteredHeights(cellCoordinates); } - std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + std::reverse(mAlteredCells.begin(), + mAlteredCells + .end()); // Instead of alphabetical order, this should be fixed to sort cells by cell coordinates + for (CSMWorld::CellCoordinates cellCoordinates : mAlteredCells) { - if (!limitAlteredHeights(cellCoordinates, true)) passing = false; + if (!limitAlteredHeights(cellCoordinates, true)) + passing = false; } ++passes; if (passes > 2) { - Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has failed, edit has been discarded."; + Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has " + "failed, edit has been discarded."; clearTransientEdits(); return; } @@ -264,7 +294,8 @@ void CSVRender::TerrainShapeMode::clearTransientEdits() mTotalDiffY = 0; mIsEditing = false; mAlteredCells.clear(); - if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) paged->resetAllAlteredHeights(); mTerrainShapeSelection->update(); } @@ -272,10 +303,10 @@ void CSVRender::TerrainShapeMode::clearTransientEdits() void CSVRender::TerrainShapeMode::applyTerrainEditChanges() { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); @@ -284,26 +315,30 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() sortAndLimitAlteredCells(); - undoStack.beginMacro ("Edit shape and normal records"); + undoStack.beginMacro("Edit shape and normal records"); // One command at the beginning of the macro for redrawing the terrain-selection grid when undoing the changes. undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget())); - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + for (CSMWorld::CellCoordinates cellCoordinates : mAlteredCells) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); - undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); - const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + undoStack.push(new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); - CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); + CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget()); // Generate land height record - for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + for (int i = 0; i < ESM::Land::LAND_SIZE; ++i) { - for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + for (int j = 0; j < ESM::Land::LAND_SIZE; ++j) { if (paged && paged->getCellAlteredHeight(cellCoordinates, i, j)) - landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); + landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + + *paged->getCellAlteredHeight(cellCoordinates, i, j); else landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; } @@ -312,37 +347,59 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() pushEditToCommand(landShapeNew, document, landTable, cellId); } - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + for (CSMWorld::CellCoordinates cellCoordinates : mAlteredCells) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); - const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), landshapeColumn)).value(); - const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer + = landTable + .data(landTable.getModelIndex( + CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), + landshapeColumn)) + .value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer + = landTable + .data(landTable.getModelIndex( + CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), + landshapeColumn)) + .value(); + const CSMWorld::LandNormalsColumn::DataType landNormalsPointer + = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)) + .value(); CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); // Generate land normals record - for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + for (int i = 0; i < ESM::Land::LAND_SIZE; ++i) { - for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + for (int j = 0; j < ESM::Land::LAND_SIZE; ++j) { osg::Vec3f v1(128, 0, 0); osg::Vec3f v2(0, 128, 0); - if (i < ESM::Land::LAND_SIZE - 1) v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + if (i < ESM::Land::LAND_SIZE - 1) + v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] + - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { - std::string shiftedCellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()); + std::string shiftedCellId + = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()); if (isLandLoaded(shiftedCellId)) - v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] + - landShapePointer[j * ESM::Land::LAND_SIZE + i]; } - if (j < ESM::Land::LAND_SIZE - 1) v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + if (j < ESM::Land::LAND_SIZE - 1) + v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] + - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { - std::string shiftedCellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1); + std::string shiftedCellId + = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1); if (isLandLoaded(shiftedCellId)) - v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] + - landShapePointer[j * ESM::Land::LAND_SIZE + i]; } osg::Vec3f normal = v1 ^ v2; @@ -367,18 +424,22 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() float CSVRender::TerrainShapeMode::calculateBumpShape(float distance, int radius, float height) { float distancePerRadius = distance / radius; - return height - height * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + return height + - height + * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); } void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation) { int r = mBrushSize / 2; - if (r == 0) r = 1; // Prevent division by zero later, which might happen when mBrushSize == 1 + if (r == 0) + r = 1; // Prevent division by zero later, which might happen when mBrushSize == 1 - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { - if (mShapeEditTool == ShapeEditTool_Drag) paged->resetAllAlteredHeights(); + if (mShapeEditTool == ShapeEditTool_Drag) + paged->resetAllAlteredHeights(); } if (mBrushShape == CSVWidget::BrushShape_Point) @@ -387,45 +448,55 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); - if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_Drag) + alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); - float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); - if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + float smoothMultiplier + = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) + smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } - if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + if (mShapeEditTool == ShapeEditTool_Smooth) + smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) + flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } if (mBrushShape == CSVWidget::BrushShape_Square) { - for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + for (int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { - for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + for (int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); - if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_Drag) + alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); - float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); - if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + float smoothMultiplier = static_cast( + CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) + smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } - if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + if (mShapeEditTool == ShapeEditTool_Smooth) + smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) + flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } if (mBrushShape == CSVWidget::BrushShape_Circle) { - for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + for (int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { - for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + for (int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; @@ -433,46 +504,59 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); - float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); + float distance = sqrt(pow(distanceX, 2) + pow(distanceY, 2)); float smoothedByDistance = 0.0f; - if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Drag) + smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength); // Using floating-point radius here to prevent selecting too few vertices. if (distance <= mBrushSize / 2.0f) { - if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance); + if (mShapeEditTool == ShapeEditTool_Drag) + alterHeight(cellCoords, x, y, smoothedByDistance); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, smoothedByDistance); - float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); - if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + float smoothMultiplier = static_cast( + CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) + smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } - if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + if (mShapeEditTool == ShapeEditTool_Smooth) + smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) + flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } } if (mBrushShape == CSVWidget::BrushShape_Custom) { - if(!mCustomBrushShape.empty()) + if (!mCustomBrushShape.empty()) { - for(auto const& value: mCustomBrushShape) + for (auto const& value : mCustomBrushShape) { - std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId( + std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); - if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_Drag) + alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); - float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); - if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + float smoothMultiplier = static_cast( + CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) + smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } - if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + if (mShapeEditTool == ShapeEditTool_Smooth) + smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) + flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } @@ -487,23 +571,24 @@ void CSVRender::TerrainShapeMode::setFlattenToolTargetHeight(const WorldspaceHit int inCellY = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - const CSMWorld::LandHeightsColumn::DataType landShapePointer = - landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); mTargetHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } - -void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool) +void CSVRender::TerrainShapeMode::alterHeight( + const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); if (!(allowLandShapeEditing(cellId, useTool) && (useTool || (isLandLoaded(cellId))))) return; - CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); + CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget()); if (!paged) return; @@ -527,75 +612,96 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c osg::Vec3d distance = eye - mEditingPos; alteredHeight = alteredHeight * (distance.length() / 500); } - if (mShapeEditTool == ShapeEditTool_PaintToRaise) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; - if (mShapeEditTool == ShapeEditTool_PaintToLower) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; - if (mShapeEditTool == ShapeEditTool_Smooth) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + if (mShapeEditTool == ShapeEditTool_PaintToRaise) + alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + if (mShapeEditTool == ShapeEditTool_PaintToLower) + alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; + if (mShapeEditTool == ShapeEditTool_Smooth) + alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; } if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1) - paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); // Change values of cornering cells if ((inCellX == 0 && inCellY == 0) && (useTool || isLandLoaded(cellUpLeftId))) { - if(allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellUpId, useTool)) + if (allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) + && allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(cornerCellCoords); - paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); - } else return; + if (useTool + && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight( + cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); + } + else + return; } else if ((inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownLeftId))) { - if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellDownId, useTool)) + if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) + && allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(cornerCellCoords); + if (useTool + && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight); - } else return; + } + else + return; } else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) && (useTool || isLandLoaded(cellUpRightId))) { - if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellUpId, useTool)) + if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) + && allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(cornerCellCoords); + if (useTool + && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight); - } else return; + } + else + return; } - else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownRightId))) + else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) + && (useTool || isLandLoaded(cellDownRightId))) { - if(allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellDownId, useTool)) + if (allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) + && allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(cornerCellCoords); + if (useTool + && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); - } else return; + } + else + return; } // Change values of edging cells if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId))) { - if(allowLandShapeEditing(cellLeftId, useTool)) + if (allowLandShapeEditing(cellLeftId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(edgeCellCoords); + mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight); } } if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId))) { - if(allowLandShapeEditing(cellUpId, useTool)) + if (allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(edgeCellCoords); + mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight); } @@ -603,41 +709,43 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId))) { - if(allowLandShapeEditing(cellRightId, useTool)) + if (allowLandShapeEditing(cellRightId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(edgeCellCoords); + mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); } } if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId))) { - if(allowLandShapeEditing(cellDownId, useTool)) + if (allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(edgeCellCoords); + mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); } } - } -void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength) +void CSVRender::TerrainShapeMode::smoothHeight( + const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength) { - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); - const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); // ### Variable naming key ### // Variables here hold either the real value, or the altered value of current edit. @@ -657,24 +765,30 @@ void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& float downAlteredHeight = 0.0f; float upHeight = 0.0f; - if(allowLandShapeEditing(cellId)) + if (allowLandShapeEditing(cellId)) { - //Get key values for calculating average, handle cell edges, check for null pointers + // Get key values for calculating average, handle cell edges, check for null pointers if (inCellX == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); - const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2)) - leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); + leftAlteredHeight + = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); } if (inCellY == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); - const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) - upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); + upAlteredHeight + = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); } if (inCellX > 0) { @@ -689,8 +803,9 @@ void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& if (inCellX == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); - const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = - landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { @@ -700,8 +815,9 @@ void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& if (inCellY == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); - const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = - landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { @@ -711,31 +827,37 @@ void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& if (inCellX < ESM::Land::LAND_SIZE - 1) { rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; - if(paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) + if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); } if (inCellY < ESM::Land::LAND_SIZE - 1) { downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; - if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); } - float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + - upAlteredHeight + downAlteredHeight + rightAlteredHeight + leftAlteredHeight) / 4; - if ((thisHeight + thisAlteredHeight) != averageHeight) mAlteredCells.emplace_back(cellCoords); - if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight)) toolStrength = abs(thisHeight + thisAlteredHeight - averageHeight); - if (thisHeight + thisAlteredHeight > averageHeight) alterHeight(cellCoords, inCellX, inCellY, - toolStrength); - if (thisHeight + thisAlteredHeight < averageHeight) alterHeight(cellCoords, inCellX, inCellY, + toolStrength); + float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight + + downAlteredHeight + rightAlteredHeight + leftAlteredHeight) + / 4; + if ((thisHeight + thisAlteredHeight) != averageHeight) + mAlteredCells.emplace_back(cellCoords); + if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight)) + toolStrength = abs(thisHeight + thisAlteredHeight - averageHeight); + if (thisHeight + thisAlteredHeight > averageHeight) + alterHeight(cellCoords, inCellX, inCellY, -toolStrength); + if (thisHeight + thisAlteredHeight < averageHeight) + alterHeight(cellCoords, inCellX, inCellY, +toolStrength); } } } -void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight) +void CSVRender::TerrainShapeMode::flattenHeight( + const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); float thisHeight = 0.0f; @@ -743,33 +865,37 @@ void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { if (!noCell(cellId) && !noLand(cellId)) { - const CSMWorld::LandHeightsColumn::DataType landShapePointer = - landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); - if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } } - if (toolStrength > abs(thisHeight - targetHeight) && toolStrength > 8.0f) toolStrength = - abs(thisHeight - targetHeight); //Cut down excessive changes - if (thisHeight + thisAlteredHeight > targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight - toolStrength); - if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight + toolStrength); + if (toolStrength > abs(thisHeight - targetHeight) && toolStrength > 8.0f) + toolStrength = abs(thisHeight - targetHeight); // Cut down excessive changes + if (thisHeight + thisAlteredHeight > targetHeight) + alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight - toolStrength); + if (thisHeight + thisAlteredHeight < targetHeight) + alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight + toolStrength); } -void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, - float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, float* rightHeight, - float* rightAlteredHeight, float* downHeight, float* downAlteredHeight) +void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, + int inCellY, float* thisHeight, float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, + float* upHeight, float* upAlteredHeight, float* rightHeight, float* rightAlteredHeight, float* downHeight, + float* downAlteredHeight) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); @@ -779,7 +905,7 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); *thisHeight = 0.0f; // real + altered height - *thisAlteredHeight = 0.0f; // only altered height + *thisAlteredHeight = 0.0f; // only altered height *leftHeight = 0.0f; *leftAlteredHeight = 0.0f; *upHeight = 0.0f; @@ -789,60 +915,66 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor *downHeight = 0.0f; *downAlteredHeight = 0.0f; - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { if (!noCell(cellId) && !noLand(cellId)) { - const CSMWorld::LandHeightsColumn::DataType landShapePointer = - landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); - if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) *thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); *thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX] + *thisAlteredHeight; - // Default to the same value as thisHeight, which happens in the case of cell edge where next cell/land is not found, - // which is to prevent unnecessary action at limitHeightChange(). + // Default to the same value as thisHeight, which happens in the case of cell edge where next cell/land is + // not found, which is to prevent unnecessary action at limitHeightChange(). *leftHeight = *thisHeight; *upHeight = *thisHeight; *rightHeight = *thisHeight; *downHeight = *thisHeight; - //If at edge, get values from neighboring cell + // If at edge, get values from neighboring cell if (inCellX == 0) { - if(isLandLoaded(cellLeftId)) + if (isLandLoaded(cellLeftId)) { - const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = - landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer + = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)) + .value(); *leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY)) { - *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); + *leftAlteredHeight + = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); *leftHeight += *leftAlteredHeight; } } } if (inCellY == 0) { - if(isLandLoaded(cellUpId)) + if (isLandLoaded(cellUpId)) { - const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = - landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer + = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)) + .value(); *upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; - if (paged->getCellAlteredHeight(cellCoords.move(0,-1), inCellX, ESM::Land::LAND_SIZE - 2)) + if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) { - *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); + *upAlteredHeight + = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); *upHeight += *upAlteredHeight; } } } if (inCellX == ESM::Land::LAND_SIZE - 1) { - if(isLandLoaded(cellRightId)) + if (isLandLoaded(cellRightId)) { - const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = - landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer + = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)) + .value(); *rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { @@ -853,10 +985,11 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor } if (inCellY == ESM::Land::LAND_SIZE - 1) { - if(isLandLoaded(cellDownId)) + if (isLandLoaded(cellDownId)) { - const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = - landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer + = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)) + .value(); *downHeight = landDownShapePointer[ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { @@ -866,7 +999,7 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor } } - //If not at edge, get values from the same cell + // If not at edge, get values from the same cell if (inCellX != 0) { *leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; @@ -895,18 +1028,18 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); *downHeight += *downAlteredHeight; } - } } } -void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits) +void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, + float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits) { if (limitedAlteredHeightXAxis) { if (limitedAlteredHeightYAxis) { - if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) + if (std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); *steepnessIsWithinLimits = false; @@ -933,7 +1066,8 @@ void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinate bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast (*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); @@ -943,8 +1077,9 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi if (isLandLoaded(cellId)) { - const CSMWorld::LandHeightsColumn::DataType landShapePointer = - landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); float thisHeight = 0.0f; float thisAlteredHeight = 0.0f; @@ -959,58 +1094,70 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi if (!reverseMode) { - for(int inCellY = 0; inCellY < ESM::Land::LAND_SIZE; ++inCellY) + for (int inCellY = 0; inCellY < ESM::Land::LAND_SIZE; ++inCellY) { - for(int inCellX = 0; inCellX < ESM::Land::LAND_SIZE; ++inCellX) + for (int inCellX = 0; inCellX < ESM::Land::LAND_SIZE; ++inCellX) { std::unique_ptr limitedAlteredHeightXAxis(nullptr); std::unique_ptr limitedAlteredHeightYAxis(nullptr); - updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, - &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); + updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, + &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, + &downAlteredHeight); // Check for height limits on x-axis if (leftHeight - thisHeight > limitHeightChange) - limitedAlteredHeightXAxis.reset(new float(leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); + limitedAlteredHeightXAxis = std::make_unique( + leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (leftHeight - thisHeight < -limitHeightChange) - limitedAlteredHeightXAxis.reset(new float(leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); + limitedAlteredHeightXAxis = std::make_unique( + leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Check for height limits on y-axis if (upHeight - thisHeight > limitHeightChange) - limitedAlteredHeightYAxis.reset(new float(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); + limitedAlteredHeightYAxis + = std::make_unique(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (upHeight - thisHeight < -limitHeightChange) - limitedAlteredHeightYAxis.reset(new float(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); + limitedAlteredHeightYAxis + = std::make_unique(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Limit altered height value based on x or y, whichever is the smallest - compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); + compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), + limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); } } } if (reverseMode) { - for(int inCellY = ESM::Land::LAND_SIZE - 1; inCellY >= 0; --inCellY) + for (int inCellY = ESM::Land::LAND_SIZE - 1; inCellY >= 0; --inCellY) { - for(int inCellX = ESM::Land::LAND_SIZE - 1; inCellX >= 0; --inCellX) + for (int inCellX = ESM::Land::LAND_SIZE - 1; inCellX >= 0; --inCellX) { std::unique_ptr limitedAlteredHeightXAxis(nullptr); std::unique_ptr limitedAlteredHeightYAxis(nullptr); - updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, - &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); + updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, + &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, + &downAlteredHeight); // Check for height limits on x-axis if (rightHeight - thisHeight > limitHeightChange) - limitedAlteredHeightXAxis.reset(new float(rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); + limitedAlteredHeightXAxis = std::make_unique( + rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (rightHeight - thisHeight < -limitHeightChange) - limitedAlteredHeightXAxis.reset(new float(rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); + limitedAlteredHeightXAxis = std::make_unique( + rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Check for height limits on y-axis if (downHeight - thisHeight > limitHeightChange) - limitedAlteredHeightYAxis.reset(new float(downHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); + limitedAlteredHeightYAxis = std::make_unique( + downHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (downHeight - thisHeight < -limitHeightChange) - limitedAlteredHeightYAxis.reset(new float(downHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); + limitedAlteredHeightYAxis = std::make_unique( + downHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Limit altered height value based on x or y, whichever is the smallest - compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); + compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), + limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); } } } @@ -1020,7 +1167,8 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi bool CSVRender::TerrainShapeMode::isInCellSelection(int globalSelectionX, int globalSelectionY) { - if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { std::pair vertexCoords = std::make_pair(globalSelectionX, globalSelectionY); std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); @@ -1029,9 +1177,11 @@ bool CSVRender::TerrainShapeMode::isInCellSelection(int globalSelectionX, int gl return false; } -void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int globalSelectionY, std::vector>* selections) +void CSVRender::TerrainShapeMode::handleSelection( + int globalSelectionX, int globalSelectionY, std::vector>* selections) { - if (isInCellSelection(globalSelectionX, globalSelectionY)) selections->emplace_back(globalSelectionX, globalSelectionY); + if (isInCellSelection(globalSelectionX, globalSelectionY)) + selections->emplace_back(globalSelectionX, globalSelectionY); else { int moduloX = globalSelectionX % (ESM::Land::LAND_SIZE - 1); @@ -1045,7 +1195,8 @@ void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int glob /* The northern and eastern edges don't belong to the current cell. - If the corresponding adjacent cell is not loaded, some special handling is necessary to select border vertices. + If the corresponding adjacent cell is not loaded, some special handling is necessary to select border + vertices. */ if (xIsAtCellBorder && yIsAtCellBorder) { @@ -1076,7 +1227,7 @@ void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int glob } } -void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation) +void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode) { int r = mBrushSize / 2; std::vector> selections; @@ -1088,9 +1239,9 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& if (mBrushShape == CSVWidget::BrushShape_Square) { - for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + for (int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { - for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + for (int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { handleSelection(i, j, &selections); } @@ -1099,13 +1250,13 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& if (mBrushShape == CSVWidget::BrushShape_Circle) { - for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + for (int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { - for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + for (int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); - float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); + float distance = sqrt(pow(distanceX, 2) + pow(distanceY, 2)); // Using floating-point radius here to prevent selecting too few vertices. if (distance <= mBrushSize / 2.0f) @@ -1116,11 +1267,12 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& if (mBrushShape == CSVWidget::BrushShape_Custom) { - if(!mCustomBrushShape.empty()) + if (!mCustomBrushShape.empty()) { - for(auto const& value: mCustomBrushShape) + for (auto const& value : mCustomBrushShape) { - std::pair localVertexCoords (vertexCoords.first + value.first, vertexCoords.second + value.second); + std::pair localVertexCoords( + vertexCoords.first + value.first, vertexCoords.second + value.second); handleSelection(localVertexCoords.first, localVertexCoords.second, &selections); } } @@ -1136,71 +1288,74 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& if (selectAction == "Select only") mTerrainShapeSelection->onlySelect(selections); else if (selectAction == "Add to selection") - mTerrainShapeSelection->addSelect(selections, dragOperation); + mTerrainShapeSelection->addSelect(selections); else if (selectAction == "Remove from selection") - mTerrainShapeSelection->removeSelect(selections, dragOperation); + mTerrainShapeSelection->removeSelect(selections); else if (selectAction == "Invert selection") - mTerrainShapeSelection->toggleSelect(selections, dragOperation); + mTerrainShapeSelection->toggleSelect(selections); } -void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, const std::string& cellId) +void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, + CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId) { QVariant changedLand; changedLand.setValue(newLandGrid); - QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); + QModelIndex index( + landTable.getModelIndex(cellId, landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex))); QUndoStack& undoStack = document.getUndoStack(); - undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); + undoStack.push(new CSMWorld::ModifyCommand(landTable, index, changedLand)); } -void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, const std::string& cellId) +void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, + CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId) { QVariant changedLand; changedLand.setValue(newLandGrid); - QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex))); + QModelIndex index( + landTable.getModelIndex(cellId, landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex))); QUndoStack& undoStack = document.getUndoStack(); - undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); + undoStack.push(new CSMWorld::ModifyCommand(landTable, index, changedLand)); } bool CSVRender::TerrainShapeMode::noCell(const std::string& cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); const CSMWorld::IdCollection& cellCollection = document.getData().getCells(); - return cellCollection.searchId (cellId) == -1; + return cellCollection.searchId(ESM::RefId::stringRefId(cellId)) == -1; } bool CSVRender::TerrainShapeMode::noLand(const std::string& cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); const CSMWorld::IdCollection& landCollection = document.getData().getLand(); - return landCollection.searchId (cellId) == -1; + return landCollection.searchId(ESM::RefId::stringRefId(cellId)) == -1; } bool CSVRender::TerrainShapeMode::noLandLoaded(const std::string& cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); const CSMWorld::IdCollection& landCollection = document.getData().getLand(); - return !landCollection.getRecord(cellId).get().isDataLoaded(ESM::Land::DATA_VNML); + return !landCollection.getRecord(ESM::RefId::stringRefId(cellId)).get().isDataLoaded(ESM::Land::DATA_VNML); } bool CSVRender::TerrainShapeMode::isLandLoaded(const std::string& cellId) { - if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) return true; + if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) + return true; return false; } void CSVRender::TerrainShapeMode::createNewLandData(const CSMWorld::CellCoordinates& cellCoords) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); @@ -1222,60 +1377,74 @@ void CSVRender::TerrainShapeMode::createNewLandData(const CSMWorld::CellCoordina float upCellSampleHeight = 0.0f; float downCellSampleHeight = 0.0f; - const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); + const CSMWorld::LandNormalsColumn::DataType landNormalsPointer + = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)) + .value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { if (isLandLoaded(cellLeftId)) { - const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = - landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer + = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)) + .value(); ++averageDivider; - leftCellSampleHeight = landLeftShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; - if(paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2)) - leftCellSampleHeight += *paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2); + leftCellSampleHeight + = landLeftShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; + if (paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2)) + leftCellSampleHeight + += *paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2); } if (isLandLoaded(cellRightId)) { - const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = - landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer + = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)) + .value(); ++averageDivider; rightCellSampleHeight = landRightShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE]; - if(paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2)) + if (paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2)) rightCellSampleHeight += *paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2); } if (isLandLoaded(cellUpId)) { - const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = - landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer + = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)) + .value(); ++averageDivider; - upCellSampleHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE / 2)]; - if(paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1)) - upCellSampleHeight += *paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1); + upCellSampleHeight + = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE / 2)]; + if (paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1)) + upCellSampleHeight + += *paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1); } if (isLandLoaded(cellDownId)) { - const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = - landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer + = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)) + .value(); ++averageDivider; downCellSampleHeight = landDownShapePointer[ESM::Land::LAND_SIZE / 2]; - if(paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0)) + if (paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0)) downCellSampleHeight += *paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0); } } - if (averageDivider > 0) defaultHeight = (leftCellSampleHeight + rightCellSampleHeight + upCellSampleHeight + downCellSampleHeight) / averageDivider; + if (averageDivider > 0) + defaultHeight = (leftCellSampleHeight + rightCellSampleHeight + upCellSampleHeight + downCellSampleHeight) + / averageDivider; - for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + for (int i = 0; i < ESM::Land::LAND_SIZE; ++i) { - for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + for (int j = 0; j < ESM::Land::LAND_SIZE; ++j) { landShapeNew[j * ESM::Land::LAND_SIZE + i] = defaultHeight; landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = 0; @@ -1287,64 +1456,64 @@ void CSVRender::TerrainShapeMode::createNewLandData(const CSMWorld::CellCoordina changedShape.setValue(landShapeNew); QVariant changedNormals; changedNormals.setValue(landNormalsNew); - QModelIndex indexShape(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); - QModelIndex indexNormal(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex))); - document.getUndoStack().push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); - document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexShape, changedShape)); - document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexNormal, changedNormals)); + QModelIndex indexShape( + landTable.getModelIndex(cellId, landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex))); + QModelIndex indexNormal( + landTable.getModelIndex(cellId, landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex))); + document.getUndoStack().push(new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); + document.getUndoStack().push(new CSMWorld::ModifyCommand(landTable, indexShape, changedShape)); + document.getUndoStack().push(new CSMWorld::ModifyCommand(landTable, indexNormal, changedNormals)); } bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellId, bool useTool) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - CSMWorld::IdTree& cellTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTree& cellTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); if (noCell(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist - if (mode=="Discard") + if (mode == "Discard") return false; - if (mode=="Create cell and land, then edit" && useTool) + if (mode == "Create cell and land, then edit" && useTool) { - std::unique_ptr createCommand ( - new CSMWorld::CreateCommand (cellTable, cellId)); - int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); - int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); - createCommand->addNestedValue (parentIndex, index, false); - document.getUndoStack().push (createCommand.release()); + auto createCommand = std::make_unique(cellTable, cellId); + int parentIndex = cellTable.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); + int index = cellTable.findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); + createCommand->addNestedValue(parentIndex, index, false); + document.getUndoStack().push(createCommand.release()); - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); - selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); - paged->setCellSelection (selection); + selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); + paged->setCellSelection(selection); } } } - else if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + else if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); - if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) + if (!selection.has(CSMWorld::CellCoordinates::fromId(cellId).first)) { // target cell exists, but is not shown - std::string mode = - CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); - if (mode=="Discard") + if (mode == "Discard") return false; - if (mode=="Show cell and edit" && useTool) + if (mode == "Show cell and edit" && useTool) { - selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); - paged->setCellSelection (selection); + selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); + paged->setCellSelection(selection); } } } @@ -1354,12 +1523,12 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellI std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist - if (mode=="Discard") + if (mode == "Discard") return false; - if (mode=="Create cell and land, then edit" && useTool) + if (mode == "Create cell and land, then edit" && useTool) { - document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); + document.getUndoStack().push(new CSMWorld::CreateCommand(landTable, cellId)); createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); sortAndLimitAlteredCells(); @@ -1369,10 +1538,10 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellI { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); - if (mode=="Discard") + if (mode == "Discard") return false; - if (mode=="Create cell and land, then edit" && useTool) + if (mode == "Create cell and land, then edit" && useTool) { createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); @@ -1391,8 +1560,8 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellI void CSVRender::TerrainShapeMode::fixEdges(CSMWorld::CellCoordinates cellCoords) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); @@ -1400,42 +1569,55 @@ void CSVRender::TerrainShapeMode::fixEdges(CSMWorld::CellCoordinates cellCoords) std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); - const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landShapePointer + = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) + .value(); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer + = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)) + .value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer + = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)) + .value(); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer + = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)) + .value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer + = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)) + .value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); - for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + for (int i = 0; i < ESM::Land::LAND_SIZE; ++i) { - if (isLandLoaded(cellLeftId) && - landShapePointer[i * ESM::Land::LAND_SIZE] != landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]) - landShapeNew[i * ESM::Land::LAND_SIZE] = landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; - if (isLandLoaded(cellRightId) && - landShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] != landRightShapePointer[i * ESM::Land::LAND_SIZE]) - landShapeNew[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] = landRightShapePointer[i * ESM::Land::LAND_SIZE]; - if (isLandLoaded(cellUpId) && - landShapePointer[i] != landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]) + if (isLandLoaded(cellLeftId) + && landShapePointer[i * ESM::Land::LAND_SIZE] + != landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]) + landShapeNew[i * ESM::Land::LAND_SIZE] + = landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; + if (isLandLoaded(cellRightId) + && landShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] + != landRightShapePointer[i * ESM::Land::LAND_SIZE]) + landShapeNew[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] + = landRightShapePointer[i * ESM::Land::LAND_SIZE]; + if (isLandLoaded(cellUpId) + && landShapePointer[i] != landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]) landShapeNew[i] = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]; - if (isLandLoaded(cellDownId) && - landShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] != landDownShapePointer[i]) + if (isLandLoaded(cellDownId) + && landShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] != landDownShapePointer[i]) landShapeNew[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] = landDownShapePointer[i]; } QVariant changedLand; changedLand.setValue(landShapeNew); - QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); + QModelIndex index( + landTable.getModelIndex(cellId, landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex))); QUndoStack& undoStack = document.getUndoStack(); - undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); + undoStack.push(new CSMWorld::ModifyCommand(landTable, index, changedLand)); } -void CSVRender::TerrainShapeMode::dragMoveEvent (QDragMoveEvent *event) -{ -} +void CSVRender::TerrainShapeMode::dragMoveEvent(QDragMoveEvent* event) {} -void CSVRender::TerrainShapeMode::mouseMoveEvent (QMouseEvent *event) +void CSVRender::TerrainShapeMode::mouseMoveEvent(QMouseEvent* event) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask()); if (hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing)) @@ -1458,7 +1640,7 @@ void CSVRender::TerrainShapeMode::setBrushShape(CSVWidget::BrushShape brushShape { mBrushShape = brushShape; - //Set custom brush shape + // Set custom brush shape if (mBrushShape == CSVWidget::BrushShape_Custom && !mTerrainShapeSelection->getTerrainSelection().empty()) { auto terrainSelection = mTerrainShapeSelection->getTerrainSelection(); @@ -1466,7 +1648,7 @@ void CSVRender::TerrainShapeMode::setBrushShape(CSVWidget::BrushShape brushShape int selectionCenterY = 0; int selectionAmount = 0; - for(auto const& value: terrainSelection) + for (auto const& value : terrainSelection) { selectionCenterX = selectionCenterX + value.first; selectionCenterY = selectionCenterY + value.second; @@ -1480,8 +1662,8 @@ void CSVRender::TerrainShapeMode::setBrushShape(CSVWidget::BrushShape brushShape } mCustomBrushShape.clear(); - std::pair differentialPos {}; - for(auto const& value: terrainSelection) + std::pair differentialPos{}; + for (auto const& value : terrainSelection) { differentialPos.first = value.first - selectionCenterX; differentialPos.second = value.second - selectionCenterY; diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index abc4b8aba..e772621c4 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -3,198 +3,223 @@ #include "editmode.hpp" -#include #include +#include +#include +#include -#include -#include +#include + +#include #ifndef Q_MOC_RUN -#include "../../model/world/data.hpp" -#include "../../model/world/land.hpp" -#include "../../model/doc/document.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/idtable.hpp" -#include "../../model/world/landtexture.hpp" +#include "../../model/world/columnimp.hpp" #include "../widget/brushshapes.hpp" #endif #include "brushdraw.hpp" -#include "terrainselection.hpp" + +class QDragMoveEvent; +class QMouseEvent; +class QObject; +class QPoint; +class QWidget; + +namespace osg +{ + class Group; +} + +namespace CSMDoc +{ + class Document; +} namespace CSVWidget { class SceneToolShapeBrush; } +namespace CSMWorld +{ + class IdTable; +} + namespace CSVRender { class PagedWorldspaceWidget; + class TerrainSelection; + class WorldspaceWidget; + struct WorldspaceHitResult; + class SceneToolbar; /// \brief EditMode for handling the terrain shape editing class TerrainShapeMode : public EditMode { Q_OBJECT - public: + public: + enum InteractionType + { + InteractionType_PrimaryEdit, + InteractionType_PrimarySelect, + InteractionType_SecondaryEdit, + InteractionType_SecondarySelect, + InteractionType_None + }; - enum InteractionType - { - InteractionType_PrimaryEdit, - InteractionType_PrimarySelect, - InteractionType_SecondaryEdit, - InteractionType_SecondarySelect, - InteractionType_None - }; + enum ShapeEditTool + { + ShapeEditTool_Drag = 0, + ShapeEditTool_PaintToRaise = 1, + ShapeEditTool_PaintToLower = 2, + ShapeEditTool_Smooth = 3, + ShapeEditTool_Flatten = 4 + }; - enum ShapeEditTool - { - ShapeEditTool_Drag = 0, - ShapeEditTool_PaintToRaise = 1, - ShapeEditTool_PaintToLower = 2, - ShapeEditTool_Smooth = 3, - ShapeEditTool_Flatten = 4 - }; + /// Editmode for terrain shape grid + TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); - /// Editmode for terrain shape grid - TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); + void primaryOpenPressed(const WorldspaceHitResult& hit) override; - void primaryOpenPressed (const WorldspaceHitResult& hit) override; + /// Create single command for one-click shape editing + void primaryEditPressed(const WorldspaceHitResult& hit) override; - /// Create single command for one-click shape editing - void primaryEditPressed (const WorldspaceHitResult& hit) override; + /// Open brush settings window + void primarySelectPressed(const WorldspaceHitResult&) override; - /// Open brush settings window - void primarySelectPressed(const WorldspaceHitResult&) override; + void secondarySelectPressed(const WorldspaceHitResult&) override; - void secondarySelectPressed(const WorldspaceHitResult&) override; + void activate(CSVWidget::SceneToolbar*) override; + void deactivate(CSVWidget::SceneToolbar*) override; - void activate(CSVWidget::SceneToolbar*) override; - void deactivate(CSVWidget::SceneToolbar*) override; + /// Start shape editing command macro + bool primaryEditStartDrag(const QPoint& pos) override; - /// Start shape editing command macro - bool primaryEditStartDrag (const QPoint& pos) override; + bool secondaryEditStartDrag(const QPoint& pos) override; + bool primarySelectStartDrag(const QPoint& pos) override; + bool secondarySelectStartDrag(const QPoint& pos) override; - bool secondaryEditStartDrag (const QPoint& pos) override; - bool primarySelectStartDrag (const QPoint& pos) override; - bool secondarySelectStartDrag (const QPoint& pos) override; + /// Handle shape edit behavior during dragging + void drag(const QPoint& pos, int diffX, int diffY, double speedFactor) override; - /// Handle shape edit behavior during dragging - void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; + /// End shape editing command macro + void dragCompleted(const QPoint& pos) override; - /// End shape editing command macro - void dragCompleted(const QPoint& pos) override; + /// Cancel shape editing, and reset all pending changes + void dragAborted() override; - /// Cancel shape editing, and reset all pending changes - void dragAborted() override; + void dragWheel(int diff, double speedFactor) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; - void dragWheel (int diff, double speedFactor) override; - void dragMoveEvent (QDragMoveEvent *event) override; - void mouseMoveEvent (QMouseEvent *event) override; + std::shared_ptr getTerrainSelection(); - std::shared_ptr getTerrainSelection(); + private: + /// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse + void sortAndLimitAlteredCells(); - private: + /// Reset everything in the current edit + void clearTransientEdits(); - /// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse - void sortAndLimitAlteredCells(); + /// Move pending alteredHeights changes to omwgame/omwaddon -data + void applyTerrainEditChanges(); - /// Reset everything in the current edit - void clearTransientEdits(); + /// Handle brush mechanics for shape editing + void editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation); - /// Move pending alteredHeights changes to omwgame/omwaddon -data - void applyTerrainEditChanges(); + /// Calculate height, when aiming for bump-shaped terrain change + float calculateBumpShape(float distance, int radius, float height); - /// Handle brush mechanics for shape editing - void editTerrainShapeGrid (const std::pair& vertexCoords, bool dragOperation); + /// set the target height for flatten tool + void setFlattenToolTargetHeight(const WorldspaceHitResult& hit); - /// Calculate height, when aiming for bump-shaped terrain change - float calculateBumpShape(float distance, int radius, float height); + /// Do a single height alteration for transient shape edit map + void alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, + bool useTool = true); - /// set the target height for flatten tool - void setFlattenToolTargetHeight(const WorldspaceHitResult& hit); + /// Do a single smoothing height alteration for transient shape edit map + void smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength); - /// Do a single height alteration for transient shape edit map - void alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool = true); + /// Do a single flattening height alteration for transient shape edit map + void flattenHeight( + const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight); - /// Do a single smoothing height alteration for transient shape edit map - void smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength); + /// Get altered height values around one vertex + void updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, + float* thisHeight, float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, + float* upAlteredHeight, float* rightHeight, float* rightAlteredHeight, float* downHeight, + float* downAlteredHeight); - /// Do a single flattening height alteration for transient shape edit map - void flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight); + /// Limit steepness based on either X or Y and return false if steepness is limited + void compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, + float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits); - /// Get altered height values around one vertex - void updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, - float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, - float* rightHeight, float* rightAlteredHeight, float* downHeight, float* downAlteredHeight); + /// Check that the edit doesn't break save format limits, fix if necessary, return true if slope steepness is + /// within limits + bool limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode = false); - ///Limit steepness based on either X or Y and return false if steepness is limited - void compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, - float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits); + /// Check if global selection coordinate belongs to cell in view + bool isInCellSelection(int globalSelectionX, int globalSelectionY); - /// Check that the edit doesn't break save format limits, fix if necessary, return true if slope steepness is within limits - bool limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode = false); + /// Select vertex at global selection coordinate + void handleSelection(int globalSelectionX, int globalSelectionY, std::vector>* selections); - /// Check if global selection coordinate belongs to cell in view - bool isInCellSelection(int globalSelectionX, int globalSelectionY); + /// Handle brush mechanics for terrain shape selection + void selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode); - /// Select vertex at global selection coordinate - void handleSelection(int globalSelectionX, int globalSelectionY, std::vector>* selections); + /// Push terrain shape edits to command macro + void pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId); - /// Handle brush mechanics for terrain shape selection - void selectTerrainShapes (const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation); + /// Push land normals edits to command macro + void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, + CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId); - /// Push terrain shape edits to command macro - void pushEditToCommand (const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, const std::string& cellId); + bool noCell(const std::string& cellId); - /// Push land normals edits to command macro - void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, const std::string& cellId); + bool noLand(const std::string& cellId); - bool noCell(const std::string& cellId); + bool noLandLoaded(const std::string& cellId); - bool noLand(const std::string& cellId); + bool isLandLoaded(const std::string& cellId); - bool noLandLoaded(const std::string& cellId); + /// Create new blank height record and new normals, if there are valid adjancent cell, take sample points and + /// set the average height based on that + void createNewLandData(const CSMWorld::CellCoordinates& cellCoords); - bool isLandLoaded(const std::string& cellId); + /// Create new cell and land if needed, only user tools may ask for opening new cells (useTool == false is for + /// automated land changes) + bool allowLandShapeEditing(const std::string& textureFileName, bool useTool = true); - /// Create new blank height record and new normals, if there are valid adjancent cell, take sample points and set the average height based on that - void createNewLandData(const CSMWorld::CellCoordinates& cellCoords); + /// Bind the edging vertice to the values of the adjancent cells + void fixEdges(CSMWorld::CellCoordinates cellCoords); - /// Create new cell and land if needed, only user tools may ask for opening new cells (useTool == false is for automated land changes) - bool allowLandShapeEditing(const std::string& textureFileName, bool useTool = true); + std::string mBrushTexture; + int mBrushSize = 1; + CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; + std::unique_ptr mBrushDraw; + std::vector> mCustomBrushShape; + CSVWidget::SceneToolShapeBrush* mShapeBrushScenetool = nullptr; + int mDragMode = InteractionType_None; + osg::Group* mParentNode; + bool mIsEditing = false; + std::shared_ptr mTerrainShapeSelection; + int mTotalDiffY = 0; + std::vector mAlteredCells; + osg::Vec3d mEditingPos; + int mShapeEditTool = ShapeEditTool_Drag; + int mShapeEditToolStrength = 8; + int mTargetHeight = 0; - /// Bind the edging vertice to the values of the adjancent cells - void fixEdges(CSMWorld::CellCoordinates cellCoords); + PagedWorldspaceWidget& getPagedWorldspaceWidget(); - std::string mBrushTexture; - int mBrushSize = 1; - CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; - std::unique_ptr mBrushDraw; - std::vector> mCustomBrushShape; - CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool = nullptr; - int mDragMode = InteractionType_None; - osg::Group* mParentNode; - bool mIsEditing = false; - std::shared_ptr mTerrainShapeSelection; - int mTotalDiffY = 0; - std::vector mAlteredCells; - osg::Vec3d mEditingPos; - int mShapeEditTool = ShapeEditTool_Drag; - int mShapeEditToolStrength = 8; - int mTargetHeight = 0; - - PagedWorldspaceWidget& getPagedWorldspaceWidget(); - - public slots: - void setBrushSize(int brushSize); - void setBrushShape(CSVWidget::BrushShape brushShape); - void setShapeEditTool(int shapeEditTool); - void setShapeEditToolStrength(int shapeEditToolStrength); + public slots: + void setBrushSize(int brushSize); + void setBrushShape(CSVWidget::BrushShape brushShape); + void setShapeEditTool(int shapeEditTool); + void setShapeEditToolStrength(int shapeEditToolStrength); }; } - #endif diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index d9cc3015e..725b66364 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -1,34 +1,51 @@ #include "terrainstorage.hpp" -#include "../../model/world/land.hpp" -#include "../../model/world/landtexture.hpp" +#include +#include +#include -#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace CSVRender { - TerrainStorage::TerrainStorage(const CSMWorld::Data &data) + TerrainStorage::TerrainStorage(const CSMWorld::Data& data) : ESMTerrain::Storage(data.getResourceSystem()->getVFS()) , mData(data) { resetHeights(); } - osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) + osg::ref_ptr TerrainStorage::getLand(ESM::ExteriorCellLocation cellLocation) { // The cell isn't guaranteed to have Land. This is because the terrain implementation // has to wrap the vertices of the last row and column to the next cell, which may be a nonexisting cell - int index = mData.getLand().searchId(CSMWorld::Land::createUniqueRecordId(cellX, cellY)); + const int index = mData.getLand().searchId( + ESM::RefId::stringRefId(CSMWorld::Land::createUniqueRecordId(cellLocation.mX, cellLocation.mY))); if (index == -1) return nullptr; const ESM::Land& land = mData.getLand().getRecord(index).get(); - return new ESMTerrain::LandObject(&land, ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX); + return new ESMTerrain::LandObject( + &land, ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX); } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { - int row = mData.getLandTextures().searchId(CSMWorld::LandTexture::createUniqueRecordId(plugin, index)); + const int row = mData.getLandTextures().searchId( + ESM::RefId::stringRefId(CSMWorld::LandTexture::createUniqueRecordId(plugin, index))); if (row == -1) return nullptr; @@ -37,7 +54,8 @@ namespace CSVRender void TerrainStorage::setAlteredHeight(int inCellX, int inCellY, float height) { - mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] = height - fmod(height, 8); //Limit to divisible by 8 to avoid cell seam breakage + mAlteredHeight[inCellY * ESM::Land::LAND_SIZE + inCellX] + = height - fmod(height, 8); // Limit to divisible by 8 to avoid cell seam breakage } void TerrainStorage::resetHeights() @@ -48,96 +66,99 @@ namespace CSVRender float TerrainStorage::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) { float height = 0.f; - osg::ref_ptr land = getLand (cellX, cellY); - if (land) - { - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) height = getVertexHeight(data, inCellX, inCellY); - } - else return height; - return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height; + const int index + = mData.getLand().searchId(ESM::RefId::stringRefId(CSMWorld::Land::createUniqueRecordId(cellX, cellY))); + if (index == -1) // no land! + return height; + + const ESM::Land::LandData* landData = mData.getLand().getRecord(index).get().getLandData(ESM::Land::DATA_VHGT); + height = landData->mHeights[inCellY * ESM::Land::LAND_SIZE + inCellX]; + return mAlteredHeight[inCellY * ESM::Land::LAND_SIZE + inCellX] + height; } float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY) { - return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX]; + return &mAlteredHeight[inCellY * ESM::Land::LAND_SIZE + inCellX]; } - void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) + void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) { // not needed at the moment - this returns the bounds of the whole world, but we only edit individual cells throw std::runtime_error("getBounds not implemented"); } - int TerrainStorage::getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getThisHeight(int col, int row, std::span heightData) const { - return heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]; + return heightData[col * ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col * ESM::Land::LAND_SIZE + row)]; } - int TerrainStorage::getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getLeftHeight(int col, int row, std::span heightData) const { - return heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + - mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)]; + return heightData[(col)*ESM::Land::LAND_SIZE + row - 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)]; } - int TerrainStorage::getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getRightHeight(int col, int row, std::span heightData) const { - return heightData->mHeights[col*ESM::Land::LAND_SIZE + row + 1] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row + 1)]; + return heightData[col * ESM::Land::LAND_SIZE + row + 1] + + mAlteredHeight[static_cast(col * ESM::Land::LAND_SIZE + row + 1)]; } - int TerrainStorage::getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getUpHeight(int col, int row, std::span heightData) const { - return heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)]; + return heightData[(col - 1) * ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col - 1) * ESM::Land::LAND_SIZE + row)]; } - int TerrainStorage::getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getDownHeight(int col, int row, std::span heightData) const { - return heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)]; + return heightData[(col + 1) * ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col + 1) * ESM::Land::LAND_SIZE + row)]; } - int TerrainStorage::getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getHeightDifferenceToLeft(int col, int row, std::span heightData) const { return abs(getThisHeight(col, row, heightData) - getLeftHeight(col, row, heightData)); } - int TerrainStorage::getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getHeightDifferenceToRight(int col, int row, std::span heightData) const { return abs(getThisHeight(col, row, heightData) - getRightHeight(col, row, heightData)); } - int TerrainStorage::getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getHeightDifferenceToUp(int col, int row, std::span heightData) const { return abs(getThisHeight(col, row, heightData) - getUpHeight(col, row, heightData)); } - int TerrainStorage::getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const + int TerrainStorage::getHeightDifferenceToDown(int col, int row, std::span heightData) const { return abs(getThisHeight(col, row, heightData) - getDownHeight(col, row, heightData)); } - bool TerrainStorage::leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const + bool TerrainStorage::leftOrUpIsOverTheLimit( + int col, int row, int heightWarningLimit, std::span heightData) const { - return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit || - getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit; + return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit + || getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit; } - bool TerrainStorage::rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const + bool TerrainStorage::rightOrDownIsOverTheLimit( + int col, int row, int heightWarningLimit, std::span heightData) const { - return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit || - getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit; + return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit + || getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit; } - void TerrainStorage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const + void TerrainStorage::adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const { // Highlight broken height changes int heightWarningLimit = 1024; - if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData)) || - ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && rightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData))) + if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData->getHeights())) + || ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) + && rightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData->getHeights()))) { color.r() = 255; color.g() = 0; @@ -147,6 +168,6 @@ namespace CSVRender float TerrainStorage::getAlteredHeight(int col, int row) const { - return mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]; + return mAlteredHeight[static_cast(col * ESM::Land::LAND_SIZE + row)]; } } diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 762eb8003..907e63a8e 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -3,9 +3,19 @@ #include -#include +#include +#include +#include +#include -#include "../../model/world/data.hpp" +namespace CSMWorld +{ + class Data; +} +namespace osg +{ + class Vec4ub; +} namespace CSVRender { @@ -27,24 +37,25 @@ namespace CSVRender const CSMWorld::Data& mData; std::array mAlteredHeight; - osg::ref_ptr getLand (int cellX, int cellY) override; + osg::ref_ptr getLand(ESM::ExteriorCellLocation cellLocation) override; const ESM::LandTexture* getLandTexture(int index, short plugin) override; - void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; + void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) override; - int getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const; - int getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const; - int getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const; - int getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const; - int getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const; - int getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const; - int getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const; - int getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const; - int getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const; - bool leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; - bool rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; + int getThisHeight(int col, int row, std::span heightData) const; + int getLeftHeight(int col, int row, std::span heightData) const; + int getRightHeight(int col, int row, std::span heightData) const; + int getUpHeight(int col, int row, std::span heightData) const; + int getDownHeight(int col, int row, std::span heightData) const; + int getHeightDifferenceToLeft(int col, int row, std::span heightData) const; + int getHeightDifferenceToRight(int col, int row, std::span heightData) const; + int getHeightDifferenceToUp(int col, int row, std::span heightData) const; + int getHeightDifferenceToDown(int col, int row, std::span heightData) const; + bool leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, std::span heightData) const; + bool rightOrDownIsOverTheLimit( + int col, int row, int heightWarningLimit, std::span heightData) const; - void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const override; + void adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const override; float getAlteredHeight(int col, int row) const override; }; diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index dfcc29ae0..407f058a1 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -1,18 +1,33 @@ #include "terraintexturemode.hpp" +#include +#include +#include +#include +#include #include -#include -#include -#include -#include #include -#include -#include +#include +#include +#include -#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include -#include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetooltexturebrush.hpp" @@ -25,61 +40,72 @@ #include "../../model/world/landtexture.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/universalid.hpp" -#include "../widget/brushshapes.hpp" #include "brushdraw.hpp" #include "editmode.hpp" -#include "pagedworldspacewidget.hpp" #include "mask.hpp" -#include "object.hpp" // Something small needed regarding pointers from here () +#include "pagedworldspacewidget.hpp" #include "worldspacewidget.hpp" -CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) -: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent), - mBrushTexture("L0#0"), - mBrushSize(1), - mBrushShape(CSVWidget::BrushShape_Point), - mTextureBrushScenetool(nullptr), - mDragMode(InteractionType_None), - mParentNode(parentNode), - mIsEditing(false) +CSVRender::TerrainTextureMode::TerrainTextureMode( + WorldspaceWidget* worldspaceWidget, osg::Group* parentNode, QWidget* parent) + : EditMode(worldspaceWidget, QIcon{ ":scenetoolbar/editing-terrain-texture" }, Mask_Terrain | Mask_Reference, + "Terrain texture editing", parent) + , mBrushTexture("L0#0") + , mBrushSize(1) + , mBrushShape(CSVWidget::BrushShape_Point) + , mTextureBrushScenetool(nullptr) + , mDragMode(InteractionType_None) + , mParentNode(parentNode) + , mIsEditing(false) { } void CSVRender::TerrainTextureMode::activate(CSVWidget::SceneToolbar* toolbar) { - if(!mTextureBrushScenetool) + if (!mTextureBrushScenetool) { - mTextureBrushScenetool = new CSVWidget::SceneToolTextureBrush (toolbar, "scenetooltexturebrush", getWorldspaceWidget().getDocument()); - connect(mTextureBrushScenetool, SIGNAL (clicked()), mTextureBrushScenetool, SLOT (activate())); - connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int))); - connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setBrushShape(CSVWidget::BrushShape))); - connect(mTextureBrushScenetool->mTextureBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int))); - connect(mTextureBrushScenetool, SIGNAL(passTextureId(std::string)), this, SLOT(setBrushTexture(std::string))); - connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passTextureId(std::string)), this, SLOT(setBrushTexture(std::string))); + mTextureBrushScenetool = new CSVWidget::SceneToolTextureBrush( + toolbar, "scenetooltexturebrush", getWorldspaceWidget().getDocument()); + connect(mTextureBrushScenetool, &CSVWidget::SceneTool::clicked, mTextureBrushScenetool, + &CSVWidget::SceneToolTextureBrush::activate); + connect(mTextureBrushScenetool->mTextureBrushWindow, &CSVWidget::TextureBrushWindow::passBrushSize, this, + &TerrainTextureMode::setBrushSize); + connect(mTextureBrushScenetool->mTextureBrushWindow, &CSVWidget::TextureBrushWindow::passBrushShape, this, + &TerrainTextureMode::setBrushShape); + connect(mTextureBrushScenetool->mTextureBrushWindow->mSizeSliders->mBrushSizeSlider, &QSlider::valueChanged, + this, &TerrainTextureMode::setBrushSize); + connect(mTextureBrushScenetool, &CSVWidget::SceneToolTextureBrush::passTextureId, this, + &TerrainTextureMode::setBrushTexture); + connect(mTextureBrushScenetool->mTextureBrushWindow, &CSVWidget::TextureBrushWindow::passTextureId, this, + &TerrainTextureMode::setBrushTexture); - connect(mTextureBrushScenetool, SIGNAL(passEvent(QDropEvent*)), this, SLOT(handleDropEvent(QDropEvent*))); - connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool->mTextureBrushWindow, SLOT(setBrushTexture(std::string))); - connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool, SLOT(updateBrushHistory(std::string))); + connect(mTextureBrushScenetool, qOverload(&CSVWidget::SceneToolTextureBrush::passEvent), this, + &TerrainTextureMode::handleDropEvent); + connect(this, &TerrainTextureMode::passBrushTexture, mTextureBrushScenetool->mTextureBrushWindow, + &CSVWidget::TextureBrushWindow::setBrushTexture); + connect(this, &TerrainTextureMode::passBrushTexture, mTextureBrushScenetool, + &CSVWidget::SceneToolTextureBrush::updateBrushHistory); } if (!mTerrainTextureSelection) { - mTerrainTextureSelection.reset(new TerrainSelection(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Texture)); + mTerrainTextureSelection + = std::make_shared(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Texture); } if (!mBrushDraw) - mBrushDraw.reset(new BrushDraw(mParentNode, true)); + mBrushDraw = std::make_unique(mParentNode, true); EditMode::activate(toolbar); - toolbar->addTool (mTextureBrushScenetool); + toolbar->addTool(mTextureBrushScenetool); } void CSVRender::TerrainTextureMode::deactivate(CSVWidget::SceneToolbar* toolbar) { - if(mTextureBrushScenetool) + if (mTextureBrushScenetool) { - toolbar->removeTool (mTextureBrushScenetool); + toolbar->removeTool(mTextureBrushScenetool); delete mTextureBrushScenetool; mTextureBrushScenetool = nullptr; } @@ -102,23 +128,23 @@ void CSVRender::TerrainTextureMode::primaryOpenPressed(const WorldspaceHitResult void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult& hit) // Apply changes here { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); - mCellId = getWorldspaceWidget().getCellId (hit.worldPos); + mCellId = getWorldspaceWidget().getCellId(hit.worldPos); QUndoStack& undoStack = document.getUndoStack(); CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); - int index = landtexturesCollection.searchId(mBrushTexture); + int index = landtexturesCollection.searchId(ESM::RefId::stringRefId(mBrushTexture)); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { - undoStack.beginMacro ("Edit texture records"); - if(allowLandTextureEditing(mCellId)) + undoStack.beginMacro("Edit texture records"); + if (allowLandTextureEditing(mCellId)) { - undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); + undoStack.push(new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); editTerrainTextureGrid(hit); } undoStack.endMacro(); @@ -127,46 +153,48 @@ void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResult& hit) { - if(hit.hit && hit.tag == nullptr) + if (hit.hit && hit.tag == nullptr) { - selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, false); + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); + mTerrainTextureSelection->clearTemporarySelection(); } } void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit) { - if(hit.hit && hit.tag == nullptr) + if (hit.hit && hit.tag == nullptr) { - selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, false); + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); + mTerrainTextureSelection->clearTemporarySelection(); } } -bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos) +bool CSVRender::TerrainTextureMode::primaryEditStartDrag(const QPoint& pos) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); - mCellId = getWorldspaceWidget().getCellId (hit.worldPos); + mCellId = getWorldspaceWidget().getCellId(hit.worldPos); QUndoStack& undoStack = document.getUndoStack(); mDragMode = InteractionType_PrimaryEdit; CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); - int index = landtexturesCollection.searchId(mBrushTexture); + const int index = landtexturesCollection.searchId(ESM::RefId::stringRefId(mBrushTexture)); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { - undoStack.beginMacro ("Edit texture records"); + undoStack.beginMacro("Edit texture records"); mIsEditing = true; - if(allowLandTextureEditing(mCellId)) + if (allowLandTextureEditing(mCellId)) { - undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); + undoStack.push(new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); editTerrainTextureGrid(hit); } } @@ -174,47 +202,47 @@ bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos) return true; } -bool CSVRender::TerrainTextureMode::secondaryEditStartDrag (const QPoint& pos) +bool CSVRender::TerrainTextureMode::secondaryEditStartDrag(const QPoint& pos) { return false; } -bool CSVRender::TerrainTextureMode::primarySelectStartDrag (const QPoint& pos) +bool CSVRender::TerrainTextureMode::primarySelectStartDrag(const QPoint& pos) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } - selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, true); - return false; + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); + return true; } -bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos) +bool CSVRender::TerrainTextureMode::secondarySelectStartDrag(const QPoint& pos) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_SecondarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } - selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); - return false; + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); + return true; } -void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) +void CSVRender::TerrainTextureMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) { if (mDragMode == InteractionType_PrimaryEdit) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + std::string cellId = getWorldspaceWidget().getCellId(hit.worldPos); CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); - int index = landtexturesCollection.searchId(mBrushTexture); + const int index = landtexturesCollection.searchId(ESM::RefId::stringRefId(mBrushTexture)); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { @@ -224,14 +252,16 @@ void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diff if (mDragMode == InteractionType_PrimarySelect) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, true); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == nullptr) + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); } if (mDragMode == InteractionType_SecondarySelect) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == nullptr) + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); } } @@ -243,71 +273,71 @@ void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) QUndoStack& undoStack = document.getUndoStack(); CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); - int index = landtexturesCollection.searchId(mBrushTexture); + const int index = landtexturesCollection.searchId(ESM::RefId::stringRefId(mBrushTexture)); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) { - undoStack.endMacro(); - mIsEditing = false; + undoStack.endMacro(); + mIsEditing = false; } } + + mTerrainTextureSelection->clearTemporarySelection(); } -void CSVRender::TerrainTextureMode::dragAborted() +void CSVRender::TerrainTextureMode::dragAborted() {} + +void CSVRender::TerrainTextureMode::dragWheel(int diff, double speedFactor) {} + +void CSVRender::TerrainTextureMode::handleDropEvent(QDropEvent* event) { -} + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); -void CSVRender::TerrainTextureMode::dragWheel (int diff, double speedFactor) -{ -} + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; -void CSVRender::TerrainTextureMode::handleDropEvent (QDropEvent *event) -{ - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (mime->holdsType(CSMWorld::UniversalId::Type_LandTexture)) + { + const std::vector ids = mime->getData(); - if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped - return; + for (const CSMWorld::UniversalId& uid : ids) + { + mBrushTexture = uid.getId(); + emit passBrushTexture(mBrushTexture); + } + } + if (mime->holdsType(CSMWorld::UniversalId::Type_Texture)) + { + const std::vector ids = mime->getData(); - if (mime->holdsType (CSMWorld::UniversalId::Type_LandTexture)) - { - const std::vector ids = mime->getData(); - - for (const CSMWorld::UniversalId& uid : ids) - { - mBrushTexture = uid.getId(); - emit passBrushTexture(mBrushTexture); - } - } - if (mime->holdsType (CSMWorld::UniversalId::Type_Texture)) - { - const std::vector ids = mime->getData(); - - for (const CSMWorld::UniversalId& uid : ids) - { - std::string textureFileName = uid.toString(); - createTexture(textureFileName); - emit passBrushTexture(mBrushTexture); - } - } + for (const CSMWorld::UniversalId& uid : ids) + { + std::string textureFileName = uid.toString(); + createTexture(textureFileName); + emit passBrushTexture(mBrushTexture); + } + } } void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitResult& hit) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); - mCellId = getWorldspaceWidget().getCellId (hit.worldPos); - if(allowLandTextureEditing(mCellId)) {} + mCellId = getWorldspaceWidget().getCellId(hit.worldPos); + if (allowLandTextureEditing(mCellId)) + { + } - std::pair cellCoordinates_pair = CSMWorld::CellCoordinates::fromId (mCellId); + std::pair cellCoordinates_pair = CSMWorld::CellCoordinates::fromId(mCellId); int cellX = cellCoordinates_pair.first.getX(); int cellY = cellCoordinates_pair.first.getY(); // The coordinates of hit in mCellId - int xHitInCell (float(((hit.worldPos.x() - (cellX* cellSize)) * landTextureSize / cellSize) - 0.25)); - int yHitInCell (float(((hit.worldPos.y() - (cellY* cellSize)) * landTextureSize / cellSize) + 0.25)); + int xHitInCell(float(((hit.worldPos.x() - (cellX * cellSize)) * landTextureSize / cellSize) - 0.25)); + int yHitInCell(float(((hit.worldPos.y() - (cellY * cellSize)) * landTextureSize / cellSize) + 0.25)); if (xHitInCell < 0) { xHitInCell = xHitInCell + landTextureSize; @@ -320,71 +350,91 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe } mCellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); - if(allowLandTextureEditing(mCellId)) {} + if (allowLandTextureEditing(mCellId)) + { + } std::string iteratedCellId; int textureColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex); std::size_t hashlocation = mBrushTexture.find('#'); - std::string mBrushTextureInt = mBrushTexture.substr (hashlocation+1); - int brushInt = stoi(mBrushTexture.substr (hashlocation+1))+1; // All indices are offset by +1 + std::string mBrushTextureInt = mBrushTexture.substr(hashlocation + 1); + + // All indices are offset by +1 + int brushInt = Misc::StringUtils::toNumeric(mBrushTexture.substr(hashlocation + 1), 0) + 1; int r = static_cast(mBrushSize) / 2; if (mBrushShape == CSVWidget::BrushShape_Point) { - CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrainPointer + = landTable.data(landTable.getModelIndex(mCellId, textureColumn)) + .value(); CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); - if(allowLandTextureEditing(mCellId)) + if (allowLandTextureEditing(mCellId)) { - newTerrain[yHitInCell*landTextureSize+xHitInCell] = brushInt; + newTerrain[yHitInCell * landTextureSize + xHitInCell] = brushInt; pushEditToCommand(newTerrain, document, landTable, mCellId); } } if (mBrushShape == CSVWidget::BrushShape_Square) { - int upperLeftCellX = cellX - std::floor(r / landTextureSize); - int upperLeftCellY = cellY - std::floor(r / landTextureSize); - if (xHitInCell - (r % landTextureSize) < 0) upperLeftCellX--; - if (yHitInCell - (r % landTextureSize) < 0) upperLeftCellY--; + int upperLeftCellX = cellX - std::floor(r / landTextureSize); + int upperLeftCellY = cellY - std::floor(r / landTextureSize); + if (xHitInCell - (r % landTextureSize) < 0) + upperLeftCellX--; + if (yHitInCell - (r % landTextureSize) < 0) + upperLeftCellY--; int lowerrightCellX = cellX + std::floor(r / landTextureSize); int lowerrightCellY = cellY + std::floor(r / landTextureSize); - if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellX++; - if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellY++; + if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) + lowerrightCellX++; + if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) + lowerrightCellY++; - for(int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++) + for (int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++) { - for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++) + for (int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++) { iteratedCellId = CSMWorld::CellCoordinates::generateId(i_cell, j_cell); - if(allowLandTextureEditing(iteratedCellId)) + if (allowLandTextureEditing(iteratedCellId)) { - CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrainPointer + = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)) + .value(); CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); - for(int i = 0; i < landTextureSize; i++) + for (int i = 0; i < landTextureSize; i++) { - for(int j = 0; j < landTextureSize; j++) + for (int j = 0; j < landTextureSize; j++) { - if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) + if (i_cell == cellX && j_cell == cellY && abs(i - xHitInCell) < r + && abs(j - yHitInCell) < r) { - newTerrain[j*landTextureSize+i] = brushInt; + newTerrain[j * landTextureSize + i] = brushInt; } else { int distanceX(0); int distanceY(0); - if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i; - if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j; - if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize * abs(i_cell-cellX) + i; - if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j; - if (i_cell == cellX) distanceX = abs(i-xHitInCell); - if (j_cell == cellY) distanceY = abs(j-yHitInCell); - if (distanceX < r && distanceY < r) newTerrain[j*landTextureSize+i] = brushInt; + if (i_cell < cellX) + distanceX = xHitInCell + landTextureSize * abs(i_cell - cellX) - i; + if (j_cell < cellY) + distanceY = yHitInCell + landTextureSize * abs(j_cell - cellY) - j; + if (i_cell > cellX) + distanceX = -xHitInCell + landTextureSize * abs(i_cell - cellX) + i; + if (j_cell > cellY) + distanceY = -yHitInCell + landTextureSize * abs(j_cell - cellY) + j; + if (i_cell == cellX) + distanceX = abs(i - xHitInCell); + if (j_cell == cellY) + distanceY = abs(j - yHitInCell); + if (distanceX < r && distanceY < r) + newTerrain[j * landTextureSize + i] = brushInt; } } } @@ -396,50 +446,65 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe if (mBrushShape == CSVWidget::BrushShape_Circle) { - int upperLeftCellX = cellX - std::floor(r / landTextureSize); - int upperLeftCellY = cellY - std::floor(r / landTextureSize); - if (xHitInCell - (r % landTextureSize) < 0) upperLeftCellX--; - if (yHitInCell - (r % landTextureSize) < 0) upperLeftCellY--; + int upperLeftCellX = cellX - std::floor(r / landTextureSize); + int upperLeftCellY = cellY - std::floor(r / landTextureSize); + if (xHitInCell - (r % landTextureSize) < 0) + upperLeftCellX--; + if (yHitInCell - (r % landTextureSize) < 0) + upperLeftCellY--; int lowerrightCellX = cellX + std::floor(r / landTextureSize); int lowerrightCellY = cellY + std::floor(r / landTextureSize); - if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellX++; - if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellY++; + if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) + lowerrightCellX++; + if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) + lowerrightCellY++; - for(int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++) + for (int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++) { - for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++) + for (int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++) { iteratedCellId = CSMWorld::CellCoordinates::generateId(i_cell, j_cell); - if(allowLandTextureEditing(iteratedCellId)) + if (allowLandTextureEditing(iteratedCellId)) { - CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrainPointer + = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)) + .value(); CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); - for(int i = 0; i < landTextureSize; i++) + for (int i = 0; i < landTextureSize; i++) { - for(int j = 0; j < landTextureSize; j++) + for (int j = 0; j < landTextureSize; j++) { - if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) + if (i_cell == cellX && j_cell == cellY && abs(i - xHitInCell) < r + && abs(j - yHitInCell) < r) { - int distanceX = abs(i-xHitInCell); - int distanceY = abs(j-yHitInCell); - float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + int distanceX = abs(i - xHitInCell); + int distanceY = abs(j - yHitInCell); + float distance = std::round(sqrt(pow(distanceX, 2) + pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; - if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; + if (distance < rf) + newTerrain[j * landTextureSize + i] = brushInt; } else { int distanceX(0); int distanceY(0); - if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i; - if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j; - if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize * abs(i_cell-cellX) + i; - if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j; - if (i_cell == cellX) distanceX = abs(i-xHitInCell); - if (j_cell == cellY) distanceY = abs(j-yHitInCell); - float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + if (i_cell < cellX) + distanceX = xHitInCell + landTextureSize * abs(i_cell - cellX) - i; + if (j_cell < cellY) + distanceY = yHitInCell + landTextureSize * abs(j_cell - cellY) - j; + if (i_cell > cellX) + distanceX = -xHitInCell + landTextureSize * abs(i_cell - cellX) + i; + if (j_cell > cellY) + distanceY = -yHitInCell + landTextureSize * abs(j_cell - cellY) + j; + if (i_cell == cellX) + distanceX = abs(i - xHitInCell); + if (j_cell == cellY) + distanceY = abs(j - yHitInCell); + float distance = std::round(sqrt(pow(distanceX, 2) + pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; - if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; + if (distance < rf) + newTerrain[j * landTextureSize + i] = brushInt; } } } @@ -451,30 +516,36 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe if (mBrushShape == CSVWidget::BrushShape_Custom) { - CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrainPointer + = landTable.data(landTable.getModelIndex(mCellId, textureColumn)) + .value(); CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); - if(allowLandTextureEditing(mCellId) && !mCustomBrushShape.empty()) + if (allowLandTextureEditing(mCellId) && !mCustomBrushShape.empty()) { - for(auto const& value: mCustomBrushShape) + for (auto const& value : mCustomBrushShape) { - if(yHitInCell + value.second >= 0 && yHitInCell + value.second <= 15 && xHitInCell + value.first >= 0 && xHitInCell + value.first <= 15) + if (yHitInCell + value.second >= 0 && yHitInCell + value.second <= 15 && xHitInCell + value.first >= 0 + && xHitInCell + value.first <= 15) { - newTerrain[(yHitInCell+value.second)*landTextureSize+xHitInCell+value.first] = brushInt; + newTerrain[(yHitInCell + value.second) * landTextureSize + xHitInCell + value.first] = brushInt; } else { - int cellXDifference = std::floor(1.0f*(xHitInCell + value.first)/landTextureSize); - int cellYDifference = std::floor(1.0f*(yHitInCell + value.second)/landTextureSize); + int cellXDifference = std::floor(1.0f * (xHitInCell + value.first) / landTextureSize); + int cellYDifference = std::floor(1.0f * (yHitInCell + value.second) / landTextureSize); int xInOtherCell = xHitInCell + value.first - cellXDifference * landTextureSize; int yInOtherCell = yHitInCell + value.second - cellYDifference * landTextureSize; - std::string cellId = CSMWorld::CellCoordinates::generateId(cellX+cellXDifference, cellY+cellYDifference); + std::string cellId + = CSMWorld::CellCoordinates::generateId(cellX + cellXDifference, cellY + cellYDifference); if (allowLandTextureEditing(cellId)) { - CSMWorld::LandTexturesColumn::DataType newTerrainPointerOtherCell = landTable.data(landTable.getModelIndex(cellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrainPointerOtherCell + = landTable.data(landTable.getModelIndex(cellId, textureColumn)) + .value(); CSMWorld::LandTexturesColumn::DataType newTerrainOtherCell(newTerrainPointerOtherCell); - newTerrainOtherCell[yInOtherCell*landTextureSize+xInOtherCell] = brushInt; + newTerrainOtherCell[yInOtherCell * landTextureSize + xInOtherCell] = brushInt; pushEditToCommand(newTerrainOtherCell, document, landTable, cellId); } } @@ -486,7 +557,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe bool CSVRender::TerrainTextureMode::isInCellSelection(int globalSelectionX, int globalSelectionY) { - if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { std::pair textureCoords = std::make_pair(globalSelectionX, globalSelectionY); std::string cellId = CSMWorld::CellCoordinates::textureGlobalToCellId(textureCoords); @@ -495,15 +567,16 @@ bool CSVRender::TerrainTextureMode::isInCellSelection(int globalSelectionX, int return false; } - -void CSVRender::TerrainTextureMode::selectTerrainTextures(const std::pair& texCoords, unsigned char selectMode, bool dragOperation) +void CSVRender::TerrainTextureMode::selectTerrainTextures( + const std::pair& texCoords, unsigned char selectMode) { int r = mBrushSize / 2; std::vector> selections; if (mBrushShape == CSVWidget::BrushShape_Point) { - if (isInCellSelection(texCoords.first, texCoords.second)) selections.emplace_back(texCoords); + if (isInCellSelection(texCoords.first, texCoords.second)) + selections.emplace_back(texCoords); } if (mBrushShape == CSVWidget::BrushShape_Square) @@ -528,7 +601,7 @@ void CSVRender::TerrainTextureMode::selectTerrainTextures(const std::pair(mBrushSize) / 2; if (std::round(coords.length()) < rf) { @@ -545,9 +618,9 @@ void CSVRender::TerrainTextureMode::selectTerrainTextures(const std::paironlySelect(selections); - if(selectMode == 1) mTerrainTextureSelection->toggleSelect(selections, dragOperation); + std::string selectAction; + + if (selectMode == 0) + selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); + else + selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); + + if (selectAction == "Select only") + mTerrainTextureSelection->onlySelect(selections); + else if (selectAction == "Add to selection") + mTerrainTextureSelection->addSelect(selections); + else if (selectAction == "Remove from selection") + mTerrainTextureSelection->removeSelect(selections); + else if (selectAction == "Invert selection") + mTerrainTextureSelection->toggleSelect(selections); } -void CSVRender::TerrainTextureMode::pushEditToCommand(CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, std::string cellId) +void CSVRender::TerrainTextureMode::pushEditToCommand(CSMWorld::LandTexturesColumn::DataType& newLandGrid, + CSMDoc::Document& document, CSMWorld::IdTable& landTable, std::string cellId) { - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& ltexTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); QVariant changedLand; changedLand.setValue(newLandGrid); - QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandTexturesIndex))); + QModelIndex index( + landTable.getModelIndex(cellId, landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex))); QUndoStack& undoStack = document.getUndoStack(); - undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); - undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); + undoStack.push(new CSMWorld::ModifyCommand(landTable, index, changedLand)); + undoStack.push(new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); } void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& ltexTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); QUndoStack& undoStack = document.getUndoStack(); std::string newId; - int counter=0; + int counter = 0; bool freeIndexFound = false; do { @@ -598,7 +685,8 @@ void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName) try { newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); - if (ltexTable.getRecord(newId).isDeleted() == 0) counter = (counter + 1) % maxCounter; + if (ltexTable.getRecord(newId).isDeleted() == 0) + counter = (counter + 1) % maxCounter; } catch (const std::exception&) { @@ -608,18 +696,18 @@ void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName) } while (freeIndexFound == false); std::size_t idlocation = textureFileName.find("Texture: "); - textureFileName = textureFileName.substr (idlocation + 9); + textureFileName = textureFileName.substr(idlocation + 9); QVariant textureNameVariant; QVariant textureFileNameVariant; textureFileNameVariant.setValue(QString::fromStdString(textureFileName)); - undoStack.beginMacro ("Add land texture record"); + undoStack.beginMacro("Add land texture record"); - undoStack.push (new CSMWorld::CreateCommand (ltexTable, newId)); - QModelIndex index(ltexTable.getModelIndex (newId, ltexTable.findColumnIndex (CSMWorld::Columns::ColumnId_Texture))); - undoStack.push (new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant)); + undoStack.push(new CSMWorld::CreateCommand(ltexTable, newId)); + QModelIndex index(ltexTable.getModelIndex(newId, ltexTable.findColumnIndex(CSMWorld::Columns::ColumnId_Texture))); + undoStack.push(new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant)); undoStack.endMacro(); mBrushTexture = newId; } @@ -627,57 +715,56 @@ void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName) bool CSVRender::TerrainTextureMode::allowLandTextureEditing(std::string cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - CSMWorld::IdTree& cellTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + CSMWorld::IdTable& landTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTree& cellTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); - bool noCell = document.getData().getCells().searchId (cellId)==-1; - bool noLand = document.getData().getLand().searchId (cellId)==-1; + const ESM::RefId cellRefId = ESM::RefId::stringRefId(cellId); + const bool noCell = document.getData().getCells().searchId(cellRefId) == -1; + const bool noLand = document.getData().getLand().searchId(cellRefId) == -1; if (noCell) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist - if (mode=="Discard") + if (mode == "Discard") return false; - if (mode=="Create cell and land, then edit") + if (mode == "Create cell and land, then edit") { - std::unique_ptr createCommand ( - new CSMWorld::CreateCommand (cellTable, cellId)); - int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); - int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); - createCommand->addNestedValue (parentIndex, index, false); - document.getUndoStack().push (createCommand.release()); + auto createCommand = std::make_unique(cellTable, cellId); + int parentIndex = cellTable.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); + int index = cellTable.findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); + createCommand->addNestedValue(parentIndex, index, false); + document.getUndoStack().push(createCommand.release()); - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); - selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); - paged->setCellSelection (selection); + selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); + paged->setCellSelection(selection); } } } - else if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + else if (CSVRender::PagedWorldspaceWidget* paged + = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); - if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) + if (!selection.has(CSMWorld::CellCoordinates::fromId(cellId).first)) { // target cell exists, but is not shown - std::string mode = - CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); - if (mode=="Discard") + if (mode == "Discard") return false; - if (mode=="Show cell and edit") + if (mode == "Show cell and edit") { - selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); - paged->setCellSelection (selection); + selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); + paged->setCellSelection(selection); } } } @@ -687,23 +774,21 @@ bool CSVRender::TerrainTextureMode::allowLandTextureEditing(std::string cellId) std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist - if (mode=="Discard") + if (mode == "Discard") return false; - if (mode=="Create cell and land, then edit") + if (mode == "Create cell and land, then edit") { - document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); + document.getUndoStack().push(new CSMWorld::CreateCommand(landTable, cellId)); } } return true; } -void CSVRender::TerrainTextureMode::dragMoveEvent (QDragMoveEvent *event) -{ -} +void CSVRender::TerrainTextureMode::dragMoveEvent(QDragMoveEvent* event) {} -void CSVRender::TerrainTextureMode::mouseMoveEvent (QMouseEvent *event) +void CSVRender::TerrainTextureMode::mouseMoveEvent(QMouseEvent* event) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask()); if (hit.hit && mBrushDraw) @@ -717,7 +802,6 @@ std::shared_ptr CSVRender::TerrainTextureMode::getT return mTerrainTextureSelection; } - void CSVRender::TerrainTextureMode::setBrushSize(int brushSize) { mBrushSize = brushSize; @@ -727,7 +811,7 @@ void CSVRender::TerrainTextureMode::setBrushShape(CSVWidget::BrushShape brushSha { mBrushShape = brushShape; - //Set custom brush shape + // Set custom brush shape if (mBrushShape == CSVWidget::BrushShape_Custom && !mTerrainTextureSelection->getTerrainSelection().empty()) { auto terrainSelection = mTerrainTextureSelection->getTerrainSelection(); @@ -735,7 +819,7 @@ void CSVRender::TerrainTextureMode::setBrushShape(CSVWidget::BrushShape brushSha int selectionCenterY = 0; int selectionAmount = 0; - for(auto const& value: terrainSelection) + for (auto const& value : terrainSelection) { selectionCenterX += value.first; selectionCenterY += value.second; @@ -749,7 +833,7 @@ void CSVRender::TerrainTextureMode::setBrushShape(CSVWidget::BrushShape brushSha } mCustomBrushShape.clear(); - for (auto const& value: terrainSelection) + for (auto const& value : terrainSelection) mCustomBrushShape.emplace_back(value.first - selectionCenterX, value.second - selectionCenterY); } } diff --git a/apps/opencs/view/render/terraintexturemode.hpp b/apps/opencs/view/render/terraintexturemode.hpp index e0c6e4b40..3fc2e3796 100644 --- a/apps/opencs/view/render/terraintexturemode.hpp +++ b/apps/opencs/view/render/terraintexturemode.hpp @@ -3,135 +3,150 @@ #include "editmode.hpp" -#include #include +#include +#include +#include #include -#include #ifndef Q_MOC_RUN -#include "../../model/world/data.hpp" -#include "../../model/world/land.hpp" - -#include "../../model/doc/document.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/idtable.hpp" -#include "../../model/world/landtexture.hpp" +#include "../../model/world/columnimp.hpp" #include "../widget/brushshapes.hpp" #include "brushdraw.hpp" #endif -#include "terrainselection.hpp" +#include + +class QDragMoveEvent; +class QDropEvent; +class QMouseEvent; +class QObject; +class QPoint; +class QWidget; namespace osg { class Group; } +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class IdTable; +} + namespace CSVWidget { class SceneToolTextureBrush; + class SceneToolbar; } namespace CSVRender { + class TerrainSelection; + class WorldspaceWidget; + struct WorldspaceHitResult; + class TerrainTextureMode : public EditMode { Q_OBJECT - public: + public: + enum InteractionType + { + InteractionType_PrimaryEdit, + InteractionType_PrimarySelect, + InteractionType_SecondaryEdit, + InteractionType_SecondarySelect, + InteractionType_None + }; - enum InteractionType - { - InteractionType_PrimaryEdit, - InteractionType_PrimarySelect, - InteractionType_SecondaryEdit, - InteractionType_SecondarySelect, - InteractionType_None - }; + /// \brief Editmode for terrain texture grid + TerrainTextureMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); - /// \brief Editmode for terrain texture grid - TerrainTextureMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); + void primaryOpenPressed(const WorldspaceHitResult& hit) override; - void primaryOpenPressed (const WorldspaceHitResult& hit) override; + /// \brief Create single command for one-click texture editing + void primaryEditPressed(const WorldspaceHitResult& hit) override; - /// \brief Create single command for one-click texture editing - void primaryEditPressed (const WorldspaceHitResult& hit) override; + /// \brief Open brush settings window + void primarySelectPressed(const WorldspaceHitResult&) override; - /// \brief Open brush settings window - void primarySelectPressed(const WorldspaceHitResult&) override; + void secondarySelectPressed(const WorldspaceHitResult&) override; - void secondarySelectPressed(const WorldspaceHitResult&) override; + void activate(CSVWidget::SceneToolbar*) override; + void deactivate(CSVWidget::SceneToolbar*) override; - void activate(CSVWidget::SceneToolbar*) override; - void deactivate(CSVWidget::SceneToolbar*) override; + /// \brief Start texture editing command macro + bool primaryEditStartDrag(const QPoint& pos) override; - /// \brief Start texture editing command macro - bool primaryEditStartDrag (const QPoint& pos) override; + bool secondaryEditStartDrag(const QPoint& pos) override; + bool primarySelectStartDrag(const QPoint& pos) override; + bool secondarySelectStartDrag(const QPoint& pos) override; - bool secondaryEditStartDrag (const QPoint& pos) override; - bool primarySelectStartDrag (const QPoint& pos) override; - bool secondarySelectStartDrag (const QPoint& pos) override; + /// \brief Handle texture edit behavior during dragging + void drag(const QPoint& pos, int diffX, int diffY, double speedFactor) override; - /// \brief Handle texture edit behavior during dragging - void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; + /// \brief End texture editing command macro + void dragCompleted(const QPoint& pos) override; - /// \brief End texture editing command macro - void dragCompleted(const QPoint& pos) override; + void dragAborted() override; + void dragWheel(int diff, double speedFactor) override; + void dragMoveEvent(QDragMoveEvent* event) override; - void dragAborted() override; - void dragWheel (int diff, double speedFactor) override; - void dragMoveEvent (QDragMoveEvent *event) override; + void mouseMoveEvent(QMouseEvent* event) override; - void mouseMoveEvent (QMouseEvent *event) override; + std::shared_ptr getTerrainSelection(); - std::shared_ptr getTerrainSelection(); + private: + /// \brief Handle brush mechanics, maths regarding worldspace hit etc. + void editTerrainTextureGrid(const WorldspaceHitResult& hit); - private: - /// \brief Handle brush mechanics, maths regarding worldspace hit etc. - void editTerrainTextureGrid (const WorldspaceHitResult& hit); + /// \brief Check if global selection coordinate belongs to cell in view + bool isInCellSelection(int globalSelectionX, int globalSelectionY); - /// \brief Check if global selection coordinate belongs to cell in view - bool isInCellSelection(int globalSelectionX, int globalSelectionY); + /// \brief Handle brush mechanics for texture selection + void selectTerrainTextures(const std::pair& texCoords, unsigned char selectMode); - /// \brief Handle brush mechanics for texture selection - void selectTerrainTextures (const std::pair& texCoords, unsigned char selectMode, bool dragOperation); + /// \brief Push texture edits to command macro + void pushEditToCommand(CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, std::string cellId); - /// \brief Push texture edits to command macro - void pushEditToCommand (CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, std::string cellId); + /// \brief Create new land texture record from texture asset + void createTexture(std::string textureFileName); - /// \brief Create new land texture record from texture asset - void createTexture(std::string textureFileName); + /// \brief Create new cell and land if needed + bool allowLandTextureEditing(std::string textureFileName); - /// \brief Create new cell and land if needed - bool allowLandTextureEditing(std::string textureFileName); + std::string mCellId; + std::string mBrushTexture; + int mBrushSize; + CSVWidget::BrushShape mBrushShape; + std::unique_ptr mBrushDraw; + std::vector> mCustomBrushShape; + CSVWidget::SceneToolTextureBrush* mTextureBrushScenetool; + int mDragMode; + osg::Group* mParentNode; + bool mIsEditing; + std::shared_ptr mTerrainTextureSelection; - std::string mCellId; - std::string mBrushTexture; - int mBrushSize; - CSVWidget::BrushShape mBrushShape; - std::unique_ptr mBrushDraw; - std::vector> mCustomBrushShape; - CSVWidget::SceneToolTextureBrush *mTextureBrushScenetool; - int mDragMode; - osg::Group* mParentNode; - bool mIsEditing; - std::shared_ptr mTerrainTextureSelection; + const int cellSize{ ESM::Land::REAL_SIZE }; + const int landTextureSize{ ESM::Land::LAND_TEXTURE_SIZE }; - const int cellSize {ESM::Land::REAL_SIZE}; - const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; + signals: + void passBrushTexture(std::string brushTexture); - signals: - void passBrushTexture(std::string brushTexture); - - public slots: - void handleDropEvent(QDropEvent *event); - void setBrushSize(int brushSize); - void setBrushShape(CSVWidget::BrushShape brushShape); - void setBrushTexture(std::string brushShape); + public slots: + void handleDropEvent(QDropEvent* event); + void setBrushSize(int brushSize); + void setBrushShape(CSVWidget::BrushShape brushShape); + void setBrushTexture(std::string brushShape); }; } - #endif diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 20dc5b8d1..fee608b20 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -2,30 +2,51 @@ #include -#include - #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" -#include "../../model/world/tablemimedata.hpp" #include "../widget/scenetooltoggle2.hpp" #include "cameracontroller.hpp" -#include "mask.hpp" -#include "tagbase.hpp" + +namespace CSVRender +{ + class TagBase; +} + +namespace osg +{ + class Vec3f; +} void CSVRender::UnpagedWorldspaceWidget::update() { - const CSMWorld::Record& record = - dynamic_cast&> (mCellsModel->getRecord (mCellId)); + const CSMWorld::Record& record + = dynamic_cast&>(mCellsModel->getRecord(mCellId)); osg::Vec4f colour = SceneUtil::colourFromRGB(record.get().mAmbi.mAmbient); - setDefaultAmbient (colour); + setDefaultAmbient(colour); bool isInterior = (record.get().mData.mFlags & ESM::Cell::Interior) != 0; bool behaveLikeExterior = (record.get().mData.mFlags & ESM::Cell::QuasiEx) != 0; @@ -37,37 +58,38 @@ void CSVRender::UnpagedWorldspaceWidget::update() flagAsModified(); } -CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget* parent) -: WorldspaceWidget (document, parent), mDocument(document), mCellId (cellId) +CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget( + const std::string& cellId, CSMDoc::Document& document, QWidget* parent) + : WorldspaceWidget(document, parent) + , mDocument(document) + , mCellId(cellId) { - mCellsModel = &dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + mCellsModel + = &dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); - mReferenceablesModel = &dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + mReferenceablesModel = &dynamic_cast( + *document.getData().getTableModel(CSMWorld::UniversalId::Type_Referenceables)); - connect (mCellsModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (cellDataChanged (const QModelIndex&, const QModelIndex&))); - connect (mCellsModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (cellRowsAboutToBeRemoved (const QModelIndex&, int, int))); + connect(mCellsModel, &CSMWorld::IdTable::dataChanged, this, &UnpagedWorldspaceWidget::cellDataChanged); + connect(mCellsModel, &CSMWorld::IdTable::rowsAboutToBeRemoved, this, + &UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved); - connect (&document.getData(), SIGNAL (assetTablesChanged ()), - this, SLOT (assetTablesChanged ())); + connect( + &document.getData(), &CSMWorld::Data::assetTablesChanged, this, &UnpagedWorldspaceWidget::assetTablesChanged); update(); - mCell.reset (new Cell (document.getData(), mRootNode, mCellId)); + mCell = std::make_unique(document.getData(), mRootNode, mCellId); } -void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::UnpagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - int index = mCellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, index); + int index = mCellsModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + QModelIndex cellIndex = mCellsModel->getModelIndex(mCellId, index); - if (cellIndex.row()>=topLeft.row() && cellIndex.row()<=bottomRight.row()) + if (cellIndex.row() >= topLeft.row() && cellIndex.row() <= bottomRight.row()) { - if (mCellsModel->data (cellIndex).toInt()==CSMWorld::RecordBase::State_Deleted) + if (mCellsModel->data(cellIndex).toInt() == CSMWorld::RecordBase::State_Deleted) { emit closeRequest(); } @@ -80,12 +102,11 @@ void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& top } } -void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelIndex& parent, - int start, int end) +void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, 0); + QModelIndex cellIndex = mCellsModel->getModelIndex(mCellId, 0); - if (cellIndex.row()>=start && cellIndex.row()<=end) + if (cellIndex.row() >= start && cellIndex.row() <= end) emit closeRequest(); } @@ -95,17 +116,18 @@ void CSVRender::UnpagedWorldspaceWidget::assetTablesChanged() mCell->reloadAssets(); } -bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector& universalIdData, DropType type) +bool CSVRender::UnpagedWorldspaceWidget::handleDrop( + const std::vector& universalIdData, DropType type) { - if (WorldspaceWidget::handleDrop (universalIdData, type)) + if (WorldspaceWidget::handleDrop(universalIdData, type)) return true; - if (type!=Type_CellsInterior) + if (type != Type_CellsInterior) return false; mCellId = universalIdData.begin()->getId(); - mCell.reset (new Cell (getDocument().getData(), mRootNode, mCellId)); + mCell = std::make_unique(getDocument().getData(), mRootNode, mCellId); mCamPositionSet = false; mOrbitCamControl->reset(); @@ -115,41 +137,43 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vectorsetSelection (elementMask, Cell::Selection_Clear); + mCell->setSelection(elementMask, Cell::Selection_Clear); flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::invertSelection (int elementMask) +void CSVRender::UnpagedWorldspaceWidget::invertSelection(int elementMask) { - mCell->setSelection (elementMask, Cell::Selection_Invert); + mCell->setSelection(elementMask, Cell::Selection_Invert); flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::selectAll (int elementMask) +void CSVRender::UnpagedWorldspaceWidget::selectAll(int elementMask) { - mCell->setSelection (elementMask, Cell::Selection_All); + mCell->setSelection(elementMask, Cell::Selection_All); flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementMask) +void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId(int elementMask) { - mCell->selectAllWithSameParentId (elementMask); + mCell->selectAllWithSameParentId(elementMask); flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +void CSVRender::UnpagedWorldspaceWidget::selectInsideCube( + const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) { - mCell->selectInsideCube (pointA, pointB, dragMode); + mCell->selectInsideCube(pointA, pointB, dragMode); } -void CSVRender::UnpagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +void CSVRender::UnpagedWorldspaceWidget::selectWithinDistance( + const osg::Vec3d& point, float distance, DragMode dragMode) { - mCell->selectWithinDistance (point, distance, dragMode); + mCell->selectWithinDistance(point, distance, dragMode); } -std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const +std::string CSVRender::UnpagedWorldspaceWidget::getCellId(const osg::Vec3f& point) const { return mCellId; } @@ -164,83 +188,83 @@ CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const CSMWorld::Cel return mCell.get(); } -std::vector > CSVRender::UnpagedWorldspaceWidget::getSelection ( +osg::ref_ptr CSVRender::UnpagedWorldspaceWidget::getSnapTarget(unsigned int elementMask) const +{ + return mCell->getSnapTarget(elementMask); +} + +std::vector> CSVRender::UnpagedWorldspaceWidget::getSelection( unsigned int elementMask) const { - return mCell->getSelection (elementMask); + return mCell->getSelection(elementMask); } -std::vector > CSVRender::UnpagedWorldspaceWidget::getEdited ( +std::vector> CSVRender::UnpagedWorldspaceWidget::getEdited( unsigned int elementMask) const { - return mCell->getEdited (elementMask); + return mCell->getEdited(elementMask); } -void CSVRender::UnpagedWorldspaceWidget::setSubMode (int subMode, unsigned int elementMask) +void CSVRender::UnpagedWorldspaceWidget::setSubMode(int subMode, unsigned int elementMask) { - mCell->setSubMode (subMode, elementMask); + mCell->setSubMode(subMode, elementMask); } -void CSVRender::UnpagedWorldspaceWidget::reset (unsigned int elementMask) +void CSVRender::UnpagedWorldspaceWidget::reset(unsigned int elementMask) { - mCell->reset (elementMask); + mCell->reset(elementMask); } -void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged( + const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mCell.get()) - if (mCell.get()->referenceableDataChanged (topLeft, bottomRight)) + if (mCell.get()->referenceableDataChanged(topLeft, bottomRight)) flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::referenceableAboutToBeRemoved ( - const QModelIndex& parent, int start, int end) +void CSVRender::UnpagedWorldspaceWidget::referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end) { if (mCell.get()) - if (mCell.get()->referenceableAboutToBeRemoved (parent, start, end)) + if (mCell.get()->referenceableAboutToBeRemoved(parent, start, end)) flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::referenceableAdded (const QModelIndex& parent, - int start, int end) +void CSVRender::UnpagedWorldspaceWidget::referenceableAdded(const QModelIndex& parent, int start, int end) { if (mCell.get()) { - QModelIndex topLeft = mReferenceablesModel->index (start, 0); - QModelIndex bottomRight = - mReferenceablesModel->index (end, mReferenceablesModel->columnCount()); + QModelIndex topLeft = mReferenceablesModel->index(start, 0); + QModelIndex bottomRight = mReferenceablesModel->index(end, mReferenceablesModel->columnCount()); - if (mCell.get()->referenceableDataChanged (topLeft, bottomRight)) + if (mCell.get()->referenceableDataChanged(topLeft, bottomRight)) flagAsModified(); } } -void CSVRender::UnpagedWorldspaceWidget::referenceDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::UnpagedWorldspaceWidget::referenceDataChanged( + const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mCell.get()) - if (mCell.get()->referenceDataChanged (topLeft, bottomRight)) + if (mCell.get()->referenceDataChanged(topLeft, bottomRight)) flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::referenceAboutToBeRemoved (const QModelIndex& parent, - int start, int end) +void CSVRender::UnpagedWorldspaceWidget::referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end) { if (mCell.get()) - if (mCell.get()->referenceAboutToBeRemoved (parent, start, end)) + if (mCell.get()->referenceAboutToBeRemoved(parent, start, end)) flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& parent, int start, - int end) +void CSVRender::UnpagedWorldspaceWidget::referenceAdded(const QModelIndex& parent, int start, int end) { if (mCell.get()) - if (mCell.get()->referenceAdded (parent, start, end)) + if (mCell.get()->referenceAdded(parent, start, end)) flagAsModified(); } -void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); @@ -261,7 +285,7 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& for (int row = rowStart; row <= rowEnd; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); - if (mCellId == pathgrid.mId) + if (ESM::RefId::stringRefId(mCellId) == pathgrid.mId) { mCell->pathgridModified(); flagAsModified(); @@ -270,7 +294,7 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& } } -void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved(const QModelIndex& parent, int start, int end) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); @@ -280,7 +304,7 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelI for (int row = start; row <= end; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); - if (mCellId == pathgrid.mId) + if (ESM::RefId::stringRefId(mCellId) == pathgrid.mId) { mCell->pathgridRemoved(); flagAsModified(); @@ -290,7 +314,7 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelI } } -void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& parent, int start, int end) +void CSVRender::UnpagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, int start, int end) { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); @@ -299,7 +323,7 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& paren for (int row = start; row <= end; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); - if (mCellId == pathgrid.mId) + if (ESM::RefId::stringRefId(mCellId) == pathgrid.mId) { mCell->pathgridModified(); flagAsModified(); @@ -309,12 +333,11 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& paren } } -void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( - CSVWidget::SceneToolToggle2 *tool) +void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) { - WorldspaceWidget::addVisibilitySelectorButtons (tool); - tool->addButton (Button_Terrain, Mask_Terrain, "Terrain", "", true); - tool->addButton (Button_Fog, Mask_Fog, "Fog"); + WorldspaceWidget::addVisibilitySelectorButtons(tool); + tool->addButton(Button_Terrain, Mask_Terrain, "Terrain", "", true); + tool->addButton(Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() @@ -325,22 +348,21 @@ std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() std::ostringstream stream; - stream - << "player->positionCell " - << position.x() << ", " << position.y() << ", " << position.z() - << ", 0, \"" << mCellId << "\""; + stream << "player->positionCell " << position.x() << ", " << position.y() << ", " << position.z() << ", 0, \"" + << mCellId << "\""; return stream.str(); } -CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::DropType type) const +CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget::getDropRequirements( + CSVRender::WorldspaceWidget::DropType type) const { - dropRequirments requirements = WorldspaceWidget::getDropRequirements (type); + dropRequirments requirements = WorldspaceWidget::getDropRequirements(type); - if (requirements!=ignored) + if (requirements != ignored) return requirements; - switch(type) + switch (type) { case Type_CellsInterior: return canHandle; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 83233c327..10446354e 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -1,19 +1,39 @@ #ifndef OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H -#include #include +#include +#include + +#include +#include + +#include -#include "worldspacewidget.hpp" #include "cell.hpp" +#include "worldspacewidget.hpp" class QModelIndex; +class QObject; +class QWidget; + +namespace osg +{ + class Vec3f; + template + class ref_ptr; +} namespace CSMDoc { class Document; } +namespace CSVWidget +{ + class SceneToolToggle2; +} + namespace CSMWorld { class IdTable; @@ -22,103 +42,99 @@ namespace CSMWorld namespace CSVRender { + class TagBase; + class UnpagedWorldspaceWidget : public WorldspaceWidget { - Q_OBJECT + Q_OBJECT - CSMDoc::Document& mDocument; - std::string mCellId; - CSMWorld::IdTable *mCellsModel; - CSMWorld::IdTable *mReferenceablesModel; - std::unique_ptr mCell; + CSMDoc::Document& mDocument; + std::string mCellId; + CSMWorld::IdTable* mCellsModel; + CSMWorld::IdTable* mReferenceablesModel; + std::unique_ptr mCell; - void update(); + void update(); - public: + public: + UnpagedWorldspaceWidget(const std::string& cellId, CSMDoc::Document& document, QWidget* parent); - UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, - QWidget *parent); + dropRequirments getDropRequirements(DropType type) const override; - dropRequirments getDropRequirements(DropType type) const override; + /// \return Drop handled? + bool handleDrop(const std::vector& data, DropType type) override; - /// \return Drop handled? - bool handleDrop (const std::vector& data, - DropType type) override; + /// \param elementMask Elements to be affected by the clear operation + void clearSelection(int elementMask) override; - /// \param elementMask Elements to be affected by the clear operation - void clearSelection (int elementMask) override; + /// \param elementMask Elements to be affected by the select operation + void invertSelection(int elementMask) override; - /// \param elementMask Elements to be affected by the select operation - void invertSelection (int elementMask) override; + /// \param elementMask Elements to be affected by the select operation + void selectAll(int elementMask) override; - /// \param elementMask Elements to be affected by the select operation - void selectAll (int elementMask) override; + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + void selectAllWithSameParentId(int elementMask) override; - // Select everything that references the same ID as at least one of the elements - // already selected - // - /// \param elementMask Elements to be affected by the select operation - void selectAllWithSameParentId (int elementMask) override; + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; - void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; + void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; - void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; + std::string getCellId(const osg::Vec3f& point) const override; - std::string getCellId (const osg::Vec3f& point) const override; + Cell* getCell(const osg::Vec3d& point) const override; - Cell* getCell(const osg::Vec3d& point) const override; + Cell* getCell(const CSMWorld::CellCoordinates& coords) const override; - Cell* getCell(const CSMWorld::CellCoordinates& coords) const override; + osg::ref_ptr getSnapTarget(unsigned int elementMask) const override; - std::vector > getSelection (unsigned int elementMask) - const override; + std::vector> getSelection(unsigned int elementMask) const override; - std::vector > getEdited (unsigned int elementMask) - const override; + std::vector> getEdited(unsigned int elementMask) const override; - void setSubMode (int subMode, unsigned int elementMask) override; + void setSubMode(int subMode, unsigned int elementMask) override; - /// Erase all overrides and restore the visual representation to its true state. - void reset (unsigned int elementMask) override; + /// Erase all overrides and restore the visual representation to its true state. + void reset(unsigned int elementMask) override; - private: + private: + void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override; - void referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) override; + void referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; - void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; + void referenceableAdded(const QModelIndex& index, int start, int end) override; - void referenceableAdded (const QModelIndex& index, int start, int end) override; + void referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override; - void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; + void referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; - void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; + void referenceAdded(const QModelIndex& index, int start, int end) override; - void referenceAdded (const QModelIndex& index, int start, int end) override; + void pathgridDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override; - void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) override; + void pathgridAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; - void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) override; + void pathgridAdded(const QModelIndex& parent, int start, int end) override; - void pathgridAdded (const QModelIndex& parent, int start, int end) override; + std::string getStartupInstruction() override; - std::string getStartupInstruction() override; + protected: + void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) override; - protected: + private slots: - void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool) override; + void cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - private slots: + void cellRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void assetTablesChanged(); - void cellRowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + signals: - void assetTablesChanged (); - - signals: - - void cellChanged(const CSMWorld::UniversalId& id); + void cellChanged(const CSMWorld::UniversalId& id); }; } diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 2ebc083f2..6911f5f04 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,20 +1,43 @@ #include "worldspacewidget.hpp" #include +#include -#include #include #include #include +#include #include -#include -#include +#include #include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include -#include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/universalid.hpp" #include "../../model/prefs/shortcut.hpp" #include "../../model/prefs/state.hpp" @@ -22,118 +45,108 @@ #include "../render/orbitcameramode.hpp" #include "../widget/scenetoolmode.hpp" -#include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" +#include "../widget/scenetooltoggle2.hpp" -#include "object.hpp" -#include "mask.hpp" -#include "instancemode.hpp" -#include "pathgridmode.hpp" #include "cameracontroller.hpp" +#include "instancemode.hpp" +#include "object.hpp" +#include "pathgridmode.hpp" -CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) - : SceneWidget (document.getData().getResourceSystem(), parent, Qt::WindowFlags(), false) +CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidget* parent) + : SceneWidget(document.getData().getResourceSystem(), parent, Qt::WindowFlags(), false) , mSceneElements(nullptr) , mRun(nullptr) , mDocument(document) - , mInteractionMask (0) - , mEditMode (nullptr) - , mLocked (false) + , mInteractionMask(0) + , mEditMode(nullptr) + , mLocked(false) , mDragMode(InteractionType_None) - , mDragging (false) + , mDragging(false) , mDragX(0) , mDragY(0) , mSpeedMode(false) , mDragFactor(0) , mDragWheelFactor(0) , mDragShiftFactor(0) - , mToolTipPos (-1, -1) + , mToolTipPos(-1, -1) , mShowToolTips(false) , mToolTipDelay(0) , mInConstructor(true) { setAcceptDrops(true); - QAbstractItemModel *referenceables = - document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables); + QAbstractItemModel* referenceables = document.getData().getTableModel(CSMWorld::UniversalId::Type_Referenceables); - connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); - connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); - connect (referenceables, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (referenceableAdded (const QModelIndex&, int, int))); + connect(referenceables, &QAbstractItemModel::dataChanged, this, &WorldspaceWidget::referenceableDataChanged); + connect(referenceables, &QAbstractItemModel::rowsAboutToBeRemoved, this, + &WorldspaceWidget::referenceableAboutToBeRemoved); + connect(referenceables, &QAbstractItemModel::rowsInserted, this, &WorldspaceWidget::referenceableAdded); - QAbstractItemModel *references = - document.getData().getTableModel (CSMWorld::UniversalId::Type_References); + QAbstractItemModel* references = document.getData().getTableModel(CSMWorld::UniversalId::Type_References); - connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); - connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); - connect (references, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (referenceAdded (const QModelIndex&, int, int))); + connect(references, &QAbstractItemModel::dataChanged, this, &WorldspaceWidget::referenceDataChanged); + connect(references, &QAbstractItemModel::rowsAboutToBeRemoved, this, &WorldspaceWidget::referenceAboutToBeRemoved); + connect(references, &QAbstractItemModel::rowsInserted, this, &WorldspaceWidget::referenceAdded); - QAbstractItemModel *pathgrids = document.getData().getTableModel (CSMWorld::UniversalId::Type_Pathgrids); + QAbstractItemModel* pathgrids = document.getData().getTableModel(CSMWorld::UniversalId::Type_Pathgrids); - connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); - connect (pathgrids, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (pathgridAboutToBeRemoved (const QModelIndex&, int, int))); - connect (pathgrids, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (pathgridAdded (const QModelIndex&, int, int))); + connect(pathgrids, &QAbstractItemModel::dataChanged, this, &WorldspaceWidget::pathgridDataChanged); + connect(pathgrids, &QAbstractItemModel::rowsAboutToBeRemoved, this, &WorldspaceWidget::pathgridAboutToBeRemoved); + connect(pathgrids, &QAbstractItemModel::rowsInserted, this, &WorldspaceWidget::pathgridAdded); - QAbstractItemModel *debugProfiles = - document.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles); + QAbstractItemModel* debugProfiles = document.getData().getTableModel(CSMWorld::UniversalId::Type_DebugProfiles); - connect (debugProfiles, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (debugProfileDataChanged (const QModelIndex&, const QModelIndex&))); - connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); + connect(debugProfiles, &QAbstractItemModel::dataChanged, this, &WorldspaceWidget::debugProfileDataChanged); + connect(debugProfiles, &QAbstractItemModel::rowsAboutToBeRemoved, this, + &WorldspaceWidget::debugProfileAboutToBeRemoved); - mToolTipDelayTimer.setSingleShot (true); - connect (&mToolTipDelayTimer, SIGNAL (timeout()), this, SLOT (showToolTip())); + mToolTipDelayTimer.setSingleShot(true); + connect(&mToolTipDelayTimer, &QTimer::timeout, this, &WorldspaceWidget::showToolTip); CSMPrefs::get()["3D Scene Input"].update(); CSMPrefs::get()["Tooltips"].update(); // Shortcuts - CSMPrefs::Shortcut* primaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-primary", "scene-speed-modifier", - CSMPrefs::Shortcut::SM_Detach, this); + CSMPrefs::Shortcut* primaryEditShortcut + = new CSMPrefs::Shortcut("scene-edit-primary", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, this); CSMPrefs::Shortcut* primaryOpenShortcut = new CSMPrefs::Shortcut("scene-open-primary", this); - connect(primaryOpenShortcut, SIGNAL(activated(bool)), this, SLOT(primaryOpen(bool))); - connect(primaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(primaryEdit(bool))); - connect(primaryEditShortcut, SIGNAL(secondary(bool)), this, SLOT(speedMode(bool))); + connect(primaryOpenShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::primaryOpen); + connect(primaryEditShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::primaryEdit); + connect(primaryEditShortcut, qOverload(&CSMPrefs::Shortcut::secondary), this, &WorldspaceWidget::speedMode); CSMPrefs::Shortcut* secondaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-secondary", this); - connect(secondaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(secondaryEdit(bool))); + connect( + secondaryEditShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::secondaryEdit); CSMPrefs::Shortcut* primarySelectShortcut = new CSMPrefs::Shortcut("scene-select-primary", this); - connect(primarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(primarySelect(bool))); + connect( + primarySelectShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::primarySelect); CSMPrefs::Shortcut* secondarySelectShortcut = new CSMPrefs::Shortcut("scene-select-secondary", this); - connect(secondarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(secondarySelect(bool))); + connect(secondarySelectShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, + &WorldspaceWidget::secondarySelect); + + CSMPrefs::Shortcut* tertiarySelectShortcut = new CSMPrefs::Shortcut("scene-select-tertiary", this); + connect(tertiarySelectShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, + &WorldspaceWidget::tertiarySelect); CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); - connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag())); + connect(abortShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::abortDrag); mInConstructor = false; } -CSVRender::WorldspaceWidget::~WorldspaceWidget () +void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* setting) { -} - -void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setting) -{ - if (*setting=="3D Scene Input/drag-factor") + if (*setting == "3D Scene Input/drag-factor") mDragFactor = setting->toDouble(); - else if (*setting=="3D Scene Input/drag-wheel-factor") + else if (*setting == "3D Scene Input/drag-wheel-factor") mDragWheelFactor = setting->toDouble(); - else if (*setting=="3D Scene Input/drag-shift-factor") + else if (*setting == "3D Scene Input/drag-shift-factor") mDragShiftFactor = setting->toDouble(); - else if (*setting=="Rendering/object-marker-alpha" && !mInConstructor) + else if (*setting == "Rendering/object-marker-alpha" && !mInConstructor) { float alpha = setting->toDouble(); // getSelection is virtual, thus this can not be called from the constructor @@ -144,16 +157,15 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti objTag->mObject->setMarkerTransparency(alpha); } } - else if (*setting=="Tooltips/scene-delay") + else if (*setting == "Tooltips/scene-delay") mToolTipDelay = setting->toInt(); - else if (*setting=="Tooltips/scene") + else if (*setting == "Tooltips/scene") mShowToolTips = setting->isTrue(); else SceneWidget::settingChanged(setting); } - -void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {} +void CSVRender::WorldspaceWidget::useViewHint(const std::string& hint) {} void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() { @@ -162,25 +174,24 @@ void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() { - std::vector > selection = getSelection(~0u); + std::vector> selection = getSelection(~0u); - for (std::vector >::iterator it = selection.begin(); it!=selection.end(); ++it) + for (std::vector>::iterator it = selection.begin(); it != selection.end(); ++it) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (it->get())) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(it->get())) { mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3()); } } } -CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( - CSVWidget::SceneToolbar *parent) +CSVWidget::SceneToolMode* CSVRender::WorldspaceWidget::makeNavigationSelector(CSVWidget::SceneToolbar* parent) { - CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Camera Mode"); + CSVWidget::SceneToolMode* tool = new CSVWidget::SceneToolMode(parent, "Camera Mode"); /// \todo replace icons /// \todo consider user-defined button-mapping - tool->addButton (":scenetoolbar/1st-person", "1st", + tool->addButton(":scenetoolbar/1st-person", "1st", "First Person" "
        • Camera is held upright
        • " "
        • Mouse-Look while holding {scene-navi-primary}
        • " @@ -189,7 +200,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "
        • Mouse wheel moves the camera forward/backward
        • " "
        • Hold {scene-speed-modifier} to speed up movement
        • " "
        "); - tool->addButton (":scenetoolbar/free-camera", "free", + tool->addButton(":scenetoolbar/free-camera", "free", "Free Camera" "
        • Mouse-Look while holding {scene-navi-primary}
        • " "
        • Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)
        • " @@ -203,131 +214,123 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "Orbiting Camera" "
          • Always facing the centre point
          • " "
          • Rotate around the centre point via {orbit-up}, {orbit-left}, {orbit-down}, {orbit-right} or by moving " - "the mouse while holding {scene-navi-primary}
          • " + "the mouse while holding {scene-navi-primary}" "
          • Roll camera with {orbit-roll-left} and {orbit-roll-right} keys
          • " - "
          • Strafing (also vertically) by holding {scene-navi-secondary} (includes relocation of the centre point)
          • " + "
          • Strafing (also vertically) by holding {scene-navi-secondary} (includes relocation of the centre " + "point)
          • " "
          • Mouse wheel moves camera away or towards centre point but can not pass through it
          • " "
          • Hold {scene-speed-modifier} to speed up movement
          • " - "
          ", tool), + "
        ", + tool), "orbit"); - connect (tool, SIGNAL (modeChanged (const std::string&)), - this, SLOT (selectNavigationMode (const std::string&))); + connect(tool, &CSVWidget::SceneToolMode::modeChanged, this, &WorldspaceWidget::selectNavigationMode); return tool; } -CSVWidget::SceneToolToggle2 *CSVRender::WorldspaceWidget::makeSceneVisibilitySelector (CSVWidget::SceneToolbar *parent) +CSVWidget::SceneToolToggle2* CSVRender::WorldspaceWidget::makeSceneVisibilitySelector(CSVWidget::SceneToolbar* parent) { - mSceneElements = new CSVWidget::SceneToolToggle2 (parent, - "Scene Element Visibility", ":scenetoolbar/scene-view-c", ":scenetoolbar/scene-view-"); + mSceneElements = new CSVWidget::SceneToolToggle2( + parent, "Scene Element Visibility", ":scenetoolbar/scene-view-c", ":scenetoolbar/scene-view-"); - addVisibilitySelectorButtons (mSceneElements); + addVisibilitySelectorButtons(mSceneElements); - mSceneElements->setSelectionMask (0xffffffff); + mSceneElements->setSelectionMask(0xffffffff); - connect (mSceneElements, SIGNAL (selectionChanged()), - this, SLOT (elementSelectionChanged())); + connect(mSceneElements, &CSVWidget::SceneToolToggle2::selectionChanged, this, + &WorldspaceWidget::elementSelectionChanged); return mSceneElements; } -CSVWidget::SceneToolRun *CSVRender::WorldspaceWidget::makeRunTool ( - CSVWidget::SceneToolbar *parent) +CSVWidget::SceneToolRun* CSVRender::WorldspaceWidget::makeRunTool(CSVWidget::SceneToolbar* parent) { - CSMWorld::IdTable& debugProfiles = dynamic_cast ( - *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); + CSMWorld::IdTable& debugProfiles = dynamic_cast( + *mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_DebugProfiles)); std::vector profiles; - int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); - int stateColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - int defaultColumn = debugProfiles.findColumnIndex ( - CSMWorld::Columns::ColumnId_DefaultProfile); + int idColumn = debugProfiles.findColumnIndex(CSMWorld::Columns::ColumnId_Id); + int stateColumn = debugProfiles.findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + int defaultColumn = debugProfiles.findColumnIndex(CSMWorld::Columns::ColumnId_DefaultProfile); int size = debugProfiles.rowCount(); - for (int i=0; i& data) +CSVRender::WorldspaceWidget::DropType CSVRender::WorldspaceWidget::getDropType( + const std::vector& data) { DropType output = Type_Other; - for (std::vector::const_iterator iter (data.begin()); - iter!=data.end(); ++iter) + for (std::vector::const_iterator iter(data.begin()); iter != data.end(); ++iter) { DropType type = Type_Other; - if (iter->getType()==CSMWorld::UniversalId::Type_Cell || - iter->getType()==CSMWorld::UniversalId::Type_Cell_Missing) + if (iter->getType() == CSMWorld::UniversalId::Type_Cell + || iter->getType() == CSMWorld::UniversalId::Type_Cell_Missing) { - type = iter->getId().substr (0, 1)=="#" ? Type_CellsExterior : Type_CellsInterior; + type = iter->getId()[0] == '#' ? Type_CellsExterior : Type_CellsInterior; } - else if (iter->getType()==CSMWorld::UniversalId::Type_DebugProfile) + else if (iter->getType() == CSMWorld::UniversalId::Type_DebugProfile) type = Type_DebugProfile; - if (iter==data.begin()) + if (iter == data.begin()) output = type; - else if (output!=type) // mixed types -> ignore + else if (output != type) // mixed types -> ignore return Type_Other; } return output; } -CSVRender::WorldspaceWidget::dropRequirments - CSVRender::WorldspaceWidget::getDropRequirements (DropType type) const +CSVRender::WorldspaceWidget::dropRequirments CSVRender::WorldspaceWidget::getDropRequirements(DropType type) const { - if (type==Type_DebugProfile) + if (type == Type_DebugProfile) return canHandle; return ignored; } -bool CSVRender::WorldspaceWidget::handleDrop (const std::vector& universalIdData, - DropType type) +bool CSVRender::WorldspaceWidget::handleDrop(const std::vector& universalIdData, DropType type) { - if (type==Type_DebugProfile) + if (type == Type_DebugProfile) { if (mRun) { - for (std::vector::const_iterator iter (universalIdData.begin()); - iter!=universalIdData.end(); ++iter) - mRun->addProfile (iter->getId()); + for (std::vector::const_iterator iter(universalIdData.begin()); + iter != universalIdData.end(); ++iter) + mRun->addProfile(iter->getId()); } return true; @@ -341,7 +344,7 @@ unsigned int CSVRender::WorldspaceWidget::getVisibilityMask() const return mSceneElements->getSelectionMask(); } -void CSVRender::WorldspaceWidget::setInteractionMask (unsigned int mask) +void CSVRender::WorldspaceWidget::setInteractionMask(unsigned int mask) { mInteractionMask = mask | Mask_CellMarker | Mask_CellArrow; } @@ -351,24 +354,23 @@ unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const return mInteractionMask & getVisibilityMask(); } -void CSVRender::WorldspaceWidget::setEditLock (bool locked) +void CSVRender::WorldspaceWidget::setEditLock(bool locked) { - dynamic_cast (*mEditMode->getCurrent()).setEditLock (locked); + dynamic_cast(*mEditMode->getCurrent()).setEditLock(locked); } -void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( - CSVWidget::SceneToolToggle2 *tool) +void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) { - tool->addButton (Button_Reference, Mask_Reference, "Instances"); - tool->addButton (Button_Water, Mask_Water, "Water"); - tool->addButton (Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); + tool->addButton(Button_Reference, Mask_Reference, "Instances"); + tool->addButton(Button_Water, Mask_Water, "Water"); + tool->addButton(Button_Pathgrid, Mask_Pathgrid, "Pathgrid"); } -void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) +void CSVRender::WorldspaceWidget::addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool) { /// \todo replace EditMode with suitable subclasses - tool->addButton (new InstanceMode (this, mRootNode, tool), "object"); - tool->addButton (new PathgridMode (this, tool), "pathgrid"); + tool->addButton(new InstanceMode(this, mRootNode, tool), "object"); + tool->addButton(new PathgridMode(this, tool), "pathgrid"); } CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() @@ -376,27 +378,32 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() return mDocument; } -CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos, - unsigned int interactionMask) const +CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick( + const QPoint& localPos, unsigned int interactionMask) const { + // may be okay to just use devicePixelRatio() directly + QScreen* screen = SceneWidget::windowHandle() && SceneWidget::windowHandle()->screen() + ? SceneWidget::windowHandle()->screen() + : QGuiApplication::primaryScreen(); + // (0,0) is considered the lower left corner of an OpenGL window - int x = localPos.x(); - int y = height() - localPos.y(); + int x = localPos.x() * screen->devicePixelRatio(); + int y = height() * screen->devicePixelRatio() - localPos.y() * screen->devicePixelRatio(); // Convert from screen space to world space osg::Matrixd wpvMat; - wpvMat.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); - wpvMat.preMult (mView->getCamera()->getProjectionMatrix()); - wpvMat.preMult (mView->getCamera()->getViewMatrix()); - wpvMat = osg::Matrixd::inverse (wpvMat); + wpvMat.preMult(mView->getCamera()->getViewport()->computeWindowMatrix()); + wpvMat.preMult(mView->getCamera()->getProjectionMatrix()); + wpvMat.preMult(mView->getCamera()->getViewMatrix()); + wpvMat = osg::Matrixd::inverse(wpvMat); - osg::Vec3d start = wpvMat.preMult (osg::Vec3d(x, y, 0)); - osg::Vec3d end = wpvMat.preMult (osg::Vec3d(x, y, 1)); + osg::Vec3d start = wpvMat.preMult(osg::Vec3d(x, y, 0)); + osg::Vec3d end = wpvMat.preMult(osg::Vec3d(x, y, 1)); osg::Vec3d direction = end - start; // Get intersection - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( - osgUtil::Intersector::MODEL, start, end)); + osg::ref_ptr intersector( + new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, start, end)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); @@ -417,10 +424,11 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo continue; } - for (std::vector::iterator nodeIter = intersection.nodePath.begin(); nodeIter != intersection.nodePath.end(); ++nodeIter) + for (std::vector::iterator nodeIter = intersection.nodePath.begin(); + nodeIter != intersection.nodePath.end(); ++nodeIter) { osg::Node* node = *nodeIter; - if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) + if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) { WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() }; if (intersection.indexList.size() >= 3) @@ -452,115 +460,111 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo return hit; } -CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() +CSVRender::EditMode* CSVRender::WorldspaceWidget::getEditMode() { - return dynamic_cast (mEditMode->getCurrent()); + return dynamic_cast(mEditMode->getCurrent()); } void CSVRender::WorldspaceWidget::abortDrag() { if (mDragging) { - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + EditMode& editMode = dynamic_cast(*mEditMode->getCurrent()); editMode.dragAborted(); - mDragging = false; mDragMode = InteractionType_None; } } -void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) +void CSVRender::WorldspaceWidget::dragEnterEvent(QDragEnterEvent* event) { - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; - if (mime->fromDocument (mDocument)) + if (mime->fromDocument(mDocument)) { - if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || - mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || - mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + if (mime->holdsType(CSMWorld::UniversalId::Type_Cell) + || mime->holdsType(CSMWorld::UniversalId::Type_Cell_Missing) + || mime->holdsType(CSMWorld::UniversalId::Type_DebugProfile)) { // These drops are handled through the subview object. event->accept(); } else - dynamic_cast (*mEditMode->getCurrent()).dragEnterEvent (event); + dynamic_cast(*mEditMode->getCurrent()).dragEnterEvent(event); } } -void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) +void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent* event) { - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; - if (mime->fromDocument (mDocument)) + if (mime->fromDocument(mDocument)) { - if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || - mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || - mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + if (mime->holdsType(CSMWorld::UniversalId::Type_Cell) + || mime->holdsType(CSMWorld::UniversalId::Type_Cell_Missing) + || mime->holdsType(CSMWorld::UniversalId::Type_DebugProfile)) { // These drops are handled through the subview object. event->accept(); } else - dynamic_cast (*mEditMode->getCurrent()).dragMoveEvent (event); + dynamic_cast(*mEditMode->getCurrent()).dragMoveEvent(event); } } -void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) +void CSVRender::WorldspaceWidget::dropEvent(QDropEvent* event) { - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; - if (mime->fromDocument (mDocument)) + if (mime->fromDocument(mDocument)) { - if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || - mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || - mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + if (mime->holdsType(CSMWorld::UniversalId::Type_Cell) + || mime->holdsType(CSMWorld::UniversalId::Type_Cell_Missing) + || mime->holdsType(CSMWorld::UniversalId::Type_DebugProfile)) { emit dataDropped(mime->getData()); } else - dynamic_cast (*mEditMode->getCurrent()).dropEvent (event); + dynamic_cast(*mEditMode->getCurrent()).dropEvent(event); } } -void CSVRender::WorldspaceWidget::runRequest (const std::string& profile) +void CSVRender::WorldspaceWidget::runRequest(const std::string& profile) { - mDocument.startRunning (profile, getStartupInstruction()); + mDocument.startRunning(profile, getStartupInstruction()); } -void CSVRender::WorldspaceWidget::debugProfileDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) +void CSVRender::WorldspaceWidget::debugProfileDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (!mRun) return; - CSMWorld::IdTable& debugProfiles = dynamic_cast ( - *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); + CSMWorld::IdTable& debugProfiles = dynamic_cast( + *mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_DebugProfiles)); - int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); - int stateColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + int idColumn = debugProfiles.findColumnIndex(CSMWorld::Columns::ColumnId_Id); + int stateColumn = debugProfiles.findColumnIndex(CSMWorld::Columns::ColumnId_Modification); - for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { - int state = debugProfiles.data (debugProfiles.index (i, stateColumn)).toInt(); + int state = debugProfiles.data(debugProfiles.index(i, stateColumn)).toInt(); // As of version 0.33 this case can not happen because debug profiles exist only in // project or session scope, which means they will never be in deleted state. But we // are adding the code for the sake of completeness and to avoid surprises if debug // profile ever get extended to content scope. - if (state==CSMWorld::RecordBase::State_Deleted) - mRun->removeProfile (debugProfiles.data ( - debugProfiles.index (i, idColumn)).toString().toUtf8().constData()); + if (state == CSMWorld::RecordBase::State_Deleted) + mRun->removeProfile(debugProfiles.data(debugProfiles.index(i, idColumn)).toString().toUtf8().constData()); } } -void CSVRender::WorldspaceWidget::debugProfileAboutToBeRemoved (const QModelIndex& parent, - int start, int end) +void CSVRender::WorldspaceWidget::debugProfileAboutToBeRemoved(const QModelIndex& parent, int start, int end) { if (parent.isValid()) return; @@ -568,21 +572,20 @@ void CSVRender::WorldspaceWidget::debugProfileAboutToBeRemoved (const QModelInde if (!mRun) return; - CSMWorld::IdTable& debugProfiles = dynamic_cast ( - *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles)); + CSMWorld::IdTable& debugProfiles = dynamic_cast( + *mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_DebugProfiles)); - int idColumn = debugProfiles.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int idColumn = debugProfiles.findColumnIndex(CSMWorld::Columns::ColumnId_Id); - for (int i=start; i<=end; ++i) + for (int i = start; i <= end; ++i) { - mRun->removeProfile (debugProfiles.data ( - debugProfiles.index (i, idColumn)).toString().toUtf8().constData()); + mRun->removeProfile(debugProfiles.data(debugProfiles.index(i, idColumn)).toString().toUtf8().constData()); } } -void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) +void CSVRender::WorldspaceWidget::editModeChanged(const std::string& id) { - dynamic_cast (*mEditMode->getCurrent()).setEditLock (mLocked); + dynamic_cast(*mEditMode->getCurrent()).setEditLock(mLocked); mDragging = false; mDragMode = InteractionType_None; } @@ -593,29 +596,27 @@ void CSVRender::WorldspaceWidget::showToolTip() { QPoint pos = QCursor::pos(); - WorldspaceHitResult hit = mousePick (mapFromGlobal (pos), getInteractionMask()); + WorldspaceHitResult hit = mousePick(mapFromGlobal(pos), getInteractionMask()); if (hit.tag) { bool hideBasics = CSMPrefs::get()["Tooltips"]["scene-hide-basic"].isTrue(); - QToolTip::showText (pos, hit.tag->getToolTip (hideBasics), this); + QToolTip::showText(pos, hit.tag->getToolTip(hideBasics, hit), this); } } } void CSVRender::WorldspaceWidget::elementSelectionChanged() { - setVisibilityMask (getVisibilityMask()); + setVisibilityMask(getVisibilityMask()); flagAsModified(); updateOverlay(); } -void CSVRender::WorldspaceWidget::updateOverlay() -{ -} +void CSVRender::WorldspaceWidget::updateOverlay() {} -void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) +void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event) { - dynamic_cast (*mEditMode->getCurrent()).mouseMoveEvent (event); + dynamic_cast(*mEditMode->getCurrent()).mouseMoveEvent(event); if (mDragging) { @@ -630,22 +631,22 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) if (mSpeedMode) factor *= mDragShiftFactor; - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + EditMode& editMode = dynamic_cast(*mEditMode->getCurrent()); - editMode.drag (event->pos(), diffX, diffY, factor); + editMode.drag(event->pos(), diffX, diffY, factor); } else if (mDragMode != InteractionType_None) { - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + EditMode& editMode = dynamic_cast(*mEditMode->getCurrent()); if (mDragMode == InteractionType_PrimaryEdit) - mDragging = editMode.primaryEditStartDrag (event->pos()); + mDragging = editMode.primaryEditStartDrag(event->pos()); else if (mDragMode == InteractionType_SecondaryEdit) - mDragging = editMode.secondaryEditStartDrag (event->pos()); + mDragging = editMode.secondaryEditStartDrag(event->pos()); else if (mDragMode == InteractionType_PrimarySelect) - mDragging = editMode.primarySelectStartDrag (event->pos()); + mDragging = editMode.primarySelectStartDrag(event->pos()); else if (mDragMode == InteractionType_SecondarySelect) - mDragging = editMode.secondarySelectStartDrag (event->pos()); + mDragging = editMode.secondarySelectStartDrag(event->pos()); if (mDragging) { @@ -655,14 +656,14 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) } else { - if (event->globalPos()!=mToolTipPos) + if (event->globalPos() != mToolTipPos) { mToolTipPos = event->globalPos(); if (mShowToolTips) { QToolTip::hideText(); - mToolTipDelayTimer.start (mToolTipDelay); + mToolTipDelayTimer.start(mToolTipDelay); } } @@ -670,7 +671,7 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) } } -void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) +void CSVRender::WorldspaceWidget::wheelEvent(QWheelEvent* event) { if (mDragging) { @@ -679,27 +680,29 @@ void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) if (mSpeedMode) factor *= mDragShiftFactor; - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - editMode.dragWheel (event->angleDelta().y(), factor); + EditMode& editMode = dynamic_cast(*mEditMode->getCurrent()); + editMode.dragWheel(event->angleDelta().y(), factor); } else SceneWidget::wheelEvent(event); } -void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) +void CSVRender::WorldspaceWidget::handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type) { - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + EditMode& editMode = dynamic_cast(*mEditMode->getCurrent()); if (type == InteractionType_PrimaryEdit) - editMode.primaryEditPressed (hit); + editMode.primaryEditPressed(hit); else if (type == InteractionType_SecondaryEdit) - editMode.secondaryEditPressed (hit); + editMode.secondaryEditPressed(hit); else if (type == InteractionType_PrimarySelect) - editMode.primarySelectPressed (hit); + editMode.primarySelectPressed(hit); else if (type == InteractionType_SecondarySelect) - editMode.secondarySelectPressed (hit); + editMode.secondarySelectPressed(hit); + else if (type == InteractionType_TertiarySelect) + editMode.tertiarySelectPressed(hit); else if (type == InteractionType_PrimaryOpen) - editMode.primaryOpenPressed (hit); + editMode.primaryOpenPressed(hit); } void CSVRender::WorldspaceWidget::primaryOpen(bool activate) @@ -727,6 +730,11 @@ void CSVRender::WorldspaceWidget::secondarySelect(bool activate) handleInteraction(InteractionType_SecondarySelect, activate); } +void CSVRender::WorldspaceWidget::tertiarySelect(bool activate) +{ + handleInteraction(InteractionType_TertiarySelect, activate); +} + void CSVRender::WorldspaceWidget::speedMode(bool activate) { mSpeedMode = activate; diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index cf244ce71..442f4922f 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -1,15 +1,38 @@ #ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H #define OPENCS_VIEW_WORLDSPACEWIDGET_H +#include #include -#include -#include "../../model/doc/document.hpp" -#include "../../model/world/tablemimedata.hpp" +#include +#include + +#include +#include + +#include #include "instancedragmodes.hpp" #include "scenewidget.hpp" -#include "mask.hpp" + +class QDragEnterEvent; +class QDragMoveEvent; +class QDropEvent; +class QModelIndex; +class QMouseEvent; +class QObject; +class QWheelEvent; +class QWidget; + +namespace CSMDoc +{ + class Document; +} + +namespace osg +{ + class Vec3f; +} namespace CSMPrefs { @@ -32,9 +55,7 @@ namespace CSVWidget namespace CSVRender { - class TagBase; class Cell; - class CellArrow; class EditMode; struct WorldspaceHitResult @@ -47,254 +68,249 @@ namespace CSVRender class WorldspaceWidget : public SceneWidget { - Q_OBJECT - - CSVWidget::SceneToolToggle2 *mSceneElements; - CSVWidget::SceneToolRun *mRun; - CSMDoc::Document& mDocument; - unsigned int mInteractionMask; - CSVWidget::SceneToolMode *mEditMode; - bool mLocked; - int mDragMode; - bool mDragging; - int mDragX; - int mDragY; - bool mSpeedMode; - double mDragFactor; - double mDragWheelFactor; - double mDragShiftFactor; - QTimer mToolTipDelayTimer; - QPoint mToolTipPos; - bool mShowToolTips; - int mToolTipDelay; - bool mInConstructor; - - public: - - enum DropType - { - Type_CellsInterior, - Type_CellsExterior, - Type_Other, - Type_DebugProfile - }; - - enum dropRequirments - { - canHandle, - needPaged, - needUnpaged, - ignored //either mixed cells, or not cells - }; + Q_OBJECT + + CSVWidget::SceneToolToggle2* mSceneElements; + CSVWidget::SceneToolRun* mRun; + CSMDoc::Document& mDocument; + unsigned int mInteractionMask; + CSVWidget::SceneToolMode* mEditMode; + bool mLocked; + int mDragMode; + bool mDragging; + int mDragX; + int mDragY; + bool mSpeedMode; + double mDragFactor; + double mDragWheelFactor; + double mDragShiftFactor; + QTimer mToolTipDelayTimer; + QPoint mToolTipPos; + bool mShowToolTips; + int mToolTipDelay; + bool mInConstructor; + + public: + enum DropType + { + Type_CellsInterior, + Type_CellsExterior, + Type_Other, + Type_DebugProfile + }; - enum InteractionType - { - InteractionType_PrimaryEdit, - InteractionType_PrimarySelect, - InteractionType_SecondaryEdit, - InteractionType_SecondarySelect, - InteractionType_PrimaryOpen, - InteractionType_None - }; + enum dropRequirments + { + canHandle, + needPaged, + needUnpaged, + ignored // either mixed cells, or not cells + }; - WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = nullptr); - ~WorldspaceWidget (); + enum InteractionType + { + InteractionType_PrimaryEdit, + InteractionType_PrimarySelect, + InteractionType_SecondaryEdit, + InteractionType_SecondarySelect, + InteractionType_TertiarySelect, + InteractionType_PrimaryOpen, + InteractionType_None + }; - CSVWidget::SceneToolMode *makeNavigationSelector (CSVWidget::SceneToolbar *parent); - ///< \attention The created tool is not added to the toolbar (via addTool). Doing that - /// is the responsibility of the calling function. + WorldspaceWidget(CSMDoc::Document& document, QWidget* parent = nullptr); + ~WorldspaceWidget() = default; + + CSVWidget::SceneToolMode* makeNavigationSelector(CSVWidget::SceneToolbar* parent); + ///< \attention The created tool is not added to the toolbar (via addTool). Doing that + /// is the responsibility of the calling function. - /// \attention The created tool is not added to the toolbar (via addTool). Doing - /// that is the responsibility of the calling function. - CSVWidget::SceneToolToggle2 *makeSceneVisibilitySelector ( - CSVWidget::SceneToolbar *parent); + /// \attention The created tool is not added to the toolbar (via addTool). Doing + /// that is the responsibility of the calling function. + CSVWidget::SceneToolToggle2* makeSceneVisibilitySelector(CSVWidget::SceneToolbar* parent); - /// \attention The created tool is not added to the toolbar (via addTool). Doing - /// that is the responsibility of the calling function. - CSVWidget::SceneToolRun *makeRunTool (CSVWidget::SceneToolbar *parent); + /// \attention The created tool is not added to the toolbar (via addTool). Doing + /// that is the responsibility of the calling function. + CSVWidget::SceneToolRun* makeRunTool(CSVWidget::SceneToolbar* parent); - /// \attention The created tool is not added to the toolbar (via addTool). Doing - /// that is the responsibility of the calling function. - CSVWidget::SceneToolMode *makeEditModeSelector (CSVWidget::SceneToolbar *parent); + /// \attention The created tool is not added to the toolbar (via addTool). Doing + /// that is the responsibility of the calling function. + CSVWidget::SceneToolMode* makeEditModeSelector(CSVWidget::SceneToolbar* parent); - void selectDefaultNavigationMode(); + void selectDefaultNavigationMode(); - void centerOrbitCameraOnSelection(); + void centerOrbitCameraOnSelection(); - static DropType getDropType(const std::vector& data); + static DropType getDropType(const std::vector& data); - virtual dropRequirments getDropRequirements(DropType type) const; + virtual dropRequirments getDropRequirements(DropType type) const; - virtual void useViewHint (const std::string& hint); - ///< Default-implementation: ignored. + virtual void useViewHint(const std::string& hint); + ///< Default-implementation: ignored. - /// \return Drop handled? - virtual bool handleDrop (const std::vector& data, - DropType type); + /// \return Drop handled? + virtual bool handleDrop(const std::vector& data, DropType type); - virtual unsigned int getVisibilityMask() const; + virtual unsigned int getVisibilityMask() const; - /// \note This function will implicitly add elements that are independent of the - /// selected edit mode. - virtual void setInteractionMask (unsigned int mask); + /// \note This function will implicitly add elements that are independent of the + /// selected edit mode. + virtual void setInteractionMask(unsigned int mask); - /// \note This function will only return those elements that are both visible and - /// marked for interaction. - unsigned int getInteractionMask() const; + /// \note This function will only return those elements that are both visible and + /// marked for interaction. + unsigned int getInteractionMask() const; - virtual void setEditLock (bool locked); + virtual void setEditLock(bool locked); - CSMDoc::Document& getDocument(); + CSMDoc::Document& getDocument(); - /// \param elementMask Elements to be affected by the clear operation - virtual void clearSelection (int elementMask) = 0; + /// \param elementMask Elements to be affected by the clear operation + virtual void clearSelection(int elementMask) = 0; - /// \param elementMask Elements to be affected by the select operation - virtual void invertSelection (int elementMask) = 0; + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection(int elementMask) = 0; - /// \param elementMask Elements to be affected by the select operation - virtual void selectAll (int elementMask) = 0; + /// \param elementMask Elements to be affected by the select operation + virtual void selectAll(int elementMask) = 0; - // Select everything that references the same ID as at least one of the elements - // already selected - // - /// \param elementMask Elements to be affected by the select operation - virtual void selectAllWithSameParentId (int elementMask) = 0; + // Select everything that references the same ID as at least one of the elements + // already selected + // + /// \param elementMask Elements to be affected by the select operation + virtual void selectAllWithSameParentId(int elementMask) = 0; - virtual void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) = 0; + virtual void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) = 0; - virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0; + virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0; - /// Return the next intersection with scene elements matched by - /// \a interactionMask based on \a localPos and the camera vector. - /// If there is no such intersection, instead a point "in front" of \a localPos will be - /// returned. - WorldspaceHitResult mousePick (const QPoint& localPos, unsigned int interactionMask) const; + /// Return the next intersection with scene elements matched by + /// \a interactionMask based on \a localPos and the camera vector. + /// If there is no such intersection, instead a point "in front" of \a localPos will be + /// returned. + WorldspaceHitResult mousePick(const QPoint& localPos, unsigned int interactionMask) const; - virtual std::string getCellId (const osg::Vec3f& point) const = 0; + virtual std::string getCellId(const osg::Vec3f& point) const = 0; - /// \note Returns the cell if it exists, otherwise a null pointer - virtual Cell* getCell(const osg::Vec3d& point) const = 0; + /// \note Returns the cell if it exists, otherwise a null pointer + virtual Cell* getCell(const osg::Vec3d& point) const = 0; - virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const = 0; + virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const = 0; - virtual std::vector > getSelection (unsigned int elementMask) - const = 0; + virtual osg::ref_ptr getSnapTarget(unsigned int elementMask) const = 0; - virtual std::vector > getEdited (unsigned int elementMask) - const = 0; + virtual std::vector> getSelection(unsigned int elementMask) const = 0; - virtual void setSubMode (int subMode, unsigned int elementMask) = 0; + virtual std::vector> getEdited(unsigned int elementMask) const = 0; - /// Erase all overrides and restore the visual representation to its true state. - virtual void reset (unsigned int elementMask) = 0; + virtual void setSubMode(int subMode, unsigned int elementMask) = 0; - EditMode *getEditMode(); + /// Erase all overrides and restore the visual representation to its true state. + virtual void reset(unsigned int elementMask) = 0; - protected: + EditMode* getEditMode(); - /// Visual elements in a scene - /// @note do not change the enumeration values, they are used in pre-existing button file names! - enum ButtonId - { - Button_Reference = 0x1, - Button_Pathgrid = 0x2, - Button_Water = 0x4, - Button_Fog = 0x8, - Button_Terrain = 0x10 - }; + protected: + /// Visual elements in a scene + /// @note do not change the enumeration values, they are used in pre-existing button file names! + enum ButtonId + { + Button_Reference = 0x1, + Button_Pathgrid = 0x2, + Button_Water = 0x4, + Button_Fog = 0x8, + Button_Terrain = 0x10 + }; - virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); + virtual void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool); - virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); + virtual void addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool); - virtual void updateOverlay(); + virtual void updateOverlay(); - void mouseMoveEvent (QMouseEvent *event) override; - void wheelEvent (QWheelEvent *event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void wheelEvent(QWheelEvent* event) override; - virtual void handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type); + virtual void handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type); - void settingChanged (const CSMPrefs::Setting *setting) override; + void settingChanged(const CSMPrefs::Setting* setting) override; - bool getSpeedMode(); + bool getSpeedMode(); - private: + private: + void dragEnterEvent(QDragEnterEvent* event) override; - void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent* event) override; - void dropEvent(QDropEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; - void dragMoveEvent(QDragMoveEvent *event) override; + virtual std::string getStartupInstruction() = 0; - virtual std::string getStartupInstruction() = 0; + void handleInteraction(InteractionType type, bool activate); - void handleInteraction(InteractionType type, bool activate); + public slots: - public slots: + /// \note Drags will be automatically aborted when the aborting is triggered + /// (either explicitly or implicitly) from within this class. This function only + /// needs to be called, when the drag abort is triggered externally (e.g. from + /// an edit mode). + void abortDrag(); - /// \note Drags will be automatically aborted when the aborting is triggered - /// (either explicitly or implicitly) from within this class. This function only - /// needs to be called, when the drag abort is triggered externally (e.g. from - /// an edit mode). - void abortDrag(); + private slots: - private slots: + virtual void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; - virtual void referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) = 0; + virtual void referenceableAboutToBeRemoved(const QModelIndex& parent, int start, int end) = 0; - virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; + virtual void referenceableAdded(const QModelIndex& index, int start, int end) = 0; - virtual void referenceableAdded (const QModelIndex& index, int start, int end) = 0; + virtual void referenceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; - virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; + virtual void referenceAboutToBeRemoved(const QModelIndex& parent, int start, int end) = 0; - virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; + virtual void referenceAdded(const QModelIndex& index, int start, int end) = 0; - virtual void referenceAdded (const QModelIndex& index, int start, int end) = 0; + virtual void pathgridDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; - virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; + virtual void pathgridAboutToBeRemoved(const QModelIndex& parent, int start, int end) = 0; - virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; + virtual void pathgridAdded(const QModelIndex& parent, int start, int end) = 0; - virtual void pathgridAdded (const QModelIndex& parent, int start, int end) = 0; + virtual void runRequest(const std::string& profile); + void debugProfileDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - virtual void runRequest (const std::string& profile); + void debugProfileAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void debugProfileDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight); + void editModeChanged(const std::string& id); - void debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void showToolTip(); - void editModeChanged (const std::string& id); + void primaryOpen(bool activate); - void showToolTip(); + void primaryEdit(bool activate); - void primaryOpen(bool activate); + void secondaryEdit(bool activate); - void primaryEdit(bool activate); + void primarySelect(bool activate); - void secondaryEdit(bool activate); + void secondarySelect(bool activate); - void primarySelect(bool activate); + void tertiarySelect(bool activate); - void secondarySelect(bool activate); + void speedMode(bool activate); - void speedMode(bool activate); + protected slots: - protected slots: + void elementSelectionChanged(); - void elementSelectionChanged(); + signals: - signals: + void closeRequest(); - void closeRequest(); + void dataDropped(const std::vector& data); - void dataDropped(const std::vector& data); - - void requestFocus (const std::string& id); + void requestFocus(const std::string& id); friend class MouseState; }; diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp index f50a85f2f..0e67a1b1d 100644 --- a/apps/opencs/view/tools/merge.cpp +++ b/apps/opencs/view/tools/merge.cpp @@ -1,116 +1,119 @@ #include "merge.hpp" -#include #include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include + +#include #include "../../model/doc/document.hpp" #include "../../model/doc/documentmanager.hpp" +#include "../../model/doc/state.hpp" -#include "../doc/filewidget.hpp" #include "../doc/adjusterwidget.hpp" +#include "../doc/filewidget.hpp" -void CSVTools::Merge::keyPressEvent (QKeyEvent *event) +void CSVTools::Merge::keyPressEvent(QKeyEvent* event) { - if (event->key()==Qt::Key_Escape) + if (event->key() == Qt::Key_Escape) { event->accept(); cancel(); } else - QWidget::keyPressEvent (event); + QWidget::keyPressEvent(event); } -CSVTools::Merge::Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent) -: QWidget (parent), mDocument (nullptr), mDocumentManager (documentManager) +CSVTools::Merge::Merge(CSMDoc::DocumentManager& documentManager, QWidget* parent) + : QWidget(parent) + , mDocument(nullptr) + , mDocumentManager(documentManager) { - setWindowTitle ("Merge Content Files into a new Game File"); + setWindowTitle("Merge Content Files into a new Game File"); - QVBoxLayout *mainLayout = new QVBoxLayout; - setLayout (mainLayout); + QVBoxLayout* mainLayout = new QVBoxLayout; + setLayout(mainLayout); - QSplitter *splitter = new QSplitter (Qt::Horizontal, this); + QSplitter* splitter = new QSplitter(Qt::Horizontal, this); - mainLayout->addWidget (splitter, 1); + mainLayout->addWidget(splitter, 1); // left panel (files to be merged) - QWidget *left = new QWidget (this); - left->setContentsMargins (0, 0, 0, 0); - splitter->addWidget (left); + QWidget* left = new QWidget(this); + left->setContentsMargins(0, 0, 0, 0); + splitter->addWidget(left); - QVBoxLayout *leftLayout = new QVBoxLayout; - left->setLayout (leftLayout); + QVBoxLayout* leftLayout = new QVBoxLayout; + left->setLayout(leftLayout); - leftLayout->addWidget (new QLabel ("Files to be merged", this)); + leftLayout->addWidget(new QLabel("Files to be merged", this)); - mFiles = new QListWidget (this); - leftLayout->addWidget (mFiles, 1); + mFiles = new QListWidget(this); + leftLayout->addWidget(mFiles, 1); // right panel (new game file) - QWidget *right = new QWidget (this); - right->setContentsMargins (0, 0, 0, 0); - splitter->addWidget (right); + QWidget* right = new QWidget(this); + right->setContentsMargins(0, 0, 0, 0); + splitter->addWidget(right); - QVBoxLayout *rightLayout = new QVBoxLayout; - rightLayout->setAlignment (Qt::AlignTop); - right->setLayout (rightLayout); + QVBoxLayout* rightLayout = new QVBoxLayout; + rightLayout->setAlignment(Qt::AlignTop); + right->setLayout(rightLayout); - rightLayout->addWidget (new QLabel ("New game file", this)); + rightLayout->addWidget(new QLabel("New game file", this)); - mNewFile = new CSVDoc::FileWidget (this); - mNewFile->setType (false); - mNewFile->extensionLabelIsVisible (true); - rightLayout->addWidget (mNewFile); + mNewFile = new CSVDoc::FileWidget(this); + mNewFile->setType(false); + mNewFile->extensionLabelIsVisible(true); + rightLayout->addWidget(mNewFile); - mAdjuster = new CSVDoc::AdjusterWidget (this); + mAdjuster = new CSVDoc::AdjusterWidget(this); - rightLayout->addWidget (mAdjuster); + rightLayout->addWidget(mAdjuster); - connect (mNewFile, SIGNAL (nameChanged (const QString&, bool)), - mAdjuster, SLOT (setName (const QString&, bool))); - connect (mAdjuster, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); + connect(mNewFile, &CSVDoc::FileWidget::nameChanged, mAdjuster, &CSVDoc::AdjusterWidget::setName); + connect(mAdjuster, &CSVDoc::AdjusterWidget::stateChanged, this, &Merge::stateChanged); // buttons - QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, this); - connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (cancel())); + connect(buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &Merge::cancel); - mOkay = new QPushButton ("Merge", this); - connect (mOkay, SIGNAL (clicked()), this, SLOT (accept())); - mOkay->setDefault (true); - buttons->addButton (mOkay, QDialogButtonBox::AcceptRole); + mOkay = new QPushButton("Merge", this); + connect(mOkay, &QPushButton::clicked, this, &Merge::accept); + mOkay->setDefault(true); + buttons->addButton(mOkay, QDialogButtonBox::AcceptRole); - mainLayout->addWidget (buttons); + mainLayout->addWidget(buttons); } -void CSVTools::Merge::configure (CSMDoc::Document *document) +void CSVTools::Merge::configure(CSMDoc::Document* document) { mDocument = document; - mNewFile->setName (""); + mNewFile->setName(""); // content files while (mFiles->count()) - delete mFiles->takeItem (0); + delete mFiles->takeItem(0); - std::vector files = document->getContentFiles(); + std::vector files = document->getContentFiles(); - for (std::vector::const_iterator iter (files.begin()); - iter!=files.end(); ++iter) - mFiles->addItem (QString::fromUtf8 (iter->filename().string().c_str())); + for (std::vector::const_iterator iter(files.begin()); iter != files.end(); ++iter) + mFiles->addItem(Files::pathToQString(iter->filename())); } -void CSVTools::Merge::setLocalData (const boost::filesystem::path& localData) +void CSVTools::Merge::setLocalData(const std::filesystem::path& localData) { - mAdjuster->setLocalData (localData); + mAdjuster->setLocalData(localData); } -CSMDoc::Document *CSVTools::Merge::getDocument() const +CSMDoc::Document* CSVTools::Merge::getDocument() const { return mDocument; } @@ -123,20 +126,19 @@ void CSVTools::Merge::cancel() void CSVTools::Merge::accept() { - if ((mDocument->getState() & CSMDoc::State_Merging)==0) + if ((mDocument->getState() & CSMDoc::State_Merging) == 0) { - std::vector< boost::filesystem::path > files (1, mAdjuster->getPath()); + std::vector files{ mAdjuster->getPath() }; - std::unique_ptr target ( - mDocumentManager.makeDocument (files, files[0], true)); + std::unique_ptr target(mDocumentManager.makeDocument(files, files[0], true)); - mDocument->runMerge (std::move(target)); + mDocument->runMerge(std::move(target)); hide(); } } -void CSVTools::Merge::stateChanged (bool valid) +void CSVTools::Merge::stateChanged(bool valid) { - mOkay->setEnabled (valid); + mOkay->setEnabled(valid); } diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp index d394a431e..cfb36d261 100644 --- a/apps/opencs/view/tools/merge.hpp +++ b/apps/opencs/view/tools/merge.hpp @@ -1,11 +1,9 @@ -#ifndef CSV_TOOLS_REPORTTABLE_H -#define CSV_TOOLS_REPORTTABLE_H +#ifndef CSV_TOOLS_MERGE_H +#define CSV_TOOLS_MERGE_H #include -#ifndef Q_MOC_RUN -#include -#endif +#include class QPushButton; class QListWidget; @@ -26,37 +24,36 @@ namespace CSVTools { class Merge : public QWidget { - Q_OBJECT + Q_OBJECT - CSMDoc::Document *mDocument; - QPushButton *mOkay; - QListWidget *mFiles; - CSVDoc::FileWidget *mNewFile; - CSVDoc::AdjusterWidget *mAdjuster; - CSMDoc::DocumentManager& mDocumentManager; + CSMDoc::Document* mDocument; + QPushButton* mOkay; + QListWidget* mFiles; + CSVDoc::FileWidget* mNewFile; + CSVDoc::AdjusterWidget* mAdjuster; + CSMDoc::DocumentManager& mDocumentManager; - void keyPressEvent (QKeyEvent *event) override; + void keyPressEvent(QKeyEvent* event) override; - public: + public: + Merge(CSMDoc::DocumentManager& documentManager, QWidget* parent = nullptr); - Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = nullptr); + /// Configure dialogue for a new merge + void configure(CSMDoc::Document* document); - /// Configure dialogue for a new merge - void configure (CSMDoc::Document *document); + void setLocalData(const std::filesystem::path& localData); - void setLocalData (const boost::filesystem::path& localData); + CSMDoc::Document* getDocument() const; - CSMDoc::Document *getDocument() const; + public slots: - public slots: + void cancel(); - void cancel(); + private slots: - private slots: + void accept(); - void accept(); - - void stateChanged (bool valid); + void stateChanged(bool valid); }; } diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index c7712f29c..f9603aca6 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -2,27 +2,32 @@ #include "reporttable.hpp" -CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: CSVDoc::SubView (id), mDocument (document), mRefreshState (0) +#include + +#include "../../model/doc/document.hpp" +#include "../../model/doc/state.hpp" + +CSVTools::ReportSubView::ReportSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) + : CSVDoc::SubView(id) + , mDocument(document) + , mRefreshState(0) { - if (id.getType()==CSMWorld::UniversalId::Type_VerificationResults) + if (id.getType() == CSMWorld::UniversalId::Type_VerificationResults) mRefreshState = CSMDoc::State_Verifying; - setWidget (mTable = new ReportTable (document, id, false, mRefreshState, this)); + setWidget(mTable = new ReportTable(document, id, false, mRefreshState, this)); - connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), - SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); + connect(mTable, &ReportTable::editRequest, this, &ReportSubView::focusId); - if (mRefreshState==CSMDoc::State_Verifying) + if (mRefreshState == CSMDoc::State_Verifying) { - connect (mTable, SIGNAL (refreshRequest()), this, SLOT (refreshRequest())); + connect(mTable, &ReportTable::refreshRequest, this, &ReportSubView::refreshRequest); - connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), - mTable, SLOT (stateChanged (int, CSMDoc::Document *))); + connect(&document, &CSMDoc::Document::stateChanged, mTable, &ReportTable::stateChanged); } } -void CSVTools::ReportSubView::setEditLock (bool locked) +void CSVTools::ReportSubView::setEditLock(bool locked) { // ignored. We don't change document state anyway. } @@ -31,10 +36,10 @@ void CSVTools::ReportSubView::refreshRequest() { if (!(mDocument.getState() & mRefreshState)) { - if (mRefreshState==CSMDoc::State_Verifying) + if (mRefreshState == CSMDoc::State_Verifying) { mTable->clear(); - mDocument.verify (getUniversalId()); + mDocument.verify(getUniversalId()); } } } diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp index 6d48690b4..5b0cef5b1 100644 --- a/apps/opencs/view/tools/reportsubview.hpp +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -2,9 +2,9 @@ #define CSV_TOOLS_REPORTSUBVIEW_H #include "../doc/subview.hpp" +#include -class QTableView; -class QModelIndex; +class QObject; namespace CSMDoc { @@ -17,21 +17,20 @@ namespace CSVTools class ReportSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - ReportTable *mTable; - CSMDoc::Document& mDocument; - int mRefreshState; + ReportTable* mTable; + CSMDoc::Document& mDocument; + int mRefreshState; - public: + public: + ReportSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document); - ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + void setEditLock(bool locked) override; - void setEditLock (bool locked) override; + private slots: - private slots: - - void refreshRequest(); + void refreshRequest(); }; } diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index c1297d475..7ec55b96b 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -1,21 +1,28 @@ #include "reporttable.hpp" #include +#include -#include #include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include + +#include +#include +#include +#include +#include #include "../../model/tools/reportmodel.hpp" -#include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/state.hpp" #include "../../view/world/idtypedelegate.hpp" @@ -23,52 +30,50 @@ namespace CSVTools { class RichTextDelegate : public QStyledItemDelegate { - public: + public: + RichTextDelegate(QObject* parent = nullptr); - RichTextDelegate (QObject *parent = nullptr); - - void paint(QPainter *painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; } -CSVTools::RichTextDelegate::RichTextDelegate (QObject *parent) : QStyledItemDelegate (parent) -{} +CSVTools::RichTextDelegate::RichTextDelegate(QObject* parent) + : QStyledItemDelegate(parent) +{ +} -void CSVTools::RichTextDelegate::paint(QPainter *painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const +void CSVTools::RichTextDelegate::paint( + QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QTextDocument document; - QVariant value = index.data (Qt::DisplayRole); + QVariant value = index.data(Qt::DisplayRole); if (value.isValid() && !value.isNull()) { - document.setHtml (value.toString()); - painter->translate (option.rect.topLeft()); - document.drawContents (painter); - painter->translate (-option.rect.topLeft()); + document.setHtml(value.toString()); + painter->translate(option.rect.topLeft()); + document.drawContents(painter); + painter->translate(-option.rect.topLeft()); } } - -void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) +void CSVTools::ReportTable::contextMenuEvent(QContextMenuEvent* event) { QModelIndexList selectedRows = selectionModel()->selectedRows(); // create context menu - QMenu menu (this); + QMenu menu(this); if (!selectedRows.empty()) { - menu.addAction (mShowAction); - menu.addAction (mRemoveAction); + menu.addAction(mShowAction); + menu.addAction(mRemoveAction); bool found = false; - for (QModelIndexList::const_iterator iter (selectedRows.begin()); - iter!=selectedRows.end(); ++iter) + for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter) { - QString hint = mProxyModel->data (mProxyModel->index (iter->row(), 2)).toString(); + QString hint = mProxyModel->data(mProxyModel->index(iter->row(), 2)).toString(); - if (!hint.isEmpty() && hint[0]=='R') + if (!hint.isEmpty() && hint[0] == 'R') { found = true; break; @@ -76,35 +81,33 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) } if (found) - menu.addAction (mReplaceAction); + menu.addAction(mReplaceAction); } if (mRefreshAction) - menu.addAction (mRefreshAction); + menu.addAction(mRefreshAction); - menu.exec (event->globalPos()); + menu.exec(event->globalPos()); } -void CSVTools::ReportTable::mouseMoveEvent (QMouseEvent *event) +void CSVTools::ReportTable::mouseMoveEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) - startDragFromTable (*this); + startDragFromTable(*this, indexAt(event->pos())); } -void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) +void CSVTools::ReportTable::mouseDoubleClickEvent(QMouseEvent* event) { - Qt::KeyboardModifiers modifiers = - event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier); + Qt::KeyboardModifiers modifiers = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier); QModelIndex index = currentIndex(); - selectionModel()->select (index, - QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows); + selectionModel()->select( + index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows); - std::map::iterator iter = - mDoubleClickActions.find (modifiers); + std::map::iterator iter = mDoubleClickActions.find(modifiers); - if (iter==mDoubleClickActions.end()) + if (iter == mDoubleClickActions.end()) { event->accept(); return; @@ -138,69 +141,68 @@ void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) } } -CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, - const CSMWorld::UniversalId& id, bool richTextDescription, int refreshState, - QWidget *parent) -: CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)), - mRefreshAction (nullptr), mRefreshState (refreshState) +CSVTools::ReportTable::ReportTable(CSMDoc::Document& document, const CSMWorld::UniversalId& id, + bool richTextDescription, int refreshState, QWidget* parent) + : CSVWorld::DragRecordTable(document, parent) + , mModel(document.getReport(id)) + , mRefreshAction(nullptr) + , mRefreshState(refreshState) { - horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); - horizontalHeader()->setStretchLastSection (true); + horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + horizontalHeader()->setStretchLastSection(true); verticalHeader()->hide(); - setSortingEnabled (true); - setSelectionBehavior (QAbstractItemView::SelectRows); - setSelectionMode (QAbstractItemView::ExtendedSelection); + setSortingEnabled(true); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::ExtendedSelection); - mProxyModel = new QSortFilterProxyModel (this); + mProxyModel = new QSortFilterProxyModel(this); mProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - mProxyModel->setSourceModel (mModel); + mProxyModel->setSourceModel(mModel); mProxyModel->setSortRole(Qt::UserRole); - setModel (mProxyModel); - setColumnHidden (2, true); + setModel(mProxyModel); + setColumnHidden(2, true); - mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate (nullptr, - mDocument, this); + mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate(nullptr, mDocument, this); - setItemDelegateForColumn (0, mIdTypeDelegate); + setItemDelegateForColumn(0, mIdTypeDelegate); if (richTextDescription) - setItemDelegateForColumn (mModel->columnCount()-1, new RichTextDelegate (this)); + setItemDelegateForColumn(mModel->columnCount() - 1, new RichTextDelegate(this)); - mShowAction = new QAction (tr ("Show"), this); - connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); - addAction (mShowAction); + mShowAction = new QAction(tr("Show"), this); + connect(mShowAction, &QAction::triggered, this, &ReportTable::showSelection); + addAction(mShowAction); CSMPrefs::Shortcut* showShortcut = new CSMPrefs::Shortcut("reporttable-show", this); showShortcut->associateAction(mShowAction); - mRemoveAction = new QAction (tr ("Remove from list"), this); - connect (mRemoveAction, SIGNAL (triggered()), this, SLOT (removeSelection())); - addAction (mRemoveAction); + mRemoveAction = new QAction(tr("Remove from list"), this); + connect(mRemoveAction, &QAction::triggered, this, &ReportTable::removeSelection); + addAction(mRemoveAction); CSMPrefs::Shortcut* removeShortcut = new CSMPrefs::Shortcut("reporttable-remove", this); removeShortcut->associateAction(mRemoveAction); - mReplaceAction = new QAction (tr ("Replace"), this); - connect (mReplaceAction, SIGNAL (triggered()), this, SIGNAL (replaceRequest())); - addAction (mReplaceAction); + mReplaceAction = new QAction(tr("Replace"), this); + connect(mReplaceAction, &QAction::triggered, this, &ReportTable::replaceRequest); + addAction(mReplaceAction); CSMPrefs::Shortcut* replaceShortcut = new CSMPrefs::Shortcut("reporttable-replace", this); replaceShortcut->associateAction(mReplaceAction); if (mRefreshState) { - mRefreshAction = new QAction (tr ("Refresh"), this); - mRefreshAction->setEnabled (!(mDocument.getState() & mRefreshState)); - connect (mRefreshAction, SIGNAL (triggered()), this, SIGNAL (refreshRequest())); - addAction (mRefreshAction); + mRefreshAction = new QAction(tr("Refresh"), this); + mRefreshAction->setEnabled(!(mDocument.getState() & mRefreshState)); + connect(mRefreshAction, &QAction::triggered, this, &ReportTable::refreshRequest); + addAction(mRefreshAction); CSMPrefs::Shortcut* refreshShortcut = new CSMPrefs::Shortcut("reporttable-refresh", this); refreshShortcut->associateAction(mRefreshAction); } - mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_Edit)); - mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_Remove)); - mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); + mDoubleClickActions.insert(std::make_pair(Qt::NoModifier, Action_Edit)); + mDoubleClickActions.insert(std::make_pair(Qt::ShiftModifier, Action_Remove)); + mDoubleClickActions.insert(std::make_pair(Qt::ControlModifier, Action_EditAndRemove)); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &ReportTable::settingChanged); CSMPrefs::get()["Reports"].update(); } @@ -210,16 +212,15 @@ std::vector CSVTools::ReportTable::getDraggedRecords() co QModelIndexList selectedRows = selectionModel()->selectedRows(); - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) + for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter) { - ids.push_back (mModel->getUniversalId (mProxyModel->mapToSource (*iter).row())); + ids.push_back(mModel->getUniversalId(mProxyModel->mapToSource(*iter).row())); } return ids; } -std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const +std::vector CSVTools::ReportTable::getReplaceIndices(bool selection) const { std::vector indices; @@ -229,68 +230,67 @@ std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const std::vector rows; - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) + for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter) { - rows.push_back (mProxyModel->mapToSource (*iter).row()); + rows.push_back(mProxyModel->mapToSource(*iter).row()); } - std::sort (rows.begin(), rows.end()); + std::sort(rows.begin(), rows.end()); - for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + for (std::vector::const_iterator iter(rows.begin()); iter != rows.end(); ++iter) { - QString hint = mModel->data (mModel->index (*iter, 2)).toString(); + QString hint = mModel->data(mModel->index(*iter, 2)).toString(); - if (!hint.isEmpty() && hint[0]=='R') - indices.push_back (*iter); + if (!hint.isEmpty() && hint[0] == 'R') + indices.push_back(*iter); } } else { - for (int i=0; irowCount(); ++i) + for (int i = 0; i < mModel->rowCount(); ++i) { - QString hint = mModel->data (mModel->index (i, 2)).toString(); + QString hint = mModel->data(mModel->index(i, 2)).toString(); - if (!hint.isEmpty() && hint[0]=='R') - indices.push_back (i); + if (!hint.isEmpty() && hint[0] == 'R') + indices.push_back(i); } } return indices; } -void CSVTools::ReportTable::flagAsReplaced (int index) +void CSVTools::ReportTable::flagAsReplaced(int index) { - mModel->flagAsReplaced (index); + mModel->flagAsReplaced(index); } -void CSVTools::ReportTable::settingChanged (const CSMPrefs::Setting *setting) +void CSVTools::ReportTable::settingChanged(const CSMPrefs::Setting* setting) { - if (setting->getParent()->getKey()=="Reports") + if (setting->getParent()->getKey() == "Reports") { - QString base ("double"); + QString base("double"); QString key = setting->getKey().c_str(); - if (key.startsWith (base)) + if (key.startsWith(base)) { - QString modifierString = key.mid (base.size()); + QString modifierString = key.mid(base.size()); Qt::KeyboardModifiers modifiers; - if (modifierString=="-s") + if (modifierString == "-s") modifiers = Qt::ShiftModifier; - else if (modifierString=="-c") + else if (modifierString == "-c") modifiers = Qt::ControlModifier; - else if (modifierString=="-sc") + else if (modifierString == "-sc") modifiers = Qt::ShiftModifier | Qt::ControlModifier; DoubleClickAction action = Action_None; std::string value = setting->toString(); - if (value=="Edit") + if (value == "Edit") action = Action_Edit; - else if (value=="Remove") + else if (value == "Remove") action = Action_Remove; - else if (value=="Edit And Remove") + else if (value == "Edit And Remove") action = Action_EditAndRemove; mDoubleClickActions[modifiers] = action; @@ -298,19 +298,18 @@ void CSVTools::ReportTable::settingChanged (const CSMPrefs::Setting *setting) return; } } - else if (*setting=="Records/type-format") - mIdTypeDelegate->settingChanged (setting); + else if (*setting == "Records/type-format") + mIdTypeDelegate->settingChanged(setting); } void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) + for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter) { - int row = mProxyModel->mapToSource (*iter).row(); - emit editRequest (mModel->getUniversalId (row), mModel->getHint (row)); + int row = mProxyModel->mapToSource(*iter).row(); + emit editRequest(mModel->getUniversalId(row), mModel->getHint(row)); } } @@ -320,16 +319,15 @@ void CSVTools::ReportTable::removeSelection() std::vector rows; - for (QModelIndexList::iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) + for (QModelIndexList::iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter) { - rows.push_back (mProxyModel->mapToSource (*iter).row()); + rows.push_back(mProxyModel->mapToSource(*iter).row()); } - std::sort (rows.begin(), rows.end()); + std::sort(rows.begin(), rows.end()); - for (std::vector::const_reverse_iterator iter (rows.rbegin()); iter!=rows.rend(); ++iter) - mProxyModel->removeRows (*iter, 1); + for (std::vector::const_reverse_iterator iter(rows.rbegin()); iter != rows.rend(); ++iter) + mProxyModel->removeRows(*iter, 1); selectionModel()->clear(); } @@ -339,8 +337,8 @@ void CSVTools::ReportTable::clear() mModel->clear(); } -void CSVTools::ReportTable::stateChanged (int state, CSMDoc::Document *document) +void CSVTools::ReportTable::stateChanged(int state, CSMDoc::Document* document) { if (mRefreshAction) - mRefreshAction->setEnabled (!(state & mRefreshState)); + mRefreshAction->setEnabled(!(state & mRefreshState)); } diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index f39dd6f85..25f8d1b01 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -2,11 +2,24 @@ #define CSV_TOOLS_REPORTTABLE_H #include +#include +#include + +#include #include "../world/dragrecordtable.hpp" class QAction; class QSortFilterProxyModel; +class QContextMenuEvent; +class QMouseEvent; +class QObject; +class QWidget; + +namespace CSMDoc +{ + class Document; +} namespace CSMTools { @@ -27,76 +40,74 @@ namespace CSVTools { class ReportTable : public CSVWorld::DragRecordTable { - Q_OBJECT + Q_OBJECT - enum DoubleClickAction - { - Action_None, - Action_Edit, - Action_Remove, - Action_EditAndRemove - }; + enum DoubleClickAction + { + Action_None, + Action_Edit, + Action_Remove, + Action_EditAndRemove + }; - QSortFilterProxyModel *mProxyModel; - CSMTools::ReportModel *mModel; - CSVWorld::CommandDelegate *mIdTypeDelegate; - QAction *mShowAction; - QAction *mRemoveAction; - QAction *mReplaceAction; - QAction *mRefreshAction; - std::map mDoubleClickActions; - int mRefreshState; + QSortFilterProxyModel* mProxyModel; + CSMTools::ReportModel* mModel; + CSVWorld::CommandDelegate* mIdTypeDelegate; + QAction* mShowAction; + QAction* mRemoveAction; + QAction* mReplaceAction; + QAction* mRefreshAction; + std::map mDoubleClickActions; + int mRefreshState; - private: + private: + void contextMenuEvent(QContextMenuEvent* event) override; - void contextMenuEvent (QContextMenuEvent *event) override; + void mouseMoveEvent(QMouseEvent* event) override; - void mouseMoveEvent (QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; - void mouseDoubleClickEvent (QMouseEvent *event) override; + public: + /// \param richTextDescription Use rich text in the description column. + /// \param refreshState Document state to check for refresh function. If value is + /// 0 no refresh function exists. If the document current has the specified state + /// the refresh function is disabled. + ReportTable(CSMDoc::Document& document, const CSMWorld::UniversalId& id, bool richTextDescription, + int refreshState = 0, QWidget* parent = nullptr); - public: + std::vector getDraggedRecords() const override; - /// \param richTextDescription Use rich text in the description column. - /// \param refreshState Document state to check for refresh function. If value is - /// 0 no refresh function exists. If the document current has the specified state - /// the refresh function is disabled. - ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, - bool richTextDescription, int refreshState = 0, QWidget *parent = nullptr); + void clear(); - std::vector getDraggedRecords() const override; + /// Return indices of rows that are suitable for replacement. + /// + /// \param selection Only list selected rows. + /// + /// \return rows in the original model + std::vector getReplaceIndices(bool selection) const; - void clear(); + /// \param index row in the original model + void flagAsReplaced(int index); - /// Return indices of rows that are suitable for replacement. - /// - /// \param selection Only list selected rows. - /// - /// \return rows in the original model - std::vector getReplaceIndices (bool selection) const; + private slots: - /// \param index row in the original model - void flagAsReplaced (int index); + void settingChanged(const CSMPrefs::Setting* setting); - private slots: + void showSelection(); - void settingChanged (const CSMPrefs::Setting *setting); + void removeSelection(); - void showSelection(); + public slots: - void removeSelection(); + void stateChanged(int state, CSMDoc::Document* document); - public slots: + signals: - void stateChanged (int state, CSMDoc::Document *document); + void editRequest(const CSMWorld::UniversalId& id, const std::string& hint); - signals: + void replaceRequest(); - void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); - - void replaceRequest(); - - void refreshRequest(); + void refreshRequest(); }; } diff --git a/apps/opencs/view/tools/searchbox.cpp b/apps/opencs/view/tools/searchbox.cpp index e0f965e3c..a2871b548 100644 --- a/apps/opencs/view/tools/searchbox.cpp +++ b/apps/opencs/view/tools/searchbox.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include "../../model/world/columns.hpp" @@ -13,7 +11,7 @@ void CSVTools::SearchBox::updateSearchButton() { if (!mSearchEnabled) - mSearch.setEnabled (false); + mSearch.setEnabled(false); else { switch (mMode.currentIndex()) @@ -23,75 +21,77 @@ void CSVTools::SearchBox::updateSearchButton() case 2: case 3: - mSearch.setEnabled (!mText.text().isEmpty()); + mSearch.setEnabled(!mText.text().isEmpty()); break; case 4: - mSearch.setEnabled (true); + mSearch.setEnabled(true); break; } } } -CSVTools::SearchBox::SearchBox (QWidget *parent) -: QWidget (parent), mSearch (tr("Search")), mSearchEnabled (false), mReplace (tr("Replace All")) +CSVTools::SearchBox::SearchBox(QWidget* parent) + : QWidget(parent) + , mSearch(tr("Search")) + , mSearchEnabled(false) + , mReplace(tr("Replace All")) { - mLayout = new QGridLayout (this); + mLayout = new QGridLayout(this); // search panel - std::vector> states = - CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); - states.resize (states.size()-1); // ignore erased state + std::vector> states + = CSMWorld::Columns::getEnums(CSMWorld::Columns::ColumnId_Modification); + states.resize(states.size() - 1); // ignore erased state - for (std::vector>::const_iterator iter (states.begin()); iter!=states.end(); - ++iter) - mRecordState.addItem (QString::fromUtf8 (iter->second.c_str())); - - mMode.addItem (tr("Text")); - mMode.addItem (tr("Text (RegEx)")); - mMode.addItem (tr("ID")); - mMode.addItem (tr("ID (RegEx)")); - mMode.addItem (tr("Record State")); - connect (&mMode, SIGNAL (activated (int)), this, SLOT (modeSelected (int))); - mLayout->addWidget (&mMode, 0, 0); + for (std::vector>::const_iterator iter(states.begin()); iter != states.end(); ++iter) + mRecordState.addItem(QString::fromUtf8(iter->second.c_str())); - connect (&mText, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); - connect (&mText, SIGNAL (returnPressed()), this, SLOT (startSearch())); - mInput.insertWidget (0, &mText); + mMode.addItem(tr("Text")); + mMode.addItem(tr("Text (RegEx)")); + mMode.addItem(tr("ID")); + mMode.addItem(tr("ID (RegEx)")); + mMode.addItem(tr("Record State")); + connect(&mMode, qOverload(&QComboBox::activated), this, &SearchBox::modeSelected); + mLayout->addWidget(&mMode, 0, 0); - mInput.insertWidget (1, &mRecordState); - mLayout->addWidget (&mInput, 0, 1); + connect(&mText, &QLineEdit::textChanged, this, &SearchBox::textChanged); + connect(&mText, &QLineEdit::returnPressed, this, [this]() { this->startSearch(false); }); + mInput.insertWidget(0, &mText); - mCaseSensitive.setText (tr ("Case")); - mLayout->addWidget (&mCaseSensitive, 0, 2); + mInput.insertWidget(1, &mRecordState); + mLayout->addWidget(&mInput, 0, 1); - connect (&mSearch, SIGNAL (clicked (bool)), this, SLOT (startSearch (bool))); - mLayout->addWidget (&mSearch, 0, 3); + mCaseSensitive.setText(tr("Case")); + mLayout->addWidget(&mCaseSensitive, 0, 2); + + connect(&mSearch, &QPushButton::clicked, this, qOverload(&SearchBox::startSearch)); + mLayout->addWidget(&mSearch, 0, 3); // replace panel - mReplaceInput.insertWidget (0, &mReplaceText); - mReplaceInput.insertWidget (1, &mReplacePlaceholder); + mReplaceInput.insertWidget(0, &mReplaceText); + mReplaceInput.insertWidget(1, &mReplacePlaceholder); - mLayout->addWidget (&mReplaceInput, 1, 1); + mLayout->addWidget(&mReplaceInput, 1, 1); + + mLayout->addWidget(&mReplace, 1, 3); - mLayout->addWidget (&mReplace, 1, 3); - // layout adjustments - mLayout->setColumnMinimumWidth (2, 50); - mLayout->setColumnStretch (1, 1); + mLayout->setColumnMinimumWidth(2, 50); + mLayout->setColumnStretch(1, 1); - mLayout->setContentsMargins (0, 0, 0, 0); + mLayout->setContentsMargins(0, 0, 0, 0); + + connect(&mReplace, &QPushButton::clicked, this, qOverload(&SearchBox::replaceAll)); - connect (&mReplace, (SIGNAL (clicked (bool))), this, SLOT (replaceAll (bool))); - // update - modeSelected (0); + modeSelected(0); updateSearchButton(); } -void CSVTools::SearchBox::setSearchMode (bool enabled) +void CSVTools::SearchBox::setSearchMode(bool enabled) { mSearchEnabled = enabled; updateSearchButton(); @@ -99,7 +99,7 @@ void CSVTools::SearchBox::setSearchMode (bool enabled) CSMTools::Search CSVTools::SearchBox::getSearch() const { - CSMTools::Search::Type type = static_cast (mMode.currentIndex()); + CSMTools::Search::Type type = static_cast(mMode.currentIndex()); bool caseSensitive = mCaseSensitive.isChecked(); switch (type) @@ -107,29 +107,30 @@ CSMTools::Search CSVTools::SearchBox::getSearch() const case CSMTools::Search::Type_Text: case CSMTools::Search::Type_Id: - return CSMTools::Search (type, caseSensitive, std::string (mText.text().toUtf8().data())); - + return CSMTools::Search(type, caseSensitive, std::string(mText.text().toUtf8().data())); + case CSMTools::Search::Type_TextRegEx: case CSMTools::Search::Type_IdRegEx: - return CSMTools::Search (type, caseSensitive, QRegExp (mText.text().toUtf8().data(), Qt::CaseInsensitive)); - + return CSMTools::Search(type, caseSensitive, + QRegularExpression(mText.text().toUtf8().data(), QRegularExpression::CaseInsensitiveOption)); + case CSMTools::Search::Type_RecordState: - return CSMTools::Search (type, caseSensitive, mRecordState.currentIndex()); + return CSMTools::Search(type, caseSensitive, mRecordState.currentIndex()); case CSMTools::Search::Type_None: break; } - throw std::logic_error ("invalid search mode index"); + throw std::logic_error("invalid search mode index"); } std::string CSVTools::SearchBox::getReplaceText() const { - CSMTools::Search::Type type = static_cast (mMode.currentIndex()); - + CSMTools::Search::Type type = static_cast(mMode.currentIndex()); + switch (type) { case CSMTools::Search::Type_Text: @@ -141,13 +142,13 @@ std::string CSVTools::SearchBox::getReplaceText() const default: - throw std::logic_error ("Invalid search mode for replace"); + throw std::logic_error("Invalid search mode for replace"); } } -void CSVTools::SearchBox::setEditLock (bool locked) +void CSVTools::SearchBox::setEditLock(bool locked) { - mReplace.setEnabled (!locked); + mReplace.setEnabled(!locked); } void CSVTools::SearchBox::focus() @@ -155,7 +156,7 @@ void CSVTools::SearchBox::focus() mInput.currentWidget()->setFocus(); } -void CSVTools::SearchBox::modeSelected (int index) +void CSVTools::SearchBox::modeSelected(int index) { switch (index) { @@ -164,33 +165,33 @@ void CSVTools::SearchBox::modeSelected (int index) case CSMTools::Search::Type_Id: case CSMTools::Search::Type_IdRegEx: - mInput.setCurrentIndex (0); - mReplaceInput.setCurrentIndex (0); + mInput.setCurrentIndex(0); + mReplaceInput.setCurrentIndex(0); break; case CSMTools::Search::Type_RecordState: - mInput.setCurrentIndex (1); - mReplaceInput.setCurrentIndex (1); + mInput.setCurrentIndex(1); + mReplaceInput.setCurrentIndex(1); break; } mInput.currentWidget()->setFocus(); - + updateSearchButton(); } -void CSVTools::SearchBox::textChanged (const QString& text) +void CSVTools::SearchBox::textChanged(const QString& text) { updateSearchButton(); } -void CSVTools::SearchBox::startSearch (bool checked) +void CSVTools::SearchBox::startSearch(bool checked) { if (mSearch.isEnabled()) - emit startSearch (getSearch()); + emit startSearch(getSearch()); } -void CSVTools::SearchBox::replaceAll (bool checked) +void CSVTools::SearchBox::replaceAll(bool checked) { emit replaceAll(); } diff --git a/apps/opencs/view/tools/searchbox.hpp b/apps/opencs/view/tools/searchbox.hpp index cbeb150d8..76712fee4 100644 --- a/apps/opencs/view/tools/searchbox.hpp +++ b/apps/opencs/view/tools/searchbox.hpp @@ -1,13 +1,13 @@ #ifndef CSV_TOOLS_SEARCHBOX_H #define CSV_TOOLS_SEARCHBOX_H -#include -#include -#include #include -#include -#include +#include #include +#include +#include +#include +#include class QGridLayout; @@ -20,54 +20,52 @@ namespace CSVTools { class SearchBox : public QWidget { - Q_OBJECT + Q_OBJECT - QStackedWidget mInput; - QLineEdit mText; - QComboBox mRecordState; - QCheckBox mCaseSensitive; - QPushButton mSearch; - QGridLayout *mLayout; - QComboBox mMode; - bool mSearchEnabled; - QStackedWidget mReplaceInput; - QLineEdit mReplaceText; - QLabel mReplacePlaceholder; - QPushButton mReplace; + QStackedWidget mInput; + QLineEdit mText; + QComboBox mRecordState; + QCheckBox mCaseSensitive; + QPushButton mSearch; + QGridLayout* mLayout; + QComboBox mMode; + bool mSearchEnabled; + QStackedWidget mReplaceInput; + QLineEdit mReplaceText; + QLabel mReplacePlaceholder; + QPushButton mReplace; - private: + private: + void updateSearchButton(); - void updateSearchButton(); - - public: + public: + SearchBox(QWidget* parent = nullptr); - SearchBox (QWidget *parent = nullptr); + void setSearchMode(bool enabled); - void setSearchMode (bool enabled); + CSMTools::Search getSearch() const; - CSMTools::Search getSearch() const; + std::string getReplaceText() const; - std::string getReplaceText() const; + void setEditLock(bool locked); - void setEditLock (bool locked); + void focus(); - void focus(); + private slots: - private slots: + void modeSelected(int index); - void modeSelected (int index); + void textChanged(const QString& text); - void textChanged (const QString& text); + void startSearch(bool checked = true); - void startSearch (bool checked = true); + void replaceAll(bool checked); - void replaceAll (bool checked); + signals: - signals: + void startSearch(const CSMTools::Search& search); - void startSearch (const CSMTools::Search& search); - - void replaceAll(); + void replaceAll(); }; } diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 07ba7907e..98fd97e7a 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -4,160 +4,158 @@ #include "../../model/doc/document.hpp" #include "../../model/doc/state.hpp" -#include "../../model/tools/search.hpp" -#include "../../model/tools/reportmodel.hpp" -#include "../../model/world/idtablebase.hpp" #include "../../model/prefs/state.hpp" +#include "../../model/tools/reportmodel.hpp" +#include "../../model/tools/search.hpp" +#include "../../model/world/idtablebase.hpp" -#include "../world/tablebottombox.hpp" #include "../world/creator.hpp" +#include "../world/tablebottombox.hpp" + +#include +#include +#include +#include #include "reporttable.hpp" #include "searchbox.hpp" -void CSVTools::SearchSubView::replace (bool selection) +void CSVTools::SearchSubView::replace(bool selection) { if (mLocked) return; - std::vector indices = mTable->getReplaceIndices (selection); + std::vector indices = mTable->getReplaceIndices(selection); std::string replace = mSearchBox.getReplaceText(); - const CSMTools::ReportModel& model = - dynamic_cast (*mTable->model()); + const CSMTools::ReportModel& model = dynamic_cast(*mTable->model()); bool autoDelete = CSMPrefs::get()["Search & Replace"]["auto-delete"].isTrue(); - CSMTools::Search search (mSearch); - CSMWorld::IdTableBase *currentTable = nullptr; + CSMTools::Search search(mSearch); + CSMWorld::IdTableBase* currentTable = nullptr; // We are running through the indices in reverse order to avoid messing up multiple results // in a single string. - for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) + for (std::vector::const_reverse_iterator iter(indices.rbegin()); iter != indices.rend(); ++iter) { - CSMWorld::UniversalId id = model.getUniversalId (*iter); + const CSMWorld::UniversalId& id = model.getUniversalId(*iter); - CSMWorld::UniversalId::Type type = CSMWorld::UniversalId::getParentType (id.getType()); + CSMWorld::UniversalId::Type type = CSMWorld::UniversalId::getParentType(id.getType()); - CSMWorld::IdTableBase *table = &dynamic_cast ( - *mDocument.getData().getTableModel (type)); + CSMWorld::IdTableBase* table = &dynamic_cast(*mDocument.getData().getTableModel(type)); - if (table!=currentTable) + if (table != currentTable) { - search.configure (table); + search.configure(table); currentTable = table; } - std::string hint = model.getHint (*iter); + std::string hint = model.getHint(*iter); - if (search.verify (mDocument, table, id, hint)) + if (search.verify(mDocument, table, id, hint)) { - search.replace (mDocument, table, id, hint, replace); - mTable->flagAsReplaced (*iter); + search.replace(mDocument, table, id, hint, replace); + mTable->flagAsReplaced(*iter); if (autoDelete) - mTable->model()->removeRows (*iter, 1); + mTable->model()->removeRows(*iter, 1); } } } -void CSVTools::SearchSubView::showEvent (QShowEvent *event) +void CSVTools::SearchSubView::showEvent(QShowEvent* event) { - CSVDoc::SubView::showEvent (event); + CSVDoc::SubView::showEvent(event); mSearchBox.focus(); } -CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: CSVDoc::SubView (id), mDocument (document), mLocked (false) +CSVTools::SearchSubView::SearchSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) + : CSVDoc::SubView(id) + , mDocument(document) + , mLocked(false) { - QVBoxLayout *layout = new QVBoxLayout; + QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (&mSearchBox); + layout->addWidget(&mSearchBox); - layout->addWidget (mTable = new ReportTable (document, id, true), 2); + layout->addWidget(mTable = new ReportTable(document, id, true), 2); - layout->addWidget (mBottom = - new CSVWorld::TableBottomBox (CSVWorld::NullCreatorFactory(), document, id, this), 0); + layout->addWidget(mBottom = new CSVWorld::TableBottomBox(CSVWorld::NullCreatorFactory(), document, id, this), 0); - QWidget *widget = new QWidget; + QWidget* widget = new QWidget; - widget->setLayout (layout); + widget->setLayout(layout); - setWidget (widget); + setWidget(widget); - stateChanged (document.getState(), &document); + stateChanged(document.getState(), &document); - connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), - SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); + connect(mTable, &ReportTable::editRequest, this, &SearchSubView::focusId); - connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest())); + connect(mTable, &ReportTable::replaceRequest, this, &SearchSubView::replaceRequest); - connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), - this, SLOT (stateChanged (int, CSMDoc::Document *))); + connect(&document, &CSMDoc::Document::stateChanged, this, &SearchSubView::stateChanged); - connect (&mSearchBox, SIGNAL (startSearch (const CSMTools::Search&)), - this, SLOT (startSearch (const CSMTools::Search&))); + connect( + &mSearchBox, qOverload(&SearchBox::startSearch), this, &SearchSubView::startSearch); - connect (&mSearchBox, SIGNAL (replaceAll()), this, SLOT (replaceAllRequest())); + connect(&mSearchBox, qOverload<>(&SearchBox::replaceAll), this, &SearchSubView::replaceAllRequest); - connect (document.getReport (id), SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (tableSizeUpdate())); + connect(document.getReport(id), &CSMTools::ReportModel::rowsRemoved, this, &SearchSubView::tableSizeUpdate); - connect (document.getReport (id), SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (tableSizeUpdate())); + connect(document.getReport(id), &CSMTools::ReportModel::rowsInserted, this, &SearchSubView::tableSizeUpdate); - connect (&document, SIGNAL (operationDone (int, bool)), - this, SLOT (operationDone (int, bool))); + connect(&document, &CSMDoc::Document::operationDone, this, &SearchSubView::operationDone); } -void CSVTools::SearchSubView::setEditLock (bool locked) +void CSVTools::SearchSubView::setEditLock(bool locked) { mLocked = locked; - mSearchBox.setEditLock (locked); + mSearchBox.setEditLock(locked); } -void CSVTools::SearchSubView::setStatusBar (bool show) +void CSVTools::SearchSubView::setStatusBar(bool show) { mBottom->setStatusBar(show); } -void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) +void CSVTools::SearchSubView::stateChanged(int state, CSMDoc::Document* document) { - mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); + mSearchBox.setSearchMode(!(state & CSMDoc::State_Searching)); } -void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) +void CSVTools::SearchSubView::startSearch(const CSMTools::Search& search) { CSMPrefs::Category& settings = CSMPrefs::get()["Search & Replace"]; mSearch = search; - mSearch.setPadding (settings["char-before"].toInt(), settings["char-after"].toInt()); + mSearch.setPadding(settings["char-before"].toInt(), settings["char-after"].toInt()); mTable->clear(); - mDocument.runSearch (getUniversalId(), mSearch); + mDocument.runSearch(getUniversalId(), mSearch); } void CSVTools::SearchSubView::replaceRequest() { - replace (true); + replace(true); } void CSVTools::SearchSubView::replaceAllRequest() { - replace (false); + replace(false); } void CSVTools::SearchSubView::tableSizeUpdate() { - mBottom->tableSizeChanged (mDocument.getReport (getUniversalId())->rowCount(), 0, 0); + mBottom->tableSizeChanged(mDocument.getReport(getUniversalId())->rowCount(), 0, 0); } -void CSVTools::SearchSubView::operationDone (int type, bool failed) +void CSVTools::SearchSubView::operationDone(int type, bool failed) { - if (type==CSMDoc::State_Searching && !failed && - !mDocument.getReport (getUniversalId())->rowCount()) + if (type == CSMDoc::State_Searching && !failed && !mDocument.getReport(getUniversalId())->rowCount()) { - mBottom->setStatusMessage ("No Results"); + mBottom->setStatusMessage("No Results"); } } diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index cbcb01577..270706d7a 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -5,10 +5,9 @@ #include "../doc/subview.hpp" -#include "searchbox.hpp" +#include -class QTableView; -class QModelIndex; +#include "searchbox.hpp" namespace CSMDoc { @@ -26,44 +25,41 @@ namespace CSVTools class SearchSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - ReportTable *mTable; - SearchBox mSearchBox; - CSMDoc::Document& mDocument; - CSMTools::Search mSearch; - bool mLocked; - CSVWorld::TableBottomBox *mBottom; + ReportTable* mTable; + SearchBox mSearchBox; + CSMDoc::Document& mDocument; + CSMTools::Search mSearch; + bool mLocked; + CSVWorld::TableBottomBox* mBottom; - private: + private: + void replace(bool selection); - void replace (bool selection); + protected: + void showEvent(QShowEvent* event) override; - protected: + public: + SearchSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document); - void showEvent (QShowEvent *event) override; + void setEditLock(bool locked) override; - public: + void setStatusBar(bool show) override; - SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + private slots: - void setEditLock (bool locked) override; + void stateChanged(int state, CSMDoc::Document* document); - void setStatusBar (bool show) override; + void startSearch(const CSMTools::Search& search); - private slots: + void replaceRequest(); - void stateChanged (int state, CSMDoc::Document *document); + void replaceAllRequest(); - void startSearch (const CSMTools::Search& search); + void tableSizeUpdate(); - void replaceRequest(); - - void replaceAllRequest(); - - void tableSizeUpdate(); - - void operationDone (int type, bool failed); + void operationDone(int type, bool failed); }; } diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp index 8c3d6d50e..417eba61a 100644 --- a/apps/opencs/view/tools/subviews.cpp +++ b/apps/opencs/view/tools/subviews.cpp @@ -2,15 +2,16 @@ #include "../doc/subviewfactoryimp.hpp" +#include +#include +#include + #include "reportsubview.hpp" #include "searchsubview.hpp" -void CSVTools::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) +void CSVTools::addSubViewFactories(CSVDoc::SubViewFactoryManager& manager) { - manager.add (CSMWorld::UniversalId::Type_VerificationResults, - new CSVDoc::SubViewFactory); - manager.add (CSMWorld::UniversalId::Type_LoadErrorLog, - new CSVDoc::SubViewFactory); - manager.add (CSMWorld::UniversalId::Type_Search, - new CSVDoc::SubViewFactory); + manager.add(CSMWorld::UniversalId::Type_VerificationResults, new CSVDoc::SubViewFactory); + manager.add(CSMWorld::UniversalId::Type_LoadErrorLog, new CSVDoc::SubViewFactory); + manager.add(CSMWorld::UniversalId::Type_Search, new CSVDoc::SubViewFactory); } diff --git a/apps/opencs/view/tools/subviews.hpp b/apps/opencs/view/tools/subviews.hpp index 1bac32228..bd76ec0c7 100644 --- a/apps/opencs/view/tools/subviews.hpp +++ b/apps/opencs/view/tools/subviews.hpp @@ -8,7 +8,7 @@ namespace CSVDoc namespace CSVTools { - void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager); + void addSubViewFactories(CSVDoc::SubViewFactoryManager& manager); } #endif diff --git a/apps/opencs/view/widget/coloreditor.cpp b/apps/opencs/view/widget/coloreditor.cpp index 1cc649313..a8e5f670a 100644 --- a/apps/opencs/view/widget/coloreditor.cpp +++ b/apps/opencs/view/widget/coloreditor.cpp @@ -1,51 +1,48 @@ #include "coloreditor.hpp" -#include -#include -#include +#include #include -#include #include #include "colorpickerpopup.hpp" -CSVWidget::ColorEditor::ColorEditor(const QColor &color, QWidget *parent, const bool popupOnStart) +class QShowEvent; + +CSVWidget::ColorEditor::ColorEditor(const QColor& color, QWidget* parent, const bool popupOnStart) : ColorEditor(parent, popupOnStart) { setColor(color); } -CSVWidget::ColorEditor::ColorEditor(const int colorInt, QWidget *parent, const bool popupOnStart) +CSVWidget::ColorEditor::ColorEditor(const int colorInt, QWidget* parent, const bool popupOnStart) : ColorEditor(parent, popupOnStart) { setColor(colorInt); } -CSVWidget::ColorEditor::ColorEditor(QWidget *parent, const bool popupOnStart) - : QPushButton(parent), - mColorPicker(new ColorPickerPopup(this)), - mPopupOnStart(popupOnStart) +CSVWidget::ColorEditor::ColorEditor(QWidget* parent, const bool popupOnStart) + : QPushButton(parent) + , mColorPicker(new ColorPickerPopup(this)) + , mPopupOnStart(popupOnStart) { - connect(this, SIGNAL(clicked()), this, SLOT(showPicker())); - connect(mColorPicker, SIGNAL(colorChanged(const QColor &)), this, SLOT(pickerColorChanged(const QColor &))); + connect(this, &ColorEditor::clicked, this, &ColorEditor::showPicker); + connect(mColorPicker, &ColorPickerPopup::colorChanged, this, &ColorEditor::pickerColorChanged); } -void CSVWidget::ColorEditor::paintEvent(QPaintEvent *event) +void CSVWidget::ColorEditor::paintEvent(QPaintEvent* event) { QPushButton::paintEvent(event); QRect buttonRect = rect(); QRect coloredRect(buttonRect.x() + qRound(buttonRect.width() / 4.0), - buttonRect.y() + qRound(buttonRect.height() / 4.0), - buttonRect.width() / 2, - buttonRect.height() / 2); + buttonRect.y() + qRound(buttonRect.height() / 4.0), buttonRect.width() / 2, buttonRect.height() / 2); QPainter painter(this); painter.fillRect(coloredRect, mColor); painter.setPen(Qt::black); painter.drawRect(coloredRect); } -void CSVWidget::ColorEditor::showEvent(QShowEvent *event) +void CSVWidget::ColorEditor::showEvent(QShowEvent* event) { QPushButton::showEvent(event); if (isVisible() && mPopupOnStart) @@ -66,7 +63,7 @@ int CSVWidget::ColorEditor::colorInt() const return (mColor.blue() << 16) | (mColor.green() << 8) | (mColor.red()); } -void CSVWidget::ColorEditor::setColor(const QColor &color) +void CSVWidget::ColorEditor::setColor(const QColor& color) { mColor = color; update(); @@ -86,7 +83,7 @@ void CSVWidget::ColorEditor::showPicker() emit pickingFinished(); } -void CSVWidget::ColorEditor::pickerColorChanged(const QColor &color) +void CSVWidget::ColorEditor::pickerColorChanged(const QColor& color) { mColor = color; update(); diff --git a/apps/opencs/view/widget/coloreditor.hpp b/apps/opencs/view/widget/coloreditor.hpp index aa746da68..284de9f6c 100644 --- a/apps/opencs/view/widget/coloreditor.hpp +++ b/apps/opencs/view/widget/coloreditor.hpp @@ -13,42 +13,42 @@ namespace CSVWidget class ColorEditor : public QPushButton { - Q_OBJECT + Q_OBJECT - QColor mColor; - ColorPickerPopup *mColorPicker; - bool mPopupOnStart; + QColor mColor; + ColorPickerPopup* mColorPicker; + bool mPopupOnStart; - QPoint calculatePopupPosition(); + QPoint calculatePopupPosition(); - public: - ColorEditor(const QColor &color, QWidget *parent = nullptr, const bool popupOnStart = false); - ColorEditor(const int colorInt, QWidget *parent = nullptr, const bool popupOnStart = false); + public: + ColorEditor(const QColor& color, QWidget* parent = nullptr, const bool popupOnStart = false); + ColorEditor(const int colorInt, QWidget* parent = nullptr, const bool popupOnStart = false); - QColor color() const; + QColor color() const; - /// \return Color RGB value encoded in an int. - int colorInt() const; + /// \return Color RGB value encoded in an int. + int colorInt() const; - void setColor(const QColor &color); + void setColor(const QColor& color); - /// \brief Set color using given int value. - /// \param colorInt RGB color value encoded as an integer. - void setColor(const int colorInt); + /// \brief Set color using given int value. + /// \param colorInt RGB color value encoded as an integer. + void setColor(const int colorInt); - protected: - void paintEvent(QPaintEvent *event) override; - void showEvent(QShowEvent *event) override; + protected: + void paintEvent(QPaintEvent* event) override; + void showEvent(QShowEvent* event) override; - private: - ColorEditor(QWidget *parent = nullptr, const bool popupOnStart = false); + private: + ColorEditor(QWidget* parent = nullptr, const bool popupOnStart = false); - private slots: - void showPicker(); - void pickerColorChanged(const QColor &color); + private slots: + void showPicker(); + void pickerColorChanged(const QColor& color); - signals: - void pickingFinished(); + signals: + void pickingFinished(); }; } diff --git a/apps/opencs/view/widget/colorpickerpopup.cpp b/apps/opencs/view/widget/colorpickerpopup.cpp index 206a66727..87c62e137 100644 --- a/apps/opencs/view/widget/colorpickerpopup.cpp +++ b/apps/opencs/view/widget/colorpickerpopup.cpp @@ -1,29 +1,26 @@ #include "colorpickerpopup.hpp" #include -#include #include #include -#include -#include +#include +#include +#include -CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent) +CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget* parent) : QFrame(parent) { setWindowFlags(Qt::Popup); - setFrameStyle(QFrame::Box | QFrame::Plain); + setFrameStyle(QFrame::Box | static_cast(QFrame::Plain)); hide(); mColorPicker = new QColorDialog(this); mColorPicker->setWindowFlags(Qt::Widget); mColorPicker->setOptions(QColorDialog::NoButtons | QColorDialog::DontUseNativeDialog); mColorPicker->installEventFilter(this); - connect(mColorPicker, - SIGNAL(currentColorChanged(const QColor &)), - this, - SIGNAL(colorChanged(const QColor &))); + connect(mColorPicker, &QColorDialog::currentColorChanged, this, &ColorPickerPopup::colorChanged); - QVBoxLayout *layout = new QVBoxLayout(this); + QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(mColorPicker); layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); layout->setContentsMargins(0, 0, 0, 0); @@ -31,7 +28,7 @@ CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent) setFixedSize(mColorPicker->size()); } -void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColor &initialColor) +void CSVWidget::ColorPickerPopup::showPicker(const QPoint& position, const QColor& initialColor) { QRect geometry = this->geometry(); geometry.moveTo(position); @@ -43,13 +40,13 @@ void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColo mColorPicker->setCurrentColor(color); } -void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) +void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent* event) { - QPushButton *button = qobject_cast(parentWidget()); + QPushButton* button = qobject_cast(parentWidget()); if (button != nullptr) { QStyleOptionButton option; - option.init(button); + option.initFrom(button); QRect buttonRect = option.rect; buttonRect.moveTo(button->mapToGlobal(buttonRect.topLeft())); @@ -63,11 +60,11 @@ void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) QFrame::mousePressEvent(event); } -bool CSVWidget::ColorPickerPopup::eventFilter(QObject *object, QEvent *event) +bool CSVWidget::ColorPickerPopup::eventFilter(QObject* object, QEvent* event) { if (object == mColorPicker && event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); + QKeyEvent* keyEvent = static_cast(event); // Prevent QColorDialog from closing when Escape is pressed. // Instead, hide the popup. if (keyEvent->key() == Qt::Key_Escape) diff --git a/apps/opencs/view/widget/colorpickerpopup.hpp b/apps/opencs/view/widget/colorpickerpopup.hpp index d653d68fb..8a3eb030f 100644 --- a/apps/opencs/view/widget/colorpickerpopup.hpp +++ b/apps/opencs/view/widget/colorpickerpopup.hpp @@ -11,19 +11,19 @@ namespace CSVWidget { Q_OBJECT - QColorDialog *mColorPicker; + QColorDialog* mColorPicker; public: - explicit ColorPickerPopup(QWidget *parent); - - void showPicker(const QPoint &position, const QColor &initialColor); + explicit ColorPickerPopup(QWidget* parent); + + void showPicker(const QPoint& position, const QColor& initialColor); protected: - void mousePressEvent(QMouseEvent *event) override; - bool eventFilter(QObject *object, QEvent *event) override; + void mousePressEvent(QMouseEvent* event) override; + bool eventFilter(QObject* object, QEvent* event) override; signals: - void colorChanged(const QColor &color); + void colorChanged(const QColor& color); }; } diff --git a/apps/opencs/view/widget/completerpopup.cpp b/apps/opencs/view/widget/completerpopup.cpp index be509bcb9..91b090265 100644 --- a/apps/opencs/view/widget/completerpopup.cpp +++ b/apps/opencs/view/widget/completerpopup.cpp @@ -1,6 +1,6 @@ #include "completerpopup.hpp" -CSVWidget::CompleterPopup::CompleterPopup(QWidget *parent) +CSVWidget::CompleterPopup::CompleterPopup(QWidget* parent) : QListView(parent) { setEditTriggers(QAbstractItemView::NoEditTriggers); @@ -22,7 +22,12 @@ int CSVWidget::CompleterPopup::sizeHintForRow(int row) const ensurePolished(); QModelIndex index = model()->index(row, modelColumn()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QStyleOptionViewItem option; + initViewItemOption(&option); +#else QStyleOptionViewItem option = viewOptions(); - QAbstractItemDelegate *delegate = itemDelegate(index); +#endif + QAbstractItemDelegate* delegate = itemDelegate(index); return delegate->sizeHint(option, index).height(); } diff --git a/apps/opencs/view/widget/completerpopup.hpp b/apps/opencs/view/widget/completerpopup.hpp index 96675f56f..673d28800 100644 --- a/apps/opencs/view/widget/completerpopup.hpp +++ b/apps/opencs/view/widget/completerpopup.hpp @@ -7,10 +7,10 @@ namespace CSVWidget { class CompleterPopup : public QListView { - public: - CompleterPopup(QWidget *parent = nullptr); + public: + CompleterPopup(QWidget* parent = nullptr); - int sizeHintForRow(int row) const override; + int sizeHintForRow(int row) const override; }; } diff --git a/apps/opencs/view/widget/droplineedit.cpp b/apps/opencs/view/widget/droplineedit.cpp index 2ca306461..4b1bf2aef 100644 --- a/apps/opencs/view/widget/droplineedit.cpp +++ b/apps/opencs/view/widget/droplineedit.cpp @@ -1,20 +1,24 @@ #include "droplineedit.hpp" +#include + #include #include "../../model/world/tablemimedata.hpp" #include "../../model/world/universalid.hpp" +#include + #include "../world/dragdroputils.hpp" -CSVWidget::DropLineEdit::DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent) - : QLineEdit(parent), - mDropType(type) +CSVWidget::DropLineEdit::DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget* parent) + : QLineEdit(parent) + , mDropType(type) { setAcceptDrops(true); } -void CSVWidget::DropLineEdit::dragEnterEvent(QDragEnterEvent *event) +void CSVWidget::DropLineEdit::dragEnterEvent(QDragEnterEvent* event) { if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) { @@ -22,7 +26,7 @@ void CSVWidget::DropLineEdit::dragEnterEvent(QDragEnterEvent *event) } } -void CSVWidget::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) +void CSVWidget::DropLineEdit::dragMoveEvent(QDragMoveEvent* event) { if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) { @@ -30,7 +34,7 @@ void CSVWidget::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) } } -void CSVWidget::DropLineEdit::dropEvent(QDropEvent *event) +void CSVWidget::DropLineEdit::dropEvent(QDropEvent* event) { if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) { diff --git a/apps/opencs/view/widget/droplineedit.hpp b/apps/opencs/view/widget/droplineedit.hpp index 911051873..3a707d80c 100644 --- a/apps/opencs/view/widget/droplineedit.hpp +++ b/apps/opencs/view/widget/droplineedit.hpp @@ -12,7 +12,6 @@ namespace CSMDoc namespace CSMWorld { - class TableMimeData; class UniversalId; } @@ -20,21 +19,21 @@ namespace CSVWidget { class DropLineEdit : public QLineEdit { - Q_OBJECT + Q_OBJECT - CSMWorld::ColumnBase::Display mDropType; - ///< The accepted Display type for this LineEdit. + CSMWorld::ColumnBase::Display mDropType; + ///< The accepted Display type for this LineEdit. - public: - DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent = nullptr); + public: + DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget* parent = nullptr); - protected: - void dragEnterEvent(QDragEnterEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dropEvent(QDropEvent *event) override; + protected: + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; - signals: - void tableMimeDataDropped(const CSMWorld::UniversalId &id, const CSMDoc::Document *document); + signals: + void tableMimeDataDropped(const CSMWorld::UniversalId& id, const CSMDoc::Document* document); }; } diff --git a/apps/opencs/view/widget/modebutton.cpp b/apps/opencs/view/widget/modebutton.cpp index 88f050247..f19baf609 100644 --- a/apps/opencs/view/widget/modebutton.cpp +++ b/apps/opencs/view/widget/modebutton.cpp @@ -1,14 +1,19 @@ #include "modebutton.hpp" -CSVWidget::ModeButton::ModeButton (const QIcon& icon, const QString& tooltip, QWidget *parent) -: PushButton (icon, Type_Mode, tooltip, parent) -{} +#include -void CSVWidget::ModeButton::activate (SceneToolbar *toolbar) {} +class QWidget; -void CSVWidget::ModeButton::deactivate (SceneToolbar *toolbar) {} +CSVWidget::ModeButton::ModeButton(const QIcon& icon, const QString& tooltip, QWidget* parent) + : PushButton(icon, Type_Mode, tooltip, parent) +{ +} -bool CSVWidget::ModeButton::createContextMenu (QMenu *menu) +void CSVWidget::ModeButton::activate(SceneToolbar* toolbar) {} + +void CSVWidget::ModeButton::deactivate(SceneToolbar* toolbar) {} + +bool CSVWidget::ModeButton::createContextMenu(QMenu* menu) { return false; } diff --git a/apps/opencs/view/widget/modebutton.hpp b/apps/opencs/view/widget/modebutton.hpp index f59596923..75b05e28e 100644 --- a/apps/opencs/view/widget/modebutton.hpp +++ b/apps/opencs/view/widget/modebutton.hpp @@ -12,26 +12,24 @@ namespace CSVWidget /// \brief Specialist PushButton of Type_Mode for use in SceneToolMode class ModeButton : public PushButton { - Q_OBJECT + Q_OBJECT - public: + public: + ModeButton(const QIcon& icon, const QString& tooltip = "", QWidget* parent = nullptr); - ModeButton (const QIcon& icon, const QString& tooltip = "", - QWidget *parent = nullptr); + /// Default-Implementation: do nothing + virtual void activate(SceneToolbar* toolbar); - /// Default-Implementation: do nothing - virtual void activate (SceneToolbar *toolbar); + /// Default-Implementation: do nothing + virtual void deactivate(SceneToolbar* toolbar); - /// Default-Implementation: do nothing - virtual void deactivate (SceneToolbar *toolbar); - - /// Add context menu items to \a menu. Default-implementation: return false - /// - /// \attention menu can be a 0-pointer - /// - /// \return Have there been any menu items to be added (if menu is 0 and there - /// items to be added, the function must return true anyway. - virtual bool createContextMenu (QMenu *menu); + /// Add context menu items to \a menu. Default-implementation: return false + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu(QMenu* menu); }; } diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp index c4e6a4144..6eab07dcc 100644 --- a/apps/opencs/view/widget/pushbutton.cpp +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -1,10 +1,15 @@ #include "pushbutton.hpp" -#include #include +#include + +#include + +#include +#include -#include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcutmanager.hpp" +#include "../../model/prefs/state.hpp" void CSVWidget::PushButton::processShortcuts() { @@ -22,8 +27,7 @@ void CSVWidget::PushButton::setExtendedToolTip() { case Type_TopMode: - tooltip += - "

        (left click to change mode)"; + tooltip += "

        (left click to change mode)"; break; @@ -50,52 +54,56 @@ void CSVWidget::PushButton::setExtendedToolTip() break; } - setToolTip (tooltip); + setToolTip(tooltip); } -void CSVWidget::PushButton::keyPressEvent (QKeyEvent *event) +void CSVWidget::PushButton::keyPressEvent(QKeyEvent* event) { - if (event->key()!=Qt::Key_Shift) + if (event->key() != Qt::Key_Shift) mKeepOpen = false; - QPushButton::keyPressEvent (event); + QPushButton::keyPressEvent(event); } -void CSVWidget::PushButton::keyReleaseEvent (QKeyEvent *event) +void CSVWidget::PushButton::keyReleaseEvent(QKeyEvent* event) { - if (event->key()==Qt::Key_Space) + if (event->key() == Qt::Key_Space) mKeepOpen = event->modifiers() & Qt::ShiftModifier; - QPushButton::keyReleaseEvent (event); + QPushButton::keyReleaseEvent(event); } -void CSVWidget::PushButton::mouseReleaseEvent (QMouseEvent *event) +void CSVWidget::PushButton::mouseReleaseEvent(QMouseEvent* event) { - mKeepOpen = event->button()==Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier); - QPushButton::mouseReleaseEvent (event); + mKeepOpen = event->button() == Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier); + QPushButton::mouseReleaseEvent(event); } -CSVWidget::PushButton::PushButton (const QIcon& icon, Type type, const QString& tooltip, - QWidget *parent) -: QPushButton (icon, "", parent), mKeepOpen (false), mType (type), mToolTip (tooltip) +CSVWidget::PushButton::PushButton(const QIcon& icon, Type type, const QString& tooltip, QWidget* parent) + : QPushButton(icon, "", parent) + , mKeepOpen(false) + , mType(type) + , mToolTip(tooltip) { - if (type==Type_Mode || type==Type_Toggle) + if (type == Type_Mode || type == Type_Toggle) { - setCheckable (true); - connect (this, SIGNAL (toggled (bool)), this, SLOT (checkedStateChanged (bool))); + setCheckable(true); + connect(this, &PushButton::toggled, this, &PushButton::checkedStateChanged); } - setCheckable (type==Type_Mode || type==Type_Toggle); + setCheckable(type == Type_Mode || type == Type_Toggle); processShortcuts(); setExtendedToolTip(); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &PushButton::settingChanged); } -CSVWidget::PushButton::PushButton (Type type, const QString& tooltip, QWidget *parent) -: QPushButton (parent), mKeepOpen (false), mType (type), mToolTip (tooltip) +CSVWidget::PushButton::PushButton(Type type, const QString& tooltip, QWidget* parent) + : QPushButton(parent) + , mKeepOpen(false) + , mType(type) + , mToolTip(tooltip) { - setCheckable (type==Type_Mode || type==Type_Toggle); + setCheckable(type == Type_Mode || type == Type_Toggle); processShortcuts(); setExtendedToolTip(); } @@ -115,12 +123,12 @@ CSVWidget::PushButton::Type CSVWidget::PushButton::getType() const return mType; } -void CSVWidget::PushButton::checkedStateChanged (bool checked) +void CSVWidget::PushButton::checkedStateChanged(bool checked) { setExtendedToolTip(); } -void CSVWidget::PushButton::settingChanged (const CSMPrefs::Setting *setting) +void CSVWidget::PushButton::settingChanged(const CSMPrefs::Setting* setting) { if (setting->getParent()->getKey() == "Key Bindings") { diff --git a/apps/opencs/view/widget/pushbutton.hpp b/apps/opencs/view/widget/pushbutton.hpp index b3aaaebef..584d311b5 100644 --- a/apps/opencs/view/widget/pushbutton.hpp +++ b/apps/opencs/view/widget/pushbutton.hpp @@ -3,6 +3,11 @@ #include +class QKeyEvent; +class QMouseEvent; +class QObject; +class QWidget; + namespace CSMPrefs { class Setting; @@ -12,59 +17,52 @@ namespace CSVWidget { class PushButton : public QPushButton { - Q_OBJECT + Q_OBJECT - public: + public: + enum Type + { + Type_TopMode, // top level button for mode selector panel + Type_TopAction, // top level button that triggers an action + Type_Mode, // mode button + Type_Toggle + }; - enum Type - { - Type_TopMode, // top level button for mode selector panel - Type_TopAction, // top level button that triggers an action - Type_Mode, // mode button - Type_Toggle - }; + private: + bool mKeepOpen; + Type mType; + QString mToolTip; + QString mProcessedToolTip; - private: + private: + void processShortcuts(); + void setExtendedToolTip(); - bool mKeepOpen; - Type mType; - QString mToolTip; - QString mProcessedToolTip; + protected: + void keyPressEvent(QKeyEvent* event) override; - private: + void keyReleaseEvent(QKeyEvent* event) override; - void processShortcuts(); - void setExtendedToolTip(); + void mouseReleaseEvent(QMouseEvent* event) override; - protected: + public: + /// \param push Do not maintain a toggle state + PushButton(const QIcon& icon, Type type, const QString& tooltip = "", QWidget* parent = nullptr); - void keyPressEvent (QKeyEvent *event) override; + /// \param push Do not maintain a toggle state + PushButton(Type type, const QString& tooltip = "", QWidget* parent = nullptr); - void keyReleaseEvent (QKeyEvent *event) override; + bool hasKeepOpen() const; - void mouseReleaseEvent (QMouseEvent *event) override; + /// Return tooltip used at construction (without any button-specific modifications) + QString getBaseToolTip() const; - public: + Type getType() const; - /// \param push Do not maintain a toggle state - PushButton (const QIcon& icon, Type type, const QString& tooltip = "", - QWidget *parent = nullptr); + private slots: - /// \param push Do not maintain a toggle state - PushButton (Type type, const QString& tooltip = "", - QWidget *parent = nullptr); - - bool hasKeepOpen() const; - - /// Return tooltip used at construction (without any button-specific modifications) - QString getBaseToolTip() const; - - Type getType() const; - - private slots: - - void checkedStateChanged (bool checked); - void settingChanged (const CSMPrefs::Setting *setting); + void checkedStateChanged(bool checked); + void settingChanged(const CSMPrefs::Setting* setting); }; } diff --git a/apps/opencs/view/widget/scenetool.cpp b/apps/opencs/view/widget/scenetool.cpp index 796b98567..36c902f95 100644 --- a/apps/opencs/view/widget/scenetool.cpp +++ b/apps/opencs/view/widget/scenetool.cpp @@ -2,32 +2,34 @@ #include +#include + #include "scenetoolbar.hpp" -CSVWidget::SceneTool::SceneTool (SceneToolbar *parent, Type type) -: PushButton (type, "", parent) +CSVWidget::SceneTool::SceneTool(SceneToolbar* parent, Type type) + : PushButton(type, "", parent) { - setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); - setIconSize (QSize (parent->getIconSize(), parent->getIconSize())); - setFixedSize (parent->getButtonSize(), parent->getButtonSize()); + setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + setIconSize(QSize(parent->getIconSize(), parent->getIconSize())); + setFixedSize(parent->getButtonSize(), parent->getButtonSize()); - connect (this, SIGNAL (clicked()), this, SLOT (openRequest())); + connect(this, &SceneTool::clicked, this, &SceneTool::openRequest); } void CSVWidget::SceneTool::activate() {} -void CSVWidget::SceneTool::mouseReleaseEvent (QMouseEvent *event) +void CSVWidget::SceneTool::mouseReleaseEvent(QMouseEvent* event) { - if (getType()==Type_TopAction && event->button()==Qt::RightButton) - showPanel (parentWidget()->mapToGlobal (pos())); + if (getType() == Type_TopAction && event->button() == Qt::RightButton) + showPanel(parentWidget()->mapToGlobal(pos())); else - PushButton::mouseReleaseEvent (event); + PushButton::mouseReleaseEvent(event); } void CSVWidget::SceneTool::openRequest() { - if (getType()==Type_TopAction) + if (getType() == Type_TopAction) activate(); else - showPanel (parentWidget()->mapToGlobal (pos())); + showPanel(parentWidget()->mapToGlobal(pos())); } diff --git a/apps/opencs/view/widget/scenetool.hpp b/apps/opencs/view/widget/scenetool.hpp index 295375f26..bd37ed9b3 100644 --- a/apps/opencs/view/widget/scenetool.hpp +++ b/apps/opencs/view/widget/scenetool.hpp @@ -3,6 +3,10 @@ #include "pushbutton.hpp" +class QMouseEvent; +class QObject; +class QPoint; + namespace CSVWidget { class SceneToolbar; @@ -10,25 +14,23 @@ namespace CSVWidget ///< \brief Tool base class class SceneTool : public PushButton { - Q_OBJECT + Q_OBJECT - public: + public: + SceneTool(SceneToolbar* parent, Type type = Type_TopMode); - SceneTool (SceneToolbar *parent, Type type = Type_TopMode); + virtual void showPanel(const QPoint& position) = 0; - virtual void showPanel (const QPoint& position) = 0; + /// This function will only called for buttons of type Type_TopAction. The default + /// implementation is empty. + virtual void activate(); - /// This function will only called for buttons of type Type_TopAction. The default - /// implementation is empty. - virtual void activate(); + protected: + void mouseReleaseEvent(QMouseEvent* event) override; - protected: + private slots: - void mouseReleaseEvent (QMouseEvent *event) override; - - private slots: - - void openRequest(); + void openRequest(); }; } diff --git a/apps/opencs/view/widget/scenetoolbar.cpp b/apps/opencs/view/widget/scenetoolbar.cpp index a2458397f..09d99e367 100644 --- a/apps/opencs/view/widget/scenetoolbar.cpp +++ b/apps/opencs/view/widget/scenetoolbar.cpp @@ -2,48 +2,52 @@ #include +#include + #include "../../model/prefs/shortcut.hpp" #include "scenetool.hpp" -void CSVWidget::SceneToolbar::focusInEvent (QFocusEvent *event) +void CSVWidget::SceneToolbar::focusInEvent(QFocusEvent* event) { - QWidget::focusInEvent (event); + QWidget::focusInEvent(event); if (mLayout->count()) - dynamic_cast (*mLayout->itemAt (0)).widget()->setFocus(); + dynamic_cast(*mLayout->itemAt(0)).widget()->setFocus(); } -CSVWidget::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) -: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-6) +CSVWidget::SceneToolbar::SceneToolbar(int buttonSize, QWidget* parent) + : QWidget(parent) + , mButtonSize(buttonSize) + , mIconSize(buttonSize - 6) { - setFixedWidth (mButtonSize); + setFixedWidth(mButtonSize); - mLayout = new QVBoxLayout (this); - mLayout->setAlignment (Qt::AlignTop); + mLayout = new QVBoxLayout(this); + mLayout->setAlignment(Qt::AlignTop); - mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); + mLayout->setContentsMargins(QMargins(0, 0, 0, 0)); - setLayout (mLayout); + setLayout(mLayout); CSMPrefs::Shortcut* focusSceneShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this); - connect(focusSceneShortcut, SIGNAL(activated()), this, SIGNAL(focusSceneRequest())); + connect(focusSceneShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &SceneToolbar::focusSceneRequest); } -void CSVWidget::SceneToolbar::addTool (SceneTool *tool, SceneTool *insertPoint) +void CSVWidget::SceneToolbar::addTool(SceneTool* tool, SceneTool* insertPoint) { if (!insertPoint) - mLayout->addWidget (tool, 0, Qt::AlignTop); + mLayout->addWidget(tool, 0, Qt::AlignTop); else { - int index = mLayout->indexOf (insertPoint); - mLayout->insertWidget (index+1, tool, 0, Qt::AlignTop); + int index = mLayout->indexOf(insertPoint); + mLayout->insertWidget(index + 1, tool, 0, Qt::AlignTop); } } -void CSVWidget::SceneToolbar::removeTool (SceneTool *tool) +void CSVWidget::SceneToolbar::removeTool(SceneTool* tool) { - mLayout->removeWidget (tool); + mLayout->removeWidget(tool); } int CSVWidget::SceneToolbar::getButtonSize() const diff --git a/apps/opencs/view/widget/scenetoolbar.hpp b/apps/opencs/view/widget/scenetoolbar.hpp index 70f580765..6b4e503bd 100644 --- a/apps/opencs/view/widget/scenetoolbar.hpp +++ b/apps/opencs/view/widget/scenetoolbar.hpp @@ -11,33 +11,31 @@ namespace CSVWidget class SceneToolbar : public QWidget { - Q_OBJECT + Q_OBJECT - QVBoxLayout *mLayout; - int mButtonSize; - int mIconSize; + QVBoxLayout* mLayout; + int mButtonSize; + int mIconSize; - protected: + protected: + void focusInEvent(QFocusEvent* event) override; - void focusInEvent (QFocusEvent *event) override; + public: + SceneToolbar(int buttonSize, QWidget* parent = nullptr); - public: + /// If insertPoint==0, insert \a tool at the end of the scrollbar. Otherwise + /// insert tool after \a insertPoint. + void addTool(SceneTool* tool, SceneTool* insertPoint = nullptr); - SceneToolbar (int buttonSize, QWidget *parent = nullptr); + void removeTool(SceneTool* tool); - /// If insertPoint==0, insert \a tool at the end of the scrollbar. Otherwise - /// insert tool after \a insertPoint. - void addTool (SceneTool *tool, SceneTool *insertPoint = nullptr); + int getButtonSize() const; - void removeTool (SceneTool *tool); + int getIconSize() const; - int getButtonSize() const; + signals: - int getIconSize() const; - - signals: - - void focusSceneRequest(); + void focusSceneRequest(); }; } diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 3aec44f1b..a691e3a83 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -1,31 +1,37 @@ #include "scenetoolmode.hpp" -#include -#include -#include -#include #include #include +#include +#include +#include +#include + +#include +#include + +#include +#include -#include "scenetoolbar.hpp" #include "modebutton.hpp" +#include "scenetoolbar.hpp" -void CSVWidget::SceneToolMode::contextMenuEvent (QContextMenuEvent *event) +void CSVWidget::SceneToolMode::contextMenuEvent(QContextMenuEvent* event) { - QMenu menu (this); - if (createContextMenu (&menu)) - menu.exec (event->globalPos()); + QMenu menu(this); + if (createContextMenu(&menu)) + menu.exec(event->globalPos()); } -bool CSVWidget::SceneToolMode::createContextMenu (QMenu *menu) +bool CSVWidget::SceneToolMode::createContextMenu(QMenu* menu) { if (mCurrent) - return mCurrent->createContextMenu (menu); + return mCurrent->createContextMenu(menu); return false; } -void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) +void CSVWidget::SceneToolMode::adjustToolTip(const ModeButton* activeMode) { QString toolTip = mToolTip; @@ -33,103 +39,105 @@ void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) toolTip += "

        (left click to change mode)"; - if (createContextMenu (nullptr)) + if (createContextMenu(nullptr)) toolTip += "
        (right click to access context menu)"; - setToolTip (toolTip); + setToolTip(toolTip); } -void CSVWidget::SceneToolMode::setButton (std::map::iterator iter) +void CSVWidget::SceneToolMode::setButton(std::map::iterator iter) { - for (std::map::const_iterator iter2 = mButtons.begin(); - iter2!=mButtons.end(); ++iter2) - iter2->first->setChecked (iter2==iter); + for (std::map::const_iterator iter2 = mButtons.begin(); iter2 != mButtons.end(); ++iter2) + iter2->first->setChecked(iter2 == iter); - setIcon (iter->first->icon()); - adjustToolTip (iter->first); + setIcon(iter->first->icon()); + adjustToolTip(iter->first); - if (mCurrent!=iter->first) + if (mCurrent != iter->first) { if (mCurrent) - mCurrent->deactivate (mToolbar); + mCurrent->deactivate(mToolbar); mCurrent = iter->first; - mCurrent->activate (mToolbar); + mCurrent->activate(mToolbar); } - emit modeChanged (iter->second); + emit modeChanged(iter->second); } -CSVWidget::SceneToolMode::SceneToolMode (SceneToolbar *parent, const QString& toolTip) -: SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), - mToolTip (toolTip), mFirst (nullptr), mCurrent (nullptr), mToolbar (parent) +CSVWidget::SceneToolMode::SceneToolMode(SceneToolbar* parent, const QString& toolTip) + : SceneTool(parent) + , mButtonSize(parent->getButtonSize()) + , mIconSize(parent->getIconSize()) + , mToolTip(toolTip) + , mFirst(nullptr) + , mCurrent(nullptr) + , mToolbar(parent) { - mPanel = new QFrame (this, Qt::Popup); + mPanel = new QFrame(this, Qt::Popup); - mLayout = new QHBoxLayout (mPanel); + mLayout = new QHBoxLayout(mPanel); - mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); + mLayout->setContentsMargins(QMargins(0, 0, 0, 0)); - mPanel->setLayout (mLayout); + mPanel->setLayout(mLayout); } -void CSVWidget::SceneToolMode::showPanel (const QPoint& position) +void CSVWidget::SceneToolMode::showPanel(const QPoint& position) { - mPanel->move (position); + mPanel->move(position); mPanel->show(); if (mFirst) - mFirst->setFocus (Qt::OtherFocusReason); + mFirst->setFocus(Qt::OtherFocusReason); } -void CSVWidget::SceneToolMode::addButton (const std::string& icon, const std::string& id, - const QString& tooltip) +void CSVWidget::SceneToolMode::addButton(const std::string& icon, const std::string& id, const QString& tooltip) { - ModeButton *button = new ModeButton (QIcon (QPixmap (icon.c_str())), tooltip, mPanel); - addButton (button, id); + ModeButton* button = new ModeButton(QIcon(QPixmap(icon.c_str())), tooltip, mPanel); + addButton(button, id); } -void CSVWidget::SceneToolMode::addButton (ModeButton *button, const std::string& id) +void CSVWidget::SceneToolMode::addButton(ModeButton* button, const std::string& id) { - button->setParent (mPanel); + button->setParent(mPanel); - button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); - button->setIconSize (QSize (mIconSize, mIconSize)); - button->setFixedSize (mButtonSize, mButtonSize); + button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setIconSize(QSize(mIconSize, mIconSize)); + button->setFixedSize(mButtonSize, mButtonSize); - mLayout->addWidget (button); + mLayout->addWidget(button); - mButtons.insert (std::make_pair (button, id)); + mButtons.insert(std::make_pair(button, id)); - connect (button, SIGNAL (clicked()), this, SLOT (selected())); + connect(button, &ModeButton::clicked, this, &SceneToolMode::selected); - if (mButtons.size()==1) + if (mButtons.size() == 1) { mFirst = mCurrent = button; - setIcon (button->icon()); - button->setChecked (true); - adjustToolTip (button); - mCurrent->activate (mToolbar); + setIcon(button->icon()); + button->setChecked(true); + adjustToolTip(button); + mCurrent->activate(mToolbar); } } -CSVWidget::ModeButton *CSVWidget::SceneToolMode::getCurrent() +CSVWidget::ModeButton* CSVWidget::SceneToolMode::getCurrent() { return mCurrent; } std::string CSVWidget::SceneToolMode::getCurrentId() const { - return mButtons.find (mCurrent)->second; + return mButtons.find(mCurrent)->second; } -void CSVWidget::SceneToolMode::setButton (const std::string& id) +void CSVWidget::SceneToolMode::setButton(const std::string& id) { - for (std::map::iterator iter = mButtons.begin(); - iter!=mButtons.end(); ++iter) - if (iter->second==id) + for (std::map::iterator iter = mButtons.begin(); iter != mButtons.end(); ++iter) + if (iter->second == id) { - setButton (iter); + setButton(iter); break; } } @@ -146,14 +154,13 @@ bool CSVWidget::SceneToolMode::event(QEvent* event) void CSVWidget::SceneToolMode::selected() { - std::map::iterator iter = - mButtons.find (dynamic_cast (sender())); + std::map::iterator iter = mButtons.find(dynamic_cast(sender())); - if (iter!=mButtons.end()) + if (iter != mButtons.end()) { if (!iter->first->hasKeepOpen()) mPanel->hide(); - setButton (iter); + setButton(iter); } } diff --git a/apps/opencs/view/widget/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp index 896a6d9c6..33e199659 100644 --- a/apps/opencs/view/widget/scenetoolmode.hpp +++ b/apps/opencs/view/widget/scenetoolmode.hpp @@ -4,78 +4,81 @@ #include "scenetool.hpp" #include +#include class QHBoxLayout; class QMenu; class QEvent; +class QContextMenuEvent; +class QObject; +class QPoint; +class QWidget; namespace CSVWidget { class SceneToolbar; class ModeButton; + class PushButton; ///< \brief Mode selector tool class SceneToolMode : public SceneTool { - Q_OBJECT + Q_OBJECT - QWidget *mPanel; - QHBoxLayout *mLayout; - std::map mButtons; // widget, id - int mButtonSize; - int mIconSize; - QString mToolTip; - PushButton *mFirst; - ModeButton *mCurrent; - SceneToolbar *mToolbar; + QWidget* mPanel; + QHBoxLayout* mLayout; + std::map mButtons; // widget, id + int mButtonSize; + int mIconSize; + QString mToolTip; + PushButton* mFirst; + ModeButton* mCurrent; + SceneToolbar* mToolbar; - void adjustToolTip (const ModeButton *activeMode); + void adjustToolTip(const ModeButton* activeMode); - void contextMenuEvent (QContextMenuEvent *event) override; + void contextMenuEvent(QContextMenuEvent* event) override; - /// Add context menu items to \a menu. Default-implementation: Pass on request to - /// current mode button or return false, if there is no current mode button. - /// - /// \attention menu can be a 0-pointer - /// - /// \return Have there been any menu items to be added (if menu is 0 and there - /// items to be added, the function must return true anyway. - virtual bool createContextMenu (QMenu *menu); + /// Add context menu items to \a menu. Default-implementation: Pass on request to + /// current mode button or return false, if there is no current mode button. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu(QMenu* menu); - void setButton (std::map::iterator iter); + void setButton(std::map::iterator iter); - protected: + protected: + bool event(QEvent* event) override; - bool event(QEvent* event) override; + public: + SceneToolMode(SceneToolbar* parent, const QString& toolTip); - public: + void showPanel(const QPoint& position) override; - SceneToolMode (SceneToolbar *parent, const QString& toolTip); + void addButton(const std::string& icon, const std::string& id, const QString& tooltip = ""); - void showPanel (const QPoint& position) override; + /// The ownership of \a button is transferred to *this. + void addButton(ModeButton* button, const std::string& id); - void addButton (const std::string& icon, const std::string& id, - const QString& tooltip = ""); + /// Will return a 0-pointer only if the mode does not have any buttons yet. + ModeButton* getCurrent(); - /// The ownership of \a button is transferred to *this. - void addButton (ModeButton *button, const std::string& id); + /// Must not be called if there aren't any buttons yet. + std::string getCurrentId() const; - /// Will return a 0-pointer only if the mode does not have any buttons yet. - ModeButton *getCurrent(); + /// Manually change the current mode + void setButton(const std::string& id); - /// Must not be called if there aren't any buttons yet. - std::string getCurrentId() const; + signals: - /// Manually change the current mode - void setButton (const std::string& id); + void modeChanged(const std::string& id); - signals: + private slots: - void modeChanged (const std::string& id); - - private slots: - - void selected(); + void selected(); }; } diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp index 24bcf3f13..6313f10fa 100644 --- a/apps/opencs/view/widget/scenetoolrun.cpp +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -2,124 +2,134 @@ #include +#include #include -#include #include #include -#include +#include + +#include +#include + +class QPoint; + +namespace CSVWidget +{ + class SceneToolbar; +} void CSVWidget::SceneToolRun::adjustToolTips() { QString toolTip = mToolTip; - if (mSelected==mProfiles.end()) + if (mSelected == mProfiles.end()) toolTip += "

        No debug profile selected (function disabled)"; else { - toolTip += "

        Debug profile: " + QString::fromUtf8 (mSelected->c_str()); + toolTip += "

        Debug profile: " + QString::fromUtf8(mSelected->c_str()); toolTip += "

        (right click to switch to a different profile)"; } - setToolTip (toolTip); + setToolTip(toolTip); } void CSVWidget::SceneToolRun::updateIcon() { - setDisabled (mSelected==mProfiles.end()); + setDisabled(mSelected == mProfiles.end()); } void CSVWidget::SceneToolRun::updatePanel() { - mTable->setRowCount (static_cast(mProfiles.size())); + mTable->setRowCount(static_cast(mProfiles.size())); int i = 0; - for (std::set::const_iterator iter (mProfiles.begin()); iter!=mProfiles.end(); - ++iter, ++i) + for (std::set::const_iterator iter(mProfiles.begin()); iter != mProfiles.end(); ++iter, ++i) { - mTable->setItem (i, 0, new QTableWidgetItem (QString::fromUtf8 (iter->c_str()))); + mTable->setItem(i, 0, new QTableWidgetItem(QString::fromUtf8(iter->c_str()))); - mTable->setItem (i, 1, new QTableWidgetItem ( - QApplication::style()->standardIcon (QStyle::SP_TitleBarCloseButton), "")); + mTable->setItem( + i, 1, new QTableWidgetItem(QApplication::style()->standardIcon(QStyle::SP_TitleBarCloseButton), "")); } } -CSVWidget::SceneToolRun::SceneToolRun (SceneToolbar *parent, const QString& toolTip, - const QString& icon, const std::vector& profiles) -: SceneTool (parent, Type_TopAction), mProfiles (profiles.begin(), profiles.end()), - mSelected (mProfiles.begin()), mToolTip (toolTip) +CSVWidget::SceneToolRun::SceneToolRun( + SceneToolbar* parent, const QString& toolTip, const QString& icon, const std::vector& profiles) + : SceneTool(parent, Type_TopAction) + , mProfiles(profiles.begin(), profiles.end()) + , mSelected(mProfiles.begin()) + , mToolTip(toolTip) { - setIcon (QIcon (icon)); + setIcon(QIcon(icon)); updateIcon(); adjustToolTips(); - mPanel = new QFrame (this, Qt::Popup); + mPanel = new QFrame(this, Qt::Popup); - QHBoxLayout *layout = new QHBoxLayout (mPanel); + QHBoxLayout* layout = new QHBoxLayout(mPanel); - layout->setContentsMargins (QMargins (0, 0, 0, 0)); + layout->setContentsMargins(QMargins(0, 0, 0, 0)); - mTable = new QTableWidget (0, 2, this); + mTable = new QTableWidget(0, 2, this); - mTable->setShowGrid (false); + mTable->setShowGrid(false); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); - mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); - mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); - mTable->setSelectionMode (QAbstractItemView::NoSelection); + mTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + mTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + mTable->setSelectionMode(QAbstractItemView::NoSelection); - layout->addWidget (mTable); + layout->addWidget(mTable); - connect (mTable, SIGNAL (clicked (const QModelIndex&)), - this, SLOT (clicked (const QModelIndex&))); + connect(mTable, &QTableWidget::clicked, this, &SceneToolRun::clicked); } -void CSVWidget::SceneToolRun::showPanel (const QPoint& position) +void CSVWidget::SceneToolRun::showPanel(const QPoint& position) { updatePanel(); - mPanel->move (position); + mPanel->move(position); mPanel->show(); } void CSVWidget::SceneToolRun::activate() { - if (mSelected!=mProfiles.end()) - emit runRequest (*mSelected); + if (mSelected != mProfiles.end()) + emit runRequest(*mSelected); } -void CSVWidget::SceneToolRun::removeProfile (const std::string& profile) +void CSVWidget::SceneToolRun::removeProfile(const std::string& profile) { - std::set::iterator iter = mProfiles.find (profile); + std::set::iterator iter = mProfiles.find(profile); - if (iter!=mProfiles.end()) + if (iter != mProfiles.end()) { - if (iter==mSelected) + if (iter == mSelected) { - if (iter!=mProfiles.begin()) + if (iter != mProfiles.begin()) --mSelected; else ++mSelected; } - mProfiles.erase (iter); + mProfiles.erase(iter); - if (mSelected==mProfiles.end()) + if (mSelected == mProfiles.end()) updateIcon(); adjustToolTips(); } } -void CSVWidget::SceneToolRun::addProfile (const std::string& profile) +void CSVWidget::SceneToolRun::addProfile(const std::string& profile) { - std::set::iterator iter = mProfiles.find (profile); + std::set::iterator iter = mProfiles.find(profile); - if (iter==mProfiles.end()) + if (iter == mProfiles.end()) { - mProfiles.insert (profile); + mProfiles.insert(profile); - if (mSelected==mProfiles.end()) + if (mSelected == mProfiles.end()) { mSelected = mProfiles.begin(); updateIcon(); @@ -129,22 +139,22 @@ void CSVWidget::SceneToolRun::addProfile (const std::string& profile) } } -void CSVWidget::SceneToolRun::clicked (const QModelIndex& index) +void CSVWidget::SceneToolRun::clicked(const QModelIndex& index) { - if (index.column()==0) + if (index.column() == 0) { // select profile mSelected = mProfiles.begin(); - std::advance (mSelected, index.row()); + std::advance(mSelected, index.row()); mPanel->hide(); adjustToolTips(); } - else if (index.column()==1) + else if (index.column() == 1) { // remove profile from list std::set::iterator iter = mProfiles.begin(); - std::advance (iter, index.row()); - removeProfile (*iter); + std::advance(iter, index.row()); + removeProfile(*iter); updatePanel(); } } diff --git a/apps/opencs/view/widget/scenetoolrun.hpp b/apps/opencs/view/widget/scenetoolrun.hpp index 4a90aa7c0..6fd3c3822 100644 --- a/apps/opencs/view/widget/scenetoolrun.hpp +++ b/apps/opencs/view/widget/scenetoolrun.hpp @@ -3,59 +3,62 @@ #include #include +#include #include "scenetool.hpp" class QFrame; class QTableWidget; class QModelIndex; +class QObject; +class QPoint; namespace CSVWidget { + class SceneToolbar; + class SceneToolRun : public SceneTool { - Q_OBJECT + Q_OBJECT - std::set mProfiles; - std::set::iterator mSelected; - QString mToolTip; - QFrame *mPanel; - QTableWidget *mTable; + std::set mProfiles; + std::set::iterator mSelected; + QString mToolTip; + QFrame* mPanel; + QTableWidget* mTable; - private: + private: + void adjustToolTips(); - void adjustToolTips(); + void updateIcon(); - void updateIcon(); + void updatePanel(); - void updatePanel(); + public: + SceneToolRun(SceneToolbar* parent, const QString& toolTip, const QString& icon, + const std::vector& profiles); - public: + void showPanel(const QPoint& position) override; - SceneToolRun (SceneToolbar *parent, const QString& toolTip, const QString& icon, - const std::vector& profiles); + void activate() override; - void showPanel (const QPoint& position) override; + /// \attention This function does not remove the profile from the profile selection + /// panel. + void removeProfile(const std::string& profile); - void activate() override; + /// \attention This function doe not add the profile to the profile selection + /// panel. This only happens when the panel is re-opened. + /// + /// \note Adding profiles that are already listed is a no-op. + void addProfile(const std::string& profile); - /// \attention This function does not remove the profile from the profile selection - /// panel. - void removeProfile (const std::string& profile); + private slots: - /// \attention This function doe not add the profile to the profile selection - /// panel. This only happens when the panel is re-opened. - /// - /// \note Adding profiles that are already listed is a no-op. - void addProfile (const std::string& profile); + void clicked(const QModelIndex& index); - private slots: + signals: - void clicked (const QModelIndex& index); - - signals: - - void runRequest (const std::string& profile); + void runRequest(const std::string& profile); }; } diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index d0419a13a..57b78ffc7 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -1,35 +1,41 @@ #include "scenetoolshapebrush.hpp" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include #include -#include +#include #include -#include -#include +#include +#include +#include +#include #include -#include +#include +#include #include +#include +#include +#include +#include + +#include +#include +#include #include "brushshapes.hpp" #include "scenetool.hpp" -#include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" -#include "../../model/world/commands.hpp" +namespace CSVWidget +{ + class SceneToolbar; +} -CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, QWidget *parent) +namespace CSMDoc +{ + class Document; +} + +CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString& title, QWidget* parent) : QGroupBox(title, parent) { mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); @@ -40,44 +46,44 @@ CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); mBrushSizeSpinBox->setSingleStep(1); - QHBoxLayout *layoutSliderSize = new QHBoxLayout; + QHBoxLayout* layoutSliderSize = new QHBoxLayout; layoutSliderSize->addWidget(mBrushSizeSlider); layoutSliderSize->addWidget(mBrushSizeSpinBox); - connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int))); - connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int))); + connect(mBrushSizeSlider, &QSlider::valueChanged, mBrushSizeSpinBox, &QSpinBox::setValue); + connect(mBrushSizeSpinBox, qOverload(&QSpinBox::valueChanged), mBrushSizeSlider, &QSlider::setValue); setLayout(layoutSliderSize); } -CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) - : QFrame(parent, Qt::Popup), - mDocument(document) +CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget* parent) + : QFrame(parent, Qt::Popup) + , mDocument(document) { - mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this); - mButtonSquare = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-square")), "", this); - mButtonCircle = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-circle")), "", this); - mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this); + mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this); + mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this); + mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this); + mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this); mSizeSliders = new ShapeBrushSizeControls("Brush size", this); - QVBoxLayout *layoutMain = new QVBoxLayout; + QVBoxLayout* layoutMain = new QVBoxLayout; layoutMain->setSpacing(0); - layoutMain->setContentsMargins(4,0,4,4); + layoutMain->setContentsMargins(4, 0, 4, 4); - QHBoxLayout *layoutHorizontal = new QHBoxLayout; + QHBoxLayout* layoutHorizontal = new QHBoxLayout; layoutHorizontal->setSpacing(0); - layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0)); + layoutHorizontal->setContentsMargins(QMargins(0, 0, 0, 0)); configureButtonInitialSettings(mButtonPoint); configureButtonInitialSettings(mButtonSquare); configureButtonInitialSettings(mButtonCircle); configureButtonInitialSettings(mButtonCustom); - mButtonPoint->setToolTip (toolTipPoint); - mButtonSquare->setToolTip (toolTipSquare); - mButtonCircle->setToolTip (toolTipCircle); - mButtonCustom->setToolTip (toolTipCustom); + mButtonPoint->setToolTip(toolTipPoint); + mButtonSquare->setToolTip(toolTipSquare); + mButtonCircle->setToolTip(toolTipCircle); + mButtonCustom->setToolTip(toolTipCustom); QButtonGroup* brushButtonGroup = new QButtonGroup(this); brushButtonGroup->addButton(mButtonPoint); @@ -102,7 +108,7 @@ CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidge mToolSelector->addItem(tr("Smooth (paint)")); mToolSelector->addItem(tr("Flatten (paint)")); - QLabel *brushStrengthLabel = new QLabel(this); + QLabel* brushStrengthLabel = new QLabel(this); brushStrengthLabel->setText("Brush strength:"); mToolStrengthSlider = new QSlider(Qt::Horizontal); @@ -120,19 +126,19 @@ CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidge setLayout(layoutMain); - connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape())); - connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape())); - connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape())); - connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape())); + connect(mButtonPoint, &QPushButton::clicked, this, &ShapeBrushWindow::setBrushShape); + connect(mButtonSquare, &QPushButton::clicked, this, &ShapeBrushWindow::setBrushShape); + connect(mButtonCircle, &QPushButton::clicked, this, &ShapeBrushWindow::setBrushShape); + connect(mButtonCustom, &QPushButton::clicked, this, &ShapeBrushWindow::setBrushShape); } -void CSVWidget::ShapeBrushWindow::configureButtonInitialSettings(QPushButton *button) +void CSVWidget::ShapeBrushWindow::configureButtonInitialSettings(QPushButton* button) { - button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); - button->setContentsMargins (QMargins (0, 0, 0, 0)); - button->setIconSize (QSize (48-6, 48-6)); - button->setFixedSize (48, 48); - button->setCheckable(true); + button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setContentsMargins(QMargins(0, 0, 0, 0)); + button->setIconSize(QSize(48 - 6, 48 - 6)); + button->setFixedSize(48, 48); + button->setCheckable(true); } void CSVWidget::ShapeBrushWindow::setBrushSize(int brushSize) @@ -143,50 +149,51 @@ void CSVWidget::ShapeBrushWindow::setBrushSize(int brushSize) void CSVWidget::ShapeBrushWindow::setBrushShape() { - if(mButtonPoint->isChecked()) mBrushShape = BrushShape_Point; - if(mButtonSquare->isChecked()) mBrushShape = BrushShape_Square; - if(mButtonCircle->isChecked()) mBrushShape = BrushShape_Circle; - if(mButtonCustom->isChecked()) mBrushShape = BrushShape_Custom; + if (mButtonPoint->isChecked()) + mBrushShape = BrushShape_Point; + if (mButtonSquare->isChecked()) + mBrushShape = BrushShape_Square; + if (mButtonCircle->isChecked()) + mBrushShape = BrushShape_Circle; + if (mButtonCustom->isChecked()) + mBrushShape = BrushShape_Custom; emit passBrushShape(mBrushShape); } -void CSVWidget::SceneToolShapeBrush::adjustToolTips() -{ -} +void CSVWidget::SceneToolShapeBrush::adjustToolTips() {} -CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document) -: SceneTool (parent, Type_TopAction), - mToolTip (toolTip), - mDocument (document), - mShapeBrushWindow(new ShapeBrushWindow(document, this)) +CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush( + SceneToolbar* parent, const QString& toolTip, CSMDoc::Document& document) + : SceneTool(parent, Type_TopAction) + , mToolTip(toolTip) + , mDocument(document) + , mShapeBrushWindow(new ShapeBrushWindow(document, this)) { setAcceptDrops(true); - connect(mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setButtonIcon(CSVWidget::BrushShape))); + connect(mShapeBrushWindow, &ShapeBrushWindow::passBrushShape, this, &SceneToolShapeBrush::setButtonIcon); setButtonIcon(mShapeBrushWindow->mBrushShape); - mPanel = new QFrame (this, Qt::Popup); + mPanel = new QFrame(this, Qt::Popup); - QHBoxLayout *layout = new QHBoxLayout (mPanel); + QHBoxLayout* layout = new QHBoxLayout(mPanel); - layout->setContentsMargins (QMargins (0, 0, 0, 0)); + layout->setContentsMargins(QMargins(0, 0, 0, 0)); - mTable = new QTableWidget (0, 2, this); + mTable = new QTableWidget(0, 2, this); - mTable->setShowGrid (true); + mTable->setShowGrid(true); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); - mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); - mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); - mTable->setSelectionMode (QAbstractItemView::NoSelection); + mTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + mTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + mTable->setSelectionMode(QAbstractItemView::NoSelection); - layout->addWidget (mTable); - - connect (mTable, SIGNAL (clicked (const QModelIndex&)), - this, SLOT (clicked (const QModelIndex&))); + layout->addWidget(mTable); + connect(mTable, &QTableWidget::clicked, this, &SceneToolShapeBrush::clicked); } -void CSVWidget::SceneToolShapeBrush::setButtonIcon (CSVWidget::BrushShape brushShape) +void CSVWidget::SceneToolShapeBrush::setButtonIcon(CSVWidget::BrushShape brushShape) { QString tooltip = "Change brush settings

        Currently selected: "; @@ -194,59 +201,55 @@ void CSVWidget::SceneToolShapeBrush::setButtonIcon (CSVWidget::BrushShape brushS { case BrushShape_Point: - setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); + setIcon(QIcon(QPixmap(":scenetoolbar/brush-point"))); tooltip += mShapeBrushWindow->toolTipPoint; break; case BrushShape_Square: - setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); + setIcon(QIcon(QPixmap(":scenetoolbar/brush-square"))); tooltip += mShapeBrushWindow->toolTipSquare; break; case BrushShape_Circle: - setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); + setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle"))); tooltip += mShapeBrushWindow->toolTipCircle; break; case BrushShape_Custom: - setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom"))); + setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom"))); tooltip += mShapeBrushWindow->toolTipCustom; break; } - setToolTip (tooltip); + setToolTip(tooltip); } -void CSVWidget::SceneToolShapeBrush::showPanel (const QPoint& position) -{ -} +void CSVWidget::SceneToolShapeBrush::showPanel(const QPoint& position) {} -void CSVWidget::SceneToolShapeBrush::updatePanel () -{ -} +void CSVWidget::SceneToolShapeBrush::updatePanel() {} -void CSVWidget::SceneToolShapeBrush::clicked (const QModelIndex& index) -{ -} +void CSVWidget::SceneToolShapeBrush::clicked(const QModelIndex& index) {} -void CSVWidget::SceneToolShapeBrush::activate () +void CSVWidget::SceneToolShapeBrush::activate() { QPoint position = QCursor::pos(); - mShapeBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); - mShapeBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); - mShapeBrushWindow->move (position); + mShapeBrushWindow->mSizeSliders->mBrushSizeSlider->setRange( + 1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mShapeBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange( + 1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mShapeBrushWindow->move(position); mShapeBrushWindow->show(); } -void CSVWidget::SceneToolShapeBrush::dragEnterEvent (QDragEnterEvent *event) +void CSVWidget::SceneToolShapeBrush::dragEnterEvent(QDragEnterEvent* event) { emit passEvent(event); event->accept(); } -void CSVWidget::SceneToolShapeBrush::dropEvent (QDropEvent *event) +void CSVWidget::SceneToolShapeBrush::dropEvent(QDropEvent* event) { emit passEvent(event); event->accept(); diff --git a/apps/opencs/view/widget/scenetoolshapebrush.hpp b/apps/opencs/view/widget/scenetoolshapebrush.hpp index 3afd7f8b3..6143c8959 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.hpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -1,27 +1,30 @@ #ifndef CSV_WIDGET_SCENETOOLSHAPEBRUSH_H #define CSV_WIDGET_SCENETOOLSHAPEBRUSH_H -#include #include -#include - -#include -#include -#include -#include #include #include -#include -#include -#include +#include #ifndef Q_MOC_RUN #include "brushshapes.hpp" #include "scenetool.hpp" - -#include "../../model/doc/document.hpp" #endif +class QComboBox; +class QDragEnterEvent; +class QDropEvent; +class QModelIndex; +class QObject; +class QPoint; +class QPushButton; +class QWidget; + +namespace CSMDoc +{ + class Document; +} + class QTableWidget; namespace CSVRender @@ -31,17 +34,19 @@ namespace CSVRender namespace CSVWidget { + class SceneToolbar; + /// \brief Layout-box for some brush button settings class ShapeBrushSizeControls : public QGroupBox { Q_OBJECT - public: - ShapeBrushSizeControls(const QString &title, QWidget *parent); + public: + ShapeBrushSizeControls(const QString& title, QWidget* parent); - private: - QSlider *mBrushSizeSlider = new QSlider(Qt::Horizontal); - QSpinBox *mBrushSizeSpinBox = new QSpinBox; + private: + QSlider* mBrushSizeSlider = new QSlider(Qt::Horizontal); + QSpinBox* mBrushSizeSpinBox = new QSpinBox; friend class SceneToolShapeBrush; friend class CSVRender::TerrainShapeMode; @@ -52,75 +57,72 @@ namespace CSVWidget { Q_OBJECT - public: + public: + ShapeBrushWindow(CSMDoc::Document& document, QWidget* parent = nullptr); + void configureButtonInitialSettings(QPushButton* button); - ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = nullptr); - void configureButtonInitialSettings(QPushButton *button); + const QString toolTipPoint = "Paint single point"; + const QString toolTipSquare = "Paint with square brush"; + const QString toolTipCircle = "Paint with circle brush"; + const QString toolTipCustom = "Paint with custom brush, defined by terrain selection"; - const QString toolTipPoint = "Paint single point"; - const QString toolTipSquare = "Paint with square brush"; - const QString toolTipCircle = "Paint with circle brush"; - const QString toolTipCustom = "Paint with custom brush, defined by terrain selection"; - - private: - CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; - int mBrushSize = 1; - CSMDoc::Document& mDocument; - QGroupBox *mHorizontalGroupBox; - QComboBox *mToolSelector; - QSlider *mToolStrengthSlider; - QPushButton *mButtonPoint; - QPushButton *mButtonSquare; - QPushButton *mButtonCircle; - QPushButton *mButtonCustom; - ShapeBrushSizeControls* mSizeSliders; + private: + CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; + int mBrushSize = 1; + CSMDoc::Document& mDocument; + QGroupBox* mHorizontalGroupBox; + QComboBox* mToolSelector; + QSlider* mToolStrengthSlider; + QPushButton* mButtonPoint; + QPushButton* mButtonSquare; + QPushButton* mButtonCircle; + QPushButton* mButtonCustom; + ShapeBrushSizeControls* mSizeSliders; friend class SceneToolShapeBrush; friend class CSVRender::TerrainShapeMode; - public slots: - void setBrushShape(); - void setBrushSize(int brushSize); + public slots: + void setBrushShape(); + void setBrushSize(int brushSize); - signals: - void passBrushSize (int brushSize); - void passBrushShape(CSVWidget::BrushShape brushShape); + signals: + void passBrushSize(int brushSize); + void passBrushShape(CSVWidget::BrushShape brushShape); }; class SceneToolShapeBrush : public SceneTool { - Q_OBJECT + Q_OBJECT - QString mToolTip; - CSMDoc::Document& mDocument; - QFrame *mPanel; - QTableWidget *mTable; - ShapeBrushWindow *mShapeBrushWindow; + QString mToolTip; + CSMDoc::Document& mDocument; + QFrame* mPanel; + QTableWidget* mTable; + ShapeBrushWindow* mShapeBrushWindow; - private: + private: + void adjustToolTips(); - void adjustToolTips(); + public: + SceneToolShapeBrush(SceneToolbar* parent, const QString& toolTip, CSMDoc::Document& document); - public: + void showPanel(const QPoint& position) override; + void updatePanel(); - SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document); - - void showPanel (const QPoint& position) override; - void updatePanel (); - - void dropEvent (QDropEvent *event) override; - void dragEnterEvent (QDragEnterEvent *event) override; + void dropEvent(QDropEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; friend class CSVRender::TerrainShapeMode; - public slots: - void setButtonIcon(CSVWidget::BrushShape brushShape); - void clicked (const QModelIndex& index); - void activate() override; + public slots: + void setButtonIcon(CSVWidget::BrushShape brushShape); + void clicked(const QModelIndex& index); + void activate() override; - signals: - void passEvent(QDropEvent *event); - void passEvent(QDragEnterEvent *event); + signals: + void passEvent(QDropEvent* event); + void passEvent(QDragEnterEvent* event); }; } diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index 272a5de42..cc372753f 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -1,27 +1,36 @@ #include "scenetooltexturebrush.hpp" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include #include -#include #include -#include -#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include + +#include +#include +#include #include "scenetool.hpp" +#include +#include +#include +#include +#include +#include + #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" @@ -31,12 +40,16 @@ #include "../../model/world/landtexture.hpp" #include "../../model/world/universalid.hpp" +namespace CSVWidget +{ + class SceneToolbar; +} -CSVWidget::BrushSizeControls::BrushSizeControls(const QString &title, QWidget *parent) - : QGroupBox(title, parent), - mLayoutSliderSize(new QHBoxLayout), - mBrushSizeSlider(new QSlider(Qt::Horizontal)), - mBrushSizeSpinBox(new QSpinBox) +CSVWidget::BrushSizeControls::BrushSizeControls(const QString& title, QWidget* parent) + : QGroupBox(title, parent) + , mLayoutSliderSize(new QHBoxLayout) + , mBrushSizeSlider(new QSlider(Qt::Horizontal)) + , mBrushSizeSpinBox(new QSpinBox) { mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); mBrushSizeSlider->setTickInterval(10); @@ -49,56 +62,58 @@ CSVWidget::BrushSizeControls::BrushSizeControls(const QString &title, QWidget *p mLayoutSliderSize->addWidget(mBrushSizeSlider); mLayoutSliderSize->addWidget(mBrushSizeSpinBox); - connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int))); - connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int))); + connect(mBrushSizeSlider, &QSlider::valueChanged, mBrushSizeSpinBox, &QSpinBox::setValue); + connect(mBrushSizeSpinBox, qOverload(&QSpinBox::valueChanged), mBrushSizeSlider, &QSlider::setValue); setLayout(mLayoutSliderSize); } -CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QWidget *parent) - : QFrame(parent, Qt::Popup), - mDocument(document) +CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QWidget* parent) + : QFrame(parent, Qt::Popup) + , mDocument(document) { mBrushTextureLabel = "Selected texture: " + mBrushTexture + " "; CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture); - int index = landtexturesCollection.searchId(mBrushTexture); + const int index = landtexturesCollection.searchId(ESM::RefId::stringRefId(mBrushTexture)); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) { - mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value()); - } else + mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel) + + landtexturesCollection.getData(index, landTextureFilename).value()); + } + else { mBrushTextureLabel = "No selected texture or invalid texture"; mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel)); } - mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this); - mButtonSquare = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-square")), "", this); - mButtonCircle = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-circle")), "", this); - mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this); + mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this); + mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this); + mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this); + mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this); mSizeSliders = new BrushSizeControls("Brush size", this); - QVBoxLayout *layoutMain = new QVBoxLayout; + QVBoxLayout* layoutMain = new QVBoxLayout; layoutMain->setSpacing(0); - layoutMain->setContentsMargins(4,0,4,4); + layoutMain->setContentsMargins(4, 0, 4, 4); - QHBoxLayout *layoutHorizontal = new QHBoxLayout; + QHBoxLayout* layoutHorizontal = new QHBoxLayout; layoutHorizontal->setSpacing(0); - layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0)); + layoutHorizontal->setContentsMargins(QMargins(0, 0, 0, 0)); configureButtonInitialSettings(mButtonPoint); configureButtonInitialSettings(mButtonSquare); configureButtonInitialSettings(mButtonCircle); configureButtonInitialSettings(mButtonCustom); - mButtonPoint->setToolTip (toolTipPoint); - mButtonSquare->setToolTip (toolTipSquare); - mButtonCircle->setToolTip (toolTipCircle); - mButtonCustom->setToolTip (toolTipCustom); + mButtonPoint->setToolTip(toolTipPoint); + mButtonSquare->setToolTip(toolTipSquare); + mButtonCircle->setToolTip(toolTipCircle); + mButtonCustom->setToolTip(toolTipCustom); QButtonGroup* brushButtonGroup = new QButtonGroup(this); brushButtonGroup->addButton(mButtonPoint); @@ -122,25 +137,25 @@ CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QW setLayout(layoutMain); - connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape())); - connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape())); - connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape())); - connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape())); + connect(mButtonPoint, &QPushButton::clicked, this, &TextureBrushWindow::setBrushShape); + connect(mButtonSquare, &QPushButton::clicked, this, &TextureBrushWindow::setBrushShape); + connect(mButtonCircle, &QPushButton::clicked, this, &TextureBrushWindow::setBrushShape); + connect(mButtonCustom, &QPushButton::clicked, this, &TextureBrushWindow::setBrushShape); } -void CSVWidget::TextureBrushWindow::configureButtonInitialSettings(QPushButton *button) +void CSVWidget::TextureBrushWindow::configureButtonInitialSettings(QPushButton* button) { - button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); - button->setContentsMargins (QMargins (0, 0, 0, 0)); - button->setIconSize (QSize (48-6, 48-6)); - button->setFixedSize (48, 48); - button->setCheckable(true); + button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setContentsMargins(QMargins(0, 0, 0, 0)); + button->setIconSize(QSize(48 - 6, 48 - 6)); + button->setFixedSize(48, 48); + button->setCheckable(true); } void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture) { - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& ltexTable = dynamic_cast( + *mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); QUndoStack& undoStack = mDocument.getUndoStack(); CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); @@ -149,9 +164,11 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture) int index = 0; int pluginInDragged = 0; CSMWorld::LandTexture::parseUniqueRecordId(brushTexture, pluginInDragged, index); + const ESM::RefId brushTextureRefId = ESM::RefId::stringRefId(brushTexture); std::string newBrushTextureId = CSMWorld::LandTexture::createUniqueRecordId(0, index); - int rowInBase = landtexturesCollection.searchId(brushTexture); - int rowInNew = landtexturesCollection.searchId(newBrushTextureId); + ESM::RefId newBrushTextureRefId = ESM::RefId::stringRefId(newBrushTextureId); + int rowInBase = landtexturesCollection.searchId(brushTextureRefId); + int rowInNew = landtexturesCollection.searchId(newBrushTextureRefId); // Check if texture exists in current plugin, and clone if id found in base, otherwise reindex the texture // TO-DO: Handle case when texture is not found in neither base or plugin properly (finding new index is not enough) @@ -160,32 +177,38 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture) { if (rowInBase == -1) { - int counter=0; + int counter = 0; bool freeIndexFound = false; const int maxCounter = std::numeric_limits::max() - 1; - do { + do + { newBrushTextureId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); - if (landtexturesCollection.searchId(brushTexture) != -1 && - landtexturesCollection.getRecord(brushTexture).isDeleted() == 0 && - landtexturesCollection.searchId(newBrushTextureId) != -1 && - landtexturesCollection.getRecord(newBrushTextureId).isDeleted() == 0) - counter = (counter + 1) % maxCounter; - else freeIndexFound = true; + newBrushTextureRefId = ESM::RefId::stringRefId(newBrushTextureId); + if (landtexturesCollection.searchId(brushTextureRefId) != -1 + && landtexturesCollection.getRecord(brushTextureRefId).isDeleted() == 0 + && landtexturesCollection.searchId(newBrushTextureRefId) != -1 + && landtexturesCollection.getRecord(newBrushTextureRefId).isDeleted() == 0) + counter = (counter + 1) % maxCounter; + else + freeIndexFound = true; } while (freeIndexFound == false || counter < maxCounter); } - undoStack.beginMacro ("Add land texture record"); - undoStack.push (new CSMWorld::CloneCommand (ltexTable, brushTexture, newBrushTextureId, CSMWorld::UniversalId::Type_LandTexture)); + undoStack.beginMacro("Add land texture record"); + undoStack.push(new CSMWorld::CloneCommand( + ltexTable, brushTexture, newBrushTextureId, CSMWorld::UniversalId::Type_LandTexture)); undoStack.endMacro(); } - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) + if (index != -1 && !landtexturesCollection.getRecord(rowInNew).isDeleted()) { mBrushTextureLabel = "Selected texture: " + newBrushTextureId + " "; - mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value()); - } else + mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + + landtexturesCollection.getData(rowInNew, landTextureFilename).value()); + } + else { - newBrushTextureId = ""; + newBrushTextureId.clear(); mBrushTextureLabel = "No selected texture or invalid texture"; mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel)); } @@ -215,46 +238,43 @@ void CSVWidget::TextureBrushWindow::setBrushShape() emit passBrushShape(mBrushShape); } -void CSVWidget::SceneToolTextureBrush::adjustToolTips() -{ -} +void CSVWidget::SceneToolTextureBrush::adjustToolTips() {} -CSVWidget::SceneToolTextureBrush::SceneToolTextureBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document) -: SceneTool (parent, Type_TopAction), - mToolTip (toolTip), - mDocument (document), - mTextureBrushWindow(new TextureBrushWindow(document, this)) +CSVWidget::SceneToolTextureBrush::SceneToolTextureBrush( + SceneToolbar* parent, const QString& toolTip, CSMDoc::Document& document) + : SceneTool(parent, Type_TopAction) + , mToolTip(toolTip) + , mDocument(document) + , mTextureBrushWindow(new TextureBrushWindow(document, this)) { mBrushHistory.resize(1); mBrushHistory[0] = "L0#0"; setAcceptDrops(true); - connect(mTextureBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setButtonIcon(CSVWidget::BrushShape))); + connect(mTextureBrushWindow, &TextureBrushWindow::passBrushShape, this, &SceneToolTextureBrush::setButtonIcon); setButtonIcon(mTextureBrushWindow->mBrushShape); - mPanel = new QFrame (this, Qt::Popup); + mPanel = new QFrame(this, Qt::Popup); - QHBoxLayout *layout = new QHBoxLayout (mPanel); + QHBoxLayout* layout = new QHBoxLayout(mPanel); - layout->setContentsMargins (QMargins (0, 0, 0, 0)); + layout->setContentsMargins(QMargins(0, 0, 0, 0)); - mTable = new QTableWidget (0, 2, this); + mTable = new QTableWidget(0, 2, this); - mTable->setShowGrid (true); + mTable->setShowGrid(true); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); - mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); - mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); - mTable->setSelectionMode (QAbstractItemView::NoSelection); + mTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + mTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + mTable->setSelectionMode(QAbstractItemView::NoSelection); - layout->addWidget (mTable); - - connect (mTable, SIGNAL (clicked (const QModelIndex&)), - this, SLOT (clicked (const QModelIndex&))); + layout->addWidget(mTable); + connect(mTable, &QTableWidget::clicked, this, &SceneToolTextureBrush::clicked); } -void CSVWidget::SceneToolTextureBrush::setButtonIcon (CSVWidget::BrushShape brushShape) +void CSVWidget::SceneToolTextureBrush::setButtonIcon(CSVWidget::BrushShape brushShape) { QString tooltip = "Change brush settings

        Currently selected: "; @@ -262,89 +282,92 @@ void CSVWidget::SceneToolTextureBrush::setButtonIcon (CSVWidget::BrushShape brus { case BrushShape_Point: - setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); + setIcon(QIcon(QPixmap(":scenetoolbar/brush-point"))); tooltip += mTextureBrushWindow->toolTipPoint; break; case BrushShape_Square: - setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); + setIcon(QIcon(QPixmap(":scenetoolbar/brush-square"))); tooltip += mTextureBrushWindow->toolTipSquare; break; case BrushShape_Circle: - setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); + setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle"))); tooltip += mTextureBrushWindow->toolTipCircle; break; case BrushShape_Custom: - setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom"))); + setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom"))); tooltip += mTextureBrushWindow->toolTipCustom; break; } tooltip += "

        (right click to access of previously used brush settings)"; - CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture); - int index = landtexturesCollection.searchId(mTextureBrushWindow->mBrushTexture); + const int index = landtexturesCollection.searchId(ESM::RefId::stringRefId(mTextureBrushWindow->mBrushTexture)); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) { tooltip += "

        Selected texture: " + QString::fromStdString(mTextureBrushWindow->mBrushTexture) + " "; tooltip += landtexturesCollection.getData(index, landTextureFilename).value(); - } else + } + else { tooltip += "

        No selected texture or invalid texture"; } tooltip += "
        (drop texture here to change)"; - setToolTip (tooltip); + setToolTip(tooltip); } -void CSVWidget::SceneToolTextureBrush::showPanel (const QPoint& position) +void CSVWidget::SceneToolTextureBrush::showPanel(const QPoint& position) { updatePanel(); - mPanel->move (position); + mPanel->move(position); mPanel->show(); } void CSVWidget::SceneToolTextureBrush::updatePanel() { - mTable->setRowCount (mBrushHistory.size()); + mTable->setRowCount(mBrushHistory.size()); - for (int i = mBrushHistory.size()-1; i >= 0; --i) + for (int i = mBrushHistory.size() - 1; i >= 0; --i) { CSMWorld::IdCollection& landtexturesCollection = mDocument.getData().getLandTextures(); int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture); - int index = landtexturesCollection.searchId(mBrushHistory[i]); + const int index = landtexturesCollection.searchId(ESM::RefId::stringRefId(mBrushHistory[i])); if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) { - mTable->setItem (i, 1, new QTableWidgetItem (landtexturesCollection.getData(index, landTextureFilename).value())); - mTable->setItem (i, 0, new QTableWidgetItem (QString::fromStdString(mBrushHistory[i]))); - } else + mTable->setItem(i, 1, + new QTableWidgetItem(landtexturesCollection.getData(index, landTextureFilename).value())); + mTable->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(mBrushHistory[i]))); + } + else { - mTable->setItem (i, 1, new QTableWidgetItem ("Invalid/deleted texture")); - mTable->setItem (i, 0, new QTableWidgetItem (QString::fromStdString(mBrushHistory[i]))); + mTable->setItem(i, 1, new QTableWidgetItem("Invalid/deleted texture")); + mTable->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(mBrushHistory[i]))); } } } -void CSVWidget::SceneToolTextureBrush::updateBrushHistory (const std::string& brushTexture) +void CSVWidget::SceneToolTextureBrush::updateBrushHistory(const std::string& brushTexture) { mBrushHistory.insert(mBrushHistory.begin(), brushTexture); - if(mBrushHistory.size() > 5) mBrushHistory.pop_back(); + if (mBrushHistory.size() > 5) + mBrushHistory.pop_back(); } -void CSVWidget::SceneToolTextureBrush::clicked (const QModelIndex& index) +void CSVWidget::SceneToolTextureBrush::clicked(const QModelIndex& index) { - if (index.column()==0 || index.column()==1) + if (index.column() == 0 || index.column() == 1) { std::string brushTexture = mBrushHistory[index.row()]; std::swap(mBrushHistory[index.row()], mBrushHistory[0]); @@ -355,21 +378,23 @@ void CSVWidget::SceneToolTextureBrush::clicked (const QModelIndex& index) } } -void CSVWidget::SceneToolTextureBrush::activate () +void CSVWidget::SceneToolTextureBrush::activate() { QPoint position = QCursor::pos(); - mTextureBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); - mTextureBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); - mTextureBrushWindow->move (position); + mTextureBrushWindow->mSizeSliders->mBrushSizeSlider->setRange( + 1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); + mTextureBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange( + 1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt()); + mTextureBrushWindow->move(position); mTextureBrushWindow->show(); } -void CSVWidget::SceneToolTextureBrush::dragEnterEvent (QDragEnterEvent *event) +void CSVWidget::SceneToolTextureBrush::dragEnterEvent(QDragEnterEvent* event) { emit passEvent(event); event->accept(); } -void CSVWidget::SceneToolTextureBrush::dropEvent (QDropEvent *event) +void CSVWidget::SceneToolTextureBrush::dropEvent(QDropEvent* event) { emit passEvent(event); event->accept(); diff --git a/apps/opencs/view/widget/scenetooltexturebrush.hpp b/apps/opencs/view/widget/scenetooltexturebrush.hpp index 5f42800cb..940ab60ee 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.hpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.hpp @@ -1,135 +1,135 @@ #ifndef CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H #define CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H -#include #include -#include - -#include -#include -#include #include -#include -#include -#include -#include #ifndef Q_MOC_RUN #include "brushshapes.hpp" #include "scenetool.hpp" - -#include "../../model/doc/document.hpp" #endif class QTableWidget; +class QDragEnterEvent; +class QDropEvent; +class QHBoxLayout; +class QLabel; +class QModelIndex; +class QObject; +class QPoint; +class QPushButton; +class QSlider; +class QSpinBox; +class QWidget; namespace CSVRender { class TerrainTextureMode; } +namespace CSMDoc +{ + class Document; +} + namespace CSVWidget { - class SceneToolTextureBrush; + class SceneToolbar; /// \brief Layout-box for some brush button settings class BrushSizeControls : public QGroupBox { Q_OBJECT - public: - BrushSizeControls(const QString &title, QWidget *parent); + public: + BrushSizeControls(const QString& title, QWidget* parent); - private: - QHBoxLayout *mLayoutSliderSize; - QSlider *mBrushSizeSlider; - QSpinBox *mBrushSizeSpinBox; + private: + QHBoxLayout* mLayoutSliderSize; + QSlider* mBrushSizeSlider; + QSpinBox* mBrushSizeSpinBox; friend class SceneToolTextureBrush; friend class CSVRender::TerrainTextureMode; }; - class SceneToolTextureBrush; - /// \brief Brush settings window class TextureBrushWindow : public QFrame { Q_OBJECT - public: - TextureBrushWindow(CSMDoc::Document& document, QWidget *parent = nullptr); - void configureButtonInitialSettings(QPushButton *button); + public: + TextureBrushWindow(CSMDoc::Document& document, QWidget* parent = nullptr); + void configureButtonInitialSettings(QPushButton* button); - const QString toolTipPoint = "Paint single point"; - const QString toolTipSquare = "Paint with square brush"; - const QString toolTipCircle = "Paint with circle brush"; - const QString toolTipCustom = "Paint custom selection (not implemented yet)"; + const QString toolTipPoint = "Paint single point"; + const QString toolTipSquare = "Paint with square brush"; + const QString toolTipCircle = "Paint with circle brush"; + const QString toolTipCustom = "Paint custom selection (not implemented yet)"; - private: - CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; - int mBrushSize = 1; - std::string mBrushTexture = "L0#0"; - CSMDoc::Document& mDocument; - QLabel *mSelectedBrush; - QGroupBox *mHorizontalGroupBox; - std::string mBrushTextureLabel; - QPushButton *mButtonPoint; - QPushButton *mButtonSquare; - QPushButton *mButtonCircle; - QPushButton *mButtonCustom; - BrushSizeControls* mSizeSliders; + private: + CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; + int mBrushSize = 1; + std::string mBrushTexture = "L0#0"; + CSMDoc::Document& mDocument; + QLabel* mSelectedBrush; + QGroupBox* mHorizontalGroupBox; + std::string mBrushTextureLabel; + QPushButton* mButtonPoint; + QPushButton* mButtonSquare; + QPushButton* mButtonCircle; + QPushButton* mButtonCustom; + BrushSizeControls* mSizeSliders; friend class SceneToolTextureBrush; friend class CSVRender::TerrainTextureMode; - public slots: - void setBrushTexture(std::string brushTexture); - void setBrushShape(); - void setBrushSize(int brushSize); + public slots: + void setBrushTexture(std::string brushTexture); + void setBrushShape(); + void setBrushSize(int brushSize); - signals: - void passBrushSize (int brushSize); - void passBrushShape(CSVWidget::BrushShape brushShape); - void passTextureId(std::string brushTexture); + signals: + void passBrushSize(int brushSize); + void passBrushShape(CSVWidget::BrushShape brushShape); + void passTextureId(std::string brushTexture); }; class SceneToolTextureBrush : public SceneTool { - Q_OBJECT + Q_OBJECT - QString mToolTip; - CSMDoc::Document& mDocument; - QFrame *mPanel; - QTableWidget *mTable; - std::vector mBrushHistory; - TextureBrushWindow *mTextureBrushWindow; + QString mToolTip; + CSMDoc::Document& mDocument; + QFrame* mPanel; + QTableWidget* mTable; + std::vector mBrushHistory; + TextureBrushWindow* mTextureBrushWindow; - private: + private: + void adjustToolTips(); - void adjustToolTips(); + public: + SceneToolTextureBrush(SceneToolbar* parent, const QString& toolTip, CSMDoc::Document& document); - public: + void showPanel(const QPoint& position) override; + void updatePanel(); - SceneToolTextureBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document); - - void showPanel (const QPoint& position) override; - void updatePanel (); - - void dropEvent (QDropEvent *event) override; - void dragEnterEvent (QDragEnterEvent *event) override; + void dropEvent(QDropEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; friend class CSVRender::TerrainTextureMode; - public slots: - void setButtonIcon(CSVWidget::BrushShape brushShape); - void updateBrushHistory (const std::string& mBrushTexture); - void clicked (const QModelIndex& index); - void activate() override; + public slots: + void setButtonIcon(CSVWidget::BrushShape brushShape); + void updateBrushHistory(const std::string& mBrushTexture); + void clicked(const QModelIndex& index); + void activate() override; - signals: - void passEvent(QDropEvent *event); - void passEvent(QDragEnterEvent *event); - void passTextureId(std::string brushTexture); + signals: + void passEvent(QDropEvent* event); + void passEvent(QDragEnterEvent* event); + void passTextureId(std::string brushTexture); }; } diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index 04ac3322b..f32992f3a 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -1,14 +1,18 @@ #include "scenetooltoggle.hpp" #include +#include +#include -#include #include +#include #include #include -#include "scenetoolbar.hpp" +#include + #include "pushbutton.hpp" +#include "scenetoolbar.hpp" void CSVWidget::SceneToolToggle::adjustToolTip() { @@ -18,8 +22,7 @@ void CSVWidget::SceneToolToggle::adjustToolTip() bool first = true; - for (std::map::const_iterator iter (mButtons.begin()); - iter!=mButtons.end(); ++iter) + for (std::map::const_iterator iter(mButtons.begin()); iter != mButtons.end(); ++iter) if (iter->first->isChecked()) { if (!first) @@ -35,36 +38,36 @@ void CSVWidget::SceneToolToggle::adjustToolTip() toolTip += "

        (left click to alter selection)"; - setToolTip (toolTip); + setToolTip(toolTip); } void CSVWidget::SceneToolToggle::adjustIcon() { unsigned int selection = getSelectionMask(); if (!selection) - setIcon (QIcon (QString::fromUtf8 (mEmptyIcon.c_str()))); + setIcon(QIcon(QString::fromUtf8(mEmptyIcon.c_str()))); else { - QPixmap pixmap (48, 48); - pixmap.fill (QColor (0, 0, 0, 0)); + QPixmap pixmap(48, 48); + pixmap.fill(QColor(0, 0, 0, 0)); { - QPainter painter (&pixmap); + QPainter painter(&pixmap); - for (std::map::const_iterator iter (mButtons.begin()); - iter!=mButtons.end(); ++iter) + for (std::map::const_iterator iter(mButtons.begin()); iter != mButtons.end(); + ++iter) if (iter->first->isChecked()) { - painter.drawImage (getIconBox (iter->second.mIndex), - QImage (QString::fromUtf8 (iter->second.mSmallIcon.c_str()))); + painter.drawImage( + getIconBox(iter->second.mIndex), QImage(QString::fromUtf8(iter->second.mSmallIcon.c_str()))); } } - setIcon (pixmap); + setIcon(pixmap); } } -QRect CSVWidget::SceneToolToggle::getIconBox (int index) const +QRect CSVWidget::SceneToolToggle::getIconBox(int index) const { // layout for a 3x3 grid int xMax = 3; @@ -74,27 +77,27 @@ QRect CSVWidget::SceneToolToggle::getIconBox (int index) const int xBorder = 1; int yBorder = 1; - int iconXSize = (mIconSize-xBorder*(xMax+1))/xMax; - int iconYSize = (mIconSize-yBorder*(yMax+1))/yMax; + int iconXSize = (mIconSize - xBorder * (xMax + 1)) / xMax; + int iconYSize = (mIconSize - yBorder * (yMax + 1)) / yMax; int y = index / xMax; int x = index % xMax; int total = static_cast(mButtons.size()); - int actualYIcons = total/xMax; + int actualYIcons = total / xMax; if (total % xMax) ++actualYIcons; - if (actualYIcons!=yMax) + if (actualYIcons != yMax) { // space out icons vertically, if there aren't enough to populate all rows int diff = yMax - actualYIcons; - yBorder += (diff*(yBorder+iconXSize)) / (actualYIcons+1); + yBorder += (diff * (yBorder + iconXSize)) / (actualYIcons + 1); } - if (y==actualYIcons-1) + if (y == actualYIcons - 1) { // generating the last row of icons int actualXIcons = total % xMax; @@ -104,51 +107,53 @@ QRect CSVWidget::SceneToolToggle::getIconBox (int index) const // space out icons horizontally, if there aren't enough to fill the last row int diff = xMax - actualXIcons; - xBorder += (diff*(xBorder+iconXSize)) / (actualXIcons+1); + xBorder += (diff * (xBorder + iconXSize)) / (actualXIcons + 1); } } - return QRect ((iconXSize+xBorder)*x+xBorder, (iconYSize+yBorder)*y+yBorder, - iconXSize, iconYSize); + return QRect((iconXSize + xBorder) * x + xBorder, (iconYSize + yBorder) * y + yBorder, iconXSize, iconYSize); } -CSVWidget::SceneToolToggle::SceneToolToggle (SceneToolbar *parent, const QString& toolTip, - const std::string& emptyIcon) -: SceneTool (parent), mEmptyIcon (emptyIcon), mButtonSize (parent->getButtonSize()), - mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (nullptr) +CSVWidget::SceneToolToggle::SceneToolToggle(SceneToolbar* parent, const QString& toolTip, const std::string& emptyIcon) + : SceneTool(parent) + , mEmptyIcon(emptyIcon) + , mButtonSize(parent->getButtonSize()) + , mIconSize(parent->getIconSize()) + , mToolTip(toolTip) + , mFirst(nullptr) { - mPanel = new QFrame (this, Qt::Popup); + mPanel = new QFrame(this, Qt::Popup); - mLayout = new QHBoxLayout (mPanel); + mLayout = new QHBoxLayout(mPanel); - mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); + mLayout->setContentsMargins(QMargins(0, 0, 0, 0)); - mPanel->setLayout (mLayout); + mPanel->setLayout(mLayout); } -void CSVWidget::SceneToolToggle::showPanel (const QPoint& position) +void CSVWidget::SceneToolToggle::showPanel(const QPoint& position) { - mPanel->move (position); + mPanel->move(position); mPanel->show(); if (mFirst) - mFirst->setFocus (Qt::OtherFocusReason); + mFirst->setFocus(Qt::OtherFocusReason); } -void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned int mask, - const std::string& smallIcon, const QString& name, const QString& tooltip) +void CSVWidget::SceneToolToggle::addButton(const std::string& icon, unsigned int mask, const std::string& smallIcon, + const QString& name, const QString& tooltip) { - if (mButtons.size()>=9) - throw std::runtime_error ("Exceeded number of buttons in toggle type tool"); + if (mButtons.size() >= 9) + throw std::runtime_error("Exceeded number of buttons in toggle type tool"); - PushButton *button = new PushButton (QIcon (QPixmap (icon.c_str())), - PushButton::Type_Toggle, tooltip.isEmpty() ? name: tooltip, mPanel); + PushButton* button = new PushButton( + QIcon(QPixmap(icon.c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel); - button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); - button->setIconSize (QSize (mIconSize, mIconSize)); - button->setFixedSize (mButtonSize, mButtonSize); + button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setIconSize(QSize(mIconSize, mIconSize)); + button->setFixedSize(mButtonSize, mButtonSize); - mLayout->addWidget (button); + mLayout->addWidget(button); ButtonDesc desc; desc.mMask = mask; @@ -156,11 +161,11 @@ void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned in desc.mName = name; desc.mIndex = static_cast(mButtons.size()); - mButtons.insert (std::make_pair (button, desc)); + mButtons.insert(std::make_pair(button, desc)); - connect (button, SIGNAL (clicked()), this, SLOT (selected())); + connect(button, &PushButton::clicked, this, &SceneToolToggle::selected); - if (mButtons.size()==1) + if (mButtons.size() == 1) mFirst = button; } @@ -168,19 +173,17 @@ unsigned int CSVWidget::SceneToolToggle::getSelectionMask() const { unsigned int selection = 0; - for (std::map::const_iterator iter (mButtons.begin()); - iter!=mButtons.end(); ++iter) + for (std::map::const_iterator iter(mButtons.begin()); iter != mButtons.end(); ++iter) if (iter->first->isChecked()) selection |= iter->second.mMask; return selection; } -void CSVWidget::SceneToolToggle::setSelectionMask (unsigned int selection) +void CSVWidget::SceneToolToggle::setSelectionMask(unsigned int selection) { - for (std::map::iterator iter (mButtons.begin()); - iter!=mButtons.end(); ++iter) - iter->first->setChecked (selection & iter->second.mMask); + for (std::map::iterator iter(mButtons.begin()); iter != mButtons.end(); ++iter) + iter->first->setChecked(selection & iter->second.mMask); adjustToolTip(); adjustIcon(); @@ -188,10 +191,9 @@ void CSVWidget::SceneToolToggle::setSelectionMask (unsigned int selection) void CSVWidget::SceneToolToggle::selected() { - std::map::const_iterator iter = - mButtons.find (dynamic_cast (sender())); + std::map::const_iterator iter = mButtons.find(dynamic_cast(sender())); - if (iter!=mButtons.end()) + if (iter != mButtons.end()) { if (!iter->first->hasKeepOpen()) mPanel->hide(); diff --git a/apps/opencs/view/widget/scenetooltoggle.hpp b/apps/opencs/view/widget/scenetooltoggle.hpp index f08d117fb..2a3b71bce 100644 --- a/apps/opencs/view/widget/scenetooltoggle.hpp +++ b/apps/opencs/view/widget/scenetooltoggle.hpp @@ -4,9 +4,13 @@ #include "scenetool.hpp" #include +#include class QHBoxLayout; class QRect; +class QObject; +class QPoint; +class QWidget; namespace CSVWidget { @@ -16,59 +20,57 @@ namespace CSVWidget ///< \brief Multi-Toggle tool class SceneToolToggle : public SceneTool { - Q_OBJECT + Q_OBJECT - struct ButtonDesc - { - unsigned int mMask; - std::string mSmallIcon; - QString mName; - int mIndex; - }; + struct ButtonDesc + { + unsigned int mMask; + std::string mSmallIcon; + QString mName; + int mIndex; + }; - std::string mEmptyIcon; - QWidget *mPanel; - QHBoxLayout *mLayout; - std::map mButtons; // widget, id - int mButtonSize; - int mIconSize; - QString mToolTip; - PushButton *mFirst; + std::string mEmptyIcon; + QWidget* mPanel; + QHBoxLayout* mLayout; + std::map mButtons; // widget, id + int mButtonSize; + int mIconSize; + QString mToolTip; + PushButton* mFirst; - void adjustToolTip(); + void adjustToolTip(); - void adjustIcon(); + void adjustIcon(); - QRect getIconBox (int index) const; + QRect getIconBox(int index) const; - public: + public: + SceneToolToggle(SceneToolbar* parent, const QString& toolTip, const std::string& emptyIcon); - SceneToolToggle (SceneToolbar *parent, const QString& toolTip, - const std::string& emptyIcon); + void showPanel(const QPoint& position) override; - void showPanel (const QPoint& position) override; + /// \attention After the last button has been added, setSelection must be called at + /// least once to finalise the layout. + /// + /// \note The layout algorithm can not handle more than 9 buttons. To prevent this An + /// attempt to add more will result in an exception being thrown. + /// The small icons will be sized at (x-4)/3 (where x is the main icon size). + void addButton(const std::string& icon, unsigned int mask, const std::string& smallIcon, const QString& name, + const QString& tooltip = ""); - /// \attention After the last button has been added, setSelection must be called at - /// least once to finalise the layout. - /// - /// \note The layout algorithm can not handle more than 9 buttons. To prevent this An - /// attempt to add more will result in an exception being thrown. - /// The small icons will be sized at (x-4)/3 (where x is the main icon size). - void addButton (const std::string& icon, unsigned int mask, - const std::string& smallIcon, const QString& name, const QString& tooltip = ""); + unsigned int getSelectionMask() const; - unsigned int getSelectionMask() const; + /// \param or'ed button masks. buttons that do not exist will be ignored. + void setSelectionMask(unsigned int selection); - /// \param or'ed button masks. buttons that do not exist will be ignored. - void setSelectionMask (unsigned int selection); + signals: - signals: + void selectionChanged(); - void selectionChanged(); + private slots: - private slots: - - void selected(); + void selected(); }; } diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 363cae570..8dbd1e804 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -1,15 +1,17 @@ #include "scenetooltoggle2.hpp" -#include #include +#include +#include -#include #include +#include #include -#include -#include "scenetoolbar.hpp" +#include + #include "pushbutton.hpp" +#include "scenetoolbar.hpp" void CSVWidget::SceneToolToggle2::adjustToolTip() { @@ -19,8 +21,7 @@ void CSVWidget::SceneToolToggle2::adjustToolTip() bool first = true; - for (std::map::const_iterator iter (mButtons.begin()); - iter!=mButtons.end(); ++iter) + for (std::map::const_iterator iter(mButtons.begin()); iter != mButtons.end(); ++iter) if (iter->first->isChecked()) { if (!first) @@ -36,64 +37,67 @@ void CSVWidget::SceneToolToggle2::adjustToolTip() toolTip += "

        (left click to alter selection)"; - setToolTip (toolTip); + setToolTip(toolTip); } void CSVWidget::SceneToolToggle2::adjustIcon() { unsigned int buttonIds = 0; - for (std::map::const_iterator iter (mButtons.begin()); - iter!=mButtons.end(); ++iter) + for (std::map::const_iterator iter(mButtons.begin()); iter != mButtons.end(); ++iter) if (iter->first->isChecked()) buttonIds |= iter->second.mButtonId; std::ostringstream stream; stream << mCompositeIcon << buttonIds; - setIcon (QIcon (QString::fromUtf8 (stream.str().c_str()))); + setIcon(QIcon(QString::fromUtf8(stream.str().c_str()))); } -CSVWidget::SceneToolToggle2::SceneToolToggle2 (SceneToolbar *parent, const QString& toolTip, - const std::string& compositeIcon, const std::string& singleIcon) -: SceneTool (parent), mCompositeIcon (compositeIcon), mSingleIcon (singleIcon), - mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), mToolTip (toolTip), - mFirst (nullptr) +CSVWidget::SceneToolToggle2::SceneToolToggle2( + SceneToolbar* parent, const QString& toolTip, const std::string& compositeIcon, const std::string& singleIcon) + : SceneTool(parent) + , mCompositeIcon(compositeIcon) + , mSingleIcon(singleIcon) + , mButtonSize(parent->getButtonSize()) + , mIconSize(parent->getIconSize()) + , mToolTip(toolTip) + , mFirst(nullptr) { - mPanel = new QFrame (this, Qt::Popup); + mPanel = new QFrame(this, Qt::Popup); - mLayout = new QHBoxLayout (mPanel); + mLayout = new QHBoxLayout(mPanel); - mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); + mLayout->setContentsMargins(QMargins(0, 0, 0, 0)); - mPanel->setLayout (mLayout); + mPanel->setLayout(mLayout); } -void CSVWidget::SceneToolToggle2::showPanel (const QPoint& position) +void CSVWidget::SceneToolToggle2::showPanel(const QPoint& position) { - mPanel->move (position); + mPanel->move(position); mPanel->show(); if (mFirst) - mFirst->setFocus (Qt::OtherFocusReason); + mFirst->setFocus(Qt::OtherFocusReason); } -void CSVWidget::SceneToolToggle2::addButton (unsigned int id, unsigned int mask, - const QString& name, const QString& tooltip, bool disabled) +void CSVWidget::SceneToolToggle2::addButton( + unsigned int id, unsigned int mask, const QString& name, const QString& tooltip, bool disabled) { std::ostringstream stream; stream << mSingleIcon << id; - PushButton *button = new PushButton (QIcon (QPixmap (stream.str().c_str())), - PushButton::Type_Toggle, tooltip.isEmpty() ? name: tooltip, mPanel); + PushButton* button = new PushButton( + QIcon(QPixmap(stream.str().c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel); - button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); - button->setIconSize (QSize (mIconSize, mIconSize)); - button->setFixedSize (mButtonSize, mButtonSize); + button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setIconSize(QSize(mIconSize, mIconSize)); + button->setFixedSize(mButtonSize, mButtonSize); if (disabled) - button->setDisabled (true); + button->setDisabled(true); - mLayout->addWidget (button); + mLayout->addWidget(button); ButtonDesc desc; desc.mButtonId = id; @@ -101,11 +105,11 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, unsigned int mask, desc.mName = name; desc.mIndex = static_cast(mButtons.size()); - mButtons.insert (std::make_pair (button, desc)); + mButtons.insert(std::make_pair(button, desc)); - connect (button, SIGNAL (clicked()), this, SLOT (selected())); + connect(button, &QPushButton::clicked, this, &SceneToolToggle2::selected); - if (mButtons.size()==1 && !disabled) + if (mButtons.size() == 1 && !disabled) mFirst = button; } @@ -113,19 +117,17 @@ unsigned int CSVWidget::SceneToolToggle2::getSelectionMask() const { unsigned int selection = 0; - for (std::map::const_iterator iter (mButtons.begin()); - iter!=mButtons.end(); ++iter) + for (std::map::const_iterator iter(mButtons.begin()); iter != mButtons.end(); ++iter) if (iter->first->isChecked()) selection |= iter->second.mMask; return selection; } -void CSVWidget::SceneToolToggle2::setSelectionMask (unsigned int selection) +void CSVWidget::SceneToolToggle2::setSelectionMask(unsigned int selection) { - for (std::map::iterator iter (mButtons.begin()); - iter!=mButtons.end(); ++iter) - iter->first->setChecked (selection & iter->second.mMask); + for (std::map::iterator iter(mButtons.begin()); iter != mButtons.end(); ++iter) + iter->first->setChecked(selection & iter->second.mMask); adjustToolTip(); adjustIcon(); @@ -133,10 +135,9 @@ void CSVWidget::SceneToolToggle2::setSelectionMask (unsigned int selection) void CSVWidget::SceneToolToggle2::selected() { - std::map::const_iterator iter = - mButtons.find (dynamic_cast (sender())); + std::map::const_iterator iter = mButtons.find(dynamic_cast(sender())); - if (iter!=mButtons.end()) + if (iter != mButtons.end()) { if (!iter->first->hasKeepOpen()) mPanel->hide(); diff --git a/apps/opencs/view/widget/scenetooltoggle2.hpp b/apps/opencs/view/widget/scenetooltoggle2.hpp index e25019298..44fb4411f 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.hpp +++ b/apps/opencs/view/widget/scenetooltoggle2.hpp @@ -4,9 +4,12 @@ #include "scenetool.hpp" #include +#include class QHBoxLayout; -class QRect; +class QObject; +class QPoint; +class QWidget; namespace CSVWidget { @@ -18,61 +21,60 @@ namespace CSVWidget /// Top level button is using predefined icons instead building a composite icon. class SceneToolToggle2 : public SceneTool { - Q_OBJECT + Q_OBJECT - struct ButtonDesc - { - unsigned int mButtonId; - unsigned int mMask; - QString mName; - int mIndex; - }; + struct ButtonDesc + { + unsigned int mButtonId; + unsigned int mMask; + QString mName; + int mIndex; + }; - std::string mCompositeIcon; - std::string mSingleIcon; - QWidget *mPanel; - QHBoxLayout *mLayout; - std::map mButtons; // widget, id - int mButtonSize; - int mIconSize; - QString mToolTip; - PushButton *mFirst; + std::string mCompositeIcon; + std::string mSingleIcon; + QWidget* mPanel; + QHBoxLayout* mLayout; + std::map mButtons; // widget, id + int mButtonSize; + int mIconSize; + QString mToolTip; + PushButton* mFirst; - void adjustToolTip(); + void adjustToolTip(); - void adjustIcon(); + void adjustIcon(); - public: + public: + /// The top level icon is compositeIcon + sum of bitpatterns for active buttons (in + /// decimal) + /// + /// The icon for individual toggle buttons is signleIcon + bitmask for button (in + /// decimal) + SceneToolToggle2(SceneToolbar* parent, const QString& toolTip, const std::string& compositeIcon, + const std::string& singleIcon); - /// The top level icon is compositeIcon + sum of bitpatterns for active buttons (in - /// decimal) - /// - /// The icon for individual toggle buttons is signleIcon + bitmask for button (in - /// decimal) - SceneToolToggle2 (SceneToolbar *parent, const QString& toolTip, - const std::string& compositeIcon, const std::string& singleIcon); + void showPanel(const QPoint& position) override; - void showPanel (const QPoint& position) override; + /// \param buttonId used to compose the icon filename + /// \param mask used for the reported getSelectionMask() / setSelectionMask() + /// \attention After the last button has been added, setSelection must be called at + /// least once to finalise the layout. + void addButton(unsigned int buttonId, unsigned int mask, const QString& name, const QString& tooltip = "", + bool disabled = false); - /// \param buttonId used to compose the icon filename - /// \param mask used for the reported getSelectionMask() / setSelectionMask() - /// \attention After the last button has been added, setSelection must be called at - /// least once to finalise the layout. - void addButton (unsigned int buttonId, unsigned int mask, - const QString& name, const QString& tooltip = "", bool disabled = false); + unsigned int getSelectionMask() const; - unsigned int getSelectionMask() const; + /// \param or'ed button masks. buttons that do not exist will be ignored. + void setSelectionMask(unsigned int selection); - /// \param or'ed button masks. buttons that do not exist will be ignored. - void setSelectionMask (unsigned int selection); + signals: - signals: + void selectionChanged(); - void selectionChanged(); + private slots: - private slots: - - void selected(); + void selected(); }; } diff --git a/apps/opencs/view/world/bodypartcreator.cpp b/apps/opencs/view/world/bodypartcreator.cpp index a9fc3e063..9c26b8446 100644 --- a/apps/opencs/view/world/bodypartcreator.cpp +++ b/apps/opencs/view/world/bodypartcreator.cpp @@ -5,6 +5,8 @@ #include "../../model/world/data.hpp" #include "../../model/world/universalid.hpp" +#include + std::string CSVWorld::BodyPartCreator::getId() const { std::string id = CSVWorld::GenericCreator::getId(); @@ -17,16 +19,13 @@ std::string CSVWorld::BodyPartCreator::getId() const return id; } -CSVWorld::BodyPartCreator::BodyPartCreator( - CSMWorld::Data& data, - QUndoStack& undoStack, - const CSMWorld::UniversalId& id -) : GenericCreator(data, undoStack, id) +CSVWorld::BodyPartCreator::BodyPartCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) + : GenericCreator(data, undoStack, id) { mFirstPerson = new QCheckBox("First Person", this); insertBeforeButtons(mFirstPerson, false); - connect(mFirstPerson, SIGNAL(clicked(bool)), this, SLOT(checkboxClicked())); + connect(mFirstPerson, &QCheckBox::clicked, this, &BodyPartCreator::checkboxClicked); } std::string CSVWorld::BodyPartCreator::getErrors() const diff --git a/apps/opencs/view/world/bodypartcreator.hpp b/apps/opencs/view/world/bodypartcreator.hpp index f526b7fae..adb795eac 100644 --- a/apps/opencs/view/world/bodypartcreator.hpp +++ b/apps/opencs/view/world/bodypartcreator.hpp @@ -18,29 +18,24 @@ namespace CSVWorld { Q_OBJECT - QCheckBox *mFirstPerson; + QCheckBox* mFirstPerson; - private: + private: + /// \return ID entered by user. + std::string getId() const override; - /// \return ID entered by user. - std::string getId() const override; + public: + BodyPartCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); - public: + /// \return Error description for current user input. + std::string getErrors() const override; - BodyPartCreator( - CSMWorld::Data& data, - QUndoStack& undoStack, - const CSMWorld::UniversalId& id); + /// \brief Clear ID and checkbox input widgets. + void reset() override; - /// \return Error description for current user input. - std::string getErrors() const override; + private slots: - /// \brief Clear ID and checkbox input widgets. - void reset() override; - - private slots: - - void checkboxClicked(); + void checkboxClicked(); }; } diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index 5b428a4b3..7545a5c08 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -4,15 +4,21 @@ #include #include -#include #include +#include + +#include +#include +#include #include "../../model/world/commands.hpp" #include "../../model/world/idtree.hpp" +class QUndoStack; + std::string CSVWorld::CellCreator::getId() const { - if (mType->currentIndex()==0) + if (mType->currentIndex() == 0) return GenericCreator::getId(); std::ostringstream stream; @@ -31,76 +37,83 @@ void CSVWorld::CellCreator::configureCreateCommand(CSMWorld::CreateCommand& comm command.addNestedValue(parentIndex, index, mType->currentIndex() == 0); } -CSVWorld::CellCreator::CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) -: GenericCreator (data, undoStack, id) +CSVWorld::CellCreator::CellCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) + : GenericCreator(data, undoStack, id) { - mY = new QSpinBox (this); - mY->setVisible (false); - mY->setMinimum (std::numeric_limits::min()); - mY->setMaximum (std::numeric_limits::max()); - connect (mY, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); - insertAtBeginning (mY, true); + mY = new QSpinBox(this); + mY->setVisible(false); + mY->setMinimum(std::numeric_limits::min()); + mY->setMaximum(std::numeric_limits::max()); + connect(mY, qOverload(&QSpinBox::valueChanged), this, &CellCreator::valueChanged); + insertAtBeginning(mY, true); - mYLabel = new QLabel ("Y", this); - mYLabel->setVisible (false); - insertAtBeginning (mYLabel, false); + mYLabel = new QLabel("Y", this); + mYLabel->setVisible(false); + insertAtBeginning(mYLabel, false); - mX = new QSpinBox (this); - mX->setVisible (false); - mX->setMinimum (std::numeric_limits::min()); - mX->setMaximum (std::numeric_limits::max()); - connect (mX, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); - insertAtBeginning (mX, true); + mX = new QSpinBox(this); + mX->setVisible(false); + mX->setMinimum(std::numeric_limits::min()); + mX->setMaximum(std::numeric_limits::max()); + connect(mX, qOverload(&QSpinBox::valueChanged), this, &CellCreator::valueChanged); + insertAtBeginning(mX, true); - mXLabel = new QLabel ("X", this); - mXLabel->setVisible (false); - insertAtBeginning (mXLabel, false); + mXLabel = new QLabel("X", this); + mXLabel->setVisible(false); + insertAtBeginning(mXLabel, false); - mType = new QComboBox (this); + mType = new QComboBox(this); - mType->addItem ("Interior Cell"); - mType->addItem ("Exterior Cell"); + mType->addItem("Interior Cell"); + mType->addItem("Exterior Cell"); - connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); + connect(mType, qOverload(&QComboBox::currentIndexChanged), this, &CellCreator::setType); - insertAtBeginning (mType, false); + insertAtBeginning(mType, false); } void CSVWorld::CellCreator::reset() { - mX->setValue (0); - mY->setValue (0); - mType->setCurrentIndex (0); + mX->setValue(0); + mY->setValue(0); + mType->setCurrentIndex(0); setType(0); GenericCreator::reset(); } -void CSVWorld::CellCreator::setType (int index) +void CSVWorld::CellCreator::setType(int index) { - setManualEditing (index==0); - mXLabel->setVisible (index==1); - mX->setVisible (index==1); - mYLabel->setVisible (index==1); - mY->setVisible (index==1); + setManualEditing(index == 0); + mXLabel->setVisible(index == 1); + mX->setVisible(index == 1); + mYLabel->setVisible(index == 1); + mY->setVisible(index == 1); + + // The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell) + std::string text = mType->currentText().toStdString(); + if (text == "Interior Cell") + GenericCreator::setEditorMaxLength(64); + else + GenericCreator::setEditorMaxLength(32767); update(); } -void CSVWorld::CellCreator::valueChanged (int index) +void CSVWorld::CellCreator::valueChanged(int index) { update(); } -void CSVWorld::CellCreator::cloneMode(const std::string& originId, - const CSMWorld::UniversalId::Type type) +void CSVWorld::CellCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { CSVWorld::GenericCreator::cloneMode(originId, type); - if (*(originId.begin()) == '#') //if originid points to the exterior cell + if (*(originId.begin()) == '#') // if originid points to the exterior cell { - setType(1); //enable x and y controls + setType(1); // enable x and y controls mType->setCurrentIndex(1); - } else { + } + else + { setType(0); mType->setCurrentIndex(0); } diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp index 032096aa2..0e0b4324a 100644 --- a/apps/opencs/view/world/cellcreator.hpp +++ b/apps/opencs/view/world/cellcreator.hpp @@ -1,49 +1,58 @@ #ifndef CSV_WORLD_CELLCREATOR_H #define CSV_WORLD_CELLCREATOR_H -class QLabel; -class QSpinBox; -class QComboBox; +#include + +#include #include "genericcreator.hpp" +class QComboBox; +class QLabel; +class QObject; +class QSpinBox; +class QUndoStack; + +namespace CSMWorld +{ + class CreateCommand; + class Data; +} + namespace CSVWorld { class CellCreator : public GenericCreator { - Q_OBJECT + Q_OBJECT - QComboBox *mType; - QLabel *mXLabel; - QSpinBox *mX; - QLabel *mYLabel; - QSpinBox *mY; + QComboBox* mType; + QLabel* mXLabel; + QSpinBox* mX; + QLabel* mYLabel; + QSpinBox* mY; - protected: + protected: + std::string getId() const override; - std::string getId() const override; + /// Allow subclasses to add additional data to \a command. + void configureCreateCommand(CSMWorld::CreateCommand& command) const override; - /// Allow subclasses to add additional data to \a command. - void configureCreateCommand(CSMWorld::CreateCommand& command) const override; + public: + CellCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); - public: + void reset() override; - CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - void reset() override; + std::string getErrors() const override; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. - void cloneMode(const std::string& originId, - const CSMWorld::UniversalId::Type type) override; + private slots: - std::string getErrors() const override; - ///< Return formatted error descriptions for the current state of the creator. if an empty - /// string is returned, there is no error. + void setType(int index); - private slots: - - void setType (int index); - - void valueChanged (int index); + void valueChanged(int index); }; } diff --git a/apps/opencs/view/world/colordelegate.cpp b/apps/opencs/view/world/colordelegate.cpp index 3b5f692fe..18dd10864 100644 --- a/apps/opencs/view/world/colordelegate.cpp +++ b/apps/opencs/view/world/colordelegate.cpp @@ -1,25 +1,36 @@ #include "colordelegate.hpp" +#include +#include #include -#include +#include -CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, - QObject *parent) +#include + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + +CSVWorld::ColorDelegate::ColorDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) : CommandDelegate(dispatcher, document, parent) -{} +{ +} -void CSVWorld::ColorDelegate::paint(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const +void CSVWorld::ColorDelegate::paint( + QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { int colorInt = index.data().toInt(); QColor color(colorInt & 0xff, (colorInt >> 8) & 0xff, (colorInt >> 16) & 0xff); QRect coloredRect(option.rect.x() + qRound(option.rect.width() / 4.0), - option.rect.y() + qRound(option.rect.height() / 4.0), - option.rect.width() / 2, - option.rect.height() / 2); + option.rect.y() + qRound(option.rect.height() / 4.0), option.rect.width() / 2, option.rect.height() / 2); painter->save(); painter->fillRect(coloredRect, color); painter->setPen(Qt::black); @@ -27,11 +38,8 @@ void CSVWorld::ColorDelegate::paint(QPainter *painter, painter->restore(); } -CSVWorld::CommandDelegate *CSVWorld::ColorDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document &document, - QObject *parent) const +CSVWorld::CommandDelegate* CSVWorld::ColorDelegateFactory::makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const { return new ColorDelegate(dispatcher, document, parent); } - - diff --git a/apps/opencs/view/world/colordelegate.hpp b/apps/opencs/view/world/colordelegate.hpp index 041051d13..669fa5a4c 100644 --- a/apps/opencs/view/world/colordelegate.hpp +++ b/apps/opencs/view/world/colordelegate.hpp @@ -3,34 +3,36 @@ #include "util.hpp" -class QRect; +class QModelIndex; +class QObject; +class QPainter; -namespace CSVWidget +namespace CSMDoc { - class ColorEditButton; + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; } namespace CSVWorld { class ColorDelegate : public CommandDelegate { - public: - ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, - QObject *parent); + public: + ColorDelegate(CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent); - void paint(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; class ColorDelegateFactory : public CommandDelegateFactory { - public: - CommandDelegate *makeDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document &document, - QObject *parent) const override; - ///< The ownership of the returned CommandDelegate is transferred to the caller. + public: + CommandDelegate* makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const override; + ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp index 53664c186..45b2faf3e 100644 --- a/apps/opencs/view/world/creator.cpp +++ b/apps/opencs/view/world/creator.cpp @@ -1,21 +1,23 @@ #include "creator.hpp" +#include +#include + #include -CSVWorld::Creator::~Creator() {} - -void CSVWorld::Creator::setScope (unsigned int scope) +namespace CSMDoc { - if (scope!=CSMWorld::Scope_Content) - throw std::logic_error ("Invalid scope in creator"); + class Document; } +void CSVWorld::Creator::setScope(unsigned int scope) +{ + if (scope != CSMWorld::Scope_Content) + throw std::logic_error("Invalid scope in creator"); +} -CSVWorld::CreatorFactoryBase::~CreatorFactoryBase() {} - - -CSVWorld::Creator *CSVWorld::NullCreatorFactory::makeCreator (CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const +CSVWorld::Creator* CSVWorld::NullCreatorFactory::makeCreator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return nullptr; } diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp index 516f71f15..ed2c754b1 100644 --- a/apps/opencs/view/world/creator.hpp +++ b/apps/opencs/view/world/creator.hpp @@ -2,6 +2,7 @@ #define CSV_WORLD_CREATOR_H #include +#include #include @@ -12,93 +13,83 @@ #include "../../model/world/universalid.hpp" #endif -namespace CSMDoc -{ - class Document; -} - namespace CSVWorld { /// \brief Record creator UI base class class Creator : public QWidget { - Q_OBJECT + Q_OBJECT - public: + public: + ~Creator() override = default; - virtual ~Creator(); + virtual void reset() = 0; - virtual void reset() = 0; + virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) = 0; - virtual void cloneMode(const std::string& originId, - const CSMWorld::UniversalId::Type type) = 0; + /// Touches a record, if the creator supports it. + virtual void touch(const std::vector& ids) = 0; - /// Touches a record, if the creator supports it. - virtual void touch(const std::vector& ids) = 0; + virtual void setEditLock(bool locked) = 0; - virtual void setEditLock (bool locked) = 0; + virtual void toggleWidgets(bool active = true) = 0; - virtual void toggleWidgets(bool active = true) = 0; + /// Default implementation: Throw an exception if scope!=Scope_Content. + virtual void setScope(unsigned int scope); - /// Default implementation: Throw an exception if scope!=Scope_Content. - virtual void setScope (unsigned int scope); + /// Focus main input widget + virtual void focus() = 0; - /// Focus main input widget - virtual void focus() = 0; + signals: - signals: + void done(); - void done(); - - void requestFocus (const std::string& id); - ///< Request owner of this creator to focus the just created \a id. The owner may - /// ignore this request. + void requestFocus(const std::string& id); + ///< Request owner of this creator to focus the just created \a id. The owner may + /// ignore this request. }; /// \brief Base class for Creator factory class CreatorFactoryBase { - public: + public: + virtual ~CreatorFactoryBase() = default; - virtual ~CreatorFactoryBase(); - - virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const = 0; - ///< The ownership of the returned Creator is transferred to the caller. - /// - /// \note The function can return a 0-pointer, which means no UI for creating/deleting - /// records should be provided. + virtual Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const = 0; + ///< The ownership of the returned Creator is transferred to the caller. + /// + /// \note The function can return a 0-pointer, which means no UI for creating/deleting + /// records should be provided. }; /// \brief Creator factory that does not produces any creator class NullCreatorFactory : public CreatorFactoryBase { - public: - - Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; - ///< The ownership of the returned Creator is transferred to the caller. - /// - /// \note The function always returns 0. + public: + Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; + ///< The ownership of the returned Creator is transferred to the caller. + /// + /// \note The function always returns 0. }; - template + template class CreatorFactory : public CreatorFactoryBase { - public: - - Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; - ///< The ownership of the returned Creator is transferred to the caller. - /// - /// \note The function can return a 0-pointer, which means no UI for creating/deleting - /// records should be provided. + public: + Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; + ///< The ownership of the returned Creator is transferred to the caller. + /// + /// \note The function can return a 0-pointer, which means no UI for creating/deleting + /// records should be provided. }; - template - Creator *CreatorFactory::makeCreator (CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const + template + Creator* CreatorFactory::makeCreator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - std::unique_ptr creator (new CreatorT (document.getData(), document.getUndoStack(), id)); + auto creator = std::make_unique(document.getData(), document.getUndoStack(), id); - creator->setScope (scope); + creator->setScope(scope); return creator.release(); } diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index 6da62d408..fc6a2b7d8 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -3,27 +3,51 @@ #include "../../model/prefs/state.hpp" #include +#include #include +#include +#include +#include +#include -CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, - const IconList &icons, - CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, - const std::string &pageName, - const std::string &settingName, - QObject *parent) - : EnumDelegate (values, dispatcher, document, parent), mDisplayMode (Mode_TextOnly), - mIcons (icons), mIconSize (QSize(16, 16)), - mHorizontalMargin(QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1), - mTextLeftOffset(8), mSettingKey (pageName + '/' + settingName) +#include + +#include +#include +#include +#include + +class QModelIndex; +class QObject; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + +CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList& values, const IconList& icons, + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, const std::string& pageName, + const std::string& settingName, QObject* parent) + : EnumDelegate(values, dispatcher, document, parent) + , mDisplayMode(Mode_TextOnly) + , mIcons(icons) + , mIconSize(QSize(16, 16)) + , mHorizontalMargin(QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1) + , mTextLeftOffset(8) + , mSettingKey(pageName + '/' + settingName) { buildPixmaps(); if (!pageName.empty()) - updateDisplayMode (CSMPrefs::get()[pageName][settingName].toString()); + updateDisplayMode(CSMPrefs::get()[pageName][settingName].toString()); } -void CSVWorld::DataDisplayDelegate::buildPixmaps () +void CSVWorld::DataDisplayDelegate::buildPixmaps() { if (!mPixmaps.empty()) mPixmaps.clear(); @@ -32,7 +56,7 @@ void CSVWorld::DataDisplayDelegate::buildPixmaps () while (it != mIcons.end()) { - mPixmaps.emplace_back (it->mValue, it->mIcon.pixmap (mIconSize) ); + mPixmaps.emplace_back(it->mValue, it->mIcon.pixmap(mIconSize)); ++it; } } @@ -48,7 +72,7 @@ void CSVWorld::DataDisplayDelegate::setTextLeftOffset(int offset) mTextLeftOffset = offset; } -QSize CSVWorld::DataDisplayDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +QSize CSVWorld::DataDisplayDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QSize size = EnumDelegate::sizeHint(option, index); @@ -72,11 +96,12 @@ QSize CSVWorld::DataDisplayDelegate::sizeHint(const QStyleOptionViewItem &option return size; } -void CSVWorld::DataDisplayDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +void CSVWorld::DataDisplayDelegate::paint( + QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { painter->save(); - //default to enum delegate's paint method for text-only conditions + // default to enum delegate's paint method for text-only conditions if (mDisplayMode == Mode_TextOnly) EnumDelegate::paint(painter, option, index); else @@ -91,7 +116,7 @@ void CSVWorld::DataDisplayDelegate::paint (QPainter *painter, const QStyleOption painter->restore(); } -void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int index) const +void CSVWorld::DataDisplayDelegate::paintIcon(QPainter* painter, const QStyleOptionViewItem& option, int index) const { QRect iconRect = option.rect; QRect textRect = iconRect; @@ -104,20 +129,14 @@ void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOp textRect.setLeft(iconRect.right() + mTextLeftOffset); textRect.setRight(option.rect.right() - mHorizontalMargin); - QString text = option.fontMetrics.elidedText(mValues.at(index).second, - option.textElideMode, - textRect.width()); - QApplication::style()->drawItemText(painter, - textRect, - Qt::AlignLeft | Qt::AlignVCenter, - option.palette, - true, - text); + QString text = option.fontMetrics.elidedText(mValues.at(index).second, option.textElideMode, textRect.width()); + QApplication::style()->drawItemText( + painter, textRect, Qt::AlignLeft | Qt::AlignVCenter, option.palette, true, text); } QApplication::style()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, mPixmaps.at(index).second); } -void CSVWorld::DataDisplayDelegate::updateDisplayMode (const std::string &mode) +void CSVWorld::DataDisplayDelegate::updateDisplayMode(const std::string& mode) { if (mode == "Icon and Text") mDisplayMode = Mode_IconAndText; @@ -129,18 +148,13 @@ void CSVWorld::DataDisplayDelegate::updateDisplayMode (const std::string &mode) mDisplayMode = Mode_TextOnly; } -CSVWorld::DataDisplayDelegate::~DataDisplayDelegate() +void CSVWorld::DataDisplayDelegate::settingChanged(const CSMPrefs::Setting* setting) { + if (*setting == mSettingKey) + updateDisplayMode(setting->toString()); } -void CSVWorld::DataDisplayDelegate::settingChanged (const CSMPrefs::Setting *setting) -{ - if (*setting==mSettingKey) - updateDisplayMode (setting->toString()); -} - - -void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, const QString& enumName, const QString& iconFilename) +void CSVWorld::DataDisplayDelegateFactory::add(int enumValue, const QString& enumName, const QString& iconFilename) { EnumDelegateFactory::add(enumValue, enumName); @@ -149,7 +163,7 @@ void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, const QString& en icon.mName = enumName; icon.mIcon = QIcon(iconFilename); - for (auto it=mIcons.begin(); it!=mIcons.end(); ++it) + for (auto it = mIcons.begin(); it != mIcons.end(); ++it) { if (it->mName > enumName) { @@ -161,8 +175,8 @@ void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, const QString& en mIcons.push_back(icon); } -CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate ( - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const +CSVWorld::CommandDelegate* CSVWorld::DataDisplayDelegateFactory::makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const { - return new DataDisplayDelegate (mValues, mIcons, dispatcher, document, "", "", parent); + return new DataDisplayDelegate(mValues, mIcons, dispatcher, document, "", "", parent); } diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index df06359a0..087fc2a08 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -1,9 +1,28 @@ #ifndef DATADISPLAYDELEGATE_HPP #define DATADISPLAYDELEGATE_HPP -#include #include "enumdelegate.hpp" +#include +#include +#include +#include + +class QModelIndex; +class QObject; +class QPainter; +class QPixmap; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + namespace CSMPrefs { class Setting; @@ -11,6 +30,8 @@ namespace CSMPrefs namespace CSVWorld { + class CommandDelegate; + struct Icon { int mValue; @@ -21,12 +42,10 @@ namespace CSVWorld class DataDisplayDelegate : public EnumDelegate { public: - typedef std::vector IconList; - typedef std::vector > ValueList; + typedef std::vector> ValueList; protected: - enum DisplayMode { Mode_TextOnly, @@ -38,8 +57,7 @@ namespace CSVWorld IconList mIcons; private: - - std::vector > mPixmaps; + std::vector> mPixmaps; QSize mIconSize; int mHorizontalMargin; int mTextLeftOffset; @@ -47,51 +65,46 @@ namespace CSVWorld std::string mSettingKey; public: - DataDisplayDelegate (const ValueList & values, const IconList & icons, - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, - const std::string& pageName, const std::string& settingName, QObject *parent); + DataDisplayDelegate(const ValueList& values, const IconList& icons, CSMWorld::CommandDispatcher* dispatcher, + CSMDoc::Document& document, const std::string& pageName, const std::string& settingName, QObject* parent); - ~DataDisplayDelegate(); + ~DataDisplayDelegate() = default; - void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; /// pass a QSize defining height / width of icon. Default is QSize (16,16). - void setIconSize (const QSize& icon); + void setIconSize(const QSize& icon); /// offset the horizontal position of the text from the right edge of the icon. Default is 8 pixels. - void setTextLeftOffset (int offset); + void setTextLeftOffset(int offset); private: - /// update the display mode based on a passed string - void updateDisplayMode (const std::string &); + void updateDisplayMode(const std::string&); /// custom paint function for painting the icon. Mode_IconAndText and Mode_Icon only. - void paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int i) const; + void paintIcon(QPainter* painter, const QStyleOptionViewItem& option, int i) const; /// rebuild the list of pixmaps from the provided icons (called when icon size is changed) void buildPixmaps(); - void settingChanged (const CSMPrefs::Setting *setting) override; + void settingChanged(const CSMPrefs::Setting* setting) override; }; class DataDisplayDelegateFactory : public EnumDelegateFactory { protected: - DataDisplayDelegate::IconList mIcons; public: - - CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; + CommandDelegate* makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const override; ///< The ownership of the returned CommandDelegate is transferred to the caller. protected: - - void add (int enumValue, const QString& enumName, const QString& iconFilename); - + void add(int enumValue, const QString& enumName, const QString& iconFilename); }; } diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp index 7c6fb2e81..db78c3524 100644 --- a/apps/opencs/view/world/dialoguecreator.cpp +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -1,36 +1,40 @@ #include "dialoguecreator.hpp" -#include +#include +#include +#include +#include -#include "../../model/doc/document.hpp" +#include -#include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" -#include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" -void CSVWorld::DialogueCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const -{ - int index = - dynamic_cast (*getData().getTableModel (getCollectionId())). - findColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); +class QUndoStack; - command.addValue (index, mType); +void CSVWorld::DialogueCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const +{ + int index = dynamic_cast(*getData().getTableModel(getCollectionId())) + .findColumnIndex(CSMWorld::Columns::ColumnId_DialogueType); + + command.addValue(index, mType); } -CSVWorld::DialogueCreator::DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, int type) -: GenericCreator (data, undoStack, id, true), mType (type) -{} - -CSVWorld::Creator *CSVWorld::TopicCreatorFactory::makeCreator (CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const +CSVWorld::DialogueCreator::DialogueCreator( + CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, int type) + : GenericCreator(data, undoStack, id, true) + , mType(type) { - return new DialogueCreator (document.getData(), document.getUndoStack(), id, ESM::Dialogue::Topic); } -CSVWorld::Creator *CSVWorld::JournalCreatorFactory::makeCreator (CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const +CSVWorld::Creator* CSVWorld::TopicCreatorFactory::makeCreator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - return new DialogueCreator (document.getData(), document.getUndoStack(), id, ESM::Dialogue::Journal); + return new DialogueCreator(document.getData(), document.getUndoStack(), id, ESM::Dialogue::Topic); +} + +CSVWorld::Creator* CSVWorld::JournalCreatorFactory::makeCreator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id) const +{ + return new DialogueCreator(document.getData(), document.getUndoStack(), id, ESM::Dialogue::Journal); } diff --git a/apps/opencs/view/world/dialoguecreator.hpp b/apps/opencs/view/world/dialoguecreator.hpp index 0aef2f84d..6a89387b8 100644 --- a/apps/opencs/view/world/dialoguecreator.hpp +++ b/apps/opencs/view/world/dialoguecreator.hpp @@ -3,36 +3,47 @@ #include "genericcreator.hpp" +#include +#include + +class QUndoStack; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CreateCommand; + class Data; +} + namespace CSVWorld { class DialogueCreator : public GenericCreator { - int mType; + int mType; - protected: + protected: + void configureCreateCommand(CSMWorld::CreateCommand& command) const override; - void configureCreateCommand (CSMWorld::CreateCommand& command) const override; - - public: - - DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, int type); + public: + DialogueCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, int type); }; class TopicCreatorFactory : public CreatorFactoryBase { - public: - - Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; - ///< The ownership of the returned Creator is transferred to the caller. + public: + Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; + ///< The ownership of the returned Creator is transferred to the caller. }; class JournalCreatorFactory : public CreatorFactoryBase { - public: - - Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; - ///< The ownership of the returned Creator is transferred to the caller. + public: + Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; + ///< The ownership of the returned Creator is transferred to the caller. }; } diff --git a/apps/opencs/view/world/dialoguespinbox.cpp b/apps/opencs/view/world/dialoguespinbox.cpp index 1228ca0da..25b15e51b 100644 --- a/apps/opencs/view/world/dialoguespinbox.cpp +++ b/apps/opencs/view/world/dialoguespinbox.cpp @@ -2,24 +2,25 @@ #include -CSVWorld::DialogueSpinBox::DialogueSpinBox(QWidget *parent) : QSpinBox(parent) +CSVWorld::DialogueSpinBox::DialogueSpinBox(QWidget* parent) + : QSpinBox(parent) { setFocusPolicy(Qt::StrongFocus); } -void CSVWorld::DialogueSpinBox::focusInEvent(QFocusEvent *event) +void CSVWorld::DialogueSpinBox::focusInEvent(QFocusEvent* event) { setFocusPolicy(Qt::WheelFocus); QSpinBox::focusInEvent(event); } -void CSVWorld::DialogueSpinBox::focusOutEvent(QFocusEvent *event) +void CSVWorld::DialogueSpinBox::focusOutEvent(QFocusEvent* event) { setFocusPolicy(Qt::StrongFocus); QSpinBox::focusOutEvent(event); } -void CSVWorld::DialogueSpinBox::wheelEvent(QWheelEvent *event) +void CSVWorld::DialogueSpinBox::wheelEvent(QWheelEvent* event) { if (!hasFocus()) event->ignore(); @@ -27,24 +28,25 @@ void CSVWorld::DialogueSpinBox::wheelEvent(QWheelEvent *event) QSpinBox::wheelEvent(event); } -CSVWorld::DialogueDoubleSpinBox::DialogueDoubleSpinBox(QWidget *parent) : QDoubleSpinBox(parent) +CSVWorld::DialogueDoubleSpinBox::DialogueDoubleSpinBox(QWidget* parent) + : QDoubleSpinBox(parent) { setFocusPolicy(Qt::StrongFocus); } -void CSVWorld::DialogueDoubleSpinBox::focusInEvent(QFocusEvent *event) +void CSVWorld::DialogueDoubleSpinBox::focusInEvent(QFocusEvent* event) { setFocusPolicy(Qt::WheelFocus); QDoubleSpinBox::focusInEvent(event); } -void CSVWorld::DialogueDoubleSpinBox::focusOutEvent(QFocusEvent *event) +void CSVWorld::DialogueDoubleSpinBox::focusOutEvent(QFocusEvent* event) { setFocusPolicy(Qt::StrongFocus); QDoubleSpinBox::focusOutEvent(event); } -void CSVWorld::DialogueDoubleSpinBox::wheelEvent(QWheelEvent *event) +void CSVWorld::DialogueDoubleSpinBox::wheelEvent(QWheelEvent* event) { if (!hasFocus()) event->ignore(); diff --git a/apps/opencs/view/world/dialoguespinbox.hpp b/apps/opencs/view/world/dialoguespinbox.hpp index 90fe8d20c..beffb5656 100644 --- a/apps/opencs/view/world/dialoguespinbox.hpp +++ b/apps/opencs/view/world/dialoguespinbox.hpp @@ -1,8 +1,8 @@ #ifndef CSV_WORLD_DIALOGUESPINBOX_H #define CSV_WORLD_DIALOGUESPINBOX_H -#include #include +#include namespace CSVWorld { @@ -10,30 +10,26 @@ namespace CSVWorld { Q_OBJECT - public: + public: + DialogueSpinBox(QWidget* parent = nullptr); - DialogueSpinBox (QWidget *parent = nullptr); - - protected: - - void focusInEvent(QFocusEvent *event) override; - void focusOutEvent(QFocusEvent *event) override; - void wheelEvent(QWheelEvent *event) override; + protected: + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + void wheelEvent(QWheelEvent* event) override; }; class DialogueDoubleSpinBox : public QDoubleSpinBox { Q_OBJECT - public: + public: + DialogueDoubleSpinBox(QWidget* parent = nullptr); - DialogueDoubleSpinBox (QWidget *parent = nullptr); - - protected: - - void focusInEvent(QFocusEvent *event) override; - void focusOutEvent(QFocusEvent *event) override; - void wheelEvent(QWheelEvent *event) override; + protected: + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + void wheelEvent(QWheelEvent* event) override; }; } diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index f2360b137..24fe70af9 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -1,58 +1,74 @@ #include "dialoguesubview.hpp" -#include +#include #include #include +#include +#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include -#include -#include #include +#include +#include +#include #include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "../../model/world/nestedtableproxymodel.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" #include "../../model/world/columnbase.hpp" +#include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" -#include "../../model/world/columns.hpp" +#include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/record.hpp" #include "../../model/world/tablemimedata.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../widget/coloreditor.hpp" #include "../widget/droplineedit.hpp" -#include "recordstatusdelegate.hpp" -#include "util.hpp" -#include "tablebottombox.hpp" #include "nestedtable.hpp" #include "recordbuttonbar.hpp" +#include "tablebottombox.hpp" +#include "util.hpp" + +class QPainter; +class QPoint; + /* ==============================NotEditableSubDelegate========================================== */ -CSVWorld::NotEditableSubDelegate::NotEditableSubDelegate(const CSMWorld::IdTable* table, QObject * parent) : -QAbstractItemDelegate(parent), -mTable(table) -{} +CSVWorld::NotEditableSubDelegate::NotEditableSubDelegate(const CSMWorld::IdTable* table, QObject* parent) + : QAbstractItemDelegate(parent) + , mTable(table) +{ +} -void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QModelIndex& index) const +void CSVWorld::NotEditableSubDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { QLabel* label = qobject_cast(editor); - if(!label) + if (!label) return; QVariant v = index.data(Qt::EditRole); @@ -65,61 +81,64 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo } } - CSMWorld::Columns::ColumnId columnId = static_cast ( - mTable->getColumnId (index.column())); + CSMWorld::Columns::ColumnId columnId + = static_cast(mTable->getColumnId(index.column())); if (QVariant::String == v.type()) { label->setText(v.toString()); } - else if (CSMWorld::Columns::hasEnums (columnId)) + else if (CSMWorld::Columns::hasEnums(columnId)) { int data = v.toInt(); - std::vector> enumNames (CSMWorld::Columns::getEnums (columnId)); + std::vector> enumNames(CSMWorld::Columns::getEnums(columnId)); label->setText(QString::fromUtf8(enumNames.at(data).second.c_str())); } else { - label->setText (v.toString()); + label->setText(v.toString()); } } -void CSVWorld::NotEditableSubDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +void CSVWorld::NotEditableSubDelegate::setModelData( + QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { - //not editable widgets will not save model data + // not editable widgets will not save model data } -void CSVWorld::NotEditableSubDelegate::paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +void CSVWorld::NotEditableSubDelegate::paint( + QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - //does nothing + // does nothing } -QSize CSVWorld::NotEditableSubDelegate::sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const +QSize CSVWorld::NotEditableSubDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { return QSize(); } -QWidget* CSVWorld::NotEditableSubDelegate::createEditor (QWidget *parent, - const QStyleOptionViewItem& option, - const QModelIndex& index) const +QWidget* CSVWorld::NotEditableSubDelegate::createEditor( + QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { - QLabel *label = new QLabel(parent); - label->setTextInteractionFlags (Qt::TextSelectableByMouse); + QLabel* label = new QLabel(parent); + label->setTextInteractionFlags(Qt::TextSelectableByMouse); return label; } /* ==============================DialogueDelegateDispatcherProxy========================================== */ -CSVWorld::DialogueDelegateDispatcherProxy::refWrapper::refWrapper(const QModelIndex& index) : -mIndex(index) -{} +CSVWorld::DialogueDelegateDispatcherProxy::refWrapper::refWrapper(const QModelIndex& index) + : mIndex(index) +{ +} -CSVWorld::DialogueDelegateDispatcherProxy::DialogueDelegateDispatcherProxy(QWidget* editor, CSMWorld::ColumnBase::Display display) : -mEditor(editor), -mDisplay(display), -mIndexWrapper(nullptr) +CSVWorld::DialogueDelegateDispatcherProxy::DialogueDelegateDispatcherProxy( + QWidget* editor, CSMWorld::ColumnBase::Display display) + : mEditor(editor) + , mDisplay(display) + , mIndexWrapper(nullptr) { } @@ -133,7 +152,7 @@ void CSVWorld::DialogueDelegateDispatcherProxy::editorDataCommited() void CSVWorld::DialogueDelegateDispatcherProxy::setIndex(const QModelIndex& index) { - mIndexWrapper.reset(new refWrapper(index)); + mIndexWrapper = std::make_unique(index); } QWidget* CSVWorld::DialogueDelegateDispatcherProxy::getEditor() const @@ -145,54 +164,58 @@ QWidget* CSVWorld::DialogueDelegateDispatcherProxy::getEditor() const ==============================DialogueDelegateDispatcher========================================== */ -CSVWorld::DialogueDelegateDispatcher::DialogueDelegateDispatcher(QObject* parent, - CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, - CSMDoc::Document& document, QAbstractItemModel *model) : -mParent(parent), -mTable(model ? model : table), -mCommandDispatcher (commandDispatcher), mDocument (document), -mNotEditableDelegate(table, parent) +CSVWorld::DialogueDelegateDispatcher::DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, + CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, QAbstractItemModel* model) + : mParent(parent) + , mTable(model ? model : table) + , mCommandDispatcher(commandDispatcher) + , mDocument(document) + , mNotEditableDelegate(table, parent) { } CSVWorld::CommandDelegate* CSVWorld::DialogueDelegateDispatcher::makeDelegate(CSMWorld::ColumnBase::Display display) { - CommandDelegate *delegate = nullptr; + CommandDelegate* delegate = nullptr; std::map::const_iterator delegateIt(mDelegates.find(display)); if (delegateIt == mDelegates.end()) { - delegate = CommandDelegateFactoryCollection::get().makeDelegate ( - display, &mCommandDispatcher, mDocument, mParent); + delegate + = CommandDelegateFactoryCollection::get().makeDelegate(display, &mCommandDispatcher, mDocument, mParent); mDelegates.insert(std::make_pair(display, delegate)); - } else + } + else { delegate = delegateIt->second; } return delegate; } -void CSVWorld::DialogueDelegateDispatcher::editorDataCommited(QWidget* editor, - const QModelIndex& index, CSMWorld::ColumnBase::Display display) +void CSVWorld::DialogueDelegateDispatcher::editorDataCommited( + QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display) { setModelData(editor, mTable, index, display); } -void CSVWorld::DialogueDelegateDispatcher::setEditorData (QWidget* editor, const QModelIndex& index) const +void CSVWorld::DialogueDelegateDispatcher::setEditorData(QWidget* editor, const QModelIndex& index) const { CSMWorld::ColumnBase::Display display = CSMWorld::ColumnBase::Display_None; if (index.parent().isValid()) { - display = static_cast - (static_cast(mTable)->nestedHeaderData (index.parent().column(), index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + display + = static_cast(static_cast(mTable) + ->nestedHeaderData(index.parent().column(), index.column(), + Qt::Horizontal, CSMWorld::ColumnBase::Role_Display) + .toInt()); } else { - display = static_cast - (mTable->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + display = static_cast( + mTable->headerData(index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); } QLabel* label = qobject_cast(editor); - if(label) + if (label) { mNotEditableDelegate.setEditorData(label, index); return; @@ -204,23 +227,23 @@ void CSVWorld::DialogueDelegateDispatcher::setEditorData (QWidget* editor, const delegateIt->second->setEditorData(editor, index, true); } - for (unsigned i = 0; i < mProxys.size(); ++i) + for (const auto& proxy : mProxys) { - if (mProxys[i]->getEditor() == editor) + if (proxy->getEditor() == editor) { - mProxys[i]->setIndex(index); + proxy->setIndex(index); } } } -void CSVWorld::DialogueDelegateDispatcher::setModelData(QWidget* editor, - QAbstractItemModel* model, const QModelIndex& index) const +void CSVWorld::DialogueDelegateDispatcher::setModelData( + QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { setModelData(editor, model, index, CSMWorld::ColumnBase::Display_None); } -void CSVWorld::DialogueDelegateDispatcher::setModelData(QWidget* editor, - QAbstractItemModel* model, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const +void CSVWorld::DialogueDelegateDispatcher::setModelData( + QWidget* editor, QAbstractItemModel* model, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { std::map::const_iterator delegateIt(mDelegates.find(display)); if (delegateIt != mDelegates.end()) @@ -229,20 +252,19 @@ void CSVWorld::DialogueDelegateDispatcher::setModelData(QWidget* editor, } } -void CSVWorld::DialogueDelegateDispatcher::paint (QPainter* painter, - const QStyleOptionViewItem& option, const QModelIndex& index) const +void CSVWorld::DialogueDelegateDispatcher::paint( + QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - //Does nothing + // Does nothing } -QSize CSVWorld::DialogueDelegateDispatcher::sizeHint (const QStyleOptionViewItem& option, - const QModelIndex& index) const +QSize CSVWorld::DialogueDelegateDispatcher::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { - return QSize(); //silencing warning, otherwise does nothing + return QSize(); // silencing warning, otherwise does nothing } -QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase::Display display, - const QModelIndex& index) +QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor( + CSMWorld::ColumnBase::Display display, const QModelIndex& index) { QVariant variant = index.data(); if (!variant.isValid()) @@ -255,18 +277,17 @@ QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase:: } QWidget* editor = nullptr; - if (! (mTable->flags (index) & Qt::ItemIsEditable)) + if (!(mTable->flags(index) & Qt::ItemIsEditable)) { - return mNotEditableDelegate.createEditor(qobject_cast(mParent), - QStyleOptionViewItem(), index); + return mNotEditableDelegate.createEditor(qobject_cast(mParent), QStyleOptionViewItem(), index); } std::map::iterator delegateIt(mDelegates.find(display)); if (delegateIt != mDelegates.end()) { - editor = delegateIt->second->createEditor(qobject_cast(mParent), - QStyleOptionViewItem(), index, display); + editor + = delegateIt->second->createEditor(qobject_cast(mParent), QStyleOptionViewItem(), index, display); DialogueDelegateDispatcherProxy* proxy = new DialogueDelegateDispatcherProxy(editor, display); @@ -274,70 +295,74 @@ QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase:: // is required here if (qobject_cast(editor)) { - connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); + connect(static_cast(editor), &CSVWidget::DropLineEdit::editingFinished, proxy, + qOverload<>(&DialogueDelegateDispatcherProxy::editorDataCommited)); - connect(editor, SIGNAL(tableMimeDataDropped(const CSMWorld::UniversalId&, const CSMDoc::Document*)), - proxy, SLOT(editorDataCommited())); + connect(static_cast(editor), &CSVWidget::DropLineEdit::tableMimeDataDropped, + proxy, qOverload<>(&DialogueDelegateDispatcherProxy::editorDataCommited)); } else if (qobject_cast(editor)) { - connect(editor, SIGNAL(stateChanged(int)), proxy, SLOT(editorDataCommited())); + connect(static_cast(editor), &QCheckBox::stateChanged, proxy, + qOverload<>(&DialogueDelegateDispatcherProxy::editorDataCommited)); } else if (qobject_cast(editor)) { - connect(editor, SIGNAL(textChanged()), proxy, SLOT(editorDataCommited())); + connect(static_cast(editor), &QPlainTextEdit::textChanged, proxy, + qOverload<>(&DialogueDelegateDispatcherProxy::editorDataCommited)); } else if (qobject_cast(editor)) { - connect(editor, SIGNAL(currentIndexChanged (int)), proxy, SLOT(editorDataCommited())); + connect(static_cast(editor), qOverload(&QComboBox::currentIndexChanged), proxy, + qOverload<>(&DialogueDelegateDispatcherProxy::editorDataCommited)); } else if (qobject_cast(editor) || qobject_cast(editor)) { - connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); + connect(static_cast(editor), &QAbstractSpinBox::editingFinished, proxy, + qOverload<>(&DialogueDelegateDispatcherProxy::editorDataCommited)); } - else if (qobject_cast(editor)) + else if (qobject_cast(editor)) { - connect(editor, SIGNAL(pickingFinished()), proxy, SLOT(editorDataCommited())); + connect(static_cast(editor), &CSVWidget::ColorEditor::pickingFinished, proxy, + qOverload<>(&DialogueDelegateDispatcherProxy::editorDataCommited)); } else // throw an exception because this is a coding error - throw std::logic_error ("Dialogue editor type missing"); + throw std::logic_error("Dialogue editor type missing"); - connect(proxy, SIGNAL(editorDataCommited(QWidget*, const QModelIndex&, CSMWorld::ColumnBase::Display)), - this, SLOT(editorDataCommited(QWidget*, const QModelIndex&, CSMWorld::ColumnBase::Display))); + connect(proxy, + qOverload( + &DialogueDelegateDispatcherProxy::editorDataCommited), + this, &DialogueDelegateDispatcher::editorDataCommited); - mProxys.push_back(proxy); //deleted in the destructor + mProxys.push_back(proxy); // deleted in the destructor } return editor; } CSVWorld::DialogueDelegateDispatcher::~DialogueDelegateDispatcher() { - for (unsigned i = 0; i < mProxys.size(); ++i) + for (auto* proxy : mProxys) { - delete mProxys[i]; //unique_ptr could be handy + delete proxy; // unique_ptr could be handy } } - -CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display) - : QObject(widget), - mWidget(widget), - mIdType(CSMWorld::TableMimeData::convertEnums(display)) +CSVWorld::IdContextMenu::IdContextMenu(QWidget* widget, CSMWorld::ColumnBase::Display display) + : QObject(widget) + , mWidget(widget) + , mIdType(CSMWorld::TableMimeData::convertEnums(display)) { Q_ASSERT(mWidget != nullptr); Q_ASSERT(CSMWorld::ColumnBase::isId(display)); Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); mWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(mWidget, - SIGNAL(customContextMenuRequested(const QPoint &)), - this, - SLOT(showContextMenu(const QPoint &))); + connect(mWidget, &QWidget::customContextMenuRequested, this, &IdContextMenu::showContextMenu); mEditIdAction = new QAction(this); - connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editIdRequest())); + connect(mEditIdAction, &QAction::triggered, this, qOverload<>(&IdContextMenu::editIdRequest)); - QLineEdit *lineEdit = qobject_cast(mWidget); + QLineEdit* lineEdit = qobject_cast(mWidget); if (lineEdit != nullptr) { mContextMenu = lineEdit->createStandardContextMenu(); @@ -348,15 +373,15 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di } } -void CSVWorld::IdContextMenu::excludeId(const std::string &id) +void CSVWorld::IdContextMenu::excludeId(const std::string& id) { mExcludedIds.insert(id); } QString CSVWorld::IdContextMenu::getWidgetValue() const { - QLineEdit *lineEdit = qobject_cast(mWidget); - QLabel *label = qobject_cast(mWidget); + QLineEdit* lineEdit = qobject_cast(mWidget); + QLabel* label = qobject_cast(mWidget); QString value = ""; if (lineEdit != nullptr) @@ -370,7 +395,7 @@ QString CSVWorld::IdContextMenu::getWidgetValue() const return value; } -void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) +void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString& text) { mEditIdAction->setText(text); if (mContextMenu->actions().isEmpty()) @@ -379,7 +404,7 @@ void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) } else if (mContextMenu->actions().first() != mEditIdAction) { - QAction *action = mContextMenu->actions().first(); + QAction* action = mContextMenu->actions().first(); mContextMenu->insertAction(action, mEditIdAction); mContextMenu->insertSeparator(action); } @@ -402,7 +427,7 @@ void CSVWorld::IdContextMenu::removeEditIdActionFromMenu() } } -void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) +void CSVWorld::IdContextMenu::showContextMenu(const QPoint& pos) { QString value = getWidgetValue(); bool isExcludedId = mExcludedIds.find(value.toUtf8().constData()) != mExcludedIds.end(); @@ -431,32 +456,29 @@ void CSVWorld::IdContextMenu::editIdRequest() =============================================================EditWidget===================================================== */ -void CSVWorld::EditWidget::createEditorContextMenu(QWidget *editor, - CSMWorld::ColumnBase::Display display, - int currentRow) const +void CSVWorld::EditWidget::createEditorContextMenu( + QWidget* editor, CSMWorld::ColumnBase::Display display, int currentRow) const { Q_ASSERT(editor != nullptr); - if (CSMWorld::ColumnBase::isId(display) && - CSMWorld::TableMimeData::convertEnums(display) != CSMWorld::UniversalId::Type_None) + if (CSMWorld::ColumnBase::isId(display) + && CSMWorld::TableMimeData::convertEnums(display) != CSMWorld::UniversalId::Type_None) { int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); QString id = mTable->data(mTable->index(currentRow, idColumn)).toString(); - IdContextMenu *menu = new IdContextMenu(editor, display); + IdContextMenu* menu = new IdContextMenu(editor, display); // Current ID is already opened, so no need to create Edit 'ID' action for it menu->excludeId(id.toUtf8().constData()); - connect(menu, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), - this, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + connect(menu, qOverload(&IdContextMenu::editIdRequest), this, + &EditWidget::editIdRequest); } } CSVWorld::EditWidget::~EditWidget() { - for (unsigned i = 0; i < mNestedModels.size(); ++i) - delete mNestedModels[i]; + for (auto* model : mNestedModels) + delete model; if (mDispatcher) delete mDispatcher; @@ -465,55 +487,53 @@ CSVWorld::EditWidget::~EditWidget() delete mNestedTableDispatcher; } -CSVWorld::EditWidget::EditWidget(QWidget *parent, - int row, CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, - CSMDoc::Document& document, bool createAndDelete) : -QScrollArea(parent), -mWidgetMapper(nullptr), -mNestedTableMapper(nullptr), -mDispatcher(nullptr), -mNestedTableDispatcher(nullptr), -mMainWidget(nullptr), -mTable(table), -mCommandDispatcher (commandDispatcher), -mDocument (document) +CSVWorld::EditWidget::EditWidget(QWidget* parent, int row, CSMWorld::IdTable* table, + CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, bool createAndDelete) + : QScrollArea(parent) + , mWidgetMapper(nullptr) + , mNestedTableMapper(nullptr) + , mDispatcher(nullptr) + , mNestedTableDispatcher(nullptr) + , mMainWidget(nullptr) + , mTable(table) + , mCommandDispatcher(commandDispatcher) + , mDocument(document) { - remake (row); + remake(row); } void CSVWorld::EditWidget::remake(int row) { if (mMainWidget) { - QWidget *del = this->takeWidget(); + QWidget* del = this->takeWidget(); del->deleteLater(); } - mMainWidget = new QWidget (this); + mMainWidget = new QWidget(this); - for (unsigned i = 0; i < mNestedModels.size(); ++i) - delete mNestedModels[i]; + for (auto* model : mNestedModels) + delete model; mNestedModels.clear(); if (mDispatcher) delete mDispatcher; - mDispatcher = new DialogueDelegateDispatcher(nullptr/*this*/, mTable, mCommandDispatcher, mDocument); + mDispatcher = new DialogueDelegateDispatcher(nullptr /*this*/, mTable, mCommandDispatcher, mDocument); if (mNestedTableDispatcher) delete mNestedTableDispatcher; - //not sure if widget mapper can handle deleting the widgets that were mapped + // not sure if widget mapper can handle deleting the widgets that were mapped if (mWidgetMapper) delete mWidgetMapper; - mWidgetMapper = new QDataWidgetMapper (this); + mWidgetMapper = new QDataWidgetMapper(this); mWidgetMapper->setModel(mTable); mWidgetMapper->setItemDelegate(mDispatcher); if (mNestedTableMapper) delete mNestedTableMapper; - QFrame* line = new QFrame(mMainWidget); line->setObjectName(QString::fromUtf8("line")); line->setGeometry(QRect(320, 150, 118, 3)); @@ -526,10 +546,10 @@ void CSVWorld::EditWidget::remake(int row) line2->setFrameShape(QFrame::HLine); line2->setFrameShadow(QFrame::Sunken); - QVBoxLayout *mainLayout = new QVBoxLayout(mMainWidget); - QGridLayout *lockedLayout = new QGridLayout(); - QGridLayout *unlockedLayout = new QGridLayout(); - QVBoxLayout *tablesLayout = new QVBoxLayout(); + QVBoxLayout* mainLayout = new QVBoxLayout(mMainWidget); + QGridLayout* lockedLayout = new QGridLayout(); + QGridLayout* unlockedLayout = new QGridLayout(); + QVBoxLayout* tablesLayout = new QVBoxLayout(); mainLayout->addLayout(lockedLayout, QSizePolicy::Fixed); mainLayout->addWidget(line, 1); @@ -538,51 +558,57 @@ void CSVWorld::EditWidget::remake(int row) mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred); mainLayout->addStretch(1); + int blockedColumn = mTable->searchColumnIndex(CSMWorld::Columns::ColumnId_Blocked); + bool isBlocked = mTable->data(mTable->index(row, blockedColumn)).toInt(); + int unlocked = 0; int locked = 0; const int columns = mTable->columnCount(); - for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + int flags = mTable->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); if (flags & CSMWorld::ColumnBase::Flag_Dialogue) { - CSMWorld::ColumnBase::Display display = static_cast - (mTable->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + CSMWorld::ColumnBase::Display display = static_cast( + mTable->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - if (mTable->hasChildren(mTable->index(row, i)) && - !(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) + if (mTable->hasChildren(mTable->index(row, i)) && !(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { CSMWorld::IdTree* innerTable = &dynamic_cast(*mTable); - mNestedModels.push_back(new CSMWorld::NestedTableProxyModel (mTable->index(row, i), display, innerTable)); + mNestedModels.push_back( + new CSMWorld::NestedTableProxyModel(mTable->index(row, i), display, innerTable)); - int idColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + int typeColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); CSMWorld::UniversalId id = CSMWorld::UniversalId( - static_cast (mTable->data (mTable->index (row, typeColumn)).toInt()), - mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); + static_cast(mTable->data(mTable->index(row, typeColumn)).toInt()), + mTable->data(mTable->index(row, idColumn)).toString().toUtf8().constData()); bool editable = true; bool fixedRows = false; QVariant v = mTable->index(row, i).data(); if (v.canConvert()) { - assert (QString(v.typeName()) == "CSMWorld::ColumnBase::TableEditModes"); + assert(QString(v.typeName()) == "CSMWorld::ColumnBase::TableEditModes"); if (v.value() == CSMWorld::ColumnBase::TableEdit_None) editable = false; - else if (v.value() == CSMWorld::ColumnBase::TableEdit_FixedRows) + else if (v.value() + == CSMWorld::ColumnBase::TableEdit_FixedRows) fixedRows = true; } // Create and display nested table only if it's editable. if (editable) { - NestedTable* table = - new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); + NestedTable* table + = new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); table->resizeColumnsToContents(); + if (isBlocked) + table->setEditTriggers(QAbstractItemView::NoEditTriggers); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); @@ -590,47 +616,46 @@ void CSVWorld::EditWidget::remake(int row) int tableMaxHeight = (5 * rowHeight) + headerHeight + (2 * table->frameWidth()); table->setMinimumHeight(tableMaxHeight); - QString headerText = mTable->headerData (i, Qt::Horizontal, Qt::DisplayRole).toString(); - QLabel* label = new QLabel (headerText, mMainWidget); - label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); + QString headerText = mTable->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + QLabel* label = new QLabel(headerText, mMainWidget); + label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); tablesLayout->addWidget(label); tablesLayout->addWidget(table); - connect(table, - SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), - this, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + connect(table, &NestedTable::editRequest, this, &EditWidget::editIdRequest); } } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { - mDispatcher->makeDelegate (display); - QWidget* editor = mDispatcher->makeEditor (display, (mTable->index (row, i))); + mDispatcher->makeDelegate(display); + QWidget* editor = mDispatcher->makeEditor(display, (mTable->index(row, i))); if (editor) { - mWidgetMapper->addMapping (editor, i); + mWidgetMapper->addMapping(editor, i); - QLabel* label = new QLabel (mTable->headerData (i, Qt::Horizontal).toString(), mMainWidget); + QLabel* label = new QLabel(mTable->headerData(i, Qt::Horizontal).toString(), mMainWidget); - label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + editor->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - if (! (mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable)) + // HACK: the blocked checkbox needs to keep the same position + // FIXME: unfortunately blocked record displays a little differently to unblocked one + if (!(mTable->flags(mTable->index(row, i)) & Qt::ItemIsEditable) || i == blockedColumn) { - lockedLayout->addWidget (label, locked, 0); - lockedLayout->addWidget (editor, locked, 1); + lockedLayout->addWidget(label, locked, 0); + lockedLayout->addWidget(editor, locked, 1); ++locked; } else { - unlockedLayout->addWidget (label, unlocked, 0); - unlockedLayout->addWidget (editor, unlocked, 1); + unlockedLayout->addWidget(label, unlocked, 0); + unlockedLayout->addWidget(editor, unlocked, 1); ++unlocked; } - if(mTable->index(row, i).data().type() == QVariant::UserType) + if (mTable->index(row, i).data().type() == QVariant::UserType) { editor->setEnabled(false); label->setEnabled(false); @@ -639,54 +664,57 @@ void CSVWorld::EditWidget::remake(int row) createEditorContextMenu(editor, display, row); } } - else + else // Flag_Dialogue_List { - CSMWorld::IdTree *tree = static_cast(mTable); - mNestedTableMapper = new QDataWidgetMapper (this); + CSMWorld::IdTree* tree = static_cast(mTable); + mNestedTableMapper = new QDataWidgetMapper(this); mNestedTableMapper->setModel(tree); // FIXME: lack MIME support? - mNestedTableDispatcher = - new DialogueDelegateDispatcher (nullptr/*this*/, mTable, mCommandDispatcher, mDocument, tree); - mNestedTableMapper->setRootIndex (tree->index(row, i)); + mNestedTableDispatcher + = new DialogueDelegateDispatcher(nullptr /*this*/, mTable, mCommandDispatcher, mDocument, tree); + mNestedTableMapper->setRootIndex(tree->index(row, i)); mNestedTableMapper->setItemDelegate(mNestedTableDispatcher); int columnCount = tree->columnCount(tree->index(row, i)); for (int col = 0; col < columnCount; ++col) { - int displayRole = tree->nestedHeaderData (i, col, - Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt(); + int displayRole + = tree->nestedHeaderData(i, col, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt(); - display = static_cast (displayRole); + display = static_cast(displayRole); - mNestedTableDispatcher->makeDelegate (display); + mNestedTableDispatcher->makeDelegate(display); // FIXME: assumed all columns are editable - QWidget* editor = - mNestedTableDispatcher->makeEditor (display, tree->index (0, col, tree->index(row, i))); + QWidget* editor + = mNestedTableDispatcher->makeEditor(display, tree->index(0, col, tree->index(row, i))); if (editor) { - mNestedTableMapper->addMapping (editor, col); + mNestedTableMapper->addMapping(editor, col); // Need to use Qt::DisplayRole in order to get the correct string // from CSMWorld::Columns - QLabel* label = new QLabel (tree->nestedHeaderData (i, col, - Qt::Horizontal, Qt::DisplayRole).toString(), mMainWidget); + QLabel* label = new QLabel( + tree->nestedHeaderData(i, col, Qt::Horizontal, Qt::DisplayRole).toString(), mMainWidget); - label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + editor->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - unlockedLayout->addWidget (label, unlocked, 0); - unlockedLayout->addWidget (editor, unlocked, 1); + unlockedLayout->addWidget(label, unlocked, 0); + unlockedLayout->addWidget(editor, unlocked, 1); ++unlocked; - if(tree->index(0, col, tree->index(row, i)).data().type() == QVariant::UserType) + if (tree->index(0, col, tree->index(row, i)).data().type() == QVariant::UserType) { editor->setEnabled(false); label->setEnabled(false); } - createEditorContextMenu(editor, display, row); + if (!isBlocked) + createEditorContextMenu(editor, display, row); + else + editor->setEnabled(false); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); @@ -703,7 +731,6 @@ void CSVWorld::EditWidget::remake(int row) this->setWidgetResizable(true); } - QVBoxLayout& CSVWorld::SimpleDialogueSubView::getMainLayout() { return *mMainLayout; @@ -729,98 +756,96 @@ bool CSVWorld::SimpleDialogueSubView::isLocked() const return mLocked; } -CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : - SubView (id), - mEditWidget(nullptr), - mMainLayout(nullptr), - mTable(dynamic_cast(document.getData().getTableModel(id))), - mLocked(false), - mDocument(document), - mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) +CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) + : SubView(id) + , mEditWidget(nullptr) + , mMainLayout(nullptr) + , mTable(dynamic_cast(document.getData().getTableModel(id))) + , mLocked(false) + , mDocument(document) + , mCommandDispatcher(document, CSMWorld::UniversalId::getParentType(id.getType())) { - connect(mTable, SIGNAL(dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT(dataChanged(const QModelIndex&))); - connect(mTable, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int))); + connect(mTable, &CSMWorld::IdTable::dataChanged, this, &SimpleDialogueSubView::dataChanged); + connect(mTable, &CSMWorld::IdTable::rowsAboutToBeRemoved, this, &SimpleDialogueSubView::rowsAboutToBeRemoved); updateCurrentId(); - QWidget *mainWidget = new QWidget(this); + QWidget* mainWidget = new QWidget(this); mMainLayout = new QVBoxLayout(mainWidget); - setWidget (mainWidget); + setWidget(mainWidget); - int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int idColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Id); - mEditWidget = new EditWidget(mainWidget, - mTable->getModelIndex(getUniversalId().getId(), idColumn).row(), mTable, mCommandDispatcher, document, false); + mEditWidget = new EditWidget(mainWidget, mTable->getModelIndex(getUniversalId().getId(), idColumn).row(), mTable, + mCommandDispatcher, document, false); mMainLayout->addWidget(mEditWidget); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - dataChanged(mTable->getModelIndex (getUniversalId().getId(), idColumn)); + dataChanged(mTable->getModelIndex(getUniversalId().getId(), idColumn)); - connect(mEditWidget, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), - this, - SIGNAL(focusId(const CSMWorld::UniversalId &, const std::string &))); + connect(mEditWidget, &EditWidget::editIdRequest, this, &SimpleDialogueSubView::focusId); } -void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) +void CSVWorld::SimpleDialogueSubView::setEditLock(bool locked) { if (!mEditWidget) // hack to indicate that getUniversalId().getId() is no longer valid return; - int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int idColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Id); mLocked = locked; QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid()) { - CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (currentIndex.row(), 1)).toInt()); + CSMWorld::RecordBase::State state + = static_cast(mTable->data(mTable->index(currentIndex.row(), 1)).toInt()); - mEditWidget->setDisabled (state==CSMWorld::RecordBase::State_Deleted || locked); + mEditWidget->setDisabled(state == CSMWorld::RecordBase::State_Deleted || locked); - mCommandDispatcher.setEditLock (locked); + mCommandDispatcher.setEditLock(locked); } - } -void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) +void CSVWorld::SimpleDialogueSubView::dataChanged(const QModelIndex& index) { - int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int idColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Id); QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); - if (currentIndex.isValid() && - (index.parent().isValid() ? index.parent().row() : index.row()) == currentIndex.row()) + if (currentIndex.isValid() && (index.parent().isValid() ? index.parent().row() : index.row()) == currentIndex.row()) { - CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (currentIndex.row(), 1)).toInt()); + CSMWorld::RecordBase::State state + = static_cast(mTable->data(mTable->index(currentIndex.row(), 1)).toInt()); - mEditWidget->setDisabled (state==CSMWorld::RecordBase::State_Deleted || mLocked); + mEditWidget->setDisabled(state == CSMWorld::RecordBase::State_Deleted || mLocked); // Check if the changed data should force refresh (rebuild) the dialogue subview int flags = 0; if (index.parent().isValid()) // TODO: check that index is topLeft { - flags = static_cast(mTable)->nestedHeaderData (index.parent().column(), - index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + flags = static_cast(mTable) + ->nestedHeaderData( + index.parent().column(), index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags) + .toInt(); } else { - flags = mTable->headerData (index.column(), - Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + flags = mTable->headerData(index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); } if (flags & CSMWorld::ColumnBase::Flag_Dialogue_Refresh) { int y = mEditWidget->verticalScrollBar()->value(); - mEditWidget->remake (index.parent().isValid() ? index.parent().row() : index.row()); + mEditWidget->remake(index.parent().isValid() ? index.parent().row() : index.row()); mEditWidget->verticalScrollBar()->setValue(y); } } } -void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int idColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Id); QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (!currentIndex.isValid()) @@ -830,7 +855,7 @@ void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &pa if (currentIndex.parent() == parent && currentIndex.row() >= start && currentIndex.row() <= end) { - if(mEditWidget) + if (mEditWidget) { delete mEditWidget; mEditWidget = nullptr; @@ -842,59 +867,55 @@ void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &pa void CSVWorld::SimpleDialogueSubView::updateCurrentId() { std::vector selection; - selection.push_back (getUniversalId().getId()); + selection.push_back(getUniversalId().getId()); mCommandDispatcher.setSelection(selection); } - void CSVWorld::DialogueSubView::addButtonBar() { if (mButtons) return; - mButtons = new RecordButtonBar (getUniversalId(), getTable(), mBottom, - &getCommandDispatcher(), this); + mButtons = new RecordButtonBar(getUniversalId(), getTable(), mBottom, &getCommandDispatcher(), this); - getMainLayout().insertWidget (1, mButtons); + getMainLayout().insertWidget(1, mButtons); // connections - connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview())); - connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord())); - connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + connect(mButtons, &RecordButtonBar::showPreview, this, &DialogueSubView::showPreview); + connect(mButtons, &RecordButtonBar::viewRecord, this, &DialogueSubView::viewRecord); + connect(mButtons, &RecordButtonBar::switchToRow, this, &DialogueSubView::switchToRow); - connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), - mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); + connect(this, &DialogueSubView::universalIdChanged, mButtons, &RecordButtonBar::universalIdChanged); } -CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, - CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) -: SimpleDialogueSubView (id, document), mButtons (nullptr) +CSVWorld::DialogueSubView::DialogueSubView( + const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) + : SimpleDialogueSubView(id, document) + , mButtons(nullptr) { // bottom box - mBottom = new TableBottomBox (creatorFactory, document, id, this); + mBottom = new TableBottomBox(creatorFactory, document, id, this); - connect (mBottom, SIGNAL (requestFocus (const std::string&)), - this, SLOT (requestFocus (const std::string&))); + connect(mBottom, &TableBottomBox::requestFocus, this, &DialogueSubView::requestFocus); // layout - getMainLayout().addWidget (mBottom); + getMainLayout().addWidget(mBottom); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &DialogueSubView::settingChanged); CSMPrefs::get()["ID Dialogues"].update(); } -void CSVWorld::DialogueSubView::setEditLock (bool locked) +void CSVWorld::DialogueSubView::setEditLock(bool locked) { - SimpleDialogueSubView::setEditLock (locked); + SimpleDialogueSubView::setEditLock(locked); if (mButtons) - mButtons->setEditLock (locked); + mButtons->setEditLock(locked); } -void CSVWorld::DialogueSubView::settingChanged (const CSMPrefs::Setting *setting) +void CSVWorld::DialogueSubView::settingChanged(const CSMPrefs::Setting* setting) { - if (*setting=="ID Dialogues/toolbar") + if (*setting == "ID Dialogues/toolbar") { if (setting->isTrue()) { @@ -902,67 +923,65 @@ void CSVWorld::DialogueSubView::settingChanged (const CSMPrefs::Setting *setting } else if (mButtons) { - getMainLayout().removeWidget (mButtons); + getMainLayout().removeWidget(mButtons); delete mButtons; mButtons = nullptr; } } } -void CSVWorld::DialogueSubView::showPreview () +void CSVWorld::DialogueSubView::showPreview() { - int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); - QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); + int idColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(getTable().getModelIndex(getUniversalId().getId(), idColumn)); - if (currentIndex.isValid() && - getTable().getFeatures() & CSMWorld::IdTable::Feature_Preview && - currentIndex.row() < getTable().rowCount()) + if (currentIndex.isValid() && getTable().getFeatures() & CSMWorld::IdTable::Feature_Preview + && currentIndex.row() < getTable().rowCount()) { emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, getUniversalId().getId()), ""); } } -void CSVWorld::DialogueSubView::viewRecord () +void CSVWorld::DialogueSubView::viewRecord() { - int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); - QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); + int idColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(getTable().getModelIndex(getUniversalId().getId(), idColumn)); - if (currentIndex.isValid() && - currentIndex.row() < getTable().rowCount()) + if (currentIndex.isValid() && currentIndex.row() < getTable().rowCount()) { - std::pair params = getTable().view (currentIndex.row()); + std::pair params = getTable().view(currentIndex.row()); - if (params.first.getType()!=CSMWorld::UniversalId::Type_None) - emit focusId (params.first, params.second); + if (params.first.getType() != CSMWorld::UniversalId::Type_None) + emit focusId(params.first, params.second); } } -void CSVWorld::DialogueSubView::switchToRow (int row) +void CSVWorld::DialogueSubView::switchToRow(int row) { - int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); - std::string id = getTable().data (getTable().index (row, idColumn)).toString().toUtf8().constData(); + int idColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Id); + std::string id = getTable().data(getTable().index(row, idColumn)).toString().toUtf8().constData(); - int typeColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); - CSMWorld::UniversalId::Type type = static_cast ( - getTable().data (getTable().index (row, typeColumn)).toInt()); + int typeColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); + CSMWorld::UniversalId::Type type + = static_cast(getTable().data(getTable().index(row, typeColumn)).toInt()); - setUniversalId (CSMWorld::UniversalId (type, id)); + setUniversalId(CSMWorld::UniversalId(type, id)); updateCurrentId(); - getEditWidget().remake (row); + getEditWidget().remake(row); - int stateColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - CSMWorld::RecordBase::State state = static_cast ( - getTable().data (getTable().index (row, stateColumn)).toInt()); + int stateColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + CSMWorld::RecordBase::State state + = static_cast(getTable().data(getTable().index(row, stateColumn)).toInt()); - getEditWidget().setDisabled (isLocked() || state==CSMWorld::RecordBase::State_Deleted); + getEditWidget().setDisabled(isLocked() || state == CSMWorld::RecordBase::State_Deleted); } -void CSVWorld::DialogueSubView::requestFocus (const std::string& id) +void CSVWorld::DialogueSubView::requestFocus(const std::string& id) { - int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); - QModelIndex index = getTable().getModelIndex (id, idColumn); + int idColumn = getTable().findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QModelIndex index = getTable().getModelIndex(id, idColumn); if (index.isValid()) - switchToRow (index.row()); + switchToRow(index.row()); } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 2cf05f711..b5108f3b0 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -1,12 +1,16 @@ #ifndef CSV_WORLD_DIALOGUESUBVIEW_H #define CSV_WORLD_DIALOGUESUBVIEW_H -#include #include #include +#include +#include +#include #include +#include #include +#include #ifndef Q_MOC_RUN #include "../doc/subview.hpp" @@ -16,12 +20,16 @@ #include "../../model/world/universalid.hpp" #endif +class QAbstractItemModel; +class QAction; class QDataWidgetMapper; -class QSize; -class QEvent; -class QLabel; -class QVBoxLayout; class QMenu; +class QModelIndex; +class QPainter; +class QPoint; +class QSize; +class QVBoxLayout; +class QWidget; namespace CSMWorld { @@ -48,29 +56,25 @@ namespace CSVWorld class NotEditableSubDelegate : public QAbstractItemDelegate { const CSMWorld::IdTable* mTable; + public: - NotEditableSubDelegate(const CSMWorld::IdTable* table, - QObject * parent = nullptr); + NotEditableSubDelegate(const CSMWorld::IdTable* table, QObject* parent = nullptr); - void setEditorData (QWidget* editor, const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; - void setModelData (QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; - void paint (QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; ///< does nothing - QSize sizeHint (const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; ///< does nothing - QWidget *createEditor (QWidget *parent, - const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + QWidget* createEditor( + QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; - //this can't be nested into the DialogueDelegateDispatcher, because it needs to emit signals + // this can't be nested into the DialogueDelegateDispatcher, because it needs to emit signals class DialogueDelegateDispatcherProxy : public QObject { Q_OBJECT @@ -89,8 +93,7 @@ namespace CSVWorld std::unique_ptr mIndexWrapper; public: - DialogueDelegateDispatcherProxy(QWidget* editor, - CSMWorld::ColumnBase::Display display); + DialogueDelegateDispatcherProxy(QWidget* editor, CSMWorld::ColumnBase::Display display); QWidget* getEditor() const; public slots: @@ -98,10 +101,7 @@ namespace CSVWorld void setIndex(const QModelIndex& index); signals: - void editorDataCommited(QWidget* editor, - const QModelIndex& index, - CSMWorld::ColumnBase::Display display); - + void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); }; class DialogueDelegateDispatcher : public QAbstractItemDelegate @@ -119,14 +119,12 @@ namespace CSVWorld NotEditableSubDelegate mNotEditableDelegate; std::vector mProxys; - //once we move to the C++11 we should use unique_ptr + // once we move to the C++11 we should use unique_ptr public: - DialogueDelegateDispatcher(QObject* parent, - CSMWorld::IdTable* table, - CSMWorld::CommandDispatcher& commandDispatcher, - CSMDoc::Document& document, - QAbstractItemModel* model = nullptr); + DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, + CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, + QAbstractItemModel* model = nullptr); ~DialogueDelegateDispatcher(); @@ -134,161 +132,148 @@ namespace CSVWorld QWidget* makeEditor(CSMWorld::ColumnBase::Display display, const QModelIndex& index); ///< will return null if delegate is not present, parent of the widget is - //same as for dispatcher itself + // same as for dispatcher itself - void setEditorData (QWidget* editor, const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; - void setModelData (QWidget* editor, QAbstractItemModel* model, - const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; - virtual void setModelData (QWidget* editor, - QAbstractItemModel* model, const QModelIndex& index, - CSMWorld::ColumnBase::Display display) const; + virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index, + CSMWorld::ColumnBase::Display display) const; - void paint (QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; ///< does nothing - QSize sizeHint (const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; ///< does nothing private slots: - void editorDataCommited(QWidget* editor, const QModelIndex& index, - CSMWorld::ColumnBase::Display display); + void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); }; /// A context menu with "Edit 'ID'" action for editors in the dialogue subview class IdContextMenu : public QObject { - Q_OBJECT + Q_OBJECT - QWidget *mWidget; - CSMWorld::UniversalId::Type mIdType; - std::set mExcludedIds; - ///< A list of IDs that should not have the Edit 'ID' action. + QWidget* mWidget; + CSMWorld::UniversalId::Type mIdType; + std::set mExcludedIds; + ///< A list of IDs that should not have the Edit 'ID' action. - QMenu *mContextMenu; - QAction *mEditIdAction; + QMenu* mContextMenu; + QAction* mEditIdAction; - QString getWidgetValue() const; - void addEditIdActionToMenu(const QString &text); - void removeEditIdActionFromMenu(); + QString getWidgetValue() const; + void addEditIdActionToMenu(const QString& text); + void removeEditIdActionFromMenu(); - public: - IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); + public: + IdContextMenu(QWidget* widget, CSMWorld::ColumnBase::Display display); - void excludeId(const std::string &id); + void excludeId(const std::string& id); - private slots: - void showContextMenu(const QPoint &pos); - void editIdRequest(); + private slots: + void showContextMenu(const QPoint& pos); + void editIdRequest(); - signals: - void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); + signals: + void editIdRequest(const CSMWorld::UniversalId& id, const std::string& hint); }; class EditWidget : public QScrollArea { Q_OBJECT - QDataWidgetMapper *mWidgetMapper; - QDataWidgetMapper *mNestedTableMapper; - DialogueDelegateDispatcher *mDispatcher; - DialogueDelegateDispatcher *mNestedTableDispatcher; - QWidget* mMainWidget; - CSMWorld::IdTable* mTable; - CSMWorld::CommandDispatcher& mCommandDispatcher; - CSMDoc::Document& mDocument; - std::vector mNestedModels; //Plain, raw C pointers, deleted in the dtor + QDataWidgetMapper* mWidgetMapper; + QDataWidgetMapper* mNestedTableMapper; + DialogueDelegateDispatcher* mDispatcher; + DialogueDelegateDispatcher* mNestedTableDispatcher; + QWidget* mMainWidget; + CSMWorld::IdTable* mTable; + CSMWorld::CommandDispatcher& mCommandDispatcher; + CSMDoc::Document& mDocument; + std::vector mNestedModels; // Plain, raw C pointers, deleted in the dtor - void createEditorContextMenu(QWidget *editor, - CSMWorld::ColumnBase::Display display, - int currentRow) const; - public: + void createEditorContextMenu(QWidget* editor, CSMWorld::ColumnBase::Display display, int currentRow) const; - EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, - CSMWorld::CommandDispatcher& commandDispatcher, - CSMDoc::Document& document, bool createAndDelete = false); + public: + EditWidget(QWidget* parent, int row, CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, + CSMDoc::Document& document, bool createAndDelete = false); - virtual ~EditWidget(); + virtual ~EditWidget(); - void remake(int row); + void remake(int row); - signals: - void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); + signals: + void editIdRequest(const CSMWorld::UniversalId& id, const std::string& hint); }; class SimpleDialogueSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - EditWidget* mEditWidget; - QVBoxLayout* mMainLayout; - CSMWorld::IdTable* mTable; - bool mLocked; - const CSMDoc::Document& mDocument; - CSMWorld::CommandDispatcher mCommandDispatcher; + EditWidget* mEditWidget; + QVBoxLayout* mMainLayout; + CSMWorld::IdTable* mTable; + bool mLocked; + const CSMDoc::Document& mDocument; + CSMWorld::CommandDispatcher mCommandDispatcher; - protected: + protected: + QVBoxLayout& getMainLayout(); - QVBoxLayout& getMainLayout(); + CSMWorld::IdTable& getTable(); - CSMWorld::IdTable& getTable(); + CSMWorld::CommandDispatcher& getCommandDispatcher(); - CSMWorld::CommandDispatcher& getCommandDispatcher(); + EditWidget& getEditWidget(); - EditWidget& getEditWidget(); + void updateCurrentId(); - void updateCurrentId(); + bool isLocked() const; - bool isLocked() const; + public: + SimpleDialogueSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document); - public: + void setEditLock(bool locked) override; - SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + private slots: - void setEditLock (bool locked) override; + void dataChanged(const QModelIndex& index); + ///\brief we need to care for deleting currently edited record - private slots: - - void dataChanged(const QModelIndex & index); - ///\brief we need to care for deleting currently edited record - - void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); }; class RecordButtonBar; class DialogueSubView : public SimpleDialogueSubView { - Q_OBJECT + Q_OBJECT - TableBottomBox* mBottom; - RecordButtonBar *mButtons; + TableBottomBox* mBottom; + RecordButtonBar* mButtons; - private: + private: + void addButtonBar(); - void addButtonBar(); + public: + DialogueSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting = false); - public: + void setEditLock(bool locked) override; - DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting = false); + private slots: - void setEditLock (bool locked) override; + void settingChanged(const CSMPrefs::Setting* setting); - private slots: + void showPreview(); - void settingChanged (const CSMPrefs::Setting *setting); + void viewRecord(); - void showPreview(); + void switchToRow(int row); - void viewRecord(); - - void switchToRow (int row); - - void requestFocus (const std::string& id); + void requestFocus(const std::string& id); }; } diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp index bb4d97273..e6c68fa12 100644 --- a/apps/opencs/view/world/dragdroputils.cpp +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -2,33 +2,43 @@ #include +#include +#include + #include "../../model/world/tablemimedata.hpp" -const CSMWorld::TableMimeData *CSVWorld::DragDropUtils::getTableMimeData(const QDropEvent &event) +const CSMWorld::TableMimeData* CSVWorld::DragDropUtils::getTableMimeData(const QDropEvent& event) { - return dynamic_cast(event.mimeData()); + return dynamic_cast(event.mimeData()); } -bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) +bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent& event, CSMWorld::ColumnBase::Display type) { - const CSMWorld::TableMimeData *data = getTableMimeData(event); + const CSMWorld::TableMimeData* data = getTableMimeData(event); return data != nullptr && data->holdsType(type); } -bool CSVWorld::DragDropUtils::isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type) +bool CSVWorld::DragDropUtils::isTopicOrJournal(const QDropEvent& event, CSMWorld::ColumnBase::Display type) { - const CSMWorld::TableMimeData *data = getTableMimeData(event); - return data != nullptr && ( - data->holdsType(CSMWorld::UniversalId::Type_TopicInfo) || - data->holdsType(CSMWorld::UniversalId::Type_JournalInfo) ); + const CSMWorld::TableMimeData* data = getTableMimeData(event); + return data != nullptr + && (data->holdsType(CSMWorld::UniversalId::Type_Topic) || data->holdsType(CSMWorld::UniversalId::Type_Journal)); } -CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, - CSMWorld::ColumnBase::Display type) +bool CSVWorld::DragDropUtils::isInfo(const QDropEvent& event, CSMWorld::ColumnBase::Display type) +{ + const CSMWorld::TableMimeData* data = getTableMimeData(event); + return data != nullptr + && (data->holdsType(CSMWorld::UniversalId::Type_TopicInfo) + || data->holdsType(CSMWorld::UniversalId::Type_JournalInfo)); +} + +CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData( + const QDropEvent& event, CSMWorld::ColumnBase::Display type) { if (canAcceptData(event, type)) { - if (const CSMWorld::TableMimeData *data = getTableMimeData(event)) + if (const CSMWorld::TableMimeData* data = getTableMimeData(event)) return data->returnMatching(type); } return CSMWorld::UniversalId::Type_None; diff --git a/apps/opencs/view/world/dragdroputils.hpp b/apps/opencs/view/world/dragdroputils.hpp index 2181e7606..bf822180c 100644 --- a/apps/opencs/view/world/dragdroputils.hpp +++ b/apps/opencs/view/world/dragdroputils.hpp @@ -15,15 +15,17 @@ namespace CSVWorld { namespace DragDropUtils { - const CSMWorld::TableMimeData *getTableMimeData(const QDropEvent &event); + const CSMWorld::TableMimeData* getTableMimeData(const QDropEvent& event); - bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + bool canAcceptData(const QDropEvent& event, CSMWorld::ColumnBase::Display type); ///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type - bool isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + bool isTopicOrJournal(const QDropEvent& event, CSMWorld::ColumnBase::Display type); + + bool isInfo(const QDropEvent& event, CSMWorld::ColumnBase::Display type); ///< Info types can be dragged to sort the info table - CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + CSMWorld::UniversalId getAcceptedData(const QDropEvent& event, CSMWorld::ColumnBase::Display type); ///< Gets the accepted data from the \a event using the \a type ///< \return Type_None if the \a event data doesn't holds the \a type } diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index 58041af9f..ae8c1cb70 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -3,14 +3,20 @@ #include #include +#include +#include + #include "../../model/doc/document.hpp" -#include "../../model/world/tablemimedata.hpp" #include "../../model/world/commands.hpp" +#include "../../model/world/tablemimedata.hpp" + +#include +#include #include "dragdroputils.hpp" -void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTable& table) +void CSVWorld::DragRecordTable::startDragFromTable(const CSVWorld::DragRecordTable& table, const QModelIndex& index) { std::vector records = table.getDraggedRecords(); if (records.empty()) @@ -18,36 +24,39 @@ void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTa return; } - CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument); - QDrag* drag = new QDrag (this); - drag->setMimeData (mime); - drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); - drag->exec (Qt::CopyAction); + CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData(records, mDocument); + mime->setTableOfDragStart(&table); + mime->setIndexAtDragStart(index); + QDrag* drag = new QDrag(this); + drag->setMimeData(mime); + drag->setPixmap(QString::fromUtf8(mime->getIcon().c_str())); + drag->exec(Qt::CopyAction); } -CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) : -QTableView(parent), -mDocument(document), -mEditLock(false) +CSVWorld::DragRecordTable::DragRecordTable(CSMDoc::Document& document, QWidget* parent) + : QTableView(parent) + , mDocument(document) + , mEditLock(false) { setAcceptDrops(true); } -void CSVWorld::DragRecordTable::setEditLock (bool locked) +void CSVWorld::DragRecordTable::setEditLock(bool locked) { mEditLock = locked; } -void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event) +void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent* event) { event->acceptProposedAction(); } -void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) +void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent* event) { QModelIndex index = indexAt(event->pos()); - if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index)) || - CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index)) ) + if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index)) + || CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index)) + || CSVWorld::DragDropUtils::isTopicOrJournal(*event, getIndexDisplayType(index))) { if (index.flags() & Qt::ItemIsEditable) { @@ -58,13 +67,13 @@ void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) event->ignore(); } -void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) +void CSVWorld::DragRecordTable::dropEvent(QDropEvent* event) { QModelIndex index = indexAt(event->pos()); CSMWorld::ColumnBase::Display display = getIndexDisplayType(index); if (CSVWorld::DragDropUtils::canAcceptData(*event, display)) { - const CSMWorld::TableMimeData *tableMimeData = CSVWorld::DragDropUtils::getTableMimeData(*event); + const CSMWorld::TableMimeData* tableMimeData = CSVWorld::DragDropUtils::getTableMimeData(*event); if (tableMimeData->fromDocument(mDocument)) { CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, display); @@ -80,9 +89,17 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) { emit moveRecordsFromSameTable(event); } + if (CSVWorld::DragDropUtils::isTopicOrJournal(*event, display)) + { + const CSMWorld::TableMimeData* tableMimeData = CSVWorld::DragDropUtils::getTableMimeData(*event); + for (auto universalId : tableMimeData->getData()) + { + emit createNewInfoRecord(universalId.getId()); + } + } } -CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const +CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex& index) const { Q_ASSERT(model() != nullptr); @@ -96,3 +113,12 @@ CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(con } return CSMWorld::ColumnBase::Display_None; } + +int CSVWorld::DragRecordTable::sizeHintForColumn(int column) const +{ + // Prevent the column width from getting too long or too short + constexpr int minWidth = 100; + constexpr int maxWidth = 300; + int width = QTableView::sizeHintForColumn(column); + return std::clamp(width, minWidth, maxWidth); +} diff --git a/apps/opencs/view/world/dragrecordtable.hpp b/apps/opencs/view/world/dragrecordtable.hpp index f6c3fa890..8653c2747 100644 --- a/apps/opencs/view/world/dragrecordtable.hpp +++ b/apps/opencs/view/world/dragrecordtable.hpp @@ -2,12 +2,17 @@ #define CSV_WORLD_DRAGRECORDTABLE_H #include -#include + +#include #include "../../model/world/columnbase.hpp" +class QDragEnterEvent; +class QDragMoveEvent; +class QDropEvent; +class QModelIndex; +class QObject; class QWidget; -class QAction; namespace CSMDoc { @@ -25,33 +30,35 @@ namespace CSVWorld { Q_OBJECT - protected: - CSMDoc::Document& mDocument; - bool mEditLock; + protected: + CSMDoc::Document& mDocument; + bool mEditLock; - public: - DragRecordTable(CSMDoc::Document& document, QWidget* parent = nullptr); + public: + DragRecordTable(CSMDoc::Document& document, QWidget* parent = nullptr); - virtual std::vector getDraggedRecords() const = 0; + virtual std::vector getDraggedRecords() const = 0; - void setEditLock(bool locked); + void setEditLock(bool locked); - protected: - void startDragFromTable(const DragRecordTable& table); + protected: + void startDragFromTable(const DragRecordTable& table, const QModelIndex& index); - void dragEnterEvent(QDragEnterEvent *event) override; + void dragEnterEvent(QDragEnterEvent* event) override; - void dragMoveEvent(QDragMoveEvent *event) override; + void dragMoveEvent(QDragMoveEvent* event) override; - void dropEvent(QDropEvent *event) override; + void dropEvent(QDropEvent* event) override; - private: - CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const; + int sizeHintForColumn(int column) const override; - signals: - void moveRecordsFromSameTable(QDropEvent *event); + private: + CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex& index) const; + + signals: + void moveRecordsFromSameTable(QDropEvent* event); + void createNewInfoRecord(const std::string& id); }; } #endif - diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 65ded46c7..188860e2a 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -1,15 +1,16 @@ #include "enumdelegate.hpp" #include -#include +#include -#include #include -#include +#include #include "../../model/world/commands.hpp" -int CSVWorld::EnumDelegate::getValueIndex(const QModelIndex &index, int role) const +#include + +int CSVWorld::EnumDelegate::getValueIndex(const QModelIndex& index, int role) const { if (index.isValid() && index.data(role).isValid()) { @@ -27,64 +28,60 @@ int CSVWorld::EnumDelegate::getValueIndex(const QModelIndex &index, int role) co return -1; } -void CSVWorld::EnumDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, - const QModelIndex& index) const +void CSVWorld::EnumDelegate::setModelDataImp(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { - if (QComboBox *comboBox = dynamic_cast (editor)) + if (QComboBox* comboBox = dynamic_cast(editor)) { QString value = comboBox->currentText(); - for (std::vector >::const_iterator iter (mValues.begin()); - iter!=mValues.end(); ++iter) - if (iter->second==value) + for (std::vector>::const_iterator iter(mValues.begin()); iter != mValues.end(); ++iter) + if (iter->second == value) { // do nothing if the value has not changed if (model->data(index).toInt() != iter->first) - addCommands (model, index, iter->first); + addCommands(model, index, iter->first); break; } } } -void CSVWorld::EnumDelegate::addCommands (QAbstractItemModel *model, - const QModelIndex& index, int type) const +void CSVWorld::EnumDelegate::addCommands(QAbstractItemModel* model, const QModelIndex& index, int type) const { - getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, type)); + getUndoStack().push(new CSMWorld::ModifyCommand(*model, index, type)); } - -CSVWorld::EnumDelegate::EnumDelegate (const std::vector >& values, - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) -: CommandDelegate (dispatcher, document, parent), mValues (values) +CSVWorld::EnumDelegate::EnumDelegate(const std::vector>& values, + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) + : CommandDelegate(dispatcher, document, parent) + , mValues(values) { - } -QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, - const QStyleOptionViewItem& option, - const QModelIndex& index) const +QWidget* CSVWorld::EnumDelegate::createEditor( + QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_None); } -QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option, +QWidget* CSVWorld::EnumDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { if (!index.data(Qt::EditRole).isValid() && !index.data(Qt::DisplayRole).isValid()) return nullptr; - QComboBox *comboBox = new QComboBox (parent); + QComboBox* comboBox = new QComboBox(parent); - for (std::vector >::const_iterator iter (mValues.begin()); - iter!=mValues.end(); ++iter) - comboBox->addItem (iter->second); + for (std::vector>::const_iterator iter(mValues.begin()); iter != mValues.end(); ++iter) + comboBox->addItem(iter->second); + + comboBox->setMaxVisibleItems(20); return comboBox; } -void CSVWorld::EnumDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const +void CSVWorld::EnumDelegate::setEditorData(QWidget* editor, const QModelIndex& index, bool tryDisplay) const { - if (QComboBox *comboBox = dynamic_cast(editor)) + if (QComboBox* comboBox = dynamic_cast(editor)) { int role = Qt::EditRole; if (tryDisplay && !index.data(role).isValid()) @@ -104,8 +101,8 @@ void CSVWorld::EnumDelegate::setEditorData (QWidget *editor, const QModelIndex& } } -void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const +void CSVWorld::EnumDelegate::paint( + QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { int valueIndex = getValueIndex(index); if (valueIndex != -1) @@ -116,7 +113,7 @@ void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewIte } } -QSize CSVWorld::EnumDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +QSize CSVWorld::EnumDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { int valueIndex = getValueIndex(index); if (valueIndex != -1) @@ -129,7 +126,7 @@ QSize CSVWorld::EnumDelegate::sizeHint(const QStyleOptionViewItem &option, const itemOption.rect = option.rect; itemOption.state = option.state; - const QString &valueText = mValues.at(valueIndex).second; + const QString& valueText = mValues.at(valueIndex).second; QSize valueSize = QSize(itemOption.fontMetrics.horizontalAdvance(valueText), itemOption.fontMetrics.height()); itemOption.currentText = valueText; return QApplication::style()->sizeFromContents(QStyle::CT_ComboBox, &itemOption, valueSize); @@ -137,42 +134,40 @@ QSize CSVWorld::EnumDelegate::sizeHint(const QStyleOptionViewItem &option, const return option.rect.size(); } -CSVWorld::EnumDelegateFactory::EnumDelegateFactory() {} - -CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const char **names, bool allowNone) +CSVWorld::EnumDelegateFactory::EnumDelegateFactory(const char** names, bool allowNone) { - assert (names); + assert(names); if (allowNone) - add (-1, ""); + add(-1, ""); - for (int i=0; names[i]; ++i) - add (i, names[i]); + for (int i = 0; names[i]; ++i) + add(i, names[i]); } -CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const std::vector>& names, - bool allowNone) +CSVWorld::EnumDelegateFactory::EnumDelegateFactory( + const std::vector>& names, bool allowNone) { if (allowNone) - add (-1, ""); + add(-1, ""); - int size = static_cast (names.size()); + int size = static_cast(names.size()); - for (int i=0; isecond > name) { @@ -181,5 +176,5 @@ void CSVWorld::EnumDelegateFactory::add (int value, const QString& name) } } - mValues.emplace_back (value, name); + mValues.emplace_back(value, name); } diff --git a/apps/opencs/view/world/enumdelegate.hpp b/apps/opencs/view/world/enumdelegate.hpp index 91326e2c0..e80b61fa8 100644 --- a/apps/opencs/view/world/enumdelegate.hpp +++ b/apps/opencs/view/world/enumdelegate.hpp @@ -1,80 +1,80 @@ #ifndef CSV_WORLD_ENUMDELEGATE_H #define CSV_WORLD_ENUMDELEGATE_H +#include +#include #include #include -#include -#include +#include #include "util.hpp" +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + namespace CSVWorld { /// \brief Integer value that represents an enum and is interacted with via a combobox class EnumDelegate : public CommandDelegate { - protected: + protected: + std::vector> mValues; - std::vector > mValues; + int getValueIndex(const QModelIndex& index, int role = Qt::DisplayRole) const; - int getValueIndex(const QModelIndex &index, int role = Qt::DisplayRole) const; + private: + void setModelDataImp(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; - private: + virtual void addCommands(QAbstractItemModel* model, const QModelIndex& index, int type) const; - void setModelDataImp (QWidget *editor, QAbstractItemModel *model, - const QModelIndex& index) const override; + public: + EnumDelegate(const std::vector>& values, CSMWorld::CommandDispatcher* dispatcher, + CSMDoc::Document& document, QObject* parent); - virtual void addCommands (QAbstractItemModel *model, - const QModelIndex& index, int type) const; + QWidget* createEditor( + QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - public: + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index, + CSMWorld::ColumnBase::Display display = CSMWorld::ColumnBase::Display_None) const override; - EnumDelegate (const std::vector >& values, - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent); + void setEditorData(QWidget* editor, const QModelIndex& index, bool tryDisplay = false) const override; - QWidget *createEditor(QWidget *parent, - const QStyleOptionViewItem& option, - const QModelIndex& index) const override; - - QWidget *createEditor(QWidget *parent, - const QStyleOptionViewItem& option, - const QModelIndex& index, - CSMWorld::ColumnBase::Display display = CSMWorld::ColumnBase::Display_None) const override; - - void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay = false) const override; - - void paint (QPainter *painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; - - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; class EnumDelegateFactory : public CommandDelegateFactory { - protected: - std::vector > mValues; + protected: + std::vector> mValues; - public: + public: + EnumDelegateFactory() = default; - EnumDelegateFactory(); + EnumDelegateFactory(const char** names, bool allowNone = false); + ///< \param names Array of char pointer with a 0-pointer as end mark + /// \param allowNone Use value of -1 for "none selected" (empty string) - EnumDelegateFactory (const char **names, bool allowNone = false); - ///< \param names Array of char pointer with a 0-pointer as end mark - /// \param allowNone Use value of -1 for "none selected" (empty string) + EnumDelegateFactory(const std::vector>& names, bool allowNone = false); + /// \param allowNone Use value of -1 for "none selected" (empty string) - EnumDelegateFactory (const std::vector>& names, bool allowNone = false); - /// \param allowNone Use value of -1 for "none selected" (empty string) + CommandDelegate* makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const override; + ///< The ownership of the returned CommandDelegate is transferred to the caller. - CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; - ///< The ownership of the returned CommandDelegate is transferred to the caller. - - void add (int value, const QString& name); + void add(int value, const QString& name); }; - } #endif diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 894742024..69659be8a 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -1,48 +1,50 @@ #include "extendedcommandconfigurator.hpp" #include +#include +#include -#include -#include #include -#include +#include #include +#include #include "../../model/doc/document.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/world/data.hpp" -CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, - const CSMWorld::UniversalId &id, - QWidget *parent) - : QWidget(parent), - mNumUsedCheckBoxes(0), - mNumChecked(0), - mMode(Mode_None), - mData(document.getData()), - mEditLock(false) +#include + +CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget* parent) + : QWidget(parent) + , mNumUsedCheckBoxes(0) + , mNumChecked(0) + , mMode(Mode_None) + , mData(document.getData()) + , mEditLock(false) { mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); - connect(&mData, SIGNAL(idListChanged()), this, SLOT(dataIdListChanged())); + connect(&mData, &CSMWorld::Data::idListChanged, this, &ExtendedCommandConfigurator::dataIdListChanged); mPerformButton = new QPushButton(this); mPerformButton->setDefault(true); mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - connect(mPerformButton, SIGNAL(clicked(bool)), this, SLOT(performExtendedCommand())); + connect(mPerformButton, &QPushButton::clicked, this, &ExtendedCommandConfigurator::performExtendedCommand); mCancelButton = new QPushButton("Cancel", this); mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - connect(mCancelButton, SIGNAL(clicked(bool)), this, SIGNAL(done())); + connect(mCancelButton, &QPushButton::clicked, this, &ExtendedCommandConfigurator::done); mTypeGroup = new QGroupBox(this); - QGridLayout *groupLayout = new QGridLayout(mTypeGroup); + QGridLayout* groupLayout = new QGridLayout(mTypeGroup); groupLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); mTypeGroup->setLayout(groupLayout); - QHBoxLayout *mainLayout = new QHBoxLayout(this); + QHBoxLayout* mainLayout = new QHBoxLayout(this); mainLayout->setSizeConstraint(QLayout::SetNoConstraint); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addWidget(mTypeGroup); @@ -50,8 +52,8 @@ CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Docum mainLayout->addWidget(mCancelButton); } -void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode, - const std::vector &selectedIds) +void CSVWorld::ExtendedCommandConfigurator::configure( + CSVWorld::ExtendedCommandConfigurator::Mode mode, const std::vector& selectedIds) { mMode = mode; if (mMode != Mode_None) @@ -75,7 +77,7 @@ void CSVWorld::ExtendedCommandConfigurator::setEditLock(bool locked) } } -void CSVWorld::ExtendedCommandConfigurator::resizeEvent(QResizeEvent *event) +void CSVWorld::ExtendedCommandConfigurator::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); setupGroupLayout(); @@ -89,7 +91,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() } int groupWidth = mTypeGroup->geometry().width(); - QGridLayout *layout = qobject_cast(mTypeGroup->layout()); + QGridLayout* layout = qobject_cast(mTypeGroup->layout()); // Find the optimal number of rows to place the checkboxes within the available space int divider = 1; @@ -115,21 +117,20 @@ void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() ++counter; } divider *= 2; - } - while (groupWidth < mTypeGroup->sizeHint().width() && divider <= mNumUsedCheckBoxes); + } while (groupWidth < mTypeGroup->sizeHint().width() && divider <= mNumUsedCheckBoxes); } -void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector &types) +void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector& types) { // Make sure that we have enough checkboxes - int numTypes = static_cast(types.size()); + int numTypes = static_cast(types.size()); int numCheckBoxes = static_cast(mTypeCheckBoxes.size()); if (numTypes > numCheckBoxes) { for (int i = numTypes - numCheckBoxes; i > 0; --i) { - QCheckBox *checkBox = new QCheckBox(mTypeGroup); - connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxStateChanged(int))); + QCheckBox* checkBox = new QCheckBox(mTypeGroup); + connect(checkBox, &QCheckBox::stateChanged, this, &ExtendedCommandConfigurator::checkBoxStateChanged); mTypeCheckBoxes.insert(std::make_pair(checkBox, CSMWorld::UniversalId::Type_None)); } } @@ -171,7 +172,7 @@ void CSVWorld::ExtendedCommandConfigurator::lockWidgets(bool locked) void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() { std::vector types; - + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); for (; current != end; ++current) diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index 85862ac49..e441cc393 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -2,6 +2,7 @@ #define CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP #include +#include #include @@ -10,8 +11,7 @@ class QPushButton; class QGroupBox; class QCheckBox; -class QLabel; -class QHBoxLayout; +class QResizeEvent; namespace CSMDoc { @@ -28,50 +28,54 @@ namespace CSVWorld { class ExtendedCommandConfigurator : public QWidget { - Q_OBJECT + Q_OBJECT - public: - enum Mode { Mode_None, Mode_Delete, Mode_Revert }; - - private: - typedef std::map CheckBoxMap; + public: + enum Mode + { + Mode_None, + Mode_Delete, + Mode_Revert + }; - QPushButton *mPerformButton; - QPushButton *mCancelButton; - QGroupBox *mTypeGroup; - CheckBoxMap mTypeCheckBoxes; - int mNumUsedCheckBoxes; - int mNumChecked; + private: + typedef std::map CheckBoxMap; - Mode mMode; - CSMWorld::CommandDispatcher *mCommandDispatcher; - CSMWorld::Data &mData; - std::vector mSelectedIds; - - bool mEditLock; + QPushButton* mPerformButton; + QPushButton* mCancelButton; + QGroupBox* mTypeGroup; + CheckBoxMap mTypeCheckBoxes; + int mNumUsedCheckBoxes; + int mNumChecked; - void setupGroupLayout(); - void setupCheckBoxes(const std::vector &types); - void lockWidgets(bool locked); + Mode mMode; + CSMWorld::CommandDispatcher* mCommandDispatcher; + CSMWorld::Data& mData; + std::vector mSelectedIds; - public: - ExtendedCommandConfigurator(CSMDoc::Document &document, - const CSMWorld::UniversalId &id, - QWidget *parent = nullptr); + bool mEditLock; - void configure(Mode mode, const std::vector &selectedIds); - void setEditLock(bool locked); + void setupGroupLayout(); + void setupCheckBoxes(const std::vector& types); + void lockWidgets(bool locked); - protected: - void resizeEvent(QResizeEvent *event) override; + public: + ExtendedCommandConfigurator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget* parent = nullptr); - private slots: - void performExtendedCommand(); - void checkBoxStateChanged(int state); - void dataIdListChanged(); + void configure(Mode mode, const std::vector& selectedIds); + void setEditLock(bool locked); - signals: - void done(); + protected: + void resizeEvent(QResizeEvent* event) override; + + private slots: + void performExtendedCommand(); + void checkBoxStateChanged(int state); + void dataIdListChanged(); + + signals: + void done(); }; } diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index 23813f806..e4c084e46 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -1,15 +1,18 @@ #include "genericcreator.hpp" #include +#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include -#include +#include + +#include #include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" @@ -21,25 +24,25 @@ void CSVWorld::GenericCreator::update() { mErrors = getErrors(); - mCreate->setToolTip (QString::fromUtf8 (mErrors.c_str())); - mId->setToolTip (QString::fromUtf8 (mErrors.c_str())); + mCreate->setToolTip(QString::fromUtf8(mErrors.c_str())); + mId->setToolTip(QString::fromUtf8(mErrors.c_str())); - mCreate->setEnabled (mErrors.empty() && !mLocked); + mCreate->setEnabled(mErrors.empty() && !mLocked); } -void CSVWorld::GenericCreator::setManualEditing (bool enabled) +void CSVWorld::GenericCreator::setManualEditing(bool enabled) { - mId->setVisible (enabled); + mId->setVisible(enabled); } -void CSVWorld::GenericCreator::insertAtBeginning (QWidget *widget, bool stretched) +void CSVWorld::GenericCreator::insertAtBeginning(QWidget* widget, bool stretched) { - mLayout->insertWidget (0, widget, stretched ? 1 : 0); + mLayout->insertWidget(0, widget, stretched ? 1 : 0); } -void CSVWorld::GenericCreator::insertBeforeButtons (QWidget *widget, bool stretched) +void CSVWorld::GenericCreator::insertBeforeButtons(QWidget* widget, bool stretched) { - mLayout->insertWidget (mLayout->count()-2, widget, stretched ? 1 : 0); + mLayout->insertWidget(mLayout->count() - 2, widget, stretched ? 1 : 0); // Reset tab order relative to buttons. setTabOrder(widget, mCreate); @@ -66,12 +69,11 @@ std::string CSVWorld::GenericCreator::getIdValidatorResult() const return errors; } -void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {} +void CSVWorld::GenericCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const {} -void CSVWorld::GenericCreator::pushCommand (std::unique_ptr command, - const std::string& id) +void CSVWorld::GenericCreator::pushCommand(std::unique_ptr command, const std::string& id) { - mUndoStack.push (command.release()); + mUndoStack.push(command.release()); } CSMWorld::Data& CSVWorld::GenericCreator::getData() const @@ -95,7 +97,7 @@ std::string CSVWorld::GenericCreator::getNamespace() const if (mScope) { - scope = static_cast (mScope->itemData (mScope->currentIndex()).toInt()); + scope = static_cast(mScope->itemData(mScope->currentIndex()).toInt()); } else { @@ -107,9 +109,12 @@ std::string CSVWorld::GenericCreator::getNamespace() const switch (scope) { - case CSMWorld::Scope_Content: return ""; - case CSMWorld::Scope_Project: return "project::"; - case CSMWorld::Scope_Session: return "session::"; + case CSMWorld::Scope_Content: + return ""; + case CSMWorld::Scope_Project: + return "project::"; + case CSMWorld::Scope_Session: + return "session::"; } return ""; @@ -119,37 +124,41 @@ void CSVWorld::GenericCreator::updateNamespace() { std::string namespace_ = getNamespace(); - mValidator->setNamespace (namespace_); + mValidator->setNamespace(namespace_); - int index = mId->text().indexOf ("::"); + int index = mId->text().indexOf("::"); - if (index==-1) + if (index == -1) { // no namespace in old text - mId->setText (QString::fromUtf8 (namespace_.c_str()) + mId->text()); + mId->setText(QString::fromUtf8(namespace_.c_str()) + mId->text()); } else { - std::string oldNamespace = - Misc::StringUtils::lowerCase (mId->text().left (index).toUtf8().constData()); + std::string oldNamespace = Misc::StringUtils::lowerCase(mId->text().left(index).toUtf8().constData()); - if (oldNamespace=="project" || oldNamespace=="session") - mId->setText (QString::fromUtf8 (namespace_.c_str()) + mId->text().mid (index+2)); + if (oldNamespace == "project" || oldNamespace == "session") + mId->setText(QString::fromUtf8(namespace_.c_str()) + mId->text().mid(index + 2)); } } -void CSVWorld::GenericCreator::addScope (const QString& name, CSMWorld::Scope scope, - const QString& tooltip) +void CSVWorld::GenericCreator::addScope(const QString& name, CSMWorld::Scope scope, const QString& tooltip) { - mScope->addItem (name, static_cast (scope)); - mScope->setItemData (mScope->count()-1, tooltip, Qt::ToolTipRole); + mScope->addItem(name, static_cast(scope)); + mScope->setItemData(mScope->count() - 1, tooltip, Qt::ToolTipRole); } -CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, bool relaxedIdRules) -: mData (data), mUndoStack (undoStack), mListId (id), mLocked (false), - mClonedType (CSMWorld::UniversalId::Type_None), mScopes (CSMWorld::Scope_Content), mScope (nullptr), - mScopeLabel (nullptr), mCloneMode (false) +CSVWorld::GenericCreator::GenericCreator( + CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, bool relaxedIdRules) + : mData(data) + , mUndoStack(undoStack) + , mListId(id) + , mLocked(false) + , mClonedType(CSMWorld::UniversalId::Type_None) + , mScopes(CSMWorld::Scope_Content) + , mScope(nullptr) + , mScopeLabel(nullptr) + , mCloneMode(false) { // If the collection ID has a parent type, use it instead. // It will change IDs with Record/SubRecord class (used for creators in Dialogue subviews) @@ -161,30 +170,35 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo } mLayout = new QHBoxLayout; - mLayout->setContentsMargins (0, 0, 0, 0); + mLayout->setContentsMargins(0, 0, 0, 0); mId = new QLineEdit; - mId->setValidator (mValidator = new IdValidator (relaxedIdRules, this)); - mLayout->addWidget (mId, 1); + mId->setValidator(mValidator = new IdValidator(relaxedIdRules, this)); + mLayout->addWidget(mId, 1); - mCreate = new QPushButton ("Create"); - mLayout->addWidget (mCreate); + mCreate = new QPushButton("Create"); + mLayout->addWidget(mCreate); mCancel = new QPushButton("Cancel"); mLayout->addWidget(mCancel); - setLayout (mLayout); + setLayout(mLayout); - connect (mCancel, SIGNAL (clicked (bool)), this, SIGNAL (done())); - connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create())); + connect(mCancel, &QPushButton::clicked, this, &GenericCreator::done); + connect(mCreate, &QPushButton::clicked, this, &GenericCreator::create); - connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); - connect (mId, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); + connect(mId, &QLineEdit::textChanged, this, &GenericCreator::textChanged); + connect(mId, &QLineEdit::returnPressed, this, &GenericCreator::inputReturnPressed); - connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); + connect(&mData, &CSMWorld::Data::idListChanged, this, &GenericCreator::dataIdListChanged); } -void CSVWorld::GenericCreator::setEditLock (bool locked) +void CSVWorld::GenericCreator::setEditorMaxLength(int length) +{ + mId->setMaxLength(length); +} + +void CSVWorld::GenericCreator::setEditLock(bool locked) { mLocked = locked; update(); @@ -193,7 +207,7 @@ void CSVWorld::GenericCreator::setEditLock (bool locked) void CSVWorld::GenericCreator::reset() { mCloneMode = false; - mId->setText (""); + mId->setText(""); update(); updateNamespace(); } @@ -204,13 +218,13 @@ std::string CSVWorld::GenericCreator::getErrors() const if (!mId->hasAcceptableInput()) errors = mValidator->getError(); - else if (mData.hasId (getId())) + else if (mData.hasId(getId())) errors = "ID is already in use"; return errors; } -void CSVWorld::GenericCreator::textChanged (const QString& text) +void CSVWorld::GenericCreator::textChanged(const QString& text) { update(); } @@ -233,26 +247,24 @@ void CSVWorld::GenericCreator::create() if (mCloneMode) { - command.reset (new CSMWorld::CloneCommand ( - dynamic_cast (*mData.getTableModel(mListId)), mClonedId, id, mClonedType)); + command = std::make_unique( + dynamic_cast(*mData.getTableModel(mListId)), mClonedId, id, mClonedType); } else { - command.reset (new CSMWorld::CreateCommand ( - dynamic_cast (*mData.getTableModel (mListId)), id)); - + command = std::make_unique( + dynamic_cast(*mData.getTableModel(mListId)), id); } - configureCreateCommand (*command); - pushCommand (std::move(command), id); + configureCreateCommand(*command); + pushCommand(std::move(command), id); emit done(); emit requestFocus(id); } } -void CSVWorld::GenericCreator::cloneMode(const std::string& originId, - const CSMWorld::UniversalId::Type type) +void CSVWorld::GenericCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { mCloneMode = true; mClonedId = originId; @@ -275,49 +287,46 @@ void CSVWorld::GenericCreator::touch(const std::vector& i mUndoStack.endMacro(); } -void CSVWorld::GenericCreator::toggleWidgets(bool active) -{ -} +void CSVWorld::GenericCreator::toggleWidgets(bool active) {} void CSVWorld::GenericCreator::focus() { mId->setFocus(); } -void CSVWorld::GenericCreator::setScope (unsigned int scope) +void CSVWorld::GenericCreator::setScope(unsigned int scope) { mScopes = scope; - int count = (mScopes & CSMWorld::Scope_Content) + (mScopes & CSMWorld::Scope_Project) + - (mScopes & CSMWorld::Scope_Session); + int count = (mScopes & CSMWorld::Scope_Content) + (mScopes & CSMWorld::Scope_Project) + + (mScopes & CSMWorld::Scope_Session); // scope selector widget - if (count>1) + if (count > 1) { - mScope = new QComboBox (this); - insertAtBeginning (mScope, false); + mScope = new QComboBox(this); + insertAtBeginning(mScope, false); if (mScopes & CSMWorld::Scope_Content) - addScope ("Content", CSMWorld::Scope_Content, - "Record will be stored in the currently edited content file."); + addScope("Content", CSMWorld::Scope_Content, "Record will be stored in the currently edited content file."); if (mScopes & CSMWorld::Scope_Project) - addScope ("Project", CSMWorld::Scope_Project, + addScope("Project", CSMWorld::Scope_Project, "Record will be stored in a local project file.

        " "Record will be created in the reserved namespace \"project\".

        " "Record is available when running OpenMW via OpenCS."); if (mScopes & CSMWorld::Scope_Session) - addScope ("Session", CSMWorld::Scope_Session, + addScope("Session", CSMWorld::Scope_Session, "Record exists only for the duration of the current editing session.

        " "Record will be created in the reserved namespace \"session\".

        " "Record is not available when running OpenMW via OpenCS."); - connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (scopeChanged (int))); + connect(mScope, qOverload(&QComboBox::currentIndexChanged), this, &GenericCreator::scopeChanged); - mScopeLabel = new QLabel ("Scope", this); - insertAtBeginning (mScopeLabel, false); + mScopeLabel = new QLabel("Scope", this); + insertAtBeginning(mScopeLabel, false); - mScope->setCurrentIndex (0); + mScope->setCurrentIndex(0); } else { @@ -331,7 +340,7 @@ void CSVWorld::GenericCreator::setScope (unsigned int scope) updateNamespace(); } -void CSVWorld::GenericCreator::scopeChanged (int index) +void CSVWorld::GenericCreator::scopeChanged(int index) { update(); updateNamespace(); diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 3e2a43c91..a39b530ef 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -1,13 +1,18 @@ #ifndef CSV_WORLD_GENERICCREATOR_H #define CSV_WORLD_GENERICCREATOR_H +#include + #include +#include +#include + +#include #include "../../model/world/universalid.hpp" #include "creator.hpp" -class QString; class QPushButton; class QLineEdit; class QHBoxLayout; @@ -27,107 +32,105 @@ namespace CSVWorld class GenericCreator : public Creator { - Q_OBJECT + Q_OBJECT - CSMWorld::Data& mData; - QUndoStack& mUndoStack; - CSMWorld::UniversalId mListId; - QPushButton *mCreate; - QPushButton *mCancel; - QLineEdit *mId; - std::string mErrors; - QHBoxLayout *mLayout; - bool mLocked; - std::string mClonedId; - CSMWorld::UniversalId::Type mClonedType; - unsigned int mScopes; - QComboBox *mScope; - QLabel *mScopeLabel; - IdValidator *mValidator; + CSMWorld::Data& mData; + QUndoStack& mUndoStack; + CSMWorld::UniversalId mListId; + QPushButton* mCreate; + QPushButton* mCancel; + QLineEdit* mId; + std::string mErrors; + QHBoxLayout* mLayout; + bool mLocked; + std::string mClonedId; + CSMWorld::UniversalId::Type mClonedType; + unsigned int mScopes; + QComboBox* mScope; + QLabel* mScopeLabel; + IdValidator* mValidator; - protected: - bool mCloneMode; + protected: + bool mCloneMode; - protected: + protected: + void update(); - void update(); + virtual void setManualEditing(bool enabled); + ///< Enable/disable manual ID editing (enabled by default). - virtual void setManualEditing (bool enabled); - ///< Enable/disable manual ID editing (enabled by default). + void insertAtBeginning(QWidget* widget, bool stretched); - void insertAtBeginning (QWidget *widget, bool stretched); + /// \brief Insert given widget before Create and Cancel buttons. + /// \param widget Widget to add to layout. + /// \param stretched Whether widget should be streched or not. + void insertBeforeButtons(QWidget* widget, bool stretched); - /// \brief Insert given widget before Create and Cancel buttons. - /// \param widget Widget to add to layout. - /// \param stretched Whether widget should be streched or not. - void insertBeforeButtons (QWidget *widget, bool stretched); + virtual std::string getId() const; - virtual std::string getId() const; + std::string getClonedId() const; - std::string getClonedId() const; + virtual std::string getIdValidatorResult() const; - virtual std::string getIdValidatorResult() const; + /// Allow subclasses to add additional data to \a command. + virtual void configureCreateCommand(CSMWorld::CreateCommand& command) const; - /// Allow subclasses to add additional data to \a command. - virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + /// Allow subclasses to wrap the create command together with additional commands + /// into a macro. + virtual void pushCommand(std::unique_ptr command, const std::string& id); - /// Allow subclasses to wrap the create command together with additional commands - /// into a macro. - virtual void pushCommand (std::unique_ptr command, - const std::string& id); + CSMWorld::Data& getData() const; - CSMWorld::Data& getData() const; + QUndoStack& getUndoStack(); - QUndoStack& getUndoStack(); + const CSMWorld::UniversalId& getCollectionId() const; - const CSMWorld::UniversalId& getCollectionId() const; + std::string getNamespace() const; - std::string getNamespace() const; + void setEditorMaxLength(int length); - private: + private: + void updateNamespace(); - void updateNamespace(); + void addScope(const QString& name, CSMWorld::Scope scope, const QString& tooltip); - void addScope (const QString& name, CSMWorld::Scope scope, - const QString& tooltip); + public: + GenericCreator( + CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, bool relaxedIdRules = false); - public: + void setEditLock(bool locked) override; - GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, bool relaxedIdRules = false); + void reset() override; - void setEditLock (bool locked) override; + void toggleWidgets(bool active = true) override; - void reset() override; + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - void toggleWidgets (bool active = true) override; + void touch(const std::vector& ids) override; - void cloneMode(const std::string& originId, - const CSMWorld::UniversalId::Type type) override; + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. - void touch(const std::vector& ids) override; + void setScope(unsigned int scope) override; - virtual std::string getErrors() const; - ///< Return formatted error descriptions for the current state of the creator. if an empty - /// string is returned, there is no error. + /// Focus main input widget + void focus() override; - void setScope (unsigned int scope) override; + protected slots: - /// Focus main input widget - void focus() override; + /// \brief Create record if able to after Return key is pressed on input. + void inputReturnPressed(); - private slots: + private slots: - void textChanged (const QString& text); + void textChanged(const QString& text); - /// \brief Create record if able to after Return key is pressed on input. - void inputReturnPressed(); + void create(); - void create(); + void scopeChanged(int index); - void scopeChanged (int index); - - void dataIdListChanged(); + void dataIdListChanged(); }; } diff --git a/apps/opencs/view/world/globalcreator.cpp b/apps/opencs/view/world/globalcreator.cpp index c7b140e15..43c121059 100644 --- a/apps/opencs/view/world/globalcreator.cpp +++ b/apps/opencs/view/world/globalcreator.cpp @@ -1,15 +1,19 @@ #include "globalcreator.hpp" -#include +#include +#include +#include + +#include -#include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" -#include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" +class QUndoStack; + namespace CSVWorld { - void GlobalCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const + void GlobalCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const { CSMWorld::IdTable* table = static_cast(getData().getTableModel(getCollectionId())); @@ -20,7 +24,7 @@ namespace CSVWorld } GlobalCreator::GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) - : GenericCreator (data, undoStack, id, true) + : GenericCreator(data, undoStack, id, true) { } } diff --git a/apps/opencs/view/world/globalcreator.hpp b/apps/opencs/view/world/globalcreator.hpp index 057798a4c..81f4c7c88 100644 --- a/apps/opencs/view/world/globalcreator.hpp +++ b/apps/opencs/view/world/globalcreator.hpp @@ -3,19 +3,28 @@ #include "genericcreator.hpp" +class QObject; +class QUndoStack; + +#include + +namespace CSMWorld +{ + class CreateCommand; + class Data; +} + namespace CSVWorld { class GlobalCreator : public GenericCreator { - Q_OBJECT + Q_OBJECT - public: + public: + GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); - GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); - - protected: - - void configureCreateCommand(CSMWorld::CreateCommand& command) const override; + protected: + void configureCreateCommand(CSMWorld::CreateCommand& command) const override; }; } diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 447bcc25d..3e26ed925 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -3,25 +3,35 @@ #include "../../model/world/idcompletionmanager.hpp" #include "../../model/world/infoselectwrapper.hpp" +#include +#include + +#include + #include "../widget/droplineedit.hpp" -CSVWorld::IdCompletionDelegate::IdCompletionDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, - QObject *parent) - : CommandDelegate(dispatcher, document, parent) -{} +namespace CSMWorld +{ + class CommandDispatcher; +} -QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, - const QStyleOptionViewItem &option, - const QModelIndex &index) const +class QObject; +class QWidget; + +CSVWorld::IdCompletionDelegate::IdCompletionDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) + : CommandDelegate(dispatcher, document, parent) +{ +} + +QWidget* CSVWorld::IdCompletionDelegate::createEditor( + QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { return createEditor(parent, option, index, getDisplayTypeFromIndex(index)); } -QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, - const QStyleOptionViewItem &option, - const QModelIndex &index, - CSMWorld::ColumnBase::Display display) const +QWidget* CSVWorld::IdCompletionDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, + const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { if (!index.data(Qt::EditRole).isValid() && !index.data(Qt::DisplayRole).isValid()) { @@ -38,55 +48,70 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, { case CSMWorld::ConstInfoSelectWrapper::Function_Global: { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); } case CSMWorld::ConstInfoSelectWrapper::Function_Journal: { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Journal); + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Journal); } case CSMWorld::ConstInfoSelectWrapper::Function_Item: { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } case CSMWorld::ConstInfoSelectWrapper::Function_Dead: case CSMWorld::ConstInfoSelectWrapper::Function_NotId: { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Faction); + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Faction); } case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Class); + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Class); } case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Race); + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Race); } case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Cell); + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell); } case CSMWorld::ConstInfoSelectWrapper::Function_Local: case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: { return new CSVWidget::DropLineEdit(display, parent); } - default: return nullptr; // The rest of them can't be edited anyway + default: + return nullptr; // The rest of them can't be edited anyway } } - CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); - CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); + CSMWorld::IdCompletionManager& completionManager = getDocument().getIdCompletionManager(); + CSVWidget::DropLineEdit* editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); + + // The savegame format limits the player faction string to 32 characters. + // The region sound name is limited to 32 characters. (ESM::Region::SoundRef::mSound) + // The script name is limited to 32 characters. (ESM::Script::SCHD::mName) + // The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell) + if (display == CSMWorld::ColumnBase::Display_Faction || display == CSMWorld::ColumnBase::Display_Sound + || display == CSMWorld::ColumnBase::Display_Script || display == CSMWorld::ColumnBase::Display_Referenceable) + { + editor->setMaxLength(32); + } + else if (display == CSMWorld::ColumnBase::Display_Cell) + { + editor->setMaxLength(64); + } + return editor; } -CSVWorld::CommandDelegate *CSVWorld::IdCompletionDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, - QObject *parent) const +CSVWorld::CommandDelegate* CSVWorld::IdCompletionDelegateFactory::makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const { return new IdCompletionDelegate(dispatcher, document, parent); } diff --git a/apps/opencs/view/world/idcompletiondelegate.hpp b/apps/opencs/view/world/idcompletiondelegate.hpp index 57c2c11c4..9326e3b69 100644 --- a/apps/opencs/view/world/idcompletiondelegate.hpp +++ b/apps/opencs/view/world/idcompletiondelegate.hpp @@ -3,33 +3,43 @@ #include "util.hpp" +#include + +class QModelIndex; +class QObject; +class QWidget; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + namespace CSVWorld { /// \brief Enables the Id completion for a column class IdCompletionDelegate : public CommandDelegate { - public: - IdCompletionDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, - QObject *parent); + public: + IdCompletionDelegate(CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent); - QWidget *createEditor (QWidget *parent, - const QStyleOptionViewItem &option, - const QModelIndex &index) const override; + QWidget* createEditor( + QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - QWidget *createEditor (QWidget *parent, - const QStyleOptionViewItem &option, - const QModelIndex &index, - CSMWorld::ColumnBase::Display display) const override; + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index, + CSMWorld::ColumnBase::Display display) const override; }; class IdCompletionDelegateFactory : public CommandDelegateFactory { - public: - CommandDelegate *makeDelegate(CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, - QObject *parent) const override; - ///< The ownership of the returned CommandDelegate is transferred to the caller. + public: + CommandDelegate* makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const override; + ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } diff --git a/apps/opencs/view/world/idtypedelegate.cpp b/apps/opencs/view/world/idtypedelegate.cpp index ee35e07d4..6aa77b38f 100755 --- a/apps/opencs/view/world/idtypedelegate.cpp +++ b/apps/opencs/view/world/idtypedelegate.cpp @@ -2,26 +2,42 @@ #include "../../model/world/universalid.hpp" -CSVWorld::IdTypeDelegate::IdTypeDelegate - (const ValueList &values, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) - : DataDisplayDelegate (values, icons, dispatcher, document, - "Records", "type-format", - parent) -{} +#include +#include + +#include + +class QObject; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + +CSVWorld::IdTypeDelegate::IdTypeDelegate(const ValueList& values, const IconList& icons, + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) + : DataDisplayDelegate(values, icons, dispatcher, document, "Records", "type-format", parent) +{ +} CSVWorld::IdTypeDelegateFactory::IdTypeDelegateFactory() { - for (int i=0; i (i)); + CSMWorld::UniversalId id(static_cast(i)); - DataDisplayDelegateFactory::add (id.getType(), QString::fromUtf8 (id.getTypeName().c_str()), - QString::fromUtf8 (id.getIcon().c_str())); + DataDisplayDelegateFactory::add( + id.getType(), QString::fromUtf8(id.getTypeName().c_str()), QString::fromUtf8(id.getIcon().c_str())); } } -CSVWorld::CommandDelegate *CSVWorld::IdTypeDelegateFactory::makeDelegate ( - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const +CSVWorld::CommandDelegate* CSVWorld::IdTypeDelegateFactory::makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const { - return new IdTypeDelegate (mValues, mIcons, dispatcher, document, parent); + return new IdTypeDelegate(mValues, mIcons, dispatcher, document, parent); } diff --git a/apps/opencs/view/world/idtypedelegate.hpp b/apps/opencs/view/world/idtypedelegate.hpp index f1c3b539c..4a138910c 100755 --- a/apps/opencs/view/world/idtypedelegate.hpp +++ b/apps/opencs/view/world/idtypedelegate.hpp @@ -1,27 +1,39 @@ #ifndef IDTYPEDELEGATE_HPP #define IDTYPEDELEGATE_HPP -#include "enumdelegate.hpp" -#include "util.hpp" -#include "../../model/world/universalid.hpp" #include "datadisplaydelegate.hpp" +class QObject; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + namespace CSVWorld { + class CommandDelegate; + class IdTypeDelegate : public DataDisplayDelegate { - public: - IdTypeDelegate (const ValueList &mValues, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent); + public: + IdTypeDelegate(const ValueList& mValues, const IconList& icons, CSMWorld::CommandDispatcher* dispatcher, + CSMDoc::Document& document, QObject* parent); }; class IdTypeDelegateFactory : public DataDisplayDelegateFactory { - public: + public: + IdTypeDelegateFactory(); - IdTypeDelegateFactory(); - - CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; - ///< The ownership of the returned CommandDelegate is transferred to the caller. + CommandDelegate* makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const override; + ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 442157ac5..6f790d20c 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -1,29 +1,31 @@ #include "idvalidator.hpp" -#include +#include -bool CSVWorld::IdValidator::isValid (const QChar& c, bool first) const +bool CSVWorld::IdValidator::isValid(const QChar& c, bool first) const { - if (c.isLetter() || c=='_') + if (c.isLetter() || c == '_') return true; - if (!first && (c.isDigit() || c.isSpace())) + if (!first && (c.isDigit() || c.isSpace())) return true; return false; } -CSVWorld::IdValidator::IdValidator (bool relaxed, QObject *parent) -: QValidator (parent), mRelaxed (relaxed) -{} +CSVWorld::IdValidator::IdValidator(bool relaxed, QObject* parent) + : QValidator(parent) + , mRelaxed(relaxed) +{ +} -QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) const +QValidator::State CSVWorld::IdValidator::validate(QString& input, int& pos) const { mError.clear(); if (mRelaxed) { - if (input.indexOf ('"')!=-1 || input.indexOf ("::")!=-1 || input.indexOf ("#")!=-1) + if (input.indexOf('"') != -1 || input.indexOf("::") != -1 || input.indexOf("#") != -1) return QValidator::Invalid; } else @@ -42,9 +44,9 @@ QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) con if (!mNamespace.empty()) { - std::string namespace_ = input.left (static_cast(mNamespace.size())).toUtf8().constData(); + std::string namespace_ = input.left(static_cast(mNamespace.size())).toUtf8().constData(); - if (Misc::StringUtils::lowerCase (namespace_)!=mNamespace) + if (Misc::StringUtils::lowerCase(namespace_) != mNamespace) return QValidator::Invalid; // incorrect namespace iter += namespace_.size(); @@ -53,20 +55,20 @@ QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) con } else { - int index = input.indexOf (":"); + int index = input.indexOf(":"); - if (index!=-1) + if (index != -1) { - QString namespace_ = input.left (index); + QString namespace_ = input.left(index); - if (namespace_=="project" || namespace_=="session") + if (namespace_ == "project" || namespace_ == "session") return QValidator::Invalid; // reserved namespace } } - for (; iter!=input.end(); ++iter, first = false) + for (; iter != input.end(); ++iter, first = false) { - if (*iter==':') + if (*iter == ':') { if (first) return QValidator::Invalid; // scope operator at the beginning @@ -90,7 +92,7 @@ QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) con { prevScope = false; - if (!isValid (*iter, first)) + if (!isValid(*iter, first)) return QValidator::Invalid; } } @@ -111,9 +113,9 @@ QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) con return QValidator::Acceptable; } -void CSVWorld::IdValidator::setNamespace (const std::string& namespace_) +void CSVWorld::IdValidator::setNamespace(const std::string& namespace_) { - mNamespace = Misc::StringUtils::lowerCase (namespace_); + mNamespace = Misc::StringUtils::lowerCase(namespace_); } std::string CSVWorld::IdValidator::getError() const diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp index 278335a65..e83154296 100644 --- a/apps/opencs/view/world/idvalidator.hpp +++ b/apps/opencs/view/world/idvalidator.hpp @@ -9,29 +9,26 @@ namespace CSVWorld { class IdValidator : public QValidator { - bool mRelaxed; - std::string mNamespace; - mutable std::string mError; + bool mRelaxed; + std::string mNamespace; + mutable std::string mError; - private: + private: + bool isValid(const QChar& c, bool first) const; - bool isValid (const QChar& c, bool first) const; + public: + IdValidator(bool relaxed = false, QObject* parent = nullptr); + ///< \param relaxed Relaxed rules for IDs that also functino as user visible text - public: + State validate(QString& input, int& pos) const override; - IdValidator (bool relaxed = false, QObject *parent = nullptr); - ///< \param relaxed Relaxed rules for IDs that also functino as user visible text - - State validate (QString& input, int& pos) const override; - - void setNamespace (const std::string& namespace_); - - /// Return a description of the error that resulted in the last call of validate - /// returning QValidator::Intermediate. If the last call to validate returned - /// a different value (or if there was no such call yet), an empty string is - /// returned. - std::string getError() const; + void setNamespace(const std::string& namespace_); + /// Return a description of the error that resulted in the last call of validate + /// returning QValidator::Intermediate. If the last call to validate returned + /// a different value (or if there was no such call yet), an empty string is + /// returned. + std::string getError() const; }; } diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index cf1b48a19..f98fe5be5 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -1,48 +1,56 @@ #include "infocreator.hpp" #include +#include #include +#include #include -#include +#include +#include +#include + +#include #include "../../model/doc/document.hpp" -#include "../../model/world/data.hpp" -#include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" -#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/idtable.hpp" #include "../widget/droplineedit.hpp" +class QUndoStack; + std::string CSVWorld::InfoCreator::getId() const { - std::string id = Misc::StringUtils::lowerCase (mTopic->text().toUtf8().constData()); + const std::string topic = mTopic->text().toStdString(); std::string unique = QUuid::createUuid().toByteArray().data(); - unique.erase (std::remove (unique.begin(), unique.end(), '-'), unique.end()); + unique.erase(std::remove(unique.begin(), unique.end(), '-'), unique.end()); - unique = unique.substr (1, unique.size()-2); + unique = unique.substr(1, unique.size() - 2); - return id + '#' + unique; + return topic + '#' + unique; } -void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +void CSVWorld::InfoCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const { - CSMWorld::IdTable& table = dynamic_cast (*getData().getTableModel (getCollectionId())); + CSMWorld::IdTable& table = dynamic_cast(*getData().getTableModel(getCollectionId())); - CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast(&command); if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos) { if (!cloneCommand) { - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + command.addValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + command.addValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); + command.addValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); + command.addValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); } else { @@ -53,16 +61,16 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com { if (!cloneCommand) { - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + command.addValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } else cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } } -CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager) -: GenericCreator (data, undoStack, id) +CSVWorld::InfoCreator::InfoCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, + CSMWorld::IdCompletionManager& completionManager) + : GenericCreator(data, undoStack, id) { // Determine if we're dealing with topics or journals. CSMWorld::ColumnBase::Display displayType = CSMWorld::ColumnBase::Display_Topic; @@ -73,52 +81,57 @@ CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, labelText = "Journal"; } - QLabel *label = new QLabel (labelText, this); - insertBeforeButtons (label, false); + QLabel* label = new QLabel(labelText, this); + insertBeforeButtons(label, false); // Add topic/journal ID input with auto-completion. // Only existing topic/journal IDs are accepted so no ID validation is performed. mTopic = new CSVWidget::DropLineEdit(displayType, this); mTopic->setCompleter(completionManager.getCompleter(displayType).get()); - insertBeforeButtons (mTopic, true); + insertBeforeButtons(mTopic, true); - setManualEditing (false); + setManualEditing(false); - connect (mTopic, SIGNAL (textChanged (const QString&)), this, SLOT (topicChanged())); - connect (mTopic, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); + connect(mTopic, &CSVWidget::DropLineEdit::textChanged, this, &InfoCreator::topicChanged); + connect(mTopic, &CSVWidget::DropLineEdit::returnPressed, this, &InfoCreator::inputReturnPressed); } -void CSVWorld::InfoCreator::cloneMode (const std::string& originId, - const CSMWorld::UniversalId::Type type) +void CSVWorld::InfoCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { - CSMWorld::IdTable& infoTable = - dynamic_cast (*getData().getTableModel (getCollectionId())); + CSMWorld::IdTable& infoTable = dynamic_cast(*getData().getTableModel(getCollectionId())); - int topicColumn = infoTable.findColumnIndex ( - getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? - CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal); + int topicColumn = infoTable.findColumnIndex(getCollectionId().getType() == CSMWorld::UniversalId::Type_TopicInfos + ? CSMWorld::Columns::ColumnId_Topic + : CSMWorld::Columns::ColumnId_Journal); - mTopic->setText ( - infoTable.data (infoTable.getModelIndex (originId, topicColumn)).toString()); + mTopic->setText(infoTable.data(infoTable.getModelIndex(originId, topicColumn)).toString()); - GenericCreator::cloneMode (originId, type); + GenericCreator::cloneMode(originId, type); } void CSVWorld::InfoCreator::reset() { - mTopic->setText (""); + mTopic->setText(""); GenericCreator::reset(); } +void CSVWorld::InfoCreator::setText(const std::string& text) +{ + QString qText = QString::fromStdString(text); + mTopic->setText(qText); +} + std::string CSVWorld::InfoCreator::getErrors() const { // We ignore errors from GenericCreator here, because they can never happen in an InfoCreator. std::string errors; - std::string topic = mTopic->text().toUtf8().constData(); + const ESM::RefId topic = ESM::RefId::stringRefId(mTopic->text().toStdString()); - if ((getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? - getData().getTopics() : getData().getJournals()).searchId (topic)==-1) + if ((getCollectionId().getType() == CSMWorld::UniversalId::Type_TopicInfos ? getData().getTopics() + : getData().getJournals()) + .searchId(topic) + == -1) { errors += "Invalid Topic ID"; } @@ -131,16 +144,18 @@ void CSVWorld::InfoCreator::focus() mTopic->setFocus(); } +void CSVWorld::InfoCreator::callReturnPressed() +{ + emit inputReturnPressed(); +} + void CSVWorld::InfoCreator::topicChanged() { update(); } -CSVWorld::Creator *CSVWorld::InfoCreatorFactory::makeCreator(CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const +CSVWorld::Creator* CSVWorld::InfoCreatorFactory::makeCreator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - return new InfoCreator(document.getData(), - document.getUndoStack(), - id, - document.getIdCompletionManager()); + return new InfoCreator(document.getData(), document.getUndoStack(), id, document.getIdCompletionManager()); } diff --git a/apps/opencs/view/world/infocreator.hpp b/apps/opencs/view/world/infocreator.hpp index 404dcf372..0c4a9fd4d 100644 --- a/apps/opencs/view/world/infocreator.hpp +++ b/apps/opencs/view/world/infocreator.hpp @@ -3,10 +3,18 @@ #include "genericcreator.hpp" +#include + +#include +#include + +class QUndoStack; + namespace CSMWorld { - class InfoCollection; class IdCompletionManager; + class CreateCommand; + class Data; } namespace CSVWidget @@ -14,46 +22,54 @@ namespace CSVWidget class DropLineEdit; } +namespace CSMDoc +{ + class Document; +} + namespace CSVWorld { class InfoCreator : public GenericCreator { - Q_OBJECT + Q_OBJECT - CSVWidget::DropLineEdit *mTopic; + CSVWidget::DropLineEdit* mTopic; - std::string getId() const override; + std::string getId() const override; - void configureCreateCommand (CSMWorld::CreateCommand& command) const override; + void configureCreateCommand(CSMWorld::CreateCommand& command) const override; - public: + public: + InfoCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, + CSMWorld::IdCompletionManager& completionManager); - InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager); + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - void cloneMode (const std::string& originId, - const CSMWorld::UniversalId::Type type) override; + void reset() override; - void reset() override; + void setText(const std::string& text); - std::string getErrors() const override; - ///< Return formatted error descriptions for the current state of the creator. if an empty - /// string is returned, there is no error. - - /// Focus main input widget - void focus() override; - - private slots: + std::string getErrors() const override; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. - void topicChanged(); + /// Focus main input widget + void focus() override; + + public slots: + + void callReturnPressed(); + + private slots: + + void topicChanged(); }; class InfoCreatorFactory : public CreatorFactoryBase { - public: - - Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; - ///< The ownership of the returned Creator is transferred to the caller. + public: + Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; + ///< The ownership of the returned Creator is transferred to the caller. }; } diff --git a/apps/opencs/view/world/landcreator.cpp b/apps/opencs/view/world/landcreator.cpp index 2ebfe1869..5ba2de603 100644 --- a/apps/opencs/view/world/landcreator.cpp +++ b/apps/opencs/view/world/landcreator.cpp @@ -5,6 +5,10 @@ #include #include +#include +#include +#include + #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/land.hpp" @@ -37,8 +41,8 @@ namespace CSVWorld insertBeforeButtons(mYLabel, false); insertBeforeButtons(mY, true); - connect (mX, SIGNAL(valueChanged(int)), this, SLOT(coordChanged(int))); - connect (mY, SIGNAL(valueChanged(int)), this, SLOT(coordChanged(int))); + connect(mX, qOverload(&QSpinBox::valueChanged), this, &LandCreator::coordChanged); + connect(mY, qOverload(&QSpinBox::valueChanged), this, &LandCreator::coordChanged); } void LandCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) @@ -57,8 +61,10 @@ namespace CSVWorld // Combine multiple touch commands into one "macro" command getUndoStack().beginMacro("Touch records"); - CSMWorld::IdTable& lands = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); - CSMWorld::IdTable& ltexs = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& lands + = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); + CSMWorld::IdTable& ltexs + = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); for (const CSMWorld::UniversalId& uid : ids) { CSMWorld::TouchLandCommand* touchCmd = new CSMWorld::TouchLandCommand(lands, ltexs, uid.getId()); @@ -83,7 +89,7 @@ namespace CSVWorld std::string LandCreator::getErrors() const { - if (getData().getLand().searchId(getId()) >= 0) + if (getData().getLand().searchId(ESM::RefId::stringRefId(getId())) >= 0) return "A land with that name already exists."; return ""; @@ -98,19 +104,22 @@ namespace CSVWorld { if (mCloneMode) { - CSMWorld::IdTable& lands = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); - CSMWorld::IdTable& ltexs = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); + CSMWorld::IdTable& lands + = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); + CSMWorld::IdTable& ltexs + = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); getUndoStack().beginMacro(("Clone " + id).c_str()); getUndoStack().push(command.release()); - CSMWorld::CopyLandTexturesCommand* ltexCopy = new CSMWorld::CopyLandTexturesCommand(lands, ltexs, getClonedId(), getId()); + CSMWorld::CopyLandTexturesCommand* ltexCopy + = new CSMWorld::CopyLandTexturesCommand(lands, ltexs, getClonedId(), getId()); getUndoStack().push(ltexCopy); getUndoStack().endMacro(); } else - getUndoStack().push (command.release()); + getUndoStack().push(command.release()); } void LandCreator::coordChanged(int value) diff --git a/apps/opencs/view/world/landcreator.hpp b/apps/opencs/view/world/landcreator.hpp index e0c5577e4..ae7eecf75 100644 --- a/apps/opencs/view/world/landcreator.hpp +++ b/apps/opencs/view/world/landcreator.hpp @@ -3,6 +3,18 @@ #include "genericcreator.hpp" +#include +#include +#include + +#include + +namespace CSMWorld +{ + class CreateCommand; + class Data; +} + class QLabel; class QSpinBox; @@ -10,37 +22,34 @@ namespace CSVWorld { class LandCreator : public GenericCreator { - Q_OBJECT + Q_OBJECT - QLabel* mXLabel; - QLabel* mYLabel; - QSpinBox* mX; - QSpinBox* mY; + QLabel* mXLabel; + QLabel* mYLabel; + QSpinBox* mX; + QSpinBox* mY; - public: + public: + LandCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); - LandCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; + void touch(const std::vector& ids) override; - void touch(const std::vector& ids) override; + void focus() override; - void focus() override; + void reset() override; - void reset() override; + std::string getErrors() const override; - std::string getErrors() const override; + protected: + std::string getId() const override; - protected: + void pushCommand(std::unique_ptr command, const std::string& id) override; - std::string getId() const override; + private slots: - void pushCommand(std::unique_ptr command, - const std::string& id) override; - - private slots: - - void coordChanged(int value); + void coordChanged(int value); }; } diff --git a/apps/opencs/view/world/landtexturecreator.cpp b/apps/opencs/view/world/landtexturecreator.cpp index 43d911e50..a46e5b6db 100644 --- a/apps/opencs/view/world/landtexturecreator.cpp +++ b/apps/opencs/view/world/landtexturecreator.cpp @@ -2,11 +2,17 @@ #include #include +#include #include #include #include +#include +#include +#include +#include + #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexture.hpp" @@ -35,8 +41,8 @@ namespace CSVWorld mIndexBox->setMaximum(MaxIndex); insertBeforeButtons(mIndexBox, true); - connect(mNameEdit, SIGNAL(textChanged(const QString&)), this, SLOT(nameChanged(const QString&))); - connect(mIndexBox, SIGNAL(valueChanged(int)), this, SLOT(indexChanged(int))); + connect(mNameEdit, &QLineEdit::textChanged, this, &LandTextureCreator::nameChanged); + connect(mIndexBox, qOverload(&QSpinBox::valueChanged), this, &LandTextureCreator::indexChanged); } void LandTextureCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) @@ -66,7 +72,7 @@ namespace CSVWorld std::string LandTextureCreator::getErrors() const { - if (getData().getLandTextures().searchId(getId()) >= 0) + if (getData().getLandTextures().searchId(ESM::RefId::stringRefId(getId())) >= 0) { return "Index is already in use"; } diff --git a/apps/opencs/view/world/landtexturecreator.hpp b/apps/opencs/view/world/landtexturecreator.hpp index b11c47758..a2ebf093f 100644 --- a/apps/opencs/view/world/landtexturecreator.hpp +++ b/apps/opencs/view/world/landtexturecreator.hpp @@ -5,6 +5,14 @@ #include "genericcreator.hpp" +#include + +namespace CSMWorld +{ + class CreateCommand; + class Data; +} + class QLineEdit; class QSpinBox; @@ -12,37 +20,34 @@ namespace CSVWorld { class LandTextureCreator : public GenericCreator { - Q_OBJECT + Q_OBJECT - public: + public: + LandTextureCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); - LandTextureCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; + void focus() override; - void focus() override; + void reset() override; - void reset() override; + std::string getErrors() const override; - std::string getErrors() const override; + protected: + void configureCreateCommand(CSMWorld::CreateCommand& command) const override; - protected: + std::string getId() const override; - void configureCreateCommand(CSMWorld::CreateCommand& command) const override; + private slots: - std::string getId() const override; + void nameChanged(const QString& val); + void indexChanged(int val); - private slots: + private: + QLineEdit* mNameEdit; + QSpinBox* mIndexBox; - void nameChanged(const QString& val); - void indexChanged(int val); - - private: - - QLineEdit* mNameEdit; - QSpinBox* mIndexBox; - - std::string mName; + std::string mName; }; } diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index d52f7ca73..3e1b16248 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -1,52 +1,49 @@ #include "nestedtable.hpp" -#include #include +#include #include -#include #include "../../model/prefs/shortcut.hpp" -#include "../../model/world/nestedtableproxymodel.hpp" -#include "../../model/world/universalid.hpp" -#include "../../model/world/commands.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/world/commandmacro.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/nestedtableproxymodel.hpp" +#include "../../model/world/universalid.hpp" + +#include +#include +#include #include "tableeditidaction.hpp" #include "util.hpp" -CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, - CSMWorld::UniversalId id, - CSMWorld::NestedTableProxyModel* model, - QWidget* parent, - bool editable, - bool fixedRows) - : DragRecordTable(document, parent), - mAddNewRowAction(nullptr), - mRemoveRowAction(nullptr), - mEditIdAction(nullptr), - mModel(model) +CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, + CSMWorld::NestedTableProxyModel* model, QWidget* parent, bool editable, bool fixedRows) + : DragRecordTable(document, parent) + , mAddNewRowAction(nullptr) + , mRemoveRowAction(nullptr) + , mEditIdAction(nullptr) + , mModel(model) { - mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); + mDispatcher = new CSMWorld::CommandDispatcher(document, id, this); - setSelectionBehavior (QAbstractItemView::SelectRows); - setSelectionMode (QAbstractItemView::ExtendedSelection); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::ExtendedSelection); - horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); + horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); verticalHeader()->hide(); int columns = model->columnCount(QModelIndex()); - for(int i = 0 ; i < columns; ++i) + for (int i = 0; i < columns; ++i) { - CSMWorld::ColumnBase::Display display = static_cast ( - model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + CSMWorld::ColumnBase::Display display = static_cast( + model->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, - mDispatcher, - document, - this); + CommandDelegate* delegate + = CommandDelegateFactoryCollection::get().makeDelegate(display, mDispatcher, document, this); setItemDelegateForColumn(i, delegate); } @@ -57,21 +54,19 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, { if (!fixedRows) { - mAddNewRowAction = new QAction (tr ("Add new row"), this); - connect(mAddNewRowAction, SIGNAL(triggered()), - this, SLOT(addNewRowActionTriggered())); + mAddNewRowAction = new QAction(tr("Add new row"), this); + connect(mAddNewRowAction, &QAction::triggered, this, &NestedTable::addNewRowActionTriggered); CSMPrefs::Shortcut* addRowShortcut = new CSMPrefs::Shortcut("table-add", this); addRowShortcut->associateAction(mAddNewRowAction); - mRemoveRowAction = new QAction (tr ("Remove rows"), this); - connect(mRemoveRowAction, SIGNAL(triggered()), - this, SLOT(removeRowActionTriggered())); + mRemoveRowAction = new QAction(tr("Remove rows"), this); + connect(mRemoveRowAction, &QAction::triggered, this, &NestedTable::removeRowActionTriggered); CSMPrefs::Shortcut* removeRowShortcut = new CSMPrefs::Shortcut("table-remove", this); removeRowShortcut->associateAction(mRemoveRowAction); } mEditIdAction = new TableEditIdAction(*this, this); - connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); + connect(mEditIdAction, &QAction::triggered, this, &NestedTable::editCell); } } @@ -81,7 +76,7 @@ std::vector CSVWorld::NestedTable::getDraggedRecords() co return std::vector(); } -void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) +void CSVWorld::NestedTable::contextMenuEvent(QContextMenuEvent* event) { if (!mEditIdAction) return; @@ -105,13 +100,13 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) menu.addAction(mRemoveRowAction); } - menu.exec (event->globalPos()); + menu.exec(event->globalPos()); } void CSVWorld::NestedTable::removeRowActionTriggered() { - CSMWorld::CommandMacro macro(mDocument.getUndoStack(), - selectionModel()->selectedRows().size() > 1 ? tr("Remove rows") : ""); + CSMWorld::CommandMacro macro( + mDocument.getUndoStack(), selectionModel()->selectedRows().size() > 1 ? tr("Remove rows") : ""); // Remove rows in reverse order for (int i = selectionModel()->selectedRows().size() - 1; i >= 0; --i) @@ -128,10 +123,8 @@ void CSVWorld::NestedTable::addNewRowActionTriggered() if (!selectionModel()->selectedRows().empty()) row = selectionModel()->selectedRows().back().row() + 1; - mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), - mModel->getParentId(), - row, - mModel->getParentColumn())); + mDocument.getUndoStack().push( + new CSMWorld::AddNestedCommand(*(mModel->model()), mModel->getParentId(), row, mModel->getParentColumn())); } void CSVWorld::NestedTable::editCell() diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index f864f5d80..2520f45a0 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -1,10 +1,11 @@ #ifndef CSV_WORLD_NESTEDTABLE_H #define CSV_WORLD_NESTEDTABLE_H -#include - #include "dragrecordtable.hpp" +#include +#include + class QAction; class QContextMenuEvent; @@ -28,24 +29,20 @@ namespace CSVWorld { Q_OBJECT - QAction *mAddNewRowAction; - QAction *mRemoveRowAction; - TableEditIdAction *mEditIdAction; + QAction* mAddNewRowAction; + QAction* mRemoveRowAction; + TableEditIdAction* mEditIdAction; CSMWorld::NestedTableProxyModel* mModel; - CSMWorld::CommandDispatcher *mDispatcher; + CSMWorld::CommandDispatcher* mDispatcher; public: - NestedTable(CSMDoc::Document& document, - CSMWorld::UniversalId id, - CSMWorld::NestedTableProxyModel* model, - QWidget* parent = nullptr, - bool editable = true, - bool fixedRows = false); + NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, + QWidget* parent = nullptr, bool editable = true, bool fixedRows = false); std::vector getDraggedRecords() const override; private: - void contextMenuEvent (QContextMenuEvent *event) override; + void contextMenuEvent(QContextMenuEvent* event) override; private slots: void removeRowActionTriggered(); @@ -55,7 +52,7 @@ namespace CSVWorld void editCell(); signals: - void editRequest(const CSMWorld::UniversalId &id, const std::string &hint); + void editRequest(const CSMWorld::UniversalId& id, const std::string& hint); }; } diff --git a/apps/opencs/view/world/pathgridcreator.cpp b/apps/opencs/view/world/pathgridcreator.cpp index 95628a5d9..51c3b1d28 100644 --- a/apps/opencs/view/world/pathgridcreator.cpp +++ b/apps/opencs/view/world/pathgridcreator.cpp @@ -2,6 +2,13 @@ #include +#include + +#include +#include +#include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" @@ -11,6 +18,8 @@ #include "../widget/droplineedit.hpp" +class QUndoStack; + std::string CSVWorld::PathgridCreator::getId() const { return mCell->text().toUtf8().constData(); @@ -18,21 +27,16 @@ std::string CSVWorld::PathgridCreator::getId() const CSMWorld::IdTable& CSVWorld::PathgridCreator::getPathgridsTable() const { - return dynamic_cast ( - *getData().getTableModel(getCollectionId()) - ); + return dynamic_cast(*getData().getTableModel(getCollectionId())); } -CSVWorld::PathgridCreator::PathgridCreator( - CSMWorld::Data& data, - QUndoStack& undoStack, - const CSMWorld::UniversalId& id, - CSMWorld::IdCompletionManager& completionManager -) : GenericCreator(data, undoStack, id) +CSVWorld::PathgridCreator::PathgridCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, + CSMWorld::IdCompletionManager& completionManager) + : GenericCreator(data, undoStack, id) { setManualEditing(false); - QLabel *label = new QLabel("Cell", this); + QLabel* label = new QLabel("Cell", this); insertBeforeButtons(label, false); // Add cell ID input with auto-completion. @@ -42,13 +46,11 @@ CSVWorld::PathgridCreator::PathgridCreator( mCell->setCompleter(completionManager.getCompleter(displayType).get()); insertBeforeButtons(mCell, true); - connect(mCell, SIGNAL (textChanged(const QString&)), this, SLOT (cellChanged())); - connect(mCell, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); + connect(mCell, &CSVWidget::DropLineEdit::textChanged, this, &PathgridCreator::cellChanged); + connect(mCell, &CSVWidget::DropLineEdit::returnPressed, this, &PathgridCreator::inputReturnPressed); } -void CSVWorld::PathgridCreator::cloneMode( - const std::string& originId, - const CSMWorld::UniversalId::Type type) +void CSVWorld::PathgridCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { CSVWorld::GenericCreator::cloneMode(originId, type); @@ -60,7 +62,7 @@ void CSVWorld::PathgridCreator::cloneMode( std::string CSVWorld::PathgridCreator::getErrors() const { - std::string cellId = getId(); + const ESM::RefId cellId = ESM::RefId::stringRefId(getId()); // Check user input for any errors. std::string errors; @@ -96,14 +98,8 @@ void CSVWorld::PathgridCreator::cellChanged() update(); } -CSVWorld::Creator *CSVWorld::PathgridCreatorFactory::makeCreator( - CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const +CSVWorld::Creator* CSVWorld::PathgridCreatorFactory::makeCreator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - return new PathgridCreator( - document.getData(), - document.getUndoStack(), - id, - document.getIdCompletionManager() - ); + return new PathgridCreator(document.getData(), document.getUndoStack(), id, document.getIdCompletionManager()); } diff --git a/apps/opencs/view/world/pathgridcreator.hpp b/apps/opencs/view/world/pathgridcreator.hpp index 773735e25..ce26dc370 100644 --- a/apps/opencs/view/world/pathgridcreator.hpp +++ b/apps/opencs/view/world/pathgridcreator.hpp @@ -1,8 +1,13 @@ #ifndef PATHGRIDCREATOR_HPP #define PATHGRIDCREATOR_HPP +#include + #include "genericcreator.hpp" +#include +#include + namespace CSMDoc { class Document; @@ -13,7 +18,6 @@ namespace CSMWorld class Data; class IdCompletionManager; class IdTable; - class UniversalId; } namespace CSVWidget @@ -28,54 +32,44 @@ namespace CSVWorld { Q_OBJECT - CSVWidget::DropLineEdit *mCell; + CSVWidget::DropLineEdit* mCell; - private: + private: + /// \return Cell ID entered by user. + std::string getId() const override; - /// \return Cell ID entered by user. - std::string getId() const override; + /// \return reference to table containing pathgrids. + CSMWorld::IdTable& getPathgridsTable() const; - /// \return reference to table containing pathgrids. - CSMWorld::IdTable& getPathgridsTable() const; + public: + PathgridCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, + CSMWorld::IdCompletionManager& completionManager); - public: + /// \brief Set cell ID input widget to ID of record to be cloned. + /// \param originId Cell ID to be cloned. + /// \param type Type of record to be cloned. + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - PathgridCreator( - CSMWorld::Data& data, - QUndoStack& undoStack, - const CSMWorld::UniversalId& id, - CSMWorld::IdCompletionManager& completionManager); + /// \return Error description for current user input. + std::string getErrors() const override; - /// \brief Set cell ID input widget to ID of record to be cloned. - /// \param originId Cell ID to be cloned. - /// \param type Type of record to be cloned. - void cloneMode( - const std::string& originId, - const CSMWorld::UniversalId::Type type) override; + /// \brief Set focus to cell ID input widget. + void focus() override; - /// \return Error description for current user input. - std::string getErrors() const override; + /// \brief Clear cell ID input widget. + void reset() override; - /// \brief Set focus to cell ID input widget. - void focus() override; + private slots: - /// \brief Clear cell ID input widget. - void reset() override; - - private slots: - - /// \brief Check user input for errors. - void cellChanged(); + /// \brief Check user input for errors. + void cellChanged(); }; /// \brief Creator factory for pathgrid record creator. class PathgridCreatorFactory : public CreatorFactoryBase { - public: - - Creator *makeCreator( - CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const override; + public: + Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; }; } diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index f3312bb20..73ecb2157 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -2,66 +2,81 @@ #include +#include +#include +#include +#include +#include +#include + #include "../render/previewwidget.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" -CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mTitle (id.toString().c_str()) +#include "../../model/doc/document.hpp" + +CSVWorld::PreviewSubView::PreviewSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) + : SubView(id) + , mTitle(id.toString().c_str()) { - QHBoxLayout *layout = new QHBoxLayout; + QHBoxLayout* layout = new QHBoxLayout; - if (document.getData().getReferenceables().searchId (id.getId())==-1) + if (document.getData().getReferenceables().searchId(ESM::RefId::stringRefId(id.getId())) == -1) { - std::string referenceableId = - document.getData().getReferences().getRecord (id.getId()).get().mRefID; + std::string referenceableId = document.getData() + .getReferences() + .getRecord(ESM::RefId::stringRefId(id.getId())) + .get() + .mRefID.getRefIdString(); - referenceableIdChanged (referenceableId); + referenceableIdChanged(referenceableId); - mScene = - new CSVRender::PreviewWidget (document.getData(), id.getId(), false, this); + mScene = new CSVRender::PreviewWidget(document.getData(), id.getId(), false, this); } else - mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), true, this); + mScene = new CSVRender::PreviewWidget(document.getData(), id.getId(), true, this); - CSVWidget::SceneToolbar *toolbar = new CSVWidget::SceneToolbar (48+6, this); + mScene->setExterior(true); - CSVWidget::SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); - toolbar->addTool (lightingTool); + CSVWidget::SceneToolbar* toolbar = new CSVWidget::SceneToolbar(48 + 6, this); - layout->addWidget (toolbar, 0); + CSVWidget::SceneToolMode* lightingTool = mScene->makeLightingSelector(toolbar); + toolbar->addTool(lightingTool); - layout->addWidget (mScene, 1); + layout->addWidget(toolbar, 0); - QWidget *widget = new QWidget; + layout->addWidget(mScene, 1); - widget->setLayout (layout); + QWidget* widget = new QWidget; - setWidget (widget); + widget->setLayout(layout); - connect (mScene, SIGNAL (closeRequest()), this, SLOT (closeRequest())); - connect (mScene, SIGNAL (referenceableIdChanged (const std::string&)), - this, SLOT (referenceableIdChanged (const std::string&))); - connect (mScene, SIGNAL (focusToolbarRequest()), toolbar, SLOT (setFocus())); - connect (toolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); + setWidget(widget); + + connect(mScene, &CSVRender::PreviewWidget::closeRequest, this, qOverload<>(&PreviewSubView::closeRequest)); + connect(mScene, &CSVRender::PreviewWidget::referenceableIdChanged, this, &PreviewSubView::referenceableIdChanged); + connect(mScene, &CSVRender::PreviewWidget::focusToolbarRequest, toolbar, + qOverload<>(&CSVWidget::SceneToolbar::setFocus)); + connect( + toolbar, &CSVWidget::SceneToolbar::focusSceneRequest, mScene, qOverload<>(&CSVRender::PreviewWidget::setFocus)); } -void CSVWorld::PreviewSubView::setEditLock (bool locked) {} +void CSVWorld::PreviewSubView::setEditLock(bool locked) {} std::string CSVWorld::PreviewSubView::getTitle() const { return mTitle; } -void CSVWorld::PreviewSubView::referenceableIdChanged (const std::string& id) +void CSVWorld::PreviewSubView::referenceableIdChanged(const std::string& id) { if (id.empty()) mTitle = "Preview: Reference to "; else mTitle = "Preview: Reference to " + id; - setWindowTitle (QString::fromUtf8 (mTitle.c_str())); + setWindowTitle(QString::fromUtf8(mTitle.c_str())); emit updateTitle(); } diff --git a/apps/opencs/view/world/previewsubview.hpp b/apps/opencs/view/world/previewsubview.hpp index ed88d0488..8ae3c9dba 100644 --- a/apps/opencs/view/world/previewsubview.hpp +++ b/apps/opencs/view/world/previewsubview.hpp @@ -3,6 +3,10 @@ #include "../doc/subview.hpp" +#include + +#include + namespace CSMDoc { class Document; @@ -17,22 +21,21 @@ namespace CSVWorld { class PreviewSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - CSVRender::PreviewWidget *mScene; - std::string mTitle; + CSVRender::PreviewWidget* mScene; + std::string mTitle; - public: + public: + PreviewSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document); - PreviewSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + void setEditLock(bool locked) override; - void setEditLock (bool locked) override; + std::string getTitle() const override; - std::string getTitle() const override; + private slots: - private slots: - - void referenceableIdChanged (const std::string& id); + void referenceableIdChanged(const std::string& id); }; } diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 9fea7b303..67270bd1e 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -3,156 +3,164 @@ #include #include -#include "../../model/world/idtable.hpp" +#include + #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/idtable.hpp" #include "../../model/prefs/state.hpp" #include "../world/tablebottombox.hpp" +#include +#include +#include +#include +#include + void CSVWorld::RecordButtonBar::updateModificationButtons() { bool createAndDeleteDisabled = !mBottom || !mBottom->canCreateAndDelete() || mLocked; - mCloneButton->setDisabled (createAndDeleteDisabled); - mAddButton->setDisabled (createAndDeleteDisabled); + mCloneButton->setDisabled(createAndDeleteDisabled); + mAddButton->setDisabled(createAndDeleteDisabled); bool commandDisabled = !mCommandDispatcher || mLocked; - mRevertButton->setDisabled (commandDisabled); - mDeleteButton->setDisabled (commandDisabled || createAndDeleteDisabled); + mRevertButton->setDisabled(commandDisabled); + mDeleteButton->setDisabled(commandDisabled || createAndDeleteDisabled); } void CSVWorld::RecordButtonBar::updatePrevNextButtons() { int rows = mTable.rowCount(); - if (rows<=1) + if (rows <= 1) { - mPrevButton->setDisabled (true); - mNextButton->setDisabled (true); + mPrevButton->setDisabled(true); + mNextButton->setDisabled(true); } else if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) { - mPrevButton->setDisabled (false); - mNextButton->setDisabled (false); + mPrevButton->setDisabled(false); + mNextButton->setDisabled(false); } else { - int row = mTable.getModelIndex (mId.getId(), 0).row(); + int row = mTable.getModelIndex(mId.getId(), 0).row(); - mPrevButton->setDisabled (row<=0); - mNextButton->setDisabled (row>=rows-1); + mPrevButton->setDisabled(row <= 0); + mNextButton->setDisabled(row >= rows - 1); } } -CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, - CSMWorld::IdTable& table, TableBottomBox *bottomBox, - CSMWorld::CommandDispatcher *commandDispatcher, QWidget *parent) -: QWidget (parent), mId (id), mTable (table), mBottom (bottomBox), - mCommandDispatcher (commandDispatcher), mLocked (false) +CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMWorld::IdTable& table, + TableBottomBox* bottomBox, CSMWorld::CommandDispatcher* commandDispatcher, QWidget* parent) + : QWidget(parent) + , mId(id) + , mTable(table) + , mBottom(bottomBox) + , mCommandDispatcher(commandDispatcher) + , mLocked(false) { - QHBoxLayout *buttonsLayout = new QHBoxLayout; - buttonsLayout->setContentsMargins (0, 0, 0, 0); + QHBoxLayout* buttonsLayout = new QHBoxLayout; + buttonsLayout->setContentsMargins(0, 0, 0, 0); // left section - mPrevButton = new QToolButton (this); + mPrevButton = new QToolButton(this); mPrevButton->setIcon(QIcon(":record-previous")); - mPrevButton->setToolTip ("Switch to previous record"); - buttonsLayout->addWidget (mPrevButton, 0); + mPrevButton->setToolTip("Switch to previous record"); + buttonsLayout->addWidget(mPrevButton, 0); - mNextButton = new QToolButton (this); + mNextButton = new QToolButton(this); mNextButton->setIcon(QIcon(":/record-next")); - mNextButton->setToolTip ("Switch to next record"); - buttonsLayout->addWidget (mNextButton, 1); + mNextButton->setToolTip("Switch to next record"); + buttonsLayout->addWidget(mNextButton, 1); buttonsLayout->addStretch(2); // optional buttons of the right section if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview) { - QToolButton* previewButton = new QToolButton (this); + QToolButton* previewButton = new QToolButton(this); previewButton->setIcon(QIcon(":edit-preview")); - previewButton->setToolTip ("Open a preview of this record"); + previewButton->setToolTip("Open a preview of this record"); buttonsLayout->addWidget(previewButton); - connect (previewButton, SIGNAL(clicked()), this, SIGNAL (showPreview())); + connect(previewButton, &QToolButton::clicked, this, &RecordButtonBar::showPreview); } if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) { - QToolButton* viewButton = new QToolButton (this); + QToolButton* viewButton = new QToolButton(this); viewButton->setIcon(QIcon(":/cell.png")); - viewButton->setToolTip ("Open a scene view of the cell this record is located in"); + viewButton->setToolTip("Open a scene view of the cell this record is located in"); buttonsLayout->addWidget(viewButton); - connect (viewButton, SIGNAL(clicked()), this, SIGNAL (viewRecord())); + connect(viewButton, &QToolButton::clicked, this, &RecordButtonBar::viewRecord); } // right section - mCloneButton = new QToolButton (this); + mCloneButton = new QToolButton(this); mCloneButton->setIcon(QIcon(":edit-clone")); - mCloneButton->setToolTip ("Clone record"); + mCloneButton->setToolTip("Clone record"); buttonsLayout->addWidget(mCloneButton); - mAddButton = new QToolButton (this); + mAddButton = new QToolButton(this); mAddButton->setIcon(QIcon(":edit-add")); - mAddButton->setToolTip ("Add new record"); + mAddButton->setToolTip("Add new record"); buttonsLayout->addWidget(mAddButton); - mDeleteButton = new QToolButton (this); + mDeleteButton = new QToolButton(this); mDeleteButton->setIcon(QIcon(":edit-delete")); - mDeleteButton->setToolTip ("Delete record"); + mDeleteButton->setToolTip("Delete record"); buttonsLayout->addWidget(mDeleteButton); - mRevertButton = new QToolButton (this); + mRevertButton = new QToolButton(this); mRevertButton->setIcon(QIcon(":edit-undo")); - mRevertButton->setToolTip ("Revert record"); + mRevertButton->setToolTip("Revert record"); buttonsLayout->addWidget(mRevertButton); - setLayout (buttonsLayout); + setLayout(buttonsLayout); // connections - if(mBottom && mBottom->canCreateAndDelete()) + if (mBottom && mBottom->canCreateAndDelete()) { - connect (mAddButton, SIGNAL (clicked()), mBottom, SLOT (createRequest())); - connect (mCloneButton, SIGNAL (clicked()), this, SLOT (cloneRequest())); + connect(mAddButton, &QToolButton::clicked, mBottom, &TableBottomBox::createRequest); + connect(mCloneButton, &QToolButton::clicked, this, &RecordButtonBar::cloneRequest); } - connect (mNextButton, SIGNAL (clicked()), this, SLOT (nextId())); - connect (mPrevButton, SIGNAL (clicked()), this, SLOT (prevId())); + connect(mNextButton, &QToolButton::clicked, this, &RecordButtonBar::nextId); + connect(mPrevButton, &QToolButton::clicked, this, &RecordButtonBar::prevId); if (mCommandDispatcher) { - connect (mRevertButton, SIGNAL (clicked()), mCommandDispatcher, SLOT (executeRevert())); - connect (mDeleteButton, SIGNAL (clicked()), mCommandDispatcher, SLOT (executeDelete())); + connect(mRevertButton, &QToolButton::clicked, mCommandDispatcher, &CSMWorld::CommandDispatcher::executeRevert); + connect(mDeleteButton, &QToolButton::clicked, mCommandDispatcher, &CSMWorld::CommandDispatcher::executeDelete); } - connect (&mTable, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); - connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); + connect(&mTable, &CSMWorld::IdTable::rowsInserted, this, &RecordButtonBar::rowNumberChanged); + connect(&mTable, &CSMWorld::IdTable::rowsRemoved, this, &RecordButtonBar::rowNumberChanged); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &RecordButtonBar::settingChanged); updateModificationButtons(); updatePrevNextButtons(); } -void CSVWorld::RecordButtonBar::setEditLock (bool locked) +void CSVWorld::RecordButtonBar::setEditLock(bool locked) { mLocked = locked; updateModificationButtons(); } -void CSVWorld::RecordButtonBar::universalIdChanged (const CSMWorld::UniversalId& id) +void CSVWorld::RecordButtonBar::universalIdChanged(const CSMWorld::UniversalId& id) { mId = id; updatePrevNextButtons(); } -void CSVWorld::RecordButtonBar::settingChanged (const CSMPrefs::Setting *setting) +void CSVWorld::RecordButtonBar::settingChanged(const CSMPrefs::Setting* setting) { - if (*setting=="General Input/cycle") + if (*setting == "General Input/cycle") updatePrevNextButtons(); } @@ -160,19 +168,18 @@ void CSVWorld::RecordButtonBar::cloneRequest() { if (mBottom) { - int typeColumn = mTable.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + int typeColumn = mTable.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - QModelIndex typeIndex = mTable.getModelIndex (mId.getId(), typeColumn); - CSMWorld::UniversalId::Type type = static_cast ( - mTable.data (typeIndex).toInt()); + QModelIndex typeIndex = mTable.getModelIndex(mId.getId(), typeColumn); + CSMWorld::UniversalId::Type type = static_cast(mTable.data(typeIndex).toInt()); - mBottom->cloneRequest (mId.getId(), type); + mBottom->cloneRequest(mId.getId(), type); } } void CSVWorld::RecordButtonBar::nextId() { - int newRow = mTable.getModelIndex (mId.getId(), 0).row() + 1; + int newRow = mTable.getModelIndex(mId.getId(), 0).row() + 1; if (newRow >= mTable.rowCount()) { @@ -182,25 +189,25 @@ void CSVWorld::RecordButtonBar::nextId() return; } - emit switchToRow (newRow); + emit switchToRow(newRow); } void CSVWorld::RecordButtonBar::prevId() { - int newRow = mTable.getModelIndex (mId.getId(), 0).row() - 1; + int newRow = mTable.getModelIndex(mId.getId(), 0).row() - 1; if (newRow < 0) { if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) - newRow = mTable.rowCount()-1; + newRow = mTable.rowCount() - 1; else return; } - emit switchToRow (newRow); + emit switchToRow(newRow); } -void CSVWorld::RecordButtonBar::rowNumberChanged (const QModelIndex& parent, int start, int end) +void CSVWorld::RecordButtonBar::rowNumberChanged(const QModelIndex& parent, int start, int end) { updatePrevNextButtons(); } diff --git a/apps/opencs/view/world/recordbuttonbar.hpp b/apps/opencs/view/world/recordbuttonbar.hpp index aca3211f8..605738670 100644 --- a/apps/opencs/view/world/recordbuttonbar.hpp +++ b/apps/opencs/view/world/recordbuttonbar.hpp @@ -35,57 +35,54 @@ namespace CSVWorld /// - view (optional) class RecordButtonBar : public QWidget { - Q_OBJECT + Q_OBJECT - CSMWorld::UniversalId mId; - CSMWorld::IdTable& mTable; - TableBottomBox *mBottom; - CSMWorld::CommandDispatcher *mCommandDispatcher; - QToolButton *mPrevButton; - QToolButton *mNextButton; - QToolButton *mCloneButton; - QToolButton *mAddButton; - QToolButton *mDeleteButton; - QToolButton *mRevertButton; - bool mLocked; + CSMWorld::UniversalId mId; + CSMWorld::IdTable& mTable; + TableBottomBox* mBottom; + CSMWorld::CommandDispatcher* mCommandDispatcher; + QToolButton* mPrevButton; + QToolButton* mNextButton; + QToolButton* mCloneButton; + QToolButton* mAddButton; + QToolButton* mDeleteButton; + QToolButton* mRevertButton; + bool mLocked; - private: + private: + void updateModificationButtons(); - void updateModificationButtons(); + void updatePrevNextButtons(); - void updatePrevNextButtons(); + public: + RecordButtonBar(const CSMWorld::UniversalId& id, CSMWorld::IdTable& table, TableBottomBox* bottomBox = nullptr, + CSMWorld::CommandDispatcher* commandDispatcher = nullptr, QWidget* parent = nullptr); - public: + void setEditLock(bool locked); - RecordButtonBar (const CSMWorld::UniversalId& id, - CSMWorld::IdTable& table, TableBottomBox *bottomBox = nullptr, - CSMWorld::CommandDispatcher *commandDispatcher = nullptr, QWidget *parent = nullptr); + public slots: - void setEditLock (bool locked); + void universalIdChanged(const CSMWorld::UniversalId& id); - public slots: + private slots: - void universalIdChanged (const CSMWorld::UniversalId& id); + void settingChanged(const CSMPrefs::Setting* setting); - private slots: + void cloneRequest(); - void settingChanged (const CSMPrefs::Setting *setting); + void nextId(); - void cloneRequest(); + void prevId(); - void nextId(); + void rowNumberChanged(const QModelIndex& parent, int start, int end); - void prevId(); + signals: - void rowNumberChanged (const QModelIndex& parent, int start, int end); + void showPreview(); - signals: + void viewRecord(); - void showPreview(); - - void viewRecord(); - - void switchToRow (int row); + void switchToRow(int row); }; } diff --git a/apps/opencs/view/world/recordstatusdelegate.cpp b/apps/opencs/view/world/recordstatusdelegate.cpp index fd98fe6cd..9e172f336 100644 --- a/apps/opencs/view/world/recordstatusdelegate.cpp +++ b/apps/opencs/view/world/recordstatusdelegate.cpp @@ -1,38 +1,49 @@ #include "recordstatusdelegate.hpp" -#include -#include -#include - #include "../../model/world/columns.hpp" -CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values, - const IconList & icons, - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) - : DataDisplayDelegate (values, icons, dispatcher, document, - "Records", "status-format", - parent) -{} +#include +#include -CSVWorld::CommandDelegate *CSVWorld::RecordStatusDelegateFactory::makeDelegate ( - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const +#include +#include +#include + +class QObject; + +namespace CSMDoc { - return new RecordStatusDelegate (mValues, mIcons, dispatcher, document, parent); + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + +CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values, const IconList& icons, + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) + : DataDisplayDelegate(values, icons, dispatcher, document, "Records", "status-format", parent) +{ +} + +CSVWorld::CommandDelegate* CSVWorld::RecordStatusDelegateFactory::makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const +{ + return new RecordStatusDelegate(mValues, mIcons, dispatcher, document, parent); } CSVWorld::RecordStatusDelegateFactory::RecordStatusDelegateFactory() { - std::vector> enums = - CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); + std::vector> enums + = CSMWorld::Columns::getEnums(CSMWorld::Columns::ColumnId_Modification); - static const char *sIcons[] = - { - ":list-base", ":list-modified", ":list-added", ":list-removed", ":list-removed", 0 - }; + static const char* sIcons[] + = { ":list-base", ":list-modified", ":list-added", ":list-removed", ":list-removed", 0 }; - for (int i=0; sIcons[i]; ++i) + for (int i = 0; sIcons[i]; ++i) { auto& enumPair = enums.at(i); - add (enumPair.first, enumPair.second.c_str(), sIcons[i]); + add(enumPair.first, enumPair.second.c_str(), sIcons[i]); } } diff --git a/apps/opencs/view/world/recordstatusdelegate.hpp b/apps/opencs/view/world/recordstatusdelegate.hpp index 38f066862..9afac7129 100644 --- a/apps/opencs/view/world/recordstatusdelegate.hpp +++ b/apps/opencs/view/world/recordstatusdelegate.hpp @@ -1,37 +1,39 @@ #ifndef RECORDSTATUSDELEGATE_H #define RECORDSTATUSDELEGATE_H -#include "util.hpp" -#include -#include - #include "datadisplaydelegate.hpp" -#include "../../model/world/record.hpp" -class QIcon; -class QFont; +class QObject; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} namespace CSVWorld { + class CommandDelegate; + class RecordStatusDelegate : public DataDisplayDelegate { public: - - RecordStatusDelegate (const ValueList& values, const IconList& icons, - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, - QObject *parent = nullptr); + RecordStatusDelegate(const ValueList& values, const IconList& icons, CSMWorld::CommandDispatcher* dispatcher, + CSMDoc::Document& document, QObject* parent = nullptr); }; class RecordStatusDelegateFactory : public DataDisplayDelegateFactory { - public: - - RecordStatusDelegateFactory(); - - CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const override; - ///< The ownership of the returned CommandDelegate is transferred to the caller. + public: + RecordStatusDelegateFactory(); + CommandDelegate* makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const override; + ///< The ownership of the returned CommandDelegate is transferred to the caller. }; } #endif // RECORDSTATUSDELEGATE_HPP - diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 836e8ac7d..e13648d63 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -1,51 +1,77 @@ #include "referenceablecreator.hpp" +#include + #include #include -#include "../../model/world/universalid.hpp" -#include "../../model/world/commands.hpp" +#include -void CSVWorld::ReferenceableCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +#include "../../model/world/commands.hpp" +#include "../../model/world/universalid.hpp" + +class QUndoStack; + +namespace CSMWorld { - command.setType ( - static_cast (mType->itemData (mType->currentIndex()).toInt())); + class Data; } -CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) -: GenericCreator (data, undoStack, id) +void CSVWorld::ReferenceableCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const { - QLabel *label = new QLabel ("Type", this); - insertBeforeButtons (label, false); + command.setType(static_cast(mType->itemData(mType->currentIndex()).toInt())); +} + +CSVWorld::ReferenceableCreator::ReferenceableCreator( + CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) + : GenericCreator(data, undoStack, id) +{ + QLabel* label = new QLabel("Type", this); + insertBeforeButtons(label, false); std::vector types = CSMWorld::UniversalId::listReferenceableTypes(); - mType = new QComboBox (this); + mType = new QComboBox(this); + mType->setMaxVisibleItems(20); - for (std::vector::const_iterator iter (types.begin()); - iter!=types.end(); ++iter) + for (std::vector::const_iterator iter(types.begin()); iter != types.end(); ++iter) { - CSMWorld::UniversalId id2 (*iter, ""); + CSMWorld::UniversalId id2(*iter, ""); - mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(), - static_cast (id2.getType())); + mType->addItem(QIcon(id2.getIcon().c_str()), id2.getTypeName().c_str(), static_cast(id2.getType())); } - insertBeforeButtons (mType, false); + mType->model()->sort(0); + + insertBeforeButtons(mType, false); + + connect(mType, qOverload(&QComboBox::currentIndexChanged), this, &ReferenceableCreator::setType); } void CSVWorld::ReferenceableCreator::reset() { - mType->setCurrentIndex (0); + mType->setCurrentIndex(0); GenericCreator::reset(); } -void CSVWorld::ReferenceableCreator::cloneMode (const std::string& originId, - const CSMWorld::UniversalId::Type type) +void CSVWorld::ReferenceableCreator::setType(int index) { - GenericCreator::cloneMode (originId, type); - mType->setCurrentIndex (mType->findData (static_cast (type))); + // container items have name limit of 32 characters + std::string text = mType->currentText().toStdString(); + if (text == "Potion" || text == "Apparatus" || text == "Armor" || text == "Book" || text == "Clothing" + || text == "Ingredient" || text == "ItemLevelledList" || text == "Light" || text == "Lockpick" + || text == "Miscellaneous" || text == "Probe" || text == "Repair" || text == "Weapon") + { + GenericCreator::setEditorMaxLength(32); + } + else + GenericCreator::setEditorMaxLength(32767); +} + +void CSVWorld::ReferenceableCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) +{ + GenericCreator::cloneMode(originId, type); + mType->setCurrentIndex(mType->findData(static_cast(type))); } void CSVWorld::ReferenceableCreator::toggleWidgets(bool active) diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp index d4657bcf7..985832796 100644 --- a/apps/opencs/view/world/referenceablecreator.hpp +++ b/apps/opencs/view/world/referenceablecreator.hpp @@ -1,34 +1,45 @@ #ifndef CSV_WORLD_REFERENCEABLECREATOR_H #define CSV_WORLD_REFERENCEABLECREATOR_H -class QComboBox; - #include "genericcreator.hpp" +#include + +#include + +class QComboBox; +class QObject; +class QUndoStack; + +namespace CSMWorld +{ + class CreateCommand; + class Data; +} + namespace CSVWorld { class ReferenceableCreator : public GenericCreator { - Q_OBJECT + Q_OBJECT - QComboBox *mType; + QComboBox* mType; - private: + private: + void configureCreateCommand(CSMWorld::CreateCommand& command) const override; - void configureCreateCommand (CSMWorld::CreateCommand& command) const override; + public: + ReferenceableCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); - public: + void reset() override; - ReferenceableCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id); + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - void reset() override; + void toggleWidgets(bool active = true) override; - void cloneMode (const std::string& originId, - const CSMWorld::UniversalId::Type type) override; - - void toggleWidgets(bool active = true) override; + private slots: + void setType(int index); }; } diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index e939b9baf..925ae5378 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -2,14 +2,20 @@ #include +#include + +#include +#include +#include +#include + #include "../../model/doc/document.hpp" -#include "../../model/world/data.hpp" -#include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" -#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" #include "../../model/world/idcompletionmanager.hpp" -#include "../../model/world/commandmacro.hpp" +#include "../../model/world/idtable.hpp" #include "../widget/droplineedit.hpp" @@ -18,39 +24,38 @@ std::string CSVWorld::ReferenceCreator::getId() const return mId; } -void CSVWorld::ReferenceCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +void CSVWorld::ReferenceCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const { // Set cellID - int cellIdColumn = - dynamic_cast (*getData().getTableModel (getCollectionId())). - findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int cellIdColumn = dynamic_cast(*getData().getTableModel(getCollectionId())) + .findColumnIndex(CSMWorld::Columns::ColumnId_Cell); - command.addValue (cellIdColumn, mCell->text()); + command.addValue(cellIdColumn, mCell->text()); } -CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager &completionManager) -: GenericCreator (data, undoStack, id) +CSVWorld::ReferenceCreator::ReferenceCreator(CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager) + : GenericCreator(data, undoStack, id) { - QLabel *label = new QLabel ("Cell", this); - insertBeforeButtons (label, false); + QLabel* label = new QLabel("Cell", this); + insertBeforeButtons(label, false); // Add cell ID input with auto-completion. // Only existing cell IDs are accepted so no ID validation is performed. mCell = new CSVWidget::DropLineEdit(CSMWorld::ColumnBase::Display_Cell, this); mCell->setCompleter(completionManager.getCompleter(CSMWorld::ColumnBase::Display_Cell).get()); - insertBeforeButtons (mCell, true); + insertBeforeButtons(mCell, true); - setManualEditing (false); + setManualEditing(false); - connect (mCell, SIGNAL (textChanged (const QString&)), this, SLOT (cellChanged())); - connect (mCell, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); + connect(mCell, &CSVWidget::DropLineEdit::textChanged, this, &ReferenceCreator::cellChanged); + connect(mCell, &CSVWidget::DropLineEdit::returnPressed, this, &ReferenceCreator::inputReturnPressed); } void CSVWorld::ReferenceCreator::reset() { GenericCreator::reset(); - mCell->setText (""); + mCell->setText(""); mId = getData().getReferences().getNewId(); } @@ -60,11 +65,11 @@ std::string CSVWorld::ReferenceCreator::getErrors() const // record is internal and requires neither user input nor verification. std::string errors; - std::string cell = mCell->text().toUtf8().constData(); + const ESM::RefId cell = ESM::RefId::stringRefId(mCell->text().toStdString()); if (cell.empty()) errors += "Missing Cell ID"; - else if (getData().getCells().searchId (cell)==-1) + else if (getData().getCells().searchId(cell) == -1) errors += "Invalid Cell ID"; return errors; @@ -80,26 +85,21 @@ void CSVWorld::ReferenceCreator::cellChanged() update(); } -void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, - const CSMWorld::UniversalId::Type type) +void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { - CSMWorld::IdTable& referenceTable = dynamic_cast ( - *getData().getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& referenceTable + = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_References)); - int cellIdColumn = referenceTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int cellIdColumn = referenceTable.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); - mCell->setText ( - referenceTable.data (referenceTable.getModelIndex (originId, cellIdColumn)).toString()); + mCell->setText(referenceTable.data(referenceTable.getModelIndex(originId, cellIdColumn)).toString()); CSVWorld::GenericCreator::cloneMode(originId, type); - cellChanged(); //otherwise ok button will remain disabled + cellChanged(); // otherwise ok button will remain disabled } -CSVWorld::Creator *CSVWorld::ReferenceCreatorFactory::makeCreator (CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const +CSVWorld::Creator* CSVWorld::ReferenceCreatorFactory::makeCreator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - return new ReferenceCreator(document.getData(), - document.getUndoStack(), - id, - document.getIdCompletionManager()); + return new ReferenceCreator(document.getData(), document.getUndoStack(), id, document.getIdCompletionManager()); } diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp index 3903900ad..1e6ab5a74 100644 --- a/apps/opencs/view/world/referencecreator.hpp +++ b/apps/opencs/view/world/referencecreator.hpp @@ -1,11 +1,26 @@ #ifndef CSV_WORLD_REFERENCECREATOR_H #define CSV_WORLD_REFERENCECREATOR_H +#include + #include "genericcreator.hpp" +#include +#include + +class QObject; +class QUndoStack; + namespace CSMWorld { class IdCompletionManager; + class CreateCommand; + class Data; +} + +namespace CSMDoc +{ + class Document; } namespace CSVWidget @@ -18,45 +33,41 @@ namespace CSVWorld class ReferenceCreator : public GenericCreator { - Q_OBJECT + Q_OBJECT - CSVWidget::DropLineEdit *mCell; - std::string mId; + CSVWidget::DropLineEdit* mCell; + std::string mId; - private: + private: + std::string getId() const override; - std::string getId() const override; + void configureCreateCommand(CSMWorld::CreateCommand& command) const override; - void configureCreateCommand (CSMWorld::CreateCommand& command) const override; + public: + ReferenceCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, + CSMWorld::IdCompletionManager& completionManager); - public: + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager &completionManager); + void reset() override; - void cloneMode(const std::string& originId, - const CSMWorld::UniversalId::Type type) override; + std::string getErrors() const override; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. - void reset() override; + /// Focus main input widget + void focus() override; - std::string getErrors() const override; - ///< Return formatted error descriptions for the current state of the creator. if an empty - /// string is returned, there is no error. + private slots: - /// Focus main input widget - void focus() override; - - private slots: - - void cellChanged(); + void cellChanged(); }; class ReferenceCreatorFactory : public CreatorFactoryBase { - public: - - Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; - ///< The ownership of the returned Creator is transferred to the caller. + public: + Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; + ///< The ownership of the returned Creator is transferred to the caller. }; } diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index d2d4fbd23..a2847848d 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -1,116 +1,123 @@ #include "regionmap.hpp" #include +#include #include #include +#include +#include +#include +#include #include -#include +#include #include +#include + +#include #include "../../model/doc/document.hpp" -#include "../../model/world/regionmap.hpp" -#include "../../model/world/universalid.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/columns.hpp" +#include "../../model/world/regionmap.hpp" #include "../../model/world/tablemimedata.hpp" -#include "../../model/world/commandmacro.hpp" +#include "../../model/world/universalid.hpp" -void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) +void CSVWorld::RegionMap::contextMenuEvent(QContextMenuEvent* event) { - QMenu menu (this); + QMenu menu(this); - if (getUnselectedCells().size()>0) - menu.addAction (mSelectAllAction); + if (getUnselectedCells().size() > 0) + menu.addAction(mSelectAllAction); - if (selectionModel()->selectedIndexes().size()>0) - menu.addAction (mClearSelectionAction); + if (selectionModel()->selectedIndexes().size() > 0) + menu.addAction(mClearSelectionAction); - if (getMissingRegionCells().size()>0) - menu.addAction (mSelectRegionsAction); + if (getMissingRegionCells().size() > 0) + menu.addAction(mSelectRegionsAction); - int selectedNonExistentCells = getSelectedCells (false, true).size(); + int selectedNonExistentCells = getSelectedCells(false, true).size(); - if (selectedNonExistentCells>0) + if (selectedNonExistentCells > 0) { - if (selectedNonExistentCells==1) - mCreateCellsAction->setText ("Create one Cell"); + if (selectedNonExistentCells == 1) + mCreateCellsAction->setText("Create one Cell"); else { std::ostringstream stream; stream << "Create " << selectedNonExistentCells << " cells"; - mCreateCellsAction->setText (QString::fromUtf8 (stream.str().c_str())); + mCreateCellsAction->setText(QString::fromUtf8(stream.str().c_str())); } - menu.addAction (mCreateCellsAction); + menu.addAction(mCreateCellsAction); } - if (getSelectedCells().size()>0) + if (getSelectedCells().size() > 0) { if (!mRegionId.empty()) { - mSetRegionAction->setText (QString::fromUtf8 (("Set Region to " + mRegionId).c_str())); - menu.addAction (mSetRegionAction); + mSetRegionAction->setText(QString::fromUtf8(("Set Region to " + mRegionId).c_str())); + menu.addAction(mSetRegionAction); } - menu.addAction (mUnsetRegionAction); + menu.addAction(mUnsetRegionAction); - menu.addAction (mViewInTableAction); + menu.addAction(mViewInTableAction); } - if (selectionModel()->selectedIndexes().size()>0) - menu.addAction (mViewAction); + if (selectionModel()->selectedIndexes().size() > 0) + menu.addAction(mViewAction); - menu.exec (event->globalPos()); + menu.exec(event->globalPos()); } QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const { - const QAbstractItemModel *model = QTableView::model(); + const QAbstractItemModel* model = QTableView::model(); int rows = model->rowCount(); int columns = model->columnCount(); QModelIndexList selected = selectionModel()->selectedIndexes(); - std::sort (selected.begin(), selected.end()); + std::sort(selected.begin(), selected.end()); QModelIndexList all; - for (int y=0; yindex (y, x); - if (model->data (index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern)) - all.push_back (index); + QModelIndex index = model->index(y, x); + if (model->data(index, Qt::BackgroundRole) != QBrush(Qt::DiagCrossPattern)) + all.push_back(index); } - std::sort (all.begin(), all.end()); + std::sort(all.begin(), all.end()); QModelIndexList list; - std::set_difference (all.begin(), all.end(), selected.begin(), selected.end(), - std::back_inserter (list)); + std::set_difference(all.begin(), all.end(), selected.begin(), selected.end(), std::back_inserter(list)); return list; } -QModelIndexList CSVWorld::RegionMap::getSelectedCells (bool existent, bool nonExistent) const +QModelIndexList CSVWorld::RegionMap::getSelectedCells(bool existent, bool nonExistent) const { - const QAbstractItemModel *model = QTableView::model(); + const QAbstractItemModel* model = QTableView::model(); QModelIndexList selected = selectionModel()->selectedIndexes(); QModelIndexList list; - for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + for (QModelIndexList::const_iterator iter(selected.begin()); iter != selected.end(); ++iter) { - bool exists = model->data (*iter, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern); + bool exists = model->data(*iter, Qt::BackgroundRole) != QBrush(Qt::DiagCrossPattern); if ((exists && existent) || (!exists && nonExistent)) - list.push_back (*iter); + list.push_back(*iter); } return list; @@ -118,107 +125,103 @@ QModelIndexList CSVWorld::RegionMap::getSelectedCells (bool existent, bool nonEx QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const { - const QAbstractItemModel *model = QTableView::model(); + const QAbstractItemModel* model = QTableView::model(); QModelIndexList selected = selectionModel()->selectedIndexes(); std::set regions; - for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + for (QModelIndexList::const_iterator iter(selected.begin()); iter != selected.end(); ++iter) { - std::string region = - model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); + std::string region = model->data(*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); if (!region.empty()) - regions.insert (region); + regions.insert(region); } QModelIndexList list; QModelIndexList unselected = getUnselectedCells(); - for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) + for (QModelIndexList::const_iterator iter(unselected.begin()); iter != unselected.end(); ++iter) { - std::string region = - model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); + std::string region = model->data(*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); - if (!region.empty() && regions.find (region)!=regions.end()) - list.push_back (*iter); + if (!region.empty() && regions.find(region) != regions.end()) + list.push_back(*iter); } return list; } -void CSVWorld::RegionMap::setRegion (const std::string& regionId) +void CSVWorld::RegionMap::setRegion(const std::string& regionId) { QModelIndexList selected = getSelectedCells(); - QAbstractItemModel *regionModel = model(); + QAbstractItemModel* regionModel = model(); - CSMWorld::IdTable *cellsModel = &dynamic_cast (* - mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + CSMWorld::IdTable* cellsModel + = &dynamic_cast(*mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); - QString regionId2 = QString::fromUtf8 (regionId.c_str()); + QString regionId2 = QString::fromUtf8(regionId.c_str()); - CSMWorld::CommandMacro macro (mDocument.getUndoStack(), selected.size()>1 ? tr ("Set Region") : ""); + CSMWorld::CommandMacro macro(mDocument.getUndoStack(), selected.size() > 1 ? tr("Set Region") : ""); - for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + for (QModelIndexList::const_iterator iter(selected.begin()); iter != selected.end(); ++iter) { - std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId). - toString().toUtf8().constData(); + std::string cellId = regionModel->data(*iter, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData(); - QModelIndex index = cellsModel->getModelIndex (cellId, - cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region)); + QModelIndex index + = cellsModel->getModelIndex(cellId, cellsModel->findColumnIndex(CSMWorld::Columns::ColumnId_Region)); - macro.push (new CSMWorld::ModifyCommand (*cellsModel, index, regionId2)); + macro.push(new CSMWorld::ModifyCommand(*cellsModel, index, regionId2)); } } -CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, - CSMDoc::Document& document, QWidget *parent) -: DragRecordTable(document, parent) +CSVWorld::RegionMap::RegionMap(const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, QWidget* parent) + : DragRecordTable(document, parent) { verticalHeader()->hide(); horizontalHeader()->hide(); - setSelectionMode (QAbstractItemView::ExtendedSelection); + setSelectionMode(QAbstractItemView::ExtendedSelection); - setModel (document.getData().getTableModel (universalId)); + setModel(document.getData().getTableModel(universalId)); resizeColumnsToContents(); resizeRowsToContents(); - mSelectAllAction = new QAction (tr ("Select All"), this); - connect (mSelectAllAction, SIGNAL (triggered()), this, SLOT (selectAll())); - addAction (mSelectAllAction); + mSelectAllAction = new QAction(tr("Select All"), this); + connect(mSelectAllAction, &QAction::triggered, this, &RegionMap::selectAll); + addAction(mSelectAllAction); - mClearSelectionAction = new QAction (tr ("Clear Selection"), this); - connect (mClearSelectionAction, SIGNAL (triggered()), this, SLOT (clearSelection())); - addAction (mClearSelectionAction); + mClearSelectionAction = new QAction(tr("Clear Selection"), this); + connect(mClearSelectionAction, &QAction::triggered, this, &RegionMap::clearSelection); + addAction(mClearSelectionAction); - mSelectRegionsAction = new QAction (tr ("Select Regions"), this); - connect (mSelectRegionsAction, SIGNAL (triggered()), this, SLOT (selectRegions())); - addAction (mSelectRegionsAction); + mSelectRegionsAction = new QAction(tr("Select Regions"), this); + connect(mSelectRegionsAction, &QAction::triggered, this, &RegionMap::selectRegions); + addAction(mSelectRegionsAction); - mCreateCellsAction = new QAction (tr ("Create Cells Action"), this); - connect (mCreateCellsAction, SIGNAL (triggered()), this, SLOT (createCells())); - addAction (mCreateCellsAction); + mCreateCellsAction = new QAction(tr("Create Cells Action"), this); + connect(mCreateCellsAction, &QAction::triggered, this, &RegionMap::createCells); + addAction(mCreateCellsAction); - mSetRegionAction = new QAction (tr ("Set Region"), this); - connect (mSetRegionAction, SIGNAL (triggered()), this, SLOT (setRegion())); - addAction (mSetRegionAction); + mSetRegionAction = new QAction(tr("Set Region"), this); + connect(mSetRegionAction, &QAction::triggered, this, qOverload<>(&RegionMap::setRegion)); + addAction(mSetRegionAction); - mUnsetRegionAction = new QAction (tr ("Unset Region"), this); - connect (mUnsetRegionAction, SIGNAL (triggered()), this, SLOT (unsetRegion())); - addAction (mUnsetRegionAction); + mUnsetRegionAction = new QAction(tr("Unset Region"), this); + connect(mUnsetRegionAction, &QAction::triggered, this, &RegionMap::unsetRegion); + addAction(mUnsetRegionAction); - mViewAction = new QAction (tr ("View Cells"), this); - connect (mViewAction, SIGNAL (triggered()), this, SLOT (view())); - addAction (mViewAction); + mViewAction = new QAction(tr("View Cells"), this); + connect(mViewAction, &QAction::triggered, this, &RegionMap::view); + addAction(mViewAction); - mViewInTableAction = new QAction (tr ("View Cells in Table"), this); - connect (mViewInTableAction, SIGNAL (triggered()), this, SLOT (viewInTable())); - addAction (mViewInTableAction); + mViewInTableAction = new QAction(tr("View Cells in Table"), this); + connect(mViewInTableAction, &QAction::triggered, this, &RegionMap::viewInTable); + addAction(mViewInTableAction); setAcceptDrops(true); } @@ -227,8 +230,8 @@ void CSVWorld::RegionMap::selectAll() { QModelIndexList unselected = getUnselectedCells(); - for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) - selectionModel()->select (*iter, QItemSelectionModel::Select); + for (QModelIndexList::const_iterator iter(unselected.begin()); iter != unselected.end(); ++iter) + selectionModel()->select(*iter, QItemSelectionModel::Select); } void CSVWorld::RegionMap::clearSelection() @@ -240,8 +243,8 @@ void CSVWorld::RegionMap::selectRegions() { QModelIndexList unselected = getMissingRegionCells(); - for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) - selectionModel()->select (*iter, QItemSelectionModel::Select); + for (QModelIndexList::const_iterator iter(unselected.begin()); iter != unselected.end(); ++iter) + selectionModel()->select(*iter, QItemSelectionModel::Select); } void CSVWorld::RegionMap::createCells() @@ -249,19 +252,18 @@ void CSVWorld::RegionMap::createCells() if (mEditLock) return; - QModelIndexList selected = getSelectedCells (false, true); + QModelIndexList selected = getSelectedCells(false, true); - CSMWorld::IdTable *cellsModel = &dynamic_cast (* - mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + CSMWorld::IdTable* cellsModel + = &dynamic_cast(*mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); - CSMWorld::CommandMacro macro (mDocument.getUndoStack(), selected.size()>1 ? tr ("Create cells"): ""); + CSMWorld::CommandMacro macro(mDocument.getUndoStack(), selected.size() > 1 ? tr("Create cells") : ""); - for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + for (QModelIndexList::const_iterator iter(selected.begin()); iter != selected.end(); ++iter) { - std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). - toString().toUtf8().constData(); + std::string cellId = model()->data(*iter, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData(); - macro.push (new CSMWorld::CreateCommand (*cellsModel, cellId)); + macro.push(new CSMWorld::CreateCommand(*cellsModel, cellId)); } } @@ -270,7 +272,7 @@ void CSVWorld::RegionMap::setRegion() if (mEditLock) return; - setRegion (mRegionId); + setRegion(mRegionId); } void CSVWorld::RegionMap::unsetRegion() @@ -278,7 +280,7 @@ void CSVWorld::RegionMap::unsetRegion() if (mEditLock) return; - setRegion (""); + setRegion(""); } void CSVWorld::RegionMap::view() @@ -290,10 +292,9 @@ void CSVWorld::RegionMap::view() bool first = true; - for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + for (QModelIndexList::const_iterator iter(selected.begin()); iter != selected.end(); ++iter) { - std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). - toString().toUtf8().constData(); + std::string cellId = model()->data(*iter, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData(); if (first) first = false; @@ -303,7 +304,8 @@ void CSVWorld::RegionMap::view() hint << cellId; } - emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace), + emit editRequest( + CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::Cell::sDefaultWorldspaceId.getValue()), hint.str()); } @@ -316,10 +318,9 @@ void CSVWorld::RegionMap::viewInTable() bool first = true; - for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + for (QModelIndexList::const_iterator iter(selected.begin()); iter != selected.end(); ++iter) { - std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). - toString().toUtf8().constData(); + std::string cellId = model()->data(*iter, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData(); if (first) first = false; @@ -331,66 +332,63 @@ void CSVWorld::RegionMap::viewInTable() hint << ")"; - emit editRequest (CSMWorld::UniversalId::Type_Cells, hint.str()); + emit editRequest(CSMWorld::UniversalId::Type_Cells, hint.str()); } -void CSVWorld::RegionMap::mouseMoveEvent (QMouseEvent* event) +void CSVWorld::RegionMap::mouseMoveEvent(QMouseEvent* event) { - startDragFromTable(*this); + startDragFromTable(*this, indexAt(event->pos())); } -std::vector< CSMWorld::UniversalId > CSVWorld::RegionMap::getDraggedRecords() const +std::vector CSVWorld::RegionMap::getDraggedRecords() const { QModelIndexList selected(getSelectedCells(true, false)); std::vector ids; for (const QModelIndex& it : selected) { - ids.emplace_back( - CSMWorld::UniversalId::Type_Cell, - model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData()); + ids.emplace_back(CSMWorld::UniversalId::Type_Cell, + model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData()); } selected = getSelectedCells(false, true); for (const QModelIndex& it : selected) { - ids.emplace_back( - CSMWorld::UniversalId::Type_Cell_Missing, - model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData()); + ids.emplace_back(CSMWorld::UniversalId::Type_Cell_Missing, + model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData()); } return ids; } -void CSVWorld::RegionMap::dropEvent (QDropEvent* event) +void CSVWorld::RegionMap::dropEvent(QDropEvent* event) { - QModelIndex index = indexAt (event->pos()); + QModelIndex index = indexAt(event->pos()); - bool exists = QTableView::model()->data(index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern); + bool exists = QTableView::model()->data(index, Qt::BackgroundRole) != QBrush(Qt::DiagCrossPattern); if (!index.isValid() || !exists) { return; } - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped return; if (mime->fromDocument(mDocument) && mime->holdsType(CSMWorld::UniversalId::Type_Region)) { - CSMWorld::UniversalId record (mime->returnMatching (CSMWorld::UniversalId::Type_Region)); + CSMWorld::UniversalId record(mime->returnMatching(CSMWorld::UniversalId::Type_Region)); - QAbstractItemModel *regionModel = model(); + QAbstractItemModel* regionModel = model(); - CSMWorld::IdTable *cellsModel = &dynamic_cast (* - mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + CSMWorld::IdTable* cellsModel + = &dynamic_cast(*mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); - std::string cellId(regionModel->data (index, CSMWorld::RegionMap::Role_CellId). - toString().toUtf8().constData()); + std::string cellId(regionModel->data(index, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData()); - QModelIndex index2(cellsModel->getModelIndex (cellId, - cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region))); + QModelIndex index2( + cellsModel->getModelIndex(cellId, cellsModel->findColumnIndex(CSMWorld::Columns::ColumnId_Region))); - mDocument.getUndoStack().push(new CSMWorld::ModifyCommand - (*cellsModel, index2, QString::fromUtf8(record.getId().c_str()))); + mDocument.getUndoStack().push( + new CSMWorld::ModifyCommand(*cellsModel, index2, QString::fromUtf8(record.getId().c_str()))); mRegionId = record.getId(); } diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index 443de9ce3..b6c5078ea 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -1,15 +1,19 @@ #ifndef CSV_WORLD_REGIONMAP_H #define CSV_WORLD_REGIONMAP_H -#include -#include +#include -#include -#include +#include +#include #include "./dragrecordtable.hpp" class QAction; +class QContextMenuEvent; +class QDropEvent; +class QMouseEvent; +class QObject; +class QWidget; namespace CSMDoc { @@ -25,67 +29,64 @@ namespace CSVWorld { class RegionMap : public DragRecordTable { - Q_OBJECT + Q_OBJECT - QAction *mSelectAllAction; - QAction *mClearSelectionAction; - QAction *mSelectRegionsAction; - QAction *mCreateCellsAction; - QAction *mSetRegionAction; - QAction *mUnsetRegionAction; - QAction *mViewAction; - QAction *mViewInTableAction; - std::string mRegionId; + QAction* mSelectAllAction; + QAction* mClearSelectionAction; + QAction* mSelectRegionsAction; + QAction* mCreateCellsAction; + QAction* mSetRegionAction; + QAction* mUnsetRegionAction; + QAction* mViewAction; + QAction* mViewInTableAction; + std::string mRegionId; - private: + private: + void contextMenuEvent(QContextMenuEvent* event) override; - void contextMenuEvent (QContextMenuEvent *event) override; + QModelIndexList getUnselectedCells() const; + ///< \note Non-existent cells are not listed. - QModelIndexList getUnselectedCells() const; - ///< \note Non-existent cells are not listed. + QModelIndexList getSelectedCells(bool existent = true, bool nonExistent = false) const; + ///< \param existent Include existent cells. + /// \param nonExistent Include non-existent cells. - QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const; - ///< \param existent Include existent cells. - /// \param nonExistent Include non-existent cells. + QModelIndexList getMissingRegionCells() const; + ///< Unselected cells within all regions that have at least one selected cell. - QModelIndexList getMissingRegionCells() const; - ///< Unselected cells within all regions that have at least one selected cell. + void setRegion(const std::string& regionId); + ///< Set region Id of selected cells. - void setRegion (const std::string& regionId); - ///< Set region Id of selected cells. + void mouseMoveEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent *event) override; + void dropEvent(QDropEvent* event) override; - void dropEvent(QDropEvent* event) override; + public: + RegionMap(const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, QWidget* parent = nullptr); - public: + std::vector getDraggedRecords() const override; - RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, - QWidget *parent = nullptr); + signals: - std::vector getDraggedRecords() const override; + void editRequest(const CSMWorld::UniversalId& id, const std::string& hint); - signals: + private slots: - void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); + void selectAll() override; - private slots: + void clearSelection(); - void selectAll() override; + void selectRegions(); - void clearSelection(); + void createCells(); - void selectRegions(); + void setRegion(); - void createCells(); + void unsetRegion(); - void setRegion(); + void view(); - void unsetRegion(); - - void view(); - - void viewInTable(); + void viewInTable(); }; } diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index 996d1dc8b..7b07f2b65 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -1,26 +1,25 @@ #include "regionmapsubview.hpp" +#include + #include "regionmap.hpp" -CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, - CSMDoc::Document& document) -: CSVDoc::SubView (universalId) +CSVWorld::RegionMapSubView::RegionMapSubView(CSMWorld::UniversalId universalId, CSMDoc::Document& document) + : CSVDoc::SubView(universalId) { - mRegionMap = new RegionMap (universalId, document, this); + mRegionMap = new RegionMap(universalId, document, this); - setWidget (mRegionMap); + setWidget(mRegionMap); - connect (mRegionMap, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), - this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&))); + connect(mRegionMap, &RegionMap::editRequest, this, &RegionMapSubView::editRequest); } -void CSVWorld::RegionMapSubView::setEditLock (bool locked) +void CSVWorld::RegionMapSubView::setEditLock(bool locked) { - mRegionMap->setEditLock (locked); + mRegionMap->setEditLock(locked); } -void CSVWorld::RegionMapSubView::editRequest (const CSMWorld::UniversalId& id, - const std::string& hint) +void CSVWorld::RegionMapSubView::editRequest(const CSMWorld::UniversalId& id, const std::string& hint) { - focusId (id, hint); + focusId(id, hint); } diff --git a/apps/opencs/view/world/regionmapsubview.hpp b/apps/opencs/view/world/regionmapsubview.hpp index 232d88fc6..3ac607a28 100644 --- a/apps/opencs/view/world/regionmapsubview.hpp +++ b/apps/opencs/view/world/regionmapsubview.hpp @@ -1,9 +1,11 @@ #ifndef CSV_WORLD_REGIONMAPSUBVIEW_H #define CSV_WORLD_REGIONMAPSUBVIEW_H +#include + #include "../doc/subview.hpp" -class QAction; +#include namespace CSMDoc { @@ -16,19 +18,18 @@ namespace CSVWorld class RegionMapSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - RegionMap *mRegionMap; + RegionMap* mRegionMap; - public: + public: + RegionMapSubView(CSMWorld::UniversalId universalId, CSMDoc::Document& document); - RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document); + void setEditLock(bool locked) override; - void setEditLock (bool locked) override; + private slots: - private slots: - - void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); + void editRequest(const CSMWorld::UniversalId& id, const std::string& hint); }; } diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 58d159a17..3b3ada43b 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -1,11 +1,18 @@ #include "scenesubview.hpp" +#include +#include +#include #include -#include #include -#include -#include +#include +#include +#include + +#include +#include +#include #include "../../model/doc/document.hpp" @@ -18,30 +25,33 @@ #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" -#include "../widget/scenetooltoggle.hpp" -#include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" +#include "../widget/scenetooltoggle2.hpp" -#include "tablebottombox.hpp" #include "creator.hpp" +#include "tablebottombox.hpp" -CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mScene(nullptr), mLayout(new QHBoxLayout), mDocument(document), mToolbar(nullptr) +CSVWorld::SceneSubView::SceneSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) + : SubView(id) + , mScene(nullptr) + , mLayout(new QHBoxLayout) + , mDocument(document) + , mToolbar(nullptr) { - QVBoxLayout *layout = new QVBoxLayout; + QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (mBottom = new TableBottomBox (NullCreatorFactory(), document, id, this), 0); + layout->addWidget(mBottom = new TableBottomBox(NullCreatorFactory(), document, id, this), 0); - mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); + mLayout->setContentsMargins(QMargins(0, 0, 0, 0)); CSVRender::WorldspaceWidget* worldspaceWidget = nullptr; widgetType whatWidget; - if (id.getId()==ESM::CellId::sDefaultWorldspace) + if (Misc::StringUtils::ciEqual(id.getId(), ESM::Cell::sDefaultWorldspaceId.getValue())) { whatWidget = widget_Paged; - CSVRender::PagedWorldspaceWidget *newWidget = new CSVRender::PagedWorldspaceWidget (this, document); + CSVRender::PagedWorldspaceWidget* newWidget = new CSVRender::PagedWorldspaceWidget(this, document); worldspaceWidget = newWidget; @@ -51,7 +61,8 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D { whatWidget = widget_Unpaged; - CSVRender::UnpagedWorldspaceWidget *newWidget = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); + CSVRender::UnpagedWorldspaceWidget* newWidget + = new CSVRender::UnpagedWorldspaceWidget(id.getId(), document, this); worldspaceWidget = newWidget; @@ -60,91 +71,85 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D replaceToolbarAndWorldspace(worldspaceWidget, makeToolbar(worldspaceWidget, whatWidget)); - layout->insertLayout (0, mLayout, 1); + layout->insertLayout(0, mLayout, 1); - CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); + CSVFilter::FilterBox* filterBox = new CSVFilter::FilterBox(document.getData(), this); - layout->insertWidget (0, filterBox); + layout->insertWidget(0, filterBox); - QWidget *widget = new QWidget; + QWidget* widget = new QWidget; - widget->setLayout (layout); + widget->setLayout(layout); - setWidget (widget); + setWidget(widget); } -void CSVWorld::SceneSubView::makeConnections (CSVRender::UnpagedWorldspaceWidget* widget) +void CSVWorld::SceneSubView::makeConnections(CSVRender::UnpagedWorldspaceWidget* widget) { - connect (widget, SIGNAL (closeRequest()), this, SLOT (closeRequest())); + connect(widget, &CSVRender::UnpagedWorldspaceWidget::closeRequest, this, qOverload<>(&SceneSubView::closeRequest)); - connect(widget, SIGNAL(dataDropped(const std::vector&)), - this, SLOT(handleDrop(const std::vector&))); + connect(widget, &CSVRender::UnpagedWorldspaceWidget::dataDropped, this, &SceneSubView::handleDrop); - connect(widget, SIGNAL(cellChanged(const CSMWorld::UniversalId&)), - this, SLOT(cellSelectionChanged(const CSMWorld::UniversalId&))); + connect(widget, &CSVRender::UnpagedWorldspaceWidget::cellChanged, this, + qOverload(&SceneSubView::cellSelectionChanged)); - connect(widget, SIGNAL(requestFocus (const std::string&)), - this, SIGNAL(requestFocus (const std::string&))); + connect(widget, &CSVRender::UnpagedWorldspaceWidget::requestFocus, this, &SceneSubView::requestFocus); } -void CSVWorld::SceneSubView::makeConnections (CSVRender::PagedWorldspaceWidget* widget) +void CSVWorld::SceneSubView::makeConnections(CSVRender::PagedWorldspaceWidget* widget) { - connect (widget, SIGNAL (closeRequest()), this, SLOT (closeRequest())); + connect(widget, &CSVRender::PagedWorldspaceWidget::closeRequest, this, qOverload<>(&SceneSubView::closeRequest)); - connect(widget, SIGNAL(dataDropped(const std::vector&)), - this, SLOT(handleDrop(const std::vector&))); + connect(widget, &CSVRender::PagedWorldspaceWidget::dataDropped, this, &SceneSubView::handleDrop); - connect (widget, SIGNAL (cellSelectionChanged (const CSMWorld::CellSelection&)), - this, SLOT (cellSelectionChanged (const CSMWorld::CellSelection&))); + connect(widget, &CSVRender::PagedWorldspaceWidget::cellSelectionChanged, this, + qOverload(&SceneSubView::cellSelectionChanged)); - connect(widget, SIGNAL(requestFocus (const std::string&)), - this, SIGNAL(requestFocus (const std::string&))); + connect(widget, &CSVRender::PagedWorldspaceWidget::requestFocus, this, &SceneSubView::requestFocus); } -CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::WorldspaceWidget* widget, widgetType type) +CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar(CSVRender::WorldspaceWidget* widget, widgetType type) { - CSVWidget::SceneToolbar* toolbar = new CSVWidget::SceneToolbar (48+6, this); + CSVWidget::SceneToolbar* toolbar = new CSVWidget::SceneToolbar(48 + 6, this); - CSVWidget::SceneToolMode *navigationTool = widget->makeNavigationSelector (toolbar); - toolbar->addTool (navigationTool); + CSVWidget::SceneToolMode* navigationTool = widget->makeNavigationSelector(toolbar); + toolbar->addTool(navigationTool); - CSVWidget::SceneToolMode *lightingTool = widget->makeLightingSelector (toolbar); - toolbar->addTool (lightingTool); + CSVWidget::SceneToolMode* lightingTool = widget->makeLightingSelector(toolbar); + toolbar->addTool(lightingTool); - CSVWidget::SceneToolToggle2 *sceneVisibilityTool = - widget->makeSceneVisibilitySelector (toolbar); - toolbar->addTool (sceneVisibilityTool); + CSVWidget::SceneToolToggle2* sceneVisibilityTool = widget->makeSceneVisibilitySelector(toolbar); + toolbar->addTool(sceneVisibilityTool); - if (type==widget_Paged) + if (type == widget_Paged) { - CSVWidget::SceneToolToggle2 *controlVisibilityTool = - dynamic_cast (*widget). - makeControlVisibilitySelector (toolbar); + CSVWidget::SceneToolToggle2* controlVisibilityTool + = dynamic_cast(*widget).makeControlVisibilitySelector(toolbar); - toolbar->addTool (controlVisibilityTool); + toolbar->addTool(controlVisibilityTool); } - CSVWidget::SceneToolRun *runTool = widget->makeRunTool (toolbar); - toolbar->addTool (runTool); + CSVWidget::SceneToolRun* runTool = widget->makeRunTool(toolbar); + toolbar->addTool(runTool); - toolbar->addTool (widget->makeEditModeSelector (toolbar), runTool); + toolbar->addTool(widget->makeEditModeSelector(toolbar), runTool); return toolbar; } -void CSVWorld::SceneSubView::setEditLock (bool locked) +void CSVWorld::SceneSubView::setEditLock(bool locked) { - mScene->setEditLock (locked); + mScene->setEditLock(locked); } -void CSVWorld::SceneSubView::setStatusBar (bool show) +void CSVWorld::SceneSubView::setStatusBar(bool show) { - mBottom->setStatusBar (show); + mBottom->setStatusBar(show); } -void CSVWorld::SceneSubView::useHint (const std::string& hint) +void CSVWorld::SceneSubView::useHint(const std::string& hint) { - mScene->useViewHint (hint); + mScene->useViewHint(hint); } std::string CSVWorld::SceneSubView::getTitle() const @@ -152,56 +157,57 @@ std::string CSVWorld::SceneSubView::getTitle() const return mTitle; } -void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id) +void CSVWorld::SceneSubView::cellSelectionChanged(const CSMWorld::UniversalId& id) { setUniversalId(id); mTitle = "Scene: " + getUniversalId().getId(); - setWindowTitle (QString::fromUtf8 (mTitle.c_str())); + setWindowTitle(QString::fromUtf8(mTitle.c_str())); emit updateTitle(); } -void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection& selection) +void CSVWorld::SceneSubView::cellSelectionChanged(const CSMWorld::CellSelection& selection) { - setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace)); + setUniversalId( + CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::Cell::sDefaultWorldspaceId.getValue())); int size = selection.getSize(); std::ostringstream stream; stream << "Scene: " << getUniversalId().getId(); - if (size==0) + if (size == 0) stream << " (empty)"; - else if (size==1) + else if (size == 1) { stream << " (" << *selection.begin() << ")"; } else { - stream << " (" << selection.getCentre() << " and " << size-1 << " more "; + stream << " (" << selection.getCentre() << " and " << size - 1 << " more "; - if (size>1) + if (size > 1) stream << "cells around it)"; else stream << "cell around it)"; } mTitle = stream.str(); - setWindowTitle (QString::fromUtf8 (mTitle.c_str())); + setWindowTitle(QString::fromUtf8(mTitle.c_str())); emit updateTitle(); } -void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalId >& universalIdData) +void CSVWorld::SceneSubView::handleDrop(const std::vector& universalIdData) { CSVRender::PagedWorldspaceWidget* pagedNewWidget = nullptr; CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = nullptr; CSVWidget::SceneToolbar* toolbar = nullptr; - CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType (universalIdData); + CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType(universalIdData); - switch (mScene->getDropRequirements (type)) + switch (mScene->getDropRequirements(type)) { case CSVRender::WorldspaceWidget::canHandle: - mScene->handleDrop (universalIdData, type); + mScene->handleDrop(universalIdData, type); break; case CSVRender::WorldspaceWidget::needPaged: @@ -209,11 +215,12 @@ void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalI toolbar = makeToolbar(pagedNewWidget, widget_Paged); makeConnections(pagedNewWidget); replaceToolbarAndWorldspace(pagedNewWidget, toolbar); - mScene->handleDrop (universalIdData, type); + mScene->handleDrop(universalIdData, type); break; case CSVRender::WorldspaceWidget::needUnpaged: - unPagedNewWidget = new CSVRender::UnpagedWorldspaceWidget(universalIdData.begin()->getId(), mDocument, this); + unPagedNewWidget + = new CSVRender::UnpagedWorldspaceWidget(universalIdData.begin()->getId(), mDocument, this); toolbar = makeToolbar(unPagedNewWidget, widget_Unpaged); makeConnections(unPagedNewWidget); replaceToolbarAndWorldspace(unPagedNewWidget, toolbar); @@ -225,7 +232,8 @@ void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalI } } -void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceWidget* widget, CSVWidget::SceneToolbar* toolbar) +void CSVWorld::SceneSubView::replaceToolbarAndWorldspace( + CSVRender::WorldspaceWidget* widget, CSVWidget::SceneToolbar* toolbar) { assert(mLayout); @@ -244,12 +252,14 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mScene = widget; mToolbar = toolbar; - connect (mScene, SIGNAL (focusToolbarRequest()), mToolbar, SLOT (setFocus())); - connect (mToolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); + connect(mScene, &CSVRender::WorldspaceWidget::focusToolbarRequest, mToolbar, + qOverload<>(&CSVWidget::SceneToolbar::setFocus)); + connect(mToolbar, &CSVWidget::SceneToolbar::focusSceneRequest, mScene, + qOverload<>(&CSVRender::WorldspaceWidget::setFocus)); - mLayout->addWidget (mToolbar, 0); - mLayout->addWidget (mScene, 1); + mLayout->addWidget(mToolbar, 0); + mLayout->addWidget(mScene, 1); mScene->selectDefaultNavigationMode(); - setFocusProxy (mScene); + setFocusProxy(mScene); } diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index aabb7ca2a..7248efd14 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -3,9 +3,12 @@ #include -#include "../doc/subview.hpp" +#include +#include -class QModelIndex; +#include + +#include "../doc/subview.hpp" namespace CSMWorld { @@ -27,65 +30,60 @@ namespace CSVRender namespace CSVWidget { class SceneToolbar; - class SceneToolMode; } namespace CSVWorld { - class Table; class TableBottomBox; - class CreatorFactoryBase; class SceneSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - TableBottomBox *mBottom; - CSVRender::WorldspaceWidget *mScene; - QHBoxLayout* mLayout; - CSMDoc::Document& mDocument; - CSVWidget::SceneToolbar* mToolbar; - std::string mTitle; + TableBottomBox* mBottom; + CSVRender::WorldspaceWidget* mScene; + QHBoxLayout* mLayout; + CSMDoc::Document& mDocument; + CSVWidget::SceneToolbar* mToolbar; + std::string mTitle; - public: + public: + SceneSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document); - SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + void setEditLock(bool locked) override; - void setEditLock (bool locked) override; + void setStatusBar(bool show) override; - void setStatusBar (bool show) override; + void useHint(const std::string& hint) override; - void useHint (const std::string& hint) override; + std::string getTitle() const override; - std::string getTitle() const override; + private: + void makeConnections(CSVRender::PagedWorldspaceWidget* widget); - private: + void makeConnections(CSVRender::UnpagedWorldspaceWidget* widget); - void makeConnections(CSVRender::PagedWorldspaceWidget* widget); + void replaceToolbarAndWorldspace(CSVRender::WorldspaceWidget* widget, CSVWidget::SceneToolbar* toolbar); - void makeConnections(CSVRender::UnpagedWorldspaceWidget* widget); + enum widgetType + { + widget_Paged, + widget_Unpaged + }; - void replaceToolbarAndWorldspace(CSVRender::WorldspaceWidget* widget, CSVWidget::SceneToolbar* toolbar); + CSVWidget::SceneToolbar* makeToolbar(CSVRender::WorldspaceWidget* widget, widgetType type); - enum widgetType - { - widget_Paged, - widget_Unpaged - }; + private slots: - CSVWidget::SceneToolbar* makeToolbar(CSVRender::WorldspaceWidget* widget, widgetType type); + void cellSelectionChanged(const CSMWorld::CellSelection& selection); - private slots: + void cellSelectionChanged(const CSMWorld::UniversalId& id); - void cellSelectionChanged (const CSMWorld::CellSelection& selection); + void handleDrop(const std::vector& data); - void cellSelectionChanged (const CSMWorld::UniversalId& id); + signals: - void handleDrop(const std::vector& data); - - signals: - - void requestFocus (const std::string& id); + void requestFocus(const std::string& id); }; } diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 743df9c76..c8505f007 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -1,22 +1,27 @@ #include "scriptedit.hpp" -#include +#include #include -#include -#include -#include -#include #include +#include +#include +#include + +#include +#include +#include +#include #include "../../model/doc/document.hpp" -#include "../../model/world/universalid.hpp" -#include "../../model/world/tablemimedata.hpp" -#include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/state.hpp" +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/universalid.hpp" -CSVWorld::ScriptEdit::ChangeLock::ChangeLock (ScriptEdit& edit) : mEdit (edit) +CSVWorld::ScriptEdit::ChangeLock::ChangeLock(ScriptEdit& edit) + : mEdit(edit) { ++mEdit.mChangeLocked; } @@ -26,95 +31,77 @@ CSVWorld::ScriptEdit::ChangeLock::~ChangeLock() --mEdit.mChangeLocked; } -bool CSVWorld::ScriptEdit::event (QEvent *event) +bool CSVWorld::ScriptEdit::event(QEvent* event) { // ignore undo and redo shortcuts - if (event->type()==QEvent::ShortcutOverride) + if (event->type() == QEvent::ShortcutOverride) { - QKeyEvent *keyEvent = static_cast (event); + QKeyEvent* keyEvent = static_cast(event); - if (keyEvent->matches (QKeySequence::Undo) || keyEvent->matches (QKeySequence::Redo)) + if (keyEvent->matches(QKeySequence::Undo) || keyEvent->matches(QKeySequence::Redo)) return true; } - return QPlainTextEdit::event (event); + return QPlainTextEdit::event(event); } -CSVWorld::ScriptEdit::ScriptEdit( - const CSMDoc::Document& document, - ScriptHighlighter::Mode mode, - QWidget* parent -) : QPlainTextEdit(parent), - mChangeLocked(0), - mShowLineNum(false), - mLineNumberArea(nullptr), - mDefaultFont(font()), - mMonoFont(QFont("Monospace")), - mTabCharCount(4), - mMarkOccurrences(true), - mDocument(document), - mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive) +CSVWorld::ScriptEdit::ScriptEdit(const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent) + : QPlainTextEdit(parent) + , mChangeLocked(0) + , mShowLineNum(false) + , mLineNumberArea(nullptr) + , mDefaultFont(font()) + , mMonoFont(QFont("Monospace")) + , mTabCharCount(4) + , mMarkOccurrences(true) + , mDocument(document) + , mWhiteListQuotes("^[a-zA-Z_][a-zA-Z0-9_]*$") { wrapLines(false); setTabWidth(); - setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead + setUndoRedoEnabled(false); // we use OpenCS-wide undo/redo instead - mAllowedTypes <associateAction(mCommentAction); - mUncommentAction = new QAction (tr ("Uncomment Selection"), this); - connect(mUncommentAction, SIGNAL (triggered()), this, SLOT (uncommentSelection())); - CSMPrefs::Shortcut *uncommentShortcut = new CSMPrefs::Shortcut("script-editor-uncomment", this); + mUncommentAction = new QAction(tr("Uncomment Selection"), this); + connect(mUncommentAction, &QAction::triggered, this, &ScriptEdit::uncommentSelection); + CSMPrefs::Shortcut* uncommentShortcut = new CSMPrefs::Shortcut("script-editor-uncomment", this); uncommentShortcut->associateAction(mUncommentAction); - mHighlighter = new ScriptHighlighter (document.getData(), mode, ScriptEdit::document()); + mHighlighter = new ScriptHighlighter(document.getData(), mode, ScriptEdit::document()); - connect (&document.getData(), SIGNAL (idListChanged()), this, SLOT (idListChanged())); + connect(&document.getData(), &CSMWorld::Data::idListChanged, this, &ScriptEdit::idListChanged); - connect (&mUpdateTimer, SIGNAL (timeout()), this, SLOT (updateHighlighting())); + connect(&mUpdateTimer, &QTimer::timeout, this, &ScriptEdit::updateHighlighting); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &ScriptEdit::settingChanged); { - ChangeLock lock (*this); + ChangeLock lock(*this); CSMPrefs::get()["Scripts"].update(); } - mUpdateTimer.setSingleShot (true); + mUpdateTimer.setSingleShot(true); // TODO: provide a font selector dialogue mMonoFont.setStyleHint(QFont::TypeWriter); @@ -122,14 +109,14 @@ CSVWorld::ScriptEdit::ScriptEdit( mLineNumberArea = new LineNumberArea(this); updateLineNumberAreaWidth(0); - connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); - connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); + connect(this, &ScriptEdit::blockCountChanged, this, &ScriptEdit::updateLineNumberAreaWidth); + connect(this, &ScriptEdit::updateRequest, this, &ScriptEdit::updateLineNumberArea); updateHighlighting(); } void CSVWorld::ScriptEdit::showLineNum(bool show) { - if(show!=mShowLineNum) + if (show != mShowLineNum) { mShowLineNum = show; updateLineNumberAreaWidth(0); @@ -138,68 +125,70 @@ void CSVWorld::ScriptEdit::showLineNum(bool show) bool CSVWorld::ScriptEdit::isChangeLocked() const { - return mChangeLocked!=0; + return mChangeLocked != 0; } -void CSVWorld::ScriptEdit::dragEnterEvent (QDragEnterEvent* event) +void CSVWorld::ScriptEdit::dragEnterEvent(QDragEnterEvent* event) { - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (!mime) QPlainTextEdit::dragEnterEvent(event); else { - setTextCursor (cursorForPosition (event->pos())); + setTextCursor(cursorForPosition(event->pos())); event->acceptProposedAction(); } } -void CSVWorld::ScriptEdit::dragMoveEvent (QDragMoveEvent* event) +void CSVWorld::ScriptEdit::dragMoveEvent(QDragMoveEvent* event) { - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (!mime) QPlainTextEdit::dragMoveEvent(event); else { - setTextCursor (cursorForPosition (event->pos())); + setTextCursor(cursorForPosition(event->pos())); event->accept(); } } -void CSVWorld::ScriptEdit::dropEvent (QDropEvent* event) +void CSVWorld::ScriptEdit::dropEvent(QDropEvent* event) { - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped { QPlainTextEdit::dropEvent(event); return; } - setTextCursor (cursorForPosition (event->pos())); + setTextCursor(cursorForPosition(event->pos())); - if (mime->fromDocument (mDocument)) + if (mime->fromDocument(mDocument)) { - std::vector records (mime->getData()); + std::vector records(mime->getData()); for (std::vector::iterator it = records.begin(); it != records.end(); ++it) { - if (mAllowedTypes.contains (it->getType())) + if (mAllowedTypes.contains(it->getType())) { if (stringNeedsQuote(it->getId())) { - insertPlainText(QString::fromUtf8 (('"' + it->getId() + '"').c_str())); - } else { - insertPlainText(QString::fromUtf8 (it->getId().c_str())); + insertPlainText(QString::fromUtf8(('"' + it->getId() + '"').c_str())); + } + else + { + insertPlainText(QString::fromUtf8(it->getId().c_str())); } } } } } -bool CSVWorld::ScriptEdit::stringNeedsQuote (const std::string& id) const +bool CSVWorld::ScriptEdit::stringNeedsQuote(const std::string& id) const { - const QString string(QString::fromUtf8(id.c_str())); // is only for c++11, so let's use qregexp for now. - //I'm not quite sure when do we need to put quotes. To be safe we will use quotes for anything other than… - return !(string.contains(mWhiteListQoutes)); + const QString string(QString::fromUtf8(id.c_str())); + // I'm not quite sure when do we need to put quotes. To be safe we will use quotes for anything other than… + return !(string.contains(mWhiteListQuotes)); } void CSVWorld::ScriptEdit::setTabWidth() @@ -220,7 +209,7 @@ void CSVWorld::ScriptEdit::wrapLines(bool wrap) } } -void CSVWorld::ScriptEdit::settingChanged(const CSMPrefs::Setting *setting) +void CSVWorld::ScriptEdit::settingChanged(const CSMPrefs::Setting* setting) { // Determine which setting was changed. if (mHighlighter->settingChanged(setting)) @@ -259,7 +248,7 @@ void CSVWorld::ScriptEdit::idListChanged() mHighlighter->invalidateIds(); if (!mUpdateTimer.isActive()) - mUpdateTimer.start (0); + mUpdateTimer.start(0); } void CSVWorld::ScriptEdit::updateHighlighting() @@ -267,14 +256,14 @@ void CSVWorld::ScriptEdit::updateHighlighting() if (isChangeLocked()) return; - ChangeLock lock (*this); + ChangeLock lock(*this); mHighlighter->rehighlight(); } int CSVWorld::ScriptEdit::lineNumberAreaWidth() { - if(!mShowLineNum) + if (!mShowLineNum) return 0; int digits = 1; @@ -294,7 +283,7 @@ void CSVWorld::ScriptEdit::updateLineNumberAreaWidth(int /* newBlockCount */) setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); } -void CSVWorld::ScriptEdit::updateLineNumberArea(const QRect &rect, int dy) +void CSVWorld::ScriptEdit::updateLineNumberArea(const QRect& rect, int dy) { if (dy) mLineNumberArea->scroll(0, dy); @@ -314,16 +303,16 @@ void CSVWorld::ScriptEdit::markOccurrences() // prevent infinite recursion with cursor.select(), // which ends up calling this function again // could be fixed with blockSignals, but mDocument is const - disconnect(this, SIGNAL(cursorPositionChanged()), this, nullptr); + disconnect(this, &ScriptEdit::cursorPositionChanged, this, nullptr); cursor.select(QTextCursor::WordUnderCursor); - connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(markOccurrences())); + connect(this, &ScriptEdit::cursorPositionChanged, this, &ScriptEdit::markOccurrences); QString word = cursor.selectedText(); mHighlighter->setMarkedWord(word.toStdString()); mHighlighter->rehighlight(); } } - + void CSVWorld::ScriptEdit::commentSelection() { QTextCursor begin = textCursor(); @@ -356,7 +345,8 @@ void CSVWorld::ScriptEdit::uncommentSelection() begin.beginEditBlock(); - for (; begin < end; begin.movePosition(QTextCursor::EndOfLine), begin.movePosition(QTextCursor::Right)) { + for (; begin < end; begin.movePosition(QTextCursor::EndOfLine), begin.movePosition(QTextCursor::Right)) + { begin.select(QTextCursor::LineUnderCursor); QString line = begin.selectedText(); @@ -383,7 +373,7 @@ void CSVWorld::ScriptEdit::uncommentSelection() begin.endEditBlock(); } -void CSVWorld::ScriptEdit::resizeEvent(QResizeEvent *e) +void CSVWorld::ScriptEdit::resizeEvent(QResizeEvent* e) { QPlainTextEdit::resizeEvent(e); @@ -391,9 +381,9 @@ void CSVWorld::ScriptEdit::resizeEvent(QResizeEvent *e) mLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); } -void CSVWorld::ScriptEdit::contextMenuEvent(QContextMenuEvent *event) +void CSVWorld::ScriptEdit::contextMenuEvent(QContextMenuEvent* event) { - QMenu *menu = createStandardContextMenu(); + QMenu* menu = createStandardContextMenu(); // remove redo/undo since they are disabled QList menuActions = menu->actions(); @@ -411,22 +401,22 @@ void CSVWorld::ScriptEdit::contextMenuEvent(QContextMenuEvent *event) delete menu; } -void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event) +void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent* event) { QPainter painter(mLineNumberArea); QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); - int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); - int bottom = top + (int) blockBoundingRect(block).height(); + int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int)blockBoundingRect(block).height(); int startBlock = textCursor().blockNumber(); int endBlock = textCursor().blockNumber(); - if(textCursor().hasSelection()) + if (textCursor().hasSelection()) { QString str = textCursor().selection().toPlainText(); int offset = str.count("\n"); - if(textCursor().position() < textCursor().anchor()) + if (textCursor().position() < textCursor().anchor()) endBlock += offset; else startBlock -= offset; @@ -441,7 +431,7 @@ void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event) { QFont newFont = painter.font(); QString number = QString::number(blockNumber + 1); - if(blockNumber >= startBlock && blockNumber <= endBlock) + if (blockNumber >= startBlock && blockNumber <= endBlock) { painter.setBackground(Qt::cyan); painter.setPen(Qt::darkMagenta); @@ -453,27 +443,29 @@ void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event) painter.setPen(Qt::black); } painter.setFont(newFont); - painter.drawText(0, top, mLineNumberArea->width(), fontMetrics().height(), - Qt::AlignRight, number); + painter.drawText(0, top, mLineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number); painter.setFont(font); } block = block.next(); top = bottom; - bottom = top + (int) blockBoundingRect(block).height(); + bottom = top + (int)blockBoundingRect(block).height(); ++blockNumber; } } -CSVWorld::LineNumberArea::LineNumberArea(ScriptEdit *editor) : QWidget(editor), mScriptEdit(editor) -{} +CSVWorld::LineNumberArea::LineNumberArea(ScriptEdit* editor) + : QWidget(editor) + , mScriptEdit(editor) +{ +} QSize CSVWorld::LineNumberArea::sizeHint() const { return QSize(mScriptEdit->lineNumberAreaWidth(), 0); } -void CSVWorld::LineNumberArea::paintEvent(QPaintEvent *event) +void CSVWorld::LineNumberArea::paintEvent(QPaintEvent* event) { mScriptEdit->lineNumberAreaPaintEvent(event); } diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index 21fabee58..53fa88ced 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -1,18 +1,34 @@ #ifndef SCRIPTEDIT_H #define SCRIPTEDIT_H -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include + +#include #include "../../model/world/universalid.hpp" #include "scripthighlighter.hpp" -class QRegExp; +class QAction; +class QContextMenuEvent; +class QDragEnterEvent; +class QDragMoveEvent; +class QDropEvent; +class QEvent; +class QObject; +class QPaintEvent; +class QRect; +class QResizeEvent; + +namespace CSMPrefs +{ + class Setting; +} namespace CSMDoc { @@ -26,118 +42,107 @@ namespace CSVWorld /// \brief Editor for scripts. class ScriptEdit : public QPlainTextEdit { - Q_OBJECT + Q_OBJECT + + public: + class ChangeLock + { + ScriptEdit& mEdit; + + ChangeLock(const ChangeLock&); + ChangeLock& operator=(const ChangeLock&); public: + ChangeLock(ScriptEdit& edit); + ~ChangeLock(); + }; - class ChangeLock - { - ScriptEdit& mEdit; + friend class ChangeLock; - ChangeLock (const ChangeLock&); - ChangeLock& operator= (const ChangeLock&); + private: + int mChangeLocked; + ScriptHighlighter* mHighlighter; + QTimer mUpdateTimer; + bool mShowLineNum; + LineNumberArea* mLineNumberArea; + QFont mDefaultFont; + QFont mMonoFont; + int mTabCharCount; + bool mMarkOccurrences; + QAction* mCommentAction; + QAction* mUncommentAction; - public: + protected: + bool event(QEvent* event) override; - ChangeLock (ScriptEdit& edit); - ~ChangeLock(); - }; + public: + ScriptEdit(const CSMDoc::Document& document, ScriptHighlighter::Mode mode, QWidget* parent); - friend class ChangeLock; + /// Should changes to the data be ignored (i.e. not cause updated)? + /// + /// \note This mechanism is used to avoid infinite update recursions + bool isChangeLocked() const; - private: + void lineNumberAreaPaintEvent(QPaintEvent* event); + int lineNumberAreaWidth(); + void showLineNum(bool show); - int mChangeLocked; - ScriptHighlighter *mHighlighter; - QTimer mUpdateTimer; - bool mShowLineNum; - LineNumberArea *mLineNumberArea; - QFont mDefaultFont; - QFont mMonoFont; - int mTabCharCount; - bool mMarkOccurrences; - QAction *mCommentAction; - QAction *mUncommentAction; + protected: + void resizeEvent(QResizeEvent* e) override; - protected: + void contextMenuEvent(QContextMenuEvent* event) override; - bool event (QEvent *event) override; + private: + QVector mAllowedTypes; + const CSMDoc::Document& mDocument; + const QRegularExpression mWhiteListQuotes; - public: + void dragEnterEvent(QDragEnterEvent* event) override; - ScriptEdit (const CSMDoc::Document& document, ScriptHighlighter::Mode mode, - QWidget* parent); + void dropEvent(QDropEvent* event) override; - /// Should changes to the data be ignored (i.e. not cause updated)? - /// - /// \note This mechanism is used to avoid infinite update recursions - bool isChangeLocked() const; + void dragMoveEvent(QDragMoveEvent* event) override; - void lineNumberAreaPaintEvent(QPaintEvent *event); - int lineNumberAreaWidth(); - void showLineNum(bool show); + bool stringNeedsQuote(const std::string& id) const; - protected: + /// \brief Set tab width for script editor. + void setTabWidth(); - void resizeEvent(QResizeEvent *e) override; + /// \brief Turn line wrapping in script editor on or off. + /// \param wrap Whether or not to wrap lines. + void wrapLines(bool wrap); - void contextMenuEvent(QContextMenuEvent *event) override; + private slots: - private: + /// \brief Update editor when related setting is changed. + /// \param setting Setting that was changed. + void settingChanged(const CSMPrefs::Setting* setting); - QVector mAllowedTypes; - const CSMDoc::Document& mDocument; - const QRegExp mWhiteListQoutes; + void idListChanged(); - void dragEnterEvent (QDragEnterEvent* event) override; + void updateHighlighting(); - void dropEvent (QDropEvent* event) override; + void updateLineNumberAreaWidth(int newBlockCount); - void dragMoveEvent (QDragMoveEvent* event) override; + void updateLineNumberArea(const QRect&, int); - bool stringNeedsQuote(const std::string& id) const; + void markOccurrences(); - /// \brief Set tab width for script editor. - void setTabWidth(); + void commentSelection(); - /// \brief Turn line wrapping in script editor on or off. - /// \param wrap Whether or not to wrap lines. - void wrapLines(bool wrap); - - private slots: - - /// \brief Update editor when related setting is changed. - /// \param setting Setting that was changed. - void settingChanged(const CSMPrefs::Setting *setting); - - void idListChanged(); - - void updateHighlighting(); - - void updateLineNumberAreaWidth(int newBlockCount); - - void updateLineNumberArea(const QRect &, int); - - void markOccurrences(); - - void commentSelection(); - - void uncommentSelection(); - + void uncommentSelection(); }; class LineNumberArea : public QWidget { - ScriptEdit *mScriptEdit; + ScriptEdit* mScriptEdit; - public: + public: + LineNumberArea(ScriptEdit* editor); + QSize sizeHint() const override; - LineNumberArea(ScriptEdit *editor); - QSize sizeHint() const override; - - protected: - - void paintEvent(QPaintEvent *event) override; + protected: + void paintEvent(QPaintEvent* event) override; }; } #endif // SCRIPTEDIT_H diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index 45809b28c..4bd8c3f39 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -1,119 +1,131 @@ #include "scripterrortable.hpp" +#include +#include + #include -#include -#include -#include +#include +#include +#include +#include + #include #include +#include +#include +#include #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" -void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) +void CSVWorld::ScriptErrorTable::report(const std::string& message, const Compiler::TokenLoc& loc, Type type) { std::ostringstream stream; stream << message << " (" << loc.mLiteral << ")"; - addMessage (stream.str(), type==Compiler::ErrorHandler::WarningMessage ? - CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, - loc.mLine, loc.mColumn-loc.mLiteral.length()); + addMessage(stream.str(), + type == Compiler::ErrorHandler::WarningMessage ? CSMDoc::Message::Severity_Warning + : CSMDoc::Message::Severity_Error, + loc.mLine, loc.mColumn - loc.mLiteral.length()); } -void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type) +void CSVWorld::ScriptErrorTable::report(const std::string& message, Type type) { - addMessage (message, type==Compiler::ErrorHandler::WarningMessage ? - CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error); + addMessage(message, + type == Compiler::ErrorHandler::WarningMessage ? CSMDoc::Message::Severity_Warning + : CSMDoc::Message::Severity_Error); } -void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, - CSMDoc::Message::Severity severity, int line, int column) +void CSVWorld::ScriptErrorTable::addMessage( + const std::string& message, CSMDoc::Message::Severity severity, int line, int column) { int row = rowCount(); - setRowCount (row+1); + setRowCount(row + 1); - QTableWidgetItem *severityItem = new QTableWidgetItem ( - QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str())); - severityItem->setFlags (severityItem->flags() ^ Qt::ItemIsEditable); - setItem (row, 0, severityItem); + QTableWidgetItem* severityItem + = new QTableWidgetItem(QString::fromUtf8(CSMDoc::Message::toString(severity).c_str())); + severityItem->setFlags(severityItem->flags() ^ Qt::ItemIsEditable); + setItem(row, 0, severityItem); - if (line!=-1) + if (line != -1) { - QTableWidgetItem *lineItem = new QTableWidgetItem; - lineItem->setData (Qt::DisplayRole, line+1); - lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); - setItem (row, 1, lineItem); + QTableWidgetItem* lineItem = new QTableWidgetItem; + lineItem->setData(Qt::DisplayRole, line + 1); + lineItem->setFlags(lineItem->flags() ^ Qt::ItemIsEditable); + setItem(row, 1, lineItem); - QTableWidgetItem *columnItem = new QTableWidgetItem; - columnItem->setData (Qt::DisplayRole, column); - columnItem->setFlags (columnItem->flags() ^ Qt::ItemIsEditable); - setItem (row, 3, columnItem); + QTableWidgetItem* columnItem = new QTableWidgetItem; + columnItem->setData(Qt::DisplayRole, column); + columnItem->setFlags(columnItem->flags() ^ Qt::ItemIsEditable); + setItem(row, 3, columnItem); } else { - QTableWidgetItem *lineItem = new QTableWidgetItem; - lineItem->setData (Qt::DisplayRole, "-"); - lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); - setItem (row, 1, lineItem); + QTableWidgetItem* lineItem = new QTableWidgetItem; + lineItem->setData(Qt::DisplayRole, "-"); + lineItem->setFlags(lineItem->flags() ^ Qt::ItemIsEditable); + setItem(row, 1, lineItem); } - QTableWidgetItem *messageItem = new QTableWidgetItem (QString::fromUtf8 (message.c_str())); - messageItem->setFlags (messageItem->flags() ^ Qt::ItemIsEditable); - setItem (row, 2, messageItem); + QTableWidgetItem* messageItem = new QTableWidgetItem(QString::fromUtf8(message.c_str())); + messageItem->setFlags(messageItem->flags() ^ Qt::ItemIsEditable); + setItem(row, 2, messageItem); } -void CSVWorld::ScriptErrorTable::setWarningsMode (const std::string& value) +void CSVWorld::ScriptErrorTable::setWarningsMode(const std::string& value) { - if (value=="Ignore") - Compiler::ErrorHandler::setWarningsMode (0); - else if (value=="Normal") - Compiler::ErrorHandler::setWarningsMode (1); - else if (value=="Strict") - Compiler::ErrorHandler::setWarningsMode (2); + if (value == "Ignore") + Compiler::ErrorHandler::setWarningsMode(0); + else if (value == "Normal") + Compiler::ErrorHandler::setWarningsMode(1); + else if (value == "Strict") + Compiler::ErrorHandler::setWarningsMode(2); } -CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent) -: QTableWidget (parent), mContext (document.getData()) +CSVWorld::ScriptErrorTable::ScriptErrorTable(const CSMDoc::Document& document, QWidget* parent) + : QTableWidget(parent) + , mContext(document.getData()) { - setColumnCount (4); + setColumnCount(4); QStringList headers; - headers << "Severity" << "Line" << "Description"; - setHorizontalHeaderLabels (headers); - horizontalHeader()->setSectionResizeMode (0, QHeaderView::ResizeToContents); - horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); - horizontalHeader()->setStretchLastSection (true); + headers << "Severity" + << "Line" + << "Description"; + setHorizontalHeaderLabels(headers); + horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + horizontalHeader()->setStretchLastSection(true); verticalHeader()->hide(); - setColumnHidden (3, true); + setColumnHidden(3, true); - setSelectionMode (QAbstractItemView::NoSelection); + setSelectionMode(QAbstractItemView::NoSelection); - Compiler::registerExtensions (mExtensions); - mContext.setExtensions (&mExtensions); + Compiler::registerExtensions(mExtensions); + mContext.setExtensions(&mExtensions); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &ScriptErrorTable::settingChanged); CSMPrefs::get()["Scripts"].update(); - connect (this, SIGNAL (cellClicked (int, int)), this, SLOT (cellClicked (int, int))); + connect(this, &QTableWidget::cellClicked, this, &ScriptErrorTable::cellClicked); } -void CSVWorld::ScriptErrorTable::update (const std::string& source) +void CSVWorld::ScriptErrorTable::update(const std::string& source) { clear(); try { - std::istringstream input (source); + std::istringstream input(source); - Compiler::Scanner scanner (*this, input, mContext.getExtensions()); + Compiler::Scanner scanner(*this, input, mContext.getExtensions()); - Compiler::FileParser parser (*this, mContext); + Compiler::FileParser parser(*this, mContext); - scanner.scan (parser); + scanner.scan(parser); } catch (const Compiler::SourceException&) { @@ -121,32 +133,32 @@ void CSVWorld::ScriptErrorTable::update (const std::string& source) } catch (const std::exception& error) { - addMessage (error.what(), CSMDoc::Message::Severity_SeriousError); + addMessage(error.what(), CSMDoc::Message::Severity_SeriousError); } } void CSVWorld::ScriptErrorTable::clear() { - setRowCount (0); + setRowCount(0); } -bool CSVWorld::ScriptErrorTable::clearLocals (const std::string& script) +bool CSVWorld::ScriptErrorTable::clearLocals(const std::string& script) { - return mContext.clearLocals (script); + return mContext.clearLocals(script); } -void CSVWorld::ScriptErrorTable::settingChanged (const CSMPrefs::Setting *setting) +void CSVWorld::ScriptErrorTable::settingChanged(const CSMPrefs::Setting* setting) { - if (*setting=="Scripts/warnings") - setWarningsMode (setting->toString()); + if (*setting == "Scripts/warnings") + setWarningsMode(setting->toString()); } -void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) +void CSVWorld::ScriptErrorTable::cellClicked(int row, int column) { - if (item (row, 3)) + if (item(row, 3)) { - int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt(); - int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt(); - emit highlightError (scriptLine-1, scriptColumn); + int scriptLine = item(row, 1)->data(Qt::DisplayRole).toInt(); + int scriptColumn = item(row, 3)->data(Qt::DisplayRole).toInt(); + emit highlightError(scriptLine - 1, scriptColumn); } } diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index 7165d0fc6..d05449a4f 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -3,11 +3,18 @@ #include +#include + #include #include -#include "../../model/world/scriptcontext.hpp" #include "../../model/doc/messages.hpp" +#include "../../model/world/scriptcontext.hpp" + +namespace Compiler +{ + struct TokenLoc; +} namespace CSMDoc { @@ -23,44 +30,42 @@ namespace CSVWorld { class ScriptErrorTable : public QTableWidget, private Compiler::ErrorHandler { - Q_OBJECT + Q_OBJECT - Compiler::Extensions mExtensions; - CSMWorld::ScriptContext mContext; + Compiler::Extensions mExtensions; + CSMWorld::ScriptContext mContext; - void report (const std::string& message, const Compiler::TokenLoc& loc, Type type) override; - ///< Report error to the user. + void report(const std::string& message, const Compiler::TokenLoc& loc, Type type) override; + ///< Report error to the user. - void report (const std::string& message, Type type) override; - ///< Report a file related error + void report(const std::string& message, Type type) override; + ///< Report a file related error - void addMessage (const std::string& message, CSMDoc::Message::Severity severity, - int line = -1, int column = -1); + void addMessage(const std::string& message, CSMDoc::Message::Severity severity, int line = -1, int column = -1); - void setWarningsMode (const std::string& value); + void setWarningsMode(const std::string& value); - public: + public: + ScriptErrorTable(const CSMDoc::Document& document, QWidget* parent = nullptr); - ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = nullptr); + void update(const std::string& source); - void update (const std::string& source); + void clear(); - void clear(); + /// Clear local variable cache for \a script. + /// + /// \return Were there any locals that needed clearing? + bool clearLocals(const std::string& script); - /// Clear local variable cache for \a script. - /// - /// \return Were there any locals that needed clearing? - bool clearLocals (const std::string& script); + private slots: - private slots: + void settingChanged(const CSMPrefs::Setting* setting); - void settingChanged (const CSMPrefs::Setting *setting); + void cellClicked(int row, int column); - void cellClicked (int row, int column); + signals: - signals: - - void highlightError (int line, int column); + void highlightError(int line, int column); }; } diff --git a/apps/opencs/view/world/scripthighlighter.cpp b/apps/opencs/view/world/scripthighlighter.cpp index 147beb82a..461541295 100644 --- a/apps/opencs/view/world/scripthighlighter.cpp +++ b/apps/opencs/view/world/scripthighlighter.cpp @@ -1,74 +1,80 @@ #include "scripthighlighter.hpp" #include +#include -#include +#include +#include +#include #include +#include +#include +#include -#include "../../model/prefs/setting.hpp" -#include "../../model/prefs/category.hpp" +class QTextDocument; -bool CSVWorld::ScriptHighlighter::parseInt (int value, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) +namespace CSMWorld { - highlight (loc, Type_Int); + class Data; +} + +bool CSVWorld::ScriptHighlighter::parseInt(int value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) +{ + highlight(loc, Type_Int); return true; } -bool CSVWorld::ScriptHighlighter::parseFloat (float value, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) +bool CSVWorld::ScriptHighlighter::parseFloat(float value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { - highlight (loc, Type_Float); + highlight(loc, Type_Float); return true; } -bool CSVWorld::ScriptHighlighter::parseName (const std::string& name, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) +bool CSVWorld::ScriptHighlighter::parseName( + const std::string& name, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { - highlight (loc, mContext.isId (name) ? Type_Id : Type_Name); + highlight(loc, mContext.isId(ESM::RefId::stringRefId(name)) ? Type_Id : Type_Name); return true; } -bool CSVWorld::ScriptHighlighter::parseKeyword (int keyword, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) +bool CSVWorld::ScriptHighlighter::parseKeyword(int keyword, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { - if (((mMode==Mode_Console || mMode==Mode_Dialogue) && - (keyword==Compiler::Scanner::K_begin || keyword==Compiler::Scanner::K_end || - keyword==Compiler::Scanner::K_short || keyword==Compiler::Scanner::K_long || - keyword==Compiler::Scanner::K_float)) - || (mMode==Mode_Console && (keyword==Compiler::Scanner::K_if || - keyword==Compiler::Scanner::K_endif || keyword==Compiler::Scanner::K_else || - keyword==Compiler::Scanner::K_elseif || keyword==Compiler::Scanner::K_while || - keyword==Compiler::Scanner::K_endwhile))) - return parseName (loc.mLiteral, loc, scanner); + if (((mMode == Mode_Console || mMode == Mode_Dialogue) + && (keyword == Compiler::Scanner::K_begin || keyword == Compiler::Scanner::K_end + || keyword == Compiler::Scanner::K_short || keyword == Compiler::Scanner::K_long + || keyword == Compiler::Scanner::K_float)) + || (mMode == Mode_Console + && (keyword == Compiler::Scanner::K_if || keyword == Compiler::Scanner::K_endif + || keyword == Compiler::Scanner::K_else || keyword == Compiler::Scanner::K_elseif + || keyword == Compiler::Scanner::K_while || keyword == Compiler::Scanner::K_endwhile))) + return parseName(loc.mLiteral, loc, scanner); - highlight (loc, Type_Keyword); + highlight(loc, Type_Keyword); return true; } -bool CSVWorld::ScriptHighlighter::parseSpecial (int code, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) +bool CSVWorld::ScriptHighlighter::parseSpecial(int code, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { - highlight (loc, Type_Special); + highlight(loc, Type_Special); return true; } -bool CSVWorld::ScriptHighlighter::parseComment (const std::string& comment, - const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) +bool CSVWorld::ScriptHighlighter::parseComment( + const std::string& comment, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { - highlight (loc, Type_Comment); + highlight(loc, Type_Comment); return true; } -void CSVWorld::ScriptHighlighter::parseEOF (Compiler::Scanner& scanner) -{} +void CSVWorld::ScriptHighlighter::parseEOF(Compiler::Scanner& scanner) {} -void CSVWorld::ScriptHighlighter::highlight (const Compiler::TokenLoc& loc, Type type) +void CSVWorld::ScriptHighlighter::highlight(const Compiler::TokenLoc& loc, Type type) { // We should take in account multibyte characters int length = 0; const char* token = loc.mLiteral.c_str(); - while (*token) length += (*token++ & 0xc0) != 0x80; + while (*token) + length += (*token++ & 0xc0) != 0x80; int index = loc.mColumn; @@ -79,40 +85,41 @@ void CSVWorld::ScriptHighlighter::highlight (const Compiler::TokenLoc& loc, Type if (mMarkOccurrences && type == Type_Name && loc.mLiteral == mMarkedWord) scheme.merge(mScheme[Type_Highlight]); - setFormat (index, length, scheme); + setFormat(index, length, scheme); } -CSVWorld::ScriptHighlighter::ScriptHighlighter (const CSMWorld::Data& data, Mode mode, - QTextDocument *parent) - : QSyntaxHighlighter (parent) - , Compiler::Parser (mErrorHandler, mContext) - , mContext (data) - , mMode (mode) - , mMarkOccurrences (false) +CSVWorld::ScriptHighlighter::ScriptHighlighter(const CSMWorld::Data& data, Mode mode, QTextDocument* parent) + : QSyntaxHighlighter(parent) + , Compiler::Parser(mErrorHandler, mContext) + , mContext(data) + , mMode(mode) + , mMarkOccurrences(false) { - QColor color ("black"); + QColor color("black"); QTextCharFormat format; - format.setForeground (color); + format.setForeground(color); - for (int i=0; i<=Type_Id; ++i) - mScheme.insert (std::make_pair (static_cast (i), format)); + for (int i = 0; i <= Type_Id; ++i) + mScheme.insert(std::make_pair(static_cast(i), format)); // configure compiler - Compiler::registerExtensions (mExtensions); - mContext.setExtensions (&mExtensions); + Compiler::registerExtensions(mExtensions); + mContext.setExtensions(&mExtensions); } -void CSVWorld::ScriptHighlighter::highlightBlock (const QString& text) +void CSVWorld::ScriptHighlighter::highlightBlock(const QString& text) { - std::istringstream stream (text.toUtf8().constData()); + std::istringstream stream(text.toUtf8().constData()); - Compiler::Scanner scanner (mErrorHandler, stream, mContext.getExtensions()); + Compiler::Scanner scanner(mErrorHandler, stream, mContext.getExtensions()); try { - scanner.scan (*this); + scanner.scan(*this); } - catch (...) {} // ignore syntax errors + catch (...) + { + } // ignore syntax errors } void CSVWorld::ScriptHighlighter::setMarkOccurrences(bool flag) @@ -130,26 +137,22 @@ void CSVWorld::ScriptHighlighter::invalidateIds() mContext.invalidateIds(); } -bool CSVWorld::ScriptHighlighter::settingChanged (const CSMPrefs::Setting *setting) +bool CSVWorld::ScriptHighlighter::settingChanged(const CSMPrefs::Setting* setting) { - if (setting->getParent()->getKey()=="Scripts") + if (setting->getParent()->getKey() == "Scripts") { - static const char *const colours[Type_Id+2] = - { - "colour-int", "colour-float", "colour-name", "colour-keyword", - "colour-special", "colour-comment", "colour-highlight", "colour-id", - 0 - }; + static const char* const colours[Type_Id + 2] = { "colour-int", "colour-float", "colour-name", "colour-keyword", + "colour-special", "colour-comment", "colour-highlight", "colour-id", 0 }; - for (int i=0; colours[i]; ++i) - if (setting->getKey()==colours[i]) + for (int i = 0; colours[i]; ++i) + if (setting->getKey() == colours[i]) { QTextCharFormat format; if (i == Type_Highlight) - format.setBackground (setting->toColor()); + format.setBackground(setting->toColor()); else - format.setForeground (setting->toColor()); - mScheme[static_cast (i)] = format; + format.setForeground(setting->toColor()); + mScheme[static_cast(i)] = format; return true; } } diff --git a/apps/opencs/view/world/scripthighlighter.hpp b/apps/opencs/view/world/scripthighlighter.hpp index 9b4a5b7be..517db5224 100644 --- a/apps/opencs/view/world/scripthighlighter.hpp +++ b/apps/opencs/view/world/scripthighlighter.hpp @@ -4,14 +4,29 @@ #include #include +#include #include +#include +#include #include #include -#include #include "../../model/world/scriptcontext.hpp" +class QTextDocument; + +namespace CSMWorld +{ + class Data; +} + +namespace Compiler +{ + class Scanner; + struct TokenLoc; +} + namespace CSMPrefs { class Setting; @@ -21,87 +36,78 @@ namespace CSVWorld { class ScriptHighlighter : public QSyntaxHighlighter, private Compiler::Parser { - public: + public: + enum Type + { + Type_Int = 0, + Type_Float = 1, + Type_Name = 2, + Type_Keyword = 3, + Type_Special = 4, + Type_Comment = 5, + Type_Highlight = 6, + Type_Id = 7 + }; - enum Type - { - Type_Int = 0, - Type_Float = 1, - Type_Name = 2, - Type_Keyword = 3, - Type_Special = 4, - Type_Comment = 5, - Type_Highlight = 6, - Type_Id = 7 - }; + enum Mode + { + Mode_General, + Mode_Console, + Mode_Dialogue + }; - enum Mode - { - Mode_General, - Mode_Console, - Mode_Dialogue - }; + private: + Compiler::NullErrorHandler mErrorHandler; + Compiler::Extensions mExtensions; + CSMWorld::ScriptContext mContext; + std::map mScheme; + Mode mMode; + bool mMarkOccurrences; + std::string mMarkedWord; - private: + private: + bool parseInt(int value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; + ///< Handle an int token. + /// \return fetch another token? - Compiler::NullErrorHandler mErrorHandler; - Compiler::Extensions mExtensions; - CSMWorld::ScriptContext mContext; - std::map mScheme; - Mode mMode; - bool mMarkOccurrences; - std::string mMarkedWord; + bool parseFloat(float value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; + ///< Handle a float token. + /// \return fetch another token? - private: + bool parseName(const std::string& name, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; + ///< Handle a name token. + /// \return fetch another token? - bool parseInt (int value, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) override; - ///< Handle an int token. - /// \return fetch another token? + bool parseKeyword(int keyword, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; + ///< Handle a keyword token. + /// \return fetch another token? - bool parseFloat (float value, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) override; - ///< Handle a float token. - /// \return fetch another token? + bool parseSpecial(int code, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; + ///< Handle a special character token. + /// \return fetch another token? - bool parseName (const std::string& name, - const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; - ///< Handle a name token. - /// \return fetch another token? + bool parseComment( + const std::string& comment, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) override; + ///< Handle comment token. + /// \return fetch another token? - bool parseKeyword (int keyword, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) override; - ///< Handle a keyword token. - /// \return fetch another token? + void parseEOF(Compiler::Scanner& scanner) override; + ///< Handle EOF token. - bool parseSpecial (int code, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) override; - ///< Handle a special character token. - /// \return fetch another token? + void highlight(const Compiler::TokenLoc& loc, Type type); - bool parseComment (const std::string& comment, const Compiler::TokenLoc& loc, - Compiler::Scanner& scanner) override; - ///< Handle comment token. - /// \return fetch another token? + public: + ScriptHighlighter(const CSMWorld::Data& data, Mode mode, QTextDocument* parent); - void parseEOF (Compiler::Scanner& scanner) override; - ///< Handle EOF token. + void highlightBlock(const QString& text) override; - void highlight (const Compiler::TokenLoc& loc, Type type); + void setMarkOccurrences(bool); - public: + void setMarkedWord(const std::string& name); - ScriptHighlighter (const CSMWorld::Data& data, Mode mode, QTextDocument *parent); + void invalidateIds(); - void highlightBlock (const QString& text) override; - - void setMarkOccurrences(bool); - - void setMarkedWord(const std::string& name); - - void invalidateIds(); - - bool settingChanged (const CSMPrefs::Setting *setting); + bool settingChanged(const CSMPrefs::Setting* setting); }; } diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 6eab7aaa5..793f2e58c 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -1,52 +1,59 @@ #include "scriptsubview.hpp" -#include +#include +#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include "../../model/doc/document.hpp" -#include "../../model/world/universalid.hpp" -#include "../../model/world/data.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/idtable.hpp" #include "../../model/prefs/state.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/universalid.hpp" -#include "scriptedit.hpp" -#include "recordbuttonbar.hpp" -#include "tablebottombox.hpp" #include "genericcreator.hpp" +#include "recordbuttonbar.hpp" +#include "scriptedit.hpp" #include "scripterrortable.hpp" +#include "tablebottombox.hpp" void CSVWorld::ScriptSubView::addButtonBar() { if (mButtons) return; - mButtons = new RecordButtonBar (getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this); + mButtons = new RecordButtonBar(getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this); - mLayout.insertWidget (1, mButtons); + mLayout.insertWidget(1, mButtons); - connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + connect(mButtons, &RecordButtonBar::switchToRow, this, &ScriptSubView::switchToRow); - connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), - mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); + connect(this, &ScriptSubView::universalIdChanged, mButtons, &RecordButtonBar::universalIdChanged); } void CSVWorld::ScriptSubView::recompile() { if (!mCompileDelay->isActive() && !isDeleted()) - mCompileDelay->start (CSMPrefs::get()["Scripts"]["compile-delay"].toInt()); + mCompileDelay->start(CSMPrefs::get()["Scripts"]["compile-delay"].toInt()); } bool CSVWorld::ScriptSubView::isDeleted() const { - return mModel->data (mModel->getModelIndex (getUniversalId().getId(), mStateColumn)).toInt() - ==CSMWorld::RecordBase::State_Deleted; + return mModel->data(mModel->getModelIndex(getUniversalId().getId(), mStateColumn)).toInt() + == CSMWorld::RecordBase::State_Deleted; } void CSVWorld::ScriptSubView::updateDeletedState() @@ -55,11 +62,11 @@ void CSVWorld::ScriptSubView::updateDeletedState() { mErrors->clear(); adjustSplitter(); - mEditor->setEnabled (false); + mEditor->setEnabled(false); } else { - mEditor->setEnabled (true); + mEditor->setEnabled(true); recompile(); } } @@ -73,7 +80,7 @@ void CSVWorld::ScriptSubView::adjustSplitter() if (mErrors->height()) return; // keep old height if the error panel was already open - sizes << (mMain->height()-mErrorHeight-mMain->handleWidth()) << mErrorHeight; + sizes << (mMain->height() - mErrorHeight - mMain->handleWidth()) << mErrorHeight; } else { @@ -83,90 +90,88 @@ void CSVWorld::ScriptSubView::adjustSplitter() sizes << 1 << 0; } - mMain->setSizes (sizes); + mMain->setSizes(sizes); } -CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mBottom(nullptr), mButtons (nullptr), - mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())), - mErrorHeight (CSMPrefs::get()["Scripts"]["error-height"].toInt()) +CSVWorld::ScriptSubView::ScriptSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document) + : SubView(id) + , mDocument(document) + , mColumn(-1) + , mBottom(nullptr) + , mButtons(nullptr) + , mCommandDispatcher(document, CSMWorld::UniversalId::getParentType(id.getType())) + , mErrorHeight(CSMPrefs::get()["Scripts"]["error-height"].toInt()) { - std::vector selection (1, id.getId()); - mCommandDispatcher.setSelection (selection); + std::vector selection(1, id.getId()); + mCommandDispatcher.setSelection(selection); - mMain = new QSplitter (this); - mMain->setOrientation (Qt::Vertical); - mLayout.addWidget (mMain, 2); + mMain = new QSplitter(this); + mMain->setOrientation(Qt::Vertical); + mLayout.addWidget(mMain, 2); - mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this); - mMain->addWidget (mEditor); - mMain->setCollapsible (0, false); + mEditor = new ScriptEdit(mDocument, ScriptHighlighter::Mode_General, this); + mMain->addWidget(mEditor); + mMain->setCollapsible(0, false); - mErrors = new ScriptErrorTable (document, this); - mMain->addWidget (mErrors); + mErrors = new ScriptErrorTable(document, this); + mMain->addWidget(mErrors); QList sizes; sizes << 1 << 0; - mMain->setSizes (sizes); + mMain->setSizes(sizes); - QWidget *widget = new QWidget (this);; - widget->setLayout (&mLayout); - setWidget (widget); + QWidget* widget = new QWidget(this); + widget->setLayout(&mLayout); + setWidget(widget); - mModel = &dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); + mModel = &dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Scripts)); - mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText); - mIdColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + mColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_ScriptText); + mIdColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + mStateColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); - QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); + QString source = mModel->data(mModel->getModelIndex(id.getId(), mColumn)).toString(); - mEditor->setPlainText (source); + mEditor->setPlainText(source); // bottom box and buttons - mBottom = new TableBottomBox (CreatorFactory(), document, id, this); + mBottom = new TableBottomBox(CreatorFactory(), document, id, this); - connect (mBottom, SIGNAL (requestFocus (const std::string&)), - this, SLOT (switchToId (const std::string&))); + connect(mBottom, &TableBottomBox::requestFocus, this, &ScriptSubView::switchToId); - mLayout.addWidget (mBottom); + mLayout.addWidget(mBottom); // signals - connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); + connect(mEditor, &ScriptEdit::textChanged, this, &ScriptSubView::textChanged); - connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (dataChanged (const QModelIndex&, const QModelIndex&))); + connect(mModel, &CSMWorld::IdTable::dataChanged, this, &ScriptSubView::dataChanged); - connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int))); + connect(mModel, &CSMWorld::IdTable::rowsAboutToBeRemoved, this, &ScriptSubView::rowsAboutToBeRemoved); updateStatusBar(); - connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); + connect(mEditor, &ScriptEdit::cursorPositionChanged, this, &ScriptSubView::updateStatusBar); - mErrors->update (source.toUtf8().constData()); + mErrors->update(source.toUtf8().constData()); - connect (mErrors, SIGNAL (highlightError (int, int)), - this, SLOT (highlightError (int, int))); + connect(mErrors, &ScriptErrorTable::highlightError, this, &ScriptSubView::highlightError); - mCompileDelay = new QTimer (this); - mCompileDelay->setSingleShot (true); - connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest())); + mCompileDelay = new QTimer(this); + mCompileDelay->setSingleShot(true); + connect(mCompileDelay, &QTimer::timeout, this, &ScriptSubView::updateRequest); updateDeletedState(); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &ScriptSubView::settingChanged); CSMPrefs::get()["Scripts"].update(); } -void CSVWorld::ScriptSubView::setStatusBar (bool show) +void CSVWorld::ScriptSubView::setStatusBar(bool show) { - mBottom->setStatusBar (show); + mBottom->setStatusBar(show); } -void CSVWorld::ScriptSubView::settingChanged (const CSMPrefs::Setting *setting) +void CSVWorld::ScriptSubView::settingChanged(const CSMPrefs::Setting* setting) { - if (*setting=="Scripts/toolbar") + if (*setting == "Scripts/toolbar") { if (setting->isTrue()) { @@ -174,58 +179,58 @@ void CSVWorld::ScriptSubView::settingChanged (const CSMPrefs::Setting *setting) } else if (mButtons) { - mLayout.removeWidget (mButtons); + mLayout.removeWidget(mButtons); delete mButtons; mButtons = nullptr; } } - else if (*setting=="Scripts/compile-delay") + else if (*setting == "Scripts/compile-delay") { - mCompileDelay->setInterval (setting->toInt()); + mCompileDelay->setInterval(setting->toInt()); } - else if (*setting=="Scripts/warnings") + else if (*setting == "Scripts/warnings") recompile(); } -void CSVWorld::ScriptSubView::updateStatusBar () +void CSVWorld::ScriptSubView::updateStatusBar() { - mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1, - mEditor->textCursor().columnNumber() + 1); + mBottom->positionChanged(mEditor->textCursor().blockNumber() + 1, mEditor->textCursor().columnNumber() + 1); } -void CSVWorld::ScriptSubView::setEditLock (bool locked) +void CSVWorld::ScriptSubView::setEditLock(bool locked) { - mEditor->setReadOnly (locked); + mEditor->setReadOnly(locked); if (mButtons) - mButtons->setEditLock (locked); + mButtons->setEditLock(locked); - mCommandDispatcher.setEditLock (locked); + mCommandDispatcher.setEditLock(locked); } -void CSVWorld::ScriptSubView::useHint (const std::string& hint) +void CSVWorld::ScriptSubView::useHint(const std::string& hint) { if (hint.empty()) return; unsigned line = 0, column = 0; char c; - std::istringstream stream (hint.c_str()+1); - switch(hint[0]) + std::istringstream stream(hint.c_str() + 1); + switch (hint[0]) { case 'R': case 'r': { - QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); - QString source = mModel->data (index).toString(); + QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn); + QString source = mModel->data(index).toString(); unsigned stringSize = source.length(); unsigned pos, dummy; - if (!(stream >> c >> dummy >> pos) ) + if (!(stream >> c >> dummy >> pos)) return; if (pos > stringSize) { - Log(Debug::Warning) << "CSVWorld::ScriptSubView: requested position is higher than actual string length"; + Log(Debug::Warning) + << "CSVWorld::ScriptSubView: requested position is higher than actual string length"; pos = stringSize; } @@ -234,7 +239,7 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) if (source[i] == '\n') { ++line; - column = i+1; + column = i + 1; } } column = pos - column; @@ -247,12 +252,12 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) QTextCursor cursor = mEditor->textCursor(); - cursor.movePosition (QTextCursor::Start); - if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) - cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + cursor.movePosition(QTextCursor::Start); + if (cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column); mEditor->setFocus(); - mEditor->setTextCursor (cursor); + mEditor->setTextCursor(cursor); } void CSVWorld::ScriptSubView::textChanged() @@ -260,46 +265,46 @@ void CSVWorld::ScriptSubView::textChanged() if (mEditor->isChangeLocked()) return; - ScriptEdit::ChangeLock lock (*mEditor); + ScriptEdit::ChangeLock lock(*mEditor); QString source = mEditor->toPlainText(); - mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, - mModel->getModelIndex (getUniversalId().getId(), mColumn), source)); + mDocument.getUndoStack().push( + new CSMWorld::ModifyCommand(*mModel, mModel->getModelIndex(getUniversalId().getId(), mColumn), source)); recompile(); } -void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +void CSVWorld::ScriptSubView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mEditor->isChangeLocked()) return; - ScriptEdit::ChangeLock lock (*mEditor); + ScriptEdit::ChangeLock lock(*mEditor); bool updateRequired = false; - for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { - std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); - if (mErrors->clearLocals (id)) + std::string id = mModel->data(mModel->index(i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals(id)) updateRequired = true; } - QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn); - if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) + if (index.row() >= topLeft.row() && index.row() <= bottomRight.row()) { - if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column()) + if (mStateColumn >= topLeft.column() && mStateColumn <= bottomRight.column()) updateDeletedState(); - if (mColumn>=topLeft.column() && mColumn<=bottomRight.column()) + if (mColumn >= topLeft.column() && mColumn <= bottomRight.column()) { - QString source = mModel->data (index).toString(); + QString source = mModel->data(index).toString(); QTextCursor cursor = mEditor->textCursor(); - mEditor->setPlainText (source); - mEditor->setTextCursor (cursor); + mEditor->setPlainText(source); + mEditor->setTextCursor(cursor); updateRequired = true; } @@ -309,66 +314,66 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo recompile(); } -void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end) +void CSVWorld::ScriptSubView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { bool updateRequired = false; - for (int i=start; i<=end; ++i) + for (int i = start; i <= end; ++i) { - std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); - if (mErrors->clearLocals (id)) + std::string id = mModel->data(mModel->index(i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals(id)) updateRequired = true; } if (updateRequired) recompile(); - QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn); - if (!parent.isValid() && index.row()>=start && index.row()<=end) + if (!parent.isValid() && index.row() >= start && index.row() <= end) emit closeRequest(); } -void CSVWorld::ScriptSubView::switchToRow (int row) +void CSVWorld::ScriptSubView::switchToRow(int row) { - int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData(); - setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id)); + int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + std::string id = mModel->data(mModel->index(row, idColumn)).toString().toUtf8().constData(); + setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Script, id)); - bool oldSignalsState = mEditor->blockSignals( true ); - mEditor->setPlainText( mModel->data(mModel->index(row, mColumn)).toString() ); - mEditor->blockSignals( oldSignalsState ); + bool oldSignalsState = mEditor->blockSignals(true); + mEditor->setPlainText(mModel->data(mModel->index(row, mColumn)).toString()); + mEditor->blockSignals(oldSignalsState); - std::vector selection (1, id); - mCommandDispatcher.setSelection (selection); + std::vector selection(1, id); + mCommandDispatcher.setSelection(selection); updateDeletedState(); } -void CSVWorld::ScriptSubView::switchToId (const std::string& id) +void CSVWorld::ScriptSubView::switchToId(const std::string& id) { - switchToRow (mModel->getModelIndex (id, 0).row()); + switchToRow(mModel->getModelIndex(id, 0).row()); } -void CSVWorld::ScriptSubView::highlightError (int line, int column) +void CSVWorld::ScriptSubView::highlightError(int line, int column) { QTextCursor cursor = mEditor->textCursor(); - cursor.movePosition (QTextCursor::Start); - if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) - cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + cursor.movePosition(QTextCursor::Start); + if (cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column); mEditor->setFocus(); - mEditor->setTextCursor (cursor); + mEditor->setTextCursor(cursor); } void CSVWorld::ScriptSubView::updateRequest() { - QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn); - QString source = mModel->data (index).toString(); + QString source = mModel->data(index).toString(); - mErrors->update (source.toUtf8().constData()); + mErrors->update(source.toUtf8().constData()); adjustSplitter(); } diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index dc352cc5b..2f53295f6 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -3,15 +3,18 @@ #include +#include + +#include + #include "../../model/world/commanddispatcher.hpp" #include "../doc/subview.hpp" class QModelIndex; -class QLabel; -class QVBoxLayout; +class QObject; class QSplitter; -class QTime; +class QTimer; namespace CSMDoc { @@ -37,66 +40,64 @@ namespace CSVWorld class ScriptSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - ScriptEdit *mEditor; - CSMDoc::Document& mDocument; - CSMWorld::IdTable *mModel; - int mColumn; - int mIdColumn; - int mStateColumn; - TableBottomBox *mBottom; - RecordButtonBar *mButtons; - CSMWorld::CommandDispatcher mCommandDispatcher; - QVBoxLayout mLayout; - QSplitter *mMain; - ScriptErrorTable *mErrors; - QTimer *mCompileDelay; - int mErrorHeight; + ScriptEdit* mEditor; + CSMDoc::Document& mDocument; + CSMWorld::IdTable* mModel; + int mColumn; + int mIdColumn; + int mStateColumn; + TableBottomBox* mBottom; + RecordButtonBar* mButtons; + CSMWorld::CommandDispatcher mCommandDispatcher; + QVBoxLayout mLayout; + QSplitter* mMain; + ScriptErrorTable* mErrors; + QTimer* mCompileDelay; + int mErrorHeight; - private: + private: + void addButtonBar(); - void addButtonBar(); + void recompile(); - void recompile(); + bool isDeleted() const; - bool isDeleted() const; + void updateDeletedState(); - void updateDeletedState(); + void adjustSplitter(); - void adjustSplitter(); + public: + ScriptSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document); - public: + void setEditLock(bool locked) override; - ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + void useHint(const std::string& hint) override; - void setEditLock (bool locked) override; + void setStatusBar(bool show) override; - void useHint (const std::string& hint) override; + public slots: - void setStatusBar (bool show) override; + void textChanged(); - public slots: + void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void textChanged(); + void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + private slots: - void rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void settingChanged(const CSMPrefs::Setting* setting); - private slots: + void updateStatusBar(); - void settingChanged (const CSMPrefs::Setting *setting); + void switchToRow(int row); - void updateStatusBar(); + void switchToId(const std::string& id); - void switchToRow (int row); + void highlightError(int line, int column); - void switchToId (const std::string& id); - - void highlightError (int line, int column); - - void updateRequest(); + void updateRequest(); }; } diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp index 0eb6bae40..fcbbc7615 100644 --- a/apps/opencs/view/world/startscriptcreator.cpp +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -2,16 +2,23 @@ #include +#include + +#include +#include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" -#include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idcompletionmanager.hpp" #include "../../model/world/idtable.hpp" #include "../widget/droplineedit.hpp" +class QUndoStack; + std::string CSVWorld::StartScriptCreator::getId() const { return mScript->text().toUtf8().constData(); @@ -19,22 +26,17 @@ std::string CSVWorld::StartScriptCreator::getId() const CSMWorld::IdTable& CSVWorld::StartScriptCreator::getStartScriptsTable() const { - return dynamic_cast ( - *getData().getTableModel(getCollectionId()) - ); + return dynamic_cast(*getData().getTableModel(getCollectionId())); } -CSVWorld::StartScriptCreator::StartScriptCreator( - CSMWorld::Data &data, - QUndoStack &undoStack, - const CSMWorld::UniversalId &id, - CSMWorld::IdCompletionManager& completionManager -) : GenericCreator(data, undoStack, id) +CSVWorld::StartScriptCreator::StartScriptCreator(CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager) + : GenericCreator(data, undoStack, id) { setManualEditing(false); // Add script ID input label. - QLabel *label = new QLabel("Script", this); + QLabel* label = new QLabel("Script", this); insertBeforeButtons(label, false); // Add script ID input with auto-completion. @@ -44,13 +46,11 @@ CSVWorld::StartScriptCreator::StartScriptCreator( mScript->setCompleter(completionManager.getCompleter(displayType).get()); insertBeforeButtons(mScript, true); - connect(mScript, SIGNAL (textChanged(const QString&)), this, SLOT (scriptChanged())); - connect(mScript, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); + connect(mScript, &CSVWidget::DropLineEdit::textChanged, this, &StartScriptCreator::scriptChanged); + connect(mScript, &CSVWidget::DropLineEdit::returnPressed, this, &StartScriptCreator::inputReturnPressed); } -void CSVWorld::StartScriptCreator::cloneMode( - const std::string& originId, - const CSMWorld::UniversalId::Type type) +void CSVWorld::StartScriptCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) { CSVWorld::GenericCreator::cloneMode(originId, type); @@ -62,7 +62,7 @@ void CSVWorld::StartScriptCreator::cloneMode( std::string CSVWorld::StartScriptCreator::getErrors() const { - std::string scriptId = getId(); + const ESM::RefId scriptId = ESM::RefId::stringRefId(getId()); // Check user input for any errors. std::string errors; @@ -98,14 +98,8 @@ void CSVWorld::StartScriptCreator::scriptChanged() update(); } -CSVWorld::Creator *CSVWorld::StartScriptCreatorFactory::makeCreator( - CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const +CSVWorld::Creator* CSVWorld::StartScriptCreatorFactory::makeCreator( + CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - return new StartScriptCreator( - document.getData(), - document.getUndoStack(), - id, - document.getIdCompletionManager() - ); + return new StartScriptCreator(document.getData(), document.getUndoStack(), id, document.getIdCompletionManager()); } diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp index cb7f3f619..8d8f51bd8 100644 --- a/apps/opencs/view/world/startscriptcreator.hpp +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -3,12 +3,26 @@ #include "genericcreator.hpp" +#include + +#include +#include + +class QObject; +class QUndoStack; + namespace CSMWorld { + class Data; class IdCompletionManager; class IdTable; } +namespace CSMDoc +{ + class Document; +} + namespace CSVWidget { class DropLineEdit; @@ -21,55 +35,45 @@ namespace CSVWorld { Q_OBJECT - CSVWidget::DropLineEdit *mScript; + CSVWidget::DropLineEdit* mScript; - private: + private: + /// \return script ID entered by user. + std::string getId() const override; - /// \return script ID entered by user. - std::string getId() const override; + /// \return reference to table containing start scripts. + CSMWorld::IdTable& getStartScriptsTable() const; - /// \return reference to table containing start scripts. - CSMWorld::IdTable& getStartScriptsTable() const; + public: + StartScriptCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, + CSMWorld::IdCompletionManager& completionManager); - public: + /// \brief Set script ID input widget to ID of record to be cloned. + /// \param originId Script ID to be cloned. + /// \param type Type of record to be cloned. + void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; - StartScriptCreator( - CSMWorld::Data& data, - QUndoStack& undoStack, - const CSMWorld::UniversalId& id, - CSMWorld::IdCompletionManager& completionManager); + /// \return Error description for current user input. + std::string getErrors() const override; - /// \brief Set script ID input widget to ID of record to be cloned. - /// \param originId Script ID to be cloned. - /// \param type Type of record to be cloned. - void cloneMode( - const std::string& originId, - const CSMWorld::UniversalId::Type type) override; + /// \brief Set focus to script ID input widget. + void focus() override; - /// \return Error description for current user input. - std::string getErrors() const override; + /// \brief Clear script ID input widget. + void reset() override; - /// \brief Set focus to script ID input widget. - void focus() override; + private slots: - /// \brief Clear script ID input widget. - void reset() override; + /// \brief Check user input for any errors. + void scriptChanged(); + }; - private slots: - - /// \brief Check user input for any errors. - void scriptChanged(); - }; - - /// \brief Creator factory for start script record creator. - class StartScriptCreatorFactory : public CreatorFactoryBase - { - public: - - Creator *makeCreator( - CSMDoc::Document& document, - const CSMWorld::UniversalId& id) const override; - }; + /// \brief Creator factory for start script record creator. + class StartScriptCreatorFactory : public CreatorFactoryBase + { + public: + Creator* makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const override; + }; } #endif // STARTSCRIPTCREATOR_HPP diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 169bc2e94..0e1f34d2d 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -2,40 +2,45 @@ #include "../doc/subviewfactoryimp.hpp" -#include "tablesubview.hpp" +#include "bodypartcreator.hpp" +#include "cellcreator.hpp" +#include "dialoguecreator.hpp" #include "dialoguesubview.hpp" -#include "scriptsubview.hpp" -#include "regionmapsubview.hpp" #include "genericcreator.hpp" #include "globalcreator.hpp" -#include "cellcreator.hpp" -#include "referenceablecreator.hpp" -#include "referencecreator.hpp" -#include "startscriptcreator.hpp" -#include "scenesubview.hpp" -#include "dialoguecreator.hpp" #include "infocreator.hpp" -#include "pathgridcreator.hpp" -#include "previewsubview.hpp" -#include "bodypartcreator.hpp" #include "landcreator.hpp" #include "landtexturecreator.hpp" +#include "pathgridcreator.hpp" +#include "previewsubview.hpp" +#include "referenceablecreator.hpp" +#include "referencecreator.hpp" +#include "regionmapsubview.hpp" +#include "scenesubview.hpp" +#include "scriptsubview.hpp" +#include "startscriptcreator.hpp" +#include "tablesubview.hpp" -void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) +#include +#include +#include +#include +#include + +void CSVWorld::addSubViewFactories(CSVDoc::SubViewFactoryManager& manager) { // Regular record tables (including references which are actually sub-records, but are promoted // to top-level records within the editor) - manager.add (CSMWorld::UniversalId::Type_Gmsts, + manager.add( + CSMWorld::UniversalId::Type_Gmsts, new CSVDoc::SubViewFactoryWithCreator); + + manager.add( + CSMWorld::UniversalId::Type_Skills, new CSVDoc::SubViewFactoryWithCreator); + + manager.add(CSMWorld::UniversalId::Type_MagicEffects, new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Skills, - new CSVDoc::SubViewFactoryWithCreator); - - manager.add (CSMWorld::UniversalId::Type_MagicEffects, - new CSVDoc::SubViewFactoryWithCreator); - - static const CSMWorld::UniversalId::Type sTableTypes[] = - { + static const CSMWorld::UniversalId::Type sTableTypes[] = { CSMWorld::UniversalId::Type_Classes, CSMWorld::UniversalId::Type_Factions, CSMWorld::UniversalId::Type_Races, @@ -45,92 +50,90 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Spells, CSMWorld::UniversalId::Type_Enchantments, CSMWorld::UniversalId::Type_SoundGens, - - CSMWorld::UniversalId::Type_None // end marker + // end marker + CSMWorld::UniversalId::Type_None, }; - for (int i=0; sTableTypes[i]!=CSMWorld::UniversalId::Type_None; ++i) - manager.add (sTableTypes[i], - new CSVDoc::SubViewFactoryWithCreator >); + for (int i = 0; sTableTypes[i] != CSMWorld::UniversalId::Type_None; ++i) + manager.add( + sTableTypes[i], new CSVDoc::SubViewFactoryWithCreator>); - manager.add (CSMWorld::UniversalId::Type_BodyParts, - new CSVDoc::SubViewFactoryWithCreator >); + manager.add(CSMWorld::UniversalId::Type_BodyParts, + new CSVDoc::SubViewFactoryWithCreator>); - manager.add (CSMWorld::UniversalId::Type_StartScripts, + manager.add(CSMWorld::UniversalId::Type_StartScripts, new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Cells, - new CSVDoc::SubViewFactoryWithCreator >); + manager.add(CSMWorld::UniversalId::Type_Cells, + new CSVDoc::SubViewFactoryWithCreator>); - manager.add (CSMWorld::UniversalId::Type_Referenceables, - new CSVDoc::SubViewFactoryWithCreator >); + manager.add(CSMWorld::UniversalId::Type_Referenceables, + new CSVDoc::SubViewFactoryWithCreator>); - manager.add (CSMWorld::UniversalId::Type_References, + manager.add(CSMWorld::UniversalId::Type_References, new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Topics, - new CSVDoc::SubViewFactoryWithCreator); + manager.add( + CSMWorld::UniversalId::Type_Topics, new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Journals, + manager.add(CSMWorld::UniversalId::Type_Journals, new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_TopicInfos, + manager.add(CSMWorld::UniversalId::Type_TopicInfos, new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_JournalInfos, + manager.add(CSMWorld::UniversalId::Type_JournalInfos, new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Pathgrids, + manager.add(CSMWorld::UniversalId::Type_Pathgrids, new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Lands, - new CSVDoc::SubViewFactoryWithCreator >); + manager.add(CSMWorld::UniversalId::Type_Lands, + new CSVDoc::SubViewFactoryWithCreator>); - manager.add (CSMWorld::UniversalId::Type_LandTextures, - new CSVDoc::SubViewFactoryWithCreator >); + manager.add(CSMWorld::UniversalId::Type_LandTextures, + new CSVDoc::SubViewFactoryWithCreator>); - manager.add (CSMWorld::UniversalId::Type_Globals, - new CSVDoc::SubViewFactoryWithCreator >); + manager.add(CSMWorld::UniversalId::Type_Globals, + new CSVDoc::SubViewFactoryWithCreator>); // Subviews for resources tables - manager.add (CSMWorld::UniversalId::Type_Meshes, - new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Icons, - new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Musics, - new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_SoundsRes, - new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Textures, - new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Videos, - new CSVDoc::SubViewFactoryWithCreator); - + manager.add( + CSMWorld::UniversalId::Type_Meshes, new CSVDoc::SubViewFactoryWithCreator); + manager.add( + CSMWorld::UniversalId::Type_Icons, new CSVDoc::SubViewFactoryWithCreator); + manager.add( + CSMWorld::UniversalId::Type_Musics, new CSVDoc::SubViewFactoryWithCreator); + manager.add( + CSMWorld::UniversalId::Type_SoundsRes, new CSVDoc::SubViewFactoryWithCreator); + manager.add( + CSMWorld::UniversalId::Type_Textures, new CSVDoc::SubViewFactoryWithCreator); + manager.add( + CSMWorld::UniversalId::Type_Videos, new CSVDoc::SubViewFactoryWithCreator); // Subviews for editing/viewing individual records - manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); + manager.add(CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); // Other stuff (combined record tables) - manager.add (CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory); + manager.add(CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory); - manager.add (CSMWorld::UniversalId::Type_Scene, new CSVDoc::SubViewFactory); + manager.add(CSMWorld::UniversalId::Type_Scene, new CSVDoc::SubViewFactory); // More other stuff - manager.add (CSMWorld::UniversalId::Type_Filters, + manager.add(CSMWorld::UniversalId::Type_Filters, new CSVDoc::SubViewFactoryWithCreator >); + CreatorFactory>); - manager.add (CSMWorld::UniversalId::Type_DebugProfiles, + manager.add(CSMWorld::UniversalId::Type_DebugProfiles, new CSVDoc::SubViewFactoryWithCreator >); + CreatorFactory>); - manager.add (CSMWorld::UniversalId::Type_Scripts, - new CSVDoc::SubViewFactoryWithCreator >); + manager.add(CSMWorld::UniversalId::Type_Scripts, + new CSVDoc::SubViewFactoryWithCreator>); // Dialogue subviews - static const CSMWorld::UniversalId::Type sTableTypes2[] = - { + static const CSMWorld::UniversalId::Type sTableTypes2[] = { CSMWorld::UniversalId::Type_Region, CSMWorld::UniversalId::Type_Spell, CSMWorld::UniversalId::Type_Birthsign, @@ -141,69 +144,69 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Faction, CSMWorld::UniversalId::Type_Enchantment, CSMWorld::UniversalId::Type_SoundGen, - - CSMWorld::UniversalId::Type_None // end marker + // end marker + CSMWorld::UniversalId::Type_None, }; - for (int i=0; sTableTypes2[i]!=CSMWorld::UniversalId::Type_None; ++i) - manager.add (sTableTypes2[i], - new CSVDoc::SubViewFactoryWithCreator > (false)); + for (int i = 0; sTableTypes2[i] != CSMWorld::UniversalId::Type_None; ++i) + manager.add(sTableTypes2[i], + new CSVDoc::SubViewFactoryWithCreator>(false)); - manager.add (CSMWorld::UniversalId::Type_BodyPart, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add(CSMWorld::UniversalId::Type_BodyPart, + new CSVDoc::SubViewFactoryWithCreator>(false)); - manager.add (CSMWorld::UniversalId::Type_StartScript, + manager.add(CSMWorld::UniversalId::Type_StartScript, new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Skill, - new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add(CSMWorld::UniversalId::Type_Skill, + new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_MagicEffect, - new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add(CSMWorld::UniversalId::Type_MagicEffect, + new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Gmst, - new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add(CSMWorld::UniversalId::Type_Gmst, + new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Referenceable, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add(CSMWorld::UniversalId::Type_Referenceable, + new CSVDoc::SubViewFactoryWithCreator>(false)); - manager.add (CSMWorld::UniversalId::Type_Reference, - new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add(CSMWorld::UniversalId::Type_Reference, + new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Cell, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add(CSMWorld::UniversalId::Type_Cell, + new CSVDoc::SubViewFactoryWithCreator>(false)); - manager.add (CSMWorld::UniversalId::Type_JournalInfo, - new CSVDoc::SubViewFactoryWithCreator (false)); - - manager.add (CSMWorld::UniversalId::Type_TopicInfo, + manager.add(CSMWorld::UniversalId::Type_JournalInfo, new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Topic, - new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add(CSMWorld::UniversalId::Type_TopicInfo, + new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Journal, - new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add(CSMWorld::UniversalId::Type_Topic, + new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Pathgrid, - new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add(CSMWorld::UniversalId::Type_Journal, + new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_Land, - new CSVDoc::SubViewFactoryWithCreator >(false)); + manager.add(CSMWorld::UniversalId::Type_Pathgrid, + new CSVDoc::SubViewFactoryWithCreator(false)); - manager.add (CSMWorld::UniversalId::Type_LandTexture, - new CSVDoc::SubViewFactoryWithCreator >(false)); + manager.add(CSMWorld::UniversalId::Type_Land, + new CSVDoc::SubViewFactoryWithCreator>(false)); - manager.add (CSMWorld::UniversalId::Type_DebugProfile, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add(CSMWorld::UniversalId::Type_LandTexture, + new CSVDoc::SubViewFactoryWithCreator>(false)); - manager.add (CSMWorld::UniversalId::Type_Filter, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add(CSMWorld::UniversalId::Type_DebugProfile, + new CSVDoc::SubViewFactoryWithCreator>(false)); - manager.add (CSMWorld::UniversalId::Type_MetaData, - new CSVDoc::SubViewFactory); + manager.add(CSMWorld::UniversalId::Type_Filter, + new CSVDoc::SubViewFactoryWithCreator>(false)); - //preview - manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); + manager.add(CSMWorld::UniversalId::Type_MetaData, new CSVDoc::SubViewFactory); + + // preview + manager.add(CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } diff --git a/apps/opencs/view/world/subviews.hpp b/apps/opencs/view/world/subviews.hpp index 51e4cb083..cfa5a8213 100644 --- a/apps/opencs/view/world/subviews.hpp +++ b/apps/opencs/view/world/subviews.hpp @@ -8,7 +8,7 @@ namespace CSVDoc namespace CSVWorld { - void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager); + void addSubViewFactories(CSVDoc::SubViewFactoryManager& manager); } #endif diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index c676a5ecc..1e8080563 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -1,46 +1,65 @@ #include "table.hpp" -#include #include -#include #include +#include +#include +#include #include -#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include +#include #include "../../model/doc/document.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/infotableproxymodel.hpp" -#include "../../model/world/idtableproxymodel.hpp" -#include "../../model/world/idtablebase.hpp" -#include "../../model/world/idtable.hpp" -#include "../../model/world/landtexturetableproxymodel.hpp" -#include "../../model/world/record.hpp" -#include "../../model/world/columns.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/idtablebase.hpp" +#include "../../model/world/infotableproxymodel.hpp" +#include "../../model/world/landtexturetableproxymodel.hpp" -#include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/state.hpp" #include "tableeditidaction.hpp" +#include "tableheadermouseeventhandler.hpp" #include "util.hpp" -void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) +namespace CSMFilter +{ + class Node; +} + +void CSVWorld::Table::contextMenuEvent(QContextMenuEvent* event) { // configure dispatcher - mDispatcher->setSelection (getSelectedIds()); + mDispatcher->setSelection(getSelectedIds()); std::vector extendedTypes = mDispatcher->getExtendedTypes(); - mDispatcher->setExtendedTypes (extendedTypes); + mDispatcher->setExtendedTypes(extendedTypes); // create context menu QModelIndexList selectedRows = selectionModel()->selectedRows(); - QMenu menu (this); + QMenu menu(this); /// \todo add menu items for select all and clear selection @@ -55,50 +74,49 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) { - if (selectedRows.size()==1) + if (selectedRows.size() == 1) { - menu.addAction (mEditAction); + menu.addAction(mEditAction); if (mCreateAction) menu.addAction(mCloneAction); } if (mTouchAction) - menu.addAction (mTouchAction); + menu.addAction(mTouchAction); if (mCreateAction) - menu.addAction (mCreateAction); + menu.addAction(mCreateAction); if (mDispatcher->canRevert()) { - menu.addAction (mRevertAction); + menu.addAction(mRevertAction); if (!extendedTypes.empty()) - menu.addAction (mExtendedRevertAction); + menu.addAction(mExtendedRevertAction); } if (mDispatcher->canDelete()) { - menu.addAction (mDeleteAction); + menu.addAction(mDeleteAction); if (!extendedTypes.empty()) - menu.addAction (mExtendedDeleteAction); + menu.addAction(mExtendedDeleteAction); } if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_ReorderWithinTopic) { /// \todo allow reordering of multiple rows - if (selectedRows.size()==1) + if (selectedRows.size() == 1) { - int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); + int column = mModel->searchColumnIndex(CSMWorld::Columns::ColumnId_Topic); - if (column==-1) - column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); + if (column == -1) + column = mModel->searchColumnIndex(CSMWorld::Columns::ColumnId_Journal); - if (column!=-1) + if (column != -1) { - int row = mProxyModel->mapToSource ( - mProxyModel->index (selectedRows.begin()->row(), 0)).row(); + int row = mProxyModel->mapToSource(mProxyModel->index(selectedRows.begin()->row(), 0)).row(); QString curData = mModel->data(mModel->index(row, column)).toString(); if (row > 0) @@ -123,22 +141,22 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) } } - if (selectedRows.size()==1) + if (selectedRows.size() == 1) { int row = selectedRows.begin()->row(); - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row(); if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_View) { - CSMWorld::UniversalId id = mModel->view (row).first; + CSMWorld::UniversalId id = mModel->view(row).first; - int index = mDocument.getData().getCells().searchId (id.getId()); + const int index = mDocument.getData().getCells().searchId(ESM::RefId::stringRefId(id.getId())); // index==-1: the ID references a worldspace instead of a cell (ignore for now and go // ahead) - if (index==-1 || !mDocument.getData().getCells().getRecord (index).isDeleted()) - menu.addAction (mViewAction); + if (index == -1 || !mDocument.getData().getCells().getRecord(index).isDeleted()) + menu.addAction(mViewAction); } if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Preview) @@ -146,37 +164,33 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) const CSMWorld::UniversalId id = getUniversalId(currentRow); const CSMWorld::UniversalId::Type type = id.getType(); - QModelIndex index = mModel->index (row, - mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + QModelIndex index = mModel->index(row, mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification)); - CSMWorld::RecordBase::State state = static_cast ( - mModel->data (index).toInt()); + CSMWorld::RecordBase::State state = static_cast(mModel->data(index).toInt()); - if (state!=CSMWorld::RecordBase::State_Deleted && type != CSMWorld::UniversalId::Type_ItemLevelledList) - menu.addAction (mPreviewAction); + if (state != CSMWorld::RecordBase::State_Deleted && type != CSMWorld::UniversalId::Type_ItemLevelledList) + menu.addAction(mPreviewAction); } } if (mHelpAction) - menu.addAction (mHelpAction); + menu.addAction(mHelpAction); - menu.exec (event->globalPos()); + menu.exec(event->globalPos()); } -void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event) +void CSVWorld::Table::mouseDoubleClickEvent(QMouseEvent* event) { - Qt::KeyboardModifiers modifiers = - event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier); + Qt::KeyboardModifiers modifiers = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier); QModelIndex index = currentIndex(); - selectionModel()->select (index, - QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows); + selectionModel()->select( + index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows); - std::map::iterator iter = - mDoubleClickActions.find (modifiers); + std::map::iterator iter = mDoubleClickActions.find(modifiers); - if (iter==mDoubleClickActions.end()) + if (iter == mDoubleClickActions.end()) { event->accept(); return; @@ -191,7 +205,7 @@ void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event) case Action_InPlaceEdit: - DragRecordTable::mouseDoubleClickEvent (event); + DragRecordTable::mouseDoubleClickEvent(event); break; case Action_EditRecord: @@ -236,83 +250,95 @@ void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event) } } -CSVWorld::Table::Table (const CSMWorld::UniversalId& id, - bool createAndDelete, bool sorting, CSMDoc::Document& document) - : DragRecordTable(document), mCreateAction (nullptr), mCloneAction(nullptr), mTouchAction(nullptr), - mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false) +CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) + : DragRecordTable(document) + , mCreateAction(nullptr) + , mCloneAction(nullptr) + , mTouchAction(nullptr) + , mRecordStatusDisplay(0) + , mJumpToAddedRecord(false) + , mUnselectAfterJump(false) + , mAutoJump(false) { - mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); + mModel = &dynamic_cast(*mDocument.getData().getTableModel(id)); - bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos || - id.getType() == CSMWorld::UniversalId::Type_JournalInfos; + bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos + || id.getType() == CSMWorld::UniversalId::Type_JournalInfos; bool isLtexTable = (id.getType() == CSMWorld::UniversalId::Type_LandTextures); if (isInfoTable) { mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this); - connect (this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords); + connect(this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords); + connect(this, &CSVWorld::DragRecordTable::createNewInfoRecord, this, + &CSVWorld::Table::createRecordsDirectlyRequest); } else if (isLtexTable) { - mProxyModel = new CSMWorld::LandTextureTableProxyModel (this); + mProxyModel = new CSMWorld::LandTextureTableProxyModel(this); } else { - mProxyModel = new CSMWorld::IdTableProxyModel (this); + mProxyModel = new CSMWorld::IdTableProxyModel(this); } - mProxyModel->setSourceModel (mModel); + mProxyModel->setSourceModel(mModel); - mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); + mDispatcher = new CSMWorld::CommandDispatcher(document, id, this); - setModel (mProxyModel); - horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); + setModel(mProxyModel); + horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + // The number is arbitrary but autoresize would be way too slow otherwise. + constexpr int autoResizePrecision = 500; + horizontalHeader()->setResizeContentsPrecision(autoResizePrecision); + resizeColumnsToContents(); verticalHeader()->hide(); - setSelectionBehavior (QAbstractItemView::SelectRows); - setSelectionMode (QAbstractItemView::ExtendedSelection); - - setSortingEnabled (sorting); - if (sorting) - { - sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); - } + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::ExtendedSelection); int columns = mModel->columnCount(); - for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + int flags = mModel->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); if (flags & CSMWorld::ColumnBase::Flag_Table) { - CSMWorld::ColumnBase::Display display = static_cast ( - mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + CSMWorld::ColumnBase::Display display = static_cast( + mModel->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, - mDispatcher, document, this); + CommandDelegate* delegate + = CommandDelegateFactoryCollection::get().makeDelegate(display, mDispatcher, document, this); - mDelegates.push_back (delegate); - setItemDelegateForColumn (i, delegate); + mDelegates.push_back(delegate); + setItemDelegateForColumn(i, delegate); } else - hideColumn (i); + hideColumn(i); } - mEditAction = new QAction (tr ("Edit Record"), this); - connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); + if (sorting) + { + // FIXME: some tables (e.g. CellRef) have this column hidden, which makes it confusing + sortByColumn(mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); + } + setSortingEnabled(sorting); + + mEditAction = new QAction(tr("Edit Record"), this); + connect(mEditAction, &QAction::triggered, this, &Table::editRecord); mEditAction->setIcon(QIcon(":edit-edit")); - addAction (mEditAction); + addAction(mEditAction); CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this); editShortcut->associateAction(mEditAction); if (createAndDelete) { - mCreateAction = new QAction (tr ("Add Record"), this); - connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); + mCreateAction = new QAction(tr("Add Record"), this); + connect(mCreateAction, &QAction::triggered, this, &Table::createRequest); mCreateAction->setIcon(QIcon(":edit-add")); - addAction (mCreateAction); + addAction(mCreateAction); CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this); createShortcut->associateAction(mCreateAction); - mCloneAction = new QAction (tr ("Clone Record"), this); - connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); + mCloneAction = new QAction(tr("Clone Record"), this); + connect(mCloneAction, &QAction::triggered, this, &Table::cloneRecord); mCloneAction->setIcon(QIcon(":edit-clone")); addAction(mCloneAction); CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this); @@ -322,139 +348,134 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch) { mTouchAction = new QAction(tr("Touch Record"), this); - connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord())); + connect(mTouchAction, &QAction::triggered, this, &Table::touchRecord); mTouchAction->setIcon(QIcon(":edit-touch")); addAction(mTouchAction); CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this); touchShortcut->associateAction(mTouchAction); } - mRevertAction = new QAction (tr ("Revert Record"), this); - connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert())); + mRevertAction = new QAction(tr("Revert Record"), this); + connect(mRevertAction, &QAction::triggered, mDispatcher, &CSMWorld::CommandDispatcher::executeRevert); mRevertAction->setIcon(QIcon(":edit-undo")); - addAction (mRevertAction); + addAction(mRevertAction); CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this); revertShortcut->associateAction(mRevertAction); - mDeleteAction = new QAction (tr ("Delete Record"), this); - connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete())); + mDeleteAction = new QAction(tr("Delete Record"), this); + connect(mDeleteAction, &QAction::triggered, mDispatcher, &CSMWorld::CommandDispatcher::executeDelete); mDeleteAction->setIcon(QIcon(":edit-delete")); - addAction (mDeleteAction); + addAction(mDeleteAction); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this); deleteShortcut->associateAction(mDeleteAction); - mMoveUpAction = new QAction (tr ("Move Up"), this); - connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); + mMoveUpAction = new QAction(tr("Move Up"), this); + connect(mMoveUpAction, &QAction::triggered, this, &Table::moveUpRecord); mMoveUpAction->setIcon(QIcon(":record-up")); - addAction (mMoveUpAction); + addAction(mMoveUpAction); CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this); moveUpShortcut->associateAction(mMoveUpAction); - mMoveDownAction = new QAction (tr ("Move Down"), this); - connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); + mMoveDownAction = new QAction(tr("Move Down"), this); + connect(mMoveDownAction, &QAction::triggered, this, &Table::moveDownRecord); mMoveDownAction->setIcon(QIcon(":record-down")); - addAction (mMoveDownAction); + addAction(mMoveDownAction); CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this); moveDownShortcut->associateAction(mMoveDownAction); - mViewAction = new QAction (tr ("View"), this); - connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); + mViewAction = new QAction(tr("View"), this); + connect(mViewAction, &QAction::triggered, this, &Table::viewRecord); mViewAction->setIcon(QIcon(":/cell.png")); - addAction (mViewAction); + addAction(mViewAction); CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); viewShortcut->associateAction(mViewAction); - mPreviewAction = new QAction (tr ("Preview"), this); - connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); + mPreviewAction = new QAction(tr("Preview"), this); + connect(mPreviewAction, &QAction::triggered, this, &Table::previewRecord); mPreviewAction->setIcon(QIcon(":edit-preview")); - addAction (mPreviewAction); + addAction(mPreviewAction); CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this); previewShortcut->associateAction(mPreviewAction); - mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); - connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete())); + mExtendedDeleteAction = new QAction(tr("Extended Delete Record"), this); + connect(mExtendedDeleteAction, &QAction::triggered, this, &Table::executeExtendedDelete); mExtendedDeleteAction->setIcon(QIcon(":edit-delete")); - addAction (mExtendedDeleteAction); + addAction(mExtendedDeleteAction); CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this); extendedDeleteShortcut->associateAction(mExtendedDeleteAction); - mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); - connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert())); + mExtendedRevertAction = new QAction(tr("Extended Revert Record"), this); + connect(mExtendedRevertAction, &QAction::triggered, this, &Table::executeExtendedRevert); mExtendedRevertAction->setIcon(QIcon(":edit-undo")); - addAction (mExtendedRevertAction); + addAction(mExtendedRevertAction); CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this); extendedRevertShortcut->associateAction(mExtendedRevertAction); - mEditIdAction = new TableEditIdAction (*this, this); - connect (mEditIdAction, SIGNAL (triggered()), this, SLOT (editCell())); - addAction (mEditIdAction); + mEditIdAction = new TableEditIdAction(*this, this); + connect(mEditIdAction, &QAction::triggered, this, &Table::editCell); + addAction(mEditIdAction); - mHelpAction = new QAction (tr ("Help"), this); - connect (mHelpAction, SIGNAL (triggered()), this, SLOT (openHelp())); + mHelpAction = new QAction(tr("Help"), this); + connect(mHelpAction, &QAction::triggered, this, &Table::openHelp); mHelpAction->setIcon(QIcon(":/info.png")); - addAction (mHelpAction); + addAction(mHelpAction); CSMPrefs::Shortcut* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); - connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (tableSizeUpdate())); + connect(mProxyModel, &CSMWorld::IdTableProxyModel::rowsRemoved, this, &Table::tableSizeUpdate); - connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), - this, SLOT (rowAdded (const std::string &))); + connect(mProxyModel, &CSMWorld::IdTableProxyModel::rowAdded, this, &Table::rowAdded); /// \note This signal could instead be connected to a slot that filters out changes not affecting /// the records status column (for permanence reasons) - connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (tableSizeUpdate())); + connect(mProxyModel, &CSMWorld::IdTableProxyModel::dataChanged, this, &Table::dataChangedEvent); - connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), - this, SLOT (selectionSizeUpdate ())); + connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &Table::selectionSizeUpdate); setAcceptDrops(true); - mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_InPlaceEdit)); - mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_EditRecord)); - mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_View)); - mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose)); + mDoubleClickActions.insert(std::make_pair(Qt::NoModifier, Action_InPlaceEdit)); + mDoubleClickActions.insert(std::make_pair(Qt::ShiftModifier, Action_EditRecord)); + mDoubleClickActions.insert(std::make_pair(Qt::ControlModifier, Action_View)); + mDoubleClickActions.insert(std::make_pair(Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose)); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &Table::settingChanged); CSMPrefs::get()["ID Tables"].update(); + + new TableHeaderMouseEventHandler(this); } -void CSVWorld::Table::setEditLock (bool locked) +void CSVWorld::Table::setEditLock(bool locked) { - for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) - (*iter)->setEditLock (locked); + for (std::vector::iterator iter(mDelegates.begin()); iter != mDelegates.end(); ++iter) + (*iter)->setEditLock(locked); DragRecordTable::setEditLock(locked); - mDispatcher->setEditLock (locked); + mDispatcher->setEditLock(locked); } -CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const +CSMWorld::UniversalId CSVWorld::Table::getUniversalId(int row) const { - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row(); - int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - int typeColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + int typeColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - return CSMWorld::UniversalId ( - static_cast (mModel->data (mModel->index (row, typeColumn)).toInt()), - mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData()); + return CSMWorld::UniversalId( + static_cast(mModel->data(mModel->index(row, typeColumn)).toInt()), + mModel->data(mModel->index(row, idColumn)).toString().toUtf8().constData()); } std::vector CSVWorld::Table::getSelectedIds() const { std::vector ids; QModelIndexList selectedRows = selectionModel()->selectedRows(); - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int columnIndex = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); - for (QModelIndexList::const_iterator iter (selectedRows.begin()); - iter != selectedRows.end(); - ++iter) + for (QModelIndexList::const_iterator iter(selectedRows.begin()); iter != selectedRows.end(); ++iter) { - int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); - ids.emplace_back(mModel->data (mModel->index (row, columnIndex)).toString().toUtf8().constData()); + int row = mProxyModel->mapToSource(mProxyModel->index(iter->row(), 0)).row(); + ids.emplace_back(mModel->data(mModel->index(row, columnIndex)).toString().toUtf8().constData()); } return ids; } @@ -465,8 +486,8 @@ void CSVWorld::Table::editRecord() { QModelIndexList selectedRows = selectionModel()->selectedRows(); - if (selectedRows.size()==1) - emit editRequest (getUniversalId (selectedRows.begin()->row()), ""); + if (selectedRows.size() == 1) + emit editRequest(getUniversalId(selectedRows.begin()->row()), ""); } } @@ -478,7 +499,7 @@ void CSVWorld::Table::cloneRecord() const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); if (selectedRows.size() == 1) { - emit cloneRequest (toClone); + emit cloneRequest(toClone); } } } @@ -506,28 +527,28 @@ void CSVWorld::Table::moveUpRecord() QModelIndexList selectedRows = selectionModel()->selectedRows(); - if (selectedRows.size()==1) + if (selectedRows.size() == 1) { - int row2 =selectedRows.begin()->row(); + int row2 = selectedRows.begin()->row(); - if (row2>0) + if (row2 > 0) { - int row = row2-1; + int row = row2 - 1; - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); - row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row(); + row2 = mProxyModel->mapToSource(mProxyModel->index(row2, 0)).row(); - if (row2<=row) - throw std::runtime_error ("Inconsistent row order"); + if (row2 <= row) + throw std::runtime_error("Inconsistent row order"); - std::vector newOrder (row2-row+1); - newOrder[0] = row2-row; - newOrder[row2-row] = 0; - for (int i=1; i newOrder(row2 - row + 1); + newOrder[0] = row2 - row; + newOrder[row2 - row] = 0; + for (int i = 1; i < row2 - row; ++i) newOrder[i] = i; - mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand ( - dynamic_cast (*mModel), row, newOrder)); + mDocument.getUndoStack().push( + new CSMWorld::ReorderRowsCommand(dynamic_cast(*mModel), row, newOrder)); } } } @@ -539,33 +560,33 @@ void CSVWorld::Table::moveDownRecord() QModelIndexList selectedRows = selectionModel()->selectedRows(); - if (selectedRows.size()==1) + if (selectedRows.size() == 1) { - int row =selectedRows.begin()->row(); + int row = selectedRows.begin()->row(); - if (rowrowCount()-1) + if (row < mProxyModel->rowCount() - 1) { - int row2 = row+1; + int row2 = row + 1; - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); - row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row(); + row2 = mProxyModel->mapToSource(mProxyModel->index(row2, 0)).row(); - if (row2<=row) - throw std::runtime_error ("Inconsistent row order"); + if (row2 <= row) + throw std::runtime_error("Inconsistent row order"); - std::vector newOrder (row2-row+1); - newOrder[0] = row2-row; - newOrder[row2-row] = 0; - for (int i=1; i newOrder(row2 - row + 1); + newOrder[0] = row2 - row; + newOrder[row2 - row] = 0; + for (int i = 1; i < row2 - row; ++i) newOrder[i] = i; - mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand ( - dynamic_cast (*mModel), row, newOrder)); + mDocument.getUndoStack().push( + new CSMWorld::ReorderRowsCommand(dynamic_cast(*mModel), row, newOrder)); } } } -void CSVWorld::Table::moveRecords(QDropEvent *event) +void CSVWorld::Table::moveRecords(QDropEvent* event) { if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) return; @@ -574,21 +595,23 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) QModelIndexList selectedRows = selectionModel()->selectedRows(); int targetRowRaw = targedIndex.row(); - int targetRow = mProxyModel->mapToSource (mProxyModel->index (targetRowRaw, 0)).row(); + int targetRow = mProxyModel->mapToSource(mProxyModel->index(targetRowRaw, 0)).row(); int baseRowRaw = targedIndex.row() - 1; - int baseRow = mProxyModel->mapToSource (mProxyModel->index (baseRowRaw, 0)).row(); + int baseRow = mProxyModel->mapToSource(mProxyModel->index(baseRowRaw, 0)).row(); int highestDifference = 0; for (const auto& thisRowData : selectedRows) { - int thisRow = mProxyModel->mapToSource (mProxyModel->index (thisRowData.row(), 0)).row(); - if (std::abs(targetRow - thisRow) > highestDifference) highestDifference = std::abs(targetRow - thisRow); - if (thisRow - 1 < baseRow) baseRow = thisRow - 1; + int thisRow = mProxyModel->mapToSource(mProxyModel->index(thisRowData.row(), 0)).row(); + if (std::abs(targetRow - thisRow) > highestDifference) + highestDifference = std::abs(targetRow - thisRow); + if (thisRow - 1 < baseRow) + baseRow = thisRow - 1; } - std::vector newOrder (highestDifference + 1); + std::vector newOrder(highestDifference + 1); - for (long unsigned int i = 0; i < newOrder.size(); ++i) + for (int i = 0; i <= highestDifference; ++i) { newOrder[i] = i; } @@ -611,9 +634,9 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) */ int originRowRaw = thisRowData.row(); - int originRow = mProxyModel->mapToSource (mProxyModel->index (originRowRaw, 0)).row(); + int originRow = mProxyModel->mapToSource(mProxyModel->index(originRowRaw, 0)).row(); - newOrder.erase(newOrder.begin() + originRow - baseRow - 1); + newOrder.erase(newOrder.begin() + originRow - baseRow - 1); newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1); if (originRow > targetRow) @@ -630,10 +653,9 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) --newOrder[i]; } } - } - mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand ( - dynamic_cast (*mModel), baseRow + 1, newOrder)); + mDocument.getUndoStack().push( + new CSMWorld::ReorderRowsCommand(dynamic_cast(*mModel), baseRow + 1, newOrder)); } void CSVWorld::Table::editCell() @@ -653,16 +675,16 @@ void CSVWorld::Table::viewRecord() QModelIndexList selectedRows = selectionModel()->selectedRows(); - if (selectedRows.size()==1) + if (selectedRows.size() == 1) { int row = selectedRows.begin()->row(); - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row = mProxyModel->mapToSource(mProxyModel->index(row, 0)).row(); - std::pair params = mModel->view (row); + std::pair params = mModel->view(row); - if (params.first.getType()!=CSMWorld::UniversalId::Type_None) - emit editRequest (params.first, params.second); + if (params.first.getType() != CSMWorld::UniversalId::Type_None) + emit editRequest(params.first, params.second); } } @@ -670,16 +692,15 @@ void CSVWorld::Table::previewRecord() { QModelIndexList selectedRows = selectionModel()->selectedRows(); - if (selectedRows.size()==1) + if (selectedRows.size() == 1) { - std::string id = getUniversalId (selectedRows.begin()->row()).getId(); + std::string id = getUniversalId(selectedRows.begin()->row()).getId(); - QModelIndex index = mModel->getModelIndex (id, - mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + QModelIndex index + = mModel->getModelIndex(id, mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification)); - if (mModel->data (index)!=CSMWorld::RecordBase::State_Deleted) - emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Preview, id), - ""); + if (mModel->data(index) != CSMWorld::RecordBase::State_Deleted) + emit editRequest(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, id), ""); } } @@ -707,16 +728,16 @@ void CSVWorld::Table::executeExtendedRevert() } } -void CSVWorld::Table::settingChanged (const CSMPrefs::Setting *setting) +void CSVWorld::Table::settingChanged(const CSMPrefs::Setting* setting) { - if (*setting=="ID Tables/jump-to-added") + if (*setting == "ID Tables/jump-to-added") { - if (setting->toString()=="Jump and Select") + if (setting->toString() == "Jump and Select") { mJumpToAddedRecord = true; mUnselectAfterJump = false; } - else if (setting->toString()=="Jump Only") + else if (setting->toString() == "Jump Only") { mJumpToAddedRecord = true; mUnselectAfterJump = true; @@ -727,49 +748,47 @@ void CSVWorld::Table::settingChanged (const CSMPrefs::Setting *setting) mUnselectAfterJump = false; } } - else if (*setting=="Records/type-format" || *setting=="Records/status-format") + else if (*setting == "Records/type-format" || *setting == "Records/status-format") { int columns = mModel->columnCount(); - for (int i=0; i (*delegate).settingChanged (setting); - emit dataChanged (mModel->index (0, i), - mModel->index (mModel->rowCount()-1, i)); + dynamic_cast(*delegate).settingChanged(setting); + emit dataChanged(mModel->index(0, i), mModel->index(mModel->rowCount() - 1, i)); } } - else if (setting->getParent()->getKey()=="ID Tables" && - setting->getKey().substr (0, 6)=="double") + else if (setting->getParent()->getKey() == "ID Tables" && setting->getKey().substr(0, 6) == "double") { - std::string modifierString = setting->getKey().substr (6); + std::string modifierString = setting->getKey().substr(6); Qt::KeyboardModifiers modifiers; - if (modifierString=="-s") + if (modifierString == "-s") modifiers = Qt::ShiftModifier; - else if (modifierString=="-c") + else if (modifierString == "-c") modifiers = Qt::ControlModifier; - else if (modifierString=="-sc") + else if (modifierString == "-sc") modifiers = Qt::ShiftModifier | Qt::ControlModifier; DoubleClickAction action = Action_None; std::string value = setting->toString(); - if (value=="Edit in Place") + if (value == "Edit in Place") action = Action_InPlaceEdit; - else if (value=="Edit Record") + else if (value == "Edit Record") action = Action_EditRecord; - else if (value=="View") + else if (value == "View") action = Action_View; - else if (value=="Revert") + else if (value == "Revert") action = Action_Revert; - else if (value=="Delete") + else if (value == "Delete") action = Action_Delete; - else if (value=="Edit Record and Close") + else if (value == "Edit Record and Close") action = Action_EditRecordAndClose; - else if (value=="View and Close") + else if (value == "View and Close") action = Action_ViewAndClose; mDoubleClickActions[modifiers] = action; @@ -782,26 +801,37 @@ void CSVWorld::Table::tableSizeUpdate() int deleted = 0; int modified = 0; - if (mProxyModel->columnCount()>0) + if (mProxyModel->columnCount() > 0) { int rows = mProxyModel->rowCount(); - int columnIndex = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Modification); + int columnIndex = mModel->searchColumnIndex(CSMWorld::Columns::ColumnId_Modification); - if (columnIndex!=-1) + if (columnIndex != -1) { - for (int i=0; imapToSource (mProxyModel->index (i, 0)); + QModelIndex index = mProxyModel->mapToSource(mProxyModel->index(i, 0)); - int state = mModel->data (mModel->index (index.row(), columnIndex)).toInt(); + int state = mModel->data(mModel->index(index.row(), columnIndex)).toInt(); switch (state) { - case CSMWorld::RecordBase::State_BaseOnly: ++size; break; - case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; - case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; - case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break; + case CSMWorld::RecordBase::State_BaseOnly: + ++size; + break; + case CSMWorld::RecordBase::State_Modified: + ++size; + ++modified; + break; + case CSMWorld::RecordBase::State_ModifiedOnly: + ++size; + ++modified; + break; + case CSMWorld::RecordBase::State_Deleted: + ++deleted; + ++modified; + break; } } } @@ -809,39 +839,39 @@ void CSVWorld::Table::tableSizeUpdate() size = rows; } - emit tableSizeChanged (size, deleted, modified); + emit tableSizeChanged(size, deleted, modified); } void CSVWorld::Table::selectionSizeUpdate() { - emit selectionSizeChanged (selectionModel()->selectedRows().size()); + emit selectionSizeChanged(selectionModel()->selectedRows().size()); } -void CSVWorld::Table::requestFocus (const std::string& id) +void CSVWorld::Table::requestFocus(const std::string& id) { - QModelIndex index = mProxyModel->getModelIndex (id, 0); + QModelIndex index = mProxyModel->getModelIndex(id, 0); if (index.isValid()) { // This will scroll to the row. - selectRow (index.row()); + selectRow(index.row()); // This will actually select it. - selectionModel()->select (index, QItemSelectionModel::Select | QItemSelectionModel::Rows); + selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); } } -void CSVWorld::Table::recordFilterChanged (std::shared_ptr filter) +void CSVWorld::Table::recordFilterChanged(std::shared_ptr filter) { - mProxyModel->setFilter (filter); + mProxyModel->setFilter(filter); tableSizeUpdate(); selectionSizeUpdate(); } -void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) +void CSVWorld::Table::mouseMoveEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { - startDragFromTable(*this); + startDragFromTable(*this, indexAt(event->pos())); } } @@ -852,37 +882,69 @@ std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::Column std::vector titles; for (int i = 0; i < count; ++i) { - CSMWorld::ColumnBase::Display columndisplay = static_cast - (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + CSMWorld::ColumnBase::Display columndisplay = static_cast( + mModel->headerData(i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); if (display == columndisplay) { - titles.emplace_back(mModel->headerData (i, Qt::Horizontal).toString().toUtf8().constData()); + titles.emplace_back(mModel->headerData(i, Qt::Horizontal).toString().toUtf8().constData()); } } return titles; } -std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const +std::vector CSVWorld::Table::getDraggedRecords() const { QModelIndexList selectedRows = selectionModel()->selectedRows(); std::vector idToDrag; for (QModelIndex& it : selectedRows) - idToDrag.push_back (getUniversalId (it.row())); + idToDrag.push_back(getUniversalId(it.row())); return idToDrag; } -void CSVWorld::Table::rowAdded(const std::string &id) +// parent, start and end depend on the model sending the signal, in this case mProxyModel +// +// If, for example, mModel was used instead, then scrolTo() should use the index +// mProxyModel->mapFromSource(mModel->index(end, 0)) +void CSVWorld::Table::rowAdded(const std::string& id) { tableSizeUpdate(); - if(mJumpToAddedRecord) + if (mJumpToAddedRecord) { int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); - selectRow(mProxyModel->getModelIndex(id, idColumn).row()); + int end = mProxyModel->getModelIndex(id, idColumn).row(); + selectRow(end); - if(mUnselectAfterJump) + // without this delay the scroll works but goes to top for add/clone + QMetaObject::invokeMethod(this, "queuedScrollTo", Qt::QueuedConnection, Q_ARG(int, end)); + + if (mUnselectAfterJump) clearSelection(); } } + +void CSVWorld::Table::queuedScrollTo(int row) +{ + scrollTo(mProxyModel->index(row, 0), QAbstractItemView::PositionAtCenter); +} + +void CSVWorld::Table::dataChangedEvent(const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + tableSizeUpdate(); + + if (mAutoJump) + { + selectRow(bottomRight.row()); + scrollTo(bottomRight, QAbstractItemView::PositionAtCenter); + } +} + +void CSVWorld::Table::jumpAfterModChanged(int state) +{ + if (state == Qt::Checked) + mAutoJump = true; + else + mAutoJump = false; +} diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 9c4b9b5e5..e7a30f4a2 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -1,17 +1,26 @@ #ifndef CSV_WORLD_TABLE_H #define CSV_WORLD_TABLE_H -#include +#include +#include #include +#include -#include - -#include "../../model/filter/node.hpp" #include "../../model/world/columnbase.hpp" #include "../../model/world/universalid.hpp" #include "dragrecordtable.hpp" class QAction; +class QContextMenuEvent; +class QDropEvent; +class QModelIndex; +class QMouseEvent; +class QObject; + +namespace CSMFilter +{ + class Node; +} namespace CSMDoc { @@ -38,132 +47,137 @@ namespace CSVWorld ///< Table widget class Table : public DragRecordTable { - Q_OBJECT + Q_OBJECT - enum DoubleClickAction - { - Action_None, - Action_InPlaceEdit, - Action_EditRecord, - Action_View, - Action_Revert, - Action_Delete, - Action_EditRecordAndClose, - Action_ViewAndClose - }; + enum DoubleClickAction + { + Action_None, + Action_InPlaceEdit, + Action_EditRecord, + Action_View, + Action_Revert, + Action_Delete, + Action_EditRecordAndClose, + Action_ViewAndClose + }; - std::vector mDelegates; - QAction *mEditAction; - QAction *mCreateAction; - QAction *mCloneAction; - QAction *mTouchAction; - QAction *mRevertAction; - QAction *mDeleteAction; - QAction *mMoveUpAction; - QAction *mMoveDownAction; - QAction *mViewAction; - QAction *mPreviewAction; - QAction *mExtendedDeleteAction; - QAction *mExtendedRevertAction; - QAction *mHelpAction; - TableEditIdAction *mEditIdAction; - CSMWorld::IdTableProxyModel *mProxyModel; - CSMWorld::IdTableBase *mModel; - int mRecordStatusDisplay; - CSMWorld::CommandDispatcher *mDispatcher; - std::map mDoubleClickActions; - bool mJumpToAddedRecord; - bool mUnselectAfterJump; + std::vector mDelegates; + QAction* mEditAction; + QAction* mCreateAction; + QAction* mCloneAction; + QAction* mTouchAction; + QAction* mRevertAction; + QAction* mDeleteAction; + QAction* mMoveUpAction; + QAction* mMoveDownAction; + QAction* mViewAction; + QAction* mPreviewAction; + QAction* mExtendedDeleteAction; + QAction* mExtendedRevertAction; + QAction* mHelpAction; + TableEditIdAction* mEditIdAction; + CSMWorld::IdTableProxyModel* mProxyModel; + CSMWorld::IdTableBase* mModel; + int mRecordStatusDisplay; + CSMWorld::CommandDispatcher* mDispatcher; + std::map mDoubleClickActions; + bool mJumpToAddedRecord; + bool mUnselectAfterJump; + bool mAutoJump; - private: + private: + void contextMenuEvent(QContextMenuEvent* event) override; - void contextMenuEvent (QContextMenuEvent *event) override; + void mouseMoveEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent *event) override; + protected: + void mouseDoubleClickEvent(QMouseEvent* event) override; - protected: + public: + Table(const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document); + ///< \param createAndDelete Allow creation and deletion of records. + /// \param sorting Allow changing order of rows in the view via column headers. - void mouseDoubleClickEvent (QMouseEvent *event) override; + virtual void setEditLock(bool locked); - public: + CSMWorld::UniversalId getUniversalId(int row) const; - Table (const CSMWorld::UniversalId& id, bool createAndDelete, - bool sorting, CSMDoc::Document& document); - ///< \param createAndDelete Allow creation and deletion of records. - /// \param sorting Allow changing order of rows in the view via column headers. + std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; - virtual void setEditLock (bool locked); + std::vector getSelectedIds() const; - CSMWorld::UniversalId getUniversalId (int row) const; + std::vector getDraggedRecords() const override; - std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; + signals: - std::vector getSelectedIds() const; + void editRequest(const CSMWorld::UniversalId& id, const std::string& hint); - std::vector getDraggedRecords() const override; + void selectionSizeChanged(int size); - signals: + void tableSizeChanged(int size, int deleted, int modified); + ///< \param size Number of not deleted records + /// \param deleted Number of deleted records + /// \param modified Number of added and modified records - void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); + void createRequest(); - void selectionSizeChanged (int size); + void createRecordsDirectlyRequest(const std::string& id); - void tableSizeChanged (int size, int deleted, int modified); - ///< \param size Number of not deleted records - /// \param deleted Number of deleted records - /// \param modified Number of added and modified records + void cloneRequest(const CSMWorld::UniversalId&); - void createRequest(); + void touchRequest(const std::vector& ids); - void cloneRequest(const CSMWorld::UniversalId&); + void closeRequest(); - void touchRequest(const std::vector& ids); + void extendedDeleteConfigRequest(const std::vector& selectedIds); - void closeRequest(); + void extendedRevertConfigRequest(const std::vector& selectedIds); - void extendedDeleteConfigRequest(const std::vector &selectedIds); + private slots: - void extendedRevertConfigRequest(const std::vector &selectedIds); + void editCell(); - private slots: + static void openHelp(); - void editCell(); + void editRecord(); - static void openHelp(); + void cloneRecord(); - void editRecord(); + void touchRecord(); - void cloneRecord(); + void moveUpRecord(); - void touchRecord(); + void moveDownRecord(); - void moveUpRecord(); + void moveRecords(QDropEvent* event); - void moveDownRecord(); + void viewRecord(); - void moveRecords(QDropEvent *event); + void previewRecord(); - void viewRecord(); + void executeExtendedDelete(); - void previewRecord(); + void executeExtendedRevert(); - void executeExtendedDelete(); + public slots: - void executeExtendedRevert(); + void settingChanged(const CSMPrefs::Setting* setting); - public slots: + void tableSizeUpdate(); - void settingChanged (const CSMPrefs::Setting *setting); + void selectionSizeUpdate(); - void tableSizeUpdate(); + void requestFocus(const std::string& id); - void selectionSizeUpdate(); + void recordFilterChanged(std::shared_ptr filter); - void requestFocus (const std::string& id); + void rowAdded(const std::string& id); - void recordFilterChanged (std::shared_ptr filter); + void dataChangedEvent(const QModelIndex& topLeft, const QModelIndex& bottomRight); - void rowAdded(const std::string &id); + void jumpAfterModChanged(int state); + + void queuedScrollTo(int state); }; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 1b065da49..72b70b810 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -2,13 +2,18 @@ #include -#include -#include -#include #include #include +#include +#include +#include + +#include + +#include #include "creator.hpp" +#include "infocreator.hpp" void CSVWorld::TableBottomBox::updateSize() { @@ -30,29 +35,27 @@ void CSVWorld::TableBottomBox::updateStatus() { if (!mStatusMessage.isEmpty()) { - mStatus->setText (mStatusMessage); + mStatus->setText(mStatusMessage); return; } - static const char *sLabels[4] = { "record", "deleted", "touched", "selected" }; - static const char *sLabelsPlural[4] = { "records", "deleted", "touched", "selected" }; + static const char* sLabels[4] = { "record", "deleted", "touched", "selected" }; + static const char* sLabelsPlural[4] = { "records", "deleted", "touched", "selected" }; std::ostringstream stream; bool first = true; - for (int i=0; i<4; ++i) + for (int i = 0; i < 4; ++i) { - if (mStatusCount[i]>0) + if (mStatusCount[i] > 0) { if (first) first = false; else stream << ", "; - stream - << mStatusCount[i] << ' ' - << (mStatusCount[i]==1 ? sLabels[i] : sLabelsPlural[i]); + stream << mStatusCount[i] << ' ' << (mStatusCount[i] == 1 ? sLabels[i] : sLabelsPlural[i]); } } @@ -64,71 +67,73 @@ void CSVWorld::TableBottomBox::updateStatus() stream << "(" << mRow << ", " << mColumn << ")"; } - mStatus->setText (QString::fromUtf8 (stream.str().c_str())); + mStatus->setText(QString::fromUtf8(stream.str().c_str())); } } -void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandConfigurator::Mode mode, - const std::vector &selectedIds) +void CSVWorld::TableBottomBox::extendedConfigRequest( + CSVWorld::ExtendedCommandConfigurator::Mode mode, const std::vector& selectedIds) { - mExtendedConfigurator->configure (mode, selectedIds); - mLayout->setCurrentWidget (mExtendedConfigurator); + mExtendedConfigurator->configure(mode, selectedIds); + mLayout->setCurrentWidget(mExtendedConfigurator); mEditMode = EditMode_ExtendedConfig; - setVisible (true); + setVisible(true); mExtendedConfigurator->setFocus(); } -CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, - CSMDoc::Document& document, - const CSMWorld::UniversalId& id, - QWidget *parent) -: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false), mRow(0), mColumn(0) +CSVWorld::TableBottomBox::TableBottomBox(const CreatorFactoryBase& creatorFactory, CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QWidget* parent) + : QWidget(parent) + , mShowStatusBar(false) + , mEditMode(EditMode_None) + , mHasPosition(false) + , mRow(0) + , mColumn(0) { - for (int i=0; i<4; ++i) + for (int i = 0; i < 4; ++i) mStatusCount[i] = 0; - setVisible (false); + setVisible(false); mLayout = new QStackedLayout; - mLayout->setContentsMargins (0, 0, 0, 0); - connect (mLayout, SIGNAL (currentChanged (int)), this, SLOT (currentWidgetChanged (int))); + mLayout->setContentsMargins(0, 0, 0, 0); + connect(mLayout, &QStackedLayout::currentChanged, this, &TableBottomBox::currentWidgetChanged); mStatus = new QLabel; mStatusBar = new QStatusBar(this); - mStatusBar->addWidget (mStatus); + mStatusBar->addWidget(mStatus); - mLayout->addWidget (mStatusBar); + mLayout->addWidget(mStatusBar); - setLayout (mLayout); + setLayout(mLayout); - mCreator = creatorFactory.makeCreator (document, id); + mCreator = creatorFactory.makeCreator(document, id); if (mCreator) { mCreator->installEventFilter(this); - mLayout->addWidget (mCreator); + mLayout->addWidget(mCreator); - connect (mCreator, SIGNAL (done()), this, SLOT (requestDone())); + connect(mCreator, &Creator::done, this, &TableBottomBox::requestDone); - connect (mCreator, SIGNAL (requestFocus (const std::string&)), - this, SIGNAL (requestFocus (const std::string&))); + connect(mCreator, &Creator::requestFocus, this, &TableBottomBox::requestFocus); } - mExtendedConfigurator = new ExtendedCommandConfigurator (document, id, this); + mExtendedConfigurator = new ExtendedCommandConfigurator(document, id, this); mExtendedConfigurator->installEventFilter(this); - mLayout->addWidget (mExtendedConfigurator); - connect (mExtendedConfigurator, SIGNAL (done()), this, SLOT (requestDone())); + mLayout->addWidget(mExtendedConfigurator); + connect(mExtendedConfigurator, &ExtendedCommandConfigurator::done, this, &TableBottomBox::requestDone); updateSize(); } -void CSVWorld::TableBottomBox::setEditLock (bool locked) +void CSVWorld::TableBottomBox::setEditLock(bool locked) { if (mCreator) - mCreator->setEditLock (locked); - mExtendedConfigurator->setEditLock (locked); + mCreator->setEditLock(locked); + mExtendedConfigurator->setEditLock(locked); } CSVWorld::TableBottomBox::~TableBottomBox() @@ -136,11 +141,11 @@ CSVWorld::TableBottomBox::~TableBottomBox() delete mCreator; } -bool CSVWorld::TableBottomBox::eventFilter(QObject *object, QEvent *event) +bool CSVWorld::TableBottomBox::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); + QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape) { requestDone(); @@ -150,11 +155,11 @@ bool CSVWorld::TableBottomBox::eventFilter(QObject *object, QEvent *event) return QWidget::eventFilter(object, event); } -void CSVWorld::TableBottomBox::setStatusBar (bool show) +void CSVWorld::TableBottomBox::setStatusBar(bool show) { - if (show!=mShowStatusBar) + if (show != mShowStatusBar) { - setVisible (show || (mEditMode != EditMode_None)); + setVisible(show || (mEditMode != EditMode_None)); mShowStatusBar = show; @@ -171,11 +176,11 @@ bool CSVWorld::TableBottomBox::canCreateAndDelete() const void CSVWorld::TableBottomBox::requestDone() { if (!mShowStatusBar) - setVisible (false); + setVisible(false); else updateStatus(); - mLayout->setCurrentWidget (mStatusBar); + mLayout->setCurrentWidget(mStatusBar); mEditMode = EditMode_None; } @@ -184,15 +189,15 @@ void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/) updateSize(); } -void CSVWorld::TableBottomBox::setStatusMessage (const QString& message) +void CSVWorld::TableBottomBox::setStatusMessage(const QString& message) { mStatusMessage = message; updateStatus(); } -void CSVWorld::TableBottomBox::selectionSizeChanged (int size) +void CSVWorld::TableBottomBox::selectionSizeChanged(int size) { - if (mStatusCount[3]!=size) + if (mStatusCount[3] != size) { mStatusMessage = ""; mStatusCount[3] = size; @@ -200,23 +205,23 @@ void CSVWorld::TableBottomBox::selectionSizeChanged (int size) } } -void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modified) +void CSVWorld::TableBottomBox::tableSizeChanged(int size, int deleted, int modified) { bool changed = false; - if (mStatusCount[0]!=size) + if (mStatusCount[0] != size) { mStatusCount[0] = size; changed = true; } - if (mStatusCount[1]!=deleted) + if (mStatusCount[1] != deleted) { mStatusCount[1] = deleted; changed = true; } - if (mStatusCount[2]!=modified) + if (mStatusCount[2] != modified) { mStatusCount[2] = modified; changed = true; @@ -229,7 +234,7 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi } } -void CSVWorld::TableBottomBox::positionChanged (int row, int column) +void CSVWorld::TableBottomBox::positionChanged(int row, int column) { mRow = row; mColumn = column; @@ -247,20 +252,33 @@ void CSVWorld::TableBottomBox::createRequest() { mCreator->reset(); mCreator->toggleWidgets(true); - mLayout->setCurrentWidget (mCreator); - setVisible (true); + mLayout->setCurrentWidget(mCreator); + setVisible(true); mEditMode = EditMode_Creation; mCreator->focus(); } -void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, - const CSMWorld::UniversalId::Type type) +void CSVWorld::TableBottomBox::createRecordsDirectlyRequest(const std::string& id) +{ + if (InfoCreator* creator = dynamic_cast(mCreator)) + { + creator->reset(); + creator->setText(id); + creator->callReturnPressed(); + } + else + { + Log(Debug::Warning) << "Creating a record directly failed."; + } +} + +void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type) { mCreator->reset(); mCreator->cloneMode(id, type); mLayout->setCurrentWidget(mCreator); mCreator->toggleWidgets(false); - setVisible (true); + setVisible(true); mEditMode = EditMode_Creation; mCreator->focus(); } @@ -270,12 +288,12 @@ void CSVWorld::TableBottomBox::touchRequest(const std::vectortouch(ids); } -void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector &selectedIds) +void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector& selectedIds) { extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete, selectedIds); } -void CSVWorld::TableBottomBox::extendedRevertConfigRequest(const std::vector &selectedIds) +void CSVWorld::TableBottomBox::extendedRevertConfigRequest(const std::vector& selectedIds) { extendedConfigRequest(ExtendedCommandConfigurator::Mode_Revert, selectedIds); } diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 6ad2dbd82..7be57066f 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -1,7 +1,11 @@ #ifndef CSV_WORLD_BOTTOMBOX_H #define CSV_WORLD_BOTTOMBOX_H +#include +#include + #include + #include #include "extendedcommandconfigurator.hpp" @@ -22,93 +26,93 @@ namespace CSVWorld class TableBottomBox : public QWidget { - Q_OBJECT + Q_OBJECT - enum EditMode { EditMode_None, EditMode_Creation, EditMode_ExtendedConfig }; + enum EditMode + { + EditMode_None, + EditMode_Creation, + EditMode_ExtendedConfig + }; - bool mShowStatusBar; - QLabel *mStatus; - QStatusBar *mStatusBar; - int mStatusCount[4]; + bool mShowStatusBar; + QLabel* mStatus; + QStatusBar* mStatusBar; + int mStatusCount[4]; - EditMode mEditMode; - Creator *mCreator; - ExtendedCommandConfigurator *mExtendedConfigurator; + EditMode mEditMode; + Creator* mCreator; + ExtendedCommandConfigurator* mExtendedConfigurator; - QStackedLayout *mLayout; - bool mHasPosition; - int mRow; - int mColumn; - QString mStatusMessage; + QStackedLayout* mLayout; + bool mHasPosition; + int mRow; + int mColumn; + QString mStatusMessage; - private: + private: + // not implemented + TableBottomBox(const TableBottomBox&); + TableBottomBox& operator=(const TableBottomBox&); - // not implemented - TableBottomBox (const TableBottomBox&); - TableBottomBox& operator= (const TableBottomBox&); + void updateSize(); - void updateSize(); + void updateStatus(); - void updateStatus(); + void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode, const std::vector& selectedIds); - void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode, - const std::vector &selectedIds); + public: + TableBottomBox(const CreatorFactoryBase& creatorFactory, CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QWidget* parent = nullptr); - public: + ~TableBottomBox() override; - TableBottomBox (const CreatorFactoryBase& creatorFactory, - CSMDoc::Document& document, - const CSMWorld::UniversalId& id, - QWidget *parent = nullptr); + bool eventFilter(QObject* object, QEvent* event) override; - virtual ~TableBottomBox(); + void setEditLock(bool locked); - bool eventFilter(QObject *object, QEvent *event) override; + void setStatusBar(bool show); - void setEditLock (bool locked); + bool canCreateAndDelete() const; + ///< Is record creation and deletion supported? + /// + /// \note The BotomBox does not partake in the deletion of records. - void setStatusBar (bool show); + void setStatusMessage(const QString& message); - bool canCreateAndDelete() const; - ///< Is record creation and deletion supported? - /// - /// \note The BotomBox does not partake in the deletion of records. + signals: - void setStatusMessage (const QString& message); + void requestFocus(const std::string& id); + ///< Request owner of this box to focus the just created \a id. The owner may + /// ignore this request. - signals: + private slots: - void requestFocus (const std::string& id); - ///< Request owner of this box to focus the just created \a id. The owner may - /// ignore this request. + void requestDone(); + ///< \note This slot being called does not imply success. - private slots: + void currentWidgetChanged(int index); - void requestDone(); - ///< \note This slot being called does not imply success. + public slots: - void currentWidgetChanged(int index); + void selectionSizeChanged(int size); - public slots: + void tableSizeChanged(int size, int deleted, int modified); + ///< \param size Number of not deleted records + /// \param deleted Number of deleted records + /// \param modified Number of added and modified records - void selectionSizeChanged (int size); + void positionChanged(int row, int column); - void tableSizeChanged (int size, int deleted, int modified); - ///< \param size Number of not deleted records - /// \param deleted Number of deleted records - /// \param modified Number of added and modified records + void noMorePosition(); - void positionChanged (int row, int column); + void createRequest(); + void createRecordsDirectlyRequest(const std::string& id); + void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); + void touchRequest(const std::vector&); - void noMorePosition(); - - void createRequest(); - void cloneRequest(const std::string& id, - const CSMWorld::UniversalId::Type type); - void touchRequest(const std::vector&); - - void extendedDeleteConfigRequest(const std::vector &selectedIds); - void extendedRevertConfigRequest(const std::vector &selectedIds); + void extendedDeleteConfigRequest(const std::vector& selectedIds); + void extendedRevertConfigRequest(const std::vector& selectedIds); }; } diff --git a/apps/opencs/view/world/tableeditidaction.cpp b/apps/opencs/view/world/tableeditidaction.cpp index 4dfc537cc..27064a93c 100644 --- a/apps/opencs/view/world/tableeditidaction.cpp +++ b/apps/opencs/view/world/tableeditidaction.cpp @@ -2,8 +2,14 @@ #include +#include +#include + #include "../../model/world/tablemimedata.hpp" +#include +#include + CSVWorld::TableEditIdAction::CellData CSVWorld::TableEditIdAction::getCellData(int row, int column) const { QModelIndex index = mTable.model()->index(row, column); @@ -16,11 +22,12 @@ CSVWorld::TableEditIdAction::CellData CSVWorld::TableEditIdAction::getCellData(i return std::make_pair(CSMWorld::ColumnBase::Display_None, ""); } -CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView &table, QWidget *parent) - : QAction(parent), - mTable(table), - mCurrentId(CSMWorld::UniversalId::Type_None) -{} +CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView& table, QWidget* parent) + : QAction(parent) + , mTable(table) + , mCurrentId(CSMWorld::UniversalId::Type_None) +{ +} void CSVWorld::TableEditIdAction::setCell(int row, int column) { @@ -43,7 +50,6 @@ bool CSVWorld::TableEditIdAction::isValidIdCell(int row, int column) const { CellData data = getCellData(row, column); CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); - return CSMWorld::ColumnBase::isId(data.first) && - idType != CSMWorld::UniversalId::Type_None && - !data.second.isEmpty(); + return CSMWorld::ColumnBase::isId(data.first) && idType != CSMWorld::UniversalId::Type_None + && !data.second.isEmpty(); } diff --git a/apps/opencs/view/world/tableeditidaction.hpp b/apps/opencs/view/world/tableeditidaction.hpp index 9fe41b0de..fb88516bb 100644 --- a/apps/opencs/view/world/tableeditidaction.hpp +++ b/apps/opencs/view/world/tableeditidaction.hpp @@ -2,6 +2,9 @@ #define CSVWORLD_TABLEEDITIDACTION_HPP #include +#include + +#include #include "../../model/world/columnbase.hpp" #include "../../model/world/universalid.hpp" @@ -12,19 +15,19 @@ namespace CSVWorld { class TableEditIdAction : public QAction { - const QTableView &mTable; - CSMWorld::UniversalId mCurrentId; + const QTableView& mTable; + CSMWorld::UniversalId mCurrentId; - typedef std::pair CellData; - CellData getCellData(int row, int column) const; + typedef std::pair CellData; + CellData getCellData(int row, int column) const; - public: - TableEditIdAction(const QTableView &table, QWidget *parent = nullptr); + public: + TableEditIdAction(const QTableView& table, QWidget* parent = nullptr); - void setCell(int row, int column); + void setCell(int row, int column); - CSMWorld::UniversalId getCurrentId() const; - bool isValidIdCell(int row, int column) const; + CSMWorld::UniversalId getCurrentId() const; + bool isValidIdCell(int row, int column) const; }; } diff --git a/apps/opencs/view/world/tableheadermouseeventhandler.cpp b/apps/opencs/view/world/tableheadermouseeventhandler.cpp new file mode 100644 index 000000000..dcd2e659a --- /dev/null +++ b/apps/opencs/view/world/tableheadermouseeventhandler.cpp @@ -0,0 +1,69 @@ +#include "tableheadermouseeventhandler.hpp" +#include "dragrecordtable.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace CSVWorld +{ + + TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable* parent) + : QWidget(parent) + , table(*parent) + , header(*table.horizontalHeader()) + { + header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); + connect(&header, &QHeaderView::customContextMenuRequested, + [this](const QPoint& position) { showContextMenu(position); }); + + header.viewport()->installEventFilter(this); + } + + bool TableHeaderMouseEventHandler::eventFilter(QObject* tableWatched, QEvent* event) + { + if (event->type() == QEvent::Type::MouseButtonPress) + { + auto& clickEvent = static_cast(*event); + if ((clickEvent.button() == Qt::MiddleButton)) + { + const auto& index = table.indexAt(clickEvent.pos()); + table.setColumnHidden(index.column(), true); + clickEvent.accept(); + return true; + } + } + return false; + } + + void TableHeaderMouseEventHandler::showContextMenu(const QPoint& position) + { + auto& menu{ createContextMenu() }; + menu.popup(header.viewport()->mapToGlobal(position)); + } + + QMenu& TableHeaderMouseEventHandler::createContextMenu() + { + auto* menu = new QMenu(this); + for (int i = 0; i < table.model()->columnCount(); ++i) + { + const auto& name = table.model()->headerData(i, Qt::Horizontal, Qt::DisplayRole); + QAction* action{ new QAction(name.toString(), this) }; + action->setCheckable(true); + action->setChecked(!table.isColumnHidden(i)); + menu->addAction(action); + + connect(action, &QAction::triggered, [this, action, i]() { + table.setColumnHidden(i, !action->isChecked()); + action->setChecked(!action->isChecked()); + action->toggle(); + }); + } + return *menu; + } + +} // namespace CSVWorld diff --git a/apps/opencs/view/world/tableheadermouseeventhandler.hpp b/apps/opencs/view/world/tableheadermouseeventhandler.hpp new file mode 100644 index 000000000..bef0b3389 --- /dev/null +++ b/apps/opencs/view/world/tableheadermouseeventhandler.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +class QEvent; +class QHeaderView; +class QMenu; +class QObject; +class QPoint; + +namespace CSVWorld +{ + class DragRecordTable; + + class TableHeaderMouseEventHandler : public QWidget + { + public: + explicit TableHeaderMouseEventHandler(DragRecordTable* parent); + + void showContextMenu(const QPoint&); + + private: + DragRecordTable& table; + QHeaderView& header; + + QMenu& createContextMenu(); + bool eventFilter(QObject*, QEvent*) override; + + }; // class TableHeaderMouseEventHandler +} // namespace CSVWorld diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 5413f87a6..c9e09e2d6 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,57 +1,95 @@ #include "tablesubview.hpp" -#include -#include -#include #include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" #include "../doc/sizehint.hpp" #include "../filter/filterbox.hpp" +#include "../filter/filterdata.hpp" #include "table.hpp" #include "tablebottombox.hpp" -#include "creator.hpp" -CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting) -: SubView (id) +CSVWorld::TableSubView::TableSubView( + const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) + : SubView(id) + , mShowOptions(false) + , mOptions(0) { - QVBoxLayout *layout = new QVBoxLayout; + QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget (mBottom = - new TableBottomBox (creatorFactory, document, id, this), 0); + layout->addWidget(mBottom = new TableBottomBox(creatorFactory, document, id, this), 0); - layout->insertWidget (0, mTable = - new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2); + layout->insertWidget(0, mTable = new Table(id, mBottom->canCreateAndDelete(), sorting, document), 2); - mFilterBox = new CSVFilter::FilterBox (document.getData(), this); + mFilterBox = new CSVFilter::FilterBox(document.getData(), this); - layout->insertWidget (0, mFilterBox); + QHBoxLayout* hLayout = new QHBoxLayout; + hLayout->insertWidget(0, mFilterBox); - CSVDoc::SizeHintWidget *widget = new CSVDoc::SizeHintWidget; + mOptions = new QWidget; - widget->setLayout (layout); + QHBoxLayout* optHLayout = new QHBoxLayout; + QCheckBox* autoJump = new QCheckBox("Auto Jump"); + autoJump->setToolTip( + "Whether to jump to the modified record." + "\nCan be useful in finding the moved or modified" + "\nobject instance while 3D editing."); + autoJump->setCheckState(Qt::Unchecked); + connect(autoJump, &QCheckBox::stateChanged, mTable, &Table::jumpAfterModChanged); + optHLayout->insertWidget(0, autoJump); + optHLayout->setContentsMargins(QMargins(0, 3, 0, 0)); + mOptions->setLayout(optHLayout); + mOptions->resize(mOptions->width(), mFilterBox->height()); + mOptions->hide(); - setWidget (widget); + QPushButton* opt = new QPushButton(); + opt->setIcon(QIcon(":startup/configure")); + opt->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + opt->setToolTip("Open additional options for this subview."); + connect(opt, &QPushButton::clicked, this, &TableSubView::toggleOptions); + + QVBoxLayout* buttonLayout = new QVBoxLayout; // work around margin issues + buttonLayout->setContentsMargins(QMargins(0 /*left*/, 3 /*top*/, 3 /*right*/, 0 /*bottom*/)); + buttonLayout->insertWidget(0, opt, 0, Qt::AlignVCenter | Qt::AlignRight); + hLayout->insertWidget(1, mOptions); + hLayout->insertLayout(2, buttonLayout); + + layout->insertLayout(0, hLayout); + + CSVDoc::SizeHintWidget* widget = new CSVDoc::SizeHintWidget; + + widget->setLayout(layout); + + setWidget(widget); // prefer height of the screen and full width of the table - const QRect rect = QApplication::desktop()->screenGeometry(this); + const QRect rect = QApplication::screenAt(pos())->geometry(); int frameHeight = 40; // set a reasonable default - QWidget *topLevel = QApplication::topLevelAt(pos()); + QWidget* topLevel = QApplication::topLevelAt(pos()); if (topLevel) frameHeight = topLevel->frameGeometry().height() - topLevel->height(); - widget->setSizeHint(QSize(mTable->horizontalHeader()->length(), rect.height()-frameHeight)); + widget->setSizeHint(QSize(mTable->horizontalHeader()->length(), rect.height() - frameHeight)); - connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), - this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&))); + connect(mTable, &Table::editRequest, this, &TableSubView::editRequest); - connect (mTable, SIGNAL (selectionSizeChanged (int)), - mBottom, SLOT (selectionSizeChanged (int))); - connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), - mBottom, SLOT (tableSizeChanged (int, int, int))); + connect(mTable, &Table::selectionSizeChanged, mBottom, &TableBottomBox::selectionSizeChanged); + connect(mTable, &Table::tableSizeChanged, mBottom, &TableBottomBox::tableSizeChanged); mTable->tableSizeUpdate(); mTable->selectionSizeUpdate(); @@ -61,58 +99,53 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D if (mBottom->canCreateAndDelete()) { - connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); + connect(mTable, &Table::createRequest, mBottom, &TableBottomBox::createRequest); - connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this, - SLOT(cloneRequest(const CSMWorld::UniversalId&))); + connect( + mTable, &Table::cloneRequest, this, qOverload(&TableSubView::cloneRequest)); - connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), - mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + connect(this, qOverload(&TableSubView::cloneRequest), + mBottom, &TableBottomBox::cloneRequest); - connect (mTable, SIGNAL(touchRequest(const std::vector&)), - mBottom, SLOT(touchRequest(const std::vector&))); + connect(mTable, &Table::createRecordsDirectlyRequest, mBottom, &TableBottomBox::createRecordsDirectlyRequest); - connect (mTable, SIGNAL(extendedDeleteConfigRequest(const std::vector &)), - mBottom, SLOT(extendedDeleteConfigRequest(const std::vector &))); - connect (mTable, SIGNAL(extendedRevertConfigRequest(const std::vector &)), - mBottom, SLOT(extendedRevertConfigRequest(const std::vector &))); + connect(mTable, &Table::touchRequest, mBottom, &TableBottomBox::touchRequest); + + connect(mTable, &Table::extendedDeleteConfigRequest, mBottom, &TableBottomBox::extendedDeleteConfigRequest); + connect(mTable, &Table::extendedRevertConfigRequest, mBottom, &TableBottomBox::extendedRevertConfigRequest); } - connect (mBottom, SIGNAL (requestFocus (const std::string&)), - mTable, SLOT (requestFocus (const std::string&))); + connect(mBottom, &TableBottomBox::requestFocus, mTable, &Table::requestFocus); - connect (mFilterBox, - SIGNAL (recordFilterChanged (std::shared_ptr)), - mTable, SLOT (recordFilterChanged (std::shared_ptr))); + connect(mFilterBox, &CSVFilter::FilterBox::recordFilterChanged, mTable, &Table::recordFilterChanged); - connect(mFilterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), - this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); + connect(mFilterBox, &CSVFilter::FilterBox::recordDropped, this, &TableSubView::createFilterRequest); - connect (mTable, SIGNAL (closeRequest()), this, SLOT (closeRequest())); + connect(mTable, &Table::closeRequest, this, qOverload<>(&TableSubView::closeRequest)); } -void CSVWorld::TableSubView::setEditLock (bool locked) +void CSVWorld::TableSubView::setEditLock(bool locked) { - mTable->setEditLock (locked); - mBottom->setEditLock (locked); + mTable->setEditLock(locked); + mBottom->setEditLock(locked); } -void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const std::string& hint) +void CSVWorld::TableSubView::editRequest(const CSMWorld::UniversalId& id, const std::string& hint) { - focusId (id, hint); + focusId(id, hint); } -void CSVWorld::TableSubView::setStatusBar (bool show) +void CSVWorld::TableSubView::setStatusBar(bool show) { - mBottom->setStatusBar (show); + mBottom->setStatusBar(show); } -void CSVWorld::TableSubView::useHint (const std::string& hint) +void CSVWorld::TableSubView::useHint(const std::string& hint) { if (hint.empty()) return; - if (hint[0]=='f' && hint.size()>=2) - mFilterBox->setRecordFilter (hint.substr (2)); + if (hint[0] == 'f' && hint.size() >= 2) + mFilterBox->setRecordFilter(hint.substr(2)); } void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) @@ -120,38 +153,64 @@ void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) emit cloneRequest(toClone.getId(), toClone.getType()); } -void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::UniversalId>& types, Qt::DropAction action) +void CSVWorld::TableSubView::createFilterRequest(std::vector& types, + const std::pair& columnSearchData, Qt::DropAction action) { - std::vector > > filterSource; - - std::vector refIdColumns = mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(CSMWorld::UniversalId::Type_Referenceable)); + std::vector sourceFilter; + std::vector refIdColumns = mTable->getColumnsWithDisplay( + CSMWorld::TableMimeData::convertEnums(CSMWorld::UniversalId::Type_Referenceable)); bool hasRefIdDisplay = !refIdColumns.empty(); for (std::vector::iterator it(types.begin()); it != types.end(); ++it) { CSMWorld::UniversalId::Type type = it->getType(); std::vector col = mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(type)); - if(!col.empty()) + if (!col.empty()) { - filterSource.emplace_back(it->getId(), col); + CSVFilter::FilterData filterData; + filterData.searchData = it->getId(); + filterData.columns = col; + sourceFilter.emplace_back(filterData); } - if(hasRefIdDisplay && CSMWorld::TableMimeData::isReferencable(type)) + if (hasRefIdDisplay && CSMWorld::TableMimeData::isReferencable(type)) { - filterSource.emplace_back(it->getId(), refIdColumns); + CSVFilter::FilterData filterData; + filterData.searchData = it->getId(); + filterData.columns = refIdColumns; + sourceFilter.emplace_back(filterData); } } - mFilterBox->createFilterRequest(filterSource, action); + if (!sourceFilter.empty()) + mFilterBox->createFilterRequest(sourceFilter, action); + else + { + std::vector sourceFilterByValue; + + QVariant qData = columnSearchData.first; + std::string searchColumn = columnSearchData.second; + std::vector searchColumns; + searchColumns.emplace_back(searchColumn); + + CSVFilter::FilterData filterData; + filterData.searchData = qData; + filterData.columns = searchColumns; + + sourceFilterByValue.emplace_back(filterData); + + mFilterBox->createFilterRequest(sourceFilterByValue, action); + } } -bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) +bool CSVWorld::TableSubView::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::Drop) { if (QDropEvent* drop = dynamic_cast(event)) { - const CSMWorld::TableMimeData* tableMimeData = dynamic_cast(drop->mimeData()); + const CSMWorld::TableMimeData* tableMimeData + = dynamic_cast(drop->mimeData()); if (!tableMimeData) // May happen when non-records (e.g. plain text) are dragged and dropped return false; @@ -166,7 +225,21 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) return false; } -void CSVWorld::TableSubView::requestFocus (const std::string& id) +void CSVWorld::TableSubView::toggleOptions() +{ + if (mShowOptions) + { + mShowOptions = false; + mOptions->hide(); + } + else + { + mShowOptions = true; + mOptions->show(); + } +} + +void CSVWorld::TableSubView::requestFocus(const std::string& id) { mTable->requestFocus(id); } diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 337d2c762..967002a93 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -3,14 +3,18 @@ #include "../doc/subview.hpp" -#include +#include +#include +#include -class QModelIndex; +#include +#include -namespace CSMWorld -{ - class IdTable; -} +#include + +class QEvent; +class QObject; +class QWidget; namespace CSMDoc { @@ -30,40 +34,41 @@ namespace CSVWorld class TableSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - Table *mTable; - TableBottomBox *mBottom; - CSVFilter::FilterBox *mFilterBox; + Table* mTable; + TableBottomBox* mBottom; + CSVFilter::FilterBox* mFilterBox; + bool mShowOptions; + QWidget* mOptions; - public: + public: + TableSubView(const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting); - TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting); + void setEditLock(bool locked) override; - void setEditLock (bool locked) override; + void setStatusBar(bool show) override; - void setStatusBar (bool show) override; + void useHint(const std::string& hint) override; - void useHint (const std::string& hint) override; + protected: + bool eventFilter(QObject* object, QEvent* event) override; - protected: - bool eventFilter(QObject* object, QEvent *event) override; + signals: + void cloneRequest(const std::string&, const CSMWorld::UniversalId::Type); - signals: - void cloneRequest(const std::string&, - const CSMWorld::UniversalId::Type); + private slots: - private slots: + void editRequest(const CSMWorld::UniversalId& id, const std::string& hint); + void cloneRequest(const CSMWorld::UniversalId& toClone); + void createFilterRequest(std::vector& types, + const std::pair& columnSearchData, Qt::DropAction action); + void toggleOptions(); - void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); - void cloneRequest (const CSMWorld::UniversalId& toClone); - void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, - Qt::DropAction action); + public slots: - public slots: - - void requestFocus (const std::string& id); + void requestFocus(const std::string& id); }; } diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 58d3d49e4..dfb587cd9 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -2,19 +2,20 @@ #include #include +#include +#include -#include -#include -#include -#include -#include #include -#include -#include #include +#include +#include +#include +#include + +#include +#include +#include -#include "../../model/world/commands.hpp" -#include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../widget/coloreditor.hpp" @@ -23,26 +24,27 @@ #include "dialoguespinbox.hpp" #include "scriptedit.hpp" -CSVWorld::NastyTableModelHack::NastyTableModelHack (QAbstractItemModel& model) -: mModel (model) -{} - -int CSVWorld::NastyTableModelHack::rowCount (const QModelIndex & parent) const +CSVWorld::NastyTableModelHack::NastyTableModelHack(QAbstractItemModel& model) + : mModel(model) { - return mModel.rowCount (parent); } -int CSVWorld::NastyTableModelHack::columnCount (const QModelIndex & parent) const +int CSVWorld::NastyTableModelHack::rowCount(const QModelIndex& parent) const { - return mModel.columnCount (parent); + return mModel.rowCount(parent); } -QVariant CSVWorld::NastyTableModelHack::data (const QModelIndex & index, int role) const +int CSVWorld::NastyTableModelHack::columnCount(const QModelIndex& parent) const { - return mModel.data (index, role); + return mModel.columnCount(parent); } -bool CSVWorld::NastyTableModelHack::setData ( const QModelIndex &index, const QVariant &value, int role) +QVariant CSVWorld::NastyTableModelHack::data(const QModelIndex& index, int role) const +{ + return mModel.data(index, role); +} + +bool CSVWorld::NastyTableModelHack::setData(const QModelIndex& index, const QVariant& value, int role) { mData = value; return true; @@ -53,16 +55,12 @@ QVariant CSVWorld::NastyTableModelHack::getData() const return mData; } - -CSVWorld::CommandDelegateFactory::~CommandDelegateFactory() {} - - -CSVWorld::CommandDelegateFactoryCollection *CSVWorld::CommandDelegateFactoryCollection::sThis = nullptr; +CSVWorld::CommandDelegateFactoryCollection* CSVWorld::CommandDelegateFactoryCollection::sThis = nullptr; CSVWorld::CommandDelegateFactoryCollection::CommandDelegateFactoryCollection() { if (sThis) - throw std::logic_error ("multiple instances of CSVWorld::CommandDelegateFactoryCollection"); + throw std::logic_error("multiple instances of CSVWorld::CommandDelegateFactoryCollection"); sThis = this; } @@ -71,39 +69,37 @@ CSVWorld::CommandDelegateFactoryCollection::~CommandDelegateFactoryCollection() { sThis = nullptr; - for (std::map::iterator iter ( - mFactories.begin()); - iter!=mFactories.end(); ++iter) - delete iter->second; + for (std::map::iterator iter(mFactories.begin()); + iter != mFactories.end(); ++iter) + delete iter->second; } -void CSVWorld::CommandDelegateFactoryCollection::add (CSMWorld::ColumnBase::Display display, - CommandDelegateFactory *factory) +void CSVWorld::CommandDelegateFactoryCollection::add( + CSMWorld::ColumnBase::Display display, CommandDelegateFactory* factory) { - mFactories.insert (std::make_pair (display, factory)); + mFactories.insert(std::make_pair(display, factory)); } -CSVWorld::CommandDelegate *CSVWorld::CommandDelegateFactoryCollection::makeDelegate ( - CSMWorld::ColumnBase::Display display, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const +CSVWorld::CommandDelegate* CSVWorld::CommandDelegateFactoryCollection::makeDelegate( + CSMWorld::ColumnBase::Display display, CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, + QObject* parent) const { - std::map::const_iterator iter = - mFactories.find (display); + std::map::const_iterator iter = mFactories.find(display); - if (iter!=mFactories.end()) - return iter->second->makeDelegate (dispatcher, document, parent); + if (iter != mFactories.end()) + return iter->second->makeDelegate(dispatcher, document, parent); - return new CommandDelegate (dispatcher, document, parent); + return new CommandDelegate(dispatcher, document, parent); } const CSVWorld::CommandDelegateFactoryCollection& CSVWorld::CommandDelegateFactoryCollection::get() { if (!sThis) - throw std::logic_error ("no instance of CSVWorld::CommandDelegateFactoryCollection"); + throw std::logic_error("no instance of CSVWorld::CommandDelegateFactoryCollection"); return *sThis; } - QUndoStack& CSVWorld::CommandDelegate::getUndoStack() const { return mDocument.getUndoStack(); @@ -114,14 +110,14 @@ CSMDoc::Document& CSVWorld::CommandDelegate::getDocument() const return mDocument; } -CSMWorld::ColumnBase::Display CSVWorld::CommandDelegate::getDisplayTypeFromIndex(const QModelIndex &index) const +CSMWorld::ColumnBase::Display CSVWorld::CommandDelegate::getDisplayTypeFromIndex(const QModelIndex& index) const { int rawDisplay = index.data(CSMWorld::ColumnBase::Role_Display).toInt(); return static_cast(rawDisplay); } -void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, - const QModelIndex& index) const +void CSVWorld::CommandDelegate::setModelDataImp( + QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { if (!mCommandDispatcher) return; @@ -129,41 +125,43 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM QVariant variant; // Color columns use a custom editor, so we need to fetch selected color from it. - CSVWidget::ColorEditor *colorEditor = qobject_cast(editor); + CSVWidget::ColorEditor* colorEditor = qobject_cast(editor); if (colorEditor != nullptr) { variant = colorEditor->colorInt(); } else { - NastyTableModelHack hack (*model); - QStyledItemDelegate::setModelData (editor, &hack, index); + NastyTableModelHack hack(*model); + QStyledItemDelegate::setModelData(editor, &hack, index); variant = hack.getData(); } - if ((model->data (index)!=variant) && (model->flags(index) & Qt::ItemIsEditable)) - mCommandDispatcher->executeModify (model, index, variant); + if ((model->data(index) != variant) && (model->flags(index) & Qt::ItemIsEditable)) + mCommandDispatcher->executeModify(model, index, variant); } -CSVWorld::CommandDelegate::CommandDelegate (CSMWorld::CommandDispatcher *commandDispatcher, - CSMDoc::Document& document, QObject *parent) -: QStyledItemDelegate (parent), mEditLock (false), - mCommandDispatcher (commandDispatcher), mDocument (document) -{} +CSVWorld::CommandDelegate::CommandDelegate( + CSMWorld::CommandDispatcher* commandDispatcher, CSMDoc::Document& document, QObject* parent) + : QStyledItemDelegate(parent) + , mEditLock(false) + , mCommandDispatcher(commandDispatcher) + , mDocument(document) +{ +} -void CSVWorld::CommandDelegate::setModelData (QWidget *editor, QAbstractItemModel *model, - const QModelIndex& index) const +void CSVWorld::CommandDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { if (!mEditLock) { - setModelDataImp (editor, model, index); + setModelDataImp(editor, model, index); } ///< \todo provide some kind of feedback to the user, indicating that editing is currently not possible. } -QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, - const QModelIndex& index) const +QWidget* CSVWorld::CommandDelegate::createEditor( + QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index); @@ -181,10 +179,10 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO { return new CSVWidget::ColorEditor(index.data().toInt(), parent, true); } - return createEditor (parent, option, index, display); + return createEditor(parent, option, index, display); } -QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, +QWidget* CSVWorld::CommandDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { QVariant variant = index.data(); @@ -207,34 +205,34 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO } case CSMWorld::ColumnBase::Display_Integer: { - DialogueSpinBox *sb = new DialogueSpinBox(parent); + DialogueSpinBox* sb = new DialogueSpinBox(parent); sb->setRange(std::numeric_limits::min(), std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_SignedInteger8: { - DialogueSpinBox *sb = new DialogueSpinBox(parent); + DialogueSpinBox* sb = new DialogueSpinBox(parent); sb->setRange(std::numeric_limits::min(), std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_SignedInteger16: { - DialogueSpinBox *sb = new DialogueSpinBox(parent); + DialogueSpinBox* sb = new DialogueSpinBox(parent); sb->setRange(std::numeric_limits::min(), std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_UnsignedInteger8: { - DialogueSpinBox *sb = new DialogueSpinBox(parent); + DialogueSpinBox* sb = new DialogueSpinBox(parent); sb->setRange(0, std::numeric_limits::max()); return sb; } case CSMWorld::ColumnBase::Display_UnsignedInteger16: { - DialogueSpinBox *sb = new DialogueSpinBox(parent); + DialogueSpinBox* sb = new DialogueSpinBox(parent); sb->setRange(0, std::numeric_limits::max()); return sb; } @@ -245,7 +243,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_Float: { - DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); + DialogueDoubleSpinBox* dsb = new DialogueDoubleSpinBox(parent); dsb->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); dsb->setSingleStep(0.01f); dsb->setDecimals(3); @@ -254,7 +252,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_Double: { - DialogueDoubleSpinBox *dsb = new DialogueDoubleSpinBox(parent); + DialogueDoubleSpinBox* dsb = new DialogueDoubleSpinBox(parent); dsb->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); dsb->setSingleStep(0.01f); dsb->setDecimals(6); @@ -265,8 +263,8 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_LongString: case CSMWorld::ColumnBase::Display_LongString256: { - QPlainTextEdit *edit = new QPlainTextEdit(parent); - edit->setUndoRedoEnabled (false); + QPlainTextEdit* edit = new QPlainTextEdit(parent); + edit->setUndoRedoEnabled(false); return edit; } @@ -276,28 +274,36 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_ScriptLines: - return new ScriptEdit (mDocument, ScriptHighlighter::Mode_Console, parent); + return new ScriptEdit(mDocument, ScriptHighlighter::Mode_Console, parent); case CSMWorld::ColumnBase::Display_String: - // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used return new CSVWidget::DropLineEdit(display, parent); case CSMWorld::ColumnBase::Display_String32: { - // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used - CSVWidget::DropLineEdit *widget = new CSVWidget::DropLineEdit(display, parent); - widget->setMaxLength (32); + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + CSVWidget::DropLineEdit* widget = new CSVWidget::DropLineEdit(display, parent); + widget->setMaxLength(32); + return widget; + } + + case CSMWorld::ColumnBase::Display_String64: + { + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + CSVWidget::DropLineEdit* widget = new CSVWidget::DropLineEdit(display, parent); + widget->setMaxLength(64); return widget; } default: - return QStyledItemDelegate::createEditor (parent, option, index); + return QStyledItemDelegate::createEditor(parent, option, index); } } -void CSVWorld::CommandDelegate::setEditLock (bool locked) +void CSVWorld::CommandDelegate::setEditLock(bool locked) { mEditLock = locked; } @@ -307,12 +313,12 @@ bool CSVWorld::CommandDelegate::isEditLocked() const return mEditLock; } -void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelIndex& index) const +void CSVWorld::CommandDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { - setEditorData (editor, index, false); + setEditorData(editor, index, false); } -void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const +void CSVWorld::CommandDelegate::setEditorData(QWidget* editor, const QModelIndex& index, bool tryDisplay) const { QVariant variant = index.data(Qt::EditRole); if (tryDisplay) @@ -326,7 +332,7 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde } } QPlainTextEdit* plainTextEdit = qobject_cast(editor); - if(plainTextEdit) //for some reason it is easier to brake the loop here + if (plainTextEdit) // for some reason it is easier to brake the loop here { if (plainTextEdit->toPlainText() == variant.toString()) { @@ -336,7 +342,7 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde } // Color columns use a custom editor, so we need explicitly set a data for it - CSVWidget::ColorEditor *colorEditor = qobject_cast(editor); + CSVWidget::ColorEditor* colorEditor = qobject_cast(editor); if (colorEditor != nullptr) { colorEditor->setColor(variant.toInt()); @@ -356,10 +362,13 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde if (!n.isEmpty()) { if (!variant.isValid()) - variant = QVariant(editor->property(n).userType(), (const void *)nullptr); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + variant = QVariant(editor->property(n).metaType(), (const void*)nullptr); +#else + variant = QVariant(editor->property(n).userType(), (const void*)nullptr); +#endif editor->setProperty(n, variant); } - } -void CSVWorld::CommandDelegate::settingChanged (const CSMPrefs::Setting *setting) {} +void CSVWorld::CommandDelegate::settingChanged(const CSMPrefs::Setting* setting) {} diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index 2c4537dac..243608e2e 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -6,18 +6,20 @@ #include #include - #ifndef Q_MOC_RUN #include "../../model/world/columnbase.hpp" -#include "../../model/doc/document.hpp" #endif class QUndoStack; +class QWidget; + +namespace CSMDoc +{ + class Document; +} namespace CSMWorld { - class TableMimeData; - class UniversalId; class CommandDispatcher; } @@ -33,122 +35,108 @@ namespace CSVWorld /// Really, Qt? Really? class NastyTableModelHack : public QAbstractTableModel { - QAbstractItemModel& mModel; - QVariant mData; + QAbstractItemModel& mModel; + QVariant mData; - public: + public: + NastyTableModelHack(QAbstractItemModel& model); - NastyTableModelHack (QAbstractItemModel& model); + int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount (const QModelIndex & parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount (const QModelIndex & parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; - bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - - QVariant getData() const; + QVariant getData() const; }; class CommandDelegate; class CommandDelegateFactory { - public: + public: + virtual ~CommandDelegateFactory() = default; - virtual ~CommandDelegateFactory(); - - virtual CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, QObject *parent) - const = 0; - ///< The ownership of the returned CommandDelegate is transferred to the caller. + virtual CommandDelegate* makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const = 0; + ///< The ownership of the returned CommandDelegate is transferred to the caller. }; class CommandDelegateFactoryCollection { - static CommandDelegateFactoryCollection *sThis; - std::map mFactories; + static CommandDelegateFactoryCollection* sThis; + std::map mFactories; - private: + private: + // not implemented + CommandDelegateFactoryCollection(const CommandDelegateFactoryCollection&); + CommandDelegateFactoryCollection& operator=(const CommandDelegateFactoryCollection&); - // not implemented - CommandDelegateFactoryCollection (const CommandDelegateFactoryCollection&); - CommandDelegateFactoryCollection& operator= (const CommandDelegateFactoryCollection&); + public: + CommandDelegateFactoryCollection(); - public: + ~CommandDelegateFactoryCollection(); - CommandDelegateFactoryCollection(); + void add(CSMWorld::ColumnBase::Display display, CommandDelegateFactory* factory); + ///< The ownership of \a factory is transferred to *this. + /// + /// This function must not be called more than once per value of \a display. - ~CommandDelegateFactoryCollection(); - - void add (CSMWorld::ColumnBase::Display display, CommandDelegateFactory *factory); - ///< The ownership of \a factory is transferred to *this. - /// - /// This function must not be called more than once per value of \a display. - - CommandDelegate *makeDelegate (CSMWorld::ColumnBase::Display display, - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, - QObject *parent) const; - ///< The ownership of the returned CommandDelegate is transferred to the caller. - /// - /// If no factory is registered for \a display, a CommandDelegate will be returned. - - static const CommandDelegateFactoryCollection& get(); + CommandDelegate* makeDelegate(CSMWorld::ColumnBase::Display display, CSMWorld::CommandDispatcher* dispatcher, + CSMDoc::Document& document, QObject* parent) const; + ///< The ownership of the returned CommandDelegate is transferred to the caller. + /// + /// If no factory is registered for \a display, a CommandDelegate will be returned. + static const CommandDelegateFactoryCollection& get(); }; ///< \brief Use commands instead of manipulating the model directly class CommandDelegate : public QStyledItemDelegate { - Q_OBJECT + Q_OBJECT - bool mEditLock; - CSMWorld::CommandDispatcher *mCommandDispatcher; - CSMDoc::Document& mDocument; + bool mEditLock; + CSMWorld::CommandDispatcher* mCommandDispatcher; + CSMDoc::Document& mDocument; - protected: + protected: + QUndoStack& getUndoStack() const; - QUndoStack& getUndoStack() const; + CSMDoc::Document& getDocument() const; - CSMDoc::Document& getDocument() const; + CSMWorld::ColumnBase::Display getDisplayTypeFromIndex(const QModelIndex& index) const; - CSMWorld::ColumnBase::Display getDisplayTypeFromIndex(const QModelIndex &index) const; + virtual void setModelDataImp(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; - virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model, - const QModelIndex& index) const; + public: + /// \param commandDispatcher If CommandDelegate will be only be used on read-only + /// cells, a 0-pointer can be passed here. + CommandDelegate(CSMWorld::CommandDispatcher* commandDispatcher, CSMDoc::Document& document, QObject* parent); - public: + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; - /// \param commandDispatcher If CommandDelegate will be only be used on read-only - /// cells, a 0-pointer can be passed here. - CommandDelegate (CSMWorld::CommandDispatcher *commandDispatcher, CSMDoc::Document& document, QObject *parent); + QWidget* createEditor( + QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - void setModelData (QWidget *editor, QAbstractItemModel *model, - const QModelIndex& index) const override; + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index, + CSMWorld::ColumnBase::Display display) const; - QWidget *createEditor (QWidget *parent, - const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + void setEditLock(bool locked); - virtual QWidget *createEditor (QWidget *parent, - const QStyleOptionViewItem& option, - const QModelIndex& index, - CSMWorld::ColumnBase::Display display) const; + bool isEditLocked() const; - void setEditLock (bool locked); + ///< \return Does column require update? - bool isEditLocked() const; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; - ///< \return Does column require update? + virtual void setEditorData(QWidget* editor, const QModelIndex& index, bool tryDisplay) const; - void setEditorData (QWidget *editor, const QModelIndex& index) const override; - - virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const; - - /// \attention This is not a slot. For ordering reasons this function needs to be - /// called manually from the parent object's settingChanged function. - virtual void settingChanged (const CSMPrefs::Setting *setting); + /// \attention This is not a slot. For ordering reasons this function needs to be + /// called manually from the parent object's settingChanged function. + virtual void settingChanged(const CSMPrefs::Setting* setting); }; } diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index 48fb4ab87..acc94004c 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -1,17 +1,31 @@ #include "vartypedelegate.hpp" -#include +#include +#include +#include +#include + +#include -#include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/commandmacro.hpp" +#include "../../model/world/commands.hpp" -void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) - const +namespace CSMDoc { - QModelIndex next = model->index (index.row(), index.column()+1); + class Document; +} - QVariant old = model->data (next); +namespace CSMWorld +{ + class CommandDispatcher; +} + +void CSVWorld::VarTypeDelegate::addCommands(QAbstractItemModel* model, const QModelIndex& index, int type) const +{ + QModelIndex next = model->index(index.row(), index.column() + 1); + + QVariant old = model->data(next); QVariant value; @@ -34,50 +48,51 @@ void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QM value = old.toString(); break; - default: break; // ignore the rest + default: + break; // ignore the rest } - CSMWorld::CommandMacro macro (getUndoStack(), "Modify " + model->headerData (index.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + CSMWorld::CommandMacro macro( + getUndoStack(), "Modify " + model->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString()); - macro.push (new CSMWorld::ModifyCommand (*model, index, type)); - macro.push (new CSMWorld::ModifyCommand (*model, next, value)); + macro.push(new CSMWorld::ModifyCommand(*model, index, type)); + macro.push(new CSMWorld::ModifyCommand(*model, next, value)); } -CSVWorld::VarTypeDelegate::VarTypeDelegate (const std::vector >& values, - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) -: EnumDelegate (values, dispatcher, document, parent) -{} - - -CSVWorld::VarTypeDelegateFactory::VarTypeDelegateFactory (ESM::VarType type0, - ESM::VarType type1, ESM::VarType type2, ESM::VarType type3) +CSVWorld::VarTypeDelegate::VarTypeDelegate(const std::vector>& values, + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) + : EnumDelegate(values, dispatcher, document, parent) { - if (type0!=ESM::VT_Unknown) - add (type0); - - if (type1!=ESM::VT_Unknown) - add (type1); - - if (type2!=ESM::VT_Unknown) - add (type2); - - if (type3!=ESM::VT_Unknown) - add (type3); } -CSVWorld::CommandDelegate *CSVWorld::VarTypeDelegateFactory::makeDelegate ( - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) const +CSVWorld::VarTypeDelegateFactory::VarTypeDelegateFactory( + ESM::VarType type0, ESM::VarType type1, ESM::VarType type2, ESM::VarType type3) { - return new VarTypeDelegate (mValues, dispatcher, document, parent); + if (type0 != ESM::VT_Unknown) + add(type0); + + if (type1 != ESM::VT_Unknown) + add(type1); + + if (type2 != ESM::VT_Unknown) + add(type2); + + if (type3 != ESM::VT_Unknown) + add(type3); } -void CSVWorld::VarTypeDelegateFactory::add (ESM::VarType type) +CSVWorld::CommandDelegate* CSVWorld::VarTypeDelegateFactory::makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const { - std::vector> enums = - CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_ValueType); + return new VarTypeDelegate(mValues, dispatcher, document, parent); +} + +void CSVWorld::VarTypeDelegateFactory::add(ESM::VarType type) +{ + std::vector> enums = CSMWorld::Columns::getEnums(CSMWorld::Columns::ColumnId_ValueType); if (static_cast(type) >= enums.size()) - throw std::logic_error ("Unsupported variable type"); + throw std::logic_error("Unsupported variable type"); - mValues.emplace_back(type, QString::fromUtf8 (enums[type].second.c_str())); + mValues.emplace_back(type, QString::fromUtf8(enums[type].second.c_str())); } diff --git a/apps/opencs/view/world/vartypedelegate.hpp b/apps/opencs/view/world/vartypedelegate.hpp index 44705e80e..4398d4e4b 100644 --- a/apps/opencs/view/world/vartypedelegate.hpp +++ b/apps/opencs/view/world/vartypedelegate.hpp @@ -1,40 +1,50 @@ #ifndef CSV_WORLD_VARTYPEDELEGATE_H #define CSV_WORLD_VARTYPEDELEGATE_H -#include +#include +#include + +#include + +#include #include "enumdelegate.hpp" +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; +} + namespace CSVWorld { class VarTypeDelegate : public EnumDelegate { - private: + private: + void addCommands(QAbstractItemModel* model, const QModelIndex& index, int type) const override; - void addCommands (QAbstractItemModel *model, - const QModelIndex& index, int type) const override; - - public: - - VarTypeDelegate (const std::vector >& values, - CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent); + public: + VarTypeDelegate(const std::vector>& values, CSMWorld::CommandDispatcher* dispatcher, + CSMDoc::Document& document, QObject* parent); }; class VarTypeDelegateFactory : public CommandDelegateFactory { - std::vector > mValues; + std::vector> mValues; - public: + public: + VarTypeDelegateFactory(ESM::VarType type0 = ESM::VT_Unknown, ESM::VarType type1 = ESM::VT_Unknown, + ESM::VarType type2 = ESM::VT_Unknown, ESM::VarType type3 = ESM::VT_Unknown); - VarTypeDelegateFactory (ESM::VarType type0 = ESM::VT_Unknown, - ESM::VarType type1 = ESM::VT_Unknown, ESM::VarType type2 = ESM::VT_Unknown, - ESM::VarType type3 = ESM::VT_Unknown); + CommandDelegate* makeDelegate( + CSMWorld::CommandDispatcher* dispatcher, CSMDoc::Document& document, QObject* parent) const override; + ///< The ownership of the returned CommandDelegate is transferred to the caller. - CommandDelegate *makeDelegate (CSMWorld::CommandDispatcher *dispatcher, - CSMDoc::Document& document, QObject *parent) const override; - ///< The ownership of the returned CommandDelegate is transferred to the caller. - - void add (ESM::VarType type); + void add(ESM::VarType type); }; } diff --git a/apps/opencs_tests/CMakeLists.txt b/apps/opencs_tests/CMakeLists.txt new file mode 100644 index 000000000..5d76dc203 --- /dev/null +++ b/apps/opencs_tests/CMakeLists.txt @@ -0,0 +1,33 @@ +file(GLOB OPENCS_TESTS_SRC_FILES + main.cpp + model/world/testinfocollection.cpp + model/world/testuniversalid.cpp +) + +source_group(apps\\openmw-cs-tests FILES ${OPENCS_TESTS_SRC_FILES}) + +openmw_add_executable(openmw-cs-tests ${OPENCS_TESTS_SRC_FILES}) + +target_include_directories(openmw-cs-tests SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS}) +target_include_directories(openmw-cs-tests SYSTEM PRIVATE ${GMOCK_INCLUDE_DIRS}) + +target_link_libraries(openmw-cs-tests PRIVATE + openmw-cs-lib + GTest::GTest + GMock::GMock +) + +if (UNIX AND NOT APPLE) + target_link_libraries(openmw-cs-tests PRIVATE ${CMAKE_THREAD_LIBS_INIT}) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + target_compile_options(openmw-cs-tests PRIVATE --coverage) + target_link_libraries(openmw-cs-tests PRIVATE gcov) +endif() + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw-cs-tests PRIVATE + + ) +endif() diff --git a/apps/opencs_tests/main.cpp b/apps/opencs_tests/main.cpp new file mode 100644 index 000000000..e1a8e6739 --- /dev/null +++ b/apps/opencs_tests/main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char* argv[]) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/apps/opencs_tests/model/world/testinfocollection.cpp b/apps/opencs_tests/model/world/testinfocollection.cpp new file mode 100644 index 000000000..7065fa80b --- /dev/null +++ b/apps/opencs_tests/model/world/testinfocollection.cpp @@ -0,0 +1,664 @@ +#include "apps/opencs/model/world/infocollection.hpp" + +#include "components/esm3/esmreader.hpp" +#include "components/esm3/esmwriter.hpp" +#include "components/esm3/formatversion.hpp" +#include "components/esm3/loaddial.hpp" +#include "components/esm3/loadinfo.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace CSMWorld +{ + inline std::ostream& operator<<(std::ostream& stream, const Record* value) + { + return stream << "&Record{.mState=" << value->mState << ", .mId=" << value->get().mId << "}"; + } + + namespace + { + using namespace ::testing; + + struct DialInfoData + { + ESM::DialInfo mValue; + bool mDeleted = false; + + void save(ESM::ESMWriter& writer) const { mValue.save(writer, mDeleted); } + }; + + template + struct DialogueData + { + ESM::Dialogue mDialogue; + std::vector mInfos; + }; + + DialogueData generateDialogueWithInfos( + std::size_t infoCount, std::string_view dialogueId = "dialogue") + { + DialogueData result; + + result.mDialogue.blank(); + result.mDialogue.mId = ESM::RefId::stringRefId(dialogueId); + result.mDialogue.mStringId = dialogueId; + + for (std::size_t i = 0; i < infoCount; ++i) + { + ESM::DialInfo& info = result.mInfos.emplace_back(); + info.blank(); + info.mId = ESM::RefId::stringRefId("info" + std::to_string(i)); + } + + if (infoCount >= 2) + { + result.mInfos[0].mNext = result.mInfos[1].mId; + result.mInfos[infoCount - 1].mPrev = result.mInfos[infoCount - 2].mId; + } + + for (std::size_t i = 1; i < infoCount - 1; ++i) + { + result.mInfos[i].mPrev = result.mInfos[i - 1].mId; + result.mInfos[i].mNext = result.mInfos[i + 1].mId; + } + + return result; + } + + template + std::unique_ptr saveDialogueWithInfos(const ESM::Dialogue& dialogue, Infos&& infos) + { + auto stream = std::make_unique(); + + ESM::ESMWriter writer; + writer.setFormatVersion(ESM::CurrentSaveGameFormatVersion); + writer.save(*stream); + + writer.startRecord(ESM::REC_DIAL); + dialogue.save(writer); + writer.endRecord(ESM::REC_DIAL); + + for (const auto& info : infos) + { + writer.startRecord(ESM::REC_INFO); + info.save(writer); + writer.endRecord(ESM::REC_INFO); + } + + return stream; + } + + void loadDialogueWithInfos(bool base, std::unique_ptr stream, InfoCollection& infoCollection, + InfoOrderByTopic& infoOrder) + { + ESM::ESMReader reader; + reader.open(std::move(stream), "test"); + + ASSERT_TRUE(reader.hasMoreRecs()); + ASSERT_EQ(reader.getRecName().toInt(), ESM::REC_DIAL); + reader.getRecHeader(); + bool isDeleted; + ESM::Dialogue dialogue; + dialogue.load(reader, isDeleted); + + while (reader.hasMoreRecs()) + { + ASSERT_EQ(reader.getRecName().toInt(), ESM::REC_INFO); + reader.getRecHeader(); + infoCollection.load(reader, base, dialogue, infoOrder); + } + } + + template + void saveAndLoadDialogueWithInfos(const ESM::Dialogue& dialogue, Infos&& infos, bool base, + InfoCollection& infoCollection, InfoOrderByTopic& infoOrder) + { + loadDialogueWithInfos(base, saveDialogueWithInfos(dialogue, infos), infoCollection, infoOrder); + } + + template + void saveAndLoadDialogueWithInfos( + const DialogueData& data, bool base, InfoCollection& infoCollection, InfoOrderByTopic& infoOrder) + { + saveAndLoadDialogueWithInfos(data.mDialogue, data.mInfos, base, infoCollection, infoOrder); + } + + MATCHER_P(InfoId, v, "") + { + return arg.mId == v; + } + + TEST(CSMWorldInfoCollectionTest, loadShouldAddRecord) + { + ESM::Dialogue dialogue; + dialogue.blank(); + dialogue.mId = ESM::RefId::stringRefId("dialogue"); + dialogue.mStringId = "Dialogue"; + + ESM::DialInfo info; + info.blank(); + info.mId = ESM::RefId::stringRefId("info0"); + + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, base, collection, infoOrder); + + EXPECT_EQ(collection.getSize(), 1); + ASSERT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + const Record& record = collection.getRecord(0); + ASSERT_EQ(record.mState, RecordBase::State_BaseOnly); + EXPECT_EQ(record.mBase.mTopicId, dialogue.mId); + EXPECT_EQ(record.mBase.mOriginalId, info.mId); + EXPECT_EQ(record.mBase.mId, ESM::RefId::stringRefId("dialogue#info0")); + + ASSERT_THAT(infoOrder, ElementsAre(Key(dialogue.mId))); + EXPECT_THAT(infoOrder.find(dialogue.mId)->second.getOrderedInfo(), ElementsAre(InfoId(info.mId))); + } + + TEST(CSMWorldInfoCollectionTest, loadShouldAddRecordAndMarkModifiedOnlyWhenNotBase) + { + ESM::Dialogue dialogue; + dialogue.blank(); + dialogue.mId = ESM::RefId::stringRefId("dialogue"); + dialogue.mStringId = "Dialogue"; + + ESM::DialInfo info; + info.blank(); + info.mId = ESM::RefId::stringRefId("info0"); + + const bool base = false; + InfoOrderByTopic infoOrder; + InfoCollection collection; + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, base, collection, infoOrder); + + EXPECT_EQ(collection.getSize(), 1); + ASSERT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + const Record& record = collection.getRecord(0); + ASSERT_EQ(record.mState, RecordBase::State_ModifiedOnly); + EXPECT_EQ(record.mModified.mTopicId, dialogue.mId); + EXPECT_EQ(record.mModified.mOriginalId, info.mId); + EXPECT_EQ(record.mModified.mId, ESM::RefId::stringRefId("dialogue#info0")); + + ASSERT_THAT(infoOrder, ElementsAre(Key(dialogue.mId))); + EXPECT_THAT(infoOrder.find(dialogue.mId)->second.getOrderedInfo(), ElementsAre(InfoId(info.mId))); + } + + TEST(CSMWorldInfoCollectionTest, loadShouldUpdateRecord) + { + ESM::Dialogue dialogue; + dialogue.blank(); + dialogue.mId = ESM::RefId::stringRefId("dialogue"); + dialogue.mStringId = "Dialogue"; + + ESM::DialInfo info; + info.blank(); + info.mId = ESM::RefId::stringRefId("info0"); + + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, base, collection, infoOrder); + + ESM::DialInfo updatedInfo = info; + updatedInfo.mActor = ESM::RefId::stringRefId("newActor"); + + saveAndLoadDialogueWithInfos(dialogue, std::array{ updatedInfo }, base, collection, infoOrder); + + ASSERT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + const Record& record = collection.getRecord(0); + ASSERT_EQ(record.mState, RecordBase::State_BaseOnly); + EXPECT_EQ(record.mBase.mActor, ESM::RefId::stringRefId("newActor")); + + ASSERT_THAT(infoOrder, ElementsAre(Key(dialogue.mId))); + EXPECT_THAT(infoOrder.find(dialogue.mId)->second.getOrderedInfo(), ElementsAre(InfoId(info.mId))); + } + + TEST(CSMWorldInfoCollectionTest, loadShouldUpdateRecordAndMarkModifiedWhenNotBase) + { + ESM::Dialogue dialogue; + dialogue.blank(); + dialogue.mId = ESM::RefId::stringRefId("dialogue"); + dialogue.mStringId = "Dialogue"; + + ESM::DialInfo info; + info.blank(); + info.mId = ESM::RefId::stringRefId("info0"); + + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, base, collection, infoOrder); + + ESM::DialInfo updatedInfo = info; + updatedInfo.mActor = ESM::RefId::stringRefId("newActor"); + + saveAndLoadDialogueWithInfos(dialogue, std::array{ updatedInfo }, false, collection, infoOrder); + + ASSERT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + const Record& record = collection.getRecord(0); + ASSERT_EQ(record.mState, RecordBase::State_Modified); + EXPECT_EQ(record.mModified.mActor, ESM::RefId::stringRefId("newActor")); + + ASSERT_THAT(infoOrder, ElementsAre(Key(dialogue.mId))); + EXPECT_THAT(infoOrder.find(dialogue.mId)->second.getOrderedInfo(), ElementsAre(InfoId(info.mId))); + } + + TEST(CSMWorldInfoCollectionTest, loadShouldSkipAbsentDeletedRecord) + { + ESM::Dialogue dialogue; + dialogue.blank(); + dialogue.mId = ESM::RefId::stringRefId("dialogue"); + dialogue.mStringId = "Dialogue"; + + DialInfoData info; + info.mValue.blank(); + info.mValue.mId = ESM::RefId::stringRefId("info0"); + info.mDeleted = true; + + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, base, collection, infoOrder); + + EXPECT_EQ(collection.getSize(), 0); + + ASSERT_THAT(infoOrder, ElementsAre()); + } + + TEST(CSMWorldInfoCollectionTest, loadShouldRemovePresentDeletedBaseRecord) + { + ESM::Dialogue dialogue; + dialogue.blank(); + dialogue.mId = ESM::RefId::stringRefId("dialogue"); + dialogue.mStringId = "Dialogue"; + + DialInfoData info; + info.mValue.blank(); + info.mValue.mId = ESM::RefId::stringRefId("info0"); + + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, base, collection, infoOrder); + + info.mDeleted = true; + + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, base, collection, infoOrder); + + EXPECT_EQ(collection.getSize(), 0); + + ASSERT_THAT(infoOrder, ElementsAre(Key(dialogue.mId))); + EXPECT_THAT(infoOrder.find(dialogue.mId)->second.getOrderedInfo(), ElementsAre()); + } + + TEST(CSMWorldInfoCollectionTest, loadShouldMarkAsDeletedNotBaseRecord) + { + ESM::Dialogue dialogue; + dialogue.blank(); + dialogue.mId = ESM::RefId::stringRefId("dialogue"); + dialogue.mStringId = "Dialogue"; + + DialInfoData info; + info.mValue.blank(); + info.mValue.mId = ESM::RefId::stringRefId("info0"); + + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, base, collection, infoOrder); + + info.mDeleted = true; + + saveAndLoadDialogueWithInfos(dialogue, std::array{ info }, false, collection, infoOrder); + + EXPECT_EQ(collection.getSize(), 1); + EXPECT_EQ( + collection.getRecord(ESM::RefId::stringRefId("dialogue#info0")).mState, RecordBase::State_Deleted); + + ASSERT_THAT(infoOrder, ElementsAre(Key(dialogue.mId))); + EXPECT_THAT(infoOrder.find(dialogue.mId)->second.getOrderedInfo(), ElementsAre(InfoId(info.mValue.mId))); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldOrderRecordsBasedOnPrevAndNext) + { + const DialogueData data = generateDialogueWithInfos(3); + + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.getSize(), 3); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 2); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldOrderRecordsBasedOnPrevAndNextWhenReversed) + { + DialogueData data = generateDialogueWithInfos(3); + + std::reverse(data.mInfos.begin(), data.mInfos.end()); + + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.getSize(), 3); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 2); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldInsertNewRecordBasedOnPrev) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + const DialogueData data = generateDialogueWithInfos(3); + + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + ESM::DialInfo newInfo; + newInfo.blank(); + newInfo.mId = ESM::RefId::stringRefId("newInfo"); + newInfo.mPrev = data.mInfos[1].mId; + newInfo.mNext = ESM::RefId::stringRefId("invalid"); + + saveAndLoadDialogueWithInfos(data.mDialogue, std::array{ newInfo }, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.getSize(), 4); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#newInfo")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 3); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldInsertNewRecordBasedOnNextWhenPrevIsNotFound) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + const DialogueData data = generateDialogueWithInfos(3); + + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + ESM::DialInfo newInfo; + newInfo.blank(); + newInfo.mId = ESM::RefId::stringRefId("newInfo"); + newInfo.mPrev = ESM::RefId::stringRefId("invalid"); + newInfo.mNext = data.mInfos[2].mId; + + saveAndLoadDialogueWithInfos(data.mDialogue, std::array{ newInfo }, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.getSize(), 4); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#newInfo")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 3); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldInsertNewRecordToFrontWhenPrevIsEmpty) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + const DialogueData data = generateDialogueWithInfos(3); + + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + ESM::DialInfo newInfo; + newInfo.blank(); + newInfo.mId = ESM::RefId::stringRefId("newInfo"); + newInfo.mNext = ESM::RefId::stringRefId("invalid"); + + saveAndLoadDialogueWithInfos(data.mDialogue, std::array{ newInfo }, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.getSize(), 4); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#newInfo")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 3); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldInsertNewRecordToBackWhenNextIsEmpty) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + const DialogueData data = generateDialogueWithInfos(3); + + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + ESM::DialInfo newInfo; + newInfo.blank(); + newInfo.mId = ESM::RefId::stringRefId("newInfo"); + newInfo.mPrev = ESM::RefId::stringRefId("invalid"); + + saveAndLoadDialogueWithInfos(data.mDialogue, std::array{ newInfo }, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.getSize(), 4); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#newInfo")), 3); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldMoveBackwardUpdatedRecordBasedOnPrev) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + const DialogueData data = generateDialogueWithInfos(3); + + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + ESM::DialInfo updatedInfo = data.mInfos[2]; + updatedInfo.mPrev = data.mInfos[0].mId; + updatedInfo.mNext = ESM::RefId::stringRefId("invalid"); + + saveAndLoadDialogueWithInfos(data.mDialogue, std::array{ updatedInfo }, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 1); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldMoveForwardUpdatedRecordBasedOnPrev) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + const DialogueData data = generateDialogueWithInfos(3); + + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + ESM::DialInfo updatedInfo = data.mInfos[0]; + updatedInfo.mPrev = data.mInfos[1].mId; + updatedInfo.mNext = ESM::RefId::stringRefId("invalid"); + + saveAndLoadDialogueWithInfos(data.mDialogue, std::array{ updatedInfo }, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 2); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldMoveToFrontUpdatedRecordWhenPrevIsEmpty) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + const DialogueData data = generateDialogueWithInfos(3); + + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + ESM::DialInfo updatedInfo = data.mInfos[2]; + updatedInfo.mPrev = ESM::RefId(); + updatedInfo.mNext = ESM::RefId::stringRefId("invalid"); + + saveAndLoadDialogueWithInfos(data.mDialogue, std::array{ updatedInfo }, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 0); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldMoveToBackUpdatedRecordWhenNextIsEmpty) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + const DialogueData data = generateDialogueWithInfos(3); + + saveAndLoadDialogueWithInfos(data, base, collection, infoOrder); + + ESM::DialInfo updatedInfo = data.mInfos[0]; + updatedInfo.mPrev = ESM::RefId::stringRefId("invalid"); + updatedInfo.mNext = ESM::RefId(); + + saveAndLoadDialogueWithInfos(data.mDialogue, std::array{ updatedInfo }, base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info0")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info1")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue#info2")), 1); + } + + TEST(CSMWorldInfoCollectionTest, sortShouldProvideStableOrderByTopic) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue2"), base, collection, infoOrder); + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue0"), base, collection, infoOrder); + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue1"), base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue0#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue0#info1")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue1#info0")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue1#info1")), 3); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue2#info0")), 4); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue2#info1")), 5); + } + + TEST(CSMWorldInfoCollectionTest, getAppendIndexShouldReturnFirstIndexAfterInfoTopic) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue0"), base, collection, infoOrder); + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue1"), base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_EQ(collection.getAppendIndex(ESM::RefId::stringRefId("dialogue0#info2")), 2); + } + + TEST(CSMWorldInfoCollectionTest, reorderRowsShouldFailWhenOutOfBounds) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue0"), base, collection, infoOrder); + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue1"), base, collection, infoOrder); + + EXPECT_FALSE(collection.reorderRows(5, {})); + } + + TEST(CSMWorldInfoCollectionTest, reorderRowsShouldFailWhenAppliedToDifferentTopics) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue0"), base, collection, infoOrder); + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "dialogue1"), base, collection, infoOrder); + + EXPECT_FALSE(collection.reorderRows(0, { 0, 1, 2 })); + } + + TEST(CSMWorldInfoCollectionTest, reorderRowsShouldSucceedWhenAppliedToOneTopic) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(3, "dialogue0"), base, collection, infoOrder); + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(3, "dialogue1"), base, collection, infoOrder); + + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue0#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue0#info1")), 1); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue0#info2")), 2); + + EXPECT_TRUE(collection.reorderRows(1, { 1, 0 })); + + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue0#info0")), 0); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue0#info1")), 2); + EXPECT_EQ(collection.searchId(ESM::RefId::stringRefId("dialogue0#info2")), 1); + } + + MATCHER_P(RecordPtrIdIs, v, "") + { + return v == arg->get().mId; + } + + TEST(CSMWorldInfoCollectionTest, getInfosByTopicShouldReturnRecordsGroupedByTopic) + { + const bool base = true; + InfoOrderByTopic infoOrder; + InfoCollection collection; + + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "d0"), base, collection, infoOrder); + saveAndLoadDialogueWithInfos(generateDialogueWithInfos(2, "d1"), base, collection, infoOrder); + + collection.sort(infoOrder); + + EXPECT_THAT(collection.getInfosByTopic(), + UnorderedElementsAre(Pair("d0", ElementsAre(RecordPtrIdIs("d0#info0"), RecordPtrIdIs("d0#info1"))), + Pair("d1", ElementsAre(RecordPtrIdIs("d1#info0"), RecordPtrIdIs("d1#info1"))))); + } + } +} diff --git a/apps/opencs_tests/model/world/testuniversalid.cpp b/apps/opencs_tests/model/world/testuniversalid.cpp new file mode 100644 index 000000000..564d65a1a --- /dev/null +++ b/apps/opencs_tests/model/world/testuniversalid.cpp @@ -0,0 +1,188 @@ +#include "apps/opencs/model/world/universalid.hpp" + +#include +#include + +#include + +namespace CSMWorld +{ + namespace + { + using namespace ::testing; + + TEST(CSMWorldUniversalIdTest, shouldFailToConstructFromNoneWithInvalidType) + { + EXPECT_THROW( + UniversalId{ static_cast(std::numeric_limits::max()) }, std::logic_error); + } + + TEST(CSMWorldUniversalIdTest, shouldFailToConstructFromStringWithInvalidType) + { + EXPECT_THROW(UniversalId(UniversalId::Type_Search, "invalid"), std::logic_error); + } + + TEST(CSMWorldUniversalIdTest, shouldFailToConstructFromIntWithInvalidType) + { + EXPECT_THROW(UniversalId(UniversalId::Type_Activator, 42), std::logic_error); + } + + TEST(CSMWorldUniversalIdTest, shouldFailToConstructFromRefIdWithInvalidType) + { + EXPECT_THROW(UniversalId(UniversalId::Type_Search, ESM::RefId()), std::logic_error); + } + + TEST(CSMWorldUniversalIdTest, shouldFailToConstructFromInvalidUniversalIdString) + { + EXPECT_THROW(UniversalId("invalid"), std::runtime_error); + } + + TEST(CSMWorldUniversalIdTest, getIndexShouldThrowExceptionForDefaultConstructed) + { + const UniversalId id; + EXPECT_THROW(id.getIndex(), std::logic_error); + } + + TEST(CSMWorldUniversalIdTest, getIndexShouldThrowExceptionForConstructedFromString) + { + const UniversalId id(UniversalId::Type_Activator, "a"); + EXPECT_THROW(id.getIndex(), std::logic_error); + } + + TEST(CSMWorldUniversalIdTest, getIndexShouldReturnValueForConstructedFromInt) + { + const UniversalId id(UniversalId::Type_Search, 42); + EXPECT_EQ(id.getIndex(), 42); + } + + TEST(CSMWorldUniversalIdTest, getIdShouldThrowExceptionForConstructedFromInt) + { + const UniversalId id(UniversalId::Type_Search, 42); + EXPECT_THROW(id.getId(), std::logic_error); + } + + TEST(CSMWorldUniversalIdTest, getIdShouldReturnValueForConstructedFromString) + { + const UniversalId id(UniversalId::Type_Activator, "a"); + EXPECT_EQ(id.getId(), "a"); + } + + TEST(CSMWorldUniversalIdTest, getRefIdShouldThrowExceptionForDefaultConstructed) + { + const UniversalId id; + EXPECT_THROW(id.getRefId(), std::logic_error); + } + + TEST(CSMWorldUniversalIdTest, getRefIdShouldReturnValueForConstructedFromRefId) + { + const UniversalId id(UniversalId::Type_Skill, ESM::IndexRefId(ESM::REC_SKIL, 42)); + EXPECT_EQ(id.getRefId(), ESM::IndexRefId(ESM::REC_SKIL, 42)); + } + + struct Params + { + UniversalId mId; + UniversalId::Type mType; + UniversalId::Class mClass; + UniversalId::ArgumentType mArgumentType; + std::string mTypeName; + std::string mString; + std::string mIcon; + }; + + std::ostream& operator<<(std::ostream& stream, const Params& value) + { + return stream << ".mType = " << value.mType << " .mClass = " << value.mClass + << " .mArgumentType = " << value.mArgumentType << " .mTypeName = " << value.mTypeName + << " .mString = " << value.mString << " .mIcon = " << value.mIcon; + } + + struct CSMWorldUniversalIdValidPerTypeTest : TestWithParam + { + }; + + TEST_P(CSMWorldUniversalIdValidPerTypeTest, getTypeShouldReturnExpected) + { + EXPECT_EQ(GetParam().mId.getType(), GetParam().mType); + } + + TEST_P(CSMWorldUniversalIdValidPerTypeTest, getClassShouldReturnExpected) + { + EXPECT_EQ(GetParam().mId.getClass(), GetParam().mClass); + } + + TEST_P(CSMWorldUniversalIdValidPerTypeTest, getArgumentTypeShouldReturnExpected) + { + EXPECT_EQ(GetParam().mId.getArgumentType(), GetParam().mArgumentType); + } + + TEST_P(CSMWorldUniversalIdValidPerTypeTest, shouldBeEqualToCopy) + { + EXPECT_EQ(GetParam().mId, UniversalId(GetParam().mId)); + } + + TEST_P(CSMWorldUniversalIdValidPerTypeTest, shouldNotBeLessThanCopy) + { + EXPECT_FALSE(GetParam().mId < UniversalId(GetParam().mId)); + } + + TEST_P(CSMWorldUniversalIdValidPerTypeTest, getTypeNameShouldReturnExpected) + { + EXPECT_EQ(GetParam().mId.getTypeName(), GetParam().mTypeName); + } + + TEST_P(CSMWorldUniversalIdValidPerTypeTest, toStringShouldReturnExpected) + { + EXPECT_EQ(GetParam().mId.toString(), GetParam().mString); + } + + TEST_P(CSMWorldUniversalIdValidPerTypeTest, getIconShouldReturnExpected) + { + EXPECT_EQ(GetParam().mId.getIcon(), GetParam().mIcon); + } + + const std::array validParams = { + Params{ UniversalId(), UniversalId::Type_None, UniversalId::Class_None, UniversalId::ArgumentType_None, "-", + "-", ":placeholder" }, + + Params{ UniversalId(UniversalId::Type_None), UniversalId::Type_None, UniversalId::Class_None, + UniversalId::ArgumentType_None, "-", "-", ":placeholder" }, + Params{ UniversalId(UniversalId::Type_RegionMap), UniversalId::Type_RegionMap, UniversalId::Class_NonRecord, + UniversalId::ArgumentType_None, "Region Map", "Region Map", ":./region-map.png" }, + Params{ UniversalId(UniversalId::Type_RunLog), UniversalId::Type_RunLog, UniversalId::Class_Transient, + UniversalId::ArgumentType_None, "Run Log", "Run Log", ":./run-log.png" }, + Params{ UniversalId(UniversalId::Type_Lands), UniversalId::Type_Lands, UniversalId::Class_RecordList, + UniversalId::ArgumentType_None, "Lands", "Lands", ":./land-heightmap.png" }, + Params{ UniversalId(UniversalId::Type_Icons), UniversalId::Type_Icons, UniversalId::Class_ResourceList, + UniversalId::ArgumentType_None, "Icons", "Icons", ":./resources-icon" }, + + Params{ UniversalId(UniversalId::Type_Activator, "a"), UniversalId::Type_Activator, + UniversalId::Class_RefRecord, UniversalId::ArgumentType_Id, "Activator", "Activator: a", + ":./activator.png" }, + Params{ UniversalId(UniversalId::Type_Gmst, "b"), UniversalId::Type_Gmst, UniversalId::Class_Record, + UniversalId::ArgumentType_Id, "Game Setting", "Game Setting: b", ":./gmst.png" }, + Params{ UniversalId(UniversalId::Type_Mesh, "c"), UniversalId::Type_Mesh, UniversalId::Class_Resource, + UniversalId::ArgumentType_Id, "Mesh", "Mesh: c", ":./resources-mesh" }, + Params{ UniversalId(UniversalId::Type_Scene, "d"), UniversalId::Type_Scene, UniversalId::Class_Collection, + UniversalId::ArgumentType_Id, "Scene", "Scene: d", ":./scene.png" }, + Params{ UniversalId(UniversalId::Type_Reference, "e"), UniversalId::Type_Reference, + UniversalId::Class_SubRecord, UniversalId::ArgumentType_Id, "Instance", "Instance: e", + ":./instance.png" }, + + Params{ UniversalId(UniversalId::Type_Search, 42), UniversalId::Type_Search, UniversalId::Class_Transient, + UniversalId::ArgumentType_Index, "Global Search", "Global Search: 42", ":./menu-search.png" }, + + Params{ UniversalId("Instance: f"), UniversalId::Type_Reference, UniversalId::Class_SubRecord, + UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":./instance.png" }, + + Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::stringRefId("g")), UniversalId::Type_Reference, + UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: \"g\"", + ":./instance.png" }, + Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::index(ESM::REC_SKIL, 42)), + UniversalId::Type_Reference, UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", + "Instance: Index:SKIL:0x2a", ":./instance.png" }, + }; + + INSTANTIATE_TEST_SUITE_P(ValidParams, CSMWorldUniversalIdValidPerTypeTest, ValuesIn(validParams)); + } +} diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index eff52f891..45d514eb6 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -2,6 +2,7 @@ set(GAME main.cpp engine.cpp + options.cpp ${CMAKE_SOURCE_DIR}/files/tes3mp/tes3mp.rc ${CMAKE_SOURCE_DIR}/files/tes3mp/tes3mp.exe.manifest @@ -18,15 +19,16 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - actors objects renderingmanager animation rotatecontroller sky npcanimation vismask + actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager - bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation + bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover + postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode precipitationocclusion ripples ) add_openmw_dir (mwinput actions actionmanager bindingsmanager controllermanager controlswitch - inputmanagerimp mousemanager keyboardmanager sdlmappings sensormanager + inputmanagerimp mousemanager keyboardmanager sensormanager gyromanager ) add_openmw_dir (mwgui @@ -42,6 +44,7 @@ add_openmw_dir (mwgui tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher + postprocessorhud settings ) add_openmw_dir (mwdialogue @@ -55,6 +58,14 @@ add_openmw_dir (mwscript animationextensions transformationextensions consoleextensions userextensions ) +add_openmw_dir (mwlua + luamanagerimp object worldview userdataserializer luaevents engineevents objectvariant + context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings + camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings + types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist + worker magicbindings + ) + add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings @@ -63,11 +74,11 @@ add_openmw_dir (mwsound add_openmw_dir (mwworld refdata worldimp scene globals class action nullaction actionteleport containerstore actiontalk actiontake manualref player cellvisitors failedaction - cells localscripts customdata inventorystore ptr actionopen actionread actionharvest + worldmodel localscripts customdata inventorystore ptr actionopen actionread actionharvest actionequip timestamp actionalchemy cellstore actionapply actioneat - store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor + store esmstore fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref weather projectilemanager - cellpreloader datetimemanager + cellpreloader datetimemanager groundcoverstore magiceffects cell ptrregistry ) add_openmw_dir (mwphysics @@ -79,15 +90,16 @@ add_openmw_dir (mwphysics add_openmw_dir (mwclass classes activator creature npc weapon armor potion apparatus book clothing container door ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor bodypart + esm4base light4 ) add_openmw_dir (mwmechanics mechanicsmanagerimp stat creaturestats magiceffects movement actorutil spelllist drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance - disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning - character actors objects aistate trading weaponpriority spellpriority weapontype spellutil tickableeffects - spellabsorption linkedeffects + disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction summoning + character actors objects aistate trading weaponpriority spellpriority weapontype spellutil + spelleffects ) add_openmw_dir (mwstate @@ -182,10 +194,6 @@ target_link_libraries(tes3mp ${OSGDB_LIBRARIES} ${OSGUTIL_LIBRARIES} ${OSG_LIBRARIES} - - ${Boost_SYSTEM_LIBRARY} - ${Boost_THREAD_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${OPENAL_LIBRARY} ${FFmpeg_LIBRARIES} @@ -198,30 +206,31 @@ target_link_libraries(tes3mp ${RakNet_LIBRARY} ) -if(OSG_STATIC) - unset(_osg_plugins_static_files) - add_library(openmw_osg_plugins INTERFACE) - foreach(_plugin ${USED_OSG_PLUGINS}) - string(TOUPPER ${_plugin} _plugin_uc) - if(OPENMW_USE_SYSTEM_OSG) - list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) - else() - list(APPEND _osg_plugins_static_files $) - target_link_libraries(openmw_osg_plugins INTERFACE $) - add_dependencies(openmw_osg_plugins ${${_plugin_uc}_LIBRARY}) - endif() - endforeach() - # We use --whole-archive because OSG plugins use registration. - get_whole_archive_options(_opts ${_osg_plugins_static_files}) - target_link_options(openmw_osg_plugins INTERFACE ${_opts}) - target_link_libraries(tes3mp openmw_osg_plugins) +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(tes3mp PRIVATE + - if(OPENMW_USE_SYSTEM_OSG) - # OSG plugin pkgconfig files are missing these dependencies. - # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 - target_link_libraries(tes3mp freetype jpeg png) - endif() -endif(OSG_STATIC) + + + + + + + + + + + + + + + + + + + + ) +endif() if (ANDROID) target_link_libraries(tes3mp EGL android log z) @@ -232,19 +241,18 @@ if (USE_SYSTEM_TINYXML) endif() if (NOT UNIX) -target_link_libraries(tes3mp ${SDL2MAIN_LIBRARY}) + target_link_libraries(tes3mp ${SDL2MAIN_LIBRARY}) endif() # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) -target_link_libraries(tes3mp ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(tes3mp ${CMAKE_THREAD_LIBS_INIT}) endif() if(APPLE) set(BUNDLE_RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources") - set(OPENMW_MYGUI_FILES_ROOT ${BUNDLE_RESOURCES_DIR}) - set(OPENMW_SHADERS_ROOT ${BUNDLE_RESOURCES_DIR}) + set(OPENMW_RESOURCES_ROOT ${BUNDLE_RESOURCES_DIR}) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) @@ -261,24 +269,20 @@ if(APPLE) target_link_libraries(tes3mp ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK}) if (FFmpeg_FOUND) - find_library(COREVIDEO_FRAMEWORK CoreVideo) - find_library(VDA_FRAMEWORK VideoDecodeAcceleration) - target_link_libraries(tes3mp z ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK}) + target_link_libraries(tes3mp z) + target_link_options(tes3mp PRIVATE "LINKER:SHELL:-framework CoreVideo" + "LINKER:SHELL:-framework CoreMedia" + "LINKER:SHELL:-framework VideoToolbox" + "LINKER:SHELL:-framework AudioToolbox" + "LINKER:SHELL:-framework VideoDecodeAcceleration") endif() endif(APPLE) if (BUILD_WITH_CODE_COVERAGE) - add_definitions (--coverage) - target_link_libraries(tes3mp gcov) + target_compile_options(tes3mp PRIVATE --coverage) + target_link_libraries(tes3mp gcov) endif() -if (MSVC) - # Debug version needs increased number of sections beyond 2^16 - if (CMAKE_CL_64) - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") - endif (CMAKE_CL_64) -endif (MSVC) - if (WIN32) INSTALL(TARGETS tes3mp RUNTIME DESTINATION ".") endif (WIN32) diff --git a/apps/openmw/android_main.cpp b/apps/openmw/android_main.cpp index cc36388b0..ade009ae0 100644 --- a/apps/openmw/android_main.cpp +++ b/apps/openmw/android_main.cpp @@ -1,9 +1,11 @@ +#ifndef stderr int stderr = 0; // Hack: fix linker error +#endif #include "SDL_main.h" +#include #include #include -#include /******************************************************************************* Functions called by JNI @@ -14,43 +16,50 @@ int stderr = 0; // Hack: fix linker error extern void SDL_Android_Init(JNIEnv* env, jclass cls); extern int argcData; -extern const char **argvData; +extern const char** argvData; void releaseArgv(); - -extern "C" int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv *env, jclass cls, jobject obj) { +extern "C" int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv* env, jclass cls, jobject obj) +{ int ret = 0; SDL_GetMouseState(&ret, nullptr); return ret; } - -extern "C" int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv *env, jclass cls, jobject obj) { +extern "C" int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv* env, jclass cls, jobject obj) +{ int ret = 0; SDL_GetMouseState(nullptr, &ret); return ret; } -extern "C" int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobject obj) { +extern "C" int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv* env, jclass cls, jobject obj) +{ return SDL_ShowCursor(SDL_QUERY); } -extern SDL_Window *Android_Window; -extern "C" int SDL_SendMouseMotion(SDL_Window * window, int mouseID, int relative, int x, int y); -extern "C" void Java_org_libsdl_app_SDLActivity_sendRelativeMouseMotion(JNIEnv *env, jclass cls, int x, int y) { +extern SDL_Window* Android_Window; +extern "C" int SDL_SendMouseMotion(SDL_Window* window, int mouseID, int relative, int x, int y); +extern "C" void Java_org_libsdl_app_SDLActivity_sendRelativeMouseMotion(JNIEnv* env, jclass cls, int x, int y) +{ SDL_SendMouseMotion(Android_Window, 0, 1, x, y); } -extern "C" int SDL_SendMouseButton(SDL_Window * window, int mouseID, Uint8 state, Uint8 button); -extern "C" void Java_org_libsdl_app_SDLActivity_sendMouseButton(JNIEnv *env, jclass cls, int state, int button) { +extern "C" int SDL_SendMouseButton(SDL_Window* window, int mouseID, Uint8 state, Uint8 button); +extern "C" void Java_org_libsdl_app_SDLActivity_sendMouseButton(JNIEnv* env, jclass cls, int state, int button) +{ SDL_SendMouseButton(Android_Window, 0, state, button); } -extern "C" int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) { +extern "C" int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) +{ setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1); // 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"); + 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"); return 0; } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index fe8a0f7a3..2d1081f00 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,15 +1,12 @@ #include "engine.hpp" -#include -#include +#include #include +#include #include -#include - -#include -#include #include +#include #include @@ -21,8 +18,8 @@ #include #include -#include #include +#include #include #include @@ -30,6 +27,9 @@ #include +#include +#include + #include #include @@ -49,20 +49,32 @@ */ #include +#include #include +#include +#include +#include +#include +#include + +#include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" -#include "mwscript/scriptmanagerimp.hpp" +#include "mwlua/luamanagerimp.hpp" +#include "mwlua/worker.hpp" + #include "mwscript/interpretercontext.hpp" +#include "mwscript/scriptmanagerimp.hpp" #include "mwsound/soundmanagerimp.hpp" #include "mwworld/class.hpp" -#include "mwworld/player.hpp" #include "mwworld/worldimp.hpp" #include "mwrender/vismask.hpp" @@ -77,6 +89,8 @@ #include "mwstate/statemanagerimp.hpp" +#include "profile.hpp" + namespace { void checkSDLError(int ret) @@ -85,130 +99,6 @@ namespace Log(Debug::Error) << "SDL error: " << SDL_GetError(); } - struct UserStats - { - const std::string mLabel; - const std::string mBegin; - const std::string mEnd; - const std::string mTaken; - - UserStats(const std::string& label, const std::string& prefix) - : mLabel(label), - mBegin(prefix + "_time_begin"), - mEnd(prefix + "_time_end"), - mTaken(prefix + "_time_taken") - {} - }; - - enum class UserStatsType : std::size_t - { - Input, - Sound, - State, - Script, - Mechanics, - Physics, - PhysicsWorker, - World, - Gui, - - Number, - }; - - template - struct UserStatsValue - { - static const UserStats sValue; - }; - - template <> - const UserStats UserStatsValue::sValue {"Input", "input"}; - - template <> - const UserStats UserStatsValue::sValue {"Sound", "sound"}; - - template <> - const UserStats UserStatsValue::sValue {"State", "state"}; - - template <> - const UserStats UserStatsValue::sValue {"Script", "script"}; - - template <> - const UserStats UserStatsValue::sValue {"Mech", "mechanics"}; - - template <> - const UserStats UserStatsValue::sValue {"Phys", "physics"}; - - template <> - const UserStats UserStatsValue::sValue {" -Async", "physicsworker"}; - - template <> - const UserStats UserStatsValue::sValue {"World", "world"}; - - template <> - const UserStats UserStatsValue::sValue {"Gui", "gui"}; - - template - struct ForEachUserStatsValue - { - template - static void apply(F&& f) - { - f(UserStatsValue::sValue); - using Next = ForEachUserStatsValue(static_cast(type) + 1)>; - Next::apply(std::forward(f)); - } - }; - - template <> - struct ForEachUserStatsValue - { - template - static void apply(F&&) {} - }; - - template - void forEachUserStatsValue(F&& f) - { - ForEachUserStatsValue(0)>::apply(std::forward(f)); - } - - template - class ScopedProfile - { - public: - ScopedProfile(osg::Timer_t frameStart, unsigned int frameNumber, const osg::Timer& timer, osg::Stats& stats) - : mScopeStart(timer.tick()), - mFrameStart(frameStart), - mFrameNumber(frameNumber), - mTimer(timer), - mStats(stats) - { - } - - ScopedProfile(const ScopedProfile&) = delete; - ScopedProfile& operator=(const ScopedProfile&) = delete; - - ~ScopedProfile() - { - if (!mStats.collectStats("engine")) - return; - const osg::Timer_t end = mTimer.tick(); - const UserStats& stats = UserStatsValue::sValue; - - mStats.setAttribute(mFrameNumber, stats.mBegin, mTimer.delta_s(mFrameStart, mScopeStart)); - mStats.setAttribute(mFrameNumber, stats.mTaken, mTimer.delta_s(mScopeStart, end)); - mStats.setAttribute(mFrameNumber, stats.mEnd, mTimer.delta_s(mFrameStart, end)); - } - - private: - const osg::Timer_t mScopeStart; - const osg::Timer_t mFrameStart; - const unsigned int mFrameNumber; - const osg::Timer& mTimer; - osg::Stats& mStats; - }; - void initStatsHandler(Resource::Profiler& profiler) { const osg::Vec4f textColor(1.f, 1.f, 1.f, 1.f); @@ -218,26 +108,67 @@ namespace const bool averageInInverseSpace = false; const float maxValue = 10000; - forEachUserStatsValue([&] (const UserStats& v) - { - profiler.addUserStatsLine(v.mLabel, textColor, barColor, v.mTaken, multiplier, - average, averageInInverseSpace, v.mBegin, v.mEnd, maxValue); + OMW::forEachUserStatsValue([&](const OMW::UserStats& v) { + profiler.addUserStatsLine(v.mLabel, textColor, barColor, v.mTaken, multiplier, average, + averageInInverseSpace, v.mBegin, v.mEnd, maxValue); }); // the forEachUserStatsValue loop is "run" at compile time, hence the settings manager is not available. // Unconditionnally add the async physics stats, and then remove it at runtime if necessary if (Settings::Manager::getInt("async num threads", "Physics") == 0) profiler.removeUserStatsLine(" -Async"); } + + struct ScheduleNonDialogMessageBox + { + void operator()(std::string message) const + { + MWBase::Environment::get().getWindowManager()->scheduleMessageBox( + std::move(message), MWGui::ShowInDialogueMode_Never); + } + }; + + struct IgnoreString + { + void operator()(std::string) const {} + }; + + class IdentifyOpenGLOperation : public osg::GraphicsOperation + { + public: + IdentifyOpenGLOperation() + : GraphicsOperation("IdentifyOpenGLOperation", false) + { + } + + void operator()(osg::GraphicsContext* graphicsContext) override + { + Log(Debug::Info) << "OpenGL Vendor: " << glGetString(GL_VENDOR); + Log(Debug::Info) << "OpenGL Renderer: " << glGetString(GL_RENDERER); + Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxTextureImageUnits); + } + + int getMaxTextureImageUnits() const + { + if (mMaxTextureImageUnits == 0) + throw std::logic_error("mMaxTextureImageUnits is not initialized"); + return mMaxTextureImageUnits; + } + + private: + int mMaxTextureImageUnits = 0; + }; } void OMW::Engine::executeLocalScripts() { - MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts(); + MWWorld::LocalScripts& localScripts = mWorld->getLocalScripts(); localScripts.startIteration(); - std::pair script; + std::pair script; while (localScripts.getNext(script)) { +<<<<<<< HEAD MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); @@ -272,34 +203,40 @@ void OMW::Engine::executeLocalScripts() */ mEnvironment.getScriptManager()->run (script.first, interpreterContext); +======= + MWScript::InterpreterContext interpreterContext(&script.second.getRefData().getLocals(), script.second); + mScriptManager->run(script.first, interpreterContext); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } bool OMW::Engine::frame(float frametime) { + const osg::Timer_t frameStart = mViewer->getStartTick(); + const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); + const osg::Timer* const timer = osg::Timer::instance(); + osg::Stats* const stats = mViewer->getViewerStats(); + + mEnvironment.setFrameDuration(frametime); + try { - const osg::Timer_t frameStart = mViewer->getStartTick(); - const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); - const osg::Timer* const timer = osg::Timer::instance(); - osg::Stats* const stats = mViewer->getViewerStats(); - - mEnvironment.setFrameDuration(frametime); - // update input { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mEnvironment.getInputManager()->update(frametime, false); + mInputManager->update(frametime, false); } // When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug. - // 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 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) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (!mEnvironment.getWindowManager()->isWindowVisible()) + if (!mWindowManager->isWindowVisible()) { +<<<<<<< HEAD mEnvironment.getSoundManager()->pausePlayback(); /* Start of tes3mp change (major) @@ -310,13 +247,17 @@ bool OMW::Engine::frame(float frametime) /* End of tes3mp change (major) */ +======= + mSoundManager->pausePlayback(); + return false; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } else - mEnvironment.getSoundManager()->resumePlayback(); + mSoundManager->resumePlayback(); // sound if (mUseSound) - mEnvironment.getSoundManager()->update(frametime); + mSoundManager->update(frametime); } /* @@ -330,6 +271,7 @@ bool OMW::Engine::frame(float frametime) */ // Main menu opened? Then scripts are also paused. +<<<<<<< HEAD bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu); /* @@ -342,13 +284,24 @@ bool OMW::Engine::frame(float frametime) /* End of tes3mp change (major) */ +======= + bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu); + + { + ScopedProfile profile(frameStart, frameNumber, *timer, *stats); + // Should be called after input manager update and before any change to the game world. + // It applies to the game world queued changes from the previous frame. + mLuaManager->synchronizedUpdate(); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 // update game state { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mEnvironment.getStateManager()->update (frametime); + mStateManager->update(frametime); } +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -360,31 +313,34 @@ bool OMW::Engine::frame(float frametime) /* End of tes3mp change (major) */ +======= + bool guiActive = mWindowManager->isGuiMode(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { if (!paused) { - if (mEnvironment.getWorld()->getScriptsEnabled()) + if (mWorld->getScriptsEnabled()) { // local scripts executeLocalScripts(); // global scripts - mEnvironment.getScriptManager()->getGlobalScripts().run(); + mScriptManager->getGlobalScripts().run(); } - mEnvironment.getWorld()->markCellAsUnchanged(); + mWorld->getWorldScene().markCellAsUnchanged(); } if (!guiActive) { - double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0; - mEnvironment.getWorld()->advanceTime(hours, true); - mEnvironment.getWorld()->rechargeItems(frametime, true); + double hours = (frametime * mWorld->getTimeScaleFactor()) / 3600.0; + mWorld->advanceTime(hours, true); + mWorld->rechargeItems(frametime, true); } } } @@ -393,13 +349,14 @@ bool OMW::Engine::frame(float frametime) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mEnvironment.getMechanicsManager()->update(frametime, guiActive); + mMechanicsManager->update(frametime, guiActive); } - if (mEnvironment.getStateManager()->getState() == MWBase::StateManager::State_Running) + if (mStateManager->getState() == MWBase::StateManager::State_Running) { +<<<<<<< HEAD MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); /* Start of tes3mp change (major) @@ -412,6 +369,11 @@ bool OMW::Engine::frame(float frametime) /* End of tes3mp change (major) */ +======= + MWWorld::Ptr player = mWorld->getPlayerPtr(); + if (!guiActive && player.getClass().getCreatureStats(player).isDead()) + mStateManager->endGame(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } @@ -419,9 +381,9 @@ bool OMW::Engine::frame(float frametime) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mEnvironment.getWorld()->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); + mWorld->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); } } @@ -429,65 +391,90 @@ bool OMW::Engine::frame(float frametime) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mEnvironment.getWorld()->update(frametime, guiActive); + mWorld->update(frametime, guiActive); } } // update GUI { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mEnvironment.getWindowManager()->update(frametime); - } - - if (stats->collectStats("resource")) - { - stats->setAttribute(frameNumber, "FrameNumber", frameNumber); - - mResourceSystem->reportStats(frameNumber, stats); - - stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); - stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); - - mEnvironment.reportStats(frameNumber, *stats); + mWindowManager->update(frametime); } } catch (const std::exception& e) { Log(Debug::Error) << "Error in frame: " << e.what(); } + + const bool reportResource = stats->collectStats("resource"); + + if (reportResource) + stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getSize()); + + mUnrefQueue->flush(*mWorkQueue); + + if (reportResource) + { + stats->setAttribute(frameNumber, "FrameNumber", frameNumber); + + mResourceSystem->reportStats(frameNumber, stats); + + stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); + stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); + + mMechanicsManager->reportStats(frameNumber, *stats); + mWorld->reportStats(frameNumber, *stats); + mLuaManager->reportStats(frameNumber, *stats); + } + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + + { + ScopedProfile profile(frameStart, frameNumber, *timer, *stats); + mWorld->updateWindowManager(); + } + + mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now + + mViewer->renderingTraversals(); + + mLuaWorker->finishUpdate(); + return true; } OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) - : mWindow(nullptr) - , mEncoding(ToUTF8::WINDOWS_1252) - , mEncoder(nullptr) - , mScreenCaptureOperation(nullptr) - , mSkipMenu (false) - , mUseSound (true) - , mCompileAll (false) - , mCompileAllDialogue (false) - , mWarningsMode (1) - , mScriptConsoleMode (false) - , mActivationDistanceOverride(-1) - , mGrab(true) - , mExportFonts(false) - , mRandomSeed(0) - , mScriptContext (nullptr) - , mFSStrict (false) - , mScriptBlacklistUse (true) - , mNewGame (false) - , mCfgMgr(configurationManager) + : mWindow(nullptr) + , mEncoding(ToUTF8::WINDOWS_1252) + , mScreenCaptureOperation(nullptr) + , mSelectDepthFormatOperation(new SceneUtil::SelectDepthFormatOperation()) + , mSelectColorFormatOperation(new SceneUtil::Color::SelectColorFormatOperation()) + , mStereoManager(nullptr) + , mSkipMenu(false) + , mUseSound(true) + , mCompileAll(false) + , mCompileAllDialogue(false) + , mWarningsMode(1) + , mScriptConsoleMode(false) + , mActivationDistanceOverride(-1) + , mGrab(true) + , mRandomSeed(0) + , mScriptBlacklistUse(true) + , mNewGame(false) + , mCfgMgr(configurationManager) + , mGlMaxTextureImageUnits(0) { SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads - Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR; - if(SDL_WasInit(flags) == 0) + Uint32 flags + = SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_SENSOR; + if (SDL_WasInit(flags) == 0) { SDL_SetMainReady(); - if(SDL_Init(flags) != 0) + if (SDL_Init(flags) != 0) { throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError())); } @@ -496,6 +483,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) OMW::Engine::~Engine() { +<<<<<<< HEAD /* Start of tes3mp addition @@ -521,15 +509,34 @@ OMW::Engine::~Engine() */ delete mScriptContext; +======= + if (mScreenCaptureOperation != nullptr) + mScreenCaptureOperation->stop(); + + mMechanicsManager = nullptr; + mDialogueManager = nullptr; + mJournal = nullptr; + mScriptManager = nullptr; + mWindowManager = nullptr; + mWorld = nullptr; + mStereoManager = nullptr; + mSoundManager = nullptr; + mInputManager = nullptr; + mStateManager = nullptr; + mLuaWorker = nullptr; + mLuaManager = nullptr; + mL10nManager = nullptr; + +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mScriptContext = nullptr; + mUnrefQueue = nullptr; mWorkQueue = nullptr; mViewer = nullptr; mResourceSystem.reset(); - delete mEncoder; mEncoder = nullptr; if (mWindow) @@ -551,33 +558,29 @@ OMW::Engine::~Engine() */ } -void OMW::Engine::enableFSStrict(bool fsStrict) -{ - mFSStrict = fsStrict; -} - // Set data dir -void OMW::Engine::setDataDirs (const Files::PathContainer& dataDirs) +void OMW::Engine::setDataDirs(const Files::PathContainer& dataDirs) { mDataDirs = dataDirs; - mDataDirs.insert(mDataDirs.begin(), (mResDir / "vfs")); - mFileCollections = Files::Collections (mDataDirs, !mFSStrict); + mDataDirs.insert(mDataDirs.begin(), mResDir / "vfs"); + mFileCollections = Files::Collections(mDataDirs); } // Add BSA archive -void OMW::Engine::addArchive (const std::string& archive) { +void OMW::Engine::addArchive(const std::string& archive) +{ mArchives.push_back(archive); } // Set resource dir -void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir) +void OMW::Engine::setResourceDir(const std::filesystem::path& parResDir) { mResDir = parResDir; } // Set start cell name -void OMW::Engine::setCell (const std::string& cellName) +void OMW::Engine::setCell(const std::string& cellName) { mCellName = cellName; } @@ -592,62 +595,46 @@ void OMW::Engine::addGroundcoverFile(const std::string& file) mGroundcoverFiles.emplace_back(file); } -void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) +void OMW::Engine::setSkipMenu(bool skipMenu, bool newGame) { mSkipMenu = skipMenu; mNewGame = newGame; } -std::string OMW::Engine::loadSettings (Settings::Manager & settings) +void OMW::Engine::createWindow() { - // Create the settings manager and load default settings file - const std::string localdefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); - const std::string globaldefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); + int screen = Settings::Manager::getInt("screen", "Video"); + int width = Settings::Manager::getInt("resolution x", "Video"); + int height = Settings::Manager::getInt("resolution y", "Video"); + Settings::WindowMode windowMode + = static_cast(Settings::Manager::getInt("window mode", "Video")); + bool windowBorder = Settings::Manager::getBool("window border", "Video"); + int vsync = Settings::Manager::getInt("vsync mode", "Video"); + unsigned int antialiasing = std::max(0, Settings::Manager::getInt("antialiasing", "Video")); - // prefer local - if (boost::filesystem::exists(localdefault)) - settings.loadDefault(localdefault); - else if (boost::filesystem::exists(globaldefault)) - settings.loadDefault(globaldefault); - else - throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); + int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); - // load user settings if they exist - const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); - if (boost::filesystem::exists(settingspath)) - settings.loadUser(settingspath); - - return settingspath; -} - -void OMW::Engine::createWindow(Settings::Manager& settings) -{ - int screen = settings.getInt("screen", "Video"); - int width = settings.getInt("resolution x", "Video"); - int height = settings.getInt("resolution y", "Video"); - bool fullscreen = settings.getBool("fullscreen", "Video"); - bool windowBorder = settings.getBool("window border", "Video"); - bool vsync = settings.getBool("vsync", "Video"); - unsigned int antialiasing = std::max(0, settings.getInt("antialiasing", "Video")); - - int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), - pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); - - if(fullscreen) + if (windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::WindowedFullscreen) { pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); } - Uint32 flags = SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE; - if(fullscreen) + Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; + if (windowMode == Settings::WindowMode::Fullscreen) flags |= SDL_WINDOW_FULLSCREEN; + else if (windowMode == Settings::WindowMode::WindowedFullscreen) + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + + // Allows for Windows snapping features to properly work in borderless window + SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "1"); + SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, - settings.getBool("minimize on focus loss", "Video") ? "1" : "0"); + Settings::Manager::getBool("minimize on focus loss", "Video") ? "1" : "0"); checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)); @@ -682,7 +669,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings) // Try with a lower AA if (antialiasing > 0) { - Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2; + Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " + << antialiasing / 2; antialiasing /= 2; Settings::Manager::setInt("antialiasing", "Video", antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); @@ -697,23 +685,36 @@ void OMW::Engine::createWindow(Settings::Manager& settings) } } + // Since we use physical resolution internally, we have to create the window with scaled resolution, + // but we can't get the scale before the window exists, so instead we have to resize aftewards. + int w, h; + SDL_GetWindowSize(mWindow, &w, &h); + int dw, dh; + SDL_GL_GetDrawableSize(mWindow, &dw, &dh); + if (dw != w || dh != h) + { + SDL_SetWindowSize(mWindow, width / (dw / w), height / (dh / h)); + } + setWindowIcon(); osg::ref_ptr traits = new osg::GraphicsContext::Traits; SDL_GetWindowPosition(mWindow, &traits->x, &traits->y); - SDL_GetWindowSize(mWindow, &traits->width, &traits->height); + SDL_GL_GetDrawableSize(mWindow, &traits->width, &traits->height); traits->windowName = SDL_GetWindowTitle(mWindow); - traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS); + traits->windowDecoration = !(SDL_GetWindowFlags(mWindow) & SDL_WINDOW_BORDERLESS); traits->screenNum = SDL_GetWindowDisplayIndex(mWindow); - traits->vsync = vsync; + traits->vsync = 0; traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow); - graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits); - if (!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext"); + graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits, vsync); + if (!graphicsWindow->valid()) + throw std::runtime_error("Failed to create GraphicsContext"); if (traits->samples < antialiasing) { - Log(Debug::Warning) << "Warning: Framebuffer MSAA level is only " << traits->samples << "x instead of " << antialiasing << "x. Trying " << antialiasing / 2 << "x instead."; + Log(Debug::Warning) << "Warning: Framebuffer MSAA level is only " << traits->samples << "x instead of " + << antialiasing << "x. Trying " << antialiasing / 2 << "x instead."; graphicsWindow->closeImplementation(); SDL_DestroyWindow(mWindow); mWindow = nullptr; @@ -739,16 +740,30 @@ void OMW::Engine::createWindow(Settings::Manager& settings) camera->setGraphicsContext(graphicsWindow); camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); + osg::ref_ptr realizeOperations = new SceneUtil::OperationSequence(false); + mViewer->setRealizeOperation(realizeOperations); + osg::ref_ptr identifyOp = new IdentifyOpenGLOperation(); + realizeOperations->add(identifyOp); + if (Debug::shouldDebugOpenGL()) - mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation()); + realizeOperations->add(new Debug::EnableGLDebugOperation()); + + realizeOperations->add(mSelectDepthFormatOperation); + realizeOperations->add(mSelectColorFormatOperation); + + if (Stereo::getStereo()) + realizeOperations->add(new Stereo::InitializeStereoOperation()); mViewer->realize(); + mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits(); - mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); + mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle( + 0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); } void OMW::Engine::setWindowIcon() { +<<<<<<< HEAD boost::filesystem::ifstream windowIconStream; /* Start of tes3mp change (major) @@ -759,6 +774,10 @@ void OMW::Engine::setWindowIcon() /* End of tes3mp change (major) */ +======= + std::ifstream windowIconStream; + const auto windowIcon = mResDir / "openmw.png"; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary); if (windowIconStream.fail()) Log(Debug::Error) << "Error: Failed to open " << windowIcon; @@ -770,7 +789,8 @@ void OMW::Engine::setWindowIcon() } osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream); if (!result.success()) - Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " << result.status(); + Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " + << result.status(); else { osg::ref_ptr image = result.getImage(); @@ -779,199 +799,209 @@ void OMW::Engine::setWindowIcon() } } -void OMW::Engine::prepareEngine (Settings::Manager & settings) +void OMW::Engine::prepareEngine() { - mEnvironment.setStateManager ( - new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); + mStateManager = std::make_unique(mCfgMgr.getUserDataPath() / "saves", mContentFiles); + mEnvironment.setStateManager(*mStateManager); - createWindow(settings); + bool stereoEnabled + = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); + mStereoManager = std::make_unique(mViewer, stereoEnabled); - osg::ref_ptr rootNode (new osg::Group); + osg::ref_ptr rootNode(new osg::Group); mViewer->setSceneData(rootNode); - mVFS.reset(new VFS::Manager(mFSStrict)); + createWindow(); + + mVFS = std::make_unique(); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); - mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); - mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing - mResourceSystem->getSceneManager()->setFilterSettings( - Settings::Manager::getString("texture mag filter", "General"), + mResourceSystem = std::make_unique(mVFS.get()); + mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); + mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply( + false); // keep to Off for now to allow better state sharing + mResourceSystem->getSceneManager()->setFilterSettings(Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), - Settings::Manager::getString("texture mipmap", "General"), - Settings::Manager::getInt("anisotropy", "General") - ); + Settings::Manager::getString("texture mipmap", "General"), Settings::Manager::getInt("anisotropy", "General")); + mEnvironment.setResourceSystem(*mResourceSystem); - int numThreads = Settings::Manager::getInt("preload num threads", "Cells"); - if (numThreads <= 0) - throw std::runtime_error("Invalid setting: 'preload num threads' must be >0"); - mWorkQueue = new SceneUtil::WorkQueue(numThreads); + mWorkQueue = new SceneUtil::WorkQueue(Settings::cells().mPreloadNumThreads); + mUnrefQueue = std::make_unique(); + + mScreenCaptureOperation = new SceneUtil::AsyncScreenCaptureOperation(mWorkQueue, + new SceneUtil::WriteScreenshotToFileOperation(mCfgMgr.getScreenshotPath(), + Settings::Manager::getString("screenshot format", "General"), + Settings::Manager::getBool("notify on saved screenshot", "General") + ? std::function(ScheduleNonDialogMessageBox{}) + : std::function(IgnoreString{}))); + + mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); + + mViewer->addEventHandler(mScreenCaptureHandler); + + mL10nManager = std::make_unique(mVFS.get()); + mL10nManager->setPreferredLocales(Settings::Manager::getStringArray("preferred locales", "General"), + Settings::Manager::getBool("gmst overrides l10n", "General")); + mEnvironment.setL10nManager(*mL10nManager); + + mLuaManager = std::make_unique(mVFS.get(), mResDir / "lua_libs"); + mEnvironment.setLuaManager(*mLuaManager); + + // starts a separate lua thread if "lua num threads" > 0 + mLuaWorker = std::make_unique(*mLuaManager, *mViewer); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so - std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v3.xml").string(); - bool keybinderUserExists = boost::filesystem::exists(keybinderUser); - if(!keybinderUserExists) + const auto keybinderUser = mCfgMgr.getUserConfigPath() / "input_v3.xml"; + bool keybinderUserExists = std::filesystem::exists(keybinderUser); + if (!keybinderUserExists) { - std::string input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml").string(); - if(boost::filesystem::exists(input2)) { - boost::filesystem::copy_file(input2, keybinderUser); - keybinderUserExists = boost::filesystem::exists(keybinderUser); + const auto input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml"); + if (std::filesystem::exists(input2)) + { + keybinderUserExists = std::filesystem::copy_file(input2, keybinderUser); Log(Debug::Info) << "Loading keybindings file: " << keybinderUser; } } else Log(Debug::Info) << "Loading keybindings file: " << keybinderUser; - const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/gamecontrollerdb.txt"; - const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.txt"; - const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt"; + const auto userdefault = mCfgMgr.getUserConfigPath() / "gamecontrollerdb.txt"; + const auto localdefault = mCfgMgr.getLocalPath() / "gamecontrollerdb.txt"; + const auto globaldefault = mCfgMgr.getGlobalPath() / "gamecontrollerdb.txt"; - std::string userGameControllerdb; - if (boost::filesystem::exists(userdefault)){ + std::filesystem::path userGameControllerdb; + if (std::filesystem::exists(userdefault)) userGameControllerdb = userdefault; - } - else - userGameControllerdb = ""; - std::string gameControllerdb; - if (boost::filesystem::exists(localdefault)) + std::filesystem::path gameControllerdb; + if (std::filesystem::exists(localdefault)) gameControllerdb = localdefault; - else if (boost::filesystem::exists(globaldefault)) + else if (std::filesystem::exists(globaldefault)) gameControllerdb = globaldefault; - else - gameControllerdb = ""; //if it doesn't exist, pass in an empty string + // else if it doesn't exist, pass in an empty string + + // gui needs our shaders path before everything else + mResourceSystem->getSceneManager()->setShaderPath(mResDir / "shaders"); + + osg::ref_ptr exts = osg::GLExtensions::Get(0, false); + bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); + +#if OSG_VERSION_LESS_THAN(3, 6, 6) + // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 + if (exts) + exts->glRenderbufferStorageMultisampleCoverageNV = nullptr; +#endif - std::string myguiResources = (mResDir / "mygui").string(); osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); guiRoot->setNodeMask(MWRender::Mask_GUI); + mStereoManager->disableStereoForNode(guiRoot); rootNode->addChild(guiRoot); - MWGui::WindowManager* window = new MWGui::WindowManager(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), - mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, - mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, - Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string()); - mEnvironment.setWindowManager (window); - MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); - mEnvironment.setInputManager (input); + mWindowManager = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), + mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, + Version::getOpenmwVersionDescription(mResDir), shadersSupported, mCfgMgr); + mEnvironment.setWindowManager(*mWindowManager); + + mInputManager = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, + mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); + mEnvironment.setInputManager(*mInputManager); // Create sound system - mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound)); + mSoundManager = std::make_unique(mVFS.get(), mUseSound); + mEnvironment.setSoundManager(*mSoundManager); if (!mSkipMenu) { - const std::string& logo = Fallback::Map::getString("Movies_Company_Logo"); + std::string_view logo = Fallback::Map::getString("Movies_Company_Logo"); if (!logo.empty()) - window->playVideo(logo, true); + mWindowManager->playVideo(logo, true); } // Create the world - mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), - mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, - mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); - mEnvironment.getWorld()->setupPlayer(); + mWorld = std::make_unique(mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), *mUnrefQueue, + mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder.get(), mActivationDistanceOverride, mCellName, + mCfgMgr.getUserDataPath()); + mWorld->setupPlayer(); + mWorld->setRandomSeed(mRandomSeed); + mEnvironment.setWorld(*mWorld); + mEnvironment.setWorldModel(mWorld->getWorldModel()); + mEnvironment.setWorldScene(mWorld->getWorldScene()); + mEnvironment.setESMStore(mWorld->getStore()); - window->setStore(mEnvironment.getWorld()->getStore()); - window->initUI(); + const MWWorld::Store* gmst = &mWorld->getStore().get(); + mL10nManager->setGmstLoader( + [gmst, misses = std::set>()](std::string_view gmstName) mutable { + const ESM::GameSetting* res = gmst->search(gmstName); + if (res && res->mValue.getType() == ESM::VT_String) + return res->mValue.getString(); + else + { + if (misses.count(gmstName) == 0) + { + misses.emplace(gmstName); + Log(Debug::Error) << "GMST " << gmstName << " not found"; + } + return std::string("GMST:") + std::string(gmstName); + } + }); - //Load translation data - mTranslationDataStorage.setEncoder(mEncoder); - for (size_t i = 0; i < mContentFiles.size(); i++) - mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]); + mWindowManager->setStore(mWorld->getStore()); + mWindowManager->initUI(); - Compiler::registerExtensions (mExtensions); + // Load translation data + mTranslationDataStorage.setEncoder(mEncoder.get()); + for (auto& mContentFile : mContentFiles) + mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFile); + + Compiler::registerExtensions(mExtensions); // Create script system - mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full); - mScriptContext->setExtensions (&mExtensions); + mScriptContext = std::make_unique(MWScript::CompilerContext::Type_Full); + mScriptContext->setExtensions(&mExtensions); - mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode, - mScriptBlacklistUse ? mScriptBlacklist : std::vector())); + mScriptManager = std::make_unique(mWorld->getStore(), *mScriptContext, mWarningsMode, + mScriptBlacklistUse ? mScriptBlacklist : std::vector()); + mEnvironment.setScriptManager(*mScriptManager); // Create game mechanics system - MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager; - mEnvironment.setMechanicsManager (mechanics); + mMechanicsManager = std::make_unique(); + mEnvironment.setMechanicsManager(*mMechanicsManager); // Create dialog system - mEnvironment.setJournal (new MWDialogue::Journal); - mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage)); - mEnvironment.setResourceSystem(mResourceSystem.get()); + mJournal = std::make_unique(); + mEnvironment.setJournal(*mJournal); + + mDialogueManager = std::make_unique(mExtensions, mTranslationDataStorage); + mEnvironment.setDialogueManager(*mDialogueManager); // scripts if (mCompileAll) { - std::pair result = mEnvironment.getScriptManager()->compileAll(); + std::pair result = mScriptManager->compileAll(); if (result.first) - Log(Debug::Info) - << "compiled " << result.second << " of " << result.first << " scripts (" - << 100*static_cast (result.second)/result.first - << "%)"; + Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " scripts (" + << 100 * static_cast(result.second) / result.first << "%)"; } if (mCompileAllDialogue) { std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode); if (result.first) - Log(Debug::Info) - << "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a(" - << 100*static_cast (result.second)/result.first - << "%)"; + Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " dialogue scripts (" + << 100 * static_cast(result.second) / result.first << "%)"; } + + mLuaManager->init(); + mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath()); } -class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation -{ -public: - WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat) - : mScreenshotPath(screenshotPath) - , mScreenshotFormat(screenshotFormat) - { - } - - void operator()(const osg::Image& image, const unsigned int context_id) override - { - // Count screenshots. - int shotCount = 0; - - // Find the first unused filename with a do-while - std::ostringstream stream; - do - { - // Reset the stream - stream.str(""); - stream.clear(); - - stream << mScreenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << mScreenshotFormat; - - } while (boost::filesystem::exists(stream.str())); - - boost::filesystem::ofstream outStream; - outStream.open(boost::filesystem::path(stream.str()), std::ios::binary); - - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat); - if (!readerwriter) - { - Log(Debug::Error) << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found"; - return; - } - - osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); - if (!result.success()) - { - Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status(); - } - } - -private: - std::string mScreenshotPath; - std::string mScreenshotFormat; -}; - // Initialise and enter main loop. void OMW::Engine::go() { - assert (!mContentFiles.empty()); + assert(!mContentFiles.empty()); /* Start of tes3mp change (major) @@ -987,49 +1017,48 @@ void OMW::Engine::go() Log(Debug::Info) << "OSG version: " << osgGetVersion(); SDL_version sdlVersion; SDL_GetVersion(&sdlVersion); - Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch; + Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." + << (int)sdlVersion.patch; Misc::Rng::init(mRandomSeed); - // Load settings - Settings::Manager settings; - std::string settingspath; - settingspath = loadSettings (settings); + Settings::ShaderManager::get().load(mCfgMgr.getUserConfigPath() / "shaders.yaml"); MWClass::registerClasses(); // Create encoder - mEncoder = new ToUTF8::Utf8Encoder(mEncoding); + mEncoder = std::make_unique(mEncoding); // Setup viewer mViewer = new osgViewer::Viewer; mViewer->setReleaseContextAtEndOfFrameHint(false); -#if OSG_VERSION_GREATER_OR_EQUAL(3,5,5) // Do not try to outsmart the OS thread scheduler (see bug #4785). mViewer->setUseConfigureAffinity(false); -#endif - - mScreenCaptureOperation = new WriteScreenshotToFileOperation( - mCfgMgr.getScreenshotPath().string(), - Settings::Manager::getString("screenshot format", "General")); - - mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); - - mViewer->addEventHandler(mScreenCaptureHandler); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); - prepareEngine (settings); + prepareEngine(); + +#ifdef _WIN32 + const auto* stats_file = _wgetenv(L"OPENMW_OSG_STATS_FILE"); +#else + const auto* stats_file = std::getenv("OPENMW_OSG_STATS_FILE"); +#endif + + std::filesystem::path path; + if (stats_file != nullptr) + path = stats_file; std::ofstream stats; - if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) + if (!path.empty()) { stats.open(path, std::ios_base::out); if (stats.is_open()) - Log(Debug::Info) << "Stats will be written to: " << path; + Log(Debug::Info) << "OSG stats will be written to: " << path; else - Log(Debug::Warning) << "Failed to open file for stats: " << path; + Log(Debug::Warning) << "Failed to open file to write OSG stats \"" << path + << "\": " << std::generic_category().message(errno); } /* @@ -1053,13 +1082,13 @@ void OMW::Engine::go() */ // Setup profiler - osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open()); + osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open(), mVFS.get()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); - osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open()); + osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open(), mVFS.get()); mViewer->addEventHandler(resourceshandler); if (stats.is_open()) @@ -1068,37 +1097,37 @@ void OMW::Engine::go() // Start the game if (!mSaveGameFile.empty()) { - mEnvironment.getStateManager()->loadGame(mSaveGameFile); + mStateManager->loadGame(mSaveGameFile); } else if (!mSkipMenu) { // start in main menu - mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - mEnvironment.getSoundManager()->playTitleMusic(); - const std::string& logo = Fallback::Map::getString("Movies_Morrowind_Logo"); + mWindowManager->pushGuiMode(MWGui::GM_MainMenu); + mSoundManager->playPlaylist("Title"); + std::string_view logo = Fallback::Map::getString("Movies_Morrowind_Logo"); if (!logo.empty()) - mEnvironment.getWindowManager()->playVideo(logo, true); + mWindowManager->playVideo(logo, /*allowSkipping*/ true, /*overrideSounds*/ false); } else { - mEnvironment.getStateManager()->newGame (!mNewGame); + mStateManager->newGame(!mNewGame); } - if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running) + if (!mStartupScript.empty() && mStateManager->getState() == MWState::StateManager::State_Running) { - mEnvironment.getWindowManager()->executeInConsole(mStartupScript); + mWindowManager->executeInConsole(mStartupScript); } // Start the main rendering loop double simulationTime = 0.0; Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); - while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) + while (!mViewer->done() && !mStateManager->hasQuitRequest()) { - const double dt = std::chrono::duration_cast>(std::min( - frameRateLimiter.getLastFrameDuration(), - maxSimulationInterval - )).count(); + const double dt = std::chrono::duration_cast>( + std::min(frameRateLimiter.getLastFrameDuration(), maxSimulationInterval)) + .count() + * mEnvironment.getWorld()->getSimulationTimeScale(); mViewer->advance(simulationTime); @@ -1109,6 +1138,7 @@ void OMW::Engine::go() } else { +<<<<<<< HEAD mViewer->eventTraversal(); mViewer->updateTraversal(); @@ -1129,40 +1159,47 @@ void OMW::Engine::go() End of tes3mp change (major) */ +======= + bool guiActive = mWindowManager->isGuiMode(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (!guiActive) simulationTime += dt; } if (stats) { + constexpr unsigned statsReportDelay = 3; const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber(); - if (frameNumber >= 2) + if (frameNumber >= statsReportDelay) { - mViewer->getViewerStats()->report(stats, frameNumber - 2); + const unsigned reportFrameNumber = frameNumber - statsReportDelay; + mViewer->getViewerStats()->report(stats, reportFrameNumber); osgViewer::Viewer::Cameras cameras; mViewer->getCameras(cameras); for (auto camera : cameras) - camera->getStats()->report(stats, frameNumber - 2); + camera->getStats()->report(stats, reportFrameNumber); } } frameRateLimiter.limit(); } - // Save user settings - settings.saveUser(settingspath); + mLuaWorker->join(); - mViewer->stopThreading(); + // Save user settings + Settings::Manager::saveUser(mCfgMgr.getUserConfigPath() / "settings.cfg"); + Settings::ShaderManager::get().save(); + mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath()); Log(Debug::Info) << "Quitting peacefully."; } -void OMW::Engine::setCompileAll (bool all) +void OMW::Engine::setCompileAll(bool all) { mCompileAll = all; } -void OMW::Engine::setCompileAllDialogue (bool all) +void OMW::Engine::setCompileAllDialogue(bool all) { mCompileAllDialogue = all; } @@ -1177,42 +1214,37 @@ void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding) mEncoding = encoding; } -void OMW::Engine::setScriptConsoleMode (bool enabled) +void OMW::Engine::setScriptConsoleMode(bool enabled) { mScriptConsoleMode = enabled; } -void OMW::Engine::setStartupScript (const std::string& path) +void OMW::Engine::setStartupScript(const std::filesystem::path& path) { mStartupScript = path; } -void OMW::Engine::setActivationDistanceOverride (int distance) +void OMW::Engine::setActivationDistanceOverride(int distance) { mActivationDistanceOverride = distance; } -void OMW::Engine::setWarningsMode (int mode) +void OMW::Engine::setWarningsMode(int mode) { mWarningsMode = mode; } -void OMW::Engine::setScriptBlacklist (const std::vector& list) +void OMW::Engine::setScriptBlacklist(const std::vector& list) { mScriptBlacklist = list; } -void OMW::Engine::setScriptBlacklistUse (bool use) +void OMW::Engine::setScriptBlacklistUse(bool use) { mScriptBlacklistUse = use; } -void OMW::Engine::enableFontExport(bool exportFonts) -{ - mExportFonts = exportFonts; -} - -void OMW::Engine::setSaveGameFile(const std::string &savegame) +void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame) { mSaveGameFile = savegame; } diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 1aef62df5..2cd224785 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -1,18 +1,19 @@ #ifndef ENGINE_H #define ENGINE_H +#include + #include +#include #include -#include #include +#include #include #include #include "mwbase/environment.hpp" -#include "mwworld/ptr.hpp" - namespace Resource { class ResourceSystem; @@ -21,6 +22,8 @@ namespace Resource namespace SceneUtil { class WorkQueue; + class AsyncScreenCaptureOperation; + class UnrefQueue; } namespace VFS @@ -33,6 +36,17 @@ namespace Compiler class Context; } +namespace MWLua +{ + class LuaManager; + class Worker; +} + +namespace Stereo +{ + class Manager; +} + namespace Files { struct ConfigurationManager; @@ -43,6 +57,66 @@ namespace osgViewer class ScreenCaptureHandler; } +namespace SceneUtil +{ + class SelectDepthFormatOperation; + + namespace Color + { + class SelectColorFormatOperation; + } +} + +namespace MWState +{ + class StateManager; +} + +namespace MWGui +{ + class WindowManager; +} + +namespace MWInput +{ + class InputManager; +} + +namespace MWSound +{ + class SoundManager; +} + +namespace MWWorld +{ + class World; +} + +namespace MWScript +{ + class ScriptManager; +} + +namespace MWMechanics +{ + class MechanicsManager; +} + +namespace MWDialogue +{ + class DialogueManager; +} + +namespace MWDialogue +{ + class Journal; +} + +namespace l10n +{ + class Manager; +} + struct SDL_Window; namespace OMW @@ -50,141 +124,147 @@ namespace OMW /// \brief Main engine class, that brings together all the components of OpenMW class Engine { - SDL_Window* mWindow; - std::unique_ptr mVFS; - std::unique_ptr mResourceSystem; - osg::ref_ptr mWorkQueue; - MWBase::Environment mEnvironment; - ToUTF8::FromType mEncoding; - ToUTF8::Utf8Encoder* mEncoder; - Files::PathContainer mDataDirs; - std::vector mArchives; - boost::filesystem::path mResDir; - osg::ref_ptr mViewer; - osg::ref_ptr mScreenCaptureHandler; - osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; - std::string mCellName; - std::vector mContentFiles; - std::vector mGroundcoverFiles; - bool mSkipMenu; - bool mUseSound; - bool mCompileAll; - bool mCompileAllDialogue; - int mWarningsMode; - std::string mFocusName; - bool mScriptConsoleMode; - std::string mStartupScript; - int mActivationDistanceOverride; - std::string mSaveGameFile; - // Grab mouse? - bool mGrab; + SDL_Window* mWindow; + std::unique_ptr mVFS; + std::unique_ptr mResourceSystem; + osg::ref_ptr mWorkQueue; + std::unique_ptr mUnrefQueue; + std::unique_ptr mWorld; + std::unique_ptr mSoundManager; + std::unique_ptr mScriptManager; + std::unique_ptr mWindowManager; + std::unique_ptr mMechanicsManager; + std::unique_ptr mDialogueManager; + std::unique_ptr mJournal; + std::unique_ptr mInputManager; + std::unique_ptr mStateManager; + std::unique_ptr mLuaManager; + std::unique_ptr mLuaWorker; + std::unique_ptr mL10nManager; + MWBase::Environment mEnvironment; + ToUTF8::FromType mEncoding; + std::unique_ptr mEncoder; + Files::PathContainer mDataDirs; + std::vector mArchives; + std::filesystem::path mResDir; + osg::ref_ptr mViewer; + osg::ref_ptr mScreenCaptureHandler; + osg::ref_ptr mScreenCaptureOperation; + osg::ref_ptr mSelectDepthFormatOperation; + osg::ref_ptr mSelectColorFormatOperation; + std::string mCellName; + std::vector mContentFiles; + std::vector mGroundcoverFiles; - bool mExportFonts; - unsigned int mRandomSeed; + std::unique_ptr mStereoManager; - Compiler::Extensions mExtensions; - Compiler::Context *mScriptContext; + bool mSkipMenu; + bool mUseSound; + bool mCompileAll; + bool mCompileAllDialogue; + int mWarningsMode; + std::string mFocusName; + bool mScriptConsoleMode; + std::filesystem::path mStartupScript; + int mActivationDistanceOverride; + std::filesystem::path mSaveGameFile; + // Grab mouse? + bool mGrab; - Files::Collections mFileCollections; - bool mFSStrict; - Translation::Storage mTranslationDataStorage; - std::vector mScriptBlacklist; - bool mScriptBlacklistUse; - bool mNewGame; + unsigned int mRandomSeed; - // not implemented - Engine (const Engine&); - Engine& operator= (const Engine&); + Compiler::Extensions mExtensions; + std::unique_ptr mScriptContext; - void executeLocalScripts(); + Files::Collections mFileCollections; + Translation::Storage mTranslationDataStorage; + std::vector mScriptBlacklist; + bool mScriptBlacklistUse; + bool mNewGame; - bool frame (float dt); + // not implemented + Engine(const Engine&); + Engine& operator=(const Engine&); - /// Load settings from various files, returns the path to the user settings file - std::string loadSettings (Settings::Manager & settings); + void executeLocalScripts(); - /// Prepare engine for game play - void prepareEngine (Settings::Manager & settings); + bool frame(float dt); - void createWindow(Settings::Manager& settings); - void setWindowIcon(); + /// Prepare engine for game play + void prepareEngine(); - public: - Engine(Files::ConfigurationManager& configurationManager); - virtual ~Engine(); + void createWindow(); + void setWindowIcon(); - /// Enable strict filesystem mode (do not fold case) - /// - /// \attention The strict mode must be specified before any path-related settings - /// are given to the engine. - void enableFSStrict(bool fsStrict); + public: + Engine(Files::ConfigurationManager& configurationManager); + virtual ~Engine(); - /// Set data dirs - void setDataDirs(const Files::PathContainer& dataDirs); + /// Set data dirs + void setDataDirs(const Files::PathContainer& dataDirs); - /// Add BSA archive - void addArchive(const std::string& archive); + /// Add BSA archive + void addArchive(const std::string& archive); - /// Set resource dir - void setResourceDir(const boost::filesystem::path& parResDir); + /// Set resource dir + void setResourceDir(const std::filesystem::path& parResDir); - /// Set start cell name - void setCell(const std::string& cellName); + /// Set start cell name + void setCell(const std::string& cellName); - /** - * @brief addContentFile - Adds content file (ie. esm/esp, or omwgame/omwaddon) to the content files container. - * @param file - filename (extension is required) - */ - void addContentFile(const std::string& file); - void addGroundcoverFile(const std::string& file); + /** + * @brief addContentFile - Adds content file (ie. esm/esp, or omwgame/omwaddon) to the content files container. + * @param file - filename (extension is required) + */ + void addContentFile(const std::string& file); + void addGroundcoverFile(const std::string& file); - /// Disable or enable all sounds - void setSoundUsage(bool soundUsage); + /// Disable or enable all sounds + void setSoundUsage(bool soundUsage); - /// Skip main menu and go directly into the game - /// - /// \param newGame Start a new game instead off dumping the player into the game - /// (ignored if !skipMenu). - void setSkipMenu (bool skipMenu, bool newGame); + /// Skip main menu and go directly into the game + /// + /// \param newGame Start a new game instead off dumping the player into the game + /// (ignored if !skipMenu). + void setSkipMenu(bool skipMenu, bool newGame); - void setGrabMouse(bool grab) { mGrab = grab; } + void setGrabMouse(bool grab) { mGrab = grab; } - /// Initialise and enter main loop. - void go(); + /// Initialise and enter main loop. + void go(); - /// Compile all scripts (excludign dialogue scripts) at startup? - void setCompileAll (bool all); + /// Compile all scripts (excludign dialogue scripts) at startup? + void setCompileAll(bool all); - /// Compile all dialogue scripts at startup? - void setCompileAllDialogue (bool all); + /// Compile all dialogue scripts at startup? + void setCompileAllDialogue(bool all); - /// Font encoding - void setEncoding(const ToUTF8::FromType& encoding); + /// Font encoding + void setEncoding(const ToUTF8::FromType& encoding); - /// Enable console-only script functionality - void setScriptConsoleMode (bool enabled); + /// Enable console-only script functionality + void setScriptConsoleMode(bool enabled); - /// Set path for a script that is run on startup in the console. - void setStartupScript (const std::string& path); + /// Set path for a script that is run on startup in the console. + void setStartupScript(const std::filesystem::path& path); - /// Override the game setting specified activation distance. - void setActivationDistanceOverride (int distance); + /// Override the game setting specified activation distance. + void setActivationDistanceOverride(int distance); - void setWarningsMode (int mode); + void setWarningsMode(int mode); - void setScriptBlacklist (const std::vector& list); + void setScriptBlacklist(const std::vector& list); - void setScriptBlacklistUse (bool use); + void setScriptBlacklistUse(bool use); - void enableFontExport(bool exportFonts); + /// Set the save game file to load after initialising the engine. + void setSaveGameFile(const std::filesystem::path& savegame); - /// Set the save game file to load after initialising the engine. - void setSaveGameFile(const std::string& savegame); + void setRandomSeed(unsigned int seed); - void setRandomSeed(unsigned int seed); - - private: - Files::ConfigurationManager& mCfgMgr; + private: + Files::ConfigurationManager& mCfgMgr; + int mGlMaxTextureImageUnits; }; } diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index b71833a4c..da5f92d83 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -1,12 +1,17 @@ -#include -#include -#include +#include #include #include -#include +#include #include +#include +#include + +#include "mwgui/debugwindow.hpp" #include "engine.hpp" +#include "options.hpp" + +#include /* Start of tes3mp addition @@ -19,14 +24,15 @@ */ #if defined(_WIN32) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include +#include // makes __argc and __argv available on windows #include + +extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; #endif +#include + #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) #include #endif @@ -56,104 +62,13 @@ using namespace Fallback; * \retval true - Everything goes OK * \retval false - Error */ -bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::ConfigurationManager& cfgMgr) +bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::ConfigurationManager& cfgMgr) { // Create a local alias for brevity namespace bpo = boost::program_options; typedef std::vector StringsVector; - bpo::options_description desc("Syntax: openmw \nAllowed options"); - - desc.add_options() - ("help", "print help message") - ("version", "print version information and quit") - - ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") - - ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") - ->multitoken()->composing(), "set data directories (later directories have higher priority)") - - ("data-local", bpo::value()->default_value(Files::EscapePath(), ""), - "set local data directory (highest priority)") - - ("fallback-archive", bpo::value()->default_value(Files::EscapeStringVector(), "fallback-archive") - ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") - - ("resources", bpo::value()->default_value(Files::EscapePath(), "resources"), - "set resources directory") - - ("start", bpo::value()->default_value(""), - "set initial cell") - - ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") - - ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") - - ("no-sound", bpo::value()->implicit_value(true) - ->default_value(false), "disable all sounds") - - ("script-all", bpo::value()->implicit_value(true) - ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") - - ("script-all-dialogue", bpo::value()->implicit_value(true) - ->default_value(false), "compile all dialogue scripts at startup") - - ("script-console", bpo::value()->implicit_value(true) - ->default_value(false), "enable console-only script functionality") - - ("script-run", bpo::value()->default_value(""), - "select a file containing a list of console commands that is executed on startup") - - ("script-warn", bpo::value()->implicit_value (1) - ->default_value (1), - "handling of warnings when compiling scripts\n" - "\t0 - ignore warning\n" - "\t1 - show warning but consider script as correctly compiled anyway\n" - "\t2 - treat warnings as errors") - - ("script-blacklist", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)") - - ("script-blacklist-use", bpo::value()->implicit_value(true) - ->default_value(true), "enable script blacklisting") - - ("load-savegame", bpo::value()->default_value(Files::EscapePath(), ""), - "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") - - ("skip-menu", bpo::value()->implicit_value(true) - ->default_value(false), "skip main menu on game startup") - - ("new-game", bpo::value()->implicit_value(true) - ->default_value(false), "run new game sequence (ignored if skip-menu=0)") - - ("fs-strict", bpo::value()->implicit_value(true) - ->default_value(false), "strict file system handling (no case folding)") - - ("encoding", bpo::value()-> - default_value("win1252"), - "Character encoding used in OpenMW game messages:\n" - "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" - "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" - "\n\twin1252 - Western European (Latin) alphabet, used by default") - - ("fallback", bpo::value()->default_value(FallbackMap(), "") - ->multitoken()->composing(), "fallback values") - - ("no-grab", bpo::value()->implicit_value(true)->default_value(false), "Don't grab mouse cursor") - - ("export-fonts", bpo::value()->implicit_value(true) - ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") - - ("activate-dist", bpo::value ()->default_value (-1), "activation distance override") - - ("random-seed", bpo::value () - ->default_value(Misc::Rng::generateDefaultSeed()), - "seed value for random number generator") - ; - + bpo::options_description desc = OpenMW::makeOptionsDescription(); /* Start of tes3mp addition @@ -163,36 +78,40 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat /* End of tes3mp addition */ - - bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) - .options(desc).allow_unregistered().run(); - bpo::variables_map variables; - // Runtime options override settings from all configs - bpo::store(valid_opts, variables); + Files::parseArgs(argc, argv, variables, desc); bpo::notify(variables); - if (variables.count ("help")) + if (variables.count("help")) { getRawStdout() << desc << std::endl; return false; } - if (variables.count ("version")) + if (variables.count("version")) { cfgMgr.readConfiguration(variables, desc, true); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); + Version::Version v + = Version::getOpenmwVersion(variables["resources"] + .as() + .u8string()); // This call to u8string is redundant, but required to build + // on MSVC 14.26 due to implementation bugs. getRawStdout() << v.describe() << std::endl; return false; } - bpo::variables_map composingVariables = cfgMgr.separateComposingVariables(variables, desc); cfgMgr.readConfiguration(variables, desc); - cfgMgr.mergeComposingVariables(variables, composingVariables, desc); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); + setupLogging(cfgMgr.getLogPath(), "OpenMW"); + + Version::Version v + = Version::getOpenmwVersion(variables["resources"] + .as() + .u8string()); // This call to u8string is redundant, but required to build on + // MSVC 14.26 due to implementation bugs. + Log(Debug::Info) << v.describe(); /* Start of tes3mp addition @@ -214,35 +133,42 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat End of tes3mp change (minor) */ + Settings::Manager::load(cfgMgr); + + MWGui::DebugWindow::startLogRecording(); + engine.setGrabMouse(!variables["no-grab"].as()); // Font encoding settings - std::string encoding(variables["encoding"].as().toStdString()); + std::string encoding(variables["encoding"].as()); Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); engine.setEncoding(ToUTF8::calculateEncoding(encoding)); - // directory settings - engine.enableFSStrict(variables["fs-strict"].as()); + Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); - Files::PathContainer dataDirs(Files::EscapePath::toPathContainer(variables["data"].as())); - - Files::PathContainer::value_type local(variables["data-local"].as().mPath); + Files::PathContainer::value_type local(variables["data-local"] + .as() + .u8string()); // This call to u8string is redundant, but required to + // build on MSVC 14.26 due to implementation bugs. if (!local.empty()) dataDirs.push_back(local); - cfgMgr.processPaths(dataDirs); + cfgMgr.filterOutNonExistingPaths(dataDirs); - engine.setResourceDir(variables["resources"].as().mPath); + engine.setResourceDir(variables["resources"] + .as() + .u8string()); // This call to u8string is redundant, but required to build on MSVC 14.26 + // due to implementation bugs. engine.setDataDirs(dataDirs); // fallback archives - StringsVector archives = variables["fallback-archive"].as().toStdStringVector(); + StringsVector archives = variables["fallback-archive"].as(); for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it) { engine.addArchive(*it); } - StringsVector content = variables["content"].as().toStdStringVector(); + StringsVector content = variables["content"].as(); if (content.empty()) { Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; @@ -263,36 +189,46 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.addContentFile(file); } - StringsVector groundcover = variables["groundcover"].as().toStdStringVector(); + StringsVector groundcover = variables["groundcover"].as(); for (auto& file : groundcover) { engine.addGroundcoverFile(file); } + if (variables.count("lua-scripts")) + { + Log(Debug::Warning) << "Lua scripts have been specified via the old lua-scripts option and will not be loaded. " + "Please update them to a version which uses the new omwscripts format."; + } + // startup-settings - engine.setCell(variables["start"].as().toStdString()); - engine.setSkipMenu (variables["skip-menu"].as(), variables["new-game"].as()); + engine.setCell(variables["start"].as()); + engine.setSkipMenu(variables["skip-menu"].as(), variables["new-game"].as()); if (!variables["skip-menu"].as() && variables["new-game"].as()) Log(Debug::Warning) << "Warning: new-game used without skip-menu -> ignoring it"; // scripts engine.setCompileAll(variables["script-all"].as()); engine.setCompileAllDialogue(variables["script-all-dialogue"].as()); - engine.setScriptConsoleMode (variables["script-console"].as()); - + engine.setScriptConsoleMode(variables["script-console"].as()); + /* Start of tes3mp change (major) Clients should not be allowed to set any of these unilaterally in multiplayer, so disable them */ - /* - engine.setStartupScript (variables["script-run"].as().toStdString()); - engine.setWarningsMode (variables["script-warn"].as()); - engine.setScriptBlacklist (variables["script-blacklist"].as().toStdStringVector()); - engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); - engine.setSaveGameFile (variables["load-savegame"].as().mPath.string()); - */ + // engine.setStartupScript(variables["script-run"].as()); + // engine.setWarningsMode(variables["script-warn"].as()); + // std::vector scriptBlacklist; + // auto& scriptBlacklistString = variables["script-blacklist"].as(); + // for (const auto& blacklistString : scriptBlacklistString) + // { + // scriptBlacklist.push_back(ESM::RefId::stringRefId(blacklistString)); + // } + // engine.setScriptBlacklist(scriptBlacklist); + // engine.setScriptBlacklistUse(variables["script-blacklist-use"].as()); + // engine.setSaveGameFile(variables["load-savegame"].as().u8string()); /* End of tes3mp change (major) */ @@ -300,8 +236,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // other settings Fallback::Map::init(variables["fallback"].as().mMap); engine.setSoundUsage(!variables["no-sound"].as()); - engine.setActivationDistanceOverride (variables["activate-dist"].as()); - engine.enableFontExport(variables["export-fonts"].as()); + engine.setActivationDistanceOverride(variables["activate-dist"].as()); engine.setRandomSeed(variables["random-seed"].as()); /* @@ -331,21 +266,21 @@ namespace Debug::Level level; switch (severity) { - case osg::ALWAYS: - case osg::FATAL: - level = Debug::Error; - break; - case osg::WARN: - case osg::NOTICE: - level = Debug::Warning; - break; - case osg::INFO: - level = Debug::Info; - break; - case osg::DEBUG_INFO: - case osg::DEBUG_FP: - default: - level = Debug::Debug; + case osg::ALWAYS: + case osg::FATAL: + level = Debug::Error; + break; + case osg::WARN: + case osg::NOTICE: + level = Debug::Warning; + break; + case osg::INFO: + level = Debug::Info; + break; + case osg::DEBUG_INFO: + case osg::DEBUG_FP: + default: + level = Debug::Debug; } std::string_view s(msgCopy); if (s.size() < 1024) @@ -365,18 +300,19 @@ namespace }; } -int runApplication(int argc, char *argv[]) +int runApplication(int argc, char* argv[]) { + Platform::init(); + #ifdef __APPLE__ - boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0])); - boost::filesystem::current_path(binary_path.parent_path()); + std::filesystem::path binary_path = std::filesystem::absolute(std::filesystem::path(argv[0])); + std::filesystem::current_path(binary_path.parent_path()); setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); #endif osg::setNotifyHandler(new OSGLogHandler()); Files::ConfigurationManager cfgMgr; - std::unique_ptr engine; - engine.reset(new OMW::Engine(cfgMgr)); + std::unique_ptr engine = std::make_unique(cfgMgr); if (parseOptions(argc, argv, *engine, cfgMgr)) { @@ -387,9 +323,9 @@ int runApplication(int argc, char *argv[]) } #ifdef ANDROID -extern "C" int SDL_main(int argc, char**argv) +extern "C" int SDL_main(int argc, char** argv) #else -int main(int argc, char**argv) +int main(int argc, char** argv) #endif { /* diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 8fd4f3a9d..1e714e1a5 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -1,11 +1,12 @@ #ifndef GAME_MWBASE_DIALOGUEMANAGER_H #define GAME_MWBASE_DIALOGUEMANAGER_H -#include -#include #include +#include +#include +#include -#include +#include namespace Loading { @@ -16,6 +17,7 @@ namespace ESM { class ESMReader; class ESMWriter; + class RefId; } namespace MWWorld @@ -28,124 +30,122 @@ namespace MWBase /// \brief Interface for dialogue manager (implemented in MWDialogue) class DialogueManager { - DialogueManager (const DialogueManager&); - ///< not implemented + DialogueManager(const DialogueManager&); + ///< not implemented - DialogueManager& operator= (const DialogueManager&); - ///< not implemented + DialogueManager& operator=(const DialogueManager&); + ///< not implemented + public: + class ResponseCallback + { public: + virtual ~ResponseCallback() = default; + virtual void addResponse(std::string_view title, std::string_view text) = 0; + }; - class ResponseCallback - { - public: - virtual ~ResponseCallback() = default; - virtual void addResponse(const std::string& title, const std::string& text) = 0; - }; + DialogueManager() {} - DialogueManager() {} + virtual void clear() = 0; - virtual void clear() = 0; + virtual ~DialogueManager() {} - virtual ~DialogueManager() {} + virtual bool isInChoice() const = 0; - virtual bool isInChoice() const = 0; + virtual bool startDialogue(const MWWorld::Ptr& actor, ResponseCallback* callback) = 0; - virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) = 0; + virtual bool inJournal(const ESM::RefId& topicId, const ESM::RefId& infoId) const = 0; - virtual bool inJournal (const std::string& topicId, const std::string& infoId) = 0; + virtual void addTopic(const ESM::RefId& topic) = 0; - virtual void addTopic (const std::string& topic) = 0; + virtual void addChoice(std::string_view text, int choice) = 0; + virtual const std::vector>& getChoices() const = 0; - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to check whether a topic is known by the player from elsewhere - in the code - */ - virtual bool isNewTopic(const std::string& topic) = 0; - /* - End of tes3mp addition - */ + Make it possible to check whether a topic is known by the player from elsewhere + in the code + */ + virtual bool isNewTopic(const std::string& topic) = 0; + /* + End of tes3mp addition + */ - virtual void addChoice (const std::string& text,int choice) = 0; - virtual const std::vector >& getChoices() = 0; + virtual bool isGoodbye() const = 0; - virtual bool isGoodbye() = 0; + virtual void goodbye() = 0; - virtual void goodbye() = 0; + virtual void say(const MWWorld::Ptr& actor, const ESM::RefId& topic) = 0; - virtual void say(const MWWorld::Ptr &actor, const std::string &topic) = 0; + virtual void keywordSelected(std::string_view keyword, ResponseCallback* callback) = 0; + virtual void goodbyeSelected() = 0; + virtual void questionAnswered(int answer, ResponseCallback* callback) = 0; - virtual void keywordSelected (const std::string& keyword, ResponseCallback* callback) = 0; - virtual void goodbyeSelected() = 0; - virtual void questionAnswered (int answer, ResponseCallback* callback) = 0; + enum TopicType + { + Specific = 1, + Exhausted = 2 + }; - enum TopicType - { - Specific = 1, - Exhausted = 2 - }; + enum ServiceType + { + Any = -1, + Barter = 1, + Repair = 2, + Spells = 3, + Training = 4, + Travel = 5, + Spellmaking = 6, + Enchanting = 7 + }; - enum ServiceType - { - Any = -1, - Barter = 1, - Repair = 2, - Spells = 3, - Training = 4, - Travel = 5, - Spellmaking = 6, - Enchanting = 7 - }; + virtual std::list getAvailableTopics() = 0; + virtual int getTopicFlag(const ESM::RefId&) const = 0; - virtual std::list getAvailableTopics() = 0; - virtual int getTopicFlag(const std::string&) = 0; + virtual bool checkServiceRefused(ResponseCallback* callback, ServiceType service = ServiceType::Any) = 0; - virtual bool checkServiceRefused (ResponseCallback* callback, ServiceType service = ServiceType::Any) = 0; + virtual void persuade(int type, ResponseCallback* callback) = 0; - virtual void persuade (int type, ResponseCallback* callback) = 0; - virtual int getTemporaryDispositionChange () const = 0; + /// @note Controlled by an option, gets discarded when dialogue ends by default + virtual void applyBarterDispositionChange(int delta) = 0; - /// @note Controlled by an option, gets discarded when dialogue ends by default - virtual void applyBarterDispositionChange (int delta) = 0; + virtual int countSavedGameRecords() const = 0; - virtual int countSavedGameRecords() const = 0; + virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; - virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; + virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; - virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; + /// Changes faction1's opinion of faction2 by \a diff. + virtual void modFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int diff) = 0; - /// Changes faction1's opinion of faction2 by \a diff. - virtual void modFactionReaction (const std::string& faction1, const std::string& faction2, int diff) = 0; + virtual void setFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int absolute) = 0; - virtual void setFactionReaction (const std::string& faction1, const std::string& faction2, int absolute) = 0; + /// @return faction1's opinion of faction2 + virtual int getFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2) const = 0; - /// @return faction1's opinion of faction2 - virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const = 0; + /* + Start of tes3mp addition - /// Removes the last added topic response for the given actor from the journal - virtual void clearInfoActor (const MWWorld::Ptr& actor) const = 0; + Declare this method here so it can be used from outside of MWDialogue::DialogueManager + */ + virtual void updateActorKnownTopics() = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Declare this method here so it can be used from outside of MWDialogue::DialogueManager - */ - virtual void updateActorKnownTopics() = 0; - /* - End of tes3mp addition - */ + Make it possible to get the caption of a voice dialogue + */ + virtual std::string getVoiceCaption(const std::string& sound) const = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition - - Make it possible to get the caption of a voice dialogue - */ - virtual std::string getVoiceCaption(const std::string& sound) const = 0; - /* - End of tes3mp addition - */ + /// Removes the last added topic response for the given actor from the journal + virtual void clearInfoActor(const MWWorld::Ptr& actor) const = 0; }; } diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index b7235edd4..f2393f21c 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -4,200 +4,15 @@ #include -#include "world.hpp" -#include "scriptmanager.hpp" -#include "dialoguemanager.hpp" -#include "journal.hpp" -#include "soundmanager.hpp" -#include "mechanicsmanager.hpp" -#include "inputmanager.hpp" -#include "windowmanager.hpp" -#include "statemanager.hpp" - -MWBase::Environment *MWBase::Environment::sThis = nullptr; +MWBase::Environment* MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), - mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), - mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { - assert (!sThis); + assert(sThis == nullptr); sThis = this; } MWBase::Environment::~Environment() { - cleanup(); sThis = nullptr; } - -void MWBase::Environment::setWorld (World *world) -{ - mWorld = world; -} - -void MWBase::Environment::setSoundManager (SoundManager *soundManager) -{ - mSoundManager = soundManager; -} - -void MWBase::Environment::setScriptManager (ScriptManager *scriptManager) -{ - mScriptManager = scriptManager; -} - -void MWBase::Environment::setWindowManager (WindowManager *windowManager) -{ - mWindowManager = windowManager; -} - -void MWBase::Environment::setMechanicsManager (MechanicsManager *mechanicsManager) -{ - mMechanicsManager = mechanicsManager; -} - -void MWBase::Environment::setDialogueManager (DialogueManager *dialogueManager) -{ - mDialogueManager = dialogueManager; -} - -void MWBase::Environment::setJournal (Journal *journal) -{ - mJournal = journal; -} - -void MWBase::Environment::setInputManager (InputManager *inputManager) -{ - mInputManager = inputManager; -} - -void MWBase::Environment::setStateManager (StateManager *stateManager) -{ - mStateManager = stateManager; -} - -void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem) -{ - mResourceSystem = resourceSystem; -} - -void MWBase::Environment::setFrameDuration (float duration) -{ - mFrameDuration = duration; -} - -void MWBase::Environment::setFrameRateLimit(float limit) -{ - mFrameRateLimit = limit; -} - -float MWBase::Environment::getFrameRateLimit() const -{ - return mFrameRateLimit; -} - -MWBase::World *MWBase::Environment::getWorld() const -{ - assert (mWorld); - return mWorld; -} - -MWBase::SoundManager *MWBase::Environment::getSoundManager() const -{ - assert (mSoundManager); - return mSoundManager; -} - -MWBase::ScriptManager *MWBase::Environment::getScriptManager() const -{ - assert (mScriptManager); - return mScriptManager; -} - -MWBase::WindowManager *MWBase::Environment::getWindowManager() const -{ - assert (mWindowManager); - return mWindowManager; -} - -MWBase::MechanicsManager *MWBase::Environment::getMechanicsManager() const -{ - assert (mMechanicsManager); - return mMechanicsManager; -} - -MWBase::DialogueManager *MWBase::Environment::getDialogueManager() const -{ - assert (mDialogueManager); - return mDialogueManager; -} - -MWBase::Journal *MWBase::Environment::getJournal() const -{ - assert (mJournal); - return mJournal; -} - -MWBase::InputManager *MWBase::Environment::getInputManager() const -{ - assert (mInputManager); - return mInputManager; -} - -MWBase::StateManager *MWBase::Environment::getStateManager() const -{ - assert (mStateManager); - return mStateManager; -} - -Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const -{ - return mResourceSystem; -} - -float MWBase::Environment::getFrameDuration() const -{ - return mFrameDuration; -} - -void MWBase::Environment::cleanup() -{ - delete mMechanicsManager; - mMechanicsManager = nullptr; - - delete mDialogueManager; - mDialogueManager = nullptr; - - delete mJournal; - mJournal = nullptr; - - delete mScriptManager; - mScriptManager = nullptr; - - delete mWindowManager; - mWindowManager = nullptr; - - delete mWorld; - mWorld = nullptr; - - delete mSoundManager; - mSoundManager = nullptr; - - delete mInputManager; - mInputManager = nullptr; - - delete mStateManager; - mStateManager = nullptr; -} - -const MWBase::Environment& MWBase::Environment::get() -{ - assert (sThis); - return *sThis; -} - -void MWBase::Environment::reportStats(unsigned int frameNumber, osg::Stats& stats) const -{ - mMechanicsManager->reportStats(frameNumber, stats); - mWorld->reportStats(frameNumber, stats); -} diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 3b57e4e7c..aa8a41b7c 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -1,16 +1,27 @@ #ifndef GAME_BASE_ENVIRONMENT_H #define GAME_BASE_ENVIRONMENT_H -namespace osg -{ - class Stats; -} +#include + +#include namespace Resource { class ResourceSystem; } +namespace l10n +{ + class Manager; +} + +namespace MWWorld +{ + class ESMStore; + class WorldModel; + class Scene; +} + namespace MWBase { class World; @@ -22,97 +33,111 @@ namespace MWBase class InputManager; class WindowManager; class StateManager; + class LuaManager; /// \brief Central hub for mw-subsystems /// /// This class allows each mw-subsystem to access any others subsystem's top-level manager class. /// - /// \attention Environment takes ownership of the manager class instances it is handed over in - /// the set* functions. class Environment { - static Environment *sThis; + static Environment* sThis; - World *mWorld; - SoundManager *mSoundManager; - ScriptManager *mScriptManager; - WindowManager *mWindowManager; - MechanicsManager *mMechanicsManager; - DialogueManager *mDialogueManager; - Journal *mJournal; - InputManager *mInputManager; - StateManager *mStateManager; - Resource::ResourceSystem *mResourceSystem; - float mFrameDuration; - float mFrameRateLimit; + World* mWorld = nullptr; + MWWorld::WorldModel* mWorldModel = nullptr; + MWWorld::Scene* mWorldScene = nullptr; + MWWorld::ESMStore* mESMStore = nullptr; + SoundManager* mSoundManager = nullptr; + ScriptManager* mScriptManager = nullptr; + WindowManager* mWindowManager = nullptr; + MechanicsManager* mMechanicsManager = nullptr; + DialogueManager* mDialogueManager = nullptr; + Journal* mJournal = nullptr; + InputManager* mInputManager = nullptr; + StateManager* mStateManager = nullptr; + LuaManager* mLuaManager = nullptr; + Resource::ResourceSystem* mResourceSystem = nullptr; + l10n::Manager* mL10nManager = nullptr; + float mFrameRateLimit = 0; + float mFrameDuration = 0; - Environment (const Environment&); - ///< not implemented + public: + Environment(); - Environment& operator= (const Environment&); - ///< not implemented + ~Environment(); - public: + Environment(const Environment&) = delete; - Environment(); + Environment& operator=(const Environment&) = delete; - ~Environment(); + void setWorld(World& value) { mWorld = &value; } + void setWorldModel(MWWorld::WorldModel& value) { mWorldModel = &value; } + void setWorldScene(MWWorld::Scene& value) { mWorldScene = &value; } + void setESMStore(MWWorld::ESMStore& value) { mESMStore = &value; } - void setWorld (World *world); + void setSoundManager(SoundManager& value) { mSoundManager = &value; } - void setSoundManager (SoundManager *soundManager); + void setScriptManager(ScriptManager& value) { mScriptManager = &value; } - void setScriptManager (MWBase::ScriptManager *scriptManager); + void setWindowManager(WindowManager& value) { mWindowManager = &value; } - void setWindowManager (WindowManager *windowManager); + void setMechanicsManager(MechanicsManager& value) { mMechanicsManager = &value; } - void setMechanicsManager (MechanicsManager *mechanicsManager); + void setDialogueManager(DialogueManager& value) { mDialogueManager = &value; } - void setDialogueManager (DialogueManager *dialogueManager); + void setJournal(Journal& value) { mJournal = &value; } - void setJournal (Journal *journal); + void setInputManager(InputManager& value) { mInputManager = &value; } - void setInputManager (InputManager *inputManager); + void setStateManager(StateManager& value) { mStateManager = &value; } - void setStateManager (StateManager *stateManager); + void setLuaManager(LuaManager& value) { mLuaManager = &value; } - void setResourceSystem (Resource::ResourceSystem *resourceSystem); + void setResourceSystem(Resource::ResourceSystem& value) { mResourceSystem = &value; } - void setFrameDuration (float duration); - ///< Set length of current frame in seconds. + void setL10nManager(l10n::Manager& value) { mL10nManager = &value; } - void setFrameRateLimit(float frameRateLimit); - float getFrameRateLimit() const; + Misc::NotNullPtr getWorld() const { return mWorld; } + Misc::NotNullPtr getWorldModel() const { return mWorldModel; } + Misc::NotNullPtr getWorldScene() const { return mWorldScene; } + Misc::NotNullPtr getESMStore() const { return mESMStore; } - World *getWorld() const; + Misc::NotNullPtr getSoundManager() const { return mSoundManager; } - SoundManager *getSoundManager() const; + Misc::NotNullPtr getScriptManager() const { return mScriptManager; } - ScriptManager *getScriptManager() const; + Misc::NotNullPtr getWindowManager() const { return mWindowManager; } - WindowManager *getWindowManager() const; + Misc::NotNullPtr getMechanicsManager() const { return mMechanicsManager; } - MechanicsManager *getMechanicsManager() const; + Misc::NotNullPtr getDialogueManager() const { return mDialogueManager; } - DialogueManager *getDialogueManager() const; + Misc::NotNullPtr getJournal() const { return mJournal; } - Journal *getJournal() const; + Misc::NotNullPtr getInputManager() const { return mInputManager; } - InputManager *getInputManager() const; + Misc::NotNullPtr getStateManager() const { return mStateManager; } - StateManager *getStateManager() const; + Misc::NotNullPtr getLuaManager() const { return mLuaManager; } - Resource::ResourceSystem *getResourceSystem() const; + Misc::NotNullPtr getResourceSystem() const { return mResourceSystem; } - float getFrameDuration() const; + Misc::NotNullPtr getL10nManager() const { return mL10nManager; } - void cleanup(); - ///< Delete all mw*-subsystems. + float getFrameRateLimit() const { return mFrameRateLimit; } - static const Environment& get(); - ///< Return instance of this class. + void setFrameRateLimit(float value) { mFrameRateLimit = value; } - void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + float getFrameDuration() const { return mFrameDuration; } + + void setFrameDuration(float value) { mFrameDuration = value; } + + /// Return instance of this class. + static const Environment& get() + { + assert(sThis != nullptr); + return *sThis; + } }; } diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 951b5053a..f52f9ea45 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -1,11 +1,12 @@ #ifndef GAME_MWBASE_INPUTMANAGER_H #define GAME_MWBASE_INPUTMANAGER_H -#include #include +#include #include -#include +#include +#include namespace Loading { @@ -23,61 +24,69 @@ namespace MWBase /// \brief Interface for input manager (implemented in MWInput) class InputManager { - InputManager (const InputManager&); - ///< not implemented + InputManager(const InputManager&); + ///< not implemented - InputManager& operator= (const InputManager&); - ///< not implemented + InputManager& operator=(const InputManager&); + ///< not implemented - public: + public: + InputManager() {} - InputManager() {} + /// Clear all savegame-specific data + virtual void clear() = 0; - /// Clear all savegame-specific data - virtual void clear() = 0; + virtual ~InputManager() {} - virtual ~InputManager() {} + virtual void update(float dt, bool disableControls, bool disableEvents = false) = 0; - virtual void update(float dt, bool disableControls, bool disableEvents=false) = 0; + virtual void changeInputMode(bool guiMode) = 0; - virtual void changeInputMode(bool guiMode) = 0; + virtual void processChangedSettings(const std::set>& changed) = 0; - virtual void processChangedSettings(const std::set< std::pair >& changed) = 0; + virtual void setDragDrop(bool dragDrop) = 0; + virtual void setGamepadGuiCursorEnabled(bool enabled) = 0; - virtual void setDragDrop(bool dragDrop) = 0; - virtual void setGamepadGuiCursorEnabled(bool enabled) = 0; - virtual void setAttemptJump(bool jumping) = 0; + virtual void toggleControlSwitch(std::string_view sw, bool value) = 0; + virtual bool getControlSwitch(std::string_view sw) = 0; - virtual void toggleControlSwitch (const std::string& sw, bool value) = 0; - virtual bool getControlSwitch (const std::string& sw) = 0; + virtual std::string_view getActionDescription(int action) const = 0; + virtual std::string getActionKeyBindingName(int action) const = 0; + virtual std::string getActionControllerBindingName(int action) const = 0; + virtual bool actionIsActive(int action) const = 0; - virtual std::string getActionDescription (int action) = 0; - virtual std::string getActionKeyBindingName (int action) = 0; - virtual std::string getActionControllerBindingName (int action) = 0; - ///Actions available for binding to keyboard buttons - virtual std::vector getActionKeySorting() = 0; - ///Actions available for binding to controller buttons - virtual std::vector getActionControllerSorting() = 0; - virtual int getNumActions() = 0; - ///If keyboard is true, only pay attention to keyboard events. If false, only pay attention to controller events (excluding esc) - virtual void enableDetectingBindingMode (int action, bool keyboard) = 0; - virtual void resetToDefaultKeyBindings() = 0; - virtual void resetToDefaultControllerBindings() = 0; + virtual float getActionValue(int action) const = 0; // returns value in range [0, 1] + virtual bool isControllerButtonPressed(SDL_GameControllerButton button) const = 0; + virtual float getControllerAxisValue(SDL_GameControllerAxis axis) const = 0; // returns value in range [-1, 1] + virtual int getMouseMoveX() const = 0; + virtual int getMouseMoveY() const = 0; - /// Returns if the last used input device was a joystick or a keyboard - /// @return true if joystick, false otherwise - virtual bool joystickLastUsed() = 0; - virtual void setJoystickLastUsed(bool enabled) = 0; + /// Actions available for binding to keyboard buttons + virtual const std::initializer_list& getActionKeySorting() = 0; + /// Actions available for binding to controller buttons + virtual const std::initializer_list& getActionControllerSorting() = 0; + virtual int getNumActions() = 0; + /// If keyboard is true, only pay attention to keyboard events. If false, only pay attention to controller + /// events (excluding esc) + virtual void enableDetectingBindingMode(int action, bool keyboard) = 0; + virtual void resetToDefaultKeyBindings() = 0; + virtual void resetToDefaultControllerBindings() = 0; - virtual int countSavedGameRecords() const = 0; - virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) = 0; - virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; + /// Returns if the last used input device was a joystick or a keyboard + /// @return true if joystick, false otherwise + virtual bool joystickLastUsed() = 0; + virtual void setJoystickLastUsed(bool enabled) = 0; - virtual void resetIdleTime() = 0; + virtual int countSavedGameRecords() const = 0; + virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) = 0; + virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; - virtual void executeAction(int action) = 0; + virtual void resetIdleTime() = 0; + virtual bool isIdle() const = 0; - virtual bool controlsDisabled() = 0; + virtual void executeAction(int action) = 0; + + virtual bool controlsDisabled() = 0; }; } diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 60616797b..17ae5e29f 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -1,15 +1,16 @@ #ifndef GAME_MWBASE_JOURNAL_H #define GAME_MWBASE_JOURNAL_H -#include #include #include +#include +#include -#include +#include #include "../mwdialogue/journalentry.hpp" -#include "../mwdialogue/topic.hpp" #include "../mwdialogue/quest.hpp" +#include "../mwdialogue/topic.hpp" namespace Loading { @@ -27,92 +28,93 @@ namespace MWBase /// \brief Interface for the player's journal (implemented in MWDialogue) class Journal { - Journal (const Journal&); - ///< not implemented + Journal(const Journal&); + ///< not implemented - Journal& operator= (const Journal&); - ///< not implemented + Journal& operator=(const Journal&); + ///< not implemented - public: + public: + typedef std::deque TEntryContainer; + typedef TEntryContainer::const_iterator TEntryIter; + typedef std::map TQuestContainer; // topic, quest + typedef TQuestContainer::const_iterator TQuestIter; + typedef std::map TTopicContainer; // topic-id, topic-content + typedef TTopicContainer::const_iterator TTopicIter; - typedef std::deque TEntryContainer; - typedef TEntryContainer::const_iterator TEntryIter; - typedef std::map TQuestContainer; // topic, quest - typedef TQuestContainer::const_iterator TQuestIter; - typedef std::map TTopicContainer; // topic-id, topic-content - typedef TTopicContainer::const_iterator TTopicIter; + public: + Journal() {} - public: + virtual void clear() = 0; - Journal() {} + virtual ~Journal() {} - virtual void clear() = 0; + virtual void addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor) = 0; + ///< Add a journal entry. + /// @param actor Used as context for replacing of escape sequences (%name, etc). - virtual ~Journal() {} + virtual void setJournalIndex(const ESM::RefId& id, int index) = 0; + ///< Set the journal index without adding an entry. - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to check whether a journal entry already exists from elsewhere in the code - */ - virtual bool hasEntry(const std::string& id, int index) = 0; - /* - End of tes3mp addition - */ + Make it possible to check whether a journal entry already exists from elsewhere in the code + */ + virtual bool hasEntry(const std::string& id, int index) = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp change (minor) + /* + Start of tes3mp change (minor) - Make it possible to override current time when adding journal entries, by adding - optional timestamp override arguments - */ - virtual void addEntry (const std::string& id, int index, const MWWorld::Ptr& actor, int daysPassed = -1, int month = -1, int day = -1) = 0; - ///< Add a journal entry. - /// @param actor Used as context for replacing of escape sequences (%name, etc). - /* - End of tes3mp change (major) - */ + Make it possible to override current time when adding journal entries, by adding + optional timestamp override arguments + */ + virtual void addEntry (const std::string& id, int index, const MWWorld::Ptr& actor, int daysPassed = -1, int month = -1, int day = -1) = 0; + ///< Add a journal entry. + /// @param actor Used as context for replacing of escape sequences (%name, etc). + /* + End of tes3mp change (major) + */ + virtual int getJournalIndex(const ESM::RefId& id) const = 0; + ///< Get the journal index. - virtual void setJournalIndex (const std::string& id, int index) = 0; - ///< Set the journal index without adding an entry. + virtual void addTopic(const ESM::RefId& topicId, const ESM::RefId& infoId, const MWWorld::Ptr& actor) = 0; + /// \note topicId must be lowercase - virtual int getJournalIndex (const std::string& id) const = 0; - ///< Get the journal index. + virtual void removeLastAddedTopicResponse(const ESM::RefId& topicId, std::string_view actorName) = 0; + ///< Removes the last topic response added for the given topicId and actor name. + /// \note topicId must be lowercase - virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) = 0; - /// \note topicId must be lowercase + virtual TEntryIter begin() const = 0; + ///< Iterator pointing to the begin of the main journal. + /// + /// \note Iterators to main journal entries will never become invalid. - virtual void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName) = 0; - ///< Removes the last topic response added for the given topicId and actor name. - /// \note topicId must be lowercase + virtual TEntryIter end() const = 0; + ///< Iterator pointing past the end of the main journal. - virtual TEntryIter begin() const = 0; - ///< Iterator pointing to the begin of the main journal. - /// - /// \note Iterators to main journal entries will never become invalid. + virtual TQuestIter questBegin() const = 0; + ///< Iterator pointing to the first quest (sorted by topic ID) - virtual TEntryIter end() const = 0; - ///< Iterator pointing past the end of the main journal. + virtual TQuestIter questEnd() const = 0; + ///< Iterator pointing past the last quest. - virtual TQuestIter questBegin() const = 0; - ///< Iterator pointing to the first quest (sorted by topic ID) + virtual TTopicIter topicBegin() const = 0; + ///< Iterator pointing to the first topic (sorted by topic ID) + /// + /// \note The topic ID is identical with the user-visible topic string. - virtual TQuestIter questEnd() const = 0; - ///< Iterator pointing past the last quest. + virtual TTopicIter topicEnd() const = 0; + ///< Iterator pointing past the last topic. - virtual TTopicIter topicBegin() const = 0; - ///< Iterator pointing to the first topic (sorted by topic ID) - /// - /// \note The topic ID is identical with the user-visible topic string. + virtual int countSavedGameRecords() const = 0; - virtual TTopicIter topicEnd() const = 0; - ///< Iterator pointing past the last topic. + virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; - virtual int countSavedGameRecords() const = 0; - - virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; - - virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; + virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; }; } diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp new file mode 100644 index 000000000..ca0220c6f --- /dev/null +++ b/apps/openmw/mwbase/luamanager.hpp @@ -0,0 +1,117 @@ +#ifndef GAME_MWBASE_LUAMANAGER_H +#define GAME_MWBASE_LUAMANAGER_H + +#include +#include +#include + +#include + +#include + +namespace MWWorld +{ + class CellStore; + class Ptr; +} + +namespace Loading +{ + class Listener; +} + +namespace ESM +{ + class ESMReader; + class ESMWriter; + struct LuaScripts; +} + +namespace MWBase +{ + // \brief LuaManager is the central interface through which the engine invokes lua scripts. + // + // The native side invokes functions on this interface, which queues events to be handled by the + // scripts in the lua thread. Synchronous calls are not possible. + // + // The main implementation is in apps/openmw/mwlua/luamanagerimp.cpp. + // Lua logic in general lives under apps/openmw/mwlua and this interface is + // the main way for the rest of the engine to interact with the logic there. + class LuaManager + { + public: + virtual ~LuaManager() = default; + + virtual void newGameStarted() = 0; + virtual void gameLoaded() = 0; + virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; + virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; + virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0; + virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; + virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; + // TODO: notify LuaManager about other events + // virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, + // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0; + + struct InputEvent + { + enum + { + KeyPressed, + KeyReleased, + ControllerPressed, + ControllerReleased, + Action, + TouchPressed, + TouchReleased, + TouchMoved, + } mType; + std::variant mValue; + }; + virtual void inputEvent(const InputEvent& event) = 0; + + struct ActorControls + { + bool mDisableAI = false; + bool mChanged = false; + + bool mJump = false; + bool mRun = false; + bool mSneak = false; + float mMovement = 0; + float mSideMovement = 0; + float mPitchChange = 0; + float mYawChange = 0; + int mUse = 0; + }; + + virtual ActorControls* getActorControls(const MWWorld::Ptr&) const = 0; + + virtual void clear() = 0; + virtual void setupPlayer(const MWWorld::Ptr&) = 0; + + // Saving + int countSavedGameRecords() const { return 1; } + virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) = 0; + virtual void saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) = 0; + + // Loading from a save + virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; + virtual void loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) = 0; + + // Should be called before loading. The map is used to fix refnums if the order of content files was changed. + virtual void setContentFileMapping(const std::map&) = 0; + + // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. + virtual void reloadAllScripts() = 0; + + virtual void handleConsoleCommand( + const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) + = 0; + + virtual std::string formatResourceUsageStats() const = 0; + }; + +} + +#endif // GAME_MWBASE_LUAMANAGER_H diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index c6dfbbe9f..bb36b98d5 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -1,14 +1,14 @@ #ifndef GAME_MWBASE_MECHANICSMANAGER_H #define GAME_MWBASE_MECHANICSMANAGER_H -#include -#include -#include +#include +#include #include -#include +#include +#include +#include -#include "../mwmechanics/actorutil.hpp" -// For MWMechanics::GreetingState +#include "../mwmechanics/greetingstate.hpp" #include "../mwworld/ptr.hpp" @@ -21,7 +21,7 @@ namespace osg namespace ESM { struct Class; - + class RefId; class ESMReader; class ESMWriter; } @@ -43,273 +43,275 @@ namespace MWBase /// \brief Interface for game mechanics manager (implemented in MWMechanics) class MechanicsManager { - MechanicsManager (const MechanicsManager&); - ///< not implemented + MechanicsManager(const MechanicsManager&); + ///< not implemented - MechanicsManager& operator= (const MechanicsManager&); - ///< not implemented + MechanicsManager& operator=(const MechanicsManager&); + ///< not implemented - public: + public: + MechanicsManager() {} + + virtual ~MechanicsManager() {} + + virtual void add(const MWWorld::Ptr& ptr) = 0; + ///< Register an object for management + + virtual void remove(const MWWorld::Ptr& ptr, bool keepActive) = 0; + ///< Deregister an object for management + + virtual void updateCell(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) = 0; + ///< Moves an object to a new cell + + virtual void drop(const MWWorld::CellStore* cellStore) = 0; + ///< Deregister all objects in the given cell. + + virtual void setPlayerName(const std::string& name) = 0; + ///< Set player name. + + virtual void setPlayerRace(const ESM::RefId& id, bool male, const ESM::RefId& head, const ESM::RefId& hair) = 0; + ///< Set player race. + + virtual void setPlayerBirthsign(const ESM::RefId& id) = 0; + ///< Set player birthsign. + + virtual void setPlayerClass(const ESM::RefId& id) = 0; + ///< Set player class to stock class. + + virtual void setPlayerClass(const ESM::Class& class_) = 0; + ///< Set player class to custom class. + + virtual void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep) = 0; + + virtual void rest(double hours, bool sleep) = 0; + ///< If the player is sleeping or waiting, this should be called every hour. + /// @param sleep is the player sleeping or waiting? + + virtual int getHoursToRest() const = 0; + ///< Calculate how many hours the player needs to rest in order to be fully healed + + virtual int getBarterOffer(const MWWorld::Ptr& ptr, int basePrice, bool buying) = 0; + ///< This is used by every service to determine the price of objects given the trading skills of the player and + ///< NPC. + + virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp = true) = 0; + ///< Calculate the diposition of an NPC toward the player. + + virtual int countDeaths(const ESM::RefId& id) const = 0; + ///< Return the number of deaths for actors with the given ID. + + /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! + virtual bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0; + + /// Makes \a ptr fight \a target. Also shouts a combat taunt. + virtual void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + + /* + Start of tes3mp addition + + Make it possible to set the number of deaths for an actor with the given refId + */ + virtual void setDeaths(const std::string& refId, int number) = 0; + /* + End of tes3mp addition + */ + + /// Removes an actor and its allies from combat with the actor's targets. + virtual void stopCombat(const MWWorld::Ptr& ptr) = 0; + + enum OffenseType + { + OT_Theft, // Taking items owned by an NPC or a faction you are not a member of + OT_Assault, // Attacking a peaceful NPC + OT_Murder, // Murdering a peaceful NPC + OT_Trespassing, // Picking the lock of an owned door/chest + OT_SleepingInOwnedBed, // Sleeping in a bed owned by an NPC or a faction you are not a member of + OT_Pickpocket // Entering pickpocket mode, leaving it, and being detected. Any items stolen are a separate + // crime (Theft) + }; + /** + * @note victim may be empty + * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. + * @param victimAware Is the victim already aware of the crime? + * If this parameter is false, it will be determined by a line-of-sight and awareness check. + * @return was the crime seen? + */ + virtual bool commitCrime(const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, + const ESM::RefId& factionId = ESM::RefId(), int arg = 0, bool victimAware = false) + = 0; + /// @return false if the attack was considered a "friendly hit" and forgiven + virtual bool actorAttacked(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; + + /// Notify that actor was killed, add a murder bounty if applicable + /// @note No-op for non-player attackers + virtual void actorKilled(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; + + /// Utility to check if taking this item is illegal and calling commitCrime if so + /// @param container The container the item is in; may be empty for an item in the world + virtual void itemTaken(const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, + int count, bool alarm = true) + = 0; + /// Utility to check if unlocking this object is illegal and calling commitCrime if so + virtual void unlockAttempted(const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0; + /// Attempt sleeping in a bed. If this is illegal, call commitCrime. + /// @return was it illegal, and someone saw you doing it? + virtual bool sleepInBed(const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0; + + enum PersuasionType + { + PT_Admire, + PT_Intimidate, + PT_Taunt, + PT_Bribe10, + PT_Bribe100, + PT_Bribe1000 + }; + virtual void getPersuasionDispositionChange( + const MWWorld::Ptr& npc, PersuasionType type, bool& success, int& tempChange, int& permChange) + = 0; + ///< Perform a persuasion action on NPC + + virtual void forceStateUpdate(const MWWorld::Ptr& ptr) = 0; + ///< Forces an object to refresh its animation state. + + virtual bool playAnimationGroup( + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool persist = false) + = 0; + ///< Run animation for a MW-reference. Calls to this function for references that are currently not + /// in the scene should be ignored. + /// + /// \param mode 0 normal, 1 immediate start, 2 immediate loop + /// \param count How many times the animation should be run + /// \param persist Whether the animation state should be stored in saved games + /// and persist after cell unload. + /// \return Success or error + + virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; + ///< Skip the animation for the given MW-reference for one frame. Calls to this function for + /// references that are currently not in the scene should be ignored. + + virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + + /// Save the current animation state of managed references to their RefData. + virtual void persistAnimationStates() = 0; + + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + virtual void updateMagicEffects(const MWWorld::Ptr& ptr) = 0; + + virtual bool toggleAI() = 0; + virtual bool isAIActive() = 0; + + 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::vector getActorsSidingWith(const MWWorld::Ptr& actor) = 0; + virtual std::vector getActorsFollowing(const MWWorld::Ptr& actor) = 0; + virtual std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; + virtual std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0; + + /// Returns a list of actors who are fighting the given actor within the fAlarmDistance + /** ie AiCombat is active and the target is the actor **/ + virtual std::vector getActorsFighting(const MWWorld::Ptr& actor) = 0; + + virtual std::vector getEnemiesNearby(const MWWorld::Ptr& actor) = 0; + + /// Recursive versions of above methods + virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) = 0; + virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) = 0; + + virtual void playerLoaded() = 0; + + virtual int countSavedGameRecords() const = 0; + + virtual void write(ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; + + virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; + + virtual void clear() = 0; + + virtual bool isAggressive(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; - MechanicsManager() {} + /// Resurrects the player if necessary + virtual void resurrect(const MWWorld::Ptr& ptr) = 0; - virtual ~MechanicsManager() {} - - virtual void add (const MWWorld::Ptr& ptr) = 0; - ///< Register an object for management + virtual bool isCastingSpell(const MWWorld::Ptr& ptr) const = 0; + virtual bool isReadyToBlock(const MWWorld::Ptr& ptr) const = 0; + virtual bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const = 0; - virtual void remove (const MWWorld::Ptr& ptr) = 0; - ///< Deregister an object for management - - virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) = 0; - ///< Moves an object to a new cell - - virtual void drop (const MWWorld::CellStore *cellStore) = 0; - ///< Deregister all objects in the given cell. - - virtual void update (float duration, bool paused) = 0; - ///< Update objects - /// - /// \param paused In game type does not currently advance (this usually means some GUI - /// component is up). - - virtual void setPlayerName (const std::string& name) = 0; - ///< Set player name. - - virtual void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair) = 0; - ///< Set player race. - - virtual void setPlayerBirthsign (const std::string& id) = 0; - ///< Set player birthsign. - - virtual void setPlayerClass (const std::string& id) = 0; - ///< Set player class to stock class. - - virtual void setPlayerClass (const ESM::Class& class_) = 0; - ///< Set player class to custom class. - - virtual void restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) = 0; - - virtual void rest(double hours, bool sleep) = 0; - ///< If the player is sleeping or waiting, this should be called every hour. - /// @param sleep is the player sleeping or waiting? - - virtual int getHoursToRest() const = 0; - ///< Calculate how many hours the player needs to rest in order to be fully healed - - virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) = 0; - ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC. - - virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange = true) = 0; - ///< Calculate the diposition of an NPC toward the player. - - virtual int countDeaths (const std::string& id) const = 0; - ///< Return the number of deaths for actors with the given ID. - - /* - Start of tes3mp addition - - Make it possible to set the number of deaths for an actor with the given refId - */ - virtual void setDeaths(const std::string& refId, int number) = 0; - /* - End of tes3mp addition - */ - - /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! - virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0; - - /// Makes \a ptr fight \a target. Also shouts a combat taunt. - virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; - - enum OffenseType - { - OT_Theft, // Taking items owned by an NPC or a faction you are not a member of - OT_Assault, // Attacking a peaceful NPC - OT_Murder, // Murdering a peaceful NPC - OT_Trespassing, // Picking the lock of an owned door/chest - OT_SleepingInOwnedBed, // Sleeping in a bed owned by an NPC or a faction you are not a member of - OT_Pickpocket // Entering pickpocket mode, leaving it, and being detected. Any items stolen are a separate crime (Theft) - }; - /** - * @note victim may be empty - * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. - * @param victimAware Is the victim already aware of the crime? - * If this parameter is false, it will be determined by a line-of-sight and awareness check. - * @return was the crime seen? - */ - virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, - const std::string& factionId="", int arg=0, bool victimAware=false) = 0; - /// @return false if the attack was considered a "friendly hit" and forgiven - virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; + virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) = 0; - /// Notify that actor was killed, add a murder bounty if applicable - /// @note No-op for non-player attackers - virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; + /* + Start of tes3mp addition - /// Utility to check if taking this item is illegal and calling commitCrime if so - /// @param container The container the item is in; may be empty for an item in the world - virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, - int count, bool alarm = true) = 0; - /// Utility to check if unlocking this object is illegal and calling commitCrime if so - virtual void unlockAttempted (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0; - /// Attempt sleeping in a bed. If this is illegal, call commitCrime. - /// @return was it illegal, and someone saw you doing it? - virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0; + Make it possible to set the attackingOrSpell state from elsewhere in the code + */ + virtual void setAttackingOrSpell(const MWWorld::Ptr &ptr, bool state) const = 0; + /* + End of tes3mp addition + */ - enum PersuasionType - { - PT_Admire, - PT_Intimidate, - PT_Taunt, - PT_Bribe10, - PT_Bribe100, - PT_Bribe1000 - }; - virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange) = 0; - ///< Perform a persuasion action on NPC + virtual void processChangedSettings(const std::set>& settings) = 0; - virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; - ///< Forces an object to refresh its animation state. + virtual void notifyDied(const MWWorld::Ptr& actor) = 0; - virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1, bool persist=false) = 0; - ///< Run animation for a MW-reference. Calls to this function for references that are currently not - /// in the scene should be ignored. - /// - /// \param mode 0 normal, 1 immediate start, 2 immediate loop - /// \param count How many times the animation should be run - /// \param persist Whether the animation state should be stored in saved games - /// and persist after cell unload. - /// \return Success or error + virtual bool onOpen(const MWWorld::Ptr& ptr) = 0; + virtual void onClose(const MWWorld::Ptr& ptr) = 0; - virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; - ///< Skip the animation for the given MW-reference for one frame. Calls to this function for - /// references that are currently not in the scene should be ignored. + /// Check if the target actor was detected by an observer + /// If the observer is a non-NPC, check all actors in AI processing distance as observers + virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0; - virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + virtual void confiscateStolenItems(const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0; - /// Save the current animation state of managed references to their RefData. - virtual void persistAnimationStates() = 0; + /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). + /// + virtual std::vector> getStolenItemOwners(const ESM::RefId& itemid) = 0; - /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently - /// paused we may want to do it manually (after equipping permanent enchantment) - virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; + /// Has the player stolen this item from the given owner? + virtual bool isItemStolenFrom(const ESM::RefId& itemid, const MWWorld::Ptr& ptr) = 0; - virtual bool toggleAI() = 0; - virtual bool isAIActive() = 0; + virtual bool isBoundItem(const MWWorld::Ptr& item) = 0; + virtual bool isAllowedToUse(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) = 0; - 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; + /// Turn actor into werewolf or normal form. + virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 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; - virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; - virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; - virtual std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0; - - ///Returns a list of actors who are fighting the given actor within the fAlarmDistance - /** ie AiCombat is active and the target is the actor **/ - virtual std::list getActorsFighting(const MWWorld::Ptr& actor) = 0; + /* + Start of tes3mp addition - virtual std::list getEnemiesNearby(const MWWorld::Ptr& actor) = 0; + Make it possible to check if an itemId corresponds to a bound item + */ + virtual bool isBoundItem(std::string itemId) = 0; + /* + End of tes3mp addition + */ - /// Recursive versions of above methods - virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) = 0; - virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) = 0; + /// Sets the NPC's Acrobatics skill to match the fWerewolfAcrobatics GMST. + /// It only applies to the current form the NPC is in. + virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0; - virtual void playerLoaded() = 0; - - virtual int countSavedGameRecords() const = 0; + virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; - virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; + virtual void confiscateStolenItemToOwner( + const MWWorld::Ptr& player, const MWWorld::Ptr& item, const MWWorld::Ptr& victim, int count) + = 0; + virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) = 0; + virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; + virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; - virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; + virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; - virtual void clear() = 0; - - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; - - /// Resurrects the player if necessary - virtual void resurrect(const MWWorld::Ptr& ptr) = 0; - - virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const = 0; - virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0; - virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0; - - /* - Start of tes3mp addition - - Make it possible to set the attackingOrSpell state from elsewhere in the code - */ - virtual void setAttackingOrSpell(const MWWorld::Ptr &ptr, bool state) const = 0; - /* - End of tes3mp addition - */ - - virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0; - - virtual void processChangedSettings (const std::set< std::pair >& settings) = 0; - - virtual float getActorsProcessingRange() const = 0; - - virtual void notifyDied(const MWWorld::Ptr& actor) = 0; - - virtual bool onOpen(const MWWorld::Ptr& ptr) = 0; - virtual void onClose(const MWWorld::Ptr& ptr) = 0; - - /// Check if the target actor was detected by an observer - /// If the observer is a non-NPC, check all actors in AI processing distance as observers - virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0; - - virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0; - - /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). - /// - virtual std::vector > getStolenItemOwners(const std::string& itemid) = 0; - - /// Has the player stolen this item from the given owner? - virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr) = 0; - - virtual bool isBoundItem(const MWWorld::Ptr& item) = 0; - - /* - Start of tes3mp addition - - Make it possible to check if an itemId corresponds to a bound item - */ - virtual bool isBoundItem(std::string itemId) = 0; - /* - End of tes3mp addition - */ - - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) = 0; - - /// Turn actor into werewolf or normal form. - virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 0; - - /// Sets the NPC's Acrobatics skill to match the fWerewolfAcrobatics GMST. - /// It only applies to the current form the NPC is in. - virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0; - - virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; - - virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; - virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) = 0; - virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; - virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; - - virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; - - virtual int getGreetingTimer(const MWWorld::Ptr& ptr) const = 0; - virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0; - virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0; - virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0; - - virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) = 0; + virtual int getGreetingTimer(const MWWorld::Ptr& ptr) const = 0; + virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0; + virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0; + virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0; }; } diff --git a/apps/openmw/mwbase/scriptmanager.hpp b/apps/openmw/mwbase/scriptmanager.hpp index ac8333ed1..ceb651e69 100644 --- a/apps/openmw/mwbase/scriptmanager.hpp +++ b/apps/openmw/mwbase/scriptmanager.hpp @@ -1,15 +1,21 @@ #ifndef GAME_MWBASE_SCRIPTMANAGER_H #define GAME_MWBASE_SCRIPTMANAGER_H -#include +#include namespace Interpreter { class Context; } +namespace ESM +{ + class RefId; +} + namespace Compiler { + class Extensions; class Locals; } @@ -23,36 +29,37 @@ namespace MWBase /// \brief Interface for script manager (implemented in MWScript) class ScriptManager { - ScriptManager (const ScriptManager&); - ///< not implemented + ScriptManager(const ScriptManager&); + ///< not implemented - ScriptManager& operator= (const ScriptManager&); - ///< not implemented + ScriptManager& operator=(const ScriptManager&); + ///< not implemented - public: + public: + ScriptManager() {} - ScriptManager() {} + virtual ~ScriptManager() {} - virtual ~ScriptManager() {} + virtual void clear() = 0; - virtual void clear() = 0; + virtual bool run(const ESM::RefId& name, Interpreter::Context& interpreterContext) = 0; + ///< Run the script with the given name (compile first, if not compiled yet) - virtual bool run (const std::string& name, Interpreter::Context& interpreterContext) = 0; - ///< Run the script with the given name (compile first, if not compiled yet) + virtual bool compile(const ESM::RefId& name) = 0; + ///< Compile script with the given namen + /// \return Success? - virtual bool compile (const std::string& name) = 0; - ///< Compile script with the given namen - /// \return Success? + virtual std::pair compileAll() = 0; + ///< Compile all scripts + /// \return count, success - virtual std::pair compileAll() = 0; - ///< Compile all scripts - /// \return count, success + virtual const Compiler::Locals& getLocals(const ESM::RefId& name) = 0; + ///< Return locals for script \a name. - virtual const Compiler::Locals& getLocals (const std::string& name) = 0; - ///< Return locals for script \a name. + virtual MWScript::GlobalScripts& getGlobalScripts() = 0; - virtual MWScript::GlobalScripts& getGlobalScripts() = 0; - }; + virtual const Compiler::Extensions& getExtensions() const = 0; + }; } #endif diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 2bac561fd..593931885 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -2,17 +2,23 @@ #define GAME_MWBASE_SOUNDMANAGER_H #include -#include #include +#include +#include -#include "../mwworld/ptr.hpp" #include "../mwsound/type.hpp" +#include "../mwworld/ptr.hpp" namespace MWWorld { class CellStore; } +namespace ESM +{ + class RefId; +} + namespace MWSound { // Each entry excepts of MaxCount should be used only in one place @@ -29,28 +35,43 @@ namespace MWSound typedef std::shared_ptr DecoderPtr; /* These must all fit together */ - enum class PlayMode { - Normal = 0, /* non-looping, affected by environment */ - Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ - NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ - RemoveAtDistance = 1<<2, /* (3D only) If the listener gets further than 2000 units away - * from the sound source, the sound is removed. - * This is weird stuff but apparently how vanilla works for sounds - * played by the PlayLoopSound family of script functions. Perhaps - * we can make this cut off a more subtle fade later, but have to - * be careful to not change the overall volume of areas by too - * much. */ - NoPlayerLocal = 1<<3, /* (3D only) Don't play the sound local to the listener even if the - * player is making it. */ + enum class PlayMode + { + Normal = 0, /* non-looping, affected by environment */ + Loop = 1 << 0, /* Sound will continually loop until explicitly stopped */ + NoEnv = 1 << 1, /* Do not apply environment effects (eg, underwater filters) */ + RemoveAtDistance = 1 << 2, /* (3D only) If the listener gets further than 2000 units away + * from the sound source, the sound is removed. + * This is weird stuff but apparently how vanilla works for sounds + * played by the PlayLoopSound family of script functions. Perhaps + * we can make this cut off a more subtle fade later, but have to + * be careful to not change the overall volume of areas by too + * much. */ + NoPlayerLocal = 1 << 3, /* (3D only) Don't play the sound local to the listener even if the + * player is making it. */ + NoScaling = 1 << 4, /* Don't scale audio with simulation time */ + NoEnvNoScaling = NoEnv | NoScaling, LoopNoEnv = Loop | NoEnv, LoopRemoveAtDistance = Loop | RemoveAtDistance }; // Used for creating a type mask for SoundManager::pauseSounds and resumeSounds - inline int operator~(Type a) { return ~static_cast(a); } - inline int operator&(Type a, Type b) { return static_cast(a) & static_cast(b); } - inline int operator&(int a, Type b) { return a & static_cast(b); } - inline int operator|(Type a, Type b) { return static_cast(a) | static_cast(b); } + inline int operator~(Type a) + { + return ~static_cast(a); + } + inline int operator&(Type a, Type b) + { + return static_cast(a) & static_cast(b); + } + inline int operator&(int a, Type b) + { + return a & static_cast(b); + } + inline int operator|(Type a, Type b) + { + return static_cast(a) | static_cast(b); + } } namespace MWBase @@ -61,128 +82,133 @@ namespace MWBase /// \brief Interface for sound manager (implemented in MWSound) class SoundManager { - SoundManager (const SoundManager&); - ///< not implemented + SoundManager(const SoundManager&); + ///< not implemented - SoundManager& operator= (const SoundManager&); - ///< not implemented + SoundManager& operator=(const SoundManager&); + ///< not implemented - protected: - using PlayMode = MWSound::PlayMode; - using Type = MWSound::Type; + protected: + using PlayMode = MWSound::PlayMode; + using Type = MWSound::Type; - public: - SoundManager() {} - virtual ~SoundManager() {} + float mSimulationTimeScale = 1.0; - virtual void processChangedSettings(const std::set< std::pair >& settings) = 0; + public: + SoundManager() {} + virtual ~SoundManager() {} - virtual void stopMusic() = 0; - ///< Stops music if it's playing + virtual void processChangedSettings(const std::set>& settings) = 0; - virtual void streamMusic(const std::string& filename) = 0; - ///< Play a soundifle - /// \param filename name of a sound file in "Music/" in the data directory. + virtual void stopMusic() = 0; + ///< Stops music if it's playing - virtual bool isMusicPlaying() = 0; - ///< Returns true if music is playing + virtual void streamMusic(const std::string& filename) = 0; + ///< Play a soundifle + /// \param filename name of a sound file in "Music/" in the data directory. - virtual void playPlaylist(const std::string &playlist) = 0; - ///< Start playing music from the selected folder - /// \param name of the folder that contains the playlist + virtual bool isMusicPlaying() = 0; + ///< Returns true if music is playing - virtual void playTitleMusic() = 0; - ///< Start playing title music + virtual void playPlaylist(const std::string& playlist) = 0; + ///< Start playing music from the selected folder + /// \param name of the folder that contains the playlist + /// Title music playlist is predefined - virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename) = 0; - ///< Make an actor say some text. - /// \param filename name of a sound file in "Sound/" in the data directory. + virtual void say(const MWWorld::ConstPtr& reference, const std::string& filename) = 0; + ///< Make an actor say some text. + /// \param filename name of a sound file in "Sound/" in the data directory. - virtual void say(const std::string& filename) = 0; - ///< Say some text, without an actor ref - /// \param filename name of a sound file in "Sound/" in the data directory. + virtual void say(const std::string& filename) = 0; + ///< Say some text, without an actor ref + /// \param filename name of a sound file in "Sound/" in the data directory. - virtual bool sayActive(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const = 0; - ///< Is actor not speaking? + virtual bool sayActive(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) const = 0; + ///< Is actor not speaking? - virtual bool sayDone(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const = 0; - ///< For scripting backward compatibility + virtual bool sayDone(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) const = 0; + ///< For scripting backward compatibility - virtual void stopSay(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) = 0; - ///< Stop an actor speaking + virtual void stopSay(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) = 0; + ///< Stop an actor speaking - virtual float getSaySoundLoudness(const MWWorld::ConstPtr& reference) const = 0; - ///< Check the currently playing say sound for this actor - /// and get an average loudness value (scale [0,1]) at the current time position. - /// If the actor is not saying anything, returns 0. + virtual float getSaySoundLoudness(const MWWorld::ConstPtr& reference) const = 0; + ///< Check the currently playing say sound for this actor + /// and get an average loudness value (scale [0,1]) at the current time position. + /// If the actor is not saying anything, returns 0. - virtual SoundStream *playTrack(const MWSound::DecoderPtr& decoder, Type type) = 0; - ///< Play a 2D audio track, using a custom decoder. The caller is expected to call - /// stopTrack with the returned handle when done. + virtual SoundStream* playTrack(const MWSound::DecoderPtr& decoder, Type type) = 0; + ///< Play a 2D audio track, using a custom decoder. The caller is expected to call + /// stopTrack with the returned handle when done. - virtual void stopTrack(SoundStream *stream) = 0; - ///< Stop the given audio track from playing + virtual void stopTrack(SoundStream* stream) = 0; + ///< Stop the given audio track from playing - virtual double getTrackTimeDelay(SoundStream *stream) = 0; - ///< Retives the time delay, in seconds, of the audio track (must be a sound - /// returned by \ref playTrack). Only intended to be called by the track - /// decoder's read method. + virtual double getTrackTimeDelay(SoundStream* stream) = 0; + ///< Retives the time delay, in seconds, of the audio track (must be a sound + /// returned by \ref playTrack). Only intended to be called by the track + /// decoder's read method. - virtual Sound *playSound(const std::string& soundId, float volume, float pitch, - Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, - float offset=0) = 0; - ///< Play a sound, independently of 3D-position - ///< @param offset Number of seconds into the sound to start playback. + virtual Sound* playSound(const ESM::RefId& soundId, float volume, float pitch, Type type = Type::Sfx, + PlayMode mode = PlayMode::Normal, float offset = 0) + = 0; + ///< Play a sound, independently of 3D-position + ///< @param offset Number of seconds into the sound to start playback. - virtual Sound *playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, - float volume, float pitch, Type type=Type::Sfx, - PlayMode mode=PlayMode::Normal, float offset=0) = 0; - ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. - ///< @param offset Number of seconds into the sound to start playback. + virtual Sound* playSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId, float volume, + float pitch, Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0) + = 0; + ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless + ///< Play_NoTrack is specified. + ///< @param offset Number of seconds into the sound to start playback. - virtual Sound *playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, - float volume, float pitch, Type type=Type::Sfx, - PlayMode mode=PlayMode::Normal, float offset=0) = 0; - ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition. + virtual Sound* playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch, + Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0) + = 0; + ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using + ///< Sound::setPosition. - virtual void stopSound(Sound *sound) = 0; - ///< Stop the given sound from playing + virtual void stopSound(Sound* sound) = 0; + ///< Stop the given sound from playing - virtual void stopSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId) = 0; - ///< Stop the given object from playing the given sound, + virtual void stopSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) = 0; + ///< Stop the given object from playing the given sound, - virtual void stopSound3D(const MWWorld::ConstPtr &reference) = 0; - ///< Stop the given object from playing all sounds. + virtual void stopSound3D(const MWWorld::ConstPtr& reference) = 0; + ///< Stop the given object from playing all sounds. - virtual void stopSound(const MWWorld::CellStore *cell) = 0; - ///< Stop all sounds for the given cell. + virtual void stopSound(const MWWorld::CellStore* cell) = 0; + ///< Stop all sounds for the given cell. - virtual void fadeOutSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float duration) = 0; - ///< Fade out given sound (that is already playing) of given object - ///< @param reference Reference to object, whose sound is faded out - ///< @param soundId ID of the sound to fade out. - ///< @param duration Time until volume reaches 0. + virtual void fadeOutSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId, float duration) = 0; + ///< Fade out given sound (that is already playing) of given object + ///< @param reference Reference to object, whose sound is faded out + ///< @param soundId ID of the sound to fade out. + ///< @param duration Time until volume reaches 0. - virtual bool getSoundPlaying(const MWWorld::ConstPtr &reference, const std::string& soundId) const = 0; - ///< Is the given sound currently playing on the given object? - /// If you want to check if sound played with playSound is playing, use empty Ptr + virtual bool getSoundPlaying(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) const = 0; + ///< Is the given sound currently playing on the given object? + /// If you want to check if sound played with playSound is playing, use empty Ptr - virtual void pauseSounds(MWSound::BlockerType blocker, int types=int(Type::Mask)) = 0; - ///< Pauses all currently playing sounds, including music. + virtual void pauseSounds(MWSound::BlockerType blocker, int types = int(Type::Mask)) = 0; + ///< Pauses all currently playing sounds, including music. - virtual void resumeSounds(MWSound::BlockerType blocker) = 0; - ///< Resumes all previously paused sounds. + virtual void resumeSounds(MWSound::BlockerType blocker) = 0; + ///< Resumes all previously paused sounds. - virtual void pausePlayback() = 0; - virtual void resumePlayback() = 0; + virtual void pausePlayback() = 0; + virtual void resumePlayback() = 0; - virtual void update(float duration) = 0; + virtual void setListenerPosDir( + const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater) + = 0; - virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) = 0; + virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0; - virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0; + void setSimulationTimeScale(float scale) { mSimulationTimeScale = scale; } + float getSimulationTimeScale() const { return mSimulationTimeScale; } - virtual void clear() = 0; + virtual void clear() = 0; }; } diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index 157833a0e..3c1d4eabf 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWSTATE_STATEMANAGER_H #define GAME_MWSTATE_STATEMANAGER_H +#include #include #include @@ -15,81 +16,74 @@ namespace MWBase /// \brief Interface for game state manager (implemented in MWState) class StateManager { - public: + public: + enum State + { + State_NoGame, + State_Ended, + State_Running + }; - enum State - { - State_NoGame, - State_Ended, - State_Running - }; + typedef std::list::const_iterator CharacterIterator; - typedef std::list::const_iterator CharacterIterator; + private: + StateManager(const StateManager&); + ///< not implemented - private: + StateManager& operator=(const StateManager&); + ///< not implemented - StateManager (const StateManager&); - ///< not implemented + public: + StateManager() {} - StateManager& operator= (const StateManager&); - ///< not implemented + virtual ~StateManager() {} - public: + virtual void requestQuit() = 0; - StateManager() {} + virtual bool hasQuitRequest() const = 0; - virtual ~StateManager() {} + virtual void askLoadRecent() = 0; - virtual void requestQuit() = 0; + virtual State getState() const = 0; - virtual bool hasQuitRequest() const = 0; + virtual void newGame(bool bypass = false) = 0; + ///< Start a new game. + /// + /// \param bypass Skip new game mechanics. - virtual void askLoadRecent() = 0; + virtual void resumeGame() = 0; - virtual State getState() const = 0; + virtual void deleteGame(const MWState::Character* character, const MWState::Slot* slot) = 0; - virtual void newGame (bool bypass = false) = 0; - ///< Start a new game. - /// - /// \param bypass Skip new game mechanics. + virtual void saveGame(const std::string& description, const MWState::Slot* slot = nullptr) = 0; + ///< Write a saved game to \a slot or create a new slot if \a slot == 0. + /// + /// \note Slot must belong to the current character. - virtual void endGame() = 0; + virtual void loadGame(const std::filesystem::path& filepath) = 0; + ///< Load a saved game directly from the given file path. This will search the CharacterManager + /// for a Character containing this save file, and set this Character current if one was found. + /// Otherwise, a new Character will be created. - virtual void resumeGame() = 0; + virtual void loadGame(const MWState::Character* character, const std::filesystem::path& filepath) = 0; + ///< Load a saved game file belonging to the given character. - virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0; + /// Simple saver, writes over the file if already existing + /** Used for quick save and autosave **/ + virtual void quickSave(std::string = "Quicksave") = 0; - virtual void saveGame (const std::string& description, const MWState::Slot *slot = nullptr) = 0; - ///< Write a saved game to \a slot or create a new slot if \a slot == 0. - /// - /// \note Slot must belong to the current character. + /// Simple loader, loads the last saved file + /** Used for quickload **/ + virtual void quickLoad() = 0; - virtual void loadGame (const std::string& filepath) = 0; - ///< Load a saved game directly from the given file path. This will search the CharacterManager - /// for a Character containing this save file, and set this Character current if one was found. - /// Otherwise, a new Character will be created. + virtual MWState::Character* getCurrentCharacter() = 0; + ///< @note May return null. - virtual void loadGame (const MWState::Character *character, const std::string& filepath) = 0; - ///< Load a saved game file belonging to the given character. + virtual CharacterIterator characterBegin() = 0; + ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned + /// iterator. - ///Simple saver, writes over the file if already existing - /** Used for quick save and autosave **/ - virtual void quickSave(std::string = "Quicksave")=0; - - ///Simple loader, loads the last saved file - /** Used for quickload **/ - virtual void quickLoad()=0; - - virtual MWState::Character *getCurrentCharacter () = 0; - ///< @note May return null. - - virtual CharacterIterator characterBegin() = 0; - ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned - /// iterator. - - virtual CharacterIterator characterEnd() = 0; - - virtual void update (float duration) = 0; + virtual CharacterIterator characterEnd() = 0; }; } diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 15d977f48..fc57ed18e 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -1,11 +1,13 @@ #ifndef GAME_MWBASE_WINDOWMANAGER_H #define GAME_MWBASE_WINDOWMANAGER_H -#include +#include +#include +#include +#include +#include #include #include -#include -#include #include @@ -13,6 +15,11 @@ #include +namespace ESM +{ + class RefId; +} + namespace Loading { class Listener; @@ -34,13 +41,12 @@ namespace ESM { class ESMReader; class ESMWriter; - struct CellId; } namespace MWMechanics { class AttributeValue; - template + template class DynamicStat; class SkillValue; } @@ -69,8 +75,11 @@ namespace MWGui class DialogueWindow; class WindowModal; class JailScreen; + class MessageBox; + class PostProcessorHud; - enum ShowInDialogueMode { + enum ShowInDialogueMode + { ShowInDialogueMode_IfPossible, ShowInDialogueMode_Only, ShowInDialogueMode_Never @@ -89,361 +98,382 @@ namespace MWBase /// \brief Interface for widnow manager (implemented in MWGui) class WindowManager : public SDLUtil::WindowListener { - WindowManager (const WindowManager&); - ///< not implemented + WindowManager(const WindowManager&); + ///< not implemented - WindowManager& operator= (const WindowManager&); - ///< not implemented + WindowManager& operator=(const WindowManager&); + ///< not implemented - public: + public: + typedef std::vector SkillList; - typedef std::vector SkillList; + WindowManager() {} - WindowManager() {} + virtual ~WindowManager() {} - virtual ~WindowManager() {} + /// @note This method will block until the video finishes playing + /// (and will continually update the window while doing so) + virtual void playVideo(std::string_view name, bool allowSkipping, bool overrideSounds = true) = 0; - /// @note This method will block until the video finishes playing - /// (and will continually update the window while doing so) - virtual void playVideo(const std::string& name, bool allowSkipping) = 0; + virtual void setNewGame(bool newgame) = 0; - virtual void setNewGame(bool newgame) = 0; + virtual void pushGuiMode(MWGui::GuiMode mode, const MWWorld::Ptr& arg) = 0; + virtual void pushGuiMode(MWGui::GuiMode mode) = 0; + virtual void popGuiMode(bool noSound = false) = 0; - virtual void pushGuiMode (MWGui::GuiMode mode, const MWWorld::Ptr& arg) = 0; - virtual void pushGuiMode (MWGui::GuiMode mode) = 0; - virtual void popGuiMode(bool noSound=false) = 0; + virtual void removeGuiMode(MWGui::GuiMode mode, bool noSound = false) = 0; + ///< can be anywhere in the stack - virtual void removeGuiMode (MWGui::GuiMode mode, bool noSound=false) = 0; - ///< can be anywhere in the stack + virtual void goToJail(int days) = 0; - virtual void goToJail(int days) = 0; + virtual void updatePlayer() = 0; - virtual void updatePlayer() = 0; + virtual MWGui::GuiMode getMode() const = 0; + virtual bool containsMode(MWGui::GuiMode) const = 0; - virtual MWGui::GuiMode getMode() const = 0; - virtual bool containsMode(MWGui::GuiMode) const = 0; + virtual bool isGuiMode() const = 0; - virtual bool isGuiMode() const = 0; + virtual bool isConsoleMode() const = 0; - virtual bool isConsoleMode() const = 0; + virtual bool isPostProcessorHudVisible() const = 0; - virtual void toggleVisible (MWGui::GuiWindow wnd) = 0; + virtual void toggleVisible(MWGui::GuiWindow wnd) = 0; - virtual void forceHide(MWGui::GuiWindow wnd) = 0; - virtual void unsetForceHide(MWGui::GuiWindow wnd) = 0; + virtual void forceHide(MWGui::GuiWindow wnd) = 0; + virtual void unsetForceHide(MWGui::GuiWindow wnd) = 0; - /// Disallow all inventory mode windows - virtual void disallowAll() = 0; + /// Disallow all inventory mode windows + virtual void disallowAll() = 0; - /// Allow one or more windows - virtual void allow (MWGui::GuiWindow wnd) = 0; + /// Allow one or more windows + virtual void allow(MWGui::GuiWindow wnd) = 0; - virtual bool isAllowed (MWGui::GuiWindow wnd) const = 0; + virtual bool isAllowed(MWGui::GuiWindow wnd) const = 0; - /// \todo investigate, if we really need to expose every single lousy UI element to the outside world - virtual MWGui::InventoryWindow* getInventoryWindow() = 0; - virtual MWGui::CountDialog* getCountDialog() = 0; - virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; - virtual MWGui::TradeWindow* getTradeWindow() = 0; + /// \todo investigate, if we really need to expose every single lousy UI element to the outside world + virtual MWGui::InventoryWindow* getInventoryWindow() = 0; + virtual MWGui::CountDialog* getCountDialog() = 0; + virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; + virtual MWGui::TradeWindow* getTradeWindow() = 0; + virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to get the ContainerWindow from elsewhere - in the code - */ - virtual MWGui::ContainerWindow* getContainerWindow() = 0; - /* - End of tes3mp addition - */ + Make it possible to get the ContainerWindow from elsewhere + in the code + */ + virtual MWGui::ContainerWindow* getContainerWindow() = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to get the DialogueWindow from elsewhere - */ - virtual MWGui::DialogueWindow* getDialogueWindow() = 0; - /* - End of tes3mp addition - */ + Make it possible to get the DialogueWindow from elsewhere + */ + virtual MWGui::DialogueWindow* getDialogueWindow() = 0; + /* + End of tes3mp addition + */ - /// Make the player use an item, while updating GUI state accordingly - virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0; + /// Make the player use an item, while updating GUI state accordingly + virtual void useItem(const MWWorld::Ptr& item, bool force = false) = 0; - virtual void updateSpellWindow() = 0; + virtual void updateSpellWindow() = 0; - virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; + virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; + virtual MWWorld::Ptr getConsoleSelectedObject() const = 0; + virtual void setConsoleMode(const std::string& mode) = 0; - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Allow the direct setting of a console's Ptr, without the assumption that an object - was clicked and that key focus should be restored to the console window, for console - commands executed via server scripts - */ - virtual void setConsolePtr(const MWWorld::Ptr& object) = 0; - /* - End of tes3mp addition - */ + Allow the direct setting of a console's Ptr, without the assumption that an object + was clicked and that key focus should be restored to the console window, for console + commands executed via server scripts + */ + virtual void setConsolePtr(const MWWorld::Ptr& object) = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Allow the clearing of the console's Ptr from elsewhere in the code, so that - Ptrs used in console commands run from server scripts do not stay selected - */ - virtual void clearConsolePtr() = 0; - /* - End of tes3mp addition - */ + Allow the clearing of the console's Ptr from elsewhere in the code, so that + Ptrs used in console commands run from server scripts do not stay selected + */ + virtual void clearConsolePtr() = 0; + /* + End of tes3mp addition + */ + static constexpr std::string_view sConsoleColor_Default = "#FFFFFF"; + static constexpr std::string_view sConsoleColor_Error = "#FF2222"; + static constexpr std::string_view sConsoleColor_Success = "#FF00FF"; + static constexpr std::string_view sConsoleColor_Info = "#AAAAAA"; + virtual void printToConsole(const std::string& msg, std::string_view color) = 0; - /// Set time left for the player to start drowning (update the drowning bar) - /// @param time time left to start drowning - /// @param maxTime how long we can be underwater (in total) until drowning starts - virtual void setDrowningTimeLeft (float time, float maxTime) = 0; + /// Set time left for the player to start drowning (update the drowning bar) + /// @param time time left to start drowning + /// @param maxTime how long we can be underwater (in total) until drowning starts + virtual void setDrowningTimeLeft(float time, float maxTime) = 0; + + /* + Start of tes3mp addition + + Allow the setting of the image data for a global map tile from elsewhere + in the code + */ + virtual void setGlobalMapImage(int cellX, int cellY, const std::vector& imageData) = 0; + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Allow the completion of a drag and drop from elsewhere in the code + */ + virtual void finishDragDrop() = 0; + /* + End of tes3mp addition + */ + + virtual void changeCell(const MWWorld::CellStore* cell) = 0; + ///< change the active cell + + virtual void setFocusObject(const MWWorld::Ptr& focus) = 0; + virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) = 0; + + virtual void setCursorVisible(bool visible) = 0; + virtual void setCursorActive(bool active) = 0; + virtual void getMousePosition(int& x, int& y) = 0; + virtual void getMousePosition(float& x, float& y) = 0; + virtual void setDragDrop(bool dragDrop) = 0; + virtual bool getWorldMouseOver() = 0; + + virtual float getScalingFactor() const = 0; + + virtual bool toggleFogOfWar() = 0; + + virtual bool toggleFullHelp() = 0; + ///< show extra info in item tooltips (owner, script) + + virtual bool getFullHelp() const = 0; + + virtual void setActiveMap(int x, int y, bool interior) = 0; + ///< set the indices of the map texture that should be used + + /// sets the visibility of the drowning bar + virtual void setDrowningBarVisibility(bool visible) = 0; + + /// sets the visibility of the hud health/magicka/stamina bars + virtual void setHMSVisibility(bool visible) = 0; + + /// sets the visibility of the hud minimap + virtual void setMinimapVisibility(bool visible) = 0; + virtual void setWeaponVisibility(bool visible) = 0; + virtual void setSpellVisibility(bool visible) = 0; + virtual void setSneakVisibility(bool visible) = 0; + + /* + Start of tes3mp addition + + Make it possible to add quickKeys from elsewhere in the code + */ + virtual void setQuickKey(int slot, int quickKeyType, MWWorld::Ptr item, const std::string& spellId = "") = 0; + /* + End of tes3mp addition + */ + /// activate selected quick key + virtual void activateQuickKey(int index) = 0; + /// update activated quick key state (if action executing was delayed for some reason) + virtual void updateActivatedQuickKey() = 0; + + virtual const ESM::RefId& getSelectedSpell() = 0; + virtual void setSelectedSpell(const ESM::RefId& spellId, int successChancePercent) = 0; + virtual void setSelectedEnchantItem(const MWWorld::Ptr& item) = 0; + virtual const MWWorld::Ptr& getSelectedEnchantItem() const = 0; + virtual void setSelectedWeapon(const MWWorld::Ptr& item) = 0; + virtual const MWWorld::Ptr& getSelectedWeapon() const = 0; + virtual int getFontHeight() const = 0; + virtual void unsetSelectedSpell() = 0; + virtual void unsetSelectedWeapon() = 0; + + virtual void showCrosshair(bool show) = 0; + virtual bool getSubtitlesEnabled() = 0; + virtual bool toggleHud() = 0; + + virtual void disallowMouse() = 0; + virtual void allowMouse() = 0; + virtual void notifyInputActionBound() = 0; + + virtual void addVisitedLocation(const std::string& name, int x, int y) = 0; + + /// Hides dialog and schedules dialog to be deleted. + virtual void removeDialog(std::unique_ptr&& dialog) = 0; + + /* + Start of tes3mp change (major) + + Add a hasServerOrigin boolean to the list of arguments so those messageboxes + can be differentiated from client-only ones + */ + virtual void interactiveMessageBox (const std::string& message, + const std::vector& buttons = std::vector(), bool block=false, bool hasServerOrigin=false) = 0; + /* + End of tes3mp change (major) + */ + + /// Gracefully attempts to exit the topmost GUI mode + /** No guarantee of actually closing the window **/ + virtual void exitCurrentGuiMode() = 0; + + virtual void messageBox(std::string_view message, + enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) + = 0; + /// Puts message into a queue to show on the next update. Thread safe alternative for messageBox. + virtual void scheduleMessageBox(std::string message, + enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) + = 0; + virtual void staticMessageBox(std::string_view message) = 0; + virtual void removeStaticMessageBox() = 0; + virtual void interactiveMessageBox( + std::string_view message, const std::vector& buttons = {}, bool block = false) + = 0; + + /// returns the index of the pressed button or -1 if no button was pressed + /// (->MessageBoxmanager->InteractiveMessageBox) + virtual int readPressedButton() = 0; + + virtual void updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) = 0; + + /** + * Fetches a GMST string from the store, if there is no setting with the given + * ID or it is not a string the default string is returned. + * + * @param id Identifier for the GMST setting, e.g. "aName" + * @param default Default value if the GMST setting cannot be used. + */ + virtual std::string_view getGameSettingString(std::string_view id, std::string_view default_) = 0; + + virtual void processChangedSettings(const std::set>& changed) = 0; + + virtual void executeInConsole(const std::filesystem::path& path) = 0; + + /* + Start of tes3mp addition + + Allow the execution of console commands from elsewhere in the code + */ + virtual void executeCommandInConsole(const std::string& command) = 0; + /* + End of tes3mp addition + */ + virtual void enableRest() = 0; + virtual bool getRestEnabled() = 0; + virtual bool getJournalAllowed() = 0; - virtual void changeCell(const MWWorld::CellStore* cell) = 0; - ///< change the active cell + virtual bool getPlayerSleeping() = 0; + virtual void wakeUpPlayer() = 0; + + virtual void showSoulgemDialog(MWWorld::Ptr item) = 0; - /* - Start of tes3mp addition + virtual void changePointer(const std::string& name) = 0; + + virtual void setEnemy(const MWWorld::Ptr& enemy) = 0; - Allow the setting of the image data for a global map tile from elsewhere - in the code - */ - virtual void setGlobalMapImage(int cellX, int cellY, const std::vector& imageData) = 0; - /* - End of tes3mp addition - */ + virtual int getMessagesCount() const = 0; - virtual void setFocusObject(const MWWorld::Ptr& focus) = 0; - virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) = 0; - - virtual void setCursorVisible(bool visible) = 0; - virtual void setCursorActive(bool active) = 0; - virtual void getMousePosition(int &x, int &y) = 0; - virtual void getMousePosition(float &x, float &y) = 0; - virtual void setDragDrop(bool dragDrop) = 0; + virtual const Translation::Storage& getTranslationDataStorage() const = 0; - /* - Start of tes3mp addition - - Allow the completion of a drag and drop from elsewhere in the code - */ - virtual void finishDragDrop() = 0; - /* - End of tes3mp addition - */ - - virtual bool getWorldMouseOver() = 0; - - virtual float getScalingFactor() = 0; - - virtual bool toggleFogOfWar() = 0; - - virtual bool toggleFullHelp() = 0; - ///< show extra info in item tooltips (owner, script) - - virtual bool getFullHelp() const = 0; - - virtual void setActiveMap(int x, int y, bool interior) = 0; - ///< set the indices of the map texture that should be used - - /// sets the visibility of the drowning bar - virtual void setDrowningBarVisibility(bool visible) = 0; - - /// sets the visibility of the hud health/magicka/stamina bars - virtual void setHMSVisibility(bool visible) = 0; - - /// sets the visibility of the hud minimap - virtual void setMinimapVisibility(bool visible) = 0; - virtual void setWeaponVisibility(bool visible) = 0; - virtual void setSpellVisibility(bool visible) = 0; - virtual void setSneakVisibility(bool visible) = 0; - - /// activate selected quick key - virtual void activateQuickKey (int index) = 0; - /// update activated quick key state (if action executing was delayed for some reason) - virtual void updateActivatedQuickKey () = 0; + /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. + virtual void setKeyFocusWidget(MyGUI::Widget* widget) = 0; - /* - Start of tes3mp addition + virtual Loading::Listener* getLoadingScreen() = 0; - Make it possible to add quickKeys from elsewhere in the code - */ - virtual void setQuickKey(int slot, int quickKeyType, MWWorld::Ptr item, const std::string& spellId = "") = 0; - /* - End of tes3mp addition - */ + /// Should the cursor be visible? + virtual bool getCursorVisible() = 0; - virtual std::string getSelectedSpell() = 0; - virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0; - virtual void setSelectedEnchantItem(const MWWorld::Ptr& item) = 0; - virtual const MWWorld::Ptr& getSelectedEnchantItem() const = 0; - virtual void setSelectedWeapon(const MWWorld::Ptr& item) = 0; - virtual const MWWorld::Ptr& getSelectedWeapon() const = 0; - virtual int getFontHeight() const = 0; - virtual void unsetSelectedSpell() = 0; - virtual void unsetSelectedWeapon() = 0; + /// Clear all savegame-specific data + virtual void clear() = 0; - virtual void showCrosshair(bool show) = 0; - virtual bool getSubtitlesEnabled() = 0; - virtual bool toggleHud() = 0; + virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) = 0; + virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; + virtual int countSavedGameRecords() const = 0; - virtual void disallowMouse() = 0; - virtual void allowMouse() = 0; - virtual void notifyInputActionBound() = 0; + /// Does the current stack of GUI-windows permit saving? + virtual bool isSavingAllowed() const = 0; - virtual void addVisitedLocation(const std::string& name, int x, int y) = 0; + /// Send exit command to active Modal window + virtual void exitCurrentModal() = 0; - /// Hides dialog and schedules dialog to be deleted. - virtual void removeDialog(MWGui::Layout* dialog) = 0; + /// Sets the current Modal + /** Used to send exit command to active Modal when Esc is pressed **/ + virtual void addCurrentModal(MWGui::WindowModal* input) = 0; - ///Gracefully attempts to exit the topmost GUI mode - /** No guarantee of actually closing the window **/ - virtual void exitCurrentGuiMode() = 0; + /// Removes the top Modal + /** Used when one Modal adds another Modal + \param input Pointer to the current modal, to ensure proper modal is removed **/ + virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; - virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; - virtual void staticMessageBox(const std::string& message) = 0; - virtual void removeStaticMessageBox() = 0; - /* - Start of tes3mp change (major) + virtual void pinWindow(MWGui::GuiWindow window) = 0; + virtual void toggleMaximized(MWGui::Layout* layout) = 0; - Add a hasServerOrigin boolean to the list of arguments so those messageboxes - can be differentiated from client-only ones - */ - virtual void interactiveMessageBox (const std::string& message, - const std::vector& buttons = std::vector(), bool block=false, bool hasServerOrigin=false) = 0; - /* - End of tes3mp change (major) - */ + /// Fade the screen in, over \a time seconds + virtual void fadeScreenIn(const float time, bool clearQueue = true, float delay = 0.f) = 0; + /// Fade the screen out to black, over \a time seconds + virtual void fadeScreenOut(const float time, bool clearQueue = true, float delay = 0.f) = 0; + /// Fade the screen to a specified percentage of black, over \a time seconds + virtual void fadeScreenTo(const int percent, const float time, bool clearQueue = true, float delay = 0.f) = 0; + /// Darken the screen to a specified percentage + virtual void setBlindness(const int percent) = 0; - /// returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) - virtual int readPressedButton() = 0; + virtual void activateHitOverlay(bool interrupt = true) = 0; + virtual void setWerewolfOverlay(bool set) = 0; - virtual void update (float duration) = 0; + virtual void toggleConsole() = 0; + virtual void toggleDebugWindow() = 0; + virtual void togglePostProcessorHud() = 0; - virtual void updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) = 0; + /// Cycle to next or previous spell + virtual void cycleSpell(bool next) = 0; + /// Cycle to next or previous weapon + virtual void cycleWeapon(bool next) = 0; - /** - * Fetches a GMST string from the store, if there is no setting with the given - * ID or it is not a string the default string is returned. - * - * @param id Identifier for the GMST setting, e.g. "aName" - * @param default Default value if the GMST setting cannot be used. - */ - virtual std::string getGameSettingString(const std::string &id, const std::string &default_) = 0; + virtual void playSound(const ESM::RefId& soundId, float volume = 1.f, float pitch = 1.f) = 0; - virtual void processChangedSettings(const std::set< std::pair >& changed) = 0; + virtual void addCell(MWWorld::CellStore* cell) = 0; + virtual void removeCell(MWWorld::CellStore* cell) = 0; + virtual void writeFog(MWWorld::CellStore* cell) = 0; - virtual void executeInConsole (const std::string& path) = 0; + virtual const MWGui::TextColours& getTextColours() = 0; - /* - Start of tes3mp addition + virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) = 0; + virtual bool injectKeyRelease(MyGUI::KeyCode key) = 0; - Allow the execution of console commands from elsewhere in the code - */ - virtual void executeCommandInConsole(const std::string& command) = 0; - /* - End of tes3mp addition - */ + void windowVisibilityChange(bool visible) override = 0; + void windowResized(int x, int y) override = 0; + void windowClosed() override = 0; + virtual bool isWindowVisible() = 0; - virtual void enableRest() = 0; - virtual bool getRestEnabled() = 0; - virtual bool getJournalAllowed() = 0; + virtual void watchActor(const MWWorld::Ptr& ptr) = 0; + virtual MWWorld::Ptr getWatchedActor() const = 0; - virtual bool getPlayerSleeping() = 0; - virtual void wakeUpPlayer() = 0; + virtual const std::string& getVersionDescription() const = 0; - virtual void showSoulgemDialog (MWWorld::Ptr item) = 0; + virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0; + virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0; - virtual void changePointer (const std::string& name) = 0; + virtual void asyncPrepareSaveMap() = 0; - virtual void setEnemy (const MWWorld::Ptr& enemy) = 0; + /// Sets the cull masks for all applicable views + virtual void setCullMask(uint32_t mask) = 0; - virtual int getMessagesCount() const = 0; - - virtual const Translation::Storage& getTranslationDataStorage() const = 0; - - /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. - virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; - - virtual void loadUserFonts() = 0; - - virtual Loading::Listener* getLoadingScreen() = 0; - - /// Should the cursor be visible? - virtual bool getCursorVisible() = 0; - - /// Clear all savegame-specific data - virtual void clear() = 0; - - virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) = 0; - virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; - virtual int countSavedGameRecords() const = 0; - - /// Does the current stack of GUI-windows permit saving? - virtual bool isSavingAllowed() const = 0; - - /// Send exit command to active Modal window - virtual void exitCurrentModal() = 0; - - /// Sets the current Modal - /** Used to send exit command to active Modal when Esc is pressed **/ - virtual void addCurrentModal(MWGui::WindowModal* input) = 0; - - /// Removes the top Modal - /** Used when one Modal adds another Modal - \param input Pointer to the current modal, to ensure proper modal is removed **/ - virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; - - virtual void pinWindow (MWGui::GuiWindow window) = 0; - virtual void toggleMaximized(MWGui::Layout *layout) = 0; - - /// Fade the screen in, over \a time seconds - virtual void fadeScreenIn(const float time, bool clearQueue=true, float delay=0.f) = 0; - /// Fade the screen out to black, over \a time seconds - virtual void fadeScreenOut(const float time, bool clearQueue=true, float delay=0.f) = 0; - /// Fade the screen to a specified percentage of black, over \a time seconds - virtual void fadeScreenTo(const int percent, const float time, bool clearQueue=true, float delay=0.f) = 0; - /// Darken the screen to a specified percentage - virtual void setBlindness(const int percent) = 0; - - virtual void activateHitOverlay(bool interrupt=true) = 0; - virtual void setWerewolfOverlay(bool set) = 0; - - virtual void toggleConsole() = 0; - virtual void toggleDebugWindow() = 0; - - /// Cycle to next or previous spell - virtual void cycleSpell(bool next) = 0; - /// Cycle to next or previous weapon - virtual void cycleWeapon(bool next) = 0; - - virtual void playSound(const std::string& soundId, float volume = 1.f, float pitch = 1.f) = 0; - - // In WindowManager for now since there isn't a VFS singleton - virtual std::string correctIconPath(const std::string& path) = 0; - virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr) = 0; - virtual std::string correctTexturePath(const std::string& path) = 0; - virtual bool textureExists(const std::string& path) = 0; - - virtual void addCell(MWWorld::CellStore* cell) = 0; - virtual void removeCell(MWWorld::CellStore* cell) = 0; - virtual void writeFog(MWWorld::CellStore* cell) = 0; - - virtual const MWGui::TextColours& getTextColours() = 0; - - virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) = 0; - virtual bool injectKeyRelease(MyGUI::KeyCode key) = 0; - - void windowVisibilityChange(bool visible) override = 0; - void windowResized(int x, int y) override = 0; - void windowClosed() override = 0; - virtual bool isWindowVisible() = 0; - - virtual void watchActor(const MWWorld::Ptr& ptr) = 0; - virtual MWWorld::Ptr getWatchedActor() const = 0; + /// Same as viewer->getCamera()->getCullMask(), provided for consistency. + virtual uint32_t getCullMask() = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 01c1c55cd..44741d090 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -3,19 +3,22 @@ #include "rotationflags.hpp" -#include +#include #include #include -#include +#include +#include +#include -#include +#include /* Start of tes3mp addition Include additional headers for multiplayer purposes */ -#include +// TODO: what this for? +// #include /* End of tes3mp addition */ @@ -24,6 +27,9 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/doorstate.hpp" +#include "../mwworld/globalvariablename.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/spellcaststate.hpp" #include "../mwrender/rendermode.hpp" @@ -62,16 +68,22 @@ namespace ESM struct CreatureLevList; struct ItemLevList; struct TimeStamp; + class RefId; + struct ExteriorCellLocation; } namespace MWPhysics { + class RayCastingResult; class RayCastingInterface; } namespace MWRender { class Animation; + class Camera; + class RenderingManager; + class PostProcessor; } namespace MWMechanics @@ -82,6 +94,7 @@ namespace MWMechanics namespace DetourNavigator { struct Navigator; + struct AgentBounds; } namespace MWWorld @@ -92,8 +105,9 @@ namespace MWWorld class TimeStamp; class ESMStore; class RefData; + class Cell; - typedef std::vector > PtrMovementList; + typedef std::vector> PtrMovementList; } namespace MWBase @@ -101,767 +115,708 @@ namespace MWBase /// \brief Interface for the World (implemented in MWWorld) class World { - World (const World&); - ///< not implemented + World(const World&); + ///< not implemented - World& operator= (const World&); - ///< not implemented + World& operator=(const World&); + ///< not implemented - public: + public: + struct DoorMarker + { + std::string name; + float x, y; // world position + ESM::RefId dest; + }; - struct DoorMarker - { - std::string name; - float x, y; // world position - ESM::CellId dest; - }; + World() {} - World() {} + virtual ~World() {} - virtual ~World() {} + virtual void setRandomSeed(uint32_t seed) = 0; + ///< \param seed The seed used when starting a new game. - virtual void startNewGame (bool bypass) = 0; - ///< \param bypass Bypass regular game start. + virtual void startNewGame(bool bypass) = 0; + ///< \param bypass Bypass regular game start. - virtual void clear() = 0; + virtual void clear() = 0; - virtual int countSavedGameRecords() const = 0; - virtual int countSavedGameCells() const = 0; + virtual int countSavedGameRecords() const = 0; + virtual int countSavedGameCells() const = 0; - virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; + virtual void write(ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; - virtual void readRecord (ESM::ESMReader& reader, uint32_t type, - const std::map& contentFileMap) = 0; + virtual void readRecord(ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) = 0; - virtual MWWorld::CellStore *getExterior (int x, int y) = 0; + virtual void useDeathCamera() = 0; - virtual MWWorld::CellStore *getInterior (const std::string& name) = 0; + virtual void setWaterHeight(const float height) = 0; - virtual MWWorld::CellStore *getCell (const ESM::CellId& id) = 0; + virtual bool toggleWater() = 0; + virtual bool toggleWorld() = 0; + virtual bool toggleBorders() = 0; - virtual void testExteriorCells() = 0; - virtual void testInteriorCells() = 0; + virtual MWWorld::Player& getPlayer() = 0; + virtual MWWorld::Ptr getPlayerPtr() = 0; + virtual MWWorld::ConstPtr getPlayerConstPtr() const = 0; - virtual void useDeathCamera() = 0; + virtual MWWorld::ESMStore& getStore() = 0; + const MWWorld::ESMStore& getStore() const { return const_cast(this)->getStore(); } - virtual void setWaterHeight(const float height) = 0; + virtual const std::vector& getESMVersions() const = 0; - virtual bool toggleWater() = 0; - virtual bool toggleWorld() = 0; - virtual bool toggleBorders() = 0; + virtual MWWorld::LocalScripts& getLocalScripts() = 0; - virtual void adjustSky() = 0; + virtual bool isCellExterior() const = 0; - virtual MWWorld::Player& getPlayer() = 0; - virtual MWWorld::Ptr getPlayerPtr() = 0; - virtual MWWorld::ConstPtr getPlayerConstPtr() const = 0; + virtual bool isCellQuasiExterior() const = 0; - virtual const MWWorld::ESMStore& getStore() const = 0; + virtual void getDoorMarkers(MWWorld::CellStore& cell, std::vector& out) = 0; + ///< get a list of teleport door markers for a given cell, to be displayed on the local map - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to get the World's ESMStore as a non-const - */ - virtual MWWorld::ESMStore& getModifiableStore() = 0; - /* - End of tes3mp addition - */ + Make it possible to get the World's ESMStore as a non-const + */ + virtual MWWorld::ESMStore& getModifiableStore() = 0; + /* + End of tes3mp addition + */ - virtual std::vector& getEsmReader() = 0; + virtual void setGlobalInt(MWWorld::GlobalVariableName name, int value) = 0; + ///< Set value independently from real type. - virtual MWWorld::LocalScripts& getLocalScripts() = 0; + virtual void setGlobalFloat(MWWorld::GlobalVariableName name, float value) = 0; + ///< Set value independently from real type. - virtual bool hasCellChanged() const = 0; - ///< Has the set of active cells changed, since the last frame? + virtual int getGlobalInt(MWWorld::GlobalVariableName name) const = 0; + ///< Get value independently from real type. - virtual bool isCellExterior() const = 0; + virtual float getGlobalFloat(MWWorld::GlobalVariableName name) const = 0; + ///< Get value independently from real type. - virtual bool isCellQuasiExterior() const = 0; + virtual char getGlobalVariableType(MWWorld::GlobalVariableName name) const = 0; + ///< Return ' ', if there is no global variable with this name. - virtual osg::Vec2f getNorthVector (const MWWorld::CellStore* cell) = 0; - ///< get north vector for given interior cell + virtual std::string_view getCellName(const MWWorld::CellStore* cell = nullptr) const = 0; + ///< Return name of the cell. + /// + /// \note If cell==0, the cell the player is currently in will be used instead to + /// generate a name. + virtual std::string_view getCellName(const MWWorld::Cell& cell) const = 0; - virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) = 0; - ///< get a list of teleport door markers for a given cell, to be displayed on the local map + virtual std::string_view getCellName(const ESM::Cell* cell) const = 0; - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to check whether global variables exist and to create - new ones - */ - virtual bool hasGlobal(const std::string& name) = 0; + Make it possible to check whether global variables exist and to create + new ones + */ + virtual bool hasGlobal(const std::string& name) = 0; - virtual void createGlobal(const std::string& name, ESM::VarType varType) = 0; - /* - End of tes3mp addition - */ + virtual void createGlobal(const std::string& name, ESM::VarType varType) = 0; + /* + End of tes3mp addition + */ - virtual void setGlobalInt (const std::string& name, int value) = 0; - ///< Set value independently from real type. + virtual void removeRefScript(MWWorld::RefData* ref) = 0; + //< Remove the script attached to ref from mLocalScripts - virtual void setGlobalFloat (const std::string& name, float value) = 0; - ///< Set value independently from real type. + virtual MWWorld::Ptr getPtr(const ESM::RefId& name, bool activeOnly) = 0; + ///< Return a pointer to a liveCellRef with the given name. + /// \param activeOnly do non search inactive cells. - virtual int getGlobalInt (const std::string& name) const = 0; - ///< Get value independently from real type. + virtual MWWorld::Ptr searchPtr(const ESM::RefId& name, bool activeOnly, bool searchInContainers = true) = 0; + ///< Return a pointer to a liveCellRef with the given name. + /// \param activeOnly do non search inactive cells. - virtual float getGlobalFloat (const std::string& name) const = 0; - ///< Get value independently from real type. + virtual MWWorld::Ptr searchPtrViaActorId(int actorId) = 0; + ///< Search is limited to the active cells. - virtual char getGlobalVariableType (const std::string& name) const = 0; - ///< Return ' ', if there is no global variable with this name. + virtual MWWorld::Ptr findContainer(const MWWorld::ConstPtr& ptr) = 0; + ///< Return a pointer to a liveCellRef which contains \a ptr. + /// \note Search is limited to the active cells. - virtual std::string getCellName (const MWWorld::CellStore *cell = nullptr) const = 0; - ///< Return name of the cell. - /// - /// \note If cell==0, the cell the player is currently in will be used instead to - /// generate a name. - virtual std::string getCellName(const ESM::Cell* cell) const = 0; + virtual void enable(const MWWorld::Ptr& ptr) = 0; - virtual void removeRefScript (MWWorld::RefData *ref) = 0; - //< Remove the script attached to ref from mLocalScripts + virtual void disable(const MWWorld::Ptr& ptr) = 0; - virtual MWWorld::Ptr getPtr (const std::string& name, bool activeOnly) = 0; - ///< Return a pointer to a liveCellRef with the given name. - /// \param activeOnly do non search inactive cells. + virtual void advanceTime(double hours, bool incremental = false) = 0; + ///< Advance in-game time. - virtual MWWorld::Ptr searchPtr (const std::string& name, bool activeOnly, bool searchInContainers = true) = 0; - ///< Return a pointer to a liveCellRef with the given name. - /// \param activeOnly do non search inactive cells. + virtual std::string_view getMonthName(int month = -1) const = 0; + ///< Return name of month (-1: current month) - virtual MWWorld::Ptr searchPtrViaActorId (int actorId) = 0; - ///< Search is limited to the active cells. + virtual MWWorld::TimeStamp getTimeStamp() const = 0; + ///< Return current in-game time and number of day since new game start. - virtual MWWorld::Ptr searchPtrViaRefNum (const std::string& id, const ESM::RefNum& refNum) = 0; + virtual ESM::EpochTimeStamp getEpochTimeStamp() const = 0; + ///< Return current in-game date and time. - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to find a Ptr in any active cell based on its refNum and mpNum - */ - virtual MWWorld::Ptr searchPtrViaUniqueIndex(int refNum, int mpNum) = 0; - /* - End of tes3mp addition - */ + Make it possible to find a Ptr in any active cell based on its refNum and mpNum + */ + virtual MWWorld::Ptr searchPtrViaUniqueIndex(int refNum, int mpNum) = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to update all Ptrs in active cells that have a certain refId - */ - virtual void updatePtrsWithRefId(std::string refId) = 0; - /* - End of tes3mp addition - */ + Make it possible to update all Ptrs in active cells that have a certain refId + */ + virtual void updatePtrsWithRefId(std::string refId) = 0; + /* + End of tes3mp addition + */ - virtual MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr) = 0; - ///< Return a pointer to a liveCellRef which contains \a ptr. - /// \note Search is limited to the active cells. + virtual bool toggleSky() = 0; + ///< \return Resulting mode - virtual void enable (const MWWorld::Ptr& ptr) = 0; + virtual void changeWeather(const ESM::RefId& region, const unsigned int id) = 0; - virtual void disable (const MWWorld::Ptr& ptr) = 0; + virtual int getCurrentWeather() const = 0; - virtual void advanceTime (double hours, bool incremental = false) = 0; - ///< Advance in-game time. + virtual int getNextWeather() const = 0; - virtual std::string getMonthName (int month = -1) const = 0; - ///< Return name of month (-1: current month) + virtual float getWeatherTransition() const = 0; - virtual MWWorld::TimeStamp getTimeStamp() const = 0; - ///< Return current in-game time and number of day since new game start. + virtual unsigned int getNightDayMode() const = 0; - virtual ESM::EpochTimeStamp getEpochTimeStamp() const = 0; - ///< Return current in-game date and time. + virtual int getMasserPhase() const = 0; - virtual bool toggleSky() = 0; - ///< \return Resulting mode + virtual int getSecundaPhase() const = 0; - virtual void changeWeather(const std::string& region, const unsigned int id) = 0; + virtual void setMoonColour(bool red) = 0; - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to set a specific weather state for a region from elsewhere - in the code - */ - virtual void setRegionWeather(const std::string& region, const unsigned int currentWeather, const unsigned int nextWeather, - const unsigned int queuedWeather, const float transitionFactor, bool force) = 0; - /* - End of tes3mp addition - */ + Make it possible to set a specific weather state for a region from elsewhere + in the code + */ + virtual void setRegionWeather(const std::string& region, const unsigned int currentWeather, const unsigned int nextWeather, + const unsigned int queuedWeather, const float transitionFactor, bool force) = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to check whether the local WeatherManager has the - ability to create weather changes - */ - virtual bool getWeatherCreationState() = 0; - /* - End of tes3mp addition - */ + Make it possible to check whether the local WeatherManager has the + ability to create weather changes + */ + virtual bool getWeatherCreationState() = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to enable and disable the local WeatherManager's ability - to create weather changes - */ - virtual void setWeatherCreationState(bool state) = 0; - /* - End of tes3mp addition - */ + Make it possible to enable and disable the local WeatherManager's ability + to create weather changes + */ + virtual void setWeatherCreationState(bool state) = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to send the current weather in a WorldWeather packet - when requested from elsewhere in the code - */ - virtual void sendWeather() = 0; - /* - End of tes3mp addition - */ + Make it possible to send the current weather in a WorldWeather packet + when requested from elsewhere in the code + */ + virtual void sendWeather() = 0; + /* + End of tes3mp addition + */ - virtual int getCurrentWeather() const = 0; + virtual void modRegion(const ESM::RefId& regionid, const std::vector& chances) = 0; - virtual unsigned int getNightDayMode() const = 0; + virtual float getTimeScaleFactor() const = 0; - virtual int getMasserPhase() const = 0; + virtual float getSimulationTimeScale() const = 0; - virtual int getSecundaPhase() const = 0; + virtual void setSimulationTimeScale(float scale) = 0; - virtual void setMoonColour (bool red) = 0; + virtual void changeToInteriorCell( + std::string_view cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) + = 0; + ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void modRegion(const std::string ®ionid, const std::vector &chances) = 0; + virtual void changeToCell( + const ESM::RefId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) + = 0; + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual float getTimeScaleFactor() const = 0; + virtual MWWorld::Ptr getFacedObject() = 0; + ///< Return pointer to the object the player is looking at, if it is within activation range - virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; - ///< Move to interior cell. - ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes + virtual float getDistanceToFacedObject() = 0; - virtual void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; - ///< Move to exterior cell. - ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes + virtual float getMaxActivationDistance() const = 0; - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true) = 0; - ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes + /// Returns a pointer to the object the provided object would hit (if within the + /// specified distance), and the point where the hit occurs. This will attempt to + /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. + virtual std::pair getHitContact( + const MWWorld::ConstPtr& ptr, float distance, std::vector& targets) + = 0; - virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0; - ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. + virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) = 0; + ///< Adjust position after load to be on ground. Must be called after model load. + /// @param force do this even if the ptr is flying - virtual void markCellAsUnchanged() = 0; + virtual void fixPosition() = 0; + ///< Attempt to fix position so that the player is not stuck inside the geometry. - virtual MWWorld::Ptr getFacedObject() = 0; - ///< Return pointer to the object the player is looking at, if it is within activation range + /// @note No-op for items in containers. Use ContainerStore::removeItem instead. + virtual void deleteObject(const MWWorld::Ptr& ptr) = 0; + virtual void undeleteObject(const MWWorld::Ptr& ptr) = 0; + + /* + Start of tes3mp addition + + This has been declared here so it can be accessed from places + other than MWWorld::World + */ + virtual void updateWeather(float duration, bool paused = false) = 0; + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + This has been declared here so it can be accessed from places + other than MWWorld::World + */ + virtual void PCDropped(const MWWorld::Ptr& item) = 0; + /* + End of tes3mp addition + */ + + virtual MWWorld::Ptr moveObject( + const MWWorld::Ptr& ptr, const osg::Vec3f& position, bool movePhysics = true, bool moveToActive = false) + = 0; + ///< @return an updated Ptr in case the Ptr's cell changes + + virtual MWWorld::Ptr moveObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* newCell, + const osg::Vec3f& position, bool movePhysics = true, bool keepActive = false) + = 0; + ///< @return an updated Ptr + + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr& ptr, const osg::Vec3f& vec, bool moveToActive) = 0; + ///< @return an updated Ptr + + virtual void scaleObject(const MWWorld::Ptr& ptr, float scale, bool force = false) = 0; + + virtual void rotateObject( + const MWWorld::Ptr& ptr, const osg::Vec3f& rot, RotationFlags flags = RotationFlag_inverseOrder) + = 0; + + virtual MWWorld::Ptr placeObject( + const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) + = 0; + ///< Place an object. Makes a copy of the Ptr. + + virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, + MWWorld::CellStore* referenceCell, int direction, float distance) + = 0; + ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted + ///< placement + /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is + /// obstructed). + + virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) = 0; + ///< Queues movement for \a ptr (in local space), to be applied in the next call to + /// doPhysics. + + virtual void updateAnimatedCollisionShape(const MWWorld::Ptr& ptr) = 0; + + virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0; + + virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) + = 0; + + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; + virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; + + virtual bool toggleCollisionMode() = 0; + ///< Toggle collision mode for player. If disabled player object should ignore + /// collisions and gravity. + /// \return Resulting mode + + virtual bool toggleRenderMode(MWRender::RenderMode mode) = 0; + ///< Toggle a render mode. + ///< \return Resulting mode + + virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) = 0; + ///< copy and place an object into the gameworld at the specified cursor position + /// @param object + /// @param cursor X (relative 0-1) + /// @param cursor Y (relative 0-1) + /// @param number of objects to place + + virtual MWWorld::Ptr dropObjectOnGround(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) + = 0; + ///< copy and place an object into the gameworld at the given actor's position + /// @param actor giving the dropped object position + /// @param object + /// @param number of objects to place + + virtual bool canPlaceObject(float cursorX, float cursorY) = 0; + ///< @return true if it is possible to place on object at specified cursor location + + virtual void processChangedSettings(const std::set>& settings) = 0; + + virtual bool isFlying(const MWWorld::Ptr& ptr) const = 0; + virtual bool isSlowFalling(const MWWorld::Ptr& ptr) const = 0; + virtual bool isSwimming(const MWWorld::ConstPtr& object) const = 0; + virtual bool isWading(const MWWorld::ConstPtr& object) const = 0; + /// Is the head of the creature underwater? + virtual bool isSubmerged(const MWWorld::ConstPtr& object) const = 0; + virtual bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f& pos) const = 0; + virtual bool isUnderwater(const MWWorld::ConstPtr& object, const float heightRatio) const = 0; + virtual bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr& target) const = 0; + virtual bool isOnGround(const MWWorld::Ptr& ptr) const = 0; + + virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; + + virtual MWRender::Camera* getCamera() = 0; + virtual void togglePOV(bool force = false) = 0; + virtual bool isFirstPerson() const = 0; + virtual bool isPreviewModeEnabled() const = 0; + virtual bool toggleVanityMode(bool enable) = 0; + virtual bool vanityRotateCamera(float* rot) = 0; + virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; + virtual void disableDeferredPreviewRotation() = 0; + + virtual void saveLoaded() = 0; + + virtual void setupPlayer() = 0; + virtual void renderPlayer() = 0; + + /// open or close a non-teleport door (depending on current state) + virtual void activateDoor(const MWWorld::Ptr& door) = 0; + /// update movement state of a non-teleport door as specified + /// @param state see MWClass::setDoorState + /// @note throws an exception when invoked on a teleport door + virtual void activateDoor(const MWWorld::Ptr& door, MWWorld::DoorState state) = 0; + + virtual void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector& actors) + = 0; ///< get a list of actors standing on \a object + virtual bool getPlayerStandingOn(const MWWorld::ConstPtr& object) + = 0; ///< @return true if the player is standing on \a object + virtual bool getActorStandingOn(const MWWorld::ConstPtr& object) + = 0; ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) + = 0; ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith(const MWWorld::ConstPtr& object) + = 0; ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors(const MWWorld::ConstPtr& object, float dmgPerSecond) = 0; + ///< Apply a health difference to any actors standing on \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual void hurtCollidingActors(const MWWorld::ConstPtr& object, float dmgPerSecond) = 0; + ///< Apply a health difference to any actors colliding with \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + + virtual float getWindSpeed() = 0; + + virtual void getContainersOwnedBy(const MWWorld::ConstPtr& npc, std::vector& out) = 0; + ///< get all containers in active cells owned by this Npc + virtual void getItemsOwnedBy(const MWWorld::ConstPtr& npc, std::vector& out) = 0; + ///< get all items in active cells owned by this Npc + + virtual bool getLOS(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& targetActor) = 0; + ///< get Line of Sight (morrowind stupid implementation) + + virtual float getDistToNearestRayHit( + const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false) + = 0; + + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; + + enum RestPermitted + { + Rest_Allowed = 0, + Rest_OnlyWaiting = 1, + Rest_PlayerIsInAir = 2, + Rest_PlayerIsUnderwater = 3, + Rest_EnemiesAreNearby = 4 + }; + + /// check if the player is allowed to rest + virtual RestPermitted canRest() const = 0; + + /// \todo Probably shouldn't be here + virtual MWRender::Animation* getAnimation(const MWWorld::Ptr& ptr) = 0; + virtual const MWRender::Animation* getAnimation(const MWWorld::ConstPtr& ptr) const = 0; + virtual void reattachPlayerCamera() = 0; + + /// \todo this does not belong here + virtual void screenshot(osg::Image* image, int w, int h) = 0; + virtual bool screenshot360(osg::Image* image) = 0; + + /// Find default position inside exterior cell specified by name + /// \return empty RefId if exterior with given name not exists, the cell's RefId otherwise + virtual ESM::RefId findExteriorPosition(std::string_view name, ESM::Position& pos) = 0; + + /// Find default position inside interior cell specified by name + /// \return empty RefId if interior with given name not exists, the cell's RefId otherwise + virtual ESM::RefId findInteriorPosition(std::string_view name, ESM::Position& pos) = 0; + + /// Enables or disables use of teleport spell effects (recall, intervention, etc). + virtual void enableTeleporting(bool enable) = 0; + + /// Returns true if teleport spell effects are allowed. + virtual bool isTeleportingEnabled() const = 0; + + /// Enables or disables use of levitation spell effect. + virtual void enableLevitation(bool enable) = 0; + + /// Returns true if levitation spell effect is allowed. + virtual bool isLevitationEnabled() const = 0; + + virtual bool getGodModeState() const = 0; + + virtual bool toggleGodMode() = 0; + + virtual bool toggleScripts() = 0; + virtual bool getScriptsEnabled() const = 0; + + /** + * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. + * @param actor + * @return Success or the failure condition. + */ + virtual MWWorld::SpellCastState startSpellCast(const MWWorld::Ptr& actor) = 0; + + /* + Start of tes3mp addition + + Make it possible to set the inertial force of a Ptr directly + */ + virtual void setInertialForce(const MWWorld::Ptr& ptr, const osg::Vec3f &force) = 0; + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to set whether a Ptr is on the ground or not, needed for proper + synchronization in multiplayer + */ + virtual void setOnGround(const MWWorld::Ptr& ptr, bool onGround) = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + /* + Start of tes3mp addition - This has been declared here so it can be accessed from places - other than MWWorld::World - */ - virtual void updateWeather(float duration, bool paused = false) = 0; - /* - End of tes3mp addition - */ + Make it possible to set the physics framerate from elsewhere + */ + virtual void setPhysicsFramerate(float physFramerate) = 0; + /* + End of tes3mp addition + */ - /* - Start of tes3mp addition + virtual void castSpell(const MWWorld::Ptr& actor, bool manualSpell = false) = 0; - This has been declared here so it can be accessed from places - other than MWWorld::World - */ - virtual void PCDropped(const MWWorld::Ptr& item) = 0; - /* - End of tes3mp addition - */ + virtual void launchMagicBolt( + const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) + = 0; + virtual void launchProjectile(MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, + const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) + = 0; + virtual void updateProjectilesCasters() = 0; - virtual float getDistanceToFacedObject() = 0; + virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) const = 0; - virtual float getMaxActivationDistance() = 0; + virtual const std::vector& getContentFiles() const = 0; - /// Returns a pointer to the object the provided object would hit (if within the - /// specified distance), and the point where the hit occurs. This will attempt to - /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. - virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector &targets) = 0; + virtual void breakInvisibility(const MWWorld::Ptr& actor) = 0; - virtual void adjustPosition (const MWWorld::Ptr& ptr, bool force) = 0; - ///< Adjust position after load to be on ground. Must be called after model load. - /// @param force do this even if the ptr is flying + // Allow NPCs to use torches? + virtual bool useTorches() const = 0; - virtual void fixPosition () = 0; - ///< Attempt to fix position so that the player is not stuck inside the geometry. + virtual float getSunVisibility() const = 0; + virtual float getSunPercentage() const = 0; - /// @note No-op for items in containers. Use ContainerStore::removeItem instead. - virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; - virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; + virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; - virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0; - ///< @return an updated Ptr in case the Ptr's cell changes + /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) + /// @note id must be lower case + virtual void teleportToClosestMarker(const MWWorld::Ptr& ptr, const ESM::RefId& id) = 0; - virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; - ///< @return an updated Ptr + enum DetectionType + { + Detect_Enchantment, + Detect_Key, + Detect_Creature + }; + /// List all references (filtered by \a type) detected by \a ptr. The range + /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. + /// @note This also works for references in containers. + virtual void listDetectedReferences(const MWWorld::Ptr& ptr, std::vector& out, DetectionType type) + = 0; - virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) = 0; - ///< @return an updated Ptr + /// Update the value of some globals according to the world state, which may be used by dialogue entries. + /// This should be called when initiating a dialogue. + virtual void updateDialogueGlobals() = 0; - virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; + /// Moves all stolen items from \a ptr to the closest evidence chest. + virtual void confiscateStolenItems(const MWWorld::Ptr& ptr) = 0; - virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z, - RotationFlags flags = RotationFlag_inverseOrder) = 0; + virtual void goToJail() = 0; - virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; - ///< Place an object. Makes a copy of the Ptr. + /// Spawn a random creature from a levelled list next to the player + virtual void spawnRandomCreature(const ESM::RefId& creatureList) = 0; - virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) = 0; - ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement - /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). + /// Spawn a blood effect for \a ptr at \a worldPosition + virtual void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) - const = 0; - ///< Convert cell numbers to position. + virtual void spawnEffect(const std::string& model, const std::string& textureOverride, + const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) + = 0; - virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const = 0; - ///< Convert position to cell numbers + virtual void activate(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; - virtual void queueMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) = 0; - ///< Queues movement for \a ptr (in local space), to be applied in the next call to - /// doPhysics. + /// @see MWWorld::WeatherManager::isInStorm + virtual bool isInStorm() const = 0; - /* - Start of tes3mp addition + /// @see MWWorld::WeatherManager::getStormDirection + virtual osg::Vec3f getStormDirection() const = 0; - Make it possible to set the inertial force of a Ptr directly - */ - virtual void setInertialForce(const MWWorld::Ptr& ptr, const osg::Vec3f &force) = 0; - /* - End of tes3mp addition - */ + /// Resets all actors in the current active cells to their original location within that cell. + virtual void resetActors() = 0; - /* - Start of tes3mp addition + virtual bool isWalkingOnWater(const MWWorld::ConstPtr& actor) const = 0; - Make it possible to set whether a Ptr is on the ground or not, needed for proper - synchronization in multiplayer - */ - virtual void setOnGround(const MWWorld::Ptr& ptr, bool onGround) = 0; - /* - End of tes3mp addition - */ + /* + Start of tes3mp addition - /* - Start of tes3mp addition + Useful self-contained method for saving door states + */ + virtual void saveDoorState(const MWWorld::Ptr& door, MWWorld::DoorState state) = 0; + /* + End of tes3mp addition + */ - Make it possible to set the physics framerate from elsewhere - */ - virtual void setPhysicsFramerate(float physFramerate) = 0; - /* - End of tes3mp addition - */ + /* + Start of tes3mp addition - virtual void updateAnimatedCollisionShape(const MWWorld::Ptr &ptr) = 0; + Make it possible to check whether a cell is active + */ + virtual bool isCellActive(const ESM::Cell& cell) = 0; + /* + End of tes3mp addition + */ - virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0; + /* + Start of tes3mp addition - virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask) = 0; - ///< cast a Ray and return true if there is an object in the ray path. + Make it possible to unload a cell from elsewhere + */ + virtual void unloadCell(const ESM::Cell& cell) = 0; + /* + End of tes3mp addition + */ - virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; + /* + Start of tes3mp addition - virtual bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) = 0; + Make it possible to unload all active cells from elsewhere + */ + virtual void unloadActiveCells() = 0; + /* + End of tes3mp addition + */ - virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; - virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; + /* + Start of tes3mp addition - virtual bool toggleCollisionMode() = 0; - ///< Toggle collision mode for player. If disabled player object should ignore - /// collisions and gravity. - /// \return Resulting mode + Clear the CellStore for a specific Cell from elsewhere + */ + virtual void clearCellStore(const ESM::Cell& cell) = 0; + /* + End of tes3mp addition + */ - virtual bool toggleRenderMode (MWRender::RenderMode mode) = 0; - ///< Toggle a render mode. - ///< \return Resulting mode + /// Return a vector aiming the actor's weapon towards a target. + /// @note The length of the vector is the distance between actor and target. + virtual osg::Vec3f aimToTarget( + const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) + = 0; - virtual const ESM::Potion *createRecord (const ESM::Potion& record) = 0; - ///< Create a new record (of type potion) in the ESM store. - /// \return pointer to created record + /// Return the distance between actor's weapon and target's collision box. + virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; - virtual const ESM::Spell *createRecord (const ESM::Spell& record) = 0; - ///< Create a new record (of type spell) in the ESM store. - /// \return pointer to created record + virtual void addContainerScripts(const MWWorld::Ptr& reference, MWWorld::CellStore* cell) = 0; + virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; - virtual const ESM::Class *createRecord (const ESM::Class& record) = 0; - ///< Create a new record (of type class) in the ESM store. - /// \return pointer to created record + virtual bool isPlayerInJail() const = 0; - virtual const ESM::Cell *createRecord (const ESM::Cell& record) = 0; - ///< Create a new record (of type cell) in the ESM store. - /// \return pointer to created record + virtual void rest(double hours) = 0; - virtual const ESM::NPC *createRecord(const ESM::NPC &record) = 0; - ///< Create a new record (of type npc) in the ESM store. - /// \return pointer to created record + virtual void setPlayerTraveling(bool traveling) = 0; + virtual bool isPlayerTraveling() const = 0; - virtual const ESM::Creature *createRecord (const ESM::Creature &record) = 0; - ///< Create a new record (of type creature) in the ESM store. - /// \return pointer to created record - - virtual const ESM::Armor *createRecord (const ESM::Armor& record) = 0; - ///< Create a new record (of type armor) in the ESM store. - /// \return pointer to created record - - virtual const ESM::Weapon *createRecord (const ESM::Weapon& record) = 0; - ///< Create a new record (of type weapon) in the ESM store. - /// \return pointer to created record - - virtual const ESM::Clothing *createRecord (const ESM::Clothing& record) = 0; - ///< Create a new record (of type clothing) in the ESM store. - /// \return pointer to created record - - virtual const ESM::Enchantment *createRecord (const ESM::Enchantment& record) = 0; - ///< Create a new record (of type enchantment) in the ESM store. - /// \return pointer to created record - - virtual const ESM::Book *createRecord (const ESM::Book& record) = 0; - ///< Create a new record (of type book) in the ESM store. - /// \return pointer to created record - - virtual const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record) = 0; - ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. - /// \return pointer to created record - - virtual const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record) = 0; - ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. - /// \return pointer to created record - - virtual const ESM::Creature *createOverrideRecord (const ESM::Creature& record) = 0; - ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. - /// \return pointer to created record - - virtual const ESM::NPC *createOverrideRecord (const ESM::NPC& record) = 0; - ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. - /// \return pointer to created record - - virtual const ESM::Container *createOverrideRecord (const ESM::Container& record) = 0; - ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. - /// \return pointer to created record - - virtual void update (float duration, bool paused) = 0; - virtual void updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) = 0; - - virtual void updateWindowManager () = 0; - - virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) = 0; - ///< copy and place an object into the gameworld at the specified cursor position - /// @param object - /// @param cursor X (relative 0-1) - /// @param cursor Y (relative 0-1) - /// @param number of objects to place - - virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) = 0; - ///< copy and place an object into the gameworld at the given actor's position - /// @param actor giving the dropped object position - /// @param object - /// @param number of objects to place - - virtual bool canPlaceObject (float cursorX, float cursorY) = 0; - ///< @return true if it is possible to place on object at specified cursor location - - virtual void processChangedSettings (const std::set< std::pair >& settings) = 0; - - virtual bool isFlying(const MWWorld::Ptr &ptr) const = 0; - virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const = 0; - virtual bool isSwimming(const MWWorld::ConstPtr &object) const = 0; - virtual bool isWading(const MWWorld::ConstPtr &object) const = 0; - ///Is the head of the creature underwater? - virtual bool isSubmerged(const MWWorld::ConstPtr &object) const = 0; - virtual bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const = 0; - virtual bool isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const = 0; - virtual bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const = 0; - virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; - - virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; - - virtual void togglePOV(bool force = false) = 0; - virtual bool isFirstPerson() const = 0; - virtual bool isPreviewModeEnabled() const = 0; - virtual void togglePreviewMode(bool enable) = 0; - virtual bool toggleVanityMode(bool enable) = 0; - virtual void allowVanityMode(bool allow) = 0; - virtual bool vanityRotateCamera(float * rot) = 0; - virtual void adjustCameraDistance(float dist) = 0; - virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; - virtual void disableDeferredPreviewRotation() = 0; - - virtual void saveLoaded() = 0; - - virtual void setupPlayer() = 0; - virtual void renderPlayer() = 0; - - /// open or close a non-teleport door (depending on current state) - virtual void activateDoor(const MWWorld::Ptr& door) = 0; - /// update movement state of a non-teleport door as specified - /// @param state see MWClass::setDoorState - /// @note throws an exception when invoked on a teleport door - virtual void activateDoor(const MWWorld::Ptr& door, MWWorld::DoorState state) = 0; - - /* - Start of tes3mp addition - - Useful self-contained method for saving door states - */ - virtual void saveDoorState(const MWWorld::Ptr& door, MWWorld::DoorState state) = 0; - /* - End of tes3mp addition - */ - - /* - Start of tes3mp addition - - Make it possible to check whether a cell is active - */ - virtual bool isCellActive(const ESM::Cell& cell) = 0; - /* - End of tes3mp addition - */ - - /* - Start of tes3mp addition - - Make it possible to unload a cell from elsewhere - */ - virtual void unloadCell(const ESM::Cell& cell) = 0; - /* - End of tes3mp addition - */ - - /* - Start of tes3mp addition - - Make it possible to unload all active cells from elsewhere - */ - virtual void unloadActiveCells() = 0; - /* - End of tes3mp addition - */ - - /* - Start of tes3mp addition - - Clear the CellStore for a specific Cell from elsewhere - */ - virtual void clearCellStore(const ESM::Cell& cell) = 0; - /* - End of tes3mp addition - */ - - virtual void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) = 0; ///< get a list of actors standing on \a object - virtual bool getPlayerStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is standing on \a object - virtual bool getActorStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is standing on \a object - virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is colliding with \a object - virtual bool getActorCollidingWith (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is colliding with \a object - virtual void hurtStandingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) = 0; - ///< Apply a health difference to any actors standing on \a object. - /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. - virtual void hurtCollidingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) = 0; - ///< Apply a health difference to any actors colliding with \a object. - /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. - - virtual float getWindSpeed() = 0; - - virtual void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) = 0; - ///< get all containers in active cells owned by this Npc - virtual void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) = 0; - ///< get all items in active cells owned by this Npc - - virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor) = 0; - ///< get Line of Sight (morrowind stupid implementation) - - virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false) = 0; - - virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; - - enum RestPermitted - { - Rest_Allowed = 0, - Rest_OnlyWaiting = 1, - Rest_PlayerIsInAir = 2, - Rest_PlayerIsUnderwater = 3, - Rest_EnemiesAreNearby = 4 - }; - - /// check if the player is allowed to rest - virtual RestPermitted canRest() const = 0; + virtual void rotateWorldObject(const MWWorld::Ptr& ptr, const osg::Quat& rotate) = 0; - /// \todo Probably shouldn't be here - virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0; - virtual const MWRender::Animation* getAnimation(const MWWorld::ConstPtr &ptr) const = 0; - virtual void reattachPlayerCamera() = 0; + /// Return terrain height at \a worldPos position. + virtual float getTerrainHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) const = 0; - /// \todo this does not belong here - virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual bool screenshot360 (osg::Image* image) = 0; + /// Return physical or rendering half extents of the given actor. + virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering = false) const = 0; - /// Find default position inside exterior cell specified by name - /// \return false if exterior with given name not exists, true otherwise - virtual bool findExteriorPosition(const std::string &name, ESM::Position &pos) = 0; + /// Export scene graph to a file and return the filename. + /// \param ptr object to export scene graph for (if empty, export entire scene graph) + virtual std::filesystem::path exportSceneGraph(const MWWorld::Ptr& ptr) = 0; - /// Find default position inside interior cell specified by name - /// \return false if interior with given name not exists, true otherwise - virtual bool findInteriorPosition(const std::string &name, ESM::Position &pos) = 0; + /// Preload VFX associated with this effect list + virtual void preloadEffects(const ESM::EffectList* effectList) = 0; - /// Enables or disables use of teleport spell effects (recall, intervention, etc). - virtual void enableTeleporting(bool enable) = 0; + virtual DetourNavigator::Navigator* getNavigator() const = 0; - /// Returns true if teleport spell effects are allowed. - virtual bool isTeleportingEnabled() const = 0; + virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; - /// Enables or disables use of levitation spell effect. - virtual void enableLevitation(bool enable) = 0; + virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0; - /// Returns true if levitation spell effect is allowed. - virtual bool isLevitationEnabled() const = 0; + virtual void setNavMeshNumberToRender(const std::size_t value) = 0; - virtual bool getGodModeState() const = 0; + virtual DetourNavigator::AgentBounds getPathfindingAgentBounds(const MWWorld::ConstPtr& actor) const = 0; - virtual bool toggleGodMode() = 0; + virtual bool hasCollisionWithDoor( + const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; - virtual bool toggleScripts() = 0; - virtual bool getScriptsEnabled() const = 0; + virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + std::span ignore, std::vector* occupyingActors = nullptr) const = 0; - /** - * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. - * @param actor - * @return true if the spell can be casted (i.e. the animation should start) - */ - virtual bool startSpellCast (const MWWorld::Ptr& actor) = 0; + virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; - virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0; + virtual std::vector getAll(const ESM::RefId& id) = 0; - virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; - virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, - const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; - virtual void updateProjectilesCasters() = 0; + virtual Misc::Rng::Generator& getPrng() = 0; - virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0; + virtual MWRender::RenderingManager* getRenderingManager() = 0; - virtual const std::vector& getContentFiles() const = 0; + virtual MWRender::PostProcessor* getPostProcessor() = 0; - virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; - - // Allow NPCs to use torches? - virtual bool useTorches() const = 0; - - virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; - - /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) - /// @note id must be lower case - virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr, - const std::string& id) = 0; - - enum DetectionType - { - Detect_Enchantment, - Detect_Key, - Detect_Creature - }; - /// List all references (filtered by \a type) detected by \a ptr. The range - /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. - /// @note This also works for references in containers. - virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, - DetectionType type) = 0; - - /// Update the value of some globals according to the world state, which may be used by dialogue entries. - /// This should be called when initiating a dialogue. - virtual void updateDialogueGlobals() = 0; - - /// Moves all stolen items from \a ptr to the closest evidence chest. - virtual void confiscateStolenItems(const MWWorld::Ptr& ptr) = 0; - - virtual void goToJail () = 0; - - /// Spawn a random creature from a levelled list next to the player - virtual void spawnRandomCreature(const std::string& creatureList) = 0; - - /// Spawn a blood effect for \a ptr at \a worldPosition - virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0; - - virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, - const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, - const std::string& sourceName, const bool fromProjectile=false) = 0; - - virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; - - /// @see MWWorld::WeatherManager::isInStorm - virtual bool isInStorm() const = 0; - - /// @see MWWorld::WeatherManager::getStormDirection - virtual osg::Vec3f getStormDirection() const = 0; - - /// Resets all actors in the current active cells to their original location within that cell. - virtual void resetActors() = 0; - - virtual bool isWalkingOnWater (const MWWorld::ConstPtr& actor) const = 0; - - /// Return a vector aiming the actor's weapon towards a target. - /// @note The length of the vector is the distance between actor and target. - virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) = 0; - - /// Return the distance between actor's weapon and target's collision box. - virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; - - virtual void addContainerScripts(const MWWorld::Ptr& reference, MWWorld::CellStore* cell) = 0; - virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; - - virtual bool isPlayerInJail() const = 0; - - virtual void rest(double hours) = 0; - virtual void rechargeItems(double duration, bool activeOnly) = 0; - - virtual void setPlayerTraveling(bool traveling) = 0; - virtual bool isPlayerTraveling() const = 0; - - virtual void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) = 0; - - /// Return terrain height at \a worldPos position. - virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0; - - /// Return physical or rendering half extents of the given actor. - virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const = 0; - - /// Export scene graph to a file and return the filename. - /// \param ptr object to export scene graph for (if empty, export entire scene graph) - virtual std::string exportSceneGraph(const MWWorld::Ptr& ptr) = 0; - - /// Preload VFX associated with this effect list - virtual void preloadEffects(const ESM::EffectList* effectList) = 0; - - virtual DetourNavigator::Navigator* getNavigator() const = 0; - - virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, - const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; - - virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0; - - virtual void setNavMeshNumberToRender(const std::size_t value) = 0; - - /// Return physical half extents of the given actor to be used in pathfinding - virtual osg::Vec3f getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const = 0; - - virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; - - virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0; - - virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; - - virtual std::vector getAll(const std::string& id) = 0; + virtual void setActorActive(const MWWorld::Ptr& ptr, bool value) = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index c54b1c369..9e99b4cac 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,6 +1,11 @@ #include "activator.hpp" -#include +#include + +#include +#include +#include +#include #include #include @@ -8,12 +13,12 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/action.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/action.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/nullaction.hpp" +#include "../mwworld/ptr.hpp" #include "../mwphysics/physicssystem.hpp" @@ -22,37 +27,44 @@ #include "../mwrender/vismask.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwmechanics/npcstats.hpp" +#include "classmodel.hpp" namespace MWClass { + Activator::Activator() + : MWWorld::RegisteredClass(ESM::Activator::sRecordId) + { + } - void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + void Activator::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, true); + renderingInterface.getObjects().insertModel(ptr, model); ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model); + insertObjectPhysics(ptr, model, rotation, physics); } - std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const + void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); + } - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; + std::string Activator::getModel(const MWWorld::ConstPtr& ptr) const + { + return getClassModel(ptr); } bool Activator::isActivator() const @@ -65,89 +77,83 @@ namespace MWClass return true; } - std::string Activator::getName (const MWWorld::ConstPtr& ptr) const + std::string_view Activator::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mName; } - std::string Activator::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Activator::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - void Activator::registerSelf() - { - std::shared_ptr instance (new Activator); - - registerClass (typeid (ESM::Activator).name(), instance); - } - - bool Activator::hasToolTip (const MWWorld::ConstPtr& ptr) const + bool Activator::hasToolTip(const MWWorld::ConstPtr& ptr) const { return !getName(ptr).empty(); } - bool Activator::allowTelekinesis(const MWWorld::ConstPtr &ptr) const { - return false; - } - - MWGui::ToolTipInfo Activator::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Activator::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; return info; } - std::shared_ptr Activator::activate(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const + std::unique_ptr Activator::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { - if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfActivator"); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound* sound = store.get().searchRandom("WolfActivator", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); - if(sound) action->setSound(sound->mId); + std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); + if (sound) + action->setSound(sound->mId); return action; } - return std::shared_ptr(new MWWorld::NullAction); + return std::make_unique(); } - - MWWorld::Ptr Activator::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Activator::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const + ESM::RefId Activator::getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const { - const std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - std::string creatureId; + const std::string model + = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::RefId* creatureId = nullptr; + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - for (const ESM::Creature &iter : store.get()) + for (const ESM::Creature& iter : store.get()) { - if (!iter.mModel.empty() && Misc::StringUtils::ciEqual(model, "meshes\\" + iter.mModel)) + if (!iter.mModel.empty() + && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(iter.mModel, vfs))) { - creatureId = !iter.mOriginal.empty() ? iter.mOriginal : iter.mId; + creatureId = !iter.mOriginal.empty() ? &iter.mOriginal : &iter.mId; break; } } @@ -155,37 +161,40 @@ namespace MWClass int type = getSndGenTypeFromName(name); std::vector fallbacksounds; - if (!creatureId.empty()) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (creatureId && !creatureId->empty()) { std::vector sounds; - for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) + for (auto sound = store.get().begin(); sound != store.get().end(); + ++sound) { - if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature))) + if (type == sound->mType && !sound->mCreature.empty() && (*creatureId == sound->mCreature)) sounds.push_back(&*sound); if (type == sound->mType && sound->mCreature.empty()) fallbacksounds.push_back(&*sound); } if (!sounds.empty()) - return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound; if (!fallbacksounds.empty()) - return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; } else { // The activator doesn't have a corresponding creature ID, but we can try to use the defaults - for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) + for (auto sound = store.get().begin(); sound != store.get().end(); + ++sound) if (type == sound->mType && sound->mCreature.empty()) fallbacksounds.push_back(&*sound); if (!fallbacksounds.empty()) - return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; } - return std::string(); + return ESM::RefId(); } - int Activator::getSndGenTypeFromName(const std::string &name) + int Activator::getSndGenTypeFromName(std::string_view name) { if (name == "left") return ESM::SoundGenerator::LeftFoot; @@ -204,6 +213,6 @@ namespace MWClass if (name == "land") return ESM::SoundGenerator::Land; - throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); + throw std::runtime_error("Unexpected soundgen type: " + std::string(name)); } } diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 10ace6f74..309e163ab 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -1,52 +1,54 @@ #ifndef GAME_MWCLASS_ACTIVATOR_H #define GAME_MWCLASS_ACTIVATOR_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Activator : public MWWorld::Class + class Activator final : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + Activator(); - static int getSndGenTypeFromName(const std::string &name); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - public: + static int getSndGenTypeFromName(std::string_view name); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; - bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; - ///< @return true if this object has a tooltip when focused (default implementation: true) + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const override; - ///< Return whether this class of object can be activated with telekinesis + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; + ///< @return true if this object has a tooltip when focused (default implementation: true) - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - std::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - static void registerSelf(); + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + bool useAnim() const override; + ///< Whether or not to use animated variant of model (default false) - bool useAnim() const override; - ///< Whether or not to use animated variant of model (default false) + bool isActivator() const override; - bool isActivator() const override; - - std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const override; + ESM::RefId getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const override; }; } diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 33aeb26bb..152a4bbba 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -1,15 +1,15 @@ #include "actor.hpp" -#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/movement.hpp" #include "../mwmechanics/magiceffects.hpp" +#include "../mwmechanics/movement.hpp" #include "../mwphysics/physicssystem.hpp" @@ -17,23 +17,17 @@ namespace MWClass { - Actor::Actor() {} - - Actor::~Actor() {} - void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const { - if (!model.empty()) - { - physics.addActor(ptr, model); - if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished()) - MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); - } + physics.addActor(ptr, model); + if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished()) + MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); } bool Actor::useAnim() const @@ -41,33 +35,26 @@ namespace MWClass return true; } - void Actor::block(const MWWorld::Ptr &ptr) const + void Actor::block(const MWWorld::Ptr& ptr) const { const MWWorld::InventoryStore& inv = getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield == inv.end()) return; - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - switch (shield->getClass().getEquipmentSkill(*shield)) - { - case ESM::Skill::LightArmor: - sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); - break; - case ESM::Skill::MediumArmor: - sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); - break; - case ESM::Skill::HeavyArmor: - sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); - break; - default: - return; - } + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield); + if (skill == ESM::Skill::LightArmor) + sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); + else if (skill == ESM::Skill::MediumArmor) + sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f); + else if (skill == ESM::Skill::HeavyArmor) + sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); } osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const { - MWMechanics::Movement &movement = getMovementSettings(ptr); + MWMechanics::Movement& movement = getMovementSettings(ptr); osg::Vec3f vec(movement.mRotation[0], movement.mRotation[1], movement.mRotation[2]); movement.mRotation[0] = 0.0f; movement.mRotation[1] = 0.0f; @@ -79,13 +66,14 @@ namespace MWClass { float weight = getContainerStore(ptr).getWeight(); const MWMechanics::MagicEffects& effects = getCreatureStats(ptr).getMagicEffects(); - weight -= effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).getMagnitude(); + weight -= effects.getOrDefault(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).getMagnitude(); if (ptr != MWMechanics::getPlayer() || !MWBase::Environment::get().getWorld()->getGodModeState()) - weight += effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude(); + weight += effects.getOrDefault(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude(); return (weight < 0) ? 0.0f : weight; } - bool Actor::allowTelekinesis(const MWWorld::ConstPtr &ptr) const { + bool Actor::allowTelekinesis(const MWWorld::ConstPtr& ptr) const + { return false; } diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 3d509b276..5a143c7a7 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -3,6 +3,11 @@ #include "../mwworld/class.hpp" +#include "../mwmechanics/magiceffects.hpp" + +#include +#include + namespace ESM { struct GameSetting; @@ -14,21 +19,33 @@ namespace MWClass class Actor : public MWWorld::Class { protected: + explicit Actor(unsigned type) + : Class(type) + { + } - Actor(); + template + float getSwimSpeedImpl(const MWWorld::Ptr& ptr, const GMST& gmst, const MWMechanics::MagicEffects& mageffects, + float baseSpeed) const + { + return baseSpeed * (1.0f + 0.01f * mageffects.getOrDefault(ESM::MagicEffect::SwiftSwim).getMagnitude()) + * (gmst.fSwimRunBase->mValue.getFloat() + + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat()); + } public: - virtual ~Actor(); + ~Actor() override = default; void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; - void block(const MWWorld::Ptr &ptr) const override; + void block(const MWWorld::Ptr& ptr) const override; osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const override; ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. @@ -44,10 +61,10 @@ namespace MWClass /// Return current movement speed. float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; - + // not implemented - Actor(const Actor&); - Actor& operator= (const Actor&); + Actor(const Actor&) = delete; + Actor& operator=(const Actor&) = delete; }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index b1b8fe570..aa91e92fd 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -12,33 +12,40 @@ End of tes3mp addition */ -#include - #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include +#include -#include "../mwworld/ptr.hpp" #include "../mwworld/actionalchemy.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwphysics/physicssystem.hpp" -#include "../mwworld/nullaction.hpp" +#include "../mwworld/ptr.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" + +#include "classmodel.hpp" namespace MWClass { - - void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Apparatus::Apparatus() + : MWWorld::RegisteredClass(ESM::Apparatus::sRecordId) { - if (!model.empty()) { + } + + void Apparatus::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Apparatus::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -62,77 +69,63 @@ namespace MWClass /* End of tes3mp addition */ + return getClassModel(ptr); } - std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Apparatus::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Apparatus::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Apparatus::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Apparatus::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - std::string Apparatus::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Apparatus::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - int Apparatus::getValue (const MWWorld::ConstPtr& ptr) const + int Apparatus::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Apparatus::registerSelf() + const ESM::RefId& Apparatus::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Apparatus); - - registerClass (typeid (ESM::Apparatus).name(), instance); + static const auto sound = ESM::RefId::stringRefId("Item Apparatus Up"); + return sound; } - std::string Apparatus::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Apparatus::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Apparatus Up"); + static const auto sound = ESM::RefId::stringRefId("Item Apparatus Down"); + return sound; } - std::string Apparatus::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const std::string& Apparatus::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Apparatus Down"); - } - - std::string Apparatus::getInventoryIcon (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Apparatus::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Apparatus::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -140,35 +133,36 @@ namespace MWClass text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; return info; } - std::shared_ptr Apparatus::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Apparatus::use(const MWWorld::Ptr& ptr, bool force) const { - return std::shared_ptr(new MWWorld::ActionAlchemy(force)); + return std::make_unique(force); } - MWWorld::Ptr Apparatus::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Apparatus::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Apparatus::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Apparatus::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Apparatus) != 0; } - float Apparatus::getWeight(const MWWorld::ConstPtr &ptr) const + float Apparatus::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 8087c57ba..ce0916c07 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -1,57 +1,57 @@ #ifndef GAME_MWCLASS_APPARATUS_H #define GAME_MWCLASS_APPARATUS_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Apparatus : public MWWorld::Class + class Apparatus : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + Apparatus(); - public: + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - float getWeight (const MWWorld::ConstPtr& ptr) const override; + public: + float getWeight(const MWWorld::ConstPtr& ptr) const override; - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - static void registerSelf(); + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - std::string getModel(const MWWorld::ConstPtr &ptr) const override; - - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; }; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 5a8151048..a33af24ae 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -13,41 +13,52 @@ End of tes3mp addition */ -#include -#include -#include +#include + +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwphysics/physicssystem.hpp" -#include "../mwworld/nullaction.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/ptr.hpp" -#include "../mwrender/objects.hpp" -#include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" +#include "../mwrender/objects.hpp" +#include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" + +#include "classmodel.hpp" namespace MWClass { - - void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Armor::Armor() + : MWWorld::RegisteredClass(ESM::Armor::sRecordId) { - if (!model.empty()) { + } + + void Armor::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Armor::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -71,63 +82,50 @@ namespace MWClass /* End of tes3mp addition */ + return getClassModel(ptr); } - std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Armor::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Armor::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Armor::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Armor::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - bool Armor::hasItemHealth (const MWWorld::ConstPtr& ptr) const + bool Armor::hasItemHealth(const MWWorld::ConstPtr& ptr) const { return true; } - int Armor::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const + int Armor::getItemMaxHealth(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mHealth; } - std::string Armor::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Armor::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Armor::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const + std::pair, bool> Armor::getEquipmentSlots(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); std::vector slots_; const int size = 11; - static const int sMapping[size][2] = - { - { ESM::Armor::Helmet, MWWorld::InventoryStore::Slot_Helmet }, + static const int sMapping[size][2] = { { ESM::Armor::Helmet, MWWorld::InventoryStore::Slot_Helmet }, { ESM::Armor::Cuirass, MWWorld::InventoryStore::Slot_Cuirass }, { ESM::Armor::LPauldron, MWWorld::InventoryStore::Slot_LeftPauldron }, { ESM::Armor::RPauldron, MWWorld::InventoryStore::Slot_RightPauldron }, @@ -137,120 +135,139 @@ namespace MWClass { ESM::Armor::RGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }, { ESM::Armor::Shield, MWWorld::InventoryStore::Slot_CarriedLeft }, { ESM::Armor::LBracer, MWWorld::InventoryStore::Slot_LeftGauntlet }, - { ESM::Armor::RBracer, MWWorld::InventoryStore::Slot_RightGauntlet } - }; + { ESM::Armor::RBracer, MWWorld::InventoryStore::Slot_RightGauntlet } }; - for (int i=0; imBase->mData.mType) + for (int i = 0; i < size; ++i) + if (sMapping[i][0] == ref->mBase->mData.mType) { - slots_.push_back (int (sMapping[i][1])); + slots_.push_back(int(sMapping[i][1])); break; } - return std::make_pair (slots_, false); + return std::make_pair(slots_, false); } - int Armor::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const + ESM::RefId Armor::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); - std::string typeGmst; + std::string_view typeGmst; switch (ref->mBase->mData.mType) { - case ESM::Armor::Helmet: typeGmst = "iHelmWeight"; break; - case ESM::Armor::Cuirass: typeGmst = "iCuirassWeight"; break; + case ESM::Armor::Helmet: + typeGmst = "iHelmWeight"; + break; + case ESM::Armor::Cuirass: + typeGmst = "iCuirassWeight"; + break; case ESM::Armor::LPauldron: - case ESM::Armor::RPauldron: typeGmst = "iPauldronWeight"; break; - case ESM::Armor::Greaves: typeGmst = "iGreavesWeight"; break; - case ESM::Armor::Boots: typeGmst = "iBootsWeight"; break; + case ESM::Armor::RPauldron: + typeGmst = "iPauldronWeight"; + break; + case ESM::Armor::Greaves: + typeGmst = "iGreavesWeight"; + break; + case ESM::Armor::Boots: + typeGmst = "iBootsWeight"; + break; case ESM::Armor::LGauntlet: - case ESM::Armor::RGauntlet: typeGmst = "iGauntletWeight"; break; - case ESM::Armor::Shield: typeGmst = "iShieldWeight"; break; + case ESM::Armor::RGauntlet: + typeGmst = "iGauntletWeight"; + break; + case ESM::Armor::Shield: + typeGmst = "iShieldWeight"; + break; case ESM::Armor::LBracer: - case ESM::Armor::RBracer: typeGmst = "iGauntletWeight"; break; + case ESM::Armor::RBracer: + typeGmst = "iGauntletWeight"; + break; } if (typeGmst.empty()) - return -1; + return {}; - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); float iWeight = floor(gmst.find(typeGmst)->mValue.getFloat()); float epsilon = 0.0005f; - if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fLightMaxMod")->mValue.getFloat() + epsilon) + if (ref->mBase->mData.mWeight <= iWeight * gmst.find("fLightMaxMod")->mValue.getFloat() + epsilon) return ESM::Skill::LightArmor; - if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fMedMaxMod")->mValue.getFloat() + epsilon) + if (ref->mBase->mData.mWeight <= iWeight * gmst.find("fMedMaxMod")->mValue.getFloat() + epsilon) return ESM::Skill::MediumArmor; else return ESM::Skill::HeavyArmor; } - int Armor::getValue (const MWWorld::ConstPtr& ptr) const + int Armor::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Armor::registerSelf() + const ESM::RefId& Armor::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Armor); + const ESM::RefId es = getEquipmentSkill(ptr); + static const ESM::RefId lightUp = ESM::RefId::stringRefId("Item Armor Light Up"); + static const ESM::RefId mediumUp = ESM::RefId::stringRefId("Item Armor Medium Up"); + static const ESM::RefId heavyUp = ESM::RefId::stringRefId("Item Armor Heavy Up"); - registerClass (typeid (ESM::Armor).name(), instance); - } - - std::string Armor::getUpSoundId (const MWWorld::ConstPtr& ptr) const - { - int es = getEquipmentSkill(ptr); if (es == ESM::Skill::LightArmor) - return std::string("Item Armor Light Up"); + return lightUp; else if (es == ESM::Skill::MediumArmor) - return std::string("Item Armor Medium Up"); + return mediumUp; else - return std::string("Item Armor Heavy Up"); + return heavyUp; } - std::string Armor::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Armor::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - int es = getEquipmentSkill(ptr); + const ESM::RefId es = getEquipmentSkill(ptr); + static const ESM::RefId lightDown = ESM::RefId::stringRefId("Item Armor Light Down"); + static const ESM::RefId mediumDown = ESM::RefId::stringRefId("Item Armor Medium Down"); + static const ESM::RefId heavyDown = ESM::RefId::stringRefId("Item Armor Heavy Down"); if (es == ESM::Skill::LightArmor) - return std::string("Item Armor Light Down"); + return lightDown; else if (es == ESM::Skill::MediumArmor) - return std::string("Item Armor Medium Down"); + return mediumDown; else - return std::string("Item Armor Heavy Down"); + return heavyDown; } - std::string Armor::getInventoryIcon (const MWWorld::ConstPtr& ptr) const + const std::string& Armor::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Armor::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Armor::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; // get armor type string (light/medium/heavy) - std::string typeText; + std::string_view typeText; if (ref->mBase->mData.mWeight == 0) - typeText = ""; + { + // no type + } else { - int armorType = getEquipmentSkill(ptr); + const ESM::RefId armorType = getEquipmentSkill(ptr); if (armorType == ESM::Skill::LightArmor) typeText = "#{sLight}"; else if (armorType == ESM::Skill::MediumArmor) @@ -259,21 +276,26 @@ namespace MWClass typeText = "#{sHeavy}"; } - text += "\n#{sArmorRating}: " + MWGui::ToolTips::toString(static_cast(getEffectiveArmorRating(ptr, - MWMechanics::getPlayer()))); + text += "\n#{sArmorRating}: " + + MWGui::ToolTips::toString(static_cast(getEffectiveArmorRating(ptr, MWMechanics::getPlayer()))); int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" - + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); + + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); - if (typeText != "") - text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")"; + if (!typeText.empty()) + { + text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " ("; + text += typeText; + text += ')'; + } text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; @@ -285,22 +307,23 @@ namespace MWClass return info; } - std::string Armor::getEnchantment (const MWWorld::ConstPtr& ptr) const + ESM::RefId Armor::getEnchantment(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Armor::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + const ESM::RefId& Armor::applyEnchantment( + const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, const std::string& newName) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); ESM::Armor newItem = *ref->mBase; - newItem.mId=""; - newItem.mName=newName; - newItem.mData.mEnchant=enchCharge; - newItem.mEnchant=enchId; + newItem.mId = ESM::RefId(); + newItem.mName = newName; + newItem.mData.mEnchant = enchCharge; + newItem.mEnchant = enchId; /* Start of tes3mp addition @@ -313,111 +336,114 @@ namespace MWClass End of tes3mp addition */ - const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem); + const ESM::Armor* record = MWBase::Environment::get().getESMStore()->insert(newItem); return record->mId; } - float Armor::getEffectiveArmorRating(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &actor) const + float Armor::getEffectiveArmorRating(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& actor) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); - int armorSkillType = getEquipmentSkill(ptr); + const ESM::RefId armorSkillType = getEquipmentSkill(ptr); float armorSkill = actor.getClass().getSkill(actor, armorSkillType); - const MWBase::World *world = MWBase::Environment::get().getWorld(); - int iBaseArmorSkill = world->getStore().get().find("iBaseArmorSkill")->mValue.getInteger(); + int iBaseArmorSkill = MWBase::Environment::get() + .getESMStore() + ->get() + .find("iBaseArmorSkill") + ->mValue.getInteger(); - if(ref->mBase->mData.mWeight == 0) + if (ref->mBase->mData.mWeight == 0) return ref->mBase->mData.mArmor; else return ref->mBase->mData.mArmor * armorSkill / static_cast(iBaseArmorSkill); } - std::pair Armor::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + std::pair Armor::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const { const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); if (getItemHealth(ptr) == 0) - return std::make_pair(0, "#{sInventoryMessage1}"); + return { 0, "#{sInventoryMessage1}" }; // slots that this item can be equipped in std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) - return std::make_pair(0, ""); + return { 0, {} }; if (npc.getClass().isNpc()) { - std::string npcRace = npc.get()->mBase->mRace; + const ESM::RefId& npcRace = npc.get()->mBase->mRace; // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); - if(race->mData.mFlags & ESM::Race::Beast) + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(npcRace); + if (race->mData.mFlags & ESM::Race::Beast) { std::vector parts = ptr.get()->mBase->mParts.mParts; - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) + for (std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) { - if((*itr).mPart == ESM::PRT_Head) - return std::make_pair(0, "#{sNotifyMessage13}"); - if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) - return std::make_pair(0, "#{sNotifyMessage14}"); + if ((*itr).mPart == ESM::PRT_Head) + return { 0, "#{sNotifyMessage13}" }; + if ((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) + return { 0, "#{sNotifyMessage14}" }; } } } - for (std::vector::const_iterator slot=slots_.first.begin(); - slot!=slots_.first.end(); ++slot) + for (std::vector::const_iterator slot = slots_.first.begin(); slot != slots_.first.end(); ++slot) { // If equipping a shield, check if there's a twohanded weapon conflicting with it - if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft) + if (*slot == MWWorld::InventoryStore::Slot_CarriedLeft) { - MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != invStore.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()) + MWWorld::ConstContainerStoreIterator weapon + = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon != invStore.end() && weapon->getType() == ESM::Weapon::sRecordId) { - const MWWorld::LiveCellRef *ref = weapon->get(); + const MWWorld::LiveCellRef* ref = weapon->get(); if (MWMechanics::getWeaponType(ref->mBase->mData.mType)->mFlags & ESM::WeaponType::TwoHanded) - return std::make_pair(3,""); + return { 3, {} }; } - return std::make_pair(1,""); + return { 1, {} }; } } - return std::make_pair(1,""); + return { 1, {} }; } - std::shared_ptr Armor::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Armor::use(const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } - MWWorld::Ptr Armor::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Armor::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - int Armor::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const + int Armor::getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Armor::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Armor::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Armor) - || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); + || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Armor::getWeight(const MWWorld::ConstPtr &ptr) const + float Armor::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 4f04e0824..d46436062 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -1,85 +1,87 @@ #ifndef GAME_MWCLASS_ARMOR_H #define GAME_MWCLASS_ARMOR_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Armor : public MWWorld::Class + class Armor : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Armor(); - float getWeight (const MWWorld::ConstPtr& ptr) const override; + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + public: + float getWeight(const MWWorld::ConstPtr& ptr) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override; - ///< \return Item health data available? + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; - ///< Return item max health or throw an exception, if class does not have item health + bool hasItemHealth(const MWWorld::ConstPtr& ptr) const override; + ///< \return Item health data available? - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + int getItemMaxHealth(const MWWorld::ConstPtr& ptr) const override; + ///< Return item max health or throw an exception, if class does not have item health - std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; - ///< \return first: Return IDs of the slot this object can be equipped in; second: can object - /// stay stacked when equipped? + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const override; - /// Return the index of the skill this item corresponds to when equipped or -1, if there is - /// no such skill. + std::pair, bool> getEquipmentSlots(const MWWorld::ConstPtr& ptr) const override; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override; - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::string getEnchantment (const MWWorld::ConstPtr& ptr) const override; - ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + ESM::RefId getEnchantment(const MWWorld::ConstPtr& ptr) const override; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; - ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. + const ESM::RefId& applyEnchantment(const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, + const std::string& newName) const override; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; - ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. \n - /// Second item in the pair specifies the error message + std::pair canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const override; + ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon + ///< conflicts with that. \n + /// Second item in the pair specifies the error message - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const override; + int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; - /// Get the effective armor rating, factoring in the actor's skills, for the given armor. - float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const override; + /// Get the effective armor rating, factoring in the actor's skills, for the given armor. + float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const override; }; } diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 0315d3ddb..431fb6965 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -1,34 +1,40 @@ #include "bodypart.hpp" -#include "../mwrender/renderinginterface.hpp" +#include + #include "../mwrender/objects.hpp" +#include "../mwrender/renderinginterface.hpp" #include "../mwworld/cellstore.hpp" +#include "classmodel.hpp" + namespace MWClass { - - MWWorld::Ptr BodyPart::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + BodyPart::BodyPart() + : MWWorld::RegisteredClass(ESM::BodyPart::sRecordId) { - const MWWorld::LiveCellRef *ref = ptr.get(); + } + + MWWorld::Ptr BodyPart::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const + { + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - void BodyPart::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string &model, MWRender::RenderingInterface &renderingInterface) const + void BodyPart::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - if (!model.empty()) { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const + std::string_view BodyPart::getName(const MWWorld::ConstPtr& ptr) const { - } - - std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const - { - return std::string(); + return {}; } bool BodyPart::hasToolTip(const MWWorld::ConstPtr& ptr) const @@ -36,22 +42,9 @@ namespace MWClass return false; } - void BodyPart::registerSelf() + std::string BodyPart::getModel(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new BodyPart); - - registerClass (typeid (ESM::BodyPart).name(), instance); - } - - std::string BodyPart::getModel(const MWWorld::ConstPtr &ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; + return getClassModel(ptr); } } diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 13d914138..4a2d9d362 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -1,31 +1,31 @@ #ifndef GAME_MWCLASS_BODYPART_H #define GAME_MWCLASS_BODYPART_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class BodyPart : public MWWorld::Class + class BodyPart : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; + + BodyPart(); + + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; public: - - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - - std::string getName (const MWWorld::ConstPtr& ptr) const override; + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - static void registerSelf(); - - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; }; } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 7c4027099..7eacdc072 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -12,38 +12,48 @@ End of tes3mp addition */ -#include +#include + +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/actionread.hpp" -#include "../mwworld/failedaction.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwphysics/physicssystem.hpp" +#include "../mwworld/failedaction.hpp" +#include "../mwworld/ptr.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwmechanics/npcstats.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Book::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Book::Book() + : MWWorld::RegisteredClass(ESM::Book::sRecordId) { - if (!model.empty()) { + } + + void Book::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Book::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -67,88 +77,76 @@ namespace MWClass /* End of tes3mp addition */ + return getClassModel(ptr); } - std::string Book::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Book::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Book::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Book::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Book::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { - if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfItem"); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound* sound = store.get().searchRandom("WolfItem", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); - if(sound) action->setSound(sound->mId); + std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); + if (sound) + action->setSound(sound->mId); return action; } - return std::shared_ptr(new MWWorld::ActionRead(ptr)); + return std::make_unique(ptr); } - std::string Book::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Book::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - int Book::getValue (const MWWorld::ConstPtr& ptr) const + int Book::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Book::registerSelf() + const ESM::RefId& Book::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Book); - - registerClass (typeid (ESM::Book).name(), instance); + static auto var = ESM::RefId::stringRefId("Item Book Up"); + return var; } - std::string Book::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Book::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Book Up"); + static auto var = ESM::RefId::stringRefId("Item Book Down"); + return var; } - std::string Book::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const std::string& Book::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Book Down"); - } - - std::string Book::getInventoryIcon (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Book::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Book::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -156,9 +154,10 @@ namespace MWClass text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; @@ -168,23 +167,24 @@ namespace MWClass return info; } - std::string Book::getEnchantment (const MWWorld::ConstPtr& ptr) const + ESM::RefId Book::getEnchantment(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Book::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + const ESM::RefId& Book::applyEnchantment( + const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, const std::string& newName) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); ESM::Book newItem = *ref->mBase; - newItem.mId=""; - newItem.mName=newName; + newItem.mId = ESM::RefId(); + newItem.mName = newName; newItem.mData.mIsScroll = 1; - newItem.mData.mEnchant=enchCharge; - newItem.mEnchant=enchId; + newItem.mData.mEnchant = enchCharge; + newItem.mEnchant = enchId; /* Start of tes3mp addition @@ -197,38 +197,38 @@ namespace MWClass End of tes3mp addition */ - const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem); + const ESM::Book* record = MWBase::Environment::get().getESMStore()->insert(newItem); return record->mId; } - std::shared_ptr Book::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Book::use(const MWWorld::Ptr& ptr, bool force) const { - return std::shared_ptr(new MWWorld::ActionRead(ptr)); + return std::make_unique(ptr); } - MWWorld::Ptr Book::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Book::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - int Book::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const + int Book::getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Book::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Book::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Books) - || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); + || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Book::getWeight(const MWWorld::ConstPtr &ptr) const + float Book::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index c58e68ad8..a30e85c10 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -1,64 +1,66 @@ #ifndef GAME_MWCLASS_BOOK_H #define GAME_MWCLASS_BOOK_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Book : public MWWorld::Class + class Book : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Book(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::string getEnchantment (const MWWorld::ConstPtr& ptr) const override; - ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + ESM::RefId getEnchantment(const MWWorld::ConstPtr& ptr) const override; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; - ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. + const ESM::RefId& applyEnchantment(const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, + const std::string& newName) const override; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const override; + int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override; - float getWeight (const MWWorld::ConstPtr& ptr) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; }; } diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index a552dfebf..5b53da317 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -1,26 +1,31 @@ #include "classes.hpp" +#include + #include "activator.hpp" -#include "creature.hpp" -#include "npc.hpp" -#include "weapon.hpp" -#include "armor.hpp" -#include "potion.hpp" #include "apparatus.hpp" +#include "armor.hpp" +#include "bodypart.hpp" #include "book.hpp" #include "clothing.hpp" #include "container.hpp" +#include "creature.hpp" +#include "creaturelevlist.hpp" #include "door.hpp" #include "ingredient.hpp" -#include "creaturelevlist.hpp" #include "itemlevlist.hpp" #include "light.hpp" +#include "light4.hpp" #include "lockpick.hpp" #include "misc.hpp" +#include "npc.hpp" +#include "potion.hpp" #include "probe.hpp" #include "repair.hpp" #include "static.hpp" -#include "bodypart.hpp" +#include "weapon.hpp" + +#include "esm4base.hpp" namespace MWClass { @@ -47,5 +52,21 @@ namespace MWClass Repair::registerSelf(); Static::registerSelf(); BodyPart::registerSelf(); + + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Named::registerSelf(); + ESM4Static::registerSelf(); + ESM4Tree::registerSelf(); + ESM4Named::registerSelf(); + ESM4Light::registerSelf(); } } diff --git a/apps/openmw/mwclass/classmodel.hpp b/apps/openmw/mwclass/classmodel.hpp new file mode 100644 index 000000000..5d1019ee1 --- /dev/null +++ b/apps/openmw/mwclass/classmodel.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_MWCLASS_CLASSMODEL_H +#define OPENMW_MWCLASS_CLASSMODEL_H + +#include "../mwbase/environment.hpp" + +#include "../mwworld/livecellref.hpp" +#include "../mwworld/ptr.hpp" + +#include +#include + +#include + +namespace MWClass +{ + template + std::string getClassModel(const MWWorld::ConstPtr& ptr) + { + const MWWorld::LiveCellRef* ref = ptr.get(); + + if (!ref->mBase->mModel.empty()) + return Misc::ResourceHelpers::correctMeshPath( + ref->mBase->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); + + return {}; + } +} + +#endif diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 390cd5265..854b32306 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -12,36 +12,47 @@ End of tes3mp addition */ -#include +#include + +#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwphysics/physicssystem.hpp" -#include "../mwworld/nullaction.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Clothing::Clothing() + : MWWorld::RegisteredClass(ESM::Clothing::sRecordId) { - if (!model.empty()) { + } + + void Clothing::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Clothing::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -65,58 +76,45 @@ namespace MWClass /* End of tes3mp addition */ + return getClassModel(ptr); } - std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Clothing::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Clothing::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Clothing::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Clothing::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - std::string Clothing::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Clothing::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Clothing::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const + std::pair, bool> Clothing::getEquipmentSlots(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); std::vector slots_; - if (ref->mBase->mData.mType==ESM::Clothing::Ring) + if (ref->mBase->mData.mType == ESM::Clothing::Ring) { - slots_.push_back (int (MWWorld::InventoryStore::Slot_LeftRing)); - slots_.push_back (int (MWWorld::InventoryStore::Slot_RightRing)); + slots_.push_back(int(MWWorld::InventoryStore::Slot_LeftRing)); + slots_.push_back(int(MWWorld::InventoryStore::Slot_RightRing)); } else { const int size = 9; - static const int sMapping[size][2] = - { - { ESM::Clothing::Shirt, MWWorld::InventoryStore::Slot_Shirt }, + static const int sMapping[size][2] = { { ESM::Clothing::Shirt, MWWorld::InventoryStore::Slot_Shirt }, { ESM::Clothing::Belt, MWWorld::InventoryStore::Slot_Belt }, { ESM::Clothing::Robe, MWWorld::InventoryStore::Slot_Robe }, { ESM::Clothing::Pants, MWWorld::InventoryStore::Slot_Pants }, @@ -124,79 +122,75 @@ namespace MWClass { ESM::Clothing::LGlove, MWWorld::InventoryStore::Slot_LeftGauntlet }, { ESM::Clothing::RGlove, MWWorld::InventoryStore::Slot_RightGauntlet }, { ESM::Clothing::Skirt, MWWorld::InventoryStore::Slot_Skirt }, - { ESM::Clothing::Amulet, MWWorld::InventoryStore::Slot_Amulet } - }; + { ESM::Clothing::Amulet, MWWorld::InventoryStore::Slot_Amulet } }; - for (int i=0; imBase->mData.mType) + for (int i = 0; i < size; ++i) + if (sMapping[i][0] == ref->mBase->mData.mType) { - slots_.push_back (int (sMapping[i][1])); + slots_.push_back(int(sMapping[i][1])); break; } } - return std::make_pair (slots_, false); + return std::make_pair(slots_, false); } - int Clothing::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const + ESM::RefId Clothing::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); - if (ref->mBase->mData.mType==ESM::Clothing::Shoes) + if (ref->mBase->mData.mType == ESM::Clothing::Shoes) return ESM::Skill::Unarmored; - return -1; + return {}; } - int Clothing::getValue (const MWWorld::ConstPtr& ptr) const + int Clothing::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Clothing::registerSelf() + const ESM::RefId& Clothing::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Clothing); - - registerClass (typeid (ESM::Clothing).name(), instance); - } - - std::string Clothing::getUpSoundId (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); - - if (ref->mBase->mData.mType == 8) + const MWWorld::LiveCellRef* ref = ptr.get(); + static const ESM::RefId ringUp = ESM::RefId::stringRefId("Item Ring Up"); + static const ESM::RefId clothsUp = ESM::RefId::stringRefId("Item Clothes Up"); + if (ref->mBase->mData.mType == ESM::Clothing::Ring) { - return std::string("Item Ring Up"); + return ringUp; } - return std::string("Item Clothes Up"); + return clothsUp; } - std::string Clothing::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Clothing::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - if (ref->mBase->mData.mType == 8) + const MWWorld::LiveCellRef* ref = ptr.get(); + static const ESM::RefId ringDown = ESM::RefId::stringRefId("Item Ring Down"); + static const ESM::RefId clothsDown = ESM::RefId::stringRefId("Item Clothes Down"); + if (ref->mBase->mData.mType == ESM::Clothing::Ring) { - return std::string("Item Ring Down"); + return ringDown; } - return std::string("Item Clothes Down"); + return clothsDown; } - std::string Clothing::getInventoryIcon (const MWWorld::ConstPtr& ptr) const + const std::string& Clothing::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Clothing::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Clothing::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -204,9 +198,10 @@ namespace MWClass text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; @@ -218,22 +213,23 @@ namespace MWClass return info; } - std::string Clothing::getEnchantment (const MWWorld::ConstPtr& ptr) const + ESM::RefId Clothing::getEnchantment(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Clothing::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + const ESM::RefId& Clothing::applyEnchantment( + const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, const std::string& newName) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); ESM::Clothing newItem = *ref->mBase; - newItem.mId=""; - newItem.mName=newName; - newItem.mData.mEnchant=enchCharge; - newItem.mEnchant=enchId; + newItem.mId = ESM::RefId(); + newItem.mName = newName; + newItem.mData.mEnchant = enchCharge; + newItem.mEnchant = enchId; /* Start of tes3mp addition @@ -246,73 +242,74 @@ namespace MWClass End of tes3mp addition */ - const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem); + const ESM::Clothing* record = MWBase::Environment::get().getESMStore()->insert(newItem); return record->mId; } - std::pair Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + std::pair Clothing::canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const { // slots that this item can be equipped in std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) - return std::make_pair(0, ""); + return { 0, {} }; if (npc.getClass().isNpc()) { - std::string npcRace = npc.get()->mBase->mRace; + const ESM::RefId& npcRace = npc.get()->mBase->mRace; // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); - if(race->mData.mFlags & ESM::Race::Beast) + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(npcRace); + if (race->mData.mFlags & ESM::Race::Beast) { std::vector parts = ptr.get()->mBase->mParts.mParts; - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) + for (std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) { - if((*itr).mPart == ESM::PRT_Head) - return std::make_pair(0, "#{sNotifyMessage13}"); - if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) - return std::make_pair(0, "#{sNotifyMessage15}"); + if ((*itr).mPart == ESM::PRT_Head) + return { 0, "#{sNotifyMessage13}" }; + if ((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) + return { 0, "#{sNotifyMessage15}" }; } } } - return std::make_pair (1, ""); + return { 1, {} }; } - std::shared_ptr Clothing::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Clothing::use(const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } - MWWorld::Ptr Clothing::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Clothing::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - int Clothing::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const + int Clothing::getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Clothing::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Clothing::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Clothing) - || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); + || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Clothing::getWeight(const MWWorld::ConstPtr &ptr) const + float Clothing::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index a87e0cbe0..a1e834871 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -1,76 +1,78 @@ #ifndef GAME_MWCLASS_CLOTHING_H #define GAME_MWCLASS_CLOTHING_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Clothing : public MWWorld::Class + class Clothing : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Clothing(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; - ///< \return first: Return IDs of the slot this object can be equipped in; second: can object - /// stay stacked when equipped? + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const override; - /// Return the index of the skill this item corresponds to when equipped or -1, if there is - /// no such skill. + std::pair, bool> getEquipmentSlots(const MWWorld::ConstPtr& ptr) const override; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override; - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::string getEnchantment (const MWWorld::ConstPtr& ptr) const override; - ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + ESM::RefId getEnchantment(const MWWorld::ConstPtr& ptr) const override; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; - ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. + const ESM::RefId& applyEnchantment(const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, + const std::string& newName) const override; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; - ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. - /// Second item in the pair specifies the error message + std::pair canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const override; + ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon + ///< conflicts with that. + /// Second item in the pair specifies the error message - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const override; + int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override; - float getWeight (const MWWorld::ConstPtr& ptr) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index f7d63c4a9..f692f1366 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -12,45 +12,50 @@ End of tes3mp addition */ -#include -#include #include +#include + +#include +#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/failedaction.hpp" -#include "../mwworld/nullaction.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/customdata.hpp" -#include "../mwworld/cellstore.hpp" -#include "../mwworld/esmstore.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/actionharvest.hpp" #include "../mwworld/actionopen.hpp" #include "../mwworld/actiontrap.hpp" -#include "../mwphysics/physicssystem.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" -#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/inventory.hpp" #include "../mwmechanics/npcstats.hpp" +#include "classmodel.hpp" + namespace MWClass { ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell) { - unsigned int seed = Misc::Rng::rollDice(std::numeric_limits::max()); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + unsigned int seed = Misc::Rng::rollDice(std::numeric_limits::max(), prng); // setting ownership not needed, since taking items from a container inherits the // container's owner automatically - mStore.fillNonRandom(container.mInventory, "", seed); + mStore.fillNonRandom(container.mInventory, ESM::RefId(), seed); } ContainerCustomData::ContainerCustomData(const ESM::InventoryState& inventory) @@ -68,18 +73,18 @@ namespace MWClass } Container::Container() + : MWWorld::RegisteredClass(ESM::Container::sRecordId) { - mHarvestEnabled = Settings::Manager::getBool("graphic herbalism", "Game"); } - void Container::ensureCustomData (const MWWorld::Ptr& ptr) const + void Container::ensureCustomData(const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); // store - ptr.getRefData().setCustomData (std::make_unique(*ref->mBase, ptr.getCell())); + ptr.getRefData().setCustomData(std::make_unique(*ref->mBase, ptr.getCell())); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); } @@ -87,7 +92,7 @@ namespace MWClass bool Container::canBeHarvested(const MWWorld::ConstPtr& ptr) const { - if (!mHarvestEnabled) + if (!Settings::game().mGraphicHerbalism) return false; const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (animation == nullptr) @@ -96,10 +101,9 @@ namespace MWClass return animation->canBeHarvested(); } - void Container::respawn(const MWWorld::Ptr &ptr) const + void Container::respawn(const MWWorld::Ptr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); if (ref->mBase->mFlags & ESM::Container::Respawn) { // Container was not touched, there is no need to modify its content. @@ -111,28 +115,30 @@ namespace MWClass } } - void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + void Container::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, true); + if (!model.empty()) + { + renderingInterface.getObjects().insertModel(ptr, model); } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model); + insertObjectPhysics(ptr, model, rotation, physics); } - std::string Container::getModel(const MWWorld::ConstPtr &ptr) const + void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); + } - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; + std::string Container::getModel(const MWWorld::ConstPtr& ptr) const + { + return getClassModel(ptr); } bool Container::useAnim() const @@ -140,35 +146,33 @@ namespace MWClass return true; } - std::shared_ptr Container::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Container::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) - return std::shared_ptr (new MWWorld::NullAction ()); + return std::make_unique(); - if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfContainer"); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound* sound = store.get().searchRandom("WolfContainer", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); - if(sound) action->setSound(sound->mId); + std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); + if (sound) + action->setSound(sound->mId); return action; } - const std::string lockedSound = "LockedChest"; - const std::string trapActivationSound = "Disarm Trap Fail"; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); - bool isLocked = ptr.getCellRef().getLockLevel() > 0; + bool isLocked = ptr.getCellRef().isLocked(); bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool hasKey = false; - std::string keyName; + std::string_view keyName; - const std::string keyId = ptr.getCellRef().getKey(); + const ESM::RefId& keyId = ptr.getCellRef().getKey(); if (!keyId.empty()) { MWWorld::Ptr keyPtr = invStore.search(keyId); @@ -181,8 +185,7 @@ namespace MWClass if (isLocked && hasKey) { - MWBase::Environment::get().getWindowManager ()->messageBox (keyName + " #{sKeyUsed}"); - + MWBase::Environment::get().getWindowManager()->messageBox(std::string{ keyName } + " #{sKeyUsed}"); /* Start of tes3mp change (major) @@ -193,9 +196,8 @@ namespace MWClass /* End of tes3mp change (major) */ - // using a key disarms the trap - if(isTrapped) + if (isTrapped) { /* Start of tes3mp change (major) @@ -203,8 +205,9 @@ namespace MWClass Disable unilateral trap disarming on this client and expect the server's reply to our packet to do it instead */ - //ptr.getCellRef().setTrap(""); - //MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f); + // ptr.getCellRef().setTrap(ESM::RefId()); + // MWBase::Environment::get().getSoundManager()->playSound3D( + // ptr, ESM::RefId::stringRefId("Disarm Trap"), 1.0f, 1.0f); /* End of tes3mp change (major) */ @@ -244,93 +247,89 @@ namespace MWClass */ } - if (!isLocked || hasKey) { - if(!isTrapped) + if (!isTrapped) { if (canBeHarvested(ptr)) { - std::shared_ptr action (new MWWorld::ActionHarvest(ptr)); - return action; + return std::make_unique(ptr); } - std::shared_ptr action (new MWWorld::ActionOpen(ptr)); - return action; + return std::make_unique(ptr); } else { // Activate trap - std::shared_ptr action(new MWWorld::ActionTrap(ptr.getCellRef().getTrap(), ptr)); - action->setSound(trapActivationSound); + std::unique_ptr action + = std::make_unique(ptr.getCellRef().getTrap(), ptr); + action->setSound(ESM::RefId::stringRefId("Disarm Trap Fail")); return action; } } else { - std::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); - action->setSound(lockedSound); + std::unique_ptr action = std::make_unique(std::string_view{}, ptr); + action->setSound(ESM::RefId::stringRefId("LockedChest")); return action; } } - std::string Container::getName (const MWWorld::ConstPtr& ptr) const + std::string_view Container::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) const + MWWorld::ContainerStore& Container::getContainerStore(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); + ensureCustomData(ptr); auto& data = ptr.getRefData().getCustomData()->asContainerCustomData(); data.mStore.mPtr = ptr; return data.mStore; } - std::string Container::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Container::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - void Container::registerSelf() - { - std::shared_ptr instance (new Container); - - registerClass (typeid (ESM::Container).name(), instance); - } - - bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const + bool Container::hasToolTip(const MWWorld::ConstPtr& ptr) const { if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems(); return true; } - MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Container::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); + std::string_view name = getName(ptr); + info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)); std::string text; int lockLevel = ptr.getCellRef().getLockLevel(); - if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock) - text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(lockLevel); - else if (lockLevel < 0) - text += "\n#{sUnlocked}"; - if (ptr.getCellRef().getTrap() != "") + if (lockLevel) + { + if (ptr.getCellRef().isLocked()) + text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(lockLevel); + else + text += "\n#{sUnlocked}"; + } + if (ptr.getCellRef().getTrap() != ESM::RefId()) text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) - { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); - if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "stolen_goods")) + { + text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + if (ptr.getCellRef().getRefId() == "stolen_goods") text += "\nYou can not use evidence chests"; } @@ -339,38 +338,37 @@ namespace MWClass return info; } - float Container::getCapacity (const MWWorld::Ptr& ptr) const + float Container::getCapacity(const MWWorld::Ptr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mWeight; } - float Container::getEncumbrance (const MWWorld::Ptr& ptr) const + float Container::getEncumbrance(const MWWorld::Ptr& ptr) const { - return getContainerStore (ptr).getWeight(); + return getContainerStore(ptr).getWeight(); } - bool Container::canLock(const MWWorld::ConstPtr &ptr) const + bool Container::canLock(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return !(ref->mBase->mFlags & ESM::Container::Organic); } - void Container::modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount) const + void Container::modifyBaseInventory(const ESM::RefId& containerId, const ESM::RefId& itemId, int amount) const { MWMechanics::modifyBaseInventory(containerId, itemId, amount); } - MWWorld::Ptr Container::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Container::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const + void Container::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; @@ -379,7 +377,7 @@ namespace MWClass ptr.getRefData().setCustomData(std::make_unique(containerState.mInventory)); } - void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const + void Container::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { @@ -395,6 +393,6 @@ namespace MWClass } ESM::ContainerState& containerState = state.asContainerState(); - customData.mStore.writeState (containerState.mInventory); + customData.mStore.writeState(containerState.mInventory); } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 687d9e649..c130e6051 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -1,9 +1,9 @@ #ifndef GAME_MWCLASS_CONTAINER_H #define GAME_MWCLASS_CONTAINER_H -#include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" +#include "../mwworld/registeredclass.hpp" namespace ESM { @@ -16,6 +16,7 @@ namespace MWClass class ContainerCustomData : public MWWorld::TypedCustomData { MWWorld::ContainerStore mStore; + public: ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell); ContainerCustomData(const ESM::InventoryState& inventory); @@ -26,79 +27,79 @@ namespace MWClass friend class Container; }; - class Container : public MWWorld::Class + class Container : public MWWorld::RegisteredClass { - bool mHarvestEnabled; + friend MWWorld::RegisteredClass; - void ensureCustomData (const MWWorld::Ptr& ptr) const; + Container(); - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + void ensureCustomData(const MWWorld::Ptr& ptr) const; - bool canBeHarvested(const MWWorld::ConstPtr& ptr) const; + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - public: - Container(); + bool canBeHarvested(const MWWorld::ConstPtr& ptr) const; - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; - ///< @return true if this object has a tooltip when focused (default implementation: true) + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; + ///< @return true if this object has a tooltip when focused (default implementation: true) - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const override; - ///< Return container store + MWWorld::ContainerStore& getContainerStore(const MWWorld::Ptr& ptr) const override; + ///< Return container store - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to check whether a class has a container store - */ - virtual bool hasContainerStore(const MWWorld::Ptr &ptr) const { return true; } - /* - End of tes3mp addition - */ + Make it possible to check whether a class has a container store + */ + virtual bool hasContainerStore(const MWWorld::Ptr &ptr) const { return true; } + /* + End of tes3mp addition + */ - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - float getCapacity (const MWWorld::Ptr& ptr) const override; - ///< Return total weight that fits into the object. Throws an exception, if the object can't - /// hold other objects. + float getCapacity(const MWWorld::Ptr& ptr) const override; + ///< Return total weight that fits into the object. Throws an exception, if the object can't + /// hold other objects. - float getEncumbrance (const MWWorld::Ptr& ptr) const override; - ///< Returns total weight of objects inside this object (including modifications from magic - /// effects). Throws an exception, if the object can't hold other objects. + float getEncumbrance(const MWWorld::Ptr& ptr) const override; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. - bool canLock(const MWWorld::ConstPtr &ptr) const override; + bool canLock(const MWWorld::ConstPtr& ptr) const override; - void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const override; - ///< Read additional state from \a state into \a ptr. + void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; + ///< Read additional state from \a state into \a ptr. - void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; - ///< Write additional state from \a ptr into \a state. + void writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; + ///< Write additional state from \a ptr into \a state. - static void registerSelf(); + void respawn(const MWWorld::Ptr& ptr) const override; - void respawn (const MWWorld::Ptr& ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + bool useAnim() const override; - bool useAnim() const override; - - void modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount) const override; + void modifyBaseInventory(const ESM::RefId& containerId, const ESM::RefId& itemId, int amount) const override; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 45e9527b7..b0784835d 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,10 +1,6 @@ #include "creature.hpp" -#include -#include -#include -#include -#include +#include /* Start of tes3mp addition @@ -23,42 +19,55 @@ End of tes3mp addition */ +#include +#include +#include +#include +#include +#include +#include + +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/aisetting.hpp" +#include "../mwmechanics/combat.hpp" +#include "../mwmechanics/creaturecustomdataresetter.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/difficultyscaling.hpp" +#include "../mwmechanics/disease.hpp" +#include "../mwmechanics/inventory.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/movement.hpp" -#include "../mwmechanics/disease.hpp" -#include "../mwmechanics/spellcasting.hpp" -#include "../mwmechanics/difficultyscaling.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/setbaseaisetting.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/actiontalk.hpp" #include "../mwworld/actionopen.hpp" -#include "../mwworld/failedaction.hpp" -#include "../mwworld/customdata.hpp" -#include "../mwworld/containerstore.hpp" +#include "../mwworld/actiontalk.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/customdata.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/failedaction.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/localscripts.hpp" +#include "../mwworld/ptr.hpp" -#include "../mwrender/renderinginterface.hpp" #include "../mwrender/objects.hpp" +#include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" -#include "../mwworld/inventorystore.hpp" - -#include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/combat.hpp" -#include "../mwmechanics/actorutil.hpp" +#include "classmodel.hpp" namespace { - bool isFlagBitSet(const MWWorld::ConstPtr &ptr, ESM::Creature::Flags bitMask) + bool isFlagBitSet(const MWWorld::ConstPtr& ptr, ESM::Creature::Flags bitMask) { return (ptr.get()->mBase->mFlags & bitMask) != 0; } @@ -78,31 +87,30 @@ namespace MWClass CreatureCustomData(const CreatureCustomData& other); CreatureCustomData(CreatureCustomData&& other) = default; - CreatureCustomData& asCreatureCustomData() override - { - return *this; - } - const CreatureCustomData& asCreatureCustomData() const override - { - return *this; - } + CreatureCustomData& asCreatureCustomData() override { return *this; } + const CreatureCustomData& asCreatureCustomData() const override { return *this; } }; CreatureCustomData::CreatureCustomData(const CreatureCustomData& other) - : mCreatureStats(other.mCreatureStats), - mContainerStore(other.mContainerStore->clone()), - mMovement(other.mMovement) + : mCreatureStats(other.mCreatureStats) + , mContainerStore(other.mContainerStore->clone()) + , mMovement(other.mMovement) + { + } + + Creature::Creature() + : MWWorld::RegisteredClass(ESM::Creature::sRecordId) { } const Creature::GMST& Creature::getGmst() { - static GMST gmst; - static bool inited = false; - if (!inited) - { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); + static const GMST staticGmst = [] { + GMST gmst; + + const MWWorld::Store& store + = MWBase::Environment::get().getESMStore()->get(); + gmst.fMinWalkSpeedCreature = store.find("fMinWalkSpeedCreature"); gmst.fMaxWalkSpeedCreature = store.find("fMaxWalkSpeedCreature"); gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); @@ -116,18 +124,22 @@ namespace MWClass gmst.fKnockDownMult = store.find("fKnockDownMult"); gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); - inited = true; - } - return gmst; + + return gmst; + }(); + return staticGmst; } - void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const + void Creature::ensureCustomData(const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data (new CreatureCustomData); + auto tempData = std::make_unique(); + CreatureCustomData* data = tempData.get(); + MWMechanics::CreatureCustomDataResetter resetter{ ptr }; + ptr.getRefData().setCustomData(std::move(tempData)); - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); // creature stats data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength); @@ -146,10 +158,10 @@ namespace MWClass data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage); - data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); - data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); - data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); - data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); + data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Hello, ref->mBase->mAiData.mHello); + data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Fight, ref->mBase->mAiData.mFight); + data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Flee, ref->mBase->mAiData.mFlee); + data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Alarm, ref->mBase->mAiData.mAlarm); // Persistent actors with 0 health do not play death animation if (data->mCreatureStats.isDead()) @@ -169,36 +181,29 @@ namespace MWClass data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); - data->mCreatureStats.setNeedRecalcDynamicStats(false); + resetter.mPtr = {}; - // store - ptr.getRefData().setCustomData(std::move(data)); - - getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); if (hasInventory) - getInventoryStore(ptr).autoEquip(ptr); + getInventoryStore(ptr).autoEquip(); } } - void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + void Creature::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertCreature(ptr, model, hasInventoryStore(ptr)); } - std::string Creature::getModel(const MWWorld::ConstPtr &ptr) const + std::string Creature::getModel(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; + return getClassModel(ptr); } - void Creature::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + void Creature::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const { std::string model = getModel(ptr); if (!model.empty()) @@ -221,23 +226,22 @@ namespace MWClass } } - std::string Creature::getName (const MWWorld::ConstPtr& ptr) const + std::string_view Creature::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - MWMechanics::CreatureStats& Creature::getCreatureStats (const MWWorld::Ptr& ptr) const + MWMechanics::CreatureStats& Creature::getCreatureStats(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); + ensureCustomData(ptr); return ptr.getRefData().getCustomData()->asCreatureCustomData().mCreatureStats; } - - void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const + bool Creature::evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const { /* Start of tes3mp addition @@ -246,45 +250,84 @@ namespace MWClass */ if (mwmp::PlayerList::isDedicatedPlayer(ptr) || mwmp::Main::get().getCellController()->isDedicatedActor(ptr)) { - return; + return false; } /* End of tes3mp addition */ - MWWorld::LiveCellRef *ref = - ptr.get(); - const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - MWMechanics::CreatureStats &stats = getCreatureStats(ptr); - - if (stats.getDrawState() != MWMechanics::DrawState_Weapon) - return; + victim = MWWorld::Ptr(); + hitPosition = osg::Vec3f(); // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::Ptr weapon; if (hasInventoryStore(ptr)) { - MWWorld::InventoryStore &inv = getInventoryStore(ptr); + MWWorld::InventoryStore& inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponslot != inv.end() && weaponslot->getTypeName() == typeid(ESM::Weapon).name()) + if (weaponslot != inv.end() && weaponslot->getType() == ESM::Weapon::sRecordId) + weapon = *weaponslot; + } + + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& store = world->getStore().get(); + float dist = store.find("fCombatDistance")->mValue.getFloat(); + if (!weapon.isEmpty()) + dist *= weapon.get()->mBase->mData.mReach; + + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit + // result. + std::vector targetActors; + getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); + + std::pair result + = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors); + if (result.first.isEmpty()) // Didn't hit anything + return true; + + const MWWorld::Class& othercls = result.first.getClass(); + if (!othercls.isActor()) // Can't hit non-actors + return true; + + MWMechanics::CreatureStats& otherstats = othercls.getCreatureStats(result.first); + if (otherstats.isDead()) // Can't hit dead actors + return true; + + // Note that earlier we returned true in spite of an apparent failure to hit anything alive. + // This is because hitting nothing is not a "miss" and should be handled as such character controller-side. + victim = result.first; + hitPosition = result.second; + + float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.get()->mBase->mData.mCombat); + return Misc::Rng::roll0to99(world->getPrng()) < hitchance; + } + + void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim, + const osg::Vec3f& hitPosition, bool success) const + { + MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + + if (stats.getDrawState() != MWMechanics::DrawState::Weapon) + return; + + MWWorld::Ptr weapon; + if (hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponslot != inv.end() && weaponslot->getType() == ESM::Weapon::sRecordId) weapon = *weaponslot; } MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); - float dist = gmst.find("fCombatDistance")->mValue.getFloat(); - if (!weapon.isEmpty()) - dist *= weapon.get()->mBase->mData.mReach; - - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. - std::vector targetActors; - stats.getAiSequence().getCombatTargets(targetActors); - - std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors); - if (result.first.isEmpty()) + if (victim.isEmpty()) return; // Didn't hit anything - MWWorld::Ptr victim = result.first; + const MWWorld::Class& othercls = victim.getClass(); + MWMechanics::CreatureStats& otherstats = othercls.getCreatureStats(victim); + if (otherstats.isDead()) // Can't hit dead actors + return; /* Start of tes3mp change (major) @@ -305,10 +348,6 @@ namespace MWClass End of tes3mp change (major) */ - osg::Vec3f hitPosition (result.second); - - float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); - /* Start of tes3mp addition @@ -328,7 +367,7 @@ namespace MWClass End of tes3mp addition */ - if(Misc::Rng::roll0to99() >= hitchance) + if (!success) { /* Start of tes3mp addition @@ -359,41 +398,42 @@ namespace MWClass return; } - int min,max; + MWWorld::LiveCellRef* ref = ptr.get(); + int min, max; switch (type) { - case 0: - min = ref->mBase->mData.mAttack[0]; - max = ref->mBase->mData.mAttack[1]; - break; - case 1: - min = ref->mBase->mData.mAttack[2]; - max = ref->mBase->mData.mAttack[3]; - break; - case 2: - default: - min = ref->mBase->mData.mAttack[4]; - max = ref->mBase->mData.mAttack[5]; - break; + case 0: + min = ref->mBase->mData.mAttack[0]; + max = ref->mBase->mData.mAttack[1]; + break; + case 1: + min = ref->mBase->mData.mAttack[2]; + max = ref->mBase->mData.mAttack[3]; + break; + case 2: + default: + min = ref->mBase->mData.mAttack[4]; + max = ref->mBase->mData.mAttack[5]; + break; } float damage = min + (max - min) * attackStrength; bool healthdmg = true; if (!weapon.isEmpty()) { - const unsigned char *attack = nullptr; - if(type == ESM::Weapon::AT_Chop) + const unsigned char* attack = nullptr; + if (type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; - else if(type == ESM::Weapon::AT_Slash) + else if (type == ESM::Weapon::AT_Slash) attack = weapon.get()->mBase->mData.mSlash; - else if(type == ESM::Weapon::AT_Thrust) + else if (type == ESM::Weapon::AT_Thrust) attack = weapon.get()->mBase->mData.mThrust; - if(attack) + if (attack) { - damage = attack[0] + ((attack[1]-attack[0])*attackStrength); + damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); MWMechanics::adjustWeaponDamage(damage, weapon, ptr); - MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); + MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); } // Apply "On hit" enchanted weapons @@ -420,24 +460,28 @@ namespace MWClass MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) + { damage = 0; + victim.getClass().block(victim); + } MWMechanics::diseaseContact(victim, ptr); victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); } - void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const + void Creature::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); - // NOTE: 'object' and/or 'attacker' may be empty. + // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) stats.setAttacked(true); // Self defense bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits. - + // No retaliation for totally static creatures (they have no movement or attacks anyway) if (isMobile(ptr) && !attacker.isEmpty()) setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); @@ -483,9 +527,9 @@ namespace MWClass if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { - const std::string &script = ptr.get()->mBase->mScript; + const ESM::RefId& script = ptr.get()->mBase->mScript; /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ - if(!script.empty()) + if (!script.empty()) ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } @@ -493,7 +537,8 @@ namespace MWClass { // Missed if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); + MWBase::Environment::get().getSoundManager()->playSound3D( + ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f); return; } @@ -508,10 +553,11 @@ namespace MWClass if (!attacker.isEmpty()) { // Check for knockdown - float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->mValue.getFloat(); + float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() + * getGmst().fKnockDownMult->mValue.getFloat(); float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() - * getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f + getGmst().iKnockDownOddsBase->mValue.getInteger(); - + * getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f + + getGmst().iKnockDownOddsBase->mValue.getInteger(); /* Start of tes3mp change (major) @@ -529,8 +575,8 @@ namespace MWClass if (dedicatedAttack->knockdown) stats.setKnockedDown(true); } - - if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng)) stats.setKnockedDown(true); else { @@ -545,7 +591,7 @@ namespace MWClass */ } - if(ishealth) + if (ishealth) { damage *= damage / (damage + getArmorRating(ptr)); damage = std::max(1.f, damage); @@ -555,7 +601,8 @@ namespace MWClass MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition); } - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); + MWBase::Environment::get().getSoundManager()->playSound3D( + ptr, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f); MWMechanics::DynamicStat health(stats.getHealth()); health.setCurrent(health.getCurrent() - damage); @@ -610,84 +657,78 @@ namespace MWClass */ } - std::shared_ptr Creature::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Creature::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { - if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfCreature"); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound* sound = store.get().searchRandom("WolfCreature", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); - if(sound) action->setSound(sound->mId); + std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); + if (sound) + action->setSound(sound->mId); return action; } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); - if(stats.isDead()) + if (stats.isDead()) { - bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game"); - // by default user can loot friendly actors during death animation - if (canLoot && !stats.getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) + return std::make_unique(ptr); // otherwise wait until death animation - if(stats.isDeathAnimationFinished()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + if (stats.isDeathAnimationFinished()) + return std::make_unique(ptr); } else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) - return std::shared_ptr(new MWWorld::ActionTalk(ptr)); + return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + return std::make_unique(ptr); - return std::shared_ptr(new MWWorld::FailedAction("")); + return std::make_unique(); } - MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const + MWWorld::ContainerStore& Creature::getContainerStore(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); - - return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; + ensureCustomData(ptr); + auto& store = *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; + if (hasInventoryStore(ptr)) + static_cast(store).setActor(ptr); + return store; } - MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const + MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr& ptr) const { if (hasInventoryStore(ptr)) - return dynamic_cast(getContainerStore(ptr)); + return static_cast(getContainerStore(ptr)); else throw std::runtime_error("this creature has no inventory store"); } - bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const + bool Creature::hasInventoryStore(const MWWorld::Ptr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Weapon); } - std::string Creature::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Creature::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - bool Creature::isEssential (const MWWorld::ConstPtr& ptr) const + bool Creature::isEssential(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Essential); } - void Creature::registerSelf() - { - std::shared_ptr instance (new Creature); - - registerClass (typeid (ESM::Creature).name(), instance); - } - - float Creature::getMaxSpeed(const MWWorld::Ptr &ptr) const + float Creature::getMaxSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); @@ -696,25 +737,27 @@ namespace MWClass const GMST& gmst = getGmst(); - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); + const MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); float moveSpeed; - if(getEncumbrance(ptr) > getCapacity(ptr)) + if (getEncumbrance(ptr) > getCapacity(ptr)) moveSpeed = 0.0f; - else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && - world->isLevitationEnabled())) + else if (canFly(ptr) + || (mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled())) { - float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + - mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); - flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); + float flySpeed = 0.01f + * (stats.getAttribute(ESM::Attribute::Speed).getModified() + + mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude()); + flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + + flySpeed * (gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } - else if(world->isSwimming(ptr)) + else if (world->isSwimming(ptr)) moveSpeed = getSwimSpeed(ptr); else moveSpeed = getWalkSpeed(ptr); @@ -722,9 +765,9 @@ namespace MWClass return moveSpeed; } - MWMechanics::Movement& Creature::getMovementSettings (const MWWorld::Ptr& ptr) const + MWMechanics::Movement& Creature::getMovementSettings(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); + ensureCustomData(ptr); return ptr.getRefData().getCustomData()->asCreatureCustomData().mMovement; } @@ -742,62 +785,63 @@ namespace MWClass return !customData.mCreatureStats.getAiSequence().isInCombat(); } - MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Creature::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); + std::string_view name = getName(ptr); + info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); info.text = text; return info; } - float Creature::getArmorRating (const MWWorld::Ptr& ptr) const + float Creature::getArmorRating(const MWWorld::Ptr& ptr) const { // Equipment armor rating is deliberately ignored. - return getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); + return getCreatureStats(ptr).getMagicEffects().getOrDefault(ESM::MagicEffect::Shield).getMagnitude(); } - float Creature::getCapacity (const MWWorld::Ptr& ptr) const + float Creature::getCapacity(const MWWorld::Ptr& ptr) const { - const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); return stats.getAttribute(ESM::Attribute::Strength).getModified() * 5; } - int Creature::getServices(const MWWorld::ConstPtr &actor) const + int Creature::getServices(const MWWorld::ConstPtr& actor) const { return actor.get()->mBase->mAiData.mServices; } - bool Creature::isPersistent(const MWWorld::ConstPtr &actor) const + bool Creature::isPersistent(const MWWorld::ConstPtr& actor) const { const MWWorld::LiveCellRef* ref = actor.get(); - return ref->mBase->mPersistent; + return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0; } - std::string Creature::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const + ESM::RefId Creature::getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const { int type = getSndGenTypeFromName(ptr, name); if (type < 0) - return std::string(); + return ESM::RefId(); std::vector sounds; std::vector fallbacksounds; MWWorld::LiveCellRef* ref = ptr.get(); - const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; + const ESM::RefId& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); auto sound = store.get().begin(); while (sound != store.get().end()) { - if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(ourId, sound->mCreature)) + if (type == sound->mType && !sound->mCreature.empty() && ourId == sound->mCreature) sounds.push_back(&*sound); if (type == sound->mType && sound->mCreature.empty()) fallbacksounds.push_back(&*sound); @@ -809,17 +853,18 @@ namespace MWClass const std::string model = getModel(ptr); if (!model.empty()) { - for (const ESM::Creature &creature : store.get()) + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + for (const ESM::Creature& creature : store.get()) { if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty() - && Misc::StringUtils::ciEqual(model, "meshes\\" + creature.mModel)) + && Misc::StringUtils::ciEqual( + model, Misc::ResourceHelpers::correctMeshPath(creature.mModel, vfs))) { - const std::string& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId; + const ESM::RefId& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId; sound = store.get().begin(); while (sound != store.get().end()) { - if (type == sound->mType && !sound->mCreature.empty() - && Misc::StringUtils::ciEqual(fallbackId, sound->mCreature)) + if (type == sound->mType && !sound->mCreature.empty() && fallbackId == sound->mCreature) sounds.push_back(&*sound); ++sound; } @@ -829,143 +874,149 @@ namespace MWClass } } + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (!sounds.empty()) - return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound; if (!fallbacksounds.empty()) - return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; - return std::string(); + return ESM::RefId(); } - MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Creature::isBipedal(const MWWorld::ConstPtr &ptr) const + bool Creature::isBipedal(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Bipedal); } - bool Creature::canFly(const MWWorld::ConstPtr &ptr) const + bool Creature::canFly(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Flies); } - bool Creature::canSwim(const MWWorld::ConstPtr &ptr) const + bool Creature::canSwim(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, static_cast(ESM::Creature::Swims | ESM::Creature::Bipedal)); } - bool Creature::canWalk(const MWWorld::ConstPtr &ptr) const + bool Creature::canWalk(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, static_cast(ESM::Creature::Walks | ESM::Creature::Bipedal)); } - int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) + int Creature::getSndGenTypeFromName(const MWWorld::Ptr& ptr, std::string_view name) { - if(name == "left") + if (name == "left") { - MWBase::World *world = MWBase::Environment::get().getWorld(); - if(world->isFlying(ptr)) + MWBase::World* world = MWBase::Environment::get().getWorld(); + if (world->isFlying(ptr)) return -1; osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); - if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) + if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return ESM::SoundGenerator::SwimLeft; - if(world->isOnGround(ptr)) + if (world->isOnGround(ptr)) return ESM::SoundGenerator::LeftFoot; return -1; } - if(name == "right") + if (name == "right") { - MWBase::World *world = MWBase::Environment::get().getWorld(); - if(world->isFlying(ptr)) + MWBase::World* world = MWBase::Environment::get().getWorld(); + if (world->isFlying(ptr)) return -1; osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); - if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) + if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return ESM::SoundGenerator::SwimRight; - if(world->isOnGround(ptr)) + if (world->isOnGround(ptr)) return ESM::SoundGenerator::RightFoot; return -1; } - if(name == "swimleft") + if (name == "swimleft") return ESM::SoundGenerator::SwimLeft; - if(name == "swimright") + if (name == "swimright") return ESM::SoundGenerator::SwimRight; - if(name == "moan") + if (name == "moan") return ESM::SoundGenerator::Moan; - if(name == "roar") + if (name == "roar") return ESM::SoundGenerator::Roar; - if(name == "scream") + if (name == "scream") return ESM::SoundGenerator::Scream; - if(name == "land") + if (name == "land") return ESM::SoundGenerator::Land; - throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); + throw std::runtime_error("Unexpected soundgen type: " + std::string(name)); } - float Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const + float Creature::getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); - const ESM::Skill* skillRecord = MWBase::Environment::get().getWorld()->getStore().get().find(skill); + const ESM::Skill* skillRecord = MWBase::Environment::get().getESMStore()->get().find(id); switch (skillRecord->mData.mSpecialization) { - case ESM::Class::Combat: - return ref->mBase->mData.mCombat; - case ESM::Class::Magic: - return ref->mBase->mData.mMagic; - case ESM::Class::Stealth: - return ref->mBase->mData.mStealth; - default: - throw std::runtime_error("invalid specialisation"); + case ESM::Class::Combat: + return ref->mBase->mData.mCombat; + case ESM::Class::Magic: + return ref->mBase->mData.mMagic; + case ESM::Class::Stealth: + return ref->mBase->mData.mStealth; + default: + throw std::runtime_error("invalid specialisation"); } } - int Creature::getBloodTexture(const MWWorld::ConstPtr &ptr) const + int Creature::getBloodTexture(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mBloodType; } - void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const + void Creature::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; + const ESM::CreatureState& creatureState = state.asCreatureState(); + if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::unique_ptr data (new CreatureCustomData); - - if (hasInventoryStore(ptr)) - data->mContainerStore = std::make_unique(); + if (creatureState.mCreatureStats.mMissingACDT) + ensureCustomData(ptr); else - data->mContainerStore = std::make_unique(); + { + // Create a CustomData, but don't fill it from ESM records (not needed) + auto data = std::make_unique(); - ptr.getRefData().setCustomData (std::move(data)); + if (hasInventoryStore(ptr)) + data->mContainerStore = std::make_unique(); + else + data->mContainerStore = std::make_unique(); + + ptr.getRefData().setCustomData(std::move(data)); + } } } else - ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. + ensureCustomData( + ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); - const ESM::CreatureState& creatureState = state.asCreatureState(); - customData.mContainerStore->readState (creatureState.mInventory); + + customData.mContainerStore->readState(creatureState.mInventory); bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get()->mBase->mId); - if(spellsInitialised) + if (spellsInitialised) customData.mCreatureStats.getSpells().clear(); - customData.mCreatureStats.readState (creatureState.mCreatureStats); + customData.mCreatureStats.readState(creatureState.mCreatureStats); } - void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) - const + void Creature::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { @@ -973,16 +1024,17 @@ namespace MWClass return; } - if (ptr.getRefData().getCount() <= 0) + const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); + if (ptr.getRefData().getCount() <= 0 + && (!isFlagBitSet(ptr, ESM::Creature::Respawn) || !customData.mCreatureStats.isDead())) { state.mHasCustomState = false; return; } - const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); ESM::CreatureState& creatureState = state.asCreatureState(); - customData.mContainerStore->writeState (creatureState.mInventory); - customData.mCreatureStats.writeState (creatureState.mCreatureStats); + customData.mContainerStore->writeState(creatureState.mInventory); + customData.mCreatureStats.writeState(creatureState.mCreatureStats); } int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const @@ -990,7 +1042,7 @@ namespace MWClass return ptr.get()->mBase->mData.mGold; } - void Creature::respawn(const MWWorld::Ptr &ptr) const + void Creature::respawn(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) @@ -999,54 +1051,57 @@ namespace MWClass if (!creatureStats.isDeathAnimationFinished()) return; - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); - float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + float delay + = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (isFlagBitSet(ptr, ESM::Creature::Respawn) - && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) { ptr.getRefData().setCount(1); - const std::string& script = getScript(ptr); - if(!script.empty()) + const ESM::RefId& script = getScript(ptr); + if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); } MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); + MWBase::Environment::get().getWindowManager()->onDeleteCustomData(ptr); ptr.getRefData().setCustomData(nullptr); // Reset to original position - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], - ptr.getCellRef().getPosition().pos[1], - ptr.getCellRef().getPosition().pos[2]); + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->rotateObject( + ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } - int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const + int Creature::getBaseFightRating(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mAiData.mFight; } - void Creature::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool /* rendering */) const + void Creature::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool /* rendering */) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); scale *= ref->mBase->mScale; } - void Creature::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const + void Creature::setBaseAISetting(const ESM::RefId& id, MWMechanics::AiSetting setting, int value) const { MWMechanics::setBaseAISetting(id, setting, value); } - void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + void Creature::modifyBaseInventory(const ESM::RefId& actorId, const ESM::RefId& itemId, int amount) const { MWMechanics::modifyBaseInventory(actorId, itemId, amount); } @@ -1057,8 +1112,8 @@ namespace MWClass const GMST& gmst = getGmst(); return gmst.fMinWalkSpeedCreature->mValue.getFloat() - + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() - * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat()); + + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() + * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat()); } float Creature::getRunSpeed(const MWWorld::Ptr& ptr) const @@ -1069,12 +1124,8 @@ namespace MWClass float Creature::getSwimSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); - const GMST& gmst = getGmst(); const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); - return getWalkSpeed(ptr) - * (1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude()) - * (gmst.fSwimRunBase->mValue.getFloat() - + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat()); + return getSwimSpeedImpl(ptr, getGmst(), mageffects, getWalkSpeed(ptr)); } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 8ecb28c04..22b0f379a 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWCLASS_CREATURE_H #define GAME_MWCLASS_CREATURE_H +#include "../mwworld/registeredclass.hpp" + #include "actor.hpp" namespace ESM @@ -10,143 +12,148 @@ namespace ESM namespace MWClass { - class Creature : public Actor + class Creature : public MWWorld::RegisteredClass { - void ensureCustomData (const MWWorld::Ptr& ptr) const; + friend MWWorld::RegisteredClass; - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + Creature(); - static int getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name); + void ensureCustomData(const MWWorld::Ptr& ptr) const; - // cached GMSTs - struct GMST - { - const ESM::GameSetting *fMinWalkSpeedCreature; - const ESM::GameSetting *fMaxWalkSpeedCreature; - const ESM::GameSetting *fEncumberedMoveEffect; - const ESM::GameSetting *fSneakSpeedMultiplier; - const ESM::GameSetting *fAthleticsRunBonus; - const ESM::GameSetting *fBaseRunMultiplier; - const ESM::GameSetting *fMinFlySpeed; - const ESM::GameSetting *fMaxFlySpeed; - const ESM::GameSetting *fSwimRunBase; - const ESM::GameSetting *fSwimRunAthleticsMult; - const ESM::GameSetting *fKnockDownMult; - const ESM::GameSetting *iKnockDownOddsMult; - const ESM::GameSetting *iKnockDownOddsBase; - }; + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - static const GMST& getGmst(); + static int getSndGenTypeFromName(const MWWorld::Ptr& ptr, std::string_view name); - public: + // cached GMSTs + struct GMST + { + const ESM::GameSetting* fMinWalkSpeedCreature; + const ESM::GameSetting* fMaxWalkSpeedCreature; + const ESM::GameSetting* fEncumberedMoveEffect; + const ESM::GameSetting* fSneakSpeedMultiplier; + const ESM::GameSetting* fAthleticsRunBonus; + const ESM::GameSetting* fBaseRunMultiplier; + const ESM::GameSetting* fMinFlySpeed; + const ESM::GameSetting* fMaxFlySpeed; + const ESM::GameSetting* fSwimRunBase; + const ESM::GameSetting* fSwimRunAthleticsMult; + const ESM::GameSetting* fKnockDownMult; + const ESM::GameSetting* iKnockDownOddsMult; + const ESM::GameSetting* iKnockDownOddsBase; + }; - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + static const GMST& getGmst(); - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; - ///< @return true if this object has a tooltip when focused (default implementation: true) + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; + ///< @return true if this object has a tooltip when focused (default implementation: true) - MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const override; - ///< Return creature stats + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const override; + MWMechanics::CreatureStats& getCreatureStats(const MWWorld::Ptr& ptr) const override; + ///< Return creature stats - void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const override; + bool evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const override; - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim, + const osg::Vec3f& hitPosition, bool success) const override; - MWWorld::ContainerStore& getContainerStore ( - const MWWorld::Ptr& ptr) const override; - ///< Return container store + void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const override; - MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const override; - ///< Return inventory store + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - bool hasInventoryStore (const MWWorld::Ptr &ptr) const override; + MWWorld::ContainerStore& getContainerStore(const MWWorld::Ptr& ptr) const override; + ///< Return container store - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to check whether a class has a container store - */ - virtual bool hasContainerStore(const MWWorld::Ptr &ptr) const { return true; } - /* - End of tes3mp addition - */ + Make it possible to check whether a class has a container store + */ + virtual bool hasContainerStore(const MWWorld::Ptr &ptr) const { return true; } + /* + End of tes3mp addition + */ - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override; + ///< Return inventory store - float getCapacity (const MWWorld::Ptr& ptr) const override; - ///< Return total weight that fits into the object. Throws an exception, if the object can't - /// hold other objects. + bool hasInventoryStore(const MWWorld::Ptr& ptr) const override; - float getArmorRating (const MWWorld::Ptr& ptr) const override; - ///< @return combined armor rating of this actor + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - bool isEssential (const MWWorld::ConstPtr& ptr) const override; - ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) + float getCapacity(const MWWorld::Ptr& ptr) const override; + ///< Return total weight that fits into the object. Throws an exception, if the object can't + /// hold other objects. - int getServices (const MWWorld::ConstPtr& actor) const override; + float getArmorRating(const MWWorld::Ptr& ptr) const override; + ///< @return combined armor rating of this actor - bool isPersistent (const MWWorld::ConstPtr& ptr) const override; + bool isEssential(const MWWorld::ConstPtr& ptr) const override; + ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const override; + int getServices(const MWWorld::ConstPtr& actor) const override; - MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const override; - ///< Return desired movement. + bool isPersistent(const MWWorld::ConstPtr& ptr) const override; - float getMaxSpeed (const MWWorld::Ptr& ptr) const override; + ESM::RefId getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const override; - static void registerSelf(); + MWMechanics::Movement& getMovementSettings(const MWWorld::Ptr& ptr) const override; + ///< Return desired movement. - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + float getMaxSpeed(const MWWorld::Ptr& ptr) const override; - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; - ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - bool isBipedal (const MWWorld::ConstPtr &ptr) const override; - bool canFly (const MWWorld::ConstPtr &ptr) const override; - bool canSwim (const MWWorld::ConstPtr &ptr) const override; - bool canWalk (const MWWorld::ConstPtr &ptr) const override; + void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: + ///< list getModel(). - float getSkill(const MWWorld::Ptr &ptr, int skill) const override; + bool isBipedal(const MWWorld::ConstPtr& ptr) const override; + bool canFly(const MWWorld::ConstPtr& ptr) const override; + bool canSwim(const MWWorld::ConstPtr& ptr) const override; + bool canWalk(const MWWorld::ConstPtr& ptr) const override; - /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - int getBloodTexture (const MWWorld::ConstPtr& ptr) const override; + float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override; - void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; - ///< Read additional state from \a state into \a ptr. + /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) + int getBloodTexture(const MWWorld::ConstPtr& ptr) const override; - void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; - ///< Write additional state from \a ptr into \a state. + void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; + ///< Read additional state from \a state into \a ptr. - int getBaseGold(const MWWorld::ConstPtr& ptr) const override; + void writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; + ///< Write additional state from \a ptr into \a state. - void respawn (const MWWorld::Ptr& ptr) const override; + int getBaseGold(const MWWorld::ConstPtr& ptr) const override; - int getBaseFightRating(const MWWorld::ConstPtr &ptr) const override; + void respawn(const MWWorld::Ptr& ptr) const override; - void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const override; - /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh + int getBaseFightRating(const MWWorld::ConstPtr& ptr) const override; - void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const override; + void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const override; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh - void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const override; + void setBaseAISetting(const ESM::RefId& id, MWMechanics::AiSetting setting, int value) const override; - float getWalkSpeed(const MWWorld::Ptr& ptr) const override; + void modifyBaseInventory(const ESM::RefId& actorId, const ESM::RefId& itemId, int amount) const override; - float getRunSpeed(const MWWorld::Ptr& ptr) const override; + float getWalkSpeed(const MWWorld::Ptr& ptr) const override; - float getSwimSpeed(const MWWorld::Ptr& ptr) const override; + float getRunSpeed(const MWWorld::Ptr& ptr) const override; + + float getSwimSpeed(const MWWorld::Ptr& ptr) const override; }; } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index d12ef9b34..d3fb79b58 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -1,13 +1,20 @@ #include "creaturelevlist.hpp" -#include -#include +#include +#include #include "../mwmechanics/levelledlist.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/customdata.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/manualref.hpp" + #include "../mwmechanics/creaturestats.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + namespace MWClass { class CreatureLevListCustomData : public MWWorld::TypedCustomData @@ -17,19 +24,38 @@ namespace MWClass int mSpawnActorId; bool mSpawn; // Should a new creature be spawned? - CreatureLevListCustomData& asCreatureLevListCustomData() override - { - return *this; - } - const CreatureLevListCustomData& asCreatureLevListCustomData() const override - { - return *this; - } + CreatureLevListCustomData& asCreatureLevListCustomData() override { return *this; } + const CreatureLevListCustomData& asCreatureLevListCustomData() const override { return *this; } }; - std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const + CreatureLevList::CreatureLevList() + : MWWorld::RegisteredClass(ESM::CreatureLevList::sRecordId) { - return ""; + } + + MWWorld::Ptr CreatureLevList::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const + { + const MWWorld::LiveCellRef* ref = ptr.get(); + + return MWWorld::Ptr(cell.insert(ref), &cell); + } + + void CreatureLevList::adjustPosition(const MWWorld::Ptr& ptr, bool force) const + { + if (ptr.getRefData().getCustomData() == nullptr) + return; + + CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); + MWWorld::Ptr creature = (customData.mSpawnActorId == -1) + ? MWWorld::Ptr() + : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (!creature.isEmpty()) + MWBase::Environment::get().getWorld()->adjustPosition(creature, force); + } + + std::string_view CreatureLevList::getName(const MWWorld::ConstPtr& ptr) const + { + return {}; } bool CreatureLevList::hasToolTip(const MWWorld::ConstPtr& ptr) const @@ -37,7 +63,7 @@ namespace MWClass return false; } - void CreatureLevList::respawn(const MWWorld::Ptr &ptr) const + void CreatureLevList::respawn(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); @@ -45,7 +71,13 @@ namespace MWClass if (customData.mSpawn) return; - MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + MWWorld::Ptr creature; + if (customData.mSpawnActorId != -1) + { + creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (creature.isEmpty()) + creature = ptr.getCell()->getMovedActor(customData.mSpawnActorId); + } if (!creature.isEmpty()) { const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); @@ -53,7 +85,8 @@ namespace MWClass customData.mSpawn = true; else if (creatureStats.isDead()) { - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); @@ -66,32 +99,27 @@ namespace MWClass customData.mSpawn = true; } - void CreatureLevList::registerSelf() - { - std::shared_ptr instance (new CreatureLevList); - - registerClass (typeid (ESM::CreatureLevList).name(), instance); - } - - void CreatureLevList::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + void CreatureLevList::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const { // disable for now, too many false positives /* const MWWorld::LiveCellRef *ref = ptr.get(); - for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != ref->mBase->mList.end(); ++it) + for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != + ref->mBase->mList.end(); ++it) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (it->mLevel > player.getClass().getCreatureStats(player).getLevel()) continue; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); MWWorld::ManualRef ref(store, it->mId); ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models); } */ } - void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string& model, MWRender::RenderingInterface &renderingInterface) const + void CreatureLevList::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { ensureCustomData(ptr); @@ -99,23 +127,23 @@ namespace MWClass if (!customData.mSpawn) return; - MWWorld::LiveCellRef *ref = - ptr.get(); - - std::string id = MWMechanics::getLevelledItem(ref->mBase, true); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::RefId& id = MWMechanics::getLevelledItem( + store.get().find(ptr.getCellRef().getRefId()), true, prng); if (!id.empty()) { // Delete the previous creature if (customData.mSpawnActorId != -1) { - MWWorld::Ptr creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + MWWorld::Ptr creature + = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); if (!creature.isEmpty()) MWBase::Environment::get().getWorld()->deleteObject(creature); customData.mSpawnActorId = -1; } - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ManualRef manualRef(store, id); manualRef.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); @@ -126,7 +154,8 @@ namespace MWClass */ /* manualRef.getPtr().getCellRef().setScale(ptr.getCellRef().getScale()); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(manualRef.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject( + manualRef.getPtr(), ptr.getCell(), ptr.getCellRef().getPosition()); customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); */ /* @@ -138,7 +167,7 @@ namespace MWClass customData.mSpawn = false; } - void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const + void CreatureLevList::ensureCustomData(const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { @@ -150,8 +179,7 @@ namespace MWClass } } - void CreatureLevList::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const + void CreatureLevList::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; @@ -163,8 +191,7 @@ namespace MWClass customData.mSpawn = levListState.mSpawn; } - void CreatureLevList::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) - const + void CreatureLevList::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 35152a942..d689d1770 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -1,37 +1,44 @@ #ifndef GAME_MWCLASS_CREATURELEVLIST_H #define GAME_MWCLASS_CREATURELEVLIST_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class CreatureLevList : public MWWorld::Class + class CreatureLevList : public MWWorld::RegisteredClass { - void ensureCustomData (const MWWorld::Ptr& ptr) const; + friend MWWorld::RegisteredClass; - public: + CreatureLevList(); - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + void ensureCustomData(const MWWorld::Ptr& ptr) const; - bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; - ///< @return true if this object has a tooltip when focused (default implementation: true) + public: + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - static void registerSelf(); + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; + ///< @return true if this object has a tooltip when focused (default implementation: true) - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; - ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: + ///< list getModel(). - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; - ///< Read additional state from \a state into \a ptr. + void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; + ///< Read additional state from \a state into \a ptr. - void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; - ///< Write additional state from \a ptr into \a state. + void writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; + ///< Write additional state from \a ptr into \a state. - void respawn (const MWWorld::Ptr& ptr) const override; + void respawn(const MWWorld::Ptr& ptr) const override; + + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; + + void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; }; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index f61f246e9..978e2530a 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -1,46 +1,41 @@ #include "door.hpp" -/* - Start of tes3mp addition +#include - Include additional headers for multiplayer purposes -*/ -#include "../mwmp/Main.hpp" -#include "../mwmp/Networking.hpp" -#include "../mwmp/ObjectList.hpp" -/* - End of tes3mp addition -*/ - -#include -#include +#include +#include +#include #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/failedaction.hpp" -#include "../mwworld/actionteleport.hpp" -#include "../mwworld/actiondoor.hpp" -#include "../mwworld/cellstore.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" -#include "../mwworld/inventorystore.hpp" +#include "../mwworld/actiondoor.hpp" +#include "../mwworld/actionteleport.hpp" #include "../mwworld/actiontrap.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/failedaction.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" +#include "../mwrender/animation.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" -#include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" #include "../mwmechanics/actorutil.hpp" +#include "classmodel.hpp" + namespace MWClass { class DoorCustomData : public MWWorld::TypedCustomData @@ -48,29 +43,29 @@ namespace MWClass public: MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle; - DoorCustomData& asDoorCustomData() override - { - return *this; - } - const DoorCustomData& asDoorCustomData() const override - { - return *this; - } + DoorCustomData& asDoorCustomData() override { return *this; } + const DoorCustomData& asDoorCustomData() const override { return *this; } }; - void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Door::Door() + : MWWorld::RegisteredClass(ESM::Door::sRecordId) + { + } + + void Door::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, true); + renderingInterface.getObjects().insertModel(ptr, model); ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, MWPhysics::CollisionType_Door); + insertObjectPhysics(ptr, model, rotation, physics); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) @@ -83,6 +78,12 @@ namespace MWClass } } + void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const + { + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); + } + bool Door::isDoor() const { return true; @@ -93,68 +94,59 @@ namespace MWClass return true; } - std::string Door::getModel(const MWWorld::ConstPtr &ptr) const + std::string Door::getModel(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; + return getClassModel(ptr); } - std::string Door::getName (const MWWorld::ConstPtr& ptr) const + std::string_view Door::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Door::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Door::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); - const std::string &openSound = ref->mBase->mOpenSound; - const std::string &closeSound = ref->mBase->mCloseSound; - const std::string lockedSound = "LockedDoor"; - const std::string trapActivationSound = "Disarm Trap Fail"; + const ESM::RefId& openSound = ref->mBase->mOpenSound; + const ESM::RefId& closeSound = ref->mBase->mCloseSound; + const ESM::RefId lockedSound = ESM::RefId::stringRefId("LockedDoor"); - MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); - - bool isLocked = ptr.getCellRef().getLockLevel() > 0; - bool isTrapped = !ptr.getCellRef().getTrap().empty(); - bool hasKey = false; - std::string keyName; - - // FIXME: If NPC activate teleporting door, it can lead to crash due to iterator invalidation in the Actors update. - // Make such activation a no-op for now, like how it is in the vanilla game. + // FIXME: If NPC activate teleporting door, it can lead to crash due to iterator invalidation in the Actors + // update. Make such activation a no-op for now, like how it is in the vanilla game. if (actor != MWMechanics::getPlayer() && ptr.getCellRef().getTeleport()) { - std::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); + std::unique_ptr action = std::make_unique(std::string_view{}, ptr); action->setSound(lockedSound); return action; } // make door glow if player activates it with telekinesis - if (actor == MWMechanics::getPlayer() && - MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > - MWBase::Environment::get().getWorld()->getMaxActivationDistance()) + if (actor == MWMechanics::getPlayer() + && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() + > MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); - if(animation) + if (animation) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis"); - const ESM::MagicEffect *effect = store.get().find(index); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::MagicEffect* effect = store.get().find(ESM::MagicEffect::Telekinesis); - animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing + animation->addSpellCastGlow( + effect, 1); // 1 second glow to match the time taken for a door opening or closing } } - const std::string keyId = ptr.getCellRef().getKey(); + MWWorld::ContainerStore& invStore = actor.getClass().getContainerStore(actor); + + bool isLocked = ptr.getCellRef().isLocked(); + bool isTrapped = !ptr.getCellRef().getTrap().empty(); + bool hasKey = false; + std::string_view keyName; + const ESM::RefId& keyId = ptr.getCellRef().getKey(); if (!keyId.empty()) { MWWorld::Ptr keyPtr = invStore.search(keyId); @@ -167,9 +159,8 @@ namespace MWClass if (isLocked && hasKey) { - if(actor == MWMechanics::getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); - + if (actor == MWMechanics::getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox(std::string{ keyName } + " #{sKeyUsed}"); /* Start of tes3mp change (major) @@ -180,9 +171,8 @@ namespace MWClass /* End of tes3mp change (major) */ - // using a key disarms the trap - if(isTrapped) + if (isTrapped) { /* Start of tes3mp change (major) @@ -190,12 +180,12 @@ namespace MWClass Disable unilateral trap disarming on this client and expect the server's reply to our packet to do it instead */ - //ptr.getCellRef().setTrap(""); - //MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f); + // ptr.getCellRef().setTrap(ESM::RefId()); + // MWBase::Environment::get().getSoundManager()->playSound3D( + // ptr, ESM::RefId::stringRefId("Disarm Trap"), 1.0f, 1.0f); /* End of tes3mp change (major) */ - isTrapped = false; /* @@ -233,21 +223,23 @@ namespace MWClass if (!isLocked || hasKey) { - if(isTrapped) + if (isTrapped) { // Trap activation - std::shared_ptr action(new MWWorld::ActionTrap(ptr.getCellRef().getTrap(), ptr)); - action->setSound(trapActivationSound); + std::unique_ptr action + = std::make_unique(ptr.getCellRef().getTrap(), ptr); + action->setSound(ESM::RefId::stringRefId("Disarm Trap Fail")); return action; } if (ptr.getCellRef().getTeleport()) { - if (actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getMaxActivationDistance()) + if (actor == MWMechanics::getPlayer() + && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() + > MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { // player activated teleport door with telekinesis - std::shared_ptr action(new MWWorld::FailedAction); - return action; + return std::make_unique(); } else { @@ -262,11 +254,12 @@ namespace MWClass if (mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides.count(destinationCell) != 0) destinationCell = mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides[destinationCell]; - std::shared_ptr action(new MWWorld::ActionTeleport(destinationCell, ptr.getCellRef().getDoorDest(), true)); + std::unique_ptr action = std::make_unique( + destinationCell, ptr.getCellRef().getDoorDest(), true); /* End of tes3mp change (major) */ - + action->setSound(openSound); return action; } @@ -274,7 +267,7 @@ namespace MWClass else { // animated door - std::shared_ptr action(new MWWorld::ActionDoor(ptr)); + std::unique_ptr action = std::make_unique(ptr); const auto doorState = getDoorState(ptr); bool opening = true; float doorRot = ptr.getRefData().getPosition().rot[2] - ptr.getCellRef().getPosition().rot[2]; @@ -285,19 +278,17 @@ namespace MWClass if (opening) { - MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, - closeSound, 0.5f); + MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, closeSound, 0.5f); // Doors rotate at 90 degrees per second, so start the sound at // where it would be at the current rotation. - float offset = doorRot/(osg::PI * 0.5f); + float offset = doorRot / (osg::PI * 0.5f); action->setSoundOffset(offset); action->setSound(openSound); } else { - MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, - openSound, 0.5f); - float offset = 1.0f - doorRot/(osg::PI * 0.5f); + MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, openSound, 0.5f); + float offset = 1.0f - doorRot / (osg::PI * 0.5f); action->setSoundOffset(std::max(offset, 0.0f)); action->setSound(closeSound); } @@ -308,45 +299,39 @@ namespace MWClass else { // locked, and we can't open. - std::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); + std::unique_ptr action = std::make_unique(std::string_view{}, ptr); action->setSound(lockedSound); return action; } } - bool Door::canLock(const MWWorld::ConstPtr &ptr) const + bool Door::canLock(const MWWorld::ConstPtr& ptr) const { return true; } - bool Door::allowTelekinesis(const MWWorld::ConstPtr &ptr) const + bool Door::allowTelekinesis(const MWWorld::ConstPtr& ptr) const { - if (ptr.getCellRef().getTeleport() && ptr.getCellRef().getLockLevel() <= 0 && ptr.getCellRef().getTrap().empty()) + if (ptr.getCellRef().getTeleport() && !ptr.getCellRef().isLocked() && ptr.getCellRef().getTrap().empty()) return false; else return true; } - std::string Door::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Door::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - void Door::registerSelf() + MWGui::ToolTipInfo Door::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - std::shared_ptr instance (new Door); - - registerClass (typeid (ESM::Door).name(), instance); - } - - MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); + std::string_view name = getName(ptr); + info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)); std::string text; @@ -357,17 +342,20 @@ namespace MWClass } int lockLevel = ptr.getCellRef().getLockLevel(); - if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock) - text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel()); - else if (ptr.getCellRef().getLockLevel() < 0) - text += "\n#{sUnlocked}"; - if (ptr.getCellRef().getTrap() != "") + if (lockLevel) + { + if (ptr.getCellRef().isLocked()) + text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(lockLevel); + else + text += "\n#{sUnlocked}"; + } + if (!ptr.getCellRef().getTrap().empty()) text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; @@ -376,41 +364,32 @@ namespace MWClass std::string Door::getDestination(const MWWorld::LiveCellRef& door) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - std::string dest = door.mRef.getDestCell(); - if (dest.empty()) - { - // door leads to exterior, use cell name (if any), otherwise translated region name - int x, y; - auto world = MWBase::Environment::get().getWorld(); - world->positionToIndex(door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); - const ESM::Cell* cell = world->getStore().get().search(x, y); - dest = world->getCellName(cell); - } /* Start of tes3mp addition If there is a destination override in the mwmp::Worldstate for this door's original destination, use it */ - else if (mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides.count(dest) != 0) + if (mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides.count(dest) != 0) dest = mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides[dest]; /* End of tes3mp addition */ + std::string_view dest = MWBase::Environment::get().getWorld()->getCellName( + &MWBase::Environment::get().getWorldModel()->getCell(dest));d64a6f0e9fe3962c88618e8b27aad1b7a7 - return "#{sCell=" + dest + "}"; + return "#{sCell=" + std::string{ dest } + "}"; } - MWWorld::Ptr Door::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Door::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - void Door::ensureCustomData(const MWWorld::Ptr &ptr) const + void Door::ensureCustomData(const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { @@ -418,7 +397,7 @@ namespace MWClass } } - MWWorld::DoorState Door::getDoorState (const MWWorld::ConstPtr &ptr) const + MWWorld::DoorState Door::getDoorState(const MWWorld::ConstPtr& ptr) const { if (!ptr.getRefData().getCustomData()) return MWWorld::DoorState::Idle; @@ -426,7 +405,7 @@ namespace MWClass return customData.mDoorState; } - void Door::setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const + void Door::setDoorState(const MWWorld::Ptr& ptr, MWWorld::DoorState state) const { if (ptr.getCellRef().getTeleport()) throw std::runtime_error("load doors can't be moved"); @@ -436,7 +415,7 @@ namespace MWClass customData.mDoorState = state; } - void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const + void Door::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; @@ -447,7 +426,7 @@ namespace MWClass customData.mDoorState = MWWorld::DoorState(doorState.mDoorState); } - void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const + void Door::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 6c2fa26b8..17ada40c6 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -1,64 +1,67 @@ #ifndef GAME_MWCLASS_DOOR_H #define GAME_MWCLASS_DOOR_H -#include +#include -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Door : public MWWorld::Class + class Door : public MWWorld::RegisteredClass { - void ensureCustomData (const MWWorld::Ptr& ptr) const; + friend MWWorld::RegisteredClass; - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + Door(); - public: + void ensureCustomData(const MWWorld::Ptr& ptr) const; - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - bool isDoor() const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; - bool useAnim() const override; + bool isDoor() const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + bool useAnim() const override; - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - static std::string getDestination (const MWWorld::LiveCellRef& door); - ///< @return destination cell name or token + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - bool canLock(const MWWorld::ConstPtr &ptr) const override; + static std::string getDestination(const MWWorld::LiveCellRef& door); + ///< @return destination cell name or token - bool allowTelekinesis(const MWWorld::ConstPtr &ptr) const override; - ///< Return whether this class of object can be activated with telekinesis + bool canLock(const MWWorld::ConstPtr& ptr) const override; - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const override; + ///< Return whether this class of object can be activated with telekinesis - static void registerSelf(); + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - MWWorld::DoorState getDoorState (const MWWorld::ConstPtr &ptr) const override; - /// This does not actually cause the door to move. Use World::activateDoor instead. - void setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const override; + MWWorld::DoorState getDoorState(const MWWorld::ConstPtr& ptr) const override; + /// This does not actually cause the door to move. Use World::activateDoor instead. + void setDoorState(const MWWorld::Ptr& ptr, MWWorld::DoorState state) const override; + void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; + ///< Read additional state from \a state into \a ptr. - void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; - ///< Read additional state from \a state into \a ptr. - - void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; - ///< Write additional state from \a ptr into \a state. + void writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/esm4base.cpp b/apps/openmw/mwclass/esm4base.cpp new file mode 100644 index 000000000..a99ecda92 --- /dev/null +++ b/apps/openmw/mwclass/esm4base.cpp @@ -0,0 +1,44 @@ +#include "esm4base.hpp" + +#include + +#include + +#include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" + +#include "../mwrender/objects.hpp" +#include "../mwrender/renderinginterface.hpp" +#include "../mwrender/vismask.hpp" + +#include "../mwphysics/physicssystem.hpp" +#include "../mwworld/ptr.hpp" + +#include "classmodel.hpp" + +namespace MWClass +{ + void ESM4Impl::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) + { + if (!model.empty()) + { + renderingInterface.getObjects().insertModel(ptr, model); + ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); + } + } + + void ESM4Impl::insertObjectPhysics( + const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) + { + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); + } + + MWGui::ToolTipInfo ESM4Impl::getToolTipInfo(std::string_view name, int count) + { + MWGui::ToolTipInfo info; + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + return info; + } +} diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp new file mode 100644 index 000000000..abd4989a6 --- /dev/null +++ b/apps/openmw/mwclass/esm4base.hpp @@ -0,0 +1,129 @@ +#ifndef GAME_MWCLASS_ESM4BASE_H +#define GAME_MWCLASS_ESM4BASE_H + +#include +#include +#include + +#include "../mwgui/tooltips.hpp" + +#include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" + +#include "classmodel.hpp" + +namespace MWClass +{ + + namespace ESM4Impl + { + void insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface); + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics); + MWGui::ToolTipInfo getToolTipInfo(std::string_view name, int count); + } + + // Base for all ESM4 Classes + template + class ESM4Base : public MWWorld::Class + { + + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override + { + const MWWorld::LiveCellRef* ref = ptr.get(); + return MWWorld::Ptr(cell.insert(ref), &cell); + } + + protected: + explicit ESM4Base(unsigned type) + : MWWorld::Class(type) + { + } + + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override + { + ESM4Impl::insertObjectRendering(ptr, model, renderingInterface); + } + + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override + { + insertObjectPhysics(ptr, model, rotation, physics); + } + + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override + { + ESM4Impl::insertObjectPhysics(ptr, model, rotation, physics); + } + + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return false; } + + std::string_view getName(const MWWorld::ConstPtr& ptr) const override { return {}; } + + std::string getModel(const MWWorld::ConstPtr& ptr) const override + { + std::string model = getClassModel(ptr); + + // Hide meshes meshes/marker/* and *LOD.nif in ESM4 cells. It is a temporarty hack. + // Needed because otherwise LOD meshes are rendered on top of normal meshes. + // TODO: Figure out a better way find markers and LOD meshes; show LOD only outside of active grid. + if (model.empty() || Misc::StringUtils::ciStartsWith(model, "meshes\\marker") + || Misc::StringUtils::ciEndsWith(model, "lod.nif")) + return {}; + + return model; + } + }; + + class ESM4Static final : public MWWorld::RegisteredClass> + { + friend MWWorld::RegisteredClass>; + ESM4Static() + : MWWorld::RegisteredClass>(ESM4::Static::sRecordId) + { + } + }; + + class ESM4Tree final : public MWWorld::RegisteredClass> + { + friend MWWorld::RegisteredClass>; + ESM4Tree() + : MWWorld::RegisteredClass>(ESM4::Tree::sRecordId) + { + } + }; + + // For records with `mFullName` that should be shown as a tooltip. + // All objects with a tooltip can be activated (activation can be handled in Lua). + template + class ESM4Named : public MWWorld::RegisteredClass, ESM4Base> + { + public: + friend MWWorld::RegisteredClass>; + + ESM4Named() + : MWWorld::RegisteredClass>(Record::sRecordId) + { + } + + public: + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return true; } + + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override + { + return ESM4Impl::getToolTipInfo(ptr.get()->mBase->mFullName, count); + } + + std::string_view getName(const MWWorld::ConstPtr& ptr) const override + { + return ptr.get()->mBase->mFullName; + } + }; +} + +#endif // GAME_MWCLASS_ESM4BASE_H diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index bc0f0f44c..bcf41967e 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -12,35 +12,46 @@ End of tes3mp addition */ -#include +#include + +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" +#include "../mwworld/actioneat.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwphysics/physicssystem.hpp" -#include "../mwworld/actioneat.hpp" #include "../mwworld/nullaction.hpp" +#include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Ingredient::Ingredient() + : MWWorld::RegisteredClass(ESM::Ingredient::sRecordId) { - if (!model.empty()) { + } + + void Ingredient::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Ingredient::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -64,87 +75,75 @@ namespace MWClass /* End of tes3mp addition */ + + return getClassModel(ptr); } - std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Ingredient::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Ingredient::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Ingredient::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Ingredient::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - std::string Ingredient::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Ingredient::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - int Ingredient::getValue (const MWWorld::ConstPtr& ptr) const + int Ingredient::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - - std::shared_ptr Ingredient::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Ingredient::use(const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action (new MWWorld::ActionEat (ptr)); + if (ptr.get()->mBase->mData.mEffectID[0] < 0) + return std::make_unique(); + std::unique_ptr action = std::make_unique(ptr); - action->setSound ("Swallow"); + action->setSound(ESM::RefId::stringRefId("Swallow")); return action; } - void Ingredient::registerSelf() + const ESM::RefId& Ingredient::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Ingredient); - - registerClass (typeid (ESM::Ingredient).name(), instance); + static auto sound = ESM::RefId::stringRefId("Item Ingredient Up"); + return sound; } - std::string Ingredient::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Ingredient::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Ingredient Up"); + static auto sound = ESM::RefId::stringRefId("Item Ingredient Down"); + return sound; } - std::string Ingredient::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const std::string& Ingredient::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Ingredient Down"); - } - - std::string Ingredient::getInventoryIcon (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Ingredient::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Ingredient::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -152,19 +151,23 @@ namespace MWClass text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); float alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); - static const float fWortChanceValue = - MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); + static const float fWortChanceValue = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fWortChanceValue") + ->mValue.getFloat(); MWGui::Widgets::SpellEffectList list; - for (int i=0; i<4; ++i) + for (int i = 0; i < 4; ++i) { if (ref->mBase->mData.mEffectID[i] < 0) continue; @@ -173,10 +176,9 @@ namespace MWClass params.mAttribute = ref->mBase->mData.mAttributes[i]; params.mSkill = ref->mBase->mData.mSkills[i]; - params.mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) - || (i == 1 && alchemySkill >= fWortChanceValue*2) - || (i == 2 && alchemySkill >= fWortChanceValue*3) - || (i == 3 && alchemySkill >= fWortChanceValue*4)); + params.mKnown = ((i == 0 && alchemySkill >= fWortChanceValue) + || (i == 1 && alchemySkill >= fWortChanceValue * 2) || (i == 2 && alchemySkill >= fWortChanceValue * 3) + || (i == 3 && alchemySkill >= fWortChanceValue * 4)); list.push_back(params); } @@ -188,22 +190,21 @@ namespace MWClass return info; } - MWWorld::Ptr Ingredient::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Ingredient::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Ingredient::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Ingredient::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Ingredients) != 0; } - - float Ingredient::getWeight(const MWWorld::ConstPtr &ptr) const + float Ingredient::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 5219cf39c..b7a36d300 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -1,56 +1,57 @@ #ifndef GAME_MWCLASS_INGREDIENT_H #define GAME_MWCLASS_INGREDIENT_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Ingredient : public MWWorld::Class + class Ingredient : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Ingredient(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu - - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - float getWeight (const MWWorld::ConstPtr& ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; + + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; }; } diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index 5608a8d23..95b8e2240 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -1,24 +1,21 @@ #include "itemlevlist.hpp" -#include +#include namespace MWClass { - - std::string ItemLevList::getName (const MWWorld::ConstPtr& ptr) const + ItemLevList::ItemLevList() + : MWWorld::RegisteredClass(ESM::ItemLevList::sRecordId) { - return ""; + } + + std::string_view ItemLevList::getName(const MWWorld::ConstPtr& ptr) const + { + return {}; } bool ItemLevList::hasToolTip(const MWWorld::ConstPtr& ptr) const { return false; } - - void ItemLevList::registerSelf() - { - std::shared_ptr instance (new ItemLevList); - - registerClass (typeid (ESM::ItemLevList).name(), instance); - } } diff --git a/apps/openmw/mwclass/itemlevlist.hpp b/apps/openmw/mwclass/itemlevlist.hpp index 771f8b7a7..56652c788 100644 --- a/apps/openmw/mwclass/itemlevlist.hpp +++ b/apps/openmw/mwclass/itemlevlist.hpp @@ -1,21 +1,22 @@ #ifndef GAME_MWCLASS_ITEMLEVLIST_H #define GAME_MWCLASS_ITEMLEVLIST_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class ItemLevList : public MWWorld::Class + class ItemLevList : public MWWorld::RegisteredClass { - public: + friend MWWorld::RegisteredClass; - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + ItemLevList(); - bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; - ///< @return true if this object has a tooltip when focused (default implementation: true) + public: + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - static void registerSelf(); + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; + ///< @return true if this object has a tooltip when focused (default implementation: true) }; } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 3bdf10f47..931ed73df 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -1,52 +1,68 @@ #include "light.hpp" -#include -#include -#include +#include + +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/ptr.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwworld/nullaction.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/cellstore.hpp" -#include "../mwphysics/physicssystem.hpp" +#include "../mwworld/nullaction.hpp" +#include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Light::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Light::Light() + : MWWorld::RegisteredClass(ESM::Light::sRecordId) { - MWWorld::LiveCellRef *ref = - ptr.get(); - - // Insert even if model is empty, so that the light is added - renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Light::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert (ref->mBase != nullptr); + MWWorld::LiveCellRef* ref = ptr.get(); - // TODO: add option somewhere to enable collision for placeable objects - if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model); + // Insert even if model is empty, so that the light is added + renderingInterface.getObjects().insertModel(ptr, model, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); + } + + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const + { + MWWorld::LiveCellRef* ref = ptr.get(); + assert(ref->mBase != nullptr); + + insertObjectPhysics(ptr, model, rotation, physics); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, - MWSound::Type::Sfx, - MWSound::PlayMode::Loop); + MWBase::Environment::get().getSoundManager()->playSound3D( + ptr, ref->mBase->mSound, 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::Loop); + } + + void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + if ((ptr.get()->mBase->mData.mFlags & ESM::Light::Carry) == 0) + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } bool Light::useAnim() const @@ -54,117 +70,112 @@ namespace MWClass return true; } - std::string Light::getModel(const MWWorld::ConstPtr &ptr) const + std::string Light::getModel(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; + return getClassModel(ptr); } - std::string Light::getName (const MWWorld::ConstPtr& ptr) const + std::string_view Light::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); if (ref->mBase->mModel.empty()) - return std::string(); + return {}; const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Light::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + bool Light::isItem(const MWWorld::ConstPtr& ptr) const { - if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) - return std::shared_ptr(new MWWorld::NullAction()); + return ptr.get()->mBase->mData.mFlags & ESM::Light::Carry; + } - MWWorld::LiveCellRef *ref = ptr.get(); - if(!(ref->mBase->mData.mFlags&ESM::Light::Carry)) - return std::shared_ptr(new MWWorld::FailedAction()); + std::unique_ptr Light::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const + { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return std::make_unique(); + + MWWorld::LiveCellRef* ref = ptr.get(); + if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) + return std::make_unique(); return defaultItemActivate(ptr, actor); } - std::string Light::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Light::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Light::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const + std::pair, bool> Light::getEquipmentSlots(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); std::vector slots_; if (ref->mBase->mData.mFlags & ESM::Light::Carry) - slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft)); + slots_.push_back(int(MWWorld::InventoryStore::Slot_CarriedLeft)); - return std::make_pair (slots_, false); + return std::make_pair(slots_, false); } - int Light::getValue (const MWWorld::ConstPtr& ptr) const + int Light::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Light::registerSelf() + const ESM::RefId& Light::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Light); - - registerClass (typeid (ESM::Light).name(), instance); + static const auto sound = ESM::RefId::stringRefId("Item Misc Up"); + return sound; } - std::string Light::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Light::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Misc Up"); + static const auto sound = ESM::RefId::stringRefId("Item Misc Down"); + return sound; } - std::string Light::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const std::string& Light::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Misc Down"); - } - - - std::string Light::getInventoryIcon (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - bool Light::hasToolTip (const MWWorld::ConstPtr& ptr) const + bool Light::hasToolTip(const MWWorld::ConstPtr& ptr) const { return showsInInventory(ptr); } - MWGui::ToolTipInfo Light::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Light::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; // Don't show duration for infinite light sources. - if (Settings::Manager::getBool("show effect duration","Game") && ptr.getClass().getRemainingUsageTime(ptr) != -1) + if (Settings::game().mShowEffectDuration && ptr.getClass().getRemainingUsageTime(ptr) != -1) text += MWGui::ToolTips::getDurationString(ptr.getClass().getRemainingUsageTime(ptr), "\n#{sDuration}"); text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; @@ -172,7 +183,7 @@ namespace MWClass return info; } - bool Light::showsInInventory (const MWWorld::ConstPtr& ptr) const + bool Light::showsInInventory(const MWWorld::ConstPtr& ptr) const { const ESM::Light* light = ptr.get()->mBase; @@ -182,58 +193,59 @@ namespace MWClass return Class::showsInInventory(ptr); } - std::shared_ptr Light::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Light::use(const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } - void Light::setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const + void Light::setRemainingUsageTime(const MWWorld::Ptr& ptr, float duration) const { ptr.getCellRef().setChargeFloat(duration); } - float Light::getRemainingUsageTime (const MWWorld::ConstPtr& ptr) const + float Light::getRemainingUsageTime(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); if (ptr.getCellRef().getCharge() == -1) return static_cast(ref->mBase->mData.mTime); else return ptr.getCellRef().getChargeFloat(); } - MWWorld::Ptr Light::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Light::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Light::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Light::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Lights) != 0; } - float Light::getWeight(const MWWorld::ConstPtr &ptr) const + float Light::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } - std::pair Light::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + std::pair Light::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) - return std::make_pair(0,""); + return { 0, {} }; - return std::make_pair(1,""); + return { 1, {} }; } - std::string Light::getSound(const MWWorld::ConstPtr& ptr) const + ESM::RefId Light::getSound(const MWWorld::ConstPtr& ptr) const { - return ptr.get()->mBase->mSound; + return ptr.get()->mBase->mSound; } + } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index e37dddc25..70d6852ff 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -1,78 +1,86 @@ #ifndef GAME_MWCLASS_LIGHT_H #define GAME_MWCLASS_LIGHT_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Light : public MWWorld::Class + class Light : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Light(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - bool useAnim() const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + bool useAnim() const override; - bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; - ///< @return true if this object has a tooltip when focused (default implementation: true) + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; + ///< @return true if this object has a tooltip when focused (default implementation: true) - bool showsInInventory (const MWWorld::ConstPtr& ptr) const override; + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool showsInInventory(const MWWorld::ConstPtr& ptr) const override; - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + bool isItem(const MWWorld::ConstPtr&) const override; - std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; - ///< \return first: Return IDs of the slot this object can be equipped in; second: can object - /// stay stacked when equipped? + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - static void registerSelf(); + std::pair, bool> getEquipmentSlots(const MWWorld::ConstPtr& ptr) const override; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - void setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const override; - ///< Sets the remaining duration of the object. + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - float getRemainingUsageTime (const MWWorld::ConstPtr& ptr) const override; - ///< Returns the remaining duration of the object. + void setRemainingUsageTime(const MWWorld::Ptr& ptr, float duration) const override; + ///< Sets the remaining duration of the object. - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + float getRemainingUsageTime(const MWWorld::ConstPtr& ptr) const override; + ///< Returns the remaining duration of the object. - float getWeight (const MWWorld::ConstPtr& ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; - std::string getSound(const MWWorld::ConstPtr& ptr) const override; + std::pair canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const override; + + ESM::RefId getSound(const MWWorld::ConstPtr& ptr) const override; }; + } #endif diff --git a/apps/openmw/mwclass/light4.cpp b/apps/openmw/mwclass/light4.cpp new file mode 100644 index 000000000..8c2cd1fda --- /dev/null +++ b/apps/openmw/mwclass/light4.cpp @@ -0,0 +1,27 @@ +#include "light4.hpp" +#include "classmodel.hpp" + +#include "../mwphysics/physicssystem.hpp" +#include "../mwrender/objects.hpp" +#include "../mwrender/renderinginterface.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/ptr.hpp" + +#include + +namespace MWClass +{ + ESM4Light::ESM4Light() + : MWWorld::RegisteredClass>(ESM4::Light::sRecordId) + { + } + + void ESM4Light ::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + MWWorld::LiveCellRef* ref = ptr.get(); + + // Insert even if model is empty, so that the light is added + renderingInterface.getObjects().insertModel(ptr, model, !(ref->mBase->mData.flags & ESM4::Light::OffDefault)); + } +} diff --git a/apps/openmw/mwclass/light4.hpp b/apps/openmw/mwclass/light4.hpp new file mode 100644 index 000000000..755f6b580 --- /dev/null +++ b/apps/openmw/mwclass/light4.hpp @@ -0,0 +1,22 @@ +#ifndef OPENW_MWCLASS_LIGHT4 +#define OPENW_MWCLASS_LIGHT4 + +#include "../mwworld/registeredclass.hpp" + +#include "esm4base.hpp" + +namespace MWClass +{ + class ESM4Light : public MWWorld::RegisteredClass> + { + friend MWWorld::RegisteredClass>; + + ESM4Light(); + + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering + }; +} +#endif diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 9b8abc8f2..1fc65c8f7 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -1,116 +1,111 @@ #include "lockpick.hpp" -#include +#include + +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwphysics/physicssystem.hpp" -#include "../mwworld/nullaction.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Lockpick::Lockpick() + : MWWorld::RegisteredClass(ESM::Lockpick::sRecordId) { - if (!model.empty()) { + } + + void Lockpick::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Lockpick::getModel(const MWWorld::ConstPtr& ptr) const { - // TODO: add option somewhere to enable collision for placeable objects + return getClassModel(ptr); } - std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Lockpick::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Lockpick::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Lockpick::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Lockpick::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - std::string Lockpick::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Lockpick::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const + std::pair, bool> Lockpick::getEquipmentSlots(const MWWorld::ConstPtr& ptr) const { std::vector slots_; - slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back(int(MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots_, false); + return std::make_pair(slots_, false); } - int Lockpick::getValue (const MWWorld::ConstPtr& ptr) const + int Lockpick::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Lockpick::registerSelf() + const ESM::RefId& Lockpick::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Lockpick); - - registerClass (typeid (ESM::Lockpick).name(), instance); + static ESM::RefId sound = ESM::RefId::stringRefId("Item Lockpick Up"); + return sound; } - std::string Lockpick::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Lockpick::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Lockpick Up"); + static ESM::RefId sound = ESM::RefId::stringRefId("Item Lockpick Down"); + return sound; } - std::string Lockpick::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const std::string& Lockpick::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Lockpick Down"); - } - - std::string Lockpick::getInventoryIcon (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Lockpick::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Lockpick::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -122,9 +117,10 @@ namespace MWClass text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; @@ -132,47 +128,48 @@ namespace MWClass return info; } - std::shared_ptr Lockpick::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Lockpick::use(const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } - MWWorld::Ptr Lockpick::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Lockpick::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - std::pair Lockpick::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + std::pair Lockpick::canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const { // Do not allow equip tools from inventory during attack if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) && MWBase::Environment::get().getWindowManager()->isGuiMode()) - return std::make_pair(0, "#{sCantEquipWeapWarning}"); + return { 0, "#{sCantEquipWeapWarning}" }; - return std::make_pair(1, ""); + return { 1, {} }; } - bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Lockpick::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Picks) != 0; } - int Lockpick::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const + int Lockpick::getItemMaxHealth(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mUses; } - float Lockpick::getWeight(const MWWorld::ConstPtr &ptr) const + float Lockpick::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index fabae3343..fc65a038a 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -1,68 +1,75 @@ #ifndef GAME_MWCLASS_LOCKPICK_H #define GAME_MWCLASS_LOCKPICK_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" + +namespace ESM +{ + class RefId; +} namespace MWClass { - class Lockpick : public MWWorld::Class + class Lockpick : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Lockpick(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; - ///< \return first: Return IDs of the slot this object can be equipped in; second: can object - /// stay stacked when equipped? + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + std::pair, bool> getEquipmentSlots(const MWWorld::ConstPtr& ptr) const override; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; + std::pair canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const override; - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; - float getWeight (const MWWorld::ConstPtr& ptr) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; - ///< Return item max health or throw an exception, if class does not have item health + int getItemMaxHealth(const MWWorld::ConstPtr& ptr) const override; + ///< Return item max health or throw an exception, if class does not have item health - bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override { return true; } - ///< \return Item health data available? (default implementation: false) + bool hasItemHealth(const MWWorld::ConstPtr& ptr) const override { return true; } + ///< \return Item health data available? (default implementation: false) }; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index cf755b40a..732708f12 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -14,42 +14,55 @@ #include #include +#include + +#include +#include + +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwworld/actionsoulgem.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwphysics/physicssystem.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/nullaction.hpp" -#include "../mwworld/actionsoulgem.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "classmodel.hpp" + namespace MWClass { - bool Miscellaneous::isGold (const MWWorld::ConstPtr& ptr) const + Miscellaneous::Miscellaneous() + : MWWorld::RegisteredClass(ESM::Miscellaneous::sRecordId) { - return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100"); } - void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + bool Miscellaneous::isGold(const MWWorld::ConstPtr& ptr) const { - if (!model.empty()) { + return ptr.getCellRef().getRefId() == "gold_001" || ptr.getCellRef().getRefId() == "gold_005" + || ptr.getCellRef().getRefId() == "gold_010" || ptr.getCellRef().getRefId() == "gold_025" + || ptr.getCellRef().getRefId() == "gold_100"; + } + + void Miscellaneous::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Miscellaneous::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -73,61 +86,52 @@ namespace MWClass /* End of tes3mp addition */ + + return getClassModel(ptr); } - std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Miscellaneous::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Miscellaneous::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Miscellaneous::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - std::string Miscellaneous::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Miscellaneous::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - int Miscellaneous::getValue (const MWWorld::ConstPtr& ptr) const + int Miscellaneous::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); int value = ref->mBase->mData.mValue; if (ptr.getCellRef().getGoldValue() > 1 && ptr.getRefData().getCount() == 1) value = ptr.getCellRef().getGoldValue(); - if (ptr.getCellRef().getSoul() != "") + if (!ptr.getCellRef().getSoul().empty()) { - const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().search(ref->mRef.getSoul()); + const ESM::Creature* creature + = MWBase::Environment::get().getESMStore()->get().search(ref->mRef.getSoul()); if (creature) { int soul = creature->mData.mSoul; - if (Settings::Manager::getBool("rebalance soul gem values", "Game")) + if (Settings::game().mRebalanceSoulGemValues) { - // use the 'soul gem value rebalance' formula from the Morrowind Code Patch + // use the 'soul gem value rebalance' formula from the Morrowind Code Patch float soulValue = 0.0001 * pow(soul, 3) + 2 * soul; - + // for Azura's star add the unfilled value - if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "Misc_SoulGem_Azura")) + if (ptr.getCellRef().getRefId() == "Misc_SoulGem_Azura") value += soulValue; else value = soulValue; @@ -140,37 +144,35 @@ namespace MWClass return value; } - void Miscellaneous::registerSelf() - { - std::shared_ptr instance (new Miscellaneous); - - registerClass (typeid (ESM::Miscellaneous).name(), instance); - } - - std::string Miscellaneous::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Miscellaneous::getUpSoundId(const MWWorld::ConstPtr& ptr) const { + static const ESM::RefId soundGold = ESM::RefId::stringRefId("Item Gold Up"); + static const ESM::RefId soundMisc = ESM::RefId::stringRefId("Item Misc Up"); if (isGold(ptr)) - return std::string("Item Gold Up"); - return std::string("Item Misc Up"); + return soundGold; + + return soundMisc; } - std::string Miscellaneous::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Miscellaneous::getDownSoundId(const MWWorld::ConstPtr& ptr) const { + static const ESM::RefId soundGold = ESM::RefId::stringRefId("Item Gold Down"); + static const ESM::RefId soundMisc = ESM::RefId::stringRefId("Item Misc Down"); if (isGold(ptr)) - return std::string("Item Gold Down"); - return std::string("Item Misc Down"); + return soundGold; + return soundMisc; } - std::string Miscellaneous::getInventoryIcon (const MWWorld::ConstPtr& ptr) const + const std::string& Miscellaneous::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Miscellaneous::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Miscellaneous::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; @@ -184,18 +186,21 @@ namespace MWClass else // gold displays its count also if it's 1. countString = " (" + std::to_string(count) + ")"; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + countString + MWGui::ToolTips::getSoulString(ptr.getCellRef()); + std::string_view name = getName(ptr); + info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + + MWGui::ToolTips::getCountString(count) + MWGui::ToolTips::getSoulString(ptr.getCellRef()); info.icon = ref->mBase->mIcon; std::string text; text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); - if (!gold && !ref->mBase->mData.mIsKey) + if (!gold && !(ref->mBase->mData.mFlags & ESM::Miscellaneous::Key)) text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; @@ -203,71 +208,96 @@ namespace MWClass return info; } - MWWorld::Ptr Miscellaneous::copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const + static MWWorld::Ptr createGold(MWWorld::CellStore& cell, int goldAmount) + { + std::string_view base = "gold_001"; + if (goldAmount >= 100) + base = "gold_100"; + else if (goldAmount >= 25) + base = "gold_025"; + else if (goldAmount >= 10) + base = "gold_010"; + else if (goldAmount >= 5) + base = "gold_005"; + + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + MWWorld::ManualRef newRef(store, ESM::RefId::stringRefId(base)); + const MWWorld::LiveCellRef* ref = newRef.getPtr().get(); + + MWWorld::Ptr ptr(cell.insert(ref), &cell); + ptr.getCellRef().setGoldValue(goldAmount); + ptr.getRefData().setCount(1); + return ptr; + } + + MWWorld::Ptr Miscellaneous::copyToCell(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell, int count) const { MWWorld::Ptr newPtr; - - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); - - if (isGold(ptr)) { - int goldAmount = getValue(ptr) * count; - - std::string base = "Gold_001"; - if (goldAmount >= 100) - base = "Gold_100"; - else if (goldAmount >= 25) - base = "Gold_025"; - else if (goldAmount >= 10) - base = "Gold_010"; - else if (goldAmount >= 5) - base = "Gold_005"; - - // Really, I have no idea why moving ref out of conditional - // scope causes list::push_back throwing std::bad_alloc - MWWorld::ManualRef newRef(store, base); - const MWWorld::LiveCellRef *ref = - newRef.getPtr().get(); - - newPtr = MWWorld::Ptr(cell.insert(ref), &cell); - newPtr.getCellRef().setGoldValue(goldAmount); - newPtr.getRefData().setCount(1); - } else { - const MWWorld::LiveCellRef *ref = - ptr.get(); + if (isGold(ptr)) + newPtr = createGold(cell, getValue(ptr) * count); + else + { + const MWWorld::LiveCellRef* ref = ptr.get(); newPtr = MWWorld::Ptr(cell.insert(ref), &cell); newPtr.getRefData().setCount(count); } newPtr.getCellRef().unsetRefNum(); - + newPtr.getRefData().setLuaScripts(nullptr); + MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); return newPtr; } - std::shared_ptr Miscellaneous::use (const MWWorld::Ptr& ptr, bool force) const + MWWorld::Ptr Miscellaneous::moveToCell(const MWWorld::Ptr& ptr, MWWorld::CellStore& cell) const { - if (ptr.getCellRef().getSoul().empty() || !MWBase::Environment::get().getWorld()->getStore().get().search(ptr.getCellRef().getSoul())) - return std::shared_ptr(new MWWorld::NullAction()); + MWWorld::Ptr newPtr; + if (isGold(ptr)) + { + newPtr = createGold(cell, getValue(ptr)); + newPtr.getRefData() = ptr.getRefData(); + newPtr.getCellRef().setRefNum(ptr.getCellRef().getRefNum()); + newPtr.getCellRef().setGoldValue(ptr.getCellRef().getGoldValue()); + newPtr.getRefData().setCount(1); + } else - return std::shared_ptr(new MWWorld::ActionSoulgem(ptr)); + { + const MWWorld::LiveCellRef* ref = ptr.get(); + newPtr = MWWorld::Ptr(cell.insert(ref), &cell); + } + ptr.getRefData().setLuaScripts(nullptr); + MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); + return newPtr; } - bool Miscellaneous::canSell (const MWWorld::ConstPtr& item, int npcServices) const + std::unique_ptr Miscellaneous::use(const MWWorld::Ptr& ptr, bool force) const { - const MWWorld::LiveCellRef *ref = item.get(); + if (isSoulGem(ptr)) + return std::make_unique(ptr); - return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) && !isGold(item); + return std::make_unique(); } - float Miscellaneous::getWeight(const MWWorld::ConstPtr &ptr) const + bool Miscellaneous::canSell(const MWWorld::ConstPtr& item, int npcServices) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = item.get(); + + return !(ref->mBase->mData.mFlags & ESM::Miscellaneous::Key) && (npcServices & ESM::NPC::Misc) && !isGold(item); + } + + float Miscellaneous::getWeight(const MWWorld::ConstPtr& ptr) const + { + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } - bool Miscellaneous::isKey(const MWWorld::ConstPtr &ptr) const + bool Miscellaneous::isKey(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mIsKey != 0; + const MWWorld::LiveCellRef* ref = ptr.get(); + return ref->mBase->mData.mFlags & ESM::Miscellaneous::Key; + } + + bool Miscellaneous::isSoulGem(const MWWorld::ConstPtr& ptr) const + { + return ptr.getCellRef().getRefId().startsWith("misc_soulgem"); } } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 9bff85ca5..dafeb0c76 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -1,60 +1,64 @@ #ifndef GAME_MWCLASS_MISC_H #define GAME_MWCLASS_MISC_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Miscellaneous : public MWWorld::Class + class Miscellaneous : public MWWorld::RegisteredClass { - public: + friend MWWorld::RegisteredClass; - MWWorld::Ptr copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const override; + Miscellaneous(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + public: + MWWorld::Ptr copyToCell(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell, int count) const override; + MWWorld::Ptr moveToCell(const MWWorld::Ptr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - float getWeight (const MWWorld::ConstPtr& ptr) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; - bool isKey (const MWWorld::ConstPtr &ptr) const override; + bool isKey(const MWWorld::ConstPtr& ptr) const override; - bool isGold (const MWWorld::ConstPtr& ptr) const override; + bool isGold(const MWWorld::ConstPtr& ptr) const override; + + bool isSoulGem(const MWWorld::ConstPtr& ptr) const override; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 7082c5c67..680e6e9e5 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,15 +1,22 @@ #include "npc.hpp" +#include + #include #include +#include #include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include /* Start of tes3mp addition @@ -28,118 +35,121 @@ End of tes3mp addition */ -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/movement.hpp" -#include "../mwmechanics/spellcasting.hpp" -#include "../mwmechanics/disease.hpp" -#include "../mwmechanics/combat.hpp" -#include "../mwmechanics/autocalcspell.hpp" -#include "../mwmechanics/difficultyscaling.hpp" -#include "../mwmechanics/weapontype.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/aisetting.hpp" +#include "../mwmechanics/autocalcspell.hpp" +#include "../mwmechanics/combat.hpp" +#include "../mwmechanics/creaturecustomdataresetter.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/difficultyscaling.hpp" +#include "../mwmechanics/disease.hpp" +#include "../mwmechanics/inventory.hpp" +#include "../mwmechanics/movement.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/setbaseaisetting.hpp" +#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/weapontype.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/actiontalk.hpp" #include "../mwworld/actionopen.hpp" +#include "../mwworld/actiontalk.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/customdata.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/customdata.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwworld/localscripts.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwrender/npcanimation.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" -#include "../mwrender/npcanimation.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" namespace { - int is_even(double d) { + int is_even(double d) + { double int_part; modf(d / 2.0, &int_part); return 2.0 * int_part == d; } - int round_ieee_754(double d) { + int round_ieee_754(double d) + { double i = floor(d); d -= i; - if(d < 0.5) + if (d < 0.5) return static_cast(i); - if(d > 0.5) + if (d > 0.5) return static_cast(i) + 1; - if(is_even(i)) + if (is_even(i)) return static_cast(i); return static_cast(i) + 1; } - void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) + void autoCalculateAttributes(const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) { // race bonus - const ESM::Race *race = - MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(npc->mRace); bool male = (npc->mFlags & ESM::NPC::Female) == 0; + const auto& attributes = MWBase::Environment::get().getESMStore()->get(); int level = creatureStats.getLevel(); - for (int i=0; imData.mAttributeValues[i]; - creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); + const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[attribute.mId]; + creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); } // class bonus - const ESM::Class *class_ = - MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); + const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(npc->mClass); - for (int i=0; i<2; ++i) + for (int attribute : class_->mData.mAttribute) { - int attribute = class_->mData.mAttribute[i]; - if (attribute>=0 && attribute<8) + if (attribute >= 0 && attribute < ESM::Attribute::Length) { - creatureStats.setAttribute(attribute, - creatureStats.getAttribute(attribute).getBase() + 10); + auto id = static_cast(attribute); + creatureStats.setAttribute(id, creatureStats.getAttribute(id).getBase() + 10); } } // skill bonus - for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute) + for (const ESM::Attribute& attribute : attributes) { float modifierSum = 0; - for (int j=0; jget()) { - const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(j); - - if (skill->mData.mAttribute != attribute) + if (skill.mData.mAttribute != attribute.mId) continue; // is this a minor or major skill? - float add=0.2f; - for (int k=0; k<5; ++k) + float add = 0.2f; + for (const auto& skills : class_->mData.mSkills) { - if (class_->mData.mSkills[k][0] == j) - add=0.5; - } - for (int k=0; k<5; ++k) - { - if (class_->mData.mSkills[k][1] == j) - add=1.0; + if (skills[0] == skill.mIndex) + add = 0.5; + if (skills[1] == skill.mIndex) + add = 1.0; } modifierSum += add; } - creatureStats.setAttribute(attribute, std::min( - round_ieee_754(creatureStats.getAttribute(attribute).getBase() - + (level-1) * modifierSum), 100) ); + creatureStats.setAttribute(attribute.mId, + std::min( + round_ieee_754(creatureStats.getAttribute(attribute.mId).getBase() + (level - 1) * modifierSum), + 100)); } // initial health @@ -153,8 +163,8 @@ namespace else if (class_->mData.mSpecialization == ESM::Class::Stealth) multiplier += 1; - if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance - || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) + if (std::find(class_->mData.mAttribute.begin(), class_->mData.mAttribute.end(), ESM::Attribute::Endurance) + != class_->mData.mAttribute.end()) multiplier += 1; creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); @@ -174,31 +184,30 @@ namespace * * and by adding class, race, specialization bonus. */ - void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised) + void autoCalculateSkills( + const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised) { - const ESM::Class *class_ = - MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); + const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(npc->mClass); unsigned int level = npcStats.getLevel(); - const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); - + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(npc->mRace); for (int i = 0; i < 2; ++i) { - int bonus = (i==0) ? 10 : 25; + int bonus = (i == 0) ? 10 : 25; - for (int i2 = 0; i2 < 5; ++i2) + for (const auto& skills : class_->mData.mSkills) { - int index = class_->mData.mSkills[i2][i]; - if (index >= 0 && index < ESM::Skill::Length) + ESM::RefId id = ESM::Skill::indexToRefId(skills[i]); + if (!id.empty()) { - npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); + npcStats.getSkill(id).setBase(npcStats.getSkill(id).getBase() + bonus); } } } - for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get()) { float majorMultiplier = 0.1f; float specMultiplier = 0.0f; @@ -206,19 +215,15 @@ namespace int raceBonus = 0; int specBonus = 0; - for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) - { - if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) - { - raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; - break; - } - } + auto bonusIt = std::find_if(race->mData.mBonus.begin(), race->mData.mBonus.end(), + [&](const auto& bonus) { return bonus.mSkill == skill.mIndex; }); + if (bonusIt != race->mData.mBonus.end()) + raceBonus = bonusIt->mBonus; - for (int k = 0; k < 5; ++k) + for (const auto& skills : class_->mData.mSkills) { // is this a minor or major skill? - if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) + if (std::find(skills.begin(), skills.end(), skill.mIndex) != skills.end()) { majorMultiplier = 1.0f; break; @@ -226,34 +231,22 @@ namespace } // is this skill in the same Specialization as the class? - const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillIndex); - if (skill->mData.mSpecialization == class_->mData.mSpecialization) + if (skill.mData.mSpecialization == class_->mData.mSpecialization) { specMultiplier = 0.5f; specBonus = 5; } - npcStats.getSkill(skillIndex).setBase( - std::min( - round_ieee_754( - npcStats.getSkill(skillIndex).getBase() - + 5 - + raceBonus - + specBonus - +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 + npcStats.getSkill(skill.mId).setBase( + std::min(round_ieee_754(npcStats.getSkill(skill.mId).getBase() + 5 + raceBonus + specBonus + + (int(level) - 1) * (majorMultiplier + specMultiplier)), + 100)); // Must gracefully handle level 0 } - int skills[ESM::Skill::Length]; - for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); + std::vector spells + = MWMechanics::autoCalcNpcSpells(npcStats.getSkills(), npcStats.getAttributes(), race); npcStats.getSpells().addAllToInstance(spells); } } @@ -261,6 +254,10 @@ namespace namespace MWClass { + Npc::Npc() + : MWWorld::RegisteredClass(ESM::NPC::sRecordId) + { + } class NpcCustomData : public MWWorld::TypedCustomData { @@ -269,24 +266,17 @@ namespace MWClass MWMechanics::Movement mMovement; MWWorld::InventoryStore mInventoryStore; - NpcCustomData& asNpcCustomData() override - { - return *this; - } - const NpcCustomData& asNpcCustomData() const override - { - return *this; - } + NpcCustomData& asNpcCustomData() override { return *this; } + const NpcCustomData& asNpcCustomData() const override { return *this; } }; const Npc::GMST& Npc::getGmst() { - static GMST gmst; - static bool inited = false; - if(!inited) - { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); + static const GMST staticGmst = [] { + GMST gmst; + + const MWWorld::Store& store + = MWBase::Environment::get().getESMStore()->get(); gmst.fMinWalkSpeed = store.find("fMinWalkSpeed"); gmst.fMaxWalkSpeed = store.find("fMaxWalkSpeed"); @@ -309,29 +299,33 @@ namespace MWClass gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult"); - inited = true; - } - return gmst; + return gmst; + }(); + return staticGmst; } - void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const + void Npc::ensureCustomData(const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data(new NpcCustomData); + bool recalculate = false; + auto tempData = std::make_unique(); + NpcCustomData* data = tempData.get(); + MWMechanics::CreatureCustomDataResetter resetter{ ptr }; + ptr.getRefData().setCustomData(std::move(tempData)); - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId); // creature stats - int gold=0; - if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + int gold = 0; + if (ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { gold = ref->mBase->mNpdt.mGold; - for (unsigned int i=0; i< ESM::Skill::Length; ++i) - data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt.mSkills[i]); + for (size_t i = 0; i < ref->mBase->mNpdt.mSkills.size(); ++i) + data->mNpcStats.getSkill(ESM::Skill::indexToRefId(i)).setBase(ref->mBase->mNpdt.mSkills[i]); data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength); data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence); @@ -342,22 +336,20 @@ namespace MWClass data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt.mPersonality); data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt.mLuck); - data->mNpcStats.setHealth (ref->mBase->mNpdt.mHealth); - data->mNpcStats.setMagicka (ref->mBase->mNpdt.mMana); - data->mNpcStats.setFatigue (ref->mBase->mNpdt.mFatigue); + data->mNpcStats.setHealth(ref->mBase->mNpdt.mHealth); + data->mNpcStats.setMagicka(ref->mBase->mNpdt.mMana); + data->mNpcStats.setFatigue(ref->mBase->mNpdt.mFatigue); data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); - - data->mNpcStats.setNeedRecalcDynamicStats(false); } else { gold = ref->mBase->mNpdt.mGold; - for (int i=0; i<3; ++i) - data->mNpcStats.setDynamic (i, 10); + for (int i = 0; i < 3; ++i) + data->mNpcStats.setDynamic(i, 10); data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition); @@ -366,7 +358,7 @@ namespace MWClass autoCalculateAttributes(ref->mBase, data->mNpcStats); autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised); - data->mNpcStats.setNeedRecalcDynamicStats(true); + recalculate = true; } // Persistent actors with 0 health do not play death animation @@ -374,72 +366,83 @@ namespace MWClass data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr)); // race powers - const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mRace); data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList); if (!ref->mBase->mFaction.empty()) { - static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get() - .find("iAutoRepFacMod")->mValue.getInteger(); - static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get() - .find("iAutoRepLevMod")->mValue.getInteger(); + static const int iAutoRepFacMod = MWBase::Environment::get() + .getESMStore() + ->get() + .find("iAutoRepFacMod") + ->mValue.getInteger(); + static const int iAutoRepLevMod = MWBase::Environment::get() + .getESMStore() + ->get() + .find("iAutoRepLevMod") + ->mValue.getInteger(); int rank = ref->mBase->getFactionRank(); - data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1)); + data->mNpcStats.setReputation( + iAutoRepFacMod * (rank + 1) + iAutoRepLevMod * (data->mNpcStats.getLevel() - 1)); } data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage); - data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); - data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); - data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); - data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); + data->mNpcStats.setAiSetting(MWMechanics::AiSetting::Hello, ref->mBase->mAiData.mHello); + data->mNpcStats.setAiSetting(MWMechanics::AiSetting::Fight, ref->mBase->mAiData.mFight); + data->mNpcStats.setAiSetting(MWMechanics::AiSetting::Flee, ref->mBase->mAiData.mFlee); + data->mNpcStats.setAiSetting(MWMechanics::AiSetting::Alarm, ref->mBase->mAiData.mAlarm); // spells if (!spellsInitialised) data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); - // inventory - // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items - data->mInventoryStore.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); - data->mNpcStats.setGoldPool(gold); // store - ptr.getRefData().setCustomData(std::move(data)); + resetter.mPtr = {}; + if (recalculate) + data->mNpcStats.recalculateMagicka(); - getInventoryStore(ptr).autoEquip(ptr); + // inventory + // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); + + getInventoryStore(ptr).autoEquip(); } } - void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + void Npc::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { renderingInterface.getObjects().insertNPC(ptr); } - bool Npc::isPersistent(const MWWorld::ConstPtr &actor) const + bool Npc::isPersistent(const MWWorld::ConstPtr& actor) const { const MWWorld::LiveCellRef* ref = actor.get(); - return ref->mBase->mPersistent; + return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0; } - std::string Npc::getModel(const MWWorld::ConstPtr &ptr) const + std::string Npc::getModel(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); std::string model = Settings::Manager::getString("baseanim", "Models"); - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); - if(race->mData.mFlags & ESM::Race::Beast) + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mRace); + if (race->mData.mFlags & ESM::Race::Beast) model = Settings::Manager::getString("baseanimkna", "Models"); return model; } - void Npc::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const + void Npc::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const { - const MWWorld::LiveCellRef *npc = ptr.get(); - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mRace); - if(race && race->mData.mFlags & ESM::Race::Beast) + const MWWorld::LiveCellRef* npc = ptr.get(); + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mRace); + if (race && race->mData.mFlags & ESM::Race::Beast) models.emplace_back(Settings::Manager::getString("baseanimkna", "Models")); // keep these always loaded just in case @@ -447,20 +450,24 @@ namespace MWClass models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models")); models.emplace_back(Settings::Manager::getString("xbaseanim", "Models")); + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + if (!npc->mBase->mModel.empty()) - models.push_back("meshes/"+npc->mBase->mModel); + models.push_back(Misc::ResourceHelpers::correctMeshPath(npc->mBase->mModel, vfs)); if (!npc->mBase->mHead.empty()) { - const ESM::BodyPart* head = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHead); + const ESM::BodyPart* head + = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHead); if (head) - models.push_back("meshes/"+head->mModel); + models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel, vfs)); } if (!npc->mBase->mHair.empty()) { - const ESM::BodyPart* hair = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mHair); + const ESM::BodyPart* hair + = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHair); if (hair) - models.push_back("meshes/"+hair->mModel); + models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel, vfs)); } bool female = (npc->mBase->mFlags & ESM::NPC::Female); @@ -474,14 +481,14 @@ namespace MWClass if (equipped != invStore.end()) { std::vector parts; - if(equipped->getTypeName() == typeid(ESM::Clothing).name()) + if (equipped->getType() == ESM::Clothing::sRecordId) { - const ESM::Clothing *clothes = equipped->get()->mBase; + const ESM::Clothing* clothes = equipped->get()->mBase; parts = clothes->mParts.mParts; } - else if(equipped->getTypeName() == typeid(ESM::Armor).name()) + else if (equipped->getType() == ESM::Armor::sRecordId) { - const ESM::Armor *armor = equipped->get()->mBase; + const ESM::Armor* armor = equipped->get()->mBase; parts = armor->mParts.mParts; } else @@ -493,12 +500,13 @@ namespace MWClass for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) { - std::string partname = female ? it->mFemale : it->mMale; - if (partname.empty()) - partname = female ? it->mMale : it->mFemale; - const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(partname); + const ESM::RefId& partname + = (female && !it->mFemale.empty()) || (!female && it->mMale.empty()) ? it->mFemale : it->mMale; + + const ESM::BodyPart* part + = MWBase::Environment::get().getESMStore()->get().search(partname); if (part && !part->mModel.empty()) - models.push_back("meshes/"+part->mModel); + models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel, vfs)); } } } @@ -506,49 +514,49 @@ namespace MWClass // preload body parts if (race) { - const std::vector& parts = MWRender::NpcAnimation::getBodyParts(Misc::StringUtils::lowerCase(race->mId), female, false, false); + const std::vector& parts + = MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false); for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) { const ESM::BodyPart* part = *it; if (part && !part->mModel.empty()) - models.push_back("meshes/"+part->mModel); + models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel, vfs)); } } - } - std::string Npc::getName (const MWWorld::ConstPtr& ptr) const + std::string_view Npc::getName(const MWWorld::ConstPtr& ptr) const { - if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) + if (ptr.getRefData().getCustomData() + && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); + const MWWorld::Store& store + = MWBase::Environment::get().getESMStore()->get(); return store.find("sWerewolfPopup")->mValue.getString(); } - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const + MWMechanics::CreatureStats& Npc::getCreatureStats(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); + ensureCustomData(ptr); return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; } - MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const + MWMechanics::NpcStats& Npc::getNpcStats(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); + ensureCustomData(ptr); return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; } - - void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const + bool Npc::evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const { /* Start of tes3mp addition @@ -557,70 +565,69 @@ namespace MWClass */ if (mwmp::PlayerList::isDedicatedPlayer(ptr) || mwmp::Main::get().getCellController()->isDedicatedActor(ptr)) { - return; + return true; } /* End of tes3mp addition */ - MWBase::World *world = MWBase::Environment::get().getWorld(); - - const MWWorld::Store &store = world->getStore().get(); + victim = MWWorld::Ptr(); + hitPosition = osg::Vec3f(); // Get the weapon used (if hand-to-hand, weapon = inv.end()) - MWWorld::InventoryStore &inv = getInventoryStore(ptr); + MWWorld::InventoryStore& inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr()); - if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) - weapon = MWWorld::Ptr(); - - MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); + MWWorld::Ptr weapon; + if (weaponslot != inv.end() && weaponslot->getType() == ESM::Weapon::sRecordId) + weapon = *weaponslot; + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& store = world->getStore().get(); const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat(); - float dist = fCombatDistance * (!weapon.isEmpty() ? - weapon.get()->mBase->mData.mReach : - store.find("fHandToHandReach")->mValue.getFloat()); + float dist = fCombatDistance + * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach + : store.find("fHandToHandReach")->mValue.getFloat()); - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit + // result. std::vector targetActors; if (ptr != MWMechanics::getPlayer()) getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); // TODO: Use second to work out the hit angle std::pair result = world->getHitContact(ptr, dist, targetActors); - MWWorld::Ptr victim = result.first; - osg::Vec3f hitPosition (result.second); - if(victim.isEmpty()) // Didn't hit anything - return; + if (result.first.isEmpty()) // Didn't hit anything + return true; - const MWWorld::Class &othercls = victim.getClass(); - /* - Start of tes3mp change (major) + const MWWorld::Class& othercls = result.first.getClass(); + if (!othercls.isActor()) // Can't hit non-actors + /* + Start of tes3mp change (major) - Send an ID_OBJECT_HIT packet when hitting non-actors instead of - just returning - */ - if(!othercls.isActor()) - { + Send an ID_OBJECT_HIT packet when hitting non-actors instead of + just returning + */ mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList(); objectList->reset(); objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY; objectList->addObjectHit(victim, ptr); objectList->sendObjectHit(); - return; - } - /* - End of tes3mp change (major) - */ - MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim); - if(otherstats.isDead()) // Can't hit dead actors - return; + /* + End of tes3mp change (major) + */ + return true; - if(ptr == MWMechanics::getPlayer()) - MWBase::Environment::get().getWindowManager()->setEnemy(victim); + MWMechanics::CreatureStats& otherstats = othercls.getCreatureStats(result.first); + if (otherstats.isDead()) // Can't hit dead actors + return true; - int weapskill = ESM::Skill::HandToHand; - if(!weapon.isEmpty()) + // Note that earlier we returned true in spite of an apparent failure to hit anything alive. + // This is because hitting nothing is not a "miss" and should be handled as such character controller-side. + victim = result.first; + hitPosition = result.second; + + ESM::RefId weapskill = ESM::Skill::HandToHand; + if (!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill)); @@ -643,8 +650,32 @@ namespace MWClass /* End of tes3mp addition */ + return Misc::Rng::roll0to99(world->getPrng()) < hitchance; + } - if (Misc::Rng::roll0to99() >= hitchance) + void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim, + const osg::Vec3f& hitPosition, bool success) const + { + MWWorld::InventoryStore& inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + MWWorld::Ptr weapon; + if (weaponslot != inv.end() && weaponslot->getType() == ESM::Weapon::sRecordId) + weapon = *weaponslot; + + MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); + + if (victim.isEmpty()) // Didn't hit anything + return; + + const MWWorld::Class& othercls = victim.getClass(); + MWMechanics::CreatureStats& otherstats = othercls.getCreatureStats(victim); + if (otherstats.isDead()) // Can't hit dead actors + return; + + if (ptr == MWMechanics::getPlayer()) + MWBase::Environment::get().getWindowManager()->setEnemy(victim); + + if (!success) { /* Start of tes3mp addition @@ -677,42 +708,50 @@ namespace MWClass bool healthdmg; float damage = 0.0f; - if(!weapon.isEmpty()) + if (!weapon.isEmpty()) { - const unsigned char *attack = nullptr; - if(type == ESM::Weapon::AT_Chop) + const unsigned char* attack = nullptr; + if (type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; - else if(type == ESM::Weapon::AT_Slash) + else if (type == ESM::Weapon::AT_Slash) attack = weapon.get()->mBase->mData.mSlash; - else if(type == ESM::Weapon::AT_Thrust) + else if (type == ESM::Weapon::AT_Thrust) attack = weapon.get()->mBase->mData.mThrust; - if(attack) + if (attack) { - damage = attack[0] + ((attack[1]-attack[0])*attackStrength); + damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); } MWMechanics::adjustWeaponDamage(damage, weapon, ptr); + MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); MWMechanics::applyWerewolfDamageMult(victim, weapon, damage); - MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); healthdmg = true; } else { MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); } - if(ptr == MWMechanics::getPlayer()) + + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& store = world->getStore().get(); + + if (ptr == MWMechanics::getPlayer()) { + ESM::RefId weapskill = ESM::Skill::HandToHand; + if (!weapon.isEmpty()) + weapskill = weapon.getClass().getEquipmentSkill(weapon); skillUsageSucceeded(ptr, weapskill, 0); const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence(); - bool unaware = !seq.isInCombat() - && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); - if(unaware) + bool unaware + = !seq.isInCombat() && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); + if (unaware) { damage *= store.find("fCombatCriticalStrikeMult")->mValue.getFloat(); MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + MWBase::Environment::get().getSoundManager()->playSound3D( + victim, ESM::RefId::stringRefId("critical damage"), 1.0f, 1.0f); } } @@ -738,7 +777,10 @@ namespace MWClass MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) + { damage = 0; + victim.getClass().block(victim); + } if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState()) damage = 0; @@ -748,9 +790,10 @@ namespace MWClass othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); } - void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const + void Npc::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); MWMechanics::CreatureStats& stats = getCreatureStats(ptr); bool wasDead = stats.isDead(); @@ -805,9 +848,9 @@ namespace MWClass if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { - const std::string &script = getScript(ptr); + const ESM::RefId& script = getScript(ptr); /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ - if(!script.empty()) + if (!script.empty()) ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } @@ -815,14 +858,13 @@ namespace MWClass { // Missed if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) - sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); + sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f); return; } if (!object.isEmpty()) stats.setLastHitObject(object.getCellRef().getRefId()); - if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); @@ -839,18 +881,20 @@ namespace MWClass // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying // something, alert the character controller, scripts, etc. - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const GMST& gmst = getGmst(); int chance = store.get().find("iVoiceHitOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99() < chance) - MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) < chance) + MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("hit")); // Check for knockdown - float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->mValue.getFloat(); + float agilityTerm + = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->mValue.getFloat(); float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() - * gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f + gmst.iKnockDownOddsBase->mValue.getInteger(); - + * gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f + + gmst.iKnockDownOddsBase->mValue.getInteger(); /* Start of tes3mp change (major) @@ -886,19 +930,18 @@ namespace MWClass // cuirass = 30% // shield, helmet, greaves, boots, pauldrons = 10% each // guantlets = 5% each - static const int hitslots[20] = { - MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, - MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, - MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, - MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft, - MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Helmet, - MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Greaves, - MWWorld::InventoryStore::Slot_Boots, MWWorld::InventoryStore::Slot_Boots, - MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_LeftPauldron, - MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron, - MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet - }; - int hitslot = hitslots[Misc::Rng::rollDice(20)]; + static const int hitslots[20] + = { MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots, MWWorld::InventoryStore::Slot_Boots, + MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }; + int hitslot = hitslots[Misc::Rng::rollDice(20, prng)]; float unmitigatedDamage = damage; float x = damage / (damage + getArmorRating(ptr)); @@ -907,14 +950,14 @@ namespace MWClass damage = std::max(1.f, damage); damageDiff = std::max(1, damageDiff); - MWWorld::InventoryStore &inv = getInventoryStore(ptr); + MWWorld::InventoryStore& inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); - bool hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name(); + bool hasArmor = !armor.isEmpty() && armor.getType() == ESM::Armor::sRecordId; // If there's no item in the carried left slot or if it is not a shield redistribute the hit. if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft) { - if (Misc::Rng::rollDice(2) == 0) + if (Misc::Rng::rollDice(2, prng) == 0) hitslot = MWWorld::InventoryStore::Slot_Cuirass; else hitslot = MWWorld::InventoryStore::Slot_LeftPauldron; @@ -922,12 +965,15 @@ namespace MWClass if (armorslot != inv.end()) { armor = *armorslot; - hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name(); + hasArmor = !armor.isEmpty() && armor.getType() == ESM::Armor::sRecordId; } } if (hasArmor) { - if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()) // Unarmed creature attacks don't affect armor condition + // Unarmed creature attacks don't affect armor condition unless it was + // explicitly requested. + if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc() + || Settings::game().mUnarmedCreatureAttacksDamageArmor) { int armorhealth = armor.getClass().getItemHealth(armor); armorhealth -= std::min(damageDiff, armorhealth); @@ -935,26 +981,21 @@ namespace MWClass // Armor broken? unequip it if (armorhealth == 0) - armor = *inv.unequipItem(armor, ptr); + armor = *inv.unequipItem(armor); } + ESM::RefId skill = armor.getClass().getEquipmentSkill(armor); if (ptr == MWMechanics::getPlayer()) - skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); + skillUsageSucceeded(ptr, skill, 0); - switch(armor.getClass().getEquipmentSkill(armor)) - { - case ESM::Skill::LightArmor: - sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); - break; - case ESM::Skill::MediumArmor: - sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); - break; - case ESM::Skill::HeavyArmor: - sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); - break; - } + if (skill == ESM::Skill::LightArmor) + sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); + else if (skill == ESM::Skill::MediumArmor) + sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f); + else if (skill == ESM::Skill::HeavyArmor) + sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); } - else if(ptr == MWMechanics::getPlayer()) + else if (ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); } } @@ -966,7 +1007,7 @@ namespace MWClass if (damage > 0.0f) { - sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); + sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f); if (ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(); if (!attacker.isEmpty()) @@ -986,7 +1027,8 @@ namespace MWClass if (!wasDead && getCreatureStats(ptr).isDead()) { // NPC was killed - if (!attacker.isEmpty() && attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()) + if (!attacker.isEmpty() && attacker.getClass().isNpc() + && attacker.getClass().getNpcStats(attacker).isWerewolf()) { attacker.getClass().getNpcStats(attacker).addWerewolfKill(); } @@ -1049,8 +1091,7 @@ namespace MWClass */ } - std::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Npc::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { /* Start of tes3mp addition @@ -1077,77 +1118,76 @@ namespace MWClass */ // player got activated by another NPC - if(ptr == MWMechanics::getPlayer()) - return std::shared_ptr(new MWWorld::ActionTalk(actor)); + if (ptr == MWMechanics::getPlayer()) + return std::make_unique(actor); // Werewolfs can't activate NPCs - if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfNPC"); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound* sound = store.get().searchRandom("WolfNPC", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); - if(sound) action->setSound(sound->mId); + std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); + if (sound) + action->setSound(sound->mId); return action; } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); - if(stats.isDead()) + if (stats.isDead()) { - bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game"); - // by default user can loot friendly actors during death animation - if (canLoot && !stats.getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) + return std::make_unique(ptr); // otherwise wait until death animation - if(stats.isDeathAnimationFinished()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + if (stats.isDeathAnimationFinished()) + return std::make_unique(ptr); } else if (!stats.getAiSequence().isInCombat()) { if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + return std::make_unique(ptr); // stealing // Can't talk to werewolves if (!getNpcStats(ptr).isWerewolf()) - return std::shared_ptr(new MWWorld::ActionTalk(ptr)); + return std::make_unique(ptr); } else // In combat { - const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); - if (stealingInCombat && stats.getKnockedDown()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && stats.getKnockedDown()) + return std::make_unique(ptr); // stealing } // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + return std::make_unique(ptr); - return std::shared_ptr (new MWWorld::FailedAction("")); + return std::make_unique(); } - MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) - const + MWWorld::ContainerStore& Npc::getContainerStore(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); - - return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; + ensureCustomData(ptr); + auto& store = ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; + store.setActor(ptr); + return store; } - MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr) - const + MWWorld::InventoryStore& Npc::getInventoryStore(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); - - return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; + ensureCustomData(ptr); + auto& store = ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; + store.setActor(ptr); + return store; } - std::string Npc::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Npc::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } @@ -1156,16 +1196,15 @@ namespace MWClass { // TODO: This function is called several times per frame for each NPC. // It would be better to calculate it only once per frame for each NPC and save the result in CreatureStats. - const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::NpcStats& stats = getNpcStats(ptr); bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead()) return 0.f; - const MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWBase::World* world = MWBase::Environment::get().getWorld(); const GMST& gmst = getGmst(); - const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); - const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); + const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); @@ -1176,14 +1215,15 @@ namespace MWClass running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); float moveSpeed; - if(getEncumbrance(ptr) > getCapacity(ptr)) + if (getEncumbrance(ptr) > getCapacity(ptr)) moveSpeed = 0.0f; - else if(mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && - world->isLevitationEnabled()) + else if (mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled()) { - float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + - mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); - flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); + float flySpeed = 0.01f + * (stats.getAttribute(ESM::Attribute::Speed).getModified() + + mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude()); + flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + + flySpeed * (gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; @@ -1195,72 +1235,64 @@ namespace MWClass else moveSpeed = getWalkSpeed(ptr); - if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) + if (stats.isWerewolf() && running && stats.getDrawState() == MWMechanics::DrawState::Nothing) moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); return moveSpeed; } - float Npc::getJump(const MWWorld::Ptr &ptr) const + float Npc::getJump(const MWWorld::Ptr& ptr) const { - if(getEncumbrance(ptr) > getCapacity(ptr)) + if (getEncumbrance(ptr) > getCapacity(ptr)) return 0.f; - const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::NpcStats& stats = getNpcStats(ptr); bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead()) return 0.f; - const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const GMST& gmst = getGmst(); - const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); - const float encumbranceTerm = gmst.fJumpEncumbranceBase->mValue.getFloat() + - gmst.fJumpEncumbranceMultiplier->mValue.getFloat() * - (1.0f - Npc::getNormalizedEncumbrance(ptr)); + const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); + const float encumbranceTerm = gmst.fJumpEncumbranceBase->mValue.getFloat() + + gmst.fJumpEncumbranceMultiplier->mValue.getFloat() * (1.0f - Npc::getNormalizedEncumbrance(ptr)); float a = getSkill(ptr, ESM::Skill::Acrobatics); float b = 0.0f; - if(a > 50.0f) + if (a > 50.0f) { b = a - 50.0f; a = 50.0f; } - float x = gmst.fJumpAcrobaticsBase->mValue.getFloat() + - std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->mValue.getFloat()); + float x = gmst.fJumpAcrobaticsBase->mValue.getFloat() + + std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->mValue.getFloat()); x += 3.0f * b * gmst.fJumpAcroMultiplier->mValue.getFloat(); - x += mageffects.get(ESM::MagicEffect::Jump).getMagnitude() * 64; + x += mageffects.getOrDefault(ESM::MagicEffect::Jump).getMagnitude() * 64; x *= encumbranceTerm; - if(stats.getStance(MWMechanics::CreatureStats::Stance_Run)) + if (stats.getStance(MWMechanics::CreatureStats::Stance_Run)) x *= gmst.fJumpRunMultiplier->mValue.getFloat(); - x *= npcdata->mNpcStats.getFatigueTerm(); + x *= stats.getFatigueTerm(); x -= -Constants::GravityConst * Constants::UnitsPerMeter; x /= 3.0f; return x; } - MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const + MWMechanics::Movement& Npc::getMovementSettings(const MWWorld::Ptr& ptr) const { - ensureCustomData (ptr); + ensureCustomData(ptr); return ptr.getRefData().getCustomData()->asNpcCustomData().mMovement; } - bool Npc::isEssential (const MWWorld::ConstPtr& ptr) const + bool Npc::isEssential(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return (ref->mBase->mFlags & ESM::NPC::Essential) != 0; } - void Npc::registerSelf() - { - std::shared_ptr instance (new Npc); - registerClass (typeid (ESM::NPC).name(), instance); - } - bool Npc::hasToolTip(const MWWorld::ConstPtr& ptr) const { if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode()) @@ -1274,89 +1306,94 @@ namespace MWClass if (!customData.mNpcStats.getAiSequence().isInCombat()) return true; - const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); - if (stealingInCombat && customData.mNpcStats.getKnockedDown()) + if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && customData.mNpcStats.getKnockedDown()) return true; return false; } - MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Npc::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); - if(fullHelp && !ref->mBase->mName.empty() && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) + std::string_view name = getName(ptr); + info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)); + if (fullHelp && !ref->mBase->mName.empty() && ptr.getRefData().getCustomData() + && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { info.caption += " ("; info.caption += MyGUI::TextIterator::toTagsString(ref->mBase->mName); info.caption += ")"; } - if(fullHelp) - info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + if (fullHelp) + info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); return info; } - float Npc::getCapacity (const MWWorld::Ptr& ptr) const + float Npc::getCapacity(const MWWorld::Ptr& ptr) const { - const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEncumbranceStrMult")->mValue.getFloat(); - return stats.getAttribute(ESM::Attribute::Strength).getModified()*fEncumbranceStrMult; + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + static const float fEncumbranceStrMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fEncumbranceStrMult") + ->mValue.getFloat(); + return stats.getAttribute(ESM::Attribute::Strength).getModified() * fEncumbranceStrMult; } - float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const + float Npc::getEncumbrance(const MWWorld::Ptr& ptr) const { // According to UESP, inventory weight is ignored in werewolf form. Does that include // feather and burden effects? return getNpcStats(ptr).isWerewolf() ? 0.0f : Actor::getEncumbrance(ptr); } - bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, - const MWWorld::Ptr& actor) const + bool Npc::consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const { - MWMechanics::CastSpell cast(ptr, ptr); - return cast.cast(id); + MWBase::Environment::get().getWorld()->breakInvisibility(actor); + MWMechanics::CastSpell cast(actor, actor); + const ESM::RefId& recordId = consumable.getCellRef().getRefId(); + MWBase::Environment::get().getLuaManager()->itemConsumed(consumable, actor); + actor.getClass().getContainerStore(actor).remove(consumable, 1); + return cast.cast(recordId); } - void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const + void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const { - MWMechanics::NpcStats& stats = getNpcStats (ptr); + MWMechanics::NpcStats& stats = getNpcStats(ptr); if (stats.isWerewolf()) return; - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); - const ESM::Class *class_ = - MWBase::Environment::get().getWorld()->getStore().get().find ( - ref->mBase->mClass - ); + const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mClass); - stats.useSkill (skill, *class_, usageType, extraFactor); + stats.useSkill(skill, *class_, usageType, extraFactor); } - float Npc::getArmorRating (const MWWorld::Ptr& ptr) const + float Npc::getArmorRating(const MWWorld::Ptr& ptr) const { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); + const MWWorld::Store& store + = MWBase::Environment::get().getESMStore()->get(); - MWMechanics::NpcStats &stats = getNpcStats(ptr); - const MWWorld::InventoryStore &invStore = getInventoryStore(ptr); + MWMechanics::NpcStats& stats = getNpcStats(ptr); + const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); float unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored); float ratings[MWWorld::InventoryStore::Slots]; - for(int i = 0;i < MWWorld::InventoryStore::Slots;i++) + for (int i = 0; i < MWWorld::InventoryStore::Slots; i++) { MWWorld::ConstContainerStoreIterator it = invStore.getSlot(i); - if (it == invStore.end() || it->getTypeName() != typeid(ESM::Armor).name()) + if (it == invStore.end() || it->getType() != ESM::Armor::sRecordId) { // unarmored ratings[i] = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); @@ -1374,30 +1411,32 @@ namespace MWClass } } - float shield = stats.getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); + float shield = stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Shield).getMagnitude(); return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3f - + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet] - + ratings[MWWorld::InventoryStore::Slot_Greaves] + ratings[MWWorld::InventoryStore::Slot_Boots] - + ratings[MWWorld::InventoryStore::Slot_LeftPauldron] + ratings[MWWorld::InventoryStore::Slot_RightPauldron] - ) * 0.1f - + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + ratings[MWWorld::InventoryStore::Slot_RightGauntlet]) - * 0.05f - + shield; + + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet] + + ratings[MWWorld::InventoryStore::Slot_Greaves] + ratings[MWWorld::InventoryStore::Slot_Boots] + + ratings[MWWorld::InventoryStore::Slot_LeftPauldron] + + ratings[MWWorld::InventoryStore::Slot_RightPauldron]) + * 0.1f + + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + + ratings[MWWorld::InventoryStore::Slot_RightGauntlet]) + * 0.05f + + shield; } - void Npc::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f&scale, bool rendering) const + void Npc::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const { if (!rendering) return; // collision meshes are not scaled based on race height // having the same collision extents for all races makes the environments easier to test - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); - const ESM::Race* race = - MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mRace); - // Race weight should not affect 1st-person meshes, otherwise it will change hand proportions and can break aiming. + // Race weight should not affect 1st-person meshes, otherwise it will change hand proportions and can break + // aiming. if (ptr == MWMechanics::getPlayer() && ptr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson()) { if (ref->mBase->isMale()) @@ -1422,120 +1461,140 @@ namespace MWClass } } - int Npc::getServices(const MWWorld::ConstPtr &actor) const + int Npc::getServices(const MWWorld::ConstPtr& actor) const { - return actor.get()->mBase->mAiData.mServices; + const ESM::NPC* npc = actor.get()->mBase; + if (npc->mFlags & ESM::NPC::Autocalc) + { + const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(npc->mClass); + return class_->mData.mServices; + } + return npc->mAiData.mServices; } - - std::string Npc::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const + ESM::RefId Npc::getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const { - if(name == "left" || name == "right") + static const ESM::RefId swimLeft = ESM::RefId::stringRefId("Swim Left"); + static const ESM::RefId swimRight = ESM::RefId::stringRefId("Swim Right"); + static const ESM::RefId footWaterLeft = ESM::RefId::stringRefId("FootWaterLeft"); + static const ESM::RefId footWaterRight = ESM::RefId::stringRefId("FootWaterRight"); + static const ESM::RefId footBareLeft = ESM::RefId::stringRefId("FootBareLeft"); + static const ESM::RefId footBareRight = ESM::RefId::stringRefId("FootBareRight"); + static const ESM::RefId footLightLeft = ESM::RefId::stringRefId("footLightLeft"); + static const ESM::RefId footLightRight = ESM::RefId::stringRefId("footLightRight"); + static const ESM::RefId footMediumRight = ESM::RefId::stringRefId("FootMedRight"); + static const ESM::RefId footMediumLeft = ESM::RefId::stringRefId("FootMedLeft"); + static const ESM::RefId footHeavyLeft = ESM::RefId::stringRefId("footHeavyLeft"); + static const ESM::RefId footHeavyRight = ESM::RefId::stringRefId("footHeavyRight"); + + if (name == "left" || name == "right") { - MWBase::World *world = MWBase::Environment::get().getWorld(); - if(world->isFlying(ptr)) - return std::string(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + if (world->isFlying(ptr)) + return ESM::RefId(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); - if(world->isSwimming(ptr)) - return (name == "left") ? "Swim Left" : "Swim Right"; - if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) - return (name == "left") ? "FootWaterLeft" : "FootWaterRight"; - if(world->isOnGround(ptr)) + if (world->isSwimming(ptr)) + return (name == "left") ? swimLeft : swimRight; + if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) + return (name == "left") ? footWaterLeft : footWaterRight; + if (world->isOnGround(ptr)) { if (getNpcStats(ptr).isWerewolf() - && getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) + && getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) { int weaponType = ESM::Weapon::None; MWMechanics::getActiveWeapon(ptr, &weaponType); if (weaponType == ESM::Weapon::None) - return std::string(); + return ESM::RefId(); } - const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr); + const MWWorld::InventoryStore& inv = Npc::getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); - if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name()) - return (name == "left") ? "FootBareLeft" : "FootBareRight"; + if (boots == inv.end() || boots->getType() != ESM::Armor::sRecordId) + return (name == "left") ? footBareLeft : footBareRight; - switch(boots->getClass().getEquipmentSkill(*boots)) - { - case ESM::Skill::LightArmor: - return (name == "left") ? "FootLightLeft" : "FootLightRight"; - case ESM::Skill::MediumArmor: - return (name == "left") ? "FootMedLeft" : "FootMedRight"; - case ESM::Skill::HeavyArmor: - return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight"; - } + ESM::RefId skill = boots->getClass().getEquipmentSkill(*boots); + if (skill == ESM::Skill::LightArmor) + return (name == "left") ? footLightLeft : footLightRight; + else if (skill == ESM::Skill::MediumArmor) + return (name == "left") ? footMediumLeft : footMediumRight; + else if (skill == ESM::Skill::HeavyArmor) + return (name == "left") ? footHeavyLeft : footHeavyRight; } - return std::string(); + return ESM::RefId(); } // Morrowind ignores land soundgen for NPCs - if(name == "land") - return std::string(); - if(name == "swimleft") - return "Swim Left"; - if(name == "swimright") - return "Swim Right"; + if (name == "land") + return ESM::RefId(); + if (name == "swimleft") + return swimLeft; + if (name == "swimright") + return swimRight; // TODO: I have no idea what these are supposed to do for NPCs since they use // voiced dialog for various conditions like health loss and combat taunts. Maybe // only for biped creatures? - if(name == "moan") - return std::string(); - if(name == "roar") - return std::string(); - if(name == "scream") - return std::string(); + if (name == "moan") + return ESM::RefId(); + if (name == "roar") + return ESM::RefId(); + if (name == "scream") + return ESM::RefId(); - throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); + throw std::runtime_error("Unexpected soundgen type: " + std::string(name)); } - MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - float Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const + float Npc::getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const { - return getNpcStats(ptr).getSkill(skill).getModified(); + return getNpcStats(ptr).getSkill(id).getModified(); } - int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const + int Npc::getBloodTexture(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mBloodType; } - void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const + void Npc::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; + const ESM::NpcState& npcState = state.asNpcState(); + if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) { - // Create a CustomData, but don't fill it from ESM records (not needed) - ptr.getRefData().setCustomData(std::make_unique()); + if (npcState.mCreatureStats.mMissingACDT) + ensureCustomData(ptr); + else + // Create a CustomData, but don't fill it from ESM records (not needed) + ptr.getRefData().setCustomData(std::make_unique()); } } else - ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. + ensureCustomData( + ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); - const ESM::NpcState& npcState = state.asNpcState(); - customData.mInventoryStore.readState (npcState.mInventory); - customData.mNpcStats.readState (npcState.mNpcStats); + + customData.mInventoryStore.readState(npcState.mInventory); + customData.mNpcStats.readState(npcState.mNpcStats); bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get()->mBase->mId); - if(spellsInitialised) + if (spellsInitialised) customData.mNpcStats.getSpells().clear(); - customData.mNpcStats.readState (npcState.mCreatureStats); + customData.mNpcStats.readState(npcState.mCreatureStats); } - void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) - const + void Npc::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { @@ -1543,41 +1602,42 @@ namespace MWClass return; } - if (ptr.getRefData().getCount() <= 0) + const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); + if (ptr.getRefData().getCount() <= 0 + && (!(ptr.get()->mBase->mFlags & ESM::NPC::Respawn) || !customData.mNpcStats.isDead())) { state.mHasCustomState = false; return; } - const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); ESM::NpcState& npcState = state.asNpcState(); - customData.mInventoryStore.writeState (npcState.mInventory); - customData.mNpcStats.writeState (npcState.mNpcStats); - customData.mNpcStats.writeState (npcState.mCreatureStats); + customData.mInventoryStore.writeState(npcState.mInventory); + customData.mNpcStats.writeState(npcState.mNpcStats); + customData.mNpcStats.writeState(npcState.mCreatureStats); } int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mNpdt.mGold; } - bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const + bool Npc::isClass(const MWWorld::ConstPtr& ptr, std::string_view className) const { - return Misc::StringUtils::ciEqual(ptr.get()->mBase->mClass, className); + return ptr.get()->mBase->mClass == className; } - bool Npc::canSwim(const MWWorld::ConstPtr &ptr) const + bool Npc::canSwim(const MWWorld::ConstPtr& ptr) const { return true; } - bool Npc::canWalk(const MWWorld::ConstPtr &ptr) const + bool Npc::canWalk(const MWWorld::ConstPtr& ptr) const { return true; } - void Npc::respawn(const MWWorld::Ptr &ptr) const + void Npc::respawn(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) @@ -1586,57 +1646,60 @@ namespace MWClass if (!creatureStats.isDeathAnimationFinished()) return; - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); - float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + float delay + = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn - && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) { ptr.getRefData().setCount(1); - const std::string& script = getScript(ptr); + const ESM::RefId& script = getScript(ptr); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); } MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); + MWBase::Environment::get().getWindowManager()->onDeleteCustomData(ptr); ptr.getRefData().setCustomData(nullptr); // Reset to original position - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], - ptr.getCellRef().getPosition().pos[1], - ptr.getCellRef().getPosition().pos[2]); + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->rotateObject( + ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } - int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const + int Npc::getBaseFightRating(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mAiData.mFight; } - bool Npc::isBipedal(const MWWorld::ConstPtr &ptr) const + bool Npc::isBipedal(const MWWorld::ConstPtr& ptr) const { return true; } - std::string Npc::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const + ESM::RefId Npc::getPrimaryFaction(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mFaction; } - int Npc::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const + int Npc::getPrimaryFactionRank(const MWWorld::ConstPtr& ptr) const { - std::string factionID = ptr.getClass().getPrimaryFaction(ptr); - if(factionID.empty()) + const ESM::RefId& factionID = ptr.getClass().getPrimaryFaction(ptr); + if (factionID.empty()) return -1; // Search in the NPC data first @@ -1648,16 +1711,16 @@ namespace MWClass } // Use base NPC record as a fallback - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->getFactionRank(); } - void Npc::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const + void Npc::setBaseAISetting(const ESM::RefId& id, MWMechanics::AiSetting setting, int value) const { MWMechanics::setBaseAISetting(id, setting, value); } - void Npc::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + void Npc::modifyBaseInventory(const ESM::RefId& actorId, const ESM::RefId& itemId, int amount) const { MWMechanics::modifyBaseInventory(actorId, itemId, amount); } @@ -1665,16 +1728,16 @@ namespace MWClass float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const { const GMST& gmst = getGmst(); - const NpcCustomData* npcdata = static_cast(ptr.getRefData().getCustomData()); + const MWMechanics::NpcStats& stats = getNpcStats(ptr); const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); const bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() - + 0.01f * npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() * (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); - walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat()*normalizedEncumbrance; + walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; walkSpeed = std::max(0.0f, walkSpeed); - if(sneaking) + if (sneaking) walkSpeed *= gmst.fSneakSpeedMultiplier->mValue.getFloat(); return walkSpeed; @@ -1684,33 +1747,20 @@ namespace MWClass { const GMST& gmst = getGmst(); return getWalkSpeed(ptr) - * (0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fAthleticsRunBonus->mValue.getFloat() - + gmst.fBaseRunMultiplier->mValue.getFloat()); + * (0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fAthleticsRunBonus->mValue.getFloat() + + gmst.fBaseRunMultiplier->mValue.getFloat()); } float Npc::getSwimSpeed(const MWWorld::Ptr& ptr) const { - const GMST& gmst = getGmst(); const MWBase::World* world = MWBase::Environment::get().getWorld(); - const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); - const NpcCustomData* npcdata = static_cast(ptr.getRefData().getCustomData()); - const MWMechanics::MagicEffects& mageffects = npcdata->mNpcStats.getMagicEffects(); + const MWMechanics::NpcStats& stats = getNpcStats(ptr); + const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); const bool swimming = world->isSwimming(ptr); const bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr); const bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run) - && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); + && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); - float swimSpeed; - - if (running) - swimSpeed = getRunSpeed(ptr); - else - swimSpeed = getWalkSpeed(ptr); - - swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); - swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() - + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat(); - - return swimSpeed; + return getSwimSpeedImpl(ptr, getGmst(), mageffects, running ? getRunSpeed(ptr) : getWalkSpeed(ptr)); } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 08aa6c058..7c0f10c6f 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWCLASS_NPC_H #define GAME_MWCLASS_NPC_H +#include "../mwworld/registeredclass.hpp" + #include "actor.hpp" namespace ESM @@ -10,177 +12,178 @@ namespace ESM namespace MWClass { - class Npc : public Actor + class Npc : public MWWorld::RegisteredClass { - void ensureCustomData (const MWWorld::Ptr& ptr) const; + friend MWWorld::RegisteredClass; - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + Npc(); - struct GMST - { - const ESM::GameSetting *fMinWalkSpeed; - const ESM::GameSetting *fMaxWalkSpeed; - const ESM::GameSetting *fEncumberedMoveEffect; - const ESM::GameSetting *fSneakSpeedMultiplier; - const ESM::GameSetting *fAthleticsRunBonus; - const ESM::GameSetting *fBaseRunMultiplier; - const ESM::GameSetting *fMinFlySpeed; - const ESM::GameSetting *fMaxFlySpeed; - const ESM::GameSetting *fSwimRunBase; - const ESM::GameSetting *fSwimRunAthleticsMult; - const ESM::GameSetting *fJumpEncumbranceBase; - const ESM::GameSetting *fJumpEncumbranceMultiplier; - const ESM::GameSetting *fJumpAcrobaticsBase; - const ESM::GameSetting *fJumpAcroMultiplier; - const ESM::GameSetting *fJumpRunMultiplier; - const ESM::GameSetting *fWereWolfRunMult; - const ESM::GameSetting *fKnockDownMult; - const ESM::GameSetting *iKnockDownOddsMult; - const ESM::GameSetting *iKnockDownOddsBase; - const ESM::GameSetting *fCombatArmorMinMult; - }; + void ensureCustomData(const MWWorld::Ptr& ptr) const; - static const GMST& getGmst(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - public: + struct GMST + { + const ESM::GameSetting* fMinWalkSpeed; + const ESM::GameSetting* fMaxWalkSpeed; + const ESM::GameSetting* fEncumberedMoveEffect; + const ESM::GameSetting* fSneakSpeedMultiplier; + const ESM::GameSetting* fAthleticsRunBonus; + const ESM::GameSetting* fBaseRunMultiplier; + const ESM::GameSetting* fMinFlySpeed; + const ESM::GameSetting* fMaxFlySpeed; + const ESM::GameSetting* fSwimRunBase; + const ESM::GameSetting* fSwimRunAthleticsMult; + const ESM::GameSetting* fJumpEncumbranceBase; + const ESM::GameSetting* fJumpEncumbranceMultiplier; + const ESM::GameSetting* fJumpAcrobaticsBase; + const ESM::GameSetting* fJumpAcroMultiplier; + const ESM::GameSetting* fJumpRunMultiplier; + const ESM::GameSetting* fWereWolfRunMult; + const ESM::GameSetting* fKnockDownMult; + const ESM::GameSetting* iKnockDownOddsMult; + const ESM::GameSetting* iKnockDownOddsBase; + const ESM::GameSetting* fCombatArmorMinMult; + }; - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + static const GMST& getGmst(); - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const override; - ///< Return creature stats + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - MWMechanics::NpcStats& getNpcStats (const MWWorld::Ptr& ptr) const override; - ///< Return NPC stats + MWMechanics::CreatureStats& getCreatureStats(const MWWorld::Ptr& ptr) const override; + ///< Return creature stats - MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const override; - ///< Return container store + MWMechanics::NpcStats& getNpcStats(const MWWorld::Ptr& ptr) const override; + ///< Return NPC stats - bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; - ///< @return true if this object has a tooltip when focused (default implementation: true) + MWWorld::ContainerStore& getContainerStore(const MWWorld::Ptr& ptr) const override; + ///< Return container store - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; + ///< @return true if this object has a tooltip when focused (default implementation: true) - MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const override; - ///< Return inventory store + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - bool hasInventoryStore(const MWWorld::Ptr &ptr) const override { return true; } + MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override; + ///< Return inventory store - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to check whether a class has a container store - */ - virtual bool hasContainerStore(const MWWorld::Ptr &ptr) const { return true; } - /* - End of tes3mp addition - */ + Make it possible to check whether a class has a container store + */ + virtual bool hasContainerStore(const MWWorld::Ptr &ptr) const { return true; } + /* + End of tes3mp addition + */ - void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const override; + bool hasInventoryStore(const MWWorld::Ptr& ptr) const override { return true; } - void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const override; + bool evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const override; - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; - ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim, + const osg::Vec3f& hitPosition, bool success) const override; - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const override; - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: + ///< list getModel(). - float getMaxSpeed (const MWWorld::Ptr& ptr) const override; - ///< Return maximal movement speed. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - float getJump(const MWWorld::Ptr &ptr) const override; - ///< Return jump velocity (not accounting for movement) + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const override; - ///< Return desired movement. + float getMaxSpeed(const MWWorld::Ptr& ptr) const override; + ///< Return maximal movement speed. - float getCapacity (const MWWorld::Ptr& ptr) const override; - ///< Return total weight that fits into the object. Throws an exception, if the object can't - /// hold other objects. + float getJump(const MWWorld::Ptr& ptr) const override; + ///< Return jump velocity (not accounting for movement) - float getEncumbrance (const MWWorld::Ptr& ptr) const override; - ///< Returns total weight of objects inside this object (including modifications from magic - /// effects). Throws an exception, if the object can't hold other objects. + MWMechanics::Movement& getMovementSettings(const MWWorld::Ptr& ptr) const override; + ///< Return desired movement. - float getArmorRating (const MWWorld::Ptr& ptr) const override; - ///< @return combined armor rating of this actor + float getCapacity(const MWWorld::Ptr& ptr) const override; + ///< Return total weight that fits into the object. Throws an exception, if the object can't + /// hold other objects. - bool apply (const MWWorld::Ptr& ptr, const std::string& id, - const MWWorld::Ptr& actor) const override; - ///< Apply \a id on \a ptr. - /// \param actor Actor that is resposible for the ID being applied to \a ptr. - /// \return Any effect? + float getEncumbrance(const MWWorld::Ptr& ptr) const override; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. - void adjustScale (const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool rendering) const override; - /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh + float getArmorRating(const MWWorld::Ptr& ptr) const override; + ///< @return combined armor rating of this actor - void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor=1.f) const override; - ///< Inform actor \a ptr that a skill use has succeeded. + bool consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const override; - bool isEssential (const MWWorld::ConstPtr& ptr) const override; - ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) + void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const override; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh - int getServices (const MWWorld::ConstPtr& actor) const override; + void skillUsageSucceeded( + const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor = 1.f) const override; + ///< Inform actor \a ptr that a skill use has succeeded. - bool isPersistent (const MWWorld::ConstPtr& ptr) const override; + bool isEssential(const MWWorld::ConstPtr& ptr) const override; + ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const override; + int getServices(const MWWorld::ConstPtr& actor) const override; - static void registerSelf(); + bool isPersistent(const MWWorld::ConstPtr& ptr) const override; - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + ESM::RefId getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const override; - float getSkill(const MWWorld::Ptr& ptr, int skill) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - int getBloodTexture (const MWWorld::ConstPtr& ptr) const override; + float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override; - bool isNpc() const override - { - return true; - } + /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) + int getBloodTexture(const MWWorld::ConstPtr& ptr) const override; - void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; - ///< Read additional state from \a state into \a ptr. + bool isNpc() const override { return true; } - void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; - ///< Write additional state from \a ptr into \a state. + void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const override; + ///< Read additional state from \a state into \a ptr. - int getBaseGold(const MWWorld::ConstPtr& ptr) const override; + void writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; + ///< Write additional state from \a ptr into \a state. - bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const override; + int getBaseGold(const MWWorld::ConstPtr& ptr) const override; - bool canSwim (const MWWorld::ConstPtr &ptr) const override; + bool isClass(const MWWorld::ConstPtr& ptr, std::string_view className) const override; - bool canWalk (const MWWorld::ConstPtr &ptr) const override; + bool canSwim(const MWWorld::ConstPtr& ptr) const override; - bool isBipedal (const MWWorld::ConstPtr &ptr) const override; + bool canWalk(const MWWorld::ConstPtr& ptr) const override; - void respawn (const MWWorld::Ptr& ptr) const override; + bool isBipedal(const MWWorld::ConstPtr& ptr) const override; - int getBaseFightRating (const MWWorld::ConstPtr& ptr) const override; + void respawn(const MWWorld::Ptr& ptr) const override; - std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const override; - int getPrimaryFactionRank(const MWWorld::ConstPtr &ptr) const override; + int getBaseFightRating(const MWWorld::ConstPtr& ptr) const override; - void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const override; + ESM::RefId getPrimaryFaction(const MWWorld::ConstPtr& ptr) const override; - void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const override; + int getPrimaryFactionRank(const MWWorld::ConstPtr& ptr) const override; - float getWalkSpeed(const MWWorld::Ptr& ptr) const override; + void setBaseAISetting(const ESM::RefId& id, MWMechanics::AiSetting setting, int value) const override; - float getRunSpeed(const MWWorld::Ptr& ptr) const override; + void modifyBaseInventory(const ESM::RefId& actorId, const ESM::RefId& itemId, int amount) const override; - float getSwimSpeed(const MWWorld::Ptr& ptr) const override; + float getWalkSpeed(const MWWorld::Ptr& ptr) const override; + + float getRunSpeed(const MWWorld::Ptr& ptr) const override; + + float getSwimSpeed(const MWWorld::Ptr& ptr) const override; }; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index b318df913..0ad28141f 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -12,37 +12,45 @@ End of tes3mp addition */ -#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" +#include +#include -#include "../mwworld/ptr.hpp" #include "../mwworld/actionapply.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwphysics/physicssystem.hpp" -#include "../mwworld/nullaction.hpp" +#include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/alchemy.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Potion::Potion() + : MWWorld::RegisteredClass(ESM::Potion::sRecordId) { - if (!model.empty()) { + } + + void Potion::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Potion::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -66,78 +74,64 @@ namespace MWClass /* End of tes3mp addition */ + + return getClassModel(ptr); } - std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Potion::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Potion::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Potion::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Potion::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - std::string Potion::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Potion::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - int Potion::getValue (const MWWorld::ConstPtr& ptr) const + int Potion::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Potion::registerSelf() + const ESM::RefId& Potion::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Potion); - - registerClass (typeid (ESM::Potion).name(), instance); + static const auto sound = ESM::RefId::stringRefId("Item Potion Up"); + return sound; } - std::string Potion::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Potion::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Potion Up"); + static const auto sound = ESM::RefId::stringRefId("Item Potion Down"); + return sound; } - std::string Potion::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const std::string& Potion::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Potion Down"); - } - - std::string Potion::getInventoryIcon (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Potion::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Potion::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -148,15 +142,16 @@ namespace MWClass info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); // hide effects the player doesn't know about - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - for (unsigned int i=0; igetPlayerPtr(); + for (size_t i = 0; i < info.effects.size(); ++i) info.effects[i].mKnown = MWMechanics::Alchemy::knownEffect(i, player); info.isPotion = true; - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; @@ -164,34 +159,32 @@ namespace MWClass return info; } - std::shared_ptr Potion::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Potion::use(const MWWorld::Ptr& ptr, bool force) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef* ref = ptr.get(); - std::shared_ptr action ( - new MWWorld::ActionApply (ptr, ref->mBase->mId)); + auto action = std::make_unique(ptr, ref->mBase->mId); - action->setSound ("Drink"); + action->setSound(ESM::RefId::stringRefId("Drink")); return action; } - MWWorld::Ptr Potion::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Potion::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Potion::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Potion::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Potions) != 0; } - float Potion::getWeight(const MWWorld::ConstPtr &ptr) const + float Potion::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75d923f0b..66b11b8ef 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -1,56 +1,57 @@ #ifndef GAME_MWCLASS_POTION_H #define GAME_MWCLASS_POTION_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Potion : public MWWorld::Class + class Potion : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Potion(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - float getWeight (const MWWorld::ConstPtr& ptr) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; }; } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index dba4e8c06..e020c8944 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -1,116 +1,110 @@ #include "probe.hpp" -#include +#include + +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwphysics/physicssystem.hpp" -#include "../mwworld/nullaction.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Probe::Probe() + : MWWorld::RegisteredClass(ESM::Probe::sRecordId) { - if (!model.empty()) { + } + + void Probe::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Probe::getModel(const MWWorld::ConstPtr& ptr) const { - // TODO: add option somewhere to enable collision for placeable objects + return getClassModel(ptr); } - std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Probe::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Probe::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Probe::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Probe::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - std::string Probe::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Probe::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Probe::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const + std::pair, bool> Probe::getEquipmentSlots(const MWWorld::ConstPtr& ptr) const { std::vector slots_; - slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back(int(MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots_, false); + return std::make_pair(slots_, false); } - int Probe::getValue (const MWWorld::ConstPtr& ptr) const + int Probe::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Probe::registerSelf() + const ESM::RefId& Probe::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Probe); - - registerClass (typeid (ESM::Probe).name(), instance); + static const ESM::RefId sound = ESM::RefId::stringRefId("Item Probe Up"); + return sound; } - std::string Probe::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Probe::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Probe Up"); + static const ESM::RefId sound = ESM::RefId::stringRefId("Item Probe Down"); + return sound; } - std::string Probe::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const std::string& Probe::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Probe Down"); - } - - std::string Probe::getInventoryIcon (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Probe::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Probe::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -122,9 +116,10 @@ namespace MWClass text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; @@ -132,47 +127,47 @@ namespace MWClass return info; } - std::shared_ptr Probe::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Probe::use(const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } - MWWorld::Ptr Probe::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Probe::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - std::pair Probe::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + std::pair Probe::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const { // Do not allow equip tools from inventory during attack if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) && MWBase::Environment::get().getWindowManager()->isGuiMode()) - return std::make_pair(0, "#{sCantEquipWeapWarning}"); + return { 0, "#{sCantEquipWeapWarning}" }; - return std::make_pair(1, ""); + return { 1, {} }; } - bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Probe::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Probes) != 0; } - int Probe::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const + int Probe::getItemMaxHealth(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mUses; } - float Probe::getWeight(const MWWorld::ConstPtr &ptr) const + float Probe::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index a0a41dcfb..9a66d7a4b 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -1,68 +1,70 @@ #ifndef GAME_MWCLASS_PROBE_H #define GAME_MWCLASS_PROBE_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Probe : public MWWorld::Class + class Probe : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Probe(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; - ///< \return first: Return IDs of the slot this object can be equipped in; second: can object - /// stay stacked when equipped? + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + std::pair, bool> getEquipmentSlots(const MWWorld::ConstPtr& ptr) const override; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; + std::pair canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const override; - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; - float getWeight (const MWWorld::ConstPtr& ptr) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; - ///< Return item max health or throw an exception, if class does not have item health + int getItemMaxHealth(const MWWorld::ConstPtr& ptr) const override; + ///< Return item max health or throw an exception, if class does not have item health - bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override { return true; } - ///< \return Item health data available? (default implementation: false) + bool hasItemHealth(const MWWorld::ConstPtr& ptr) const override { return true; } + ///< \return Item health data available? (default implementation: false) }; } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index c1e43fa33..c46d2561d 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -12,32 +12,43 @@ End of tes3mp addition */ -#include +#include + +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/cellstore.hpp" -#include "../mwphysics/physicssystem.hpp" #include "../mwworld/actionrepair.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Repair::Repair() + : MWWorld::RegisteredClass(ESM::Repair::sRecordId) { - if (!model.empty()) { + } + + void Repair::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Repair::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -61,90 +72,76 @@ namespace MWClass /* End of tes3mp addition */ + + return getClassModel(ptr); } - std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Repair::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Repair::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Repair::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Repair::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - std::string Repair::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Repair::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - int Repair::getValue (const MWWorld::ConstPtr& ptr) const + int Repair::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Repair::registerSelf() + const ESM::RefId& Repair::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Repair); - - registerClass (typeid (ESM::Repair).name(), instance); + static auto val = ESM::RefId::stringRefId("Item Repair Up"); + return val; } - std::string Repair::getUpSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Repair::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Repair Up"); + static auto val = ESM::RefId::stringRefId("Item Repair Down"); + return val; } - std::string Repair::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const std::string& Repair::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - return std::string("Item Repair Down"); - } - - std::string Repair::getInventoryIcon (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - bool Repair::hasItemHealth (const MWWorld::ConstPtr& ptr) const + bool Repair::hasItemHealth(const MWWorld::ConstPtr& ptr) const { return true; } - int Repair::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const + int Repair::getItemMaxHealth(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mUses; } - MWGui::ToolTipInfo Repair::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Repair::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -156,9 +153,10 @@ namespace MWClass text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; @@ -166,26 +164,26 @@ namespace MWClass return info; } - MWWorld::Ptr Repair::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Repair::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - std::shared_ptr Repair::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Repair::use(const MWWorld::Ptr& ptr, bool force) const { - return std::shared_ptr(new MWWorld::ActionRepair(ptr, force)); + return std::make_unique(ptr, force); } - bool Repair::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Repair::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::RepairItem) != 0; } - float Repair::getWeight(const MWWorld::ConstPtr &ptr) const + float Repair::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index b9791e9cf..ee96c83ee 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -1,64 +1,65 @@ #ifndef GAME_MWCLASS_REPAIR_H #define GAME_MWCLASS_REPAIR_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Repair : public MWWorld::Class + class Repair : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Repair(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu (default implementation: return a - /// null action). + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu (default implementation: return a + /// null action). - bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override; - ///< \return Item health data available? (default implementation: false) + bool hasItemHealth(const MWWorld::ConstPtr& ptr) const override; + ///< \return Item health data available? (default implementation: false) - int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; - ///< Return item max health or throw an exception, if class does not have item health - /// (default implementation: throw an exception) + int getItemMaxHealth(const MWWorld::ConstPtr& ptr) const override; + ///< Return item max health or throw an exception, if class does not have item health + /// (default implementation: throw an exception) - float getWeight (const MWWorld::ConstPtr& ptr) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; }; } diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 5551b3d73..fe1226f19 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -1,20 +1,28 @@ #include "static.hpp" -#include +#include +#include #include -#include "../mwworld/ptr.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/ptr.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/vismask.hpp" +#include "classmodel.hpp" + namespace MWClass { + Static::Static() + : MWWorld::RegisteredClass(ESM::Static::sRecordId) + { + } - void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + void Static::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { @@ -23,26 +31,26 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model); + insertObjectPhysics(ptr, model, rotation, physics); } - std::string Static::getModel(const MWWorld::ConstPtr &ptr) const + void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } - std::string Static::getName (const MWWorld::ConstPtr& ptr) const + std::string Static::getModel(const MWWorld::ConstPtr& ptr) const { - return ""; + return getClassModel(ptr); + } + + std::string_view Static::getName(const MWWorld::ConstPtr& ptr) const + { + return {}; } bool Static::hasToolTip(const MWWorld::ConstPtr& ptr) const @@ -50,16 +58,9 @@ namespace MWClass return false; } - void Static::registerSelf() + MWWorld::Ptr Static::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - std::shared_ptr instance (new Static); - - registerClass (typeid (ESM::Static).name(), instance); - } - - MWWorld::Ptr Static::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 6bc783dad..0d853211b 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -1,30 +1,42 @@ #ifndef GAME_MWCLASS_STATIC_H #define GAME_MWCLASS_STATIC_H -#include "../mwworld/class.hpp" +#include "../mwgui/tooltips.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/registeredclass.hpp" + +#include "classmodel.hpp" + +#include "../mwgui/ustring.hpp" +#include namespace MWClass { - class Static : public MWWorld::Class + class Static : public MWWorld::RegisteredClass { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + Static(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + public: + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override; - bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; - ///< @return true if this object has a tooltip when focused (default implementation: true) + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - static void registerSelf(); + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; + ///< @return true if this object has a tooltip when focused (default implementation: true) - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index ee59950b6..f9243cc24 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -14,40 +14,49 @@ */ #include +#include + +#include #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwphysics/physicssystem.hpp" -#include "../mwworld/nullaction.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/ptr.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "classmodel.hpp" + namespace MWClass { - - void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + Weapon::Weapon() + : MWWorld::RegisteredClass(ESM::Weapon::sRecordId) { - if (!model.empty()) { + } + + void Weapon::insertObjectRendering( + const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const + { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); } } - void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + std::string Weapon::getModel(const MWWorld::ConstPtr& ptr) const { // TODO: add option somewhere to enable collision for placeable objects @@ -72,59 +81,48 @@ namespace MWClass /* End of tes3mp addition */ + + return getClassModel(ptr); } - std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const + std::string_view Weapon::getName(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); - - const std::string &model = ref->mBase->mModel; - if (!model.empty()) { - return "meshes\\" + model; - } - return ""; - } - - std::string Weapon::getName (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const std::string& name = ref->mBase->mName; - return !name.empty() ? name : ref->mBase->mId; + return !name.empty() ? name : ref->mBase->mId.getRefIdString(); } - std::shared_ptr Weapon::activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const + std::unique_ptr Weapon::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); } - bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const + bool Weapon::hasItemHealth(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); int type = ref->mBase->mData.mType; return MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::HasHealth; } - int Weapon::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const + int Weapon::getItemMaxHealth(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mHealth; } - std::string Weapon::getScript (const MWWorld::ConstPtr& ptr) const + ESM::RefId Weapon::getScript(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const + std::pair, bool> Weapon::getEquipmentSlots(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); ESM::WeaponType::Class weapClass = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mWeaponClass; std::vector slots_; @@ -132,86 +130,79 @@ namespace MWClass if (weapClass == ESM::WeaponType::Ammo) { - slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); + slots_.push_back(int(MWWorld::InventoryStore::Slot_Ammunition)); stack = true; } else if (weapClass == ESM::WeaponType::Thrown) { - slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back(int(MWWorld::InventoryStore::Slot_CarriedRight)); stack = true; } else - slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back(int(MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots_, stack); + return std::make_pair(slots_, stack); } - int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const + ESM::RefId Weapon::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); int type = ref->mBase->mData.mType; return MWMechanics::getWeaponType(type)->mSkill; } - int Weapon::getValue (const MWWorld::ConstPtr& ptr) const + int Weapon::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mValue; } - void Weapon::registerSelf() + const ESM::RefId& Weapon::getUpSoundId(const MWWorld::ConstPtr& ptr) const { - std::shared_ptr instance (new Weapon); - - registerClass (typeid (ESM::Weapon).name(), instance); - } - - std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const - { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); int type = ref->mBase->mData.mType; - std::string soundId = MWMechanics::getWeaponType(type)->mSoundId; - return soundId + " Up"; + return MWMechanics::getWeaponType(type)->mSoundIdUp; } - std::string Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const + const ESM::RefId& Weapon::getDownSoundId(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); int type = ref->mBase->mData.mType; - std::string soundId = MWMechanics::getWeaponType(type)->mSoundId; - return soundId + " Down"; + return MWMechanics::getWeaponType(type)->mSoundIdDown; } - std::string Weapon::getInventoryIcon (const MWWorld::ConstPtr& ptr) const + const std::string& Weapon::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mIcon; } - MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Weapon::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); const ESM::WeaponType* weaponType = MWMechanics::getWeaponType(ref->mBase->mData.mType); MWGui::ToolTipInfo info; - info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)) + MWGui::ToolTips::getCountString(count); + std::string_view name = getName(ptr); + info.caption + = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); std::string text; // weapon type & damage - if (weaponType->mWeaponClass != ESM::WeaponType::Ammo || Settings::Manager::getBool("show projectile damage", "Game")) + if (weaponType->mWeaponClass != ESM::WeaponType::Ammo || Settings::game().mShowProjectileDamage) { text += "\n#{sType} "; - int skill = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mSkill; - const std::string type = ESM::Skill::sSkillNameIds[skill]; - std::string oneOrTwoHanded; + const ESM::Skill* skill + = store.get().find(MWMechanics::getWeaponType(ref->mBase->mData.mType)->mSkill); + std::string_view oneOrTwoHanded; if (weaponType->mWeaponClass == ESM::WeaponType::Melee) { if (weaponType->mFlags & ESM::WeaponType::TwoHanded) @@ -220,38 +211,34 @@ namespace MWClass oneOrTwoHanded = "sOneHanded"; } - text += store.get().find(type)->mValue.getString() + - ((oneOrTwoHanded != "") ? ", " + store.get().find(oneOrTwoHanded)->mValue.getString() : ""); + text += skill->mName; + if (!oneOrTwoHanded.empty()) + text += ", " + store.get().find(oneOrTwoHanded)->mValue.getString(); // weapon damage if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { // Thrown weapons have 2x real damage applied // as they're both the weapon and the ammo - text += "\n#{sAttack}: " - + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0] * 2)) + text += "\n#{sAttack}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0] * 2)) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1] * 2)); } else if (weaponType->mWeaponClass == ESM::WeaponType::Melee) { // Chop - text += "\n#{sChop}: " - + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) - + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); + text += "\n#{sChop}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) + " - " + + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); // Slash - text += "\n#{sSlash}: " - + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mSlash[0])) + text += "\n#{sSlash}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mSlash[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mSlash[1])); // Thrust - text += "\n#{sThrust}: " - + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[0])) + text += "\n#{sThrust}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[1])); } else { // marksman - text += "\n#{sAttack}: " - + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) + text += "\n#{sAttack}: " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); } } @@ -260,15 +247,16 @@ namespace MWClass { int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" - + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); + + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); } - const bool verbose = Settings::Manager::getBool("show melee info", "Game"); + const bool verbose = Settings::game().mShowMeleeInfo; // add reach for melee weapon if (weaponType->mWeaponClass == ESM::WeaponType::Melee && verbose) { // display value in feet - const float combatDistance = store.get().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach; + const float combatDistance + = store.get().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach; text += MWGui::ToolTips::getWeightString(combatDistance / Constants::UnitsPerFoot, "#{sRange}"); text += " #{sFeet}"; } @@ -287,9 +275,10 @@ namespace MWClass if (!info.enchant.empty()) info.remainingEnchantCharge = static_cast(ptr.getCellRef().getEnchantmentCharge()); - if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); + text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = text; @@ -297,24 +286,24 @@ namespace MWClass return info; } - std::string Weapon::getEnchantment (const MWWorld::ConstPtr& ptr) const + ESM::RefId Weapon::getEnchantment(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Weapon::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + const ESM::RefId& Weapon::applyEnchantment( + const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, const std::string& newName) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); ESM::Weapon newItem = *ref->mBase; - newItem.mId=""; - newItem.mName=newName; - newItem.mData.mEnchant=enchCharge; - newItem.mEnchant=enchId; + newItem.mId = ESM::RefId(); + newItem.mName = newName; + newItem.mData.mEnchant = enchCharge; + newItem.mEnchant = enchId; newItem.mData.mFlags |= ESM::Weapon::Magical; - /* Start of tes3mp addition @@ -327,67 +316,66 @@ namespace MWClass /* End of tes3mp addition */ - - const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); + const ESM::Weapon* record = MWBase::Environment::get().getESMStore()->insert(newItem); return record->mId; } - std::pair Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + std::pair Weapon::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const { if (hasItemHealth(ptr) && getItemHealth(ptr) == 0) - return std::make_pair(0, "#{sInventoryMessage1}"); + return { 0, "#{sInventoryMessage1}" }; // Do not allow equip weapons from inventory during attack if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) && MWBase::Environment::get().getWindowManager()->isGuiMode()) - return std::make_pair(0, "#{sCantEquipWeapWarning}"); + return { 0, "#{sCantEquipWeapWarning}" }; std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) - return std::make_pair (0, ""); + return { 0, {} }; int type = ptr.get()->mBase->mData.mType; - if(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded) + if (MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded) { - return std::make_pair (2, ""); + return { 2, {} }; } - return std::make_pair(1, ""); + return { 1, {} }; } - std::shared_ptr Weapon::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Weapon::use(const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action = std::make_unique(ptr, force); action->setSound(getUpSoundId(ptr)); return action; } - MWWorld::Ptr Weapon::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Weapon::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return MWWorld::Ptr(cell.insert(ref), &cell); } - int Weapon::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const + int Weapon::getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Weapon::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Weapon::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Weapon) - || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); + || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Weapon::getWeight(const MWWorld::ConstPtr &ptr) const + float Weapon::getWeight(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index f1824b7d1..110069341 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -1,83 +1,84 @@ #ifndef GAME_MWCLASS_WEAPON_H #define GAME_MWCLASS_WEAPON_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Weapon : public MWWorld::Class + class Weapon : public MWWorld::RegisteredClass { - MWWorld::Ptr - copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + friend MWWorld::RegisteredClass; - public: + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override; - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; - ///< Add reference into a cell for rendering + public: + Weapon(); - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override; + ///< Add reference into a cell for rendering - std::string getName (const MWWorld::ConstPtr& ptr) const override; - ///< \return name or ID; can return an empty string. + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor) const override; - ///< Generate action for activation + bool isItem(const MWWorld::ConstPtr&) const override { return true; } - MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + ///< Generate action for activation - bool hasItemHealth (const MWWorld::ConstPtr& ptr) const override; - ///< \return Item health data available? + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const override; - ///< Return item max health or throw an exception, if class does not have item health + bool hasItemHealth(const MWWorld::ConstPtr& ptr) const override; + ///< \return Item health data available? - std::string getScript (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of the script attached to ptr + int getItemMaxHealth(const MWWorld::ConstPtr& ptr) const override; + ///< Return item max health or throw an exception, if class does not have item health - std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const override; - ///< \return first: Return IDs of the slot this object can be equipped in; second: can object - /// stay stacked when equipped? + ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of the script attached to ptr - int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const override; - /// Return the index of the skill this item corresponds to when equipped or -1, if there is - /// no such skill. + std::pair, bool> getEquipmentSlots(const MWWorld::ConstPtr& ptr) const override; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? - int getValue (const MWWorld::ConstPtr& ptr) const override; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. + ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override; - static void registerSelf(); + int getValue(const MWWorld::ConstPtr& ptr) const override; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the pick up sound Id + const ESM::RefId& getUpSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the pick up sound Id - std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const override; - ///< Return the put down sound Id + const ESM::RefId& getDownSoundId(const MWWorld::ConstPtr& ptr) const override; + ///< Return the put down sound Id - std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; - ///< Return name of inventory icon. + const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; + ///< Return name of inventory icon. - std::string getEnchantment (const MWWorld::ConstPtr& ptr) const override; - ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + ESM::RefId getEnchantment(const MWWorld::ConstPtr& ptr) const override; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; - ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. + const ESM::RefId& applyEnchantment(const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, + const std::string& newName) const override; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; - ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. - /// Second item in the pair specifies the error message + std::pair canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const override; + ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon + ///< conflicts with that. + /// Second item in the pair specifies the error message - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; - ///< Generate action for using via inventory menu + std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; + ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr &ptr) const override; + std::string getModel(const MWWorld::ConstPtr& ptr) const override; - bool canSell (const MWWorld::ConstPtr& item, int npcServices) const override; + bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; - float getWeight (const MWWorld::ConstPtr& ptr) const override; + float getWeight(const MWWorld::ConstPtr& ptr) const override; - int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const override; + int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override; }; } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 44d4e8f3f..c13739b88 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -2,24 +2,29 @@ #include #include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include -#include #include -#include +#include #include #include +#include #include -#include #include #include +#include + +#include /* Start of tes3mp addition @@ -35,55 +40,58 @@ */ #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" -#include "../mwbase/scriptmanager.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwscript/compilercontext.hpp" -#include "../mwscript/interpretercontext.hpp" #include "../mwscript/extensions.hpp" +#include "../mwscript/interpretercontext.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/actorutil.hpp" #include "filter.hpp" #include "hypertextparser.hpp" namespace MWDialogue { - DialogueManager::DialogueManager (const Compiler::Extensions& extensions, Translation::Storage& translationDataStorage) : - mTranslationDataStorage(translationDataStorage) - , mCompilerContext (MWScript::CompilerContext::Type_Dialogue) - , mErrorHandler() - , mTalkedTo(false) - , mTemporaryDispositionChange(0.f) - , mPermanentDispositionChange(0.f) + DialogueManager::DialogueManager( + const Compiler::Extensions& extensions, Translation::Storage& translationDataStorage) + : mTranslationDataStorage(translationDataStorage) + , mCompilerContext(MWScript::CompilerContext::Type_Dialogue) + , mErrorHandler() + , mTalkedTo(false) + , mOriginalDisposition(0) + , mCurrentDisposition(0) + , mPermanentDispositionChange(0) { mChoice = -1; mIsInChoice = false; mGoodbye = false; - mCompilerContext.setExtensions (&extensions); + mCompilerContext.setExtensions(&extensions); } void DialogueManager::clear() { mKnownTopics.clear(); mTalkedTo = false; - mTemporaryDispositionChange = 0; + mOriginalDisposition = 0; + mCurrentDisposition = 0; mPermanentDispositionChange = 0; } - void DialogueManager::addTopic (const std::string& topic) + void DialogueManager::addTopic(const ESM::RefId& topic) { - mKnownTopics.insert( Misc::StringUtils::lowerCase(topic) ); + mKnownTopics.insert(topic); } /* @@ -100,9 +108,10 @@ namespace MWDialogue End of tes3mp addition */ - void DialogueManager::parseText (const std::string& text) + std::vector DialogueManager::parseTopicIdsFromText(const std::string& text) { - updateActorKnownTopics(); + std::vector topicIdList; + std::vector hypertext = HyperTextParser::parseHyperText(text); for (std::vector::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok) @@ -113,12 +122,24 @@ namespace MWDialogue { // calculation of standard form for all hyperlinks size_t asterisk_count = HyperTextParser::removePseudoAsterisks(topicId); - for(; asterisk_count > 0; --asterisk_count) + for (; asterisk_count > 0; --asterisk_count) topicId.append("*"); topicId = mTranslationDataStorage.topicStandardForm(topicId); } + topicIdList.push_back(ESM::RefId::stringRefId(topicId)); + } + + return topicIdList; + } + + void DialogueManager::addTopicsFromText(const std::string& text) + { + updateActorKnownTopics(); + + for (const auto& topicId : parseTopicIdsFromText(text)) + { /* Start of tes3mp addition @@ -130,12 +151,26 @@ namespace MWDialogue End of tes3mp addition */ - if (mActorKnownTopics.count( topicId )) - mKnownTopics.insert( topicId ); + if (mActorKnownTopics.count(topicId)) + mKnownTopics.insert(topicId); } } - bool DialogueManager::startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) + void DialogueManager::updateOriginalDisposition() + { + if (mActor.getClass().isNpc()) + { + const auto& stats = mActor.getClass().getNpcStats(mActor); + // Disposition changed by script; discard our preconceived notions + if (stats.getBaseDisposition() != mCurrentDisposition) + { + mCurrentDisposition = stats.getBaseDisposition(); + mOriginalDisposition = mCurrentDisposition; + } + } + } + + bool DialogueManager::startDialogue(const MWWorld::Ptr& actor, ResponseCallback* callback) { updateGlobals(); @@ -143,9 +178,8 @@ namespace MWDialogue if (actor.getClass().getCreatureStats(actor).isDead()) return false; - mLastTopic = ""; - mPermanentDispositionChange = 0; - mTemporaryDispositionChange = 0; + mLastTopic = ESM::RefId(); + // Note that we intentionally don't reset mPermanentDispositionChange mChoice = -1; mIsInChoice = false; @@ -154,24 +188,22 @@ namespace MWDialogue mActor = actor; - MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats (actor); + MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); mTalkedTo = creatureStats.hasTalkedToPlayer(); mActorKnownTopics.clear(); - mActorKnownTopicsFlag.clear(); - //greeting - const MWWorld::Store &dialogs = - MWBase::Environment::get().getWorld()->getStore().get(); + // greeting + const MWWorld::Store& dialogs = MWBase::Environment::get().getESMStore()->get(); - Filter filter (actor, mChoice, mTalkedTo); + Filter filter(actor, mChoice, mTalkedTo); - for (MWWorld::Store::iterator it = dialogs.begin(); it != dialogs.end(); ++it) + for (const ESM::Dialogue& dialogue : dialogs) { - if(it->mType == ESM::Dialogue::Greeting) + if (dialogue.mType == ESM::Dialogue::Greeting) { // Search a response (we do not accept a fallback to "Info refusal" here) - if (const ESM::DialInfo *info = filter.search (*it, false)) + if (const ESM::DialInfo* info = filter.search(dialogue, false).second) { creatureStats.talkedToPlayer(); @@ -180,12 +212,12 @@ namespace MWDialogue // TODO play sound } - MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - callback->addResponse("", Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); - executeScript (info->mResultScript, mActor); - mLastTopic = it->mId; + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(), mActor); + callback->addResponse({}, Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); + executeScript(info->mResultScript, mActor); + mLastTopic = dialogue.mId; - parseText (info->mResponse); + addTopicsFromText(info->mResponse); return true; } @@ -194,9 +226,10 @@ namespace MWDialogue return false; } - bool DialogueManager::compile (const std::string& cmd, std::vector& code, const MWWorld::Ptr& actor) + std::optional DialogueManager::compile(const std::string& cmd, const MWWorld::Ptr& actor) { bool success = true; + std::optional program; try { @@ -204,29 +237,29 @@ namespace MWDialogue mErrorHandler.setContext("[dialogue script]"); - std::istringstream input (cmd + "\n"); + std::istringstream input(cmd + "\n"); - Compiler::Scanner scanner (mErrorHandler, input, mCompilerContext.getExtensions()); + Compiler::Scanner scanner(mErrorHandler, input, mCompilerContext.getExtensions()); Compiler::Locals locals; - std::string actorScript = actor.getClass().getScript (actor); + const ESM::RefId& actorScript = actor.getClass().getScript(actor); if (!actorScript.empty()) { // grab local variables from actor's script, if available. - locals = MWBase::Environment::get().getScriptManager()->getLocals (actorScript); + locals = MWBase::Environment::get().getScriptManager()->getLocals(actorScript); } - Compiler::ScriptParser parser(mErrorHandler,mCompilerContext, locals, false); + Compiler::ScriptParser parser(mErrorHandler, mCompilerContext, locals, false); - scanner.scan (parser); + scanner.scan(parser); if (!mErrorHandler.isGood()) success = false; if (success) - parser.getCode (code); + program = parser.getProgram(); } catch (const Compiler::SourceException& /* error */) { @@ -235,7 +268,7 @@ namespace MWDialogue } catch (const std::exception& error) { - Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); + Log(Debug::Error) << std::string("Dialogue error: An exception has been thrown: ") + error.what(); success = false; } @@ -244,13 +277,12 @@ namespace MWDialogue Log(Debug::Error) << "Error: compiling failed (dialogue script): \n" << cmd << "\n"; } - return success; + return program; } - void DialogueManager::executeScript (const std::string& script, const MWWorld::Ptr& actor) + void DialogueManager::executeScript(const std::string& script, const MWWorld::Ptr& actor) { - std::vector code; - if(compile(script, code, actor)) + if (const std::optional program = compile(script, actor)) { try { @@ -269,20 +301,20 @@ namespace MWDialogue */ Interpreter::Interpreter interpreter; - MWScript::installOpcodes (interpreter); - interpreter.run (&code[0], code.size(), interpreterContext); + MWScript::installOpcodes(interpreter); + interpreter.run(*program, interpreterContext); } catch (const std::exception& error) { - Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); + Log(Debug::Error) << std::string("Dialogue error: An exception has been thrown: ") + error.what(); } } } - bool DialogueManager::inJournal (const std::string& topicId, const std::string& infoId) + bool DialogueManager::inJournal(const ESM::RefId& topicId, const ESM::RefId& infoId) const { - const MWDialogue::Topic *topicHistory = nullptr; - MWBase::Journal *journal = MWBase::Environment::get().getJournal(); + const MWDialogue::Topic* topicHistory = nullptr; + MWBase::Journal* journal = MWBase::Environment::get().getJournal(); for (auto it = journal->topicBegin(); it != journal->topicEnd(); ++it) { if (it->first == topicId) @@ -295,7 +327,7 @@ namespace MWDialogue if (!topicHistory) return false; - for(const auto& topic : *topicHistory) + for (const auto& topic : *topicHistory) { if (topic.mInfoId == infoId) return true; @@ -303,65 +335,58 @@ namespace MWDialogue return false; } - void DialogueManager::executeTopic (const std::string& topic, ResponseCallback* callback) + void DialogueManager::executeTopic(const ESM::RefId& topic, ResponseCallback* callback) { - Filter filter (mActor, mChoice, mTalkedTo); + Filter filter(mActor, mChoice, mTalkedTo); - const MWWorld::Store &dialogues = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& dialogues = MWBase::Environment::get().getESMStore()->get(); - const ESM::Dialogue& dialogue = *dialogues.find (topic); + const ESM::Dialogue& dialogue = *dialogues.find(topic); + + const auto [responseTopic, info] = filter.search(dialogue, true); - const ESM::DialInfo* info = filter.search(dialogue, true); if (info) { - std::string title; - if (dialogue.mType==ESM::Dialogue::Persuasion) + std::string_view title; + if (dialogue.mType == ESM::Dialogue::Persuasion) { // Determine GMST from dialogue topic. GMSTs are: // sAdmireSuccess, sAdmireFail, sIntimidateSuccess, sIntimidateFail, // sTauntSuccess, sTauntFail, sBribeSuccess, sBribeFail - std::string modifiedTopic = "s" + topic; + std::string modifiedTopic = "s" + topic.getRefIdString(); - modifiedTopic.erase (std::remove (modifiedTopic.begin(), modifiedTopic.end(), ' '), modifiedTopic.end()); + modifiedTopic.erase(std::remove(modifiedTopic.begin(), modifiedTopic.end(), ' '), modifiedTopic.end()); - const MWWorld::Store& gmsts = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmsts + = MWBase::Environment::get().getESMStore()->get(); - title = gmsts.find (modifiedTopic)->mValue.getString(); + title = gmsts.find(modifiedTopic)->mValue.getString(); } else - title = topic; + title = dialogue.mStringId; - MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(), mActor); callback->addResponse(title, Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); if (dialogue.mType == ESM::Dialogue::Topic) { - // 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 (iter->mId == info->mId) - { - MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(topic), info->mId, mActor); - break; - } - } + // 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. + if (responseTopic == &dialogue) + MWBase::Environment::get().getJournal()->addTopic(topic, info->mId, mActor); } mLastTopic = topic; - executeScript (info->mResultScript, mActor); + executeScript(info->mResultScript, mActor); - parseText (info->mResponse); + addTopicsFromText(info->mResponse); } } - const ESM::Dialogue *DialogueManager::searchDialogue(const std::string& id) + const ESM::Dialogue* DialogueManager::searchDialogue(const ESM::RefId& id) { - return MWBase::Environment::get().getWorld()->getStore().get().search(id); + return MWBase::Environment::get().getESMStore()->get().search(id); } void DialogueManager::updateGlobals() @@ -374,34 +399,51 @@ namespace MWDialogue updateGlobals(); mActorKnownTopics.clear(); - mActorKnownTopicsFlag.clear(); - const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get(); + const auto& dialogs = MWBase::Environment::get().getESMStore()->get(); - Filter filter (mActor, -1, mTalkedTo); + Filter filter(mActor, -1, mTalkedTo); for (const auto& dialog : dialogs) { if (dialog.mType == ESM::Dialogue::Topic) { - const auto* answer = filter.search(dialog, true); - auto topicId = Misc::StringUtils::lowerCase(dialog.mId); + const auto* answer = filter.search(dialog, true).second; + const auto& topicId = dialog.mId; if (answer != nullptr) { - int flag = 0; - if(!inJournal(topicId, answer->mId)) + int topicFlags = 0; + if (!inJournal(topicId, answer->mId)) { // Does this dialogue contains some actor-specific answer? - if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId())) - flag |= MWBase::DialogueManager::TopicType::Specific; + if (answer->mActor == mActor.getCellRef().getRefId()) + topicFlags |= MWBase::DialogueManager::TopicType::Specific; } else - flag |= MWBase::DialogueManager::TopicType::Exhausted; - mActorKnownTopics.insert (dialog.mId); - mActorKnownTopicsFlag[dialog.mId] = flag; + topicFlags |= MWBase::DialogueManager::TopicType::Exhausted; + mActorKnownTopics.insert(std::make_pair(dialog.mId, ActorKnownTopicInfo{ topicFlags, answer })); } + } + } + // If response to a topic leads to a new topic, the original topic is not exhausted. + + for (auto& [dialogId, topicInfo] : mActorKnownTopics) + { + // If the topic is not marked as exhausted, we don't need to do anything about it. + // If the topic will not be shown to the player, the flag actually does not matter. + + if (!(topicInfo.mFlags & MWBase::DialogueManager::TopicType::Exhausted) || !mKnownTopics.count(dialogId)) + continue; + + for (const auto& topicId : parseTopicIdsFromText(topicInfo.mInfo->mResponse)) + { + if (mActorKnownTopics.count(topicId) && !mKnownTopics.count(topicId)) + { + topicInfo.mFlags &= ~MWBase::DialogueManager::TopicType::Exhausted; + break; + } } } } @@ -411,12 +453,12 @@ namespace MWDialogue updateActorKnownTopics(); std::list keywordList; - - for (const std::string& topic : mActorKnownTopics) + const auto& store = MWBase::Environment::get().getESMStore()->get(); + for (const auto& [topic, topicInfo] : mActorKnownTopics) { - //does the player know the topic? - if (mKnownTopics.count(topic)) - keywordList.push_back(topic); + // does the player know the topic? + if (mKnownTopics.contains(topic)) + keywordList.push_back(store.find(topic)->mStringId); } // sort again, because the previous sort was case-sensitive @@ -424,19 +466,22 @@ namespace MWDialogue return keywordList; } - int DialogueManager::getTopicFlag(const std::string& topicId) + int DialogueManager::getTopicFlag(const ESM::RefId& topicId) const { - return mActorKnownTopicsFlag[topicId]; + auto known = mActorKnownTopics.find(topicId); + if (known != mActorKnownTopics.end()) + return known->second.mFlags; + return 0; } - void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback) + void DialogueManager::keywordSelected(std::string_view keyword, ResponseCallback* callback) { - if(!mIsInChoice) + if (!mIsInChoice) { - const ESM::Dialogue* dialogue = searchDialogue(keyword); + const ESM::Dialogue* dialogue = searchDialogue(ESM::RefId::stringRefId(keyword)); if (dialogue && dialogue->mType == ESM::Dialogue::Topic) { - executeTopic (keyword, callback); + executeTopic(dialogue->mId, callback); } } } @@ -448,60 +493,57 @@ namespace MWDialogue void DialogueManager::goodbyeSelected() { - // Apply disposition change to NPC's base disposition - if (mActor.getClass().isNpc()) + // Apply disposition change to NPC's base disposition if we **think** we need to change something + if ((mPermanentDispositionChange || mOriginalDisposition != mCurrentDisposition) && mActor.getClass().isNpc()) { - // Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate) - float curDisp = static_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)); - if (curDisp + mPermanentDispositionChange < 0) - mPermanentDispositionChange = -curDisp; - + updateOriginalDisposition(); MWMechanics::NpcStats& npcStats = mActor.getClass().getNpcStats(mActor); - npcStats.setBaseDisposition(static_cast(npcStats.getBaseDisposition() + mPermanentDispositionChange)); + // Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with + // intimidate) + npcStats.setBaseDisposition(0); + int zero = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false); + int disposition = std::clamp(mOriginalDisposition + mPermanentDispositionChange, -zero, 100 - zero); + + npcStats.setBaseDisposition(disposition); } mPermanentDispositionChange = 0; - mTemporaryDispositionChange = 0; + mOriginalDisposition = 0; + mCurrentDisposition = 0; } - void DialogueManager::questionAnswered (int answer, ResponseCallback* callback) + void DialogueManager::questionAnswered(int answer, ResponseCallback* callback) { mChoice = answer; const ESM::Dialogue* dialogue = searchDialogue(mLastTopic); if (dialogue) { - Filter filter (mActor, mChoice, mTalkedTo); + Filter filter(mActor, mChoice, mTalkedTo); if (dialogue->mType == ESM::Dialogue::Topic || dialogue->mType == ESM::Dialogue::Greeting) { - if (const ESM::DialInfo *info = filter.search (*dialogue, true)) + const auto [responseTopic, info] = filter.search(*dialogue, true); + if (info) { - std::string text = info->mResponse; - parseText (text); + const std::string& text = info->mResponse; + addTopicsFromText(text); mChoice = -1; mIsInChoice = false; mChoices.clear(); - MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - callback->addResponse("", Interpreter::fixDefinesDialog(text, interpreterContext)); + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(), mActor); + callback->addResponse({}, Interpreter::fixDefinesDialog(text, interpreterContext)); if (dialogue->mType == ESM::Dialogue::Topic) { - // 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 (iter->mId == info->mId) - { - MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(mLastTopic), info->mId, mActor); - break; - } - } + // 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 + if (responseTopic == dialogue) + MWBase::Environment::get().getJournal()->addTopic(mLastTopic, info->mId, mActor); } - executeScript (info->mResultScript, mActor); + executeScript(info->mResultScript, mActor); } else { @@ -515,18 +557,18 @@ namespace MWDialogue updateActorKnownTopics(); } - void DialogueManager::addChoice (const std::string& text, int choice) + void DialogueManager::addChoice(std::string_view text, int choice) { mIsInChoice = true; mChoices.emplace_back(text, choice); } - const std::vector >& DialogueManager::getChoices() + const std::vector>& DialogueManager::getChoices() const { return mChoices; } - bool DialogueManager::isGoodbye() + bool DialogueManager::isGoodbye() const { return mGoodbye; } @@ -540,26 +582,22 @@ namespace MWDialogue void DialogueManager::persuade(int type, ResponseCallback* callback) { bool success; - float temp, perm; + int temp, perm; MWBase::Environment::get().getMechanicsManager()->getPersuasionDispositionChange( - mActor, MWBase::MechanicsManager::PersuasionType(type), - success, temp, perm); - mTemporaryDispositionChange += temp; + mActor, MWBase::MechanicsManager::PersuasionType(type), success, temp, perm); + updateOriginalDisposition(); + if (temp > 0 && perm > 0 && mOriginalDisposition + perm + mPermanentDispositionChange < 0) + perm = -(mOriginalDisposition + mPermanentDispositionChange); + mCurrentDisposition += temp; + mActor.getClass().getNpcStats(mActor).setBaseDisposition(mCurrentDisposition); mPermanentDispositionChange += perm; - // change temp disposition so that final disposition is between 0...100 - float curDisp = static_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false)); - if (curDisp + mTemporaryDispositionChange < 0) - mTemporaryDispositionChange = -curDisp; - else if (curDisp + mTemporaryDispositionChange > 100) - mTemporaryDispositionChange = 100 - curDisp; - MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); if (success) { - int gold=0; + int gold = 0; if (type == MWBase::MechanicsManager::PT_Bribe10) gold = 10; else if (type == MWBase::MechanicsManager::PT_Bribe100) @@ -569,8 +607,8 @@ namespace MWDialogue if (gold) { - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, gold, player); - mActor.getClass().getContainerStore(mActor).add(MWWorld::ContainerStore::sGoldId, gold, mActor); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, gold); + mActor.getClass().getContainerStore(mActor).add(MWWorld::ContainerStore::sGoldId, gold); } } @@ -582,58 +620,59 @@ namespace MWDialogue text = "Taunt"; else if (type == MWBase::MechanicsManager::PT_Intimidate) text = "Intimidate"; - else{ + else + { text = "Bribe"; } - executeTopic (text + (success ? " Success" : " Fail"), callback); - } - - int DialogueManager::getTemporaryDispositionChange() const - { - return static_cast(mTemporaryDispositionChange); + executeTopic(ESM::RefId::stringRefId(text + (success ? " Success" : " Fail")), callback); } void DialogueManager::applyBarterDispositionChange(int delta) { - mTemporaryDispositionChange += delta; - if (Settings::Manager::getBool("barter disposition change is permanent", "Game")) - mPermanentDispositionChange += delta; + if (mActor.getClass().isNpc()) + { + updateOriginalDisposition(); + mCurrentDisposition += delta; + mActor.getClass().getNpcStats(mActor).setBaseDisposition(mCurrentDisposition); + if (Settings::game().mBarterDispositionChangeIsPermanent) + mPermanentDispositionChange += delta; + } } bool DialogueManager::checkServiceRefused(ResponseCallback* callback, ServiceType service) { - Filter filter (mActor, service, mTalkedTo); + Filter filter(mActor, service, mTalkedTo); - const MWWorld::Store &dialogues = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& dialogues = MWBase::Environment::get().getESMStore()->get(); - const ESM::Dialogue& dialogue = *dialogues.find ("Service Refusal"); + const ESM::Dialogue& dialogue = *dialogues.find(ESM::RefId::stringRefId("Service Refusal")); - std::vector infos = filter.list (dialogue, false, false, true); + std::vector infos = filter.list(dialogue, false, false, true); if (!infos.empty()) { - const ESM::DialInfo* info = infos[0]; + const ESM::DialInfo* info = infos[0].second; - parseText (info->mResponse); + addTopicsFromText(info->mResponse); - const MWWorld::Store& gmsts = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmsts + = MWBase::Environment::get().getESMStore()->get(); - MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); + MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(), mActor); - callback->addResponse(gmsts.find ("sServiceRefusal")->mValue.getString(), Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); + callback->addResponse(gmsts.find("sServiceRefusal")->mValue.getString(), + Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); - executeScript (info->mResultScript, mActor); + executeScript(info->mResultScript, mActor); return true; } return false; } - void DialogueManager::say(const MWWorld::Ptr &actor, const std::string &topic) + void DialogueManager::say(const MWWorld::Ptr& actor, const ESM::RefId& topic) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(sndMgr->sayActive(actor)) + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + if (sndMgr->sayActive(actor)) { // Actor is already saying something. return; @@ -651,15 +690,15 @@ namespace MWDialogue return; } - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Dialogue *dial = store.get().find(topic); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::Dialogue* dial = store.get().find(topic); const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); Filter filter(actor, 0, creatureStats.hasTalkedToPlayer()); - const ESM::DialInfo *info = filter.search(*dial, false); - if(info != nullptr) + const ESM::DialInfo* info = filter.search(*dial, false).second; + if (info != nullptr) { - MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); if (winMgr->getSubtitlesEnabled()) /* Start of tes3mp change (minor) @@ -698,95 +737,86 @@ namespace MWDialogue return 1; // known topics } - void DialogueManager::write (ESM::ESMWriter& writer, Loading::Listener& progress) const + void DialogueManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) const { ESM::DialogueState state; - for (std::set::const_iterator iter (mKnownTopics.begin()); - iter!=mKnownTopics.end(); ++iter) - { - state.mKnownTopics.push_back (*iter); - } + state.mKnownTopics.reserve(mKnownTopics.size()); + std::copy(mKnownTopics.begin(), mKnownTopics.end(), std::back_inserter(state.mKnownTopics)); state.mChangedFactionReaction = mChangedFactionReaction; - writer.startRecord (ESM::REC_DIAS); - state.save (writer); - writer.endRecord (ESM::REC_DIAS); + writer.startRecord(ESM::REC_DIAS); + state.save(writer); + writer.endRecord(ESM::REC_DIAS); } - void DialogueManager::readRecord (ESM::ESMReader& reader, uint32_t type) + void DialogueManager::readRecord(ESM::ESMReader& reader, uint32_t type) { - if (type==ESM::REC_DIAS) + if (type == ESM::REC_DIAS) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); ESM::DialogueState state; - state.load (reader); + state.load(reader); - for (std::vector::const_iterator iter (state.mKnownTopics.begin()); - iter!=state.mKnownTopics.end(); ++iter) - if (store.get().search (*iter)) - mKnownTopics.insert (*iter); + for (const auto& knownTopic : state.mKnownTopics) + if (store.get().search(knownTopic)) + mKnownTopics.insert(knownTopic); mChangedFactionReaction = state.mChangedFactionReaction; } } - void DialogueManager::modFactionReaction(const std::string &faction1, const std::string &faction2, int diff) + void DialogueManager::modFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int diff) { - std::string fact1 = Misc::StringUtils::lowerCase(faction1); - std::string fact2 = Misc::StringUtils::lowerCase(faction2); - // Make sure the factions exist - MWBase::Environment::get().getWorld()->getStore().get().find(fact1); - MWBase::Environment::get().getWorld()->getStore().get().find(fact2); + MWBase::Environment::get().getESMStore()->get().find(faction1); + MWBase::Environment::get().getESMStore()->get().find(faction2); int newValue = getFactionReaction(faction1, faction2) + diff; - std::map& map = mChangedFactionReaction[fact1]; - map[fact2] = newValue; + auto& map = mChangedFactionReaction[faction1]; + map[faction2] = newValue; } - void DialogueManager::setFactionReaction(const std::string &faction1, const std::string &faction2, int absolute) + void DialogueManager::setFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int absolute) { - std::string fact1 = Misc::StringUtils::lowerCase(faction1); - std::string fact2 = Misc::StringUtils::lowerCase(faction2); - // Make sure the factions exist - MWBase::Environment::get().getWorld()->getStore().get().find(fact1); - MWBase::Environment::get().getWorld()->getStore().get().find(fact2); + MWBase::Environment::get().getESMStore()->get().find(faction1); + MWBase::Environment::get().getESMStore()->get().find(faction2); - std::map& map = mChangedFactionReaction[fact1]; - map[fact2] = absolute; + auto& map = mChangedFactionReaction[faction1]; + map[faction2] = absolute; } - int DialogueManager::getFactionReaction(const std::string &faction1, const std::string &faction2) const + int DialogueManager::getFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2) const { - std::string fact1 = Misc::StringUtils::lowerCase(faction1); - std::string fact2 = Misc::StringUtils::lowerCase(faction2); + ModFactionReactionMap::const_iterator map = mChangedFactionReaction.find(faction1); + if (map != mChangedFactionReaction.end()) + { + auto it = map->second.find(faction2); + if (it != map->second.end()) + return it->second; + } - ModFactionReactionMap::const_iterator map = mChangedFactionReaction.find(fact1); - if (map != mChangedFactionReaction.end() && map->second.find(fact2) != map->second.end()) - return map->second.at(fact2); + const ESM::Faction* faction = MWBase::Environment::get().getESMStore()->get().find(faction1); - const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(fact1); - - std::map::const_iterator it = faction->mReactions.begin(); + auto it = faction->mReactions.begin(); for (; it != faction->mReactions.end(); ++it) { - if (Misc::StringUtils::ciEqual(it->first, fact2)) - return it->second; + if (it->first == faction2) + return it->second; } return 0; } - void DialogueManager::clearInfoActor(const MWWorld::Ptr &actor) const + void DialogueManager::clearInfoActor(const MWWorld::Ptr& actor) const { if (actor == mActor && !mLastTopic.empty()) { MWBase::Environment::get().getJournal()->removeLastAddedTopicResponse( - Misc::StringUtils::lowerCase(mLastTopic), actor.getClass().getName(actor)); + mLastTopic, actor.getClass().getName(actor)); } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 9db669274..617959de2 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -4,12 +4,15 @@ #include "../mwbase/dialoguemanager.hpp" #include +#include #include #include #include +#include +#include +#include #include -#include #include "../mwworld/ptr.hpp" @@ -24,120 +27,128 @@ namespace MWDialogue { class DialogueManager : public MWBase::DialogueManager { - std::set mKnownTopics;// Those are the topics the player knows. + struct ActorKnownTopicInfo + { + int mFlags; + const ESM::DialInfo* mInfo; + }; - // Modified faction reactions. > - typedef std::map > ModFactionReactionMap; - ModFactionReactionMap mChangedFactionReaction; + std::set mKnownTopics; // Those are the topics the player knows. - std::set mActorKnownTopics; - std::unordered_map mActorKnownTopicsFlag; + // Modified faction reactions. > + typedef std::map> ModFactionReactionMap; + ModFactionReactionMap mChangedFactionReaction; - Translation::Storage& mTranslationDataStorage; - MWScript::CompilerContext mCompilerContext; - Compiler::StreamErrorHandler mErrorHandler; + std::map mActorKnownTopics; - MWWorld::Ptr mActor; - bool mTalkedTo; + Translation::Storage& mTranslationDataStorage; + MWScript::CompilerContext mCompilerContext; + Compiler::StreamErrorHandler mErrorHandler; - int mChoice; - std::string mLastTopic; // last topic ID, lowercase - bool mIsInChoice; - bool mGoodbye; + MWWorld::Ptr mActor; + bool mTalkedTo; - std::vector > mChoices; + int mChoice; + ESM::RefId mLastTopic; // last topic ID, lowercase + bool mIsInChoice; + bool mGoodbye; - float mTemporaryDispositionChange; - float mPermanentDispositionChange; + std::vector> mChoices; - void parseText (const std::string& text); + int mOriginalDisposition; + int mCurrentDisposition; + int mPermanentDispositionChange; - void updateActorKnownTopics(); - void updateGlobals(); + std::vector parseTopicIdsFromText(const std::string& text); + void addTopicsFromText(const std::string& text); - bool compile (const std::string& cmd, std::vector& code, const MWWorld::Ptr& actor); - void executeScript (const std::string& script, const MWWorld::Ptr& actor); + void updateActorKnownTopics(); + void updateGlobals(); - void executeTopic (const std::string& topic, ResponseCallback* callback); + std::optional compile(const std::string& cmd, const MWWorld::Ptr& actor); - const ESM::Dialogue* searchDialogue(const std::string& id); + void executeScript(const std::string& script, const MWWorld::Ptr& actor); - public: + void executeTopic(const ESM::RefId& topic, ResponseCallback* callback); - DialogueManager (const Compiler::Extensions& extensions, Translation::Storage& translationDataStorage); + const ESM::Dialogue* searchDialogue(const ESM::RefId& id); - void clear() override; + void updateOriginalDisposition(); - bool isInChoice() const override; + public: + DialogueManager(const Compiler::Extensions& extensions, Translation::Storage& translationDataStorage); - bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) override; + void clear() override; - std::list getAvailableTopics() override; - int getTopicFlag(const std::string& topicId) override; + bool isInChoice() const override; - bool inJournal (const std::string& topicId, const std::string& infoId) override; + bool startDialogue(const MWWorld::Ptr& actor, ResponseCallback* callback) override; - void addTopic (const std::string& topic) override; + std::list getAvailableTopics() override; + int getTopicFlag(const ESM::RefId& topicId) const override; - /* - Start of tes3mp addition + /* + Start of tes3mp addition - Make it possible to check whether a topic is known by the player from elsewhere - in the code - */ - virtual bool isNewTopic(const std::string& topic); - /* - End of tes3mp addition - */ + Make it possible to check whether a topic is known by the player from elsewhere + in the code + */ + virtual bool isNewTopic(const std::string& topic); + /* + End of tes3mp addition + */ - void addChoice (const std::string& text,int choice) override; - const std::vector >& getChoices() override; + bool inJournal(const ESM::RefId& topicId, const ESM::RefId& infoId) const override; - bool isGoodbye() override; + void addTopic(const ESM::RefId& topic) override; - void goodbye() override; + void addChoice(std::string_view text, int choice) override; + const std::vector>& getChoices() const override; - bool checkServiceRefused (ResponseCallback* callback, ServiceType service = ServiceType::Any) override; + bool isGoodbye() const override; - void say(const MWWorld::Ptr &actor, const std::string &topic) override; + void goodbye() override; - //calbacks for the GUI - void keywordSelected (const std::string& keyword, ResponseCallback* callback) override; - void goodbyeSelected() override; - void questionAnswered (int answer, ResponseCallback* callback) override; + bool checkServiceRefused(ResponseCallback* callback, ServiceType service = ServiceType::Any) override; - void persuade (int type, ResponseCallback* callback) override; - int getTemporaryDispositionChange () const override; + void say(const MWWorld::Ptr& actor, const ESM::RefId& topic) override; - /// @note Controlled by an option, gets discarded when dialogue ends by default - void applyBarterDispositionChange (int delta) override; + // calbacks for the GUI + void keywordSelected(std::string_view keyword, ResponseCallback* callback) override; + void goodbyeSelected() override; + void questionAnswered(int answer, ResponseCallback* callback) override; - int countSavedGameRecords() const override; + void persuade(int type, ResponseCallback* callback) override; - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const override; + /// @note Controlled by an option, gets discarded when dialogue ends by default + void applyBarterDispositionChange(int delta) override; - void readRecord (ESM::ESMReader& reader, uint32_t type) override; + int countSavedGameRecords() const override; - /// Changes faction1's opinion of faction2 by \a diff. - void modFactionReaction (const std::string& faction1, const std::string& faction2, int diff) override; + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override; - void setFactionReaction (const std::string& faction1, const std::string& faction2, int absolute) override; + void readRecord(ESM::ESMReader& reader, uint32_t type) override; - /// @return faction1's opinion of faction2 - int getFactionReaction (const std::string& faction1, const std::string& faction2) const override; + /// Changes faction1's opinion of faction2 by \a diff. + void modFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int diff) override; - /// Removes the last added topic response for the given actor from the journal - void clearInfoActor(const MWWorld::Ptr & actor) const override; + /* + Start of tes3mp addition - /* - Start of tes3mp addition + Make it possible to get the caption of a voice dialogue + */ + virtual std::string getVoiceCaption(const std::string& sound) const; + /* + End of tes3mp addition + */ - Make it possible to get the caption of a voice dialogue - */ - virtual std::string getVoiceCaption(const std::string& sound) const; - /* - End of tes3mp addition - */ + void setFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int absolute) override; + + /// @return faction1's opinion of faction2 + int getFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2) const override; + + /// Removes the last added topic response for the given actor from the journal + void clearInfoActor(const MWWorld::Ptr& actor) const override; }; } diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 334a9db39..e05f1b37c 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -1,34 +1,92 @@ #include "filter.hpp" #include +#include +#include +#include +#include +#include +#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" -#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" -#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" #include "selectwrapper.hpp" -bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const +namespace { - bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name()); + bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor) + { + const ESM::RefId selectId = ESM::RefId::stringRefId(select.getName()); + if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId) + return actor.getCellRef().getRefId() != selectId; + if (actor.getClass().isNpc()) + { + if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotFaction) + return actor.getClass().getPrimaryFaction(actor) != selectId; + else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotClass) + return actor.get()->mBase->mClass != selectId; + else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotRace) + return actor.get()->mBase->mRace != selectId; + } + return true; + } + + bool matchesStaticFilters(const ESM::DialInfo& info, const MWWorld::Ptr& actor) + { + for (const ESM::DialInfo::SelectStruct& select : info.mSelects) + { + MWDialogue::SelectWrapper wrapper = select; + if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Boolean) + { + if (!wrapper.selectCompare(matchesStaticFilters(wrapper, actor))) + return false; + } + else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Inverted) + { + if (!matchesStaticFilters(wrapper, actor)) + return false; + } + else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Numeric) + { + if (wrapper.getFunction() == MWDialogue::SelectWrapper::Function_Local) + { + const ESM::RefId& scriptName = actor.getClass().getScript(actor); + if (scriptName.empty()) + return false; + const Compiler::Locals& localDefs + = MWBase::Environment::get().getScriptManager()->getLocals(scriptName); + char type = localDefs.getType(wrapper.getName()); + if (type == ' ') + return false; // script does not have a variable of this name. + } + } + } + return true; + } +} + +bool MWDialogue::Filter::testActor(const ESM::DialInfo& info) const +{ + bool isCreature = (mActor.getType() != ESM::NPC::sRecordId); // actor id if (!info.mActor.empty()) { - if ( !Misc::StringUtils::ciEqual(info.mActor, mActor.getCellRef().getRefId())) + if (info.mActor != mActor.getCellRef().getRefId()) return false; } else if (isCreature) @@ -43,9 +101,9 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (isCreature) return true; - MWWorld::LiveCellRef *cellRef = mActor.get(); + MWWorld::LiveCellRef* cellRef = mActor.get(); - if (!Misc::StringUtils::ciEqual(info.mRace, cellRef->mBase->mRace)) + if (!(info.mRace == cellRef->mBase->mRace)) return false; } @@ -55,9 +113,9 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (isCreature) return true; - MWWorld::LiveCellRef *cellRef = mActor.get(); + MWWorld::LiveCellRef* cellRef = mActor.get(); - if ( !Misc::StringUtils::ciEqual(info.mClass, cellRef->mBase->mClass)) + if (!(info.mClass == cellRef->mBase->mClass)) return false; } @@ -75,7 +133,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (isCreature) return true; - if (!Misc::StringUtils::ciEqual(mActor.getClass().getPrimaryFaction(mActor), info.mFaction)) + if (!(mActor.getClass().getPrimaryFaction(mActor) == info.mFaction)) return false; // check rank @@ -97,24 +155,24 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (!isCreature) { MWWorld::LiveCellRef* npc = mActor.get(); - if (info.mData.mGender==(npc->mBase->mFlags & npc->mBase->Female ? 0 : 1)) + if (info.mData.mGender == (npc->mBase->mFlags & npc->mBase->Female ? 0 : 1)) return false; } return true; } -bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const +bool MWDialogue::Filter::testPlayer(const ESM::DialInfo& info) const { const MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::NpcStats& stats = player.getClass().getNpcStats (player); + MWMechanics::NpcStats& stats = player.getClass().getNpcStats(player); // check player faction and rank if (!info.mPcFaction.empty()) { - std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (info.mPcFaction)); + std::map::const_iterator iter = stats.getFactionRanks().find(info.mPcFaction); - if(iter==stats.getFactionRanks().end()) + if (iter == stats.getFactionRanks().end()) return false; // check rank @@ -124,9 +182,10 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const else if (info.mData.mPCrank != -1) { // required PC faction is not specified but PC rank is; use speaker's faction - std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (mActor.getClass().getPrimaryFaction(mActor))); + std::map::const_iterator iter + = stats.getFactionRanks().find(mActor.getClass().getPrimaryFaction(mActor)); - if(iter==stats.getFactionRanks().end()) + if (iter == stats.getFactionRanks().end()) return false; // check rank @@ -138,29 +197,27 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const if (!info.mCell.empty()) { // supports partial matches, just like getPcCell - const std::string& playerCell = MWBase::Environment::get().getWorld()->getCellName(player.getCell()); - bool match = playerCell.length()>=info.mCell.length() && - Misc::StringUtils::ciEqual(playerCell.substr (0, info.mCell.length()), info.mCell); - if (!match) + std::string_view playerCell = MWBase::Environment::get().getWorld()->getCellName(player.getCell()); + if (!Misc::StringUtils::ciStartsWith(playerCell, info.mCell.getRefIdString())) return false; } return true; } -bool MWDialogue::Filter::testSelectStructs (const ESM::DialInfo& info) const +bool MWDialogue::Filter::testSelectStructs(const ESM::DialInfo& info) const { - for (std::vector::const_iterator iter (info.mSelects.begin()); - iter != info.mSelects.end(); ++iter) - if (!testSelectStruct (*iter)) + for (std::vector::const_iterator iter(info.mSelects.begin()); + iter != info.mSelects.end(); ++iter) + if (!testSelectStruct(*iter)) return false; return true; } -bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert) const +bool MWDialogue::Filter::testDisposition(const ESM::DialInfo& info, bool invert) const { - bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name()); + bool isCreature = (mActor.getType() != ESM::NPC::sRecordId); if (isCreature) return true; @@ -173,22 +230,21 @@ bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert bool MWDialogue::Filter::testFunctionLocal(const MWDialogue::SelectWrapper& select) const { - std::string scriptName = mActor.getClass().getScript (mActor); + const ESM::RefId& scriptName = mActor.getClass().getScript(mActor); if (scriptName.empty()) return false; // no script - std::string name = Misc::StringUtils::lowerCase (select.getName()); + std::string name = select.getName(); - const Compiler::Locals& localDefs = - MWBase::Environment::get().getScriptManager()->getLocals (scriptName); + const Compiler::Locals& localDefs = MWBase::Environment::get().getScriptManager()->getLocals(scriptName); - char type = localDefs.getType (name); + char type = localDefs.getType(name); - if (type==' ') + if (type == ' ') return false; // script does not have a variable of this name. - int index = localDefs.getIndex (name); + int index = localDefs.getIndex(name); if (index < 0) return false; // shouldn't happen, we checked that variable has a type above, so must exist @@ -197,17 +253,20 @@ bool MWDialogue::Filter::testFunctionLocal(const MWDialogue::SelectWrapper& sele return select.selectCompare(0); switch (type) { - case 's': return select.selectCompare (static_cast (locals.mShorts[index])); - case 'l': return select.selectCompare (locals.mLongs[index]); - case 'f': return select.selectCompare (locals.mFloats[index]); + case 's': + return select.selectCompare(static_cast(locals.mShorts[index])); + case 'l': + return select.selectCompare(locals.mLongs[index]); + case 'f': + return select.selectCompare(locals.mFloats[index]); } - throw std::logic_error ("unknown local variable type in dialogue filter"); + throw std::logic_error("unknown local variable type in dialogue filter"); } -bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const +bool MWDialogue::Filter::testSelectStruct(const SelectWrapper& select) const { - if (select.isNpcOnly() && (mActor.getTypeName() != typeid (ESM::NPC).name())) + if (select.isNpcOnly() && (mActor.getType() != ESM::NPC::sRecordId)) // If the actor is a creature, we pass all conditions only applicable to NPCs. return true; @@ -215,34 +274,41 @@ bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const // If not currently in a choice, we reject all conditions that test against choices. return false; - if (select.getFunction() == SelectWrapper::Function_Weather && !(MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior())) + if (select.getFunction() == SelectWrapper::Function_Weather + && !(MWBase::Environment::get().getWorld()->isCellExterior() + || MWBase::Environment::get().getWorld()->isCellQuasiExterior())) // Reject weather conditions in interior cells - // Note that the original engine doesn't include the "|| isCellQuasiExterior()" check, which could be considered a bug. + // Note that the original engine doesn't include the "|| isCellQuasiExterior()" check, which could be considered + // a bug. return false; switch (select.getType()) { - case SelectWrapper::Type_None: return true; - case SelectWrapper::Type_Integer: return select.selectCompare (getSelectStructInteger (select)); - case SelectWrapper::Type_Numeric: return testSelectStructNumeric (select); - case SelectWrapper::Type_Boolean: return select.selectCompare (getSelectStructBoolean (select)); + case SelectWrapper::Type_None: + return true; + case SelectWrapper::Type_Integer: + return select.selectCompare(getSelectStructInteger(select)); + case SelectWrapper::Type_Numeric: + return testSelectStructNumeric(select); + case SelectWrapper::Type_Boolean: + return select.selectCompare(getSelectStructBoolean(select)); // We must not do the comparison for inverted functions (eg. Function_NotClass) - case SelectWrapper::Type_Inverted: return getSelectStructBoolean (select); + case SelectWrapper::Type_Inverted: + return getSelectStructBoolean(select); } return true; } -bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) const +bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) const { switch (select.getFunction()) { case SelectWrapper::Function_Global: // internally all globals are float :( - return select.selectCompare ( - MWBase::Environment::get().getWorld()->getGlobalFloat (select.getName())); + return select.selectCompare(MWBase::Environment::get().getWorld()->getGlobalFloat(select.getName())); case SelectWrapper::Function_Local: { @@ -257,38 +323,32 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); - - float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() / - player.getClass().getCreatureStats (player).getHealth().getModified(); - - return select.selectCompare (static_cast(ratio*100)); + return select.selectCompare( + static_cast(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100)); } case SelectWrapper::Function_PcDynamicStat: { MWWorld::Ptr player = MWMechanics::getPlayer(); - float value = player.getClass().getCreatureStats (player). - getDynamic (select.getArgument()).getCurrent(); + float value = player.getClass().getCreatureStats(player).getDynamic(select.getArgument()).getCurrent(); - return select.selectCompare (value); + return select.selectCompare(value); } case SelectWrapper::Function_HealthPercent: { - float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() / - mActor.getClass().getCreatureStats (mActor).getHealth().getModified(); - - return select.selectCompare (static_cast(ratio*100)); + return select.selectCompare( + static_cast(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100)); } default: - throw std::runtime_error ("unknown numeric select function"); + throw std::runtime_error("unknown numeric select function"); } } -int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) const +int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) const { MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -296,18 +356,19 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con { case SelectWrapper::Function_Journal: - return MWBase::Environment::get().getJournal()->getJournalIndex (select.getName()); + return MWBase::Environment::get().getJournal()->getJournalIndex(ESM::RefId::stringRefId(select.getName())); case SelectWrapper::Function_Item: { - MWWorld::ContainerStore& store = player.getClass().getContainerStore (player); + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); - return store.count(select.getName()); + return store.count(ESM::RefId::stringRefId(select.getName())); } case SelectWrapper::Function_Dead: - return MWBase::Environment::get().getMechanicsManager()->countDeaths (select.getName()); + return MWBase::Environment::get().getMechanicsManager()->countDeaths( + ESM::RefId::stringRefId(select.getName())); case SelectWrapper::Function_Choice: @@ -315,29 +376,31 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_AiSetting: - return mActor.getClass().getCreatureStats (mActor).getAiSetting ( - (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(false); + return mActor.getClass() + .getCreatureStats(mActor) + .getAiSetting((MWMechanics::AiSetting)select.getArgument()) + .getModified(false); case SelectWrapper::Function_PcAttribute: - - return player.getClass().getCreatureStats (player). - getAttribute (select.getArgument()).getModified(); - + { + auto attribute = static_cast(select.getArgument()); + return player.getClass().getCreatureStats(player).getAttribute(attribute).getModified(); + } case SelectWrapper::Function_PcSkill: - - return static_cast (player.getClass(). - getNpcStats (player).getSkill (select.getArgument()).getModified()); - + { + ESM::RefId skill = ESM::Skill::indexToRefId(select.getArgument()); + return static_cast(player.getClass().getNpcStats(player).getSkill(skill).getModified()); + } case SelectWrapper::Function_FriendlyHit: { - int hits = mActor.getClass().getCreatureStats (mActor).getFriendlyHits(); + int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits(); - return hits>4 ? 4 : hits; + return hits > 4 ? 4 : hits; } case SelectWrapper::Function_PcLevel: - return player.getClass().getCreatureStats (player).getLevel(); + return player.getClass().getCreatureStats(player).getLevel(); case SelectWrapper::Function_PcGender: @@ -345,16 +408,16 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_PcClothingModifier: { - const MWWorld::InventoryStore& store = player.getClass().getInventoryStore (player); + const MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); int value = 0; - for (int i=0; i<=15; ++i) // everything except things held in hands and ammunition + for (int i = 0; i <= 15; ++i) // everything except things held in hands and ammunition { - MWWorld::ConstContainerStoreIterator slot = store.getSlot (i); + MWWorld::ConstContainerStoreIterator slot = store.getSlot(i); - if (slot!=store.end()) - value += slot->getClass().getValue (*slot); + if (slot != store.end()) + value += slot->getClass().getValue(*slot); } return value; @@ -362,25 +425,25 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_PcCrimeLevel: - return player.getClass().getNpcStats (player).getBounty(); + return player.getClass().getNpcStats(player).getBounty(); case SelectWrapper::Function_RankRequirement: { - std::string faction = mActor.getClass().getPrimaryFaction(mActor); + const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) return 0; - int rank = getFactionRank (player, faction); + int rank = getFactionRank(player, faction); - if (rank>=9) + if (rank >= 9) return 0; // max rank int result = 0; - if (hasFactionRankSkillRequirements (player, faction, rank+1)) + if (hasFactionRankSkillRequirements(player, faction, rank + 1)) result += 1; - if (hasFactionRankReputationRequirements (player, faction, rank+1)) + if (hasFactionRankReputationRequirements(player, faction, rank + 1)) result += 2; return result; @@ -388,11 +451,11 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_Level: - return mActor.getClass().getCreatureStats (mActor).getLevel(); + return mActor.getClass().getCreatureStats(mActor).getLevel(); case SelectWrapper::Function_PCReputation: - return player.getClass().getNpcStats (player).getReputation(); + return player.getClass().getNpcStats(player).getReputation(); case SelectWrapper::Function_Weather: @@ -400,42 +463,43 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_Reputation: - return mActor.getClass().getNpcStats (mActor).getReputation(); + return mActor.getClass().getNpcStats(mActor).getReputation(); case SelectWrapper::Function_FactionRankDiff: { - std::string faction = mActor.getClass().getPrimaryFaction(mActor); + const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) return 0; - int rank = getFactionRank (player, faction); + int rank = getFactionRank(player, faction); int npcRank = mActor.getClass().getPrimaryFactionRank(mActor); - return rank-npcRank; + return rank - npcRank; } case SelectWrapper::Function_WerewolfKills: - return player.getClass().getNpcStats (player).getWerewolfKills(); + return player.getClass().getNpcStats(player).getWerewolfKills(); case SelectWrapper::Function_RankLow: case SelectWrapper::Function_RankHigh: { - bool low = select.getFunction()==SelectWrapper::Function_RankLow; + bool low = select.getFunction() == SelectWrapper::Function_RankLow; - std::string factionId = mActor.getClass().getPrimaryFaction(mActor); + const ESM::RefId& factionId = mActor.getClass().getPrimaryFaction(mActor); if (factionId.empty()) return 0; int value = 0; - MWMechanics::NpcStats& playerStats = player.getClass().getNpcStats (player); + MWMechanics::NpcStats& playerStats = player.getClass().getNpcStats(player); - std::map::const_iterator playerFactionIt = playerStats.getFactionRanks().begin(); + std::map::const_iterator playerFactionIt = playerStats.getFactionRanks().begin(); for (; playerFactionIt != playerStats.getFactionRanks().end(); ++playerFactionIt) { - int reaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction(factionId, playerFactionIt->first); + int reaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction( + factionId, playerFactionIt->first); if (low ? reaction < value : reaction > value) value = reaction; } @@ -445,26 +509,26 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_CreatureTargetted: + { + MWWorld::Ptr target; + mActor.getClass().getCreatureStats(mActor).getAiSequence().getCombatTarget(target); + if (!target.isEmpty()) { - MWWorld::Ptr target; - mActor.getClass().getCreatureStats(mActor).getAiSequence().getCombatTarget(target); - if (target) - { - if (target.getClass().isNpc() && target.getClass().getNpcStats(target).isWerewolf()) - return 2; - if (target.getTypeName() == typeid(ESM::Creature).name()) - return 1; - } + if (target.getClass().isNpc() && target.getClass().getNpcStats(target).isWerewolf()) + return 2; + if (target.getType() == ESM::Creature::sRecordId) + return 1; } + } return 0; default: - throw std::runtime_error ("unknown integer select function"); + throw std::runtime_error("unknown integer select function"); } } -bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) const +bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) const { MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -476,55 +540,58 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_NotId: - return !Misc::StringUtils::ciEqual(mActor.getCellRef().getRefId(), select.getName()); + return !(mActor.getCellRef().getRefId() == ESM::RefId::stringRefId(select.getName())); case SelectWrapper::Function_NotFaction: - return !Misc::StringUtils::ciEqual(mActor.getClass().getPrimaryFaction(mActor), select.getName()); + return !(mActor.getClass().getPrimaryFaction(mActor) == ESM::RefId::stringRefId(select.getName())); case SelectWrapper::Function_NotClass: - return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mClass, select.getName()); + return !(mActor.get()->mBase->mClass == ESM::RefId::stringRefId(select.getName())); case SelectWrapper::Function_NotRace: - return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, select.getName()); + return !(mActor.get()->mBase->mRace == ESM::RefId::stringRefId(select.getName())); case SelectWrapper::Function_NotCell: - { - const std::string& actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); - return !(actorCell.length() >= select.getName().length() - && Misc::StringUtils::ciEqual(actorCell.substr(0, select.getName().length()), select.getName())); - } + { + std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); + return !Misc::StringUtils::ciStartsWith(actorCell, select.getName()); + } case SelectWrapper::Function_SameGender: - return (player.get()->mBase->mFlags & ESM::NPC::Female)== - (mActor.get()->mBase->mFlags & ESM::NPC::Female); + return (player.get()->mBase->mFlags & ESM::NPC::Female) + == (mActor.get()->mBase->mFlags & ESM::NPC::Female); case SelectWrapper::Function_SameRace: - return Misc::StringUtils::ciEqual(mActor.get()->mBase->mRace, player.get()->mBase->mRace); + return mActor.get()->mBase->mRace == player.get()->mBase->mRace; case SelectWrapper::Function_SameFaction: - return player.getClass().getNpcStats (player).isInFaction(mActor.getClass().getPrimaryFaction(mActor)); + return player.getClass().getNpcStats(player).isInFaction(mActor.getClass().getPrimaryFaction(mActor)); case SelectWrapper::Function_PcCommonDisease: - return player.getClass().getCreatureStats (player).hasCommonDisease(); + return player.getClass().getCreatureStats(player).hasCommonDisease(); case SelectWrapper::Function_PcBlightDisease: - return player.getClass().getCreatureStats (player).hasBlightDisease(); + return player.getClass().getCreatureStats(player).hasBlightDisease(); case SelectWrapper::Function_PcCorprus: - return player.getClass().getCreatureStats (player). - getMagicEffects().get (ESM::MagicEffect::Corprus).getMagnitude()!=0; + return player.getClass() + .getCreatureStats(player) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Corprus) + .getMagnitude() + != 0; case SelectWrapper::Function_PcExpelled: { - std::string faction = mActor.getClass().getPrimaryFaction(mActor); + const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) return false; @@ -534,8 +601,12 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_PcVampire: - return player.getClass().getCreatureStats(player).getMagicEffects(). - get(ESM::MagicEffect::Vampirism).getMagnitude() > 0; + return player.getClass() + .getCreatureStats(player) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Vampirism) + .getMagnitude() + > 0; case SelectWrapper::Function_TalkedToPc: @@ -543,7 +614,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_Alarmed: - return mActor.getClass().getCreatureStats (mActor).isAlarmed(); + return mActor.getClass().getCreatureStats(mActor).isAlarmed(); case SelectWrapper::Function_Detected: @@ -551,107 +622,92 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_Attacked: - return mActor.getClass().getCreatureStats (mActor).getAttacked(); + return mActor.getClass().getCreatureStats(mActor).getAttacked(); case SelectWrapper::Function_ShouldAttack: - return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, - MWMechanics::getPlayer()); + return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWMechanics::getPlayer()); case SelectWrapper::Function_Werewolf: - return mActor.getClass().getNpcStats (mActor).isWerewolf(); + return mActor.getClass().getNpcStats(mActor).isWerewolf(); default: - throw std::runtime_error ("unknown boolean select function"); + throw std::runtime_error("unknown boolean select function"); } } -int MWDialogue::Filter::getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const +int MWDialogue::Filter::getFactionRank(const MWWorld::Ptr& actor, const ESM::RefId& factionId) const { - MWMechanics::NpcStats& stats = actor.getClass().getNpcStats (actor); - - std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase(factionId)); - - if (iter==stats.getFactionRanks().end()) - return -1; - - return iter->second; + MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor); + return stats.getFactionRank(factionId); } -bool MWDialogue::Filter::hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, - const std::string& factionId, int rank) const +bool MWDialogue::Filter::hasFactionRankSkillRequirements( + const MWWorld::Ptr& actor, const ESM::RefId& factionId, int rank) const { - if (rank<0 || rank>=10) - throw std::runtime_error ("rank index out of range"); - - if (!actor.getClass().getNpcStats (actor).hasSkillsForRank (factionId, rank)) + if (!actor.getClass().getNpcStats(actor).hasSkillsForRank(factionId, rank)) return false; - const ESM::Faction& faction = - *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); + const ESM::Faction& faction = *MWBase::Environment::get().getESMStore()->get().find(factionId); - MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats (actor); + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - return stats.getAttribute (faction.mData.mAttribute[0]).getBase()>=faction.mData.mRankData[rank].mAttribute1 && - stats.getAttribute (faction.mData.mAttribute[1]).getBase()>=faction.mData.mRankData[rank].mAttribute2; + return stats.getAttribute(ESM::Attribute::AttributeID(faction.mData.mAttribute[0])).getBase() + >= faction.mData.mRankData[rank].mAttribute1 + && stats.getAttribute(ESM::Attribute::AttributeID(faction.mData.mAttribute[1])).getBase() + >= faction.mData.mRankData[rank].mAttribute2; } -bool MWDialogue::Filter::hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, - const std::string& factionId, int rank) const +bool MWDialogue::Filter::hasFactionRankReputationRequirements( + const MWWorld::Ptr& actor, const ESM::RefId& factionId, int rank) const { - if (rank<0 || rank>=10) - throw std::runtime_error ("rank index out of range"); + MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor); - MWMechanics::NpcStats& stats = actor.getClass().getNpcStats (actor); + const ESM::Faction& faction = *MWBase::Environment::get().getESMStore()->get().find(factionId); - const ESM::Faction& faction = - *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); - - return stats.getFactionReputation (factionId)>=faction.mData.mRankData[rank].mFactReaction; + return stats.getFactionReputation(factionId) >= faction.mData.mRankData.at(rank).mFactReaction; } -MWDialogue::Filter::Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer) -: mActor (actor), mChoice (choice), mTalkedToPlayer (talkedToPlayer) -{} - -const ESM::DialInfo* MWDialogue::Filter::search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const +MWDialogue::Filter::Filter(const MWWorld::Ptr& actor, int choice, bool talkedToPlayer) + : mActor(actor) + , mChoice(choice) + , mTalkedToPlayer(talkedToPlayer) { - std::vector suitableInfos = list (dialogue, fallbackToInfoRefusal, false); +} + +MWDialogue::Filter::Response MWDialogue::Filter::search( + const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const +{ + auto suitableInfos = list(dialogue, fallbackToInfoRefusal, false); if (suitableInfos.empty()) - return nullptr; + return {}; else return suitableInfos[0]; } -std::vector MWDialogue::Filter::listAll (const ESM::Dialogue& dialogue) const +bool MWDialogue::Filter::couldPotentiallyMatch(const ESM::DialInfo& info) const { - std::vector infos; - for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) - { - if (testActor (*iter)) - infos.push_back(&*iter); - } - return infos; + return testActor(info) && matchesStaticFilters(info, mActor); } -std::vector MWDialogue::Filter::list (const ESM::Dialogue& dialogue, - bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition) const +std::vector MWDialogue::Filter::list( + const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition) const { - std::vector infos; + std::vector infos; bool infoRefusal = false; // Iterate over topic responses to find a matching one - for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); - iter!=dialogue.mInfo.end(); ++iter) + for (const auto& info : dialogue.mInfo) { - if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter)) + if (testActor(info) && testPlayer(info) && testSelectStructs(info)) { - if (testDisposition (*iter, invertDisposition)) { - infos.push_back(&*iter); + if (testDisposition(info, invertDisposition)) + { + infos.emplace_back(&dialogue, &info); if (!searchAll) break; } @@ -665,15 +721,15 @@ std::vector MWDialogue::Filter::list (const ESM::Dialogue // No response is valid because of low NPC disposition, // search a response in the topic "Info Refusal" - const MWWorld::Store &dialogues = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& dialogues = MWBase::Environment::get().getESMStore()->get(); - const ESM::Dialogue& infoRefusalDialogue = *dialogues.find ("Info Refusal"); + const ESM::Dialogue& infoRefusalDialogue = *dialogues.find(ESM::RefId::stringRefId("Info Refusal")); - for (ESM::Dialogue::InfoContainer::const_iterator iter = infoRefusalDialogue.mInfo.begin(); - iter!=infoRefusalDialogue.mInfo.end(); ++iter) - if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter) && testDisposition(*iter, invertDisposition)) { - infos.push_back(&*iter); + for (const auto& info : infoRefusalDialogue.mInfo) + if (testActor(info) && testPlayer(info) && testSelectStructs(info) + && testDisposition(info, invertDisposition)) + { + infos.emplace_back(&infoRefusalDialogue, &info); if (!searchAll) break; } diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp index d2747d59a..c44122aa5 100644 --- a/apps/openmw/mwdialogue/filter.hpp +++ b/apps/openmw/mwdialogue/filter.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWDIALOGUE_FILTER_H #define GAME_MWDIALOGUE_FILTER_H +#include #include #include "../mwworld/ptr.hpp" @@ -9,6 +10,7 @@ namespace ESM { struct DialInfo; struct Dialogue; + class RefId; } namespace MWDialogue @@ -17,55 +19,56 @@ namespace MWDialogue class Filter { - MWWorld::Ptr mActor; - int mChoice; - bool mTalkedToPlayer; + MWWorld::Ptr mActor; + int mChoice; + bool mTalkedToPlayer; - bool testActor (const ESM::DialInfo& info) const; - ///< Is this the right actor for this \a info? + bool testActor(const ESM::DialInfo& info) const; + ///< Is this the right actor for this \a info? - bool testPlayer (const ESM::DialInfo& info) const; - ///< Do the player and the cell the player is currently in match \a info? + bool testPlayer(const ESM::DialInfo& info) const; + ///< Do the player and the cell the player is currently in match \a info? - bool testSelectStructs (const ESM::DialInfo& info) const; - ///< Are all select structs matching? + bool testSelectStructs(const ESM::DialInfo& info) const; + ///< Are all select structs matching? - bool testDisposition (const ESM::DialInfo& info, bool invert=false) const; - ///< Is the actor disposition toward the player high enough (or low enough, if \a invert is true)? + bool testDisposition(const ESM::DialInfo& info, bool invert = false) const; + ///< Is the actor disposition toward the player high enough (or low enough, if \a invert is true)? - bool testFunctionLocal(const SelectWrapper& select) const; + bool testFunctionLocal(const SelectWrapper& select) const; - bool testSelectStruct (const SelectWrapper& select) const; + bool testSelectStruct(const SelectWrapper& select) const; - bool testSelectStructNumeric (const SelectWrapper& select) const; + bool testSelectStructNumeric(const SelectWrapper& select) const; - int getSelectStructInteger (const SelectWrapper& select) const; + int getSelectStructInteger(const SelectWrapper& select) const; - bool getSelectStructBoolean (const SelectWrapper& select) const; + bool getSelectStructBoolean(const SelectWrapper& select) const; - int getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const; + int getFactionRank(const MWWorld::Ptr& actor, const ESM::RefId& factionId) const; - bool hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, const std::string& factionId, - int rank) const; + bool hasFactionRankSkillRequirements(const MWWorld::Ptr& actor, const ESM::RefId& factionId, int rank) const; - bool hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, const std::string& factionId, - int rank) const; + bool hasFactionRankReputationRequirements( + const MWWorld::Ptr& actor, const ESM::RefId& factionId, int rank) const; - public: + public: + using Response = std::pair; - Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer); + Filter(const MWWorld::Ptr& actor, int choice, bool talkedToPlayer); - std::vector list (const ESM::Dialogue& dialogue, - bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition=false) const; - ///< List all infos that could be used on the given actor, using the current runtime state of the actor. - /// \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue. + std::vector list(const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, + bool invertDisposition = false) const; + ///< List all infos that could be used on the given actor, using the current runtime state of the actor. + /// \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue. - std::vector listAll (const ESM::Dialogue& dialogue) const; - ///< List all infos that could possibly be used on the given actor, ignoring runtime state filters and ignoring player filters. + bool couldPotentiallyMatch(const ESM::DialInfo& info) const; + ///< Check if this INFO could potentially be said by the given actor, ignoring runtime state filters and + ///< ignoring player filters. - const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; - ///< Get a matching response for the requested dialogue. - /// Redirect to "Info Refusal" topic if a response fulfills all conditions but disposition. + Response search(const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; + ///< Get a matching response for the requested dialogue. + /// Redirect to "Info Refusal" topic if a response fulfills all conditions but disposition. }; } diff --git a/apps/openmw/mwdialogue/hypertextparser.cpp b/apps/openmw/mwdialogue/hypertextparser.cpp index fa7de97d2..11a2de77b 100644 --- a/apps/openmw/mwdialogue/hypertextparser.cpp +++ b/apps/openmw/mwdialogue/hypertextparser.cpp @@ -1,10 +1,10 @@ -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/store.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/store.hpp" #include "keywordsearch.hpp" @@ -14,11 +14,11 @@ namespace MWDialogue { namespace HyperTextParser { - std::vector parseHyperText(const std::string & text) + std::vector parseHyperText(const std::string& text) { std::vector result; size_t pos_end = std::string::npos, iteration_pos = 0; - for(;;) + for (;;) { size_t pos_begin = text.find('@', iteration_pos); if (pos_begin != std::string::npos) @@ -45,40 +45,30 @@ namespace MWDialogue return result; } - void tokenizeKeywords(const std::string & text, std::vector & tokens) + void tokenizeKeywords(const std::string& text, std::vector& tokens) { - const MWWorld::Store & dialogs = - MWBase::Environment::get().getWorld()->getStore().get(); + const auto& keywordSearch + = MWBase::Environment::get().getESMStore()->get().getDialogIdKeywordSearch(); - std::list keywordList; - for (MWWorld::Store::iterator it = dialogs.begin(); it != dialogs.end(); ++it) - keywordList.push_back(Misc::StringUtils::lowerCase(it->mId)); - keywordList.sort(Misc::StringUtils::ciLess); - - KeywordSearch keywordSearch; - - for (std::list::const_iterator it = keywordList.begin(); it != keywordList.end(); ++it) - keywordSearch.seed(*it, 0 /*unused*/); - - std::vector::Match> matches; + std::vector::Match> matches; keywordSearch.highlightKeywords(text.begin(), text.end(), matches); - for (std::vector::Match>::const_iterator it = matches.begin(); it != matches.end(); ++it) + for (const auto& match : matches) { - tokens.emplace_back(std::string(it->mBeg, it->mEnd), Token::ImplicitKeyword); + tokens.emplace_back(std::string(match.mBeg, match.mEnd), Token::ImplicitKeyword); } } - size_t removePseudoAsterisks(std::string & phrase) + size_t removePseudoAsterisks(std::string& phrase) { size_t pseudoAsterisksCount = 0; - if( !phrase.empty() ) + if (!phrase.empty()) { std::string::reverse_iterator rit = phrase.rbegin(); const char specialPseudoAsteriskCharacter = 127; - while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) + while (rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter) { pseudoAsterisksCount++; ++rit; diff --git a/apps/openmw/mwdialogue/hypertextparser.hpp b/apps/openmw/mwdialogue/hypertextparser.hpp index 4ae0474c4..efd0982de 100644 --- a/apps/openmw/mwdialogue/hypertextparser.hpp +++ b/apps/openmw/mwdialogue/hypertextparser.hpp @@ -16,7 +16,11 @@ namespace MWDialogue ImplicitKeyword }; - Token(const std::string & text, Type type) : mText(text), mType(type) {} + Token(const std::string& text, Type type) + : mText(text) + , mType(type) + { + } bool isExplicitLink() { return mType == ExplicitLink; } @@ -26,9 +30,9 @@ namespace MWDialogue // In translations (at least Russian) the links are marked with @#, so // it should be a function to parse it - std::vector parseHyperText(const std::string & text); - void tokenizeKeywords(const std::string & text, std::vector & tokens); - size_t removePseudoAsterisks(std::string & phrase); + std::vector parseHyperText(const std::string& text); + void tokenizeKeywords(const std::string& text, std::vector& tokens); + size_t removePseudoAsterisks(std::string& phrase); } } diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 5eab6d5ca..c691e0d46 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include @@ -10,22 +10,19 @@ #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/globals.hpp" #include "../mwscript/interpretercontext.hpp" - namespace MWDialogue { - Entry::Entry() {} - - Entry::Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) - : mInfoId (infoId) + Entry::Entry(const ESM::RefId& topic, const ESM::RefId& infoId, const MWWorld::Ptr& actor) + : mInfoId(infoId) { - const ESM::Dialogue *dialogue = - MWBase::Environment::get().getWorld()->getStore().get().find (topic); + const ESM::Dialogue* dialogue = MWBase::Environment::get().getESMStore()->get().find(topic); - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) + for (ESM::Dialogue::InfoContainer::const_iterator iter(dialogue->mInfo.begin()); iter != dialogue->mInfo.end(); + ++iter) if (iter->mId == mInfoId) { if (actor.isEmpty()) @@ -35,96 +32,111 @@ namespace MWDialogue } else { - MWScript::InterpreterContext interpreterContext(&actor.getRefData().getLocals(),actor); + MWScript::InterpreterContext interpreterContext(&actor.getRefData().getLocals(), actor); mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); } return; } - throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + topic); + throw std::runtime_error("unknown info ID " + mInfoId.toDebugString() + " for topic " + topic.toDebugString()); } - Entry::Entry (const ESM::JournalEntry& record) : mInfoId (record.mInfo), mText (record.mText), mActorName(record.mActorName) {} + Entry::Entry(const ESM::JournalEntry& record) + : mInfoId(record.mInfo) + , mText(record.mText) + , mActorName(record.mActorName) + { + } - std::string Entry::getText() const + const std::string& Entry::getText() const { return mText; } - void Entry::write (ESM::JournalEntry& entry) const + void Entry::write(ESM::JournalEntry& entry) const { entry.mInfo = mInfoId; entry.mText = mText; entry.mActorName = mActorName; } - - JournalEntry::JournalEntry() {} - - JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) - : Entry (topic, infoId, actor), mTopic (topic) - {} - - JournalEntry::JournalEntry (const ESM::JournalEntry& record) - : Entry (record), mTopic (record.mTopic) - {} - - void JournalEntry::write (ESM::JournalEntry& entry) const + JournalEntry::JournalEntry(const ESM::RefId& topic, const ESM::RefId& infoId, const MWWorld::Ptr& actor) + : Entry(topic, infoId, actor) + , mTopic(topic) { - Entry::write (entry); + } + + JournalEntry::JournalEntry(const ESM::JournalEntry& record) + : Entry(record) + , mTopic(record.mTopic) + { + } + + void JournalEntry::write(ESM::JournalEntry& entry) const + { + Entry::write(entry); entry.mTopic = mTopic; } - JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index) + JournalEntry JournalEntry::makeFromQuest(const ESM::RefId& topic, int index) { - return JournalEntry (topic, idFromIndex (topic, index), MWWorld::Ptr()); + return JournalEntry(topic, idFromIndex(topic, index), MWWorld::Ptr()); } - std::string JournalEntry::idFromIndex (const std::string& topic, int index) + const ESM::RefId& JournalEntry::idFromIndex(const ESM::RefId& topic, int index) { - const ESM::Dialogue *dialogue = - MWBase::Environment::get().getWorld()->getStore().get().find (topic); + const ESM::Dialogue* dialogue = MWBase::Environment::get().getESMStore()->get().find(topic); - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) - if (iter->mData.mJournalIndex==index) + for (ESM::Dialogue::InfoContainer::const_iterator iter(dialogue->mInfo.begin()); iter != dialogue->mInfo.end(); + ++iter) + if (iter->mData.mJournalIndex == index) { return iter->mId; } - throw std::runtime_error ("unknown journal index for topic " + topic); + throw std::runtime_error("unknown journal index for topic " + topic.toDebugString()); } - StampedJournalEntry::StampedJournalEntry() - : mDay (0), mMonth (0), mDayOfMonth (0) - {} - - StampedJournalEntry::StampedJournalEntry (const std::string& topic, const std::string& infoId, - int day, int month, int dayOfMonth, const MWWorld::Ptr& actor) - : JournalEntry (topic, infoId, actor), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) - {} - - StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) - : JournalEntry (record), mDay (record.mDay), mMonth (record.mMonth), - mDayOfMonth (record.mDayOfMonth) - {} - - void StampedJournalEntry::write (ESM::JournalEntry& entry) const + : mDay(0) + , mMonth(0) + , mDayOfMonth(0) { - JournalEntry::write (entry); + } + + StampedJournalEntry::StampedJournalEntry(const ESM::RefId& topic, const ESM::RefId& infoId, int day, int month, + int dayOfMonth, const MWWorld::Ptr& actor) + : JournalEntry(topic, infoId, actor) + , mDay(day) + , mMonth(month) + , mDayOfMonth(dayOfMonth) + { + } + + StampedJournalEntry::StampedJournalEntry(const ESM::JournalEntry& record) + : JournalEntry(record) + , mDay(record.mDay) + , mMonth(record.mMonth) + , mDayOfMonth(record.mDayOfMonth) + { + } + + void StampedJournalEntry::write(ESM::JournalEntry& entry) const + { + JournalEntry::write(entry); entry.mDay = mDay; entry.mMonth = mMonth; entry.mDayOfMonth = mDayOfMonth; } - StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index, const MWWorld::Ptr& actor) + StampedJournalEntry StampedJournalEntry::makeFromQuest( + const ESM::RefId& topic, int index, const MWWorld::Ptr& actor) { - int day = MWBase::Environment::get().getWorld()->getGlobalInt ("dayspassed"); - int month = MWBase::Environment::get().getWorld()->getGlobalInt ("month"); - int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalInt ("day"); + const int day = MWBase::Environment::get().getWorld()->getGlobalInt(MWWorld::Globals::sDaysPassed); + const int month = MWBase::Environment::get().getWorld()->getGlobalInt(MWWorld::Globals::sMonth); + const int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalInt(MWWorld::Globals::sDay); - return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth, actor); + return StampedJournalEntry(topic, idFromIndex(topic, index), day, month, dayOfMonth, actor); } } diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp index 8711ab53a..34decf0dd 100644 --- a/apps/openmw/mwdialogue/journalentry.hpp +++ b/apps/openmw/mwdialogue/journalentry.hpp @@ -1,7 +1,9 @@ #ifndef GAME_MWDIALOGUE_JOURNALENTRY_H #define GAME_MWDIALOGUE_JOURNALENTRY_H +#include #include +#include namespace ESM { @@ -18,20 +20,20 @@ namespace MWDialogue /// \brief Basic quest/dialogue/topic entry struct Entry { - std::string mInfoId; + ESM::RefId mInfoId; std::string mText; std::string mActorName; // optional - Entry(); + Entry() = default; /// actor is optional - Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); + Entry(const ESM::RefId& topic, const ESM::RefId& infoId, const MWWorld::Ptr& actor); - Entry (const ESM::JournalEntry& record); + Entry(const ESM::JournalEntry& record); - std::string getText() const; + const std::string& getText() const; - void write (ESM::JournalEntry& entry) const; + void write(ESM::JournalEntry& entry) const; }; /// \brief A dialogue entry @@ -39,19 +41,19 @@ namespace MWDialogue /// Same as entry, but store TopicID struct JournalEntry : public Entry { - std::string mTopic; + ESM::RefId mTopic; - JournalEntry(); + JournalEntry() = default; - JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); + JournalEntry(const ESM::RefId& topic, const ESM::RefId& infoId, const MWWorld::Ptr& actor); - JournalEntry (const ESM::JournalEntry& record); + JournalEntry(const ESM::JournalEntry& record); - void write (ESM::JournalEntry& entry) const; + void write(ESM::JournalEntry& entry) const; - static JournalEntry makeFromQuest (const std::string& topic, int index); + static JournalEntry makeFromQuest(const ESM::RefId& topic, int index); - static std::string idFromIndex (const std::string& topic, int index); + static const ESM::RefId& idFromIndex(const ESM::RefId& topic, int index); }; /// \brief A quest entry with a timestamp. @@ -63,14 +65,14 @@ namespace MWDialogue StampedJournalEntry(); - StampedJournalEntry (const std::string& topic, const std::string& infoId, - int day, int month, int dayOfMonth, const MWWorld::Ptr& actor); + StampedJournalEntry(const ESM::RefId& topic, const ESM::RefId& infoId, int day, int month, int dayOfMonth, + const MWWorld::Ptr& actor); - StampedJournalEntry (const ESM::JournalEntry& record); + StampedJournalEntry(const ESM::JournalEntry& record); - void write (ESM::JournalEntry& entry) const; + void write(ESM::JournalEntry& entry) const; - static StampedJournalEntry makeFromQuest (const std::string& topic, int index, const MWWorld::Ptr& actor); + static StampedJournalEntry makeFromQuest(const ESM::RefId& topic, int index, const MWWorld::Ptr& actor); }; } diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index aa5dfac56..15e741b56 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -2,30 +2,29 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include + +#include -#include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" - -#include "../mwgui/messagebox.hpp" +#include "../mwbase/world.hpp" namespace MWDialogue { - Quest& Journal::getQuest (const std::string& id) + Quest& Journal::getQuest(const ESM::RefId& id) { - TQuestContainer::iterator iter = mQuests.find (id); + TQuestContainer::iterator iter = mQuests.find(id); - if (iter==mQuests.end()) + if (iter == mQuests.end()) { - std::pair result = - mQuests.insert (std::make_pair (id, Quest (id))); + std::pair result = mQuests.insert(std::make_pair(id, Quest(id))); iter = result.first; } @@ -33,14 +32,13 @@ namespace MWDialogue return iter->second; } - Topic& Journal::getTopic (const std::string& id) + Topic& Journal::getTopic(const ESM::RefId& id) { - TTopicContainer::iterator iter = mTopics.find (id); + TTopicContainer::iterator iter = mTopics.find(id); - if (iter==mTopics.end()) + if (iter == mTopics.end()) { - std::pair result - = mTopics.insert (std::make_pair (id, Topic (id))); + std::pair result = mTopics.insert(std::make_pair(id, Topic(id))); iter = result.first; } @@ -48,16 +46,16 @@ namespace MWDialogue return iter->second; } - bool Journal::isThere (const std::string& topicId, const std::string& infoId) const + bool Journal::isThere(const ESM::RefId& topicId, const ESM::RefId& infoId) const { - if (const ESM::Dialogue *dialogue = - MWBase::Environment::get().getWorld()->getStore().get().search (topicId)) + if (const ESM::Dialogue* dialogue + = MWBase::Environment::get().getESMStore()->get().search(topicId)) { if (infoId.empty()) return true; - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) + for (ESM::Dialogue::InfoContainer::const_iterator iter(dialogue->mInfo.begin()); + iter != dialogue->mInfo.end(); ++iter) if (iter->mId == infoId) return true; } @@ -65,8 +63,7 @@ namespace MWDialogue return false; } - Journal::Journal() - {} + Journal::Journal() {} void Journal::clear() { @@ -75,6 +72,7 @@ namespace MWDialogue mTopics.clear(); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -103,22 +101,26 @@ namespace MWDialogue /* End of tes3mp change (major) */ +======= + void Journal::addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { // bail out if we already have heard this... - std::string infoId = JournalEntry::idFromIndex (id, index); - for (TEntryIter i = mJournal.begin (); i != mJournal.end (); ++i) + const ESM::RefId& infoId = JournalEntry::idFromIndex(id, index); + for (TEntryIter i = mJournal.begin(); i != mJournal.end(); ++i) 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; } - StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor); + StampedJournalEntry entry = StampedJournalEntry::makeFromQuest(id, index, actor); +<<<<<<< HEAD /* Start of tes3mp addition @@ -136,34 +138,47 @@ namespace MWDialogue Quest& quest = getQuest (id); quest.addEntry (entry); // we are doing slicing on purpose here +======= + Quest& quest = getQuest(id); + if (quest.addEntry(entry)) // we are doing slicing on purpose here + { + // Restart all "other" quests with the same name as well + std::string_view name = quest.getName(); + for (auto& it : mQuests) + { + if (it.second.isFinished() && Misc::StringUtils::ciEqual(it.second.getName(), name)) + it.second.setFinished(false); + } + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 // there is no need to show empty entries in journal if (!entry.getText().empty()) { - mJournal.push_back (entry); - MWBase::Environment::get().getWindowManager()->messageBox ("#{sJournalEntry}"); + mJournal.push_back(entry); + MWBase::Environment::get().getWindowManager()->messageBox("#{sJournalEntry}"); } } - void Journal::setJournalIndex (const std::string& id, int index) + void Journal::setJournalIndex(const ESM::RefId& id, int index) { - Quest& quest = getQuest (id); + Quest& quest = getQuest(id); - quest.setIndex (index); + quest.setIndex(index); } - void Journal::addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) + void Journal::addTopic(const ESM::RefId& topicId, const ESM::RefId& infoId, const MWWorld::Ptr& actor) { - Topic& topic = getTopic (topicId); + Topic& topic = getTopic(topicId); JournalEntry entry(topicId, infoId, actor); entry.mActorName = actor.getClass().getName(actor); - topic.addEntry (entry); + topic.addEntry(entry); } - void Journal::removeLastAddedTopicResponse(const std::string &topicId, const std::string &actorName) + void Journal::removeLastAddedTopicResponse(const ESM::RefId& topicId, std::string_view actorName) { - Topic& topic = getTopic (topicId); + Topic& topic = getTopic(topicId); topic.removeLastAddedResponse(actorName); @@ -171,11 +186,11 @@ namespace MWDialogue mTopics.erase(mTopics.find(topicId)); // All responses removed -> remove topic } - int Journal::getJournalIndex (const std::string& id) const + int Journal::getJournalIndex(const ESM::RefId& id) const { - TQuestContainer::const_iterator iter = mQuests.find (id); + TQuestContainer::const_iterator iter = mQuests.find(id); - if (iter==mQuests.end()) + if (iter == mQuests.end()) return 0; return iter->second.getIndex(); @@ -213,104 +228,105 @@ namespace MWDialogue int Journal::countSavedGameRecords() const { - int count = static_cast (mQuests.size()); + int count = static_cast(mQuests.size()); - for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) - count += std::distance (iter->second.begin(), iter->second.end()); + for (TQuestIter iter(mQuests.begin()); iter != mQuests.end(); ++iter) + count += std::distance(iter->second.begin(), iter->second.end()); - count += std::distance (mJournal.begin(), mJournal.end()); + count += std::distance(mJournal.begin(), mJournal.end()); - for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) - count += std::distance (iter->second.begin(), iter->second.end()); + for (TTopicIter iter(mTopics.begin()); iter != mTopics.end(); ++iter) + count += std::distance(iter->second.begin(), iter->second.end()); return count; } - void Journal::write (ESM::ESMWriter& writer, Loading::Listener& progress) const + void Journal::write(ESM::ESMWriter& writer, Loading::Listener& progress) const { - for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) + for (TQuestIter iter(mQuests.begin()); iter != mQuests.end(); ++iter) { const Quest& quest = iter->second; ESM::QuestState state; - quest.write (state); - writer.startRecord (ESM::REC_QUES); - state.save (writer); - writer.endRecord (ESM::REC_QUES); + quest.write(state); + writer.startRecord(ESM::REC_QUES); + state.save(writer); + writer.endRecord(ESM::REC_QUES); - for (Topic::TEntryIter entryIter (quest.begin()); entryIter!=quest.end(); ++entryIter) + for (Topic::TEntryIter entryIter(quest.begin()); entryIter != quest.end(); ++entryIter) { ESM::JournalEntry entry; entry.mType = ESM::JournalEntry::Type_Quest; entry.mTopic = quest.getTopic(); - entryIter->write (entry); - writer.startRecord (ESM::REC_JOUR); - entry.save (writer); - writer.endRecord (ESM::REC_JOUR); + entryIter->write(entry); + writer.startRecord(ESM::REC_JOUR); + entry.save(writer); + writer.endRecord(ESM::REC_JOUR); } } - for (TEntryIter iter (mJournal.begin()); iter!=mJournal.end(); ++iter) + for (TEntryIter iter(mJournal.begin()); iter != mJournal.end(); ++iter) { ESM::JournalEntry entry; entry.mType = ESM::JournalEntry::Type_Journal; - iter->write (entry); - writer.startRecord (ESM::REC_JOUR); - entry.save (writer); - writer.endRecord (ESM::REC_JOUR); + iter->write(entry); + writer.startRecord(ESM::REC_JOUR); + entry.save(writer); + writer.endRecord(ESM::REC_JOUR); } - for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) + for (TTopicIter iter(mTopics.begin()); iter != mTopics.end(); ++iter) { const Topic& topic = iter->second; - for (Topic::TEntryIter entryIter (topic.begin()); entryIter!=topic.end(); ++entryIter) + for (Topic::TEntryIter entryIter(topic.begin()); entryIter != topic.end(); ++entryIter) { ESM::JournalEntry entry; entry.mType = ESM::JournalEntry::Type_Topic; entry.mTopic = topic.getTopic(); - entryIter->write (entry); - writer.startRecord (ESM::REC_JOUR); - entry.save (writer); - writer.endRecord (ESM::REC_JOUR); + entryIter->write(entry); + writer.startRecord(ESM::REC_JOUR); + entry.save(writer); + writer.endRecord(ESM::REC_JOUR); } } } - void Journal::readRecord (ESM::ESMReader& reader, uint32_t type) + void Journal::readRecord(ESM::ESMReader& reader, uint32_t type) { - if (type==ESM::REC_JOUR || type==ESM::REC_JOUR_LEGACY) + if (type == ESM::REC_JOUR || type == ESM::REC_JOUR_LEGACY) { ESM::JournalEntry record; - record.load (reader); + record.load(reader); - if (isThere (record.mTopic, record.mInfo)) + if (isThere(record.mTopic, record.mInfo)) switch (record.mType) { case ESM::JournalEntry::Type_Quest: - getQuest (record.mTopic).insertEntry (record); + getQuest(record.mTopic).insertEntry(record); break; case ESM::JournalEntry::Type_Journal: - mJournal.push_back (record); + mJournal.push_back(record); break; case ESM::JournalEntry::Type_Topic: - getTopic (record.mTopic).insertEntry (record); + getTopic(record.mTopic).insertEntry(record); break; } } - else if (type==ESM::REC_QUES) + else if (type == ESM::REC_QUES) { ESM::QuestState record; - record.load (reader); + record.load(reader); - if (isThere (record.mTopic)) + if (isThere(record.mTopic)) { - std::pair result = mQuests.insert (std::make_pair (record.mTopic, record)); + std::pair result + = mQuests.insert(std::make_pair(record.mTopic, record)); // reapply quest index, this is to handle users upgrading from only // Morrowind.esm (no quest states) to Morrowind.esm + Tribunal.esm result.first->second.setIndex(record.mState); diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index e2bea48de..5482d50e9 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -10,24 +10,30 @@ namespace MWDialogue /// \brief The player's journal class Journal : public MWBase::Journal { - TEntryContainer mJournal; - TQuestContainer mQuests; - TTopicContainer mTopics; + TEntryContainer mJournal; + TQuestContainer mQuests; + TTopicContainer mTopics; - private: + private: + Quest& getQuest(const ESM::RefId& id); - Quest& getQuest (const std::string& id); + Topic& getTopic(const ESM::RefId& id); - Topic& getTopic (const std::string& id); + bool isThere(const ESM::RefId& topicId, const ESM::RefId& infoId = ESM::RefId()) const; - bool isThere (const std::string& topicId, const std::string& infoId = "") const; + public: + Journal(); - public: + void clear() override; - Journal(); + void addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor) override; + ///< Add a journal entry. + /// @param actor Used as context for replacing of escape sequences (%name, etc). - void clear() override; + void setJournalIndex(const ESM::RefId& id, int index) override; + ///< Set the journal index without adding an entry. +<<<<<<< HEAD /* Start of tes3mp addition @@ -50,47 +56,45 @@ namespace MWDialogue /* End of tes3mp change (major) */ +======= + int getJournalIndex(const ESM::RefId& id) const override; + ///< Get the journal index. +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - void setJournalIndex (const std::string& id, int index) override; - ///< Set the journal index without adding an entry. + void addTopic(const ESM::RefId& topicId, const ESM::RefId& infoId, const MWWorld::Ptr& actor) override; + /// \note topicId must be lowercase - int getJournalIndex (const std::string& id) const override; - ///< Get the journal index. + void removeLastAddedTopicResponse(const ESM::RefId& topicId, std::string_view actorName) override; + ///< Removes the last topic response added for the given topicId and actor name. + /// \note topicId must be lowercase - void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) override; - /// \note topicId must be lowercase + TEntryIter begin() const override; + ///< Iterator pointing to the begin of the main journal. + /// + /// \note Iterators to main journal entries will never become invalid. - void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName) override; - ///< Removes the last topic response added for the given topicId and actor name. - /// \note topicId must be lowercase + TEntryIter end() const override; + ///< Iterator pointing past the end of the main journal. - TEntryIter begin() const override; - ///< Iterator pointing to the begin of the main journal. - /// - /// \note Iterators to main journal entries will never become invalid. + TQuestIter questBegin() const override; + ///< Iterator pointing to the first quest (sorted by topic ID) - TEntryIter end() const override; - ///< Iterator pointing past the end of the main journal. + TQuestIter questEnd() const override; + ///< Iterator pointing past the last quest. - TQuestIter questBegin() const override; - ///< Iterator pointing to the first quest (sorted by topic ID) + TTopicIter topicBegin() const override; + ///< Iterator pointing to the first topic (sorted by topic ID) + /// + /// \note The topic ID is identical with the user-visible topic string. - TQuestIter questEnd() const override; - ///< Iterator pointing past the last quest. + TTopicIter topicEnd() const override; + ///< Iterator pointing past the last topic. - TTopicIter topicBegin() const override; - ///< Iterator pointing to the first topic (sorted by topic ID) - /// - /// \note The topic ID is identical with the user-visible topic string. + int countSavedGameRecords() const override; - TTopicIter topicEnd() const override; - ///< Iterator pointing past the last topic. + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override; - int countSavedGameRecords() const override; - - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const override; - - void readRecord (ESM::ESMReader& reader, uint32_t type) override; + void readRecord(ESM::ESMReader& reader, uint32_t type) override; }; } diff --git a/apps/openmw/mwdialogue/keywordsearch.cpp b/apps/openmw/mwdialogue/keywordsearch.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index f296f223f..3b784cd59 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -1,256 +1,241 @@ #ifndef GAME_MWDIALOGUE_KEYWORDSEARCH_H #define GAME_MWDIALOGUE_KEYWORDSEARCH_H -#include +#include // std::reverse #include +#include #include #include -#include // std::reverse -#include +#include +#include namespace MWDialogue { -template -class KeywordSearch -{ -public: - - typedef typename string_t::const_iterator Point; - - struct Match + template + class KeywordSearch { - Point mBeg; - Point mEnd; - value_t mValue; - }; + public: + using Point = std::string::const_iterator; - void seed (string_t keyword, value_t value) - { - if (keyword.empty()) - return; - seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), 0, mRoot); - } - - void clear () - { - mRoot.mChildren.clear (); - mRoot.mKeyword.clear (); - } - - bool containsKeyword (string_t keyword, value_t& value) - { - typename Entry::childen_t::iterator current; - typename Entry::childen_t::iterator next; - - current = mRoot.mChildren.find (Misc::StringUtils::toLower (*keyword.begin())); - if (current == mRoot.mChildren.end()) - return false; - else if (current->second.mKeyword.size() && Misc::StringUtils::ciEqual(current->second.mKeyword, keyword)) + struct Match { - value = current->second.mValue; - return true; + Point mBeg; + Point mEnd; + value_t mValue; + }; + + void seed(std::string_view keyword, value_t value) + { + if (keyword.empty()) + return; + seed_impl(keyword, std::move(value), 0, mRoot); } - for (Point i = ++keyword.begin(); i != keyword.end(); ++i) + void clear() { - next = current->second.mChildren.find(Misc::StringUtils::toLower (*i)); - if (next == current->second.mChildren.end()) + mRoot.mChildren.clear(); + mRoot.mKeyword.clear(); + } + + bool containsKeyword(std::string_view keyword, value_t& value) + { + auto it = keyword.begin(); + auto current = mRoot.mChildren.find(Misc::StringUtils::toLower(*it)); + if (current == mRoot.mChildren.end()) return false; - if (Misc::StringUtils::ciEqual(next->second.mKeyword, keyword)) + else if (Misc::StringUtils::ciEqual(current->second.mKeyword, keyword)) { - value = next->second.mValue; + value = current->second.mValue; return true; } - current = next; - } - return false; - } - static bool sortMatches(const Match& left, const Match& right) - { - return left.mBeg < right.mBeg; - } - - void highlightKeywords (Point beg, Point end, std::vector& out) - { - std::vector matches; - for (Point i = beg; i != end; ++i) - { - // check if previous character marked start of new word - if (i != beg) + for (++it; it != keyword.end(); ++it) { - Point prev = i; - --prev; - if(isalpha(*prev)) - continue; - } - - - // check first character - typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); - - // no match, on to next character - if (candidate == mRoot.mChildren.end ()) - continue; - - // see how far the match goes - Point j = i; - - // some keywords might be longer variations of other keywords, so we definitely need a list of candidates - // the first element in the pair is length of the match, i.e. depth from the first character on - std::vector< typename std::pair > candidates; - - while ((j + 1) != end) - { - typename Entry::childen_t::iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); - - if (next == candidate->second.mChildren.end ()) + auto next = current->second.mChildren.find(Misc::StringUtils::toLower(*it)); + if (next == current->second.mChildren.end()) + return false; + if (Misc::StringUtils::ciEqual(next->second.mKeyword, keyword)) { + value = next->second.mValue; + return true; + } + current = next; + } + return false; + } + + static bool sortMatches(const Match& left, const Match& right) { return left.mBeg < right.mBeg; } + + void highlightKeywords(Point beg, Point end, std::vector& out) const + { + std::vector matches; + for (Point i = beg; i != end; ++i) + { + // check first character + typename Entry::childen_t::const_iterator candidate + = mRoot.mChildren.find(Misc::StringUtils::toLower(*i)); + + // no match, on to next character + if (candidate == mRoot.mChildren.end()) + continue; + + // see how far the match goes + Point j = i; + + // some keywords might be longer variations of other keywords, so we definitely need a list of + // candidates the first element in the pair is length of the match, i.e. depth from the first character + // on + std::vector> candidates; + + while ((j + 1) != end) + { + typename Entry::childen_t::const_iterator next + = candidate->second.mChildren.find(Misc::StringUtils::toLower(*++j)); + + if (next == candidate->second.mChildren.end()) + { + if (candidate->second.mKeyword.size() > 0) + candidates.push_back(std::make_pair((j - i), candidate)); + break; + } + + candidate = next; + if (candidate->second.mKeyword.size() > 0) - candidates.push_back(std::make_pair((j-i), candidate)); - break; + candidates.push_back(std::make_pair((j - i), candidate)); } - candidate = next; + if (candidates.empty()) + continue; // didn't match enough to disambiguate, on to next character - if (candidate->second.mKeyword.size() > 0) - candidates.push_back(std::make_pair((j-i), candidate)); + // shorter candidates will be added to the vector first. however, we want to check against longer + // candidates first + std::reverse(candidates.begin(), candidates.end()); + + for (const auto& [pos, c] : candidates) + { + candidate = c; + // try to match the rest of the keyword + Point k = i + pos; + Point t = candidate->second.mKeyword.begin() + (k - i); + + while (k != end && t != candidate->second.mKeyword.end()) + { + if (Misc::StringUtils::toLower(*k) != Misc::StringUtils::toLower(*t)) + break; + + ++k, ++t; + } + + // didn't match full keyword, try the next candidate + if (t != candidate->second.mKeyword.end()) + continue; + + // found a keyword, but there might still be longer keywords that start somewhere _within_ this + // keyword we will resolve these overlapping keywords later, choosing the longest one in case of + // conflict + Match match; + match.mValue = candidate->second.mValue; + match.mBeg = i; + match.mEnd = k; + matches.push_back(match); + break; + } } - if (candidates.empty()) - continue; // didn't match enough to disambiguate, on to next character - - // shorter candidates will be added to the vector first. however, we want to check against longer candidates first - std::reverse(candidates.begin(), candidates.end()); - - for (typename std::vector< std::pair >::iterator it = candidates.begin(); - it != candidates.end(); ++it) + // resolve overlapping keywords + while (!matches.empty()) { - candidate = it->second; - // try to match the rest of the keyword - Point k = i + it->first; - typename string_t::const_iterator t = candidate->second.mKeyword.begin () + (k - i); - - - while (k != end && t != candidate->second.mKeyword.end ()) + int longestKeywordSize = 0; + typename std::vector::iterator longestKeyword = matches.begin(); + for (typename std::vector::iterator it = matches.begin(); it != matches.end(); ++it) { - if (Misc::StringUtils::toLower (*k) != Misc::StringUtils::toLower (*t)) + int size = it->mEnd - it->mBeg; + if (size > longestKeywordSize) + { + longestKeywordSize = size; + longestKeyword = it; + } + + typename std::vector::iterator next = it; + ++next; + + if (next == matches.end()) break; - ++k, ++t; + if (it->mEnd <= next->mBeg) + { + break; // no overlap + } } - // didn't match full keyword, try the next candidate - if (t != candidate->second.mKeyword.end ()) - continue; - - // found a keyword, but there might still be longer keywords that start somewhere _within_ this keyword - // we will resolve these overlapping keywords later, choosing the longest one in case of conflict - Match match; - match.mValue = candidate->second.mValue; - match.mBeg = i; - match.mEnd = k; - matches.push_back(match); - break; + Match keyword = *longestKeyword; + matches.erase(longestKeyword); + out.push_back(keyword); + // erase anything that overlaps with the keyword we just added to the output + for (typename std::vector::iterator it = matches.begin(); it != matches.end();) + { + if (it->mBeg < keyword.mEnd && it->mEnd > keyword.mBeg) + it = matches.erase(it); + else + ++it; + } } + + std::sort(out.begin(), out.end(), sortMatches); } - // resolve overlapping keywords - while (!matches.empty()) + private: + struct Entry { - int longestKeywordSize = 0; - typename std::vector::iterator longestKeyword = matches.begin(); - for (typename std::vector::iterator it = matches.begin(); it != matches.end(); ++it) + typedef std::map childen_t; + + std::string mKeyword; + value_t mValue; + childen_t mChildren; + }; + + void seed_impl(std::string_view keyword, value_t value, size_t depth, Entry& entry) + { + int ch = Misc::StringUtils::toLower(keyword.at(depth)); + + typename Entry::childen_t::iterator j = entry.mChildren.find(ch); + + if (j == entry.mChildren.end()) { - int size = it->mEnd - it->mBeg; - if (size > longestKeywordSize) - { - longestKeywordSize = size; - longestKeyword = it; - } - - typename std::vector::iterator next = it; - ++next; - - if (next == matches.end()) - break; - - if (it->mEnd <= next->mBeg) - { - break; // no overlap - } + entry.mChildren[ch].mValue = std::move(value); + entry.mChildren[ch].mKeyword = keyword; } - - Match keyword = *longestKeyword; - matches.erase(longestKeyword); - out.push_back(keyword); - // erase anything that overlaps with the keyword we just added to the output - for (typename std::vector::iterator it = matches.begin(); it != matches.end();) + else { - if (it->mBeg < keyword.mEnd && it->mEnd > keyword.mBeg) - it = matches.erase(it); - else - ++it; + if (j->second.mKeyword.size() > 0) + { + if (keyword == j->second.mKeyword) + throw std::runtime_error("duplicate keyword inserted"); + + const auto& pushKeyword = j->second.mKeyword; + + if (depth >= pushKeyword.size()) + throw std::runtime_error("unexpected"); + + if (depth + 1 < pushKeyword.size()) + { + seed_impl(pushKeyword, j->second.mValue, depth + 1, j->second); + j->second.mKeyword.clear(); + } + } + if (depth + 1 == keyword.size()) + j->second.mKeyword = value; + else // depth+1 < keyword.size() + seed_impl(keyword, std::move(value), depth + 1, j->second); } } - std::sort(out.begin(), out.end(), sortMatches); - } - -private: - - struct Entry - { - typedef std::map childen_t; - - string_t mKeyword; - value_t mValue; - childen_t mChildren; + Entry mRoot; }; - void seed_impl (string_t keyword, value_t value, size_t depth, Entry & entry) - { - int ch = Misc::StringUtils::toLower (keyword.at (depth)); - - typename Entry::childen_t::iterator j = entry.mChildren.find (ch); - - if (j == entry.mChildren.end ()) - { - entry.mChildren [ch].mValue = /*std::move*/ (value); - entry.mChildren [ch].mKeyword = /*std::move*/ (keyword); - } - else - { - if (j->second.mKeyword.size () > 0) - { - if (keyword == j->second.mKeyword) - throw std::runtime_error ("duplicate keyword inserted"); - - value_t pushValue = /*std::move*/ (j->second.mValue); - string_t pushKeyword = /*std::move*/ (j->second.mKeyword); - - if (depth >= pushKeyword.size ()) - throw std::runtime_error ("unexpected"); - - if (depth+1 < pushKeyword.size()) - { - seed_impl (/*std::move*/ (pushKeyword), /*std::move*/ (pushValue), depth+1, j->second); - j->second.mKeyword.clear (); - } - } - if (depth+1 == keyword.size()) - j->second.mKeyword = value; - else // depth+1 < keyword.size() - seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), depth+1, j->second); - } - - } - - Entry mRoot; -}; - } #endif diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 5f20a8abb..0fdb91885 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,6 +1,8 @@ #include "quest.hpp" -#include +#include + +#include #include "../mwworld/esmstore.hpp" @@ -10,28 +12,36 @@ namespace MWDialogue { Quest::Quest() - : Topic(), mIndex (0), mFinished (false) - {} - - Quest::Quest (const std::string& topic) - : Topic (topic), mIndex (0), mFinished (false) - {} - - Quest::Quest (const ESM::QuestState& state) - : Topic (state.mTopic), mIndex (state.mState), mFinished (state.mFinished!=0) - {} - - std::string Quest::getName() const + : Topic() + , mIndex(0) + , mFinished(false) { - const ESM::Dialogue *dialogue = - MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); + } - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) - if (iter->mQuestStatus==ESM::DialInfo::QS_Name) + Quest::Quest(const ESM::RefId& topic) + : Topic(topic) + , mIndex(0) + , mFinished(false) + { + } + + Quest::Quest(const ESM::QuestState& state) + : Topic(state.mTopic) + , mIndex(state.mState) + , mFinished(state.mFinished != 0) + { + } + + std::string_view Quest::getName() const + { + const ESM::Dialogue* dialogue = MWBase::Environment::get().getESMStore()->get().find(mTopic); + + for (ESM::Dialogue::InfoContainer::const_iterator iter(dialogue->mInfo.begin()); iter != dialogue->mInfo.end(); + ++iter) + if (iter->mQuestStatus == ESM::DialInfo::QS_Name) return iter->mResponse; - return ""; + return {}; } int Quest::getIndex() const @@ -39,7 +49,7 @@ namespace MWDialogue return mIndex; } - void Quest::setIndex (int index) + void Quest::setIndex(int index) { // The index must be set even if no related journal entry was found mIndex = index; @@ -50,45 +60,37 @@ namespace MWDialogue return mFinished; } - void Quest::addEntry (const JournalEntry& entry) + void Quest::setFinished(bool finished) { - int index = -1; - - const ESM::Dialogue *dialogue = - MWBase::Environment::get().getWorld()->getStore().get().find (entry.mTopic); - - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) - if (iter->mId == entry.mInfoId) - { - index = iter->mData.mJournalIndex; - break; - } - - if (index==-1) - throw std::runtime_error ("unknown journal entry for topic " + mTopic); - - for (auto &info : dialogue->mInfo) - { - if (info.mData.mJournalIndex == index - && (info.mQuestStatus == ESM::DialInfo::QS_Finished || info.mQuestStatus == ESM::DialInfo::QS_Restart)) - { - mFinished = (info.mQuestStatus == ESM::DialInfo::QS_Finished); - break; - } - } - - if (index > mIndex) - mIndex = index; - - for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) - if (iter->mInfoId==entry.mInfoId) - return; - - mEntries.push_back (entry); // we want slicing here + mFinished = finished; } - void Quest::write (ESM::QuestState& state) const + bool Quest::addEntry(const JournalEntry& entry) + { + const ESM::Dialogue* dialogue + = MWBase::Environment::get().getESMStore()->get().find(entry.mTopic); + + auto info = std::find_if(dialogue->mInfo.begin(), dialogue->mInfo.end(), + [&](const auto& info) { return info.mId == entry.mInfoId; }); + + if (info == dialogue->mInfo.end() || info->mData.mJournalIndex == -1) + throw std::runtime_error("unknown journal entry for topic " + mTopic.toDebugString()); + + if (info->mQuestStatus == ESM::DialInfo::QS_Finished || info->mQuestStatus == ESM::DialInfo::QS_Restart) + mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished; + + if (info->mData.mJournalIndex > mIndex) + mIndex = info->mData.mJournalIndex; + + for (TEntryIter iter(mEntries.begin()); iter != mEntries.end(); ++iter) + if (iter->mInfoId == entry.mInfoId) + return info->mQuestStatus == ESM::DialInfo::QS_Restart; + + mEntries.push_back(entry); // we want slicing here + return info->mQuestStatus == ESM::DialInfo::QS_Restart; + } + + void Quest::write(ESM::QuestState& state) const { state.mTopic = getTopic(); state.mState = mIndex; diff --git a/apps/openmw/mwdialogue/quest.hpp b/apps/openmw/mwdialogue/quest.hpp index 712f94fae..1976c4679 100644 --- a/apps/openmw/mwdialogue/quest.hpp +++ b/apps/openmw/mwdialogue/quest.hpp @@ -13,33 +13,33 @@ namespace MWDialogue /// \brief A quest in progress or a completed quest class Quest : public Topic { - int mIndex; - bool mFinished; + int mIndex; + bool mFinished; - public: + public: + Quest(); - Quest(); + Quest(const ESM::RefId& topic); - Quest (const std::string& topic); + Quest(const ESM::QuestState& state); - Quest (const ESM::QuestState& state); + std::string_view getName() const override; + ///< May be an empty string - std::string getName() const override; - ///< May be an empty string + int getIndex() const; - int getIndex() const; + void setIndex(int index); + ///< Calling this function with a non-existent index will throw an exception. - void setIndex (int index); - ///< Calling this function with a non-existent index will throw an exception. + bool isFinished() const; + void setFinished(bool finished); - bool isFinished() const; + bool addEntry(const JournalEntry& entry) override; + ///< Add entry and adjust index accordingly. Returns true if the quest should be restarted. + /// + /// \note Redundant entries are ignored, but the index is still adjusted. - void addEntry (const JournalEntry& entry) override; - ///< Add entry and adjust index accordingly. - /// - /// \note Redundant entries are ignored, but the index is still adjusted. - - void write (ESM::QuestState& state) const; + void write(ESM::QuestState& state) const; }; } diff --git a/apps/openmw/mwdialogue/scripttest.cpp b/apps/openmw/mwdialogue/scripttest.cpp index c3d7b12c7..fd5a24071 100644 --- a/apps/openmw/mwdialogue/scripttest.cpp +++ b/apps/openmw/mwdialogue/scripttest.cpp @@ -1,122 +1,172 @@ #include "scripttest.hpp" -#include "../mwworld/manualref.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/manualref.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwscript/compilercontext.hpp" -#include #include -#include -#include #include +#include #include +#include +#include +#include +#include #include "filter.hpp" +#include +#include +#include + namespace { -void test(const MWWorld::Ptr& actor, int &compiled, int &total, const Compiler::Extensions* extensions, int warningsMode) -{ - MWDialogue::Filter filter(actor, 0, false); - - MWScript::CompilerContext compilerContext(MWScript::CompilerContext::Type_Dialogue); - compilerContext.setExtensions(extensions); - Compiler::StreamErrorHandler errorHandler; - errorHandler.setWarningsMode (warningsMode); - - const MWWorld::Store& dialogues = MWBase::Environment::get().getWorld()->getStore().get(); - for (MWWorld::Store::iterator it = dialogues.begin(); it != dialogues.end(); ++it) + bool test(const MWWorld::Ptr& actor, const ESM::DialInfo& info, int& compiled, int& total, + const Compiler::Extensions* extensions, const MWScript::CompilerContext& context, + Compiler::StreamErrorHandler& errorHandler) { - std::vector infos = filter.listAll(*it); - - for (std::vector::iterator iter = infos.begin(); iter != infos.end(); ++iter) + bool success = true; + ++total; + try { - const ESM::DialInfo* info = *iter; - if (!info->mResultScript.empty()) + std::istringstream input(info.mResultScript + "\n"); + + Compiler::Scanner scanner(errorHandler, input, extensions); + + Compiler::Locals locals; + + const ESM::RefId& actorScript = actor.getClass().getScript(actor); + if (!actorScript.empty()) { - bool success = true; - ++total; - try - { - errorHandler.reset(); - - std::istringstream input (info->mResultScript + "\n"); - - Compiler::Scanner scanner (errorHandler, input, extensions); - - Compiler::Locals locals; - - std::string actorScript = actor.getClass().getScript(actor); - - if (!actorScript.empty()) - { - // grab local variables from actor's script, if available. - locals = MWBase::Environment::get().getScriptManager()->getLocals (actorScript); - } - - Compiler::ScriptParser parser(errorHandler, compilerContext, locals, false); - - scanner.scan (parser); - - if (!errorHandler.isGood()) - success = false; - - ++compiled; - } - catch (const Compiler::SourceException& /* error */) - { - // error has already been reported via error handler - success = false; - } - catch (const std::exception& error) - { - Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); - success = false; - } - - if (!success) - { - Log(Debug::Error) << "Error: compiling failed (dialogue script): \n" << info->mResultScript << "\n"; - } + // grab local variables from actor's script, if available. + locals = MWBase::Environment::get().getScriptManager()->getLocals(actorScript); } + + Compiler::ScriptParser parser(errorHandler, context, locals, false); + + scanner.scan(parser); + + if (!errorHandler.isGood()) + success = false; + + ++compiled; } + catch (const Compiler::SourceException&) + { + // error has already been reported via error handler + success = false; + } + catch (const std::exception& error) + { + Log(Debug::Error) << "Dialogue error: An exception has been thrown: " << error.what(); + success = false; + } + + return success; + } + + bool superficiallyMatches(const MWWorld::Ptr& ptr, const ESM::DialInfo& info) + { + if (ptr.isEmpty()) + return false; + MWDialogue::Filter filter(ptr, 0, false); + return filter.couldPotentiallyMatch(info); } -} } namespace MWDialogue { -namespace ScriptTest -{ - - std::pair compileAll(const Compiler::Extensions *extensions, int warningsMode) + namespace ScriptTest { - int compiled = 0, total = 0; - const MWWorld::Store& npcs = MWBase::Environment::get().getWorld()->getStore().get(); - for (MWWorld::Store::iterator it = npcs.begin(); it != npcs.end(); ++it) + + std::pair compileAll(const Compiler::Extensions* extensions, int warningsMode) { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); - test(ref.getPtr(), compiled, total, extensions, warningsMode); + int compiled = 0, total = 0; + const auto& store = *MWBase::Environment::get().getESMStore(); + + MWScript::CompilerContext compilerContext(MWScript::CompilerContext::Type_Dialogue); + compilerContext.setExtensions(extensions); + Compiler::StreamErrorHandler errorHandler; + errorHandler.setWarningsMode(warningsMode); + + std::unique_ptr ref; + for (const ESM::Dialogue& topic : store.get()) + { + MWWorld::Ptr ptr; + for (const ESM::DialInfo& info : topic.mInfo) + { + if (info.mResultScript.empty()) + continue; + if (!info.mActor.empty()) + { + // We know it can only ever be this actor + try + { + ref = std::make_unique(store, info.mActor); + ptr = ref->getPtr(); + } + catch (const std::logic_error&) + { + // Too bad it doesn't exist + ptr = {}; + } + } + else + { + // Try to find a matching actor + if (!superficiallyMatches(ptr, info)) + { + ptr = {}; + bool found = false; + for (const auto& npc : store.get()) + { + ref = std::make_unique(store, npc.mId); + if (superficiallyMatches(ref->getPtr(), info)) + { + ptr = ref->getPtr(); + found = true; + break; + } + } + if (!found) + { + for (const auto& creature : store.get()) + { + ref = std::make_unique(store, creature.mId); + if (superficiallyMatches(ref->getPtr(), info)) + { + ptr = ref->getPtr(); + break; + } + } + } + } + } + if (ptr.isEmpty()) + Log(Debug::Error) << "Could not find an actor to test " << info.mId << " in " << topic.mId; + else + { + errorHandler.reset(); + errorHandler.setContext(info.mId.getRefIdString() + " in " + topic.mStringId); + if (!test(ptr, info, compiled, total, extensions, compilerContext, errorHandler)) + Log(Debug::Error) << "Test failed for " << info.mId << " in " << topic.mId << '\n' + << info.mResultScript; + } + } + } + + return std::make_pair(total, compiled); } - const MWWorld::Store& creatures = MWBase::Environment::get().getWorld()->getStore().get(); - for (MWWorld::Store::iterator it = creatures.begin(); it != creatures.end(); ++it) - { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); - test(ref.getPtr(), compiled, total, extensions, warningsMode); - } - return std::make_pair(total, compiled); } } - -} diff --git a/apps/openmw/mwdialogue/scripttest.hpp b/apps/openmw/mwdialogue/scripttest.hpp index 0ac259725..4b770ae12 100644 --- a/apps/openmw/mwdialogue/scripttest.hpp +++ b/apps/openmw/mwdialogue/scripttest.hpp @@ -6,14 +6,14 @@ namespace MWDialogue { -namespace ScriptTest -{ + namespace ScriptTest + { -/// Attempt to compile all dialogue scripts, use for verification purposes -/// @return A pair containing -std::pair compileAll(const Compiler::Extensions* extensions, int warningsMode); + /// Attempt to compile all dialogue scripts, use for verification purposes + /// @return A pair containing + std::pair compileAll(const Compiler::Extensions* extensions, int warningsMode); -} + } } diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 28dbd214b..94f7f7309 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -1,100 +1,179 @@ #include "selectwrapper.hpp" -#include -#include #include +#include +#include -#include +#include +#include namespace { - template - bool selectCompareImp (char comp, T1 value1, T2 value2) + template + bool selectCompareImp(char comp, T1 value1, T2 value2) { switch (comp) { - case '0': return value1==value2; - case '1': return value1!=value2; - case '2': return value1>value2; - case '3': return value1>=value2; - case '4': return value1 value2; + case '3': + return value1 >= value2; + case '4': + return value1 < value2; + case '5': + return value1 <= value2; } - throw std::runtime_error ("unknown compare type in dialogue info select"); + throw std::runtime_error("unknown compare type in dialogue info select"); } - template - bool selectCompareImp (const ESM::DialInfo::SelectStruct& select, T value1) + template + bool selectCompareImp(const ESM::DialInfo::SelectStruct& select, T value1) { - if (select.mValue.getType()==ESM::VT_Int) + if (select.mValue.getType() == ESM::VT_Int) { - return selectCompareImp (select.mSelectRule[4], value1, select.mValue.getInteger()); + return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getInteger()); } - else if (select.mValue.getType()==ESM::VT_Float) + else if (select.mValue.getType() == ESM::VT_Float) { - return selectCompareImp (select.mSelectRule[4], value1, select.mValue.getFloat()); + return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getFloat()); } else - throw std::runtime_error ( - "unsupported variable type in dialogue info select"); + throw std::runtime_error("unsupported variable type in dialogue info select"); } } MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::decodeFunction() const { - int index = 0; - - std::istringstream (mSelect.mSelectRule.substr(2,2)) >> index; + const int index = Misc::StringUtils::toNumeric(mSelect.mSelectRule.substr(2, 2), 0); switch (index) { - case 0: return Function_RankLow; - case 1: return Function_RankHigh; - case 2: return Function_RankRequirement; - case 3: return Function_Reputation; - case 4: return Function_HealthPercent; - case 5: return Function_PCReputation; - case 6: return Function_PcLevel; - case 7: return Function_PcHealthPercent; - case 8: case 9: return Function_PcDynamicStat; - case 10: return Function_PcAttribute; - case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: - case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: - case 31: case 32: case 33: case 34: case 35: case 36: case 37: return Function_PcSkill; - case 38: return Function_PcGender; - case 39: return Function_PcExpelled; - case 40: return Function_PcCommonDisease; - case 41: return Function_PcBlightDisease; - case 42: return Function_PcClothingModifier; - case 43: return Function_PcCrimeLevel; - case 44: return Function_SameGender; - case 45: return Function_SameRace; - case 46: return Function_SameFaction; - case 47: return Function_FactionRankDiff; - case 48: return Function_Detected; - case 49: return Function_Alarmed; - case 50: return Function_Choice; - case 51: case 52: case 53: case 54: case 55: case 56: case 57: return Function_PcAttribute; - case 58: return Function_PcCorprus; - case 59: return Function_Weather; - case 60: return Function_PcVampire; - case 61: return Function_Level; - case 62: return Function_Attacked; - case 63: return Function_TalkedToPc; - case 64: return Function_PcDynamicStat; - case 65: return Function_CreatureTargetted; - case 66: return Function_FriendlyHit; - case 67: case 68: case 69: case 70: return Function_AiSetting; - case 71: return Function_ShouldAttack; - case 72: return Function_Werewolf; - case 73: return Function_WerewolfKills; + case 0: + return Function_RankLow; + case 1: + return Function_RankHigh; + case 2: + return Function_RankRequirement; + case 3: + return Function_Reputation; + case 4: + return Function_HealthPercent; + case 5: + return Function_PCReputation; + case 6: + return Function_PcLevel; + case 7: + return Function_PcHealthPercent; + case 8: + case 9: + return Function_PcDynamicStat; + case 10: + return Function_PcAttribute; + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + return Function_PcSkill; + case 38: + return Function_PcGender; + case 39: + return Function_PcExpelled; + case 40: + return Function_PcCommonDisease; + case 41: + return Function_PcBlightDisease; + case 42: + return Function_PcClothingModifier; + case 43: + return Function_PcCrimeLevel; + case 44: + return Function_SameGender; + case 45: + return Function_SameRace; + case 46: + return Function_SameFaction; + case 47: + return Function_FactionRankDiff; + case 48: + return Function_Detected; + case 49: + return Function_Alarmed; + case 50: + return Function_Choice; + case 51: + case 52: + case 53: + case 54: + case 55: + case 56: + case 57: + return Function_PcAttribute; + case 58: + return Function_PcCorprus; + case 59: + return Function_Weather; + case 60: + return Function_PcVampire; + case 61: + return Function_Level; + case 62: + return Function_Attacked; + case 63: + return Function_TalkedToPc; + case 64: + return Function_PcDynamicStat; + case 65: + return Function_CreatureTargetted; + case 66: + return Function_FriendlyHit; + case 67: + case 68: + case 69: + case 70: + return Function_AiSetting; + case 71: + return Function_ShouldAttack; + case 72: + return Function_Werewolf; + case 73: + return Function_WerewolfKills; } return Function_False; } -MWDialogue::SelectWrapper::SelectWrapper (const ESM::DialInfo::SelectStruct& select) : mSelect (select) {} +MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialInfo::SelectStruct& select) + : mSelect(select) +{ +} MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const { @@ -102,18 +181,30 @@ MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() con switch (type) { - case '1': return decodeFunction(); - case '2': return Function_Global; - case '3': return Function_Local; - case '4': return Function_Journal; - case '5': return Function_Item; - case '6': return Function_Dead; - case '7': return Function_NotId; - case '8': return Function_NotFaction; - case '9': return Function_NotClass; - case 'A': return Function_NotRace; - case 'B': return Function_NotCell; - case 'C': return Function_NotLocal; + case '1': + return decodeFunction(); + case '2': + return Function_Global; + case '3': + return Function_Local; + case '4': + return Function_Journal; + case '5': + return Function_Item; + case '6': + return Function_Dead; + case '7': + return Function_NotId; + case '8': + return Function_NotFaction; + case '9': + return Function_NotClass; + case 'A': + return Function_NotRace; + case 'B': + return Function_NotCell; + case 'C': + return Function_NotLocal; } return Function_None; @@ -121,64 +212,106 @@ MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() con int MWDialogue::SelectWrapper::getArgument() const { - if (mSelect.mSelectRule[1]!='1') + if (mSelect.mSelectRule[1] != '1') return 0; int index = 0; - std::istringstream (mSelect.mSelectRule.substr(2,2)) >> index; + std::istringstream(mSelect.mSelectRule.substr(2, 2)) >> index; switch (index) { // AI settings - case 67: return 1; - case 68: return 0; - case 69: return 3; - case 70: return 2; + case 67: + return 1; + case 68: + return 0; + case 69: + return 3; + case 70: + return 2; // attributes - case 10: return 0; - case 51: return 1; - case 52: return 2; - case 53: return 3; - case 54: return 4; - case 55: return 5; - case 56: return 6; - case 57: return 7; + case 10: + return 0; + case 51: + return 1; + case 52: + return 2; + case 53: + return 3; + case 54: + return 4; + case 55: + return 5; + case 56: + return 6; + case 57: + return 7; // skills - case 11: return 0; - case 12: return 1; - case 13: return 2; - case 14: return 3; - case 15: return 4; - case 16: return 5; - case 17: return 6; - case 18: return 7; - case 19: return 8; - case 20: return 9; - case 21: return 10; - case 22: return 11; - case 23: return 12; - case 24: return 13; - case 25: return 14; - case 26: return 15; - case 27: return 16; - case 28: return 17; - case 29: return 18; - case 30: return 19; - case 31: return 20; - case 32: return 21; - case 33: return 22; - case 34: return 23; - case 35: return 24; - case 36: return 25; - case 37: return 26; + case 11: + return 0; + case 12: + return 1; + case 13: + return 2; + case 14: + return 3; + case 15: + return 4; + case 16: + return 5; + case 17: + return 6; + case 18: + return 7; + case 19: + return 8; + case 20: + return 9; + case 21: + return 10; + case 22: + return 11; + case 23: + return 12; + case 24: + return 13; + case 25: + return 14; + case 26: + return 15; + case 27: + return 16; + case 28: + return 17; + case 29: + return 18; + case 30: + return 19; + case 31: + return 20; + case 32: + return 21; + case 33: + return 22; + case 34: + return 23; + case 35: + return 24; + case 36: + return 25; + case 37: + return 26; // dynamic stats - case 8: return 1; - case 9: return 2; - case 64: return 0; + case 8: + return 1; + case 9: + return 2; + case 64: + return 0; } return 0; @@ -186,69 +319,90 @@ int MWDialogue::SelectWrapper::getArgument() const MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const { - static const Function integerFunctions[] = - { - Function_Journal, Function_Item, Function_Dead, + static const Function integerFunctions[] = { + Function_Journal, + Function_Item, + Function_Dead, Function_Choice, Function_AiSetting, - Function_PcAttribute, Function_PcSkill, + Function_PcAttribute, + Function_PcSkill, Function_FriendlyHit, - Function_PcLevel, Function_PcGender, Function_PcClothingModifier, + Function_PcLevel, + Function_PcGender, + Function_PcClothingModifier, Function_PcCrimeLevel, Function_RankRequirement, - Function_Level, Function_PCReputation, + Function_Level, + Function_PCReputation, Function_Weather, - Function_Reputation, Function_FactionRankDiff, + Function_Reputation, + Function_FactionRankDiff, Function_WerewolfKills, - Function_RankLow, Function_RankHigh, + Function_RankLow, + Function_RankHigh, Function_CreatureTargetted, - Function_None // end marker + // end marker + Function_None, }; - static const Function numericFunctions[] = - { - Function_Global, Function_Local, Function_NotLocal, - Function_PcDynamicStat, Function_PcHealthPercent, + static const Function numericFunctions[] = { + Function_Global, + Function_Local, + Function_NotLocal, + Function_PcDynamicStat, + Function_PcHealthPercent, Function_HealthPercent, - Function_None // end marker + // end marker + Function_None, }; - static const Function booleanFunctions[] = - { + static const Function booleanFunctions[] = { Function_False, - Function_SameGender, Function_SameRace, Function_SameFaction, - Function_PcCommonDisease, Function_PcBlightDisease, Function_PcCorprus, + Function_SameGender, + Function_SameRace, + Function_SameFaction, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcCorprus, Function_PcExpelled, - Function_PcVampire, Function_TalkedToPc, - Function_Alarmed, Function_Detected, - Function_Attacked, Function_ShouldAttack, + Function_PcVampire, + Function_TalkedToPc, + Function_Alarmed, + Function_Detected, + Function_Attacked, + Function_ShouldAttack, Function_Werewolf, - Function_None // end marker + // end marker + Function_None, }; - static const Function invertedBooleanFunctions[] = - { - Function_NotId, Function_NotFaction, Function_NotClass, - Function_NotRace, Function_NotCell, - Function_None // end marker + static const Function invertedBooleanFunctions[] = { + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + // end marker + Function_None, }; Function function = getFunction(); - for (int i=0; integerFunctions[i]!=Function_None; ++i) - if (integerFunctions[i]==function) + for (int i = 0; integerFunctions[i] != Function_None; ++i) + if (integerFunctions[i] == function) return Type_Integer; - for (int i=0; numericFunctions[i]!=Function_None; ++i) - if (numericFunctions[i]==function) + for (int i = 0; numericFunctions[i] != Function_None; ++i) + if (numericFunctions[i] == function) return Type_Numeric; - for (int i=0; booleanFunctions[i]!=Function_None; ++i) - if (booleanFunctions[i]==function) + for (int i = 0; booleanFunctions[i] != Function_None; ++i) + if (booleanFunctions[i] == function) return Type_Boolean; - for (int i=0; invertedBooleanFunctions[i]!=Function_None; ++i) - if (invertedBooleanFunctions[i]==function) + for (int i = 0; invertedBooleanFunctions[i] != Function_None; ++i) + if (invertedBooleanFunctions[i] == function) return Type_Inverted; return Type_None; @@ -256,42 +410,49 @@ MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const bool MWDialogue::SelectWrapper::isNpcOnly() const { - static const Function functions[] = - { - Function_NotFaction, Function_NotClass, Function_NotRace, - Function_SameGender, Function_SameRace, Function_SameFaction, + static const Function functions[] = { + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_SameGender, + Function_SameRace, + Function_SameFaction, Function_RankRequirement, - Function_Reputation, Function_FactionRankDiff, - Function_Werewolf, Function_WerewolfKills, - Function_RankLow, Function_RankHigh, - Function_None // end marker + Function_Reputation, + Function_FactionRankDiff, + Function_Werewolf, + Function_WerewolfKills, + Function_RankLow, + Function_RankHigh, + // end marker + Function_None, }; Function function = getFunction(); - for (int i=0; functions[i]!=Function_None; ++i) - if (functions[i]==function) + for (int i = 0; functions[i] != Function_None; ++i) + if (functions[i] == function) return true; return false; } -bool MWDialogue::SelectWrapper::selectCompare (int value) const +bool MWDialogue::SelectWrapper::selectCompare(int value) const { - return selectCompareImp (mSelect, value); + return selectCompareImp(mSelect, value); } -bool MWDialogue::SelectWrapper::selectCompare (float value) const +bool MWDialogue::SelectWrapper::selectCompare(float value) const { - return selectCompareImp (mSelect, value); + return selectCompareImp(mSelect, value); } -bool MWDialogue::SelectWrapper::selectCompare (bool value) const +bool MWDialogue::SelectWrapper::selectCompare(bool value) const { - return selectCompareImp (mSelect, static_cast (value)); + return selectCompareImp(mSelect, static_cast(value)); } std::string MWDialogue::SelectWrapper::getName() const { - return Misc::StringUtils::lowerCase (mSelect.mSelectRule.substr (5)); + return Misc::StringUtils::lowerCase(std::string_view(mSelect.mSelectRule).substr(5)); } diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp index ef787d8ee..0d376d957 100644 --- a/apps/openmw/mwdialogue/selectwrapper.hpp +++ b/apps/openmw/mwdialogue/selectwrapper.hpp @@ -1,85 +1,100 @@ #ifndef GAME_MWDIALOGUE_SELECTWRAPPER_H #define GAME_MWDIALOGUE_SELECTWRAPPER_H -#include +#include namespace MWDialogue { class SelectWrapper { - const ESM::DialInfo::SelectStruct& mSelect; + const ESM::DialInfo::SelectStruct& mSelect; - public: + public: + enum Function + { + Function_None, + Function_False, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + Function_Local, + Function_Global, + Function_SameGender, + Function_SameRace, + Function_SameFaction, + Function_Choice, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcCorprus, + Function_AiSetting, + Function_PcAttribute, + Function_PcSkill, + Function_PcExpelled, + Function_PcVampire, + Function_FriendlyHit, + Function_TalkedToPc, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcDynamicStat, + Function_PcGender, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_RankRequirement, + Function_HealthPercent, + Function_Level, + Function_PCReputation, + Function_Weather, + Function_Reputation, + Function_Alarmed, + Function_FactionRankDiff, + Function_Detected, + Function_Attacked, + Function_ShouldAttack, + Function_CreatureTargetted, + Function_Werewolf, + Function_WerewolfKills, + Function_RankLow, + Function_RankHigh + }; - enum Function - { - Function_None, Function_False, - Function_Journal, - Function_Item, - Function_Dead, - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - Function_NotLocal, - Function_Local, - Function_Global, - Function_SameGender, Function_SameRace, Function_SameFaction, - Function_Choice, - Function_PcCommonDisease, Function_PcBlightDisease, Function_PcCorprus, - Function_AiSetting, - Function_PcAttribute, Function_PcSkill, - Function_PcExpelled, - Function_PcVampire, - Function_FriendlyHit, - Function_TalkedToPc, - Function_PcLevel, Function_PcHealthPercent, Function_PcDynamicStat, - Function_PcGender, Function_PcClothingModifier, Function_PcCrimeLevel, - Function_RankRequirement, - Function_HealthPercent, Function_Level, Function_PCReputation, - Function_Weather, - Function_Reputation, Function_Alarmed, Function_FactionRankDiff, Function_Detected, - Function_Attacked, Function_ShouldAttack, - Function_CreatureTargetted, - Function_Werewolf, Function_WerewolfKills, - Function_RankLow, Function_RankHigh - }; + enum Type + { + Type_None, + Type_Integer, + Type_Numeric, + Type_Boolean, + Type_Inverted + }; - enum Type - { - Type_None, - Type_Integer, - Type_Numeric, - Type_Boolean, - Type_Inverted - }; + private: + Function decodeFunction() const; - private: + public: + SelectWrapper(const ESM::DialInfo::SelectStruct& select); - Function decodeFunction() const; + Function getFunction() const; - public: + int getArgument() const; - SelectWrapper (const ESM::DialInfo::SelectStruct& select); + Type getType() const; - Function getFunction() const; + bool isNpcOnly() const; + ///< \attention Do not call any of the select functions for this select struct! - int getArgument() const; + bool selectCompare(int value) const; - Type getType() const; + bool selectCompare(float value) const; - bool isNpcOnly() const; - ///< \attention Do not call any of the select functions for this select struct! + bool selectCompare(bool value) const; - bool selectCompare (int value) const; - - bool selectCompare (float value) const; - - bool selectCompare (bool value) const; - - std::string getName() const; - ///< Return case-smashed name. + std::string getName() const; + ///< Return case-smashed name. }; } diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index eb7fbdc1d..2b51c27fb 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -7,43 +7,43 @@ namespace MWDialogue { - Topic::Topic() - {} + Topic::Topic() {} - Topic::Topic (const std::string& topic) - : mTopic (topic), mName ( - MWBase::Environment::get().getWorld()->getStore().get().find (topic)->mId) - {} - - Topic::~Topic() - {} - - void Topic::addEntry (const JournalEntry& entry) + Topic::Topic(const ESM::RefId& topic) + : mTopic(topic) + , mName(MWBase::Environment::get().getESMStore()->get().find(topic)->mStringId) { - if (entry.mTopic!=mTopic) - throw std::runtime_error ("topic does not match: " + mTopic); + } + + Topic::~Topic() {} + + bool Topic::addEntry(const JournalEntry& entry) + { + if (entry.mTopic != mTopic) + throw std::runtime_error("topic does not match: " + mTopic.toDebugString()); // bail out if we already have heard this for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it) { if (it->mInfoId == entry.mInfoId) - return; + return false; } - mEntries.push_back (entry); // we want slicing here + mEntries.push_back(entry); // we want slicing here + return false; } - void Topic::insertEntry (const ESM::JournalEntry& entry) + void Topic::insertEntry(const ESM::JournalEntry& entry) { - mEntries.push_back (entry); + mEntries.push_back(entry); } - std::string Topic::getTopic() const + const ESM::RefId& Topic::getTopic() const { return mTopic; } - std::string Topic::getName() const + std::string_view Topic::getName() const { return mName; } @@ -58,14 +58,13 @@ namespace MWDialogue return mEntries.end(); } - void Topic::removeLastAddedResponse (const std::string& actorName) + void Topic::removeLastAddedResponse(std::string_view actorName) { - for (std::vector::reverse_iterator it = mEntries.rbegin(); - it != mEntries.rend(); ++it) + for (std::vector::reverse_iterator it = mEntries.rbegin(); it != mEntries.rend(); ++it) { if (it->mActorName == actorName) { - mEntries.erase( (++it).base() ); // erase doesn't take a reverse_iterator + mEntries.erase((++it).base()); // erase doesn't take a reverse_iterator return; } } diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index 72486ef8a..d95d2470c 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -2,6 +2,7 @@ #define GAME_MWDIALOG_TOPIC_H #include +#include #include #include "journalentry.hpp" @@ -16,45 +17,42 @@ namespace MWDialogue /// \brief Collection of seen responses for a topic class Topic { - public: + public: + typedef std::vector TEntryContainer; + typedef TEntryContainer::const_iterator TEntryIter; - typedef std::vector TEntryContainer; - typedef TEntryContainer::const_iterator TEntryIter; + protected: + ESM::RefId mTopic; + std::string mName; + TEntryContainer mEntries; - protected: + public: + Topic(); - std::string mTopic; - std::string mName; - TEntryContainer mEntries; + Topic(const ESM::RefId& topic); - public: + virtual ~Topic(); - Topic(); + virtual bool addEntry(const JournalEntry& entry); + ///< Add entry + /// + /// \note Redundant entries are ignored. - Topic (const std::string& topic); + void insertEntry(const ESM::JournalEntry& entry); + ///< Add entry without checking for redundant entries or modifying the state of the + /// topic otherwise - virtual ~Topic(); + const ESM::RefId& getTopic() const; - virtual void addEntry (const JournalEntry& entry); - ///< Add entry - /// - /// \note Redundant entries are ignored. + virtual std::string_view getName() const; - void insertEntry (const ESM::JournalEntry& entry); - ///< Add entry without checking for redundant entries or modifying the state of the - /// topic otherwise + void removeLastAddedResponse(std::string_view actorName); - std::string getTopic() const; + TEntryIter begin() const; + ///< Iterator pointing to the begin of the journal for this topic. - virtual std::string getName() const; - - void removeLastAddedResponse (const std::string& actorName); - - TEntryIter begin() const; - ///< Iterator pointing to the begin of the journal for this topic. - - TEntryIter end() const; - ///< Iterator pointing past the end of the journal for this topic. + TEntryIter end() const; + ///< Iterator pointing past the end of the journal for this topic. }; } diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index e174a9f1b..388b650e8 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -1,11 +1,14 @@ #include "alchemywindow.hpp" -#include #include -#include #include #include #include +#include +#include + +#include +#include /* Start of tes3mp addition @@ -20,23 +23,23 @@ */ #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwmechanics/magiceffects.hpp" -#include "../mwmechanics/alchemy.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/alchemy.hpp" +#include "../mwmechanics/magiceffects.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include -#include #include "inventoryitemmodel.hpp" -#include "sortfilteritemmodel.hpp" #include "itemview.hpp" #include "itemwidget.hpp" +#include "sortfilteritemmodel.hpp" +#include "ustring.hpp" #include "widgets.hpp" namespace MWGui @@ -46,9 +49,9 @@ namespace MWGui , mCurrentFilter(FilterType::ByName) , mModel(nullptr) , mSortModel(nullptr) - , mAlchemy(new MWMechanics::Alchemy()) - , mApparatus (4) - , mIngredients (4) + , mAlchemy(std::make_unique()) + , mApparatus(4) + , mIngredients(4) { getWidget(mCreateButton, "CreateButton"); getWidget(mCancelButton, "CancelButton"); @@ -121,7 +124,7 @@ namespace MWGui void AlchemyWindow::createPotions(int count) { MWMechanics::Alchemy::Result result = mAlchemy->create(mNameEdit->getCaption(), count); - MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); /* Start of tes3mp addition @@ -135,6 +138,7 @@ namespace MWGui switch (result) { +<<<<<<< HEAD case MWMechanics::Alchemy::Result_NoName: winMgr->messageBox("#{sNotifyMessage37}"); break; @@ -184,10 +188,34 @@ namespace MWGui */ break; +======= + case MWMechanics::Alchemy::Result_NoName: + winMgr->messageBox("#{sNotifyMessage37}"); + break; + case MWMechanics::Alchemy::Result_NoMortarAndPestle: + winMgr->messageBox("#{sNotifyMessage45}"); + break; + case MWMechanics::Alchemy::Result_LessThanTwoIngredients: + winMgr->messageBox("#{sNotifyMessage6a}"); + break; + case MWMechanics::Alchemy::Result_Success: + winMgr->playSound(ESM::RefId::stringRefId("potion success")); + if (count == 1) + winMgr->messageBox("#{sPotionSuccess}"); + else + winMgr->messageBox( + "#{sPotionSuccess} " + mNameEdit->getCaption().asUTF8() + " (" + std::to_string(count) + ")"); + break; + case MWMechanics::Alchemy::Result_NoEffects: + case MWMechanics::Alchemy::Result_RandomFailure: + winMgr->messageBox("#{sNotifyMessage8}"); + winMgr->playSound(ESM::RefId::stringRefId("potion fail")); + break; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } // remove ingredient slots that have been fully used up - for (int i=0; i<4; ++i) + for (int i = 0; i < 4; ++i) if (mIngredients[i]->isUserString("ToolTipType")) { MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); @@ -202,10 +230,9 @@ namespace MWGui void AlchemyWindow::initFilter() { auto const& wm = MWBase::Environment::get().getWindowManager(); - auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); - auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects"); + std::string_view ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); - if (mFilterType->getCaption() == ingredient) + if (mFilterType->getCaption() == toUString(ingredient)) mCurrentFilter = FilterType::ByName; else mCurrentFilter = FilterType::ByEffect; @@ -217,13 +244,12 @@ namespace MWGui void AlchemyWindow::switchFilterType(MyGUI::Widget* _sender) { auto const& wm = MWBase::Environment::get().getWindowManager(); - auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); - auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects"); - auto *button = _sender->castType(); + MyGUI::UString ingredient = toUString(wm->getGameSettingString("sIngredients", "Ingredients")); + auto* button = _sender->castType(); if (button->getCaption() == ingredient) { - button->setCaption(effect); + button->setCaption(toUString(wm->getGameSettingString("sMagicEffects", "Magic Effects"))); mCurrentFilter = FilterType::ByEffect; } else @@ -244,12 +270,12 @@ namespace MWGui for (size_t i = 0; i < mModel->getItemCount(); ++i) { MWWorld::Ptr item = mModel->getItem(i).mBase; - if (item.getTypeName() != typeid(ESM::Ingredient).name()) + if (item.getType() != ESM::Ingredient::sRecordId) continue; - itemNames.insert(item.getClass().getName(item)); + itemNames.emplace(item.getClass().getName(item)); - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); auto const alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); auto const effects = MWMechanics::Alchemy::effectsDescription(item, alchemySkill); @@ -257,15 +283,18 @@ namespace MWGui } mFilterValue->removeAllItems(); - auto const addItems = [&](auto const& container) - { + auto const addItems = [&](auto const& container) { for (auto const& item : container) mFilterValue->addItem(item); }; switch (mCurrentFilter) { - case FilterType::ByName: addItems(itemNames); break; - case FilterType::ByEffect: addItems(itemEffects); break; + case FilterType::ByName: + addItems(itemNames); + break; + case FilterType::ByEffect: + addItems(itemEffects); + break; } } @@ -273,8 +302,12 @@ namespace MWGui { switch (mCurrentFilter) { - case FilterType::ByName: mSortModel->setNameFilter(filter); break; - case FilterType::ByEffect: mSortModel->setEffectFilter(filter); break; + case FilterType::ByName: + mSortModel->setNameFilter(filter); + break; + case FilterType::ByEffect: + mSortModel->setEffectFilter(filter); + break; } mItemView->update(); } @@ -295,27 +328,30 @@ namespace MWGui void AlchemyWindow::onOpen() { mAlchemy->clear(); - mAlchemy->setAlchemist (MWMechanics::getPlayer()); + mAlchemy->setAlchemist(MWMechanics::getPlayer()); - mModel = new InventoryItemModel(MWMechanics::getPlayer()); - mSortModel = new SortFilterItemModel(mModel); + auto model = std::make_unique(MWMechanics::getPlayer()); + mModel = model.get(); + auto sortModel = std::make_unique(std::move(model)); + mSortModel = sortModel.get(); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); - mItemView->setModel (mSortModel); + mItemView->setModel(std::move(sortModel)); mItemView->resetScrollBars(); - mNameEdit->setCaption(""); + mNameEdit->setCaption({}); mBrewCountEdit->setValue(1); - int index = 0; - for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy->beginTools()); - iter!=mAlchemy->endTools() && index (mApparatus.size()); ++iter, ++index) + size_t index = 0; + for (auto iter = mAlchemy->beginTools(); iter != mAlchemy->endTools() && index < mApparatus.size(); + ++iter, ++index) { - mApparatus.at (index)->setItem(*iter); - mApparatus.at (index)->clearUserStrings(); + const auto& widget = mApparatus[index]; + widget->setItem(*iter); + widget->clearUserStrings(); if (!iter->isEmpty()) { - mApparatus.at (index)->setUserString ("ToolTipType", "ItemPtr"); - mApparatus.at (index)->setUserData (MWWorld::Ptr(*iter)); + widget->setUserString("ToolTipType", "ItemPtr"); + widget->setUserData(MWWorld::Ptr(*iter)); } } @@ -340,7 +376,7 @@ namespace MWGui { update(); - std::string sound = item.getClass().getUpSoundId(item); + const ESM::RefId& sound = item.getClass().getUpSoundId(item); MWBase::Environment::get().getWindowManager()->playSound(sound); } } @@ -354,13 +390,13 @@ namespace MWGui mSortModel->clearDragItems(); - MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy->beginIngredients (); - for (int i=0; i<4; ++i) + MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy->beginIngredients(); + for (int i = 0; i < 4; ++i) { ItemWidget* ingredient = mIngredients[i]; MWWorld::Ptr item; - if (it != mAlchemy->endIngredients ()) + if (it != mAlchemy->endIngredients()) { item = *it; ++it; @@ -372,11 +408,11 @@ namespace MWGui if (ingredient->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); - ingredient->clearUserStrings (); + ingredient->clearUserStrings(); ingredient->setItem(item); - if (item.isEmpty ()) + if (item.isEmpty()) continue; ingredient->setUserString("ToolTipType", "ItemPtr"); @@ -389,12 +425,13 @@ namespace MWGui std::set effectIds = mAlchemy->listEffects(); Widgets::SpellEffectList list; - unsigned int effectIndex=0; + unsigned int effectIndex = 0; for (const MWMechanics::EffectKey& effectKey : effectIds) { Widgets::SpellEffectParams params; params.mEffectID = effectKey.mId; - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectKey.mId); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effectKey.mId); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) params.mSkill = effectKey.mArg; else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) @@ -413,8 +450,8 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(mEffectsBox->getChildAt(0)); MyGUI::IntCoord coord(0, 0, mEffectsBox->getWidth(), 24); - Widgets::MWEffectListPtr effectsWidget = mEffectsBox->createWidget - ("MW_StatName", coord, MyGUI::Align::Left | MyGUI::Align::Top); + Widgets::MWEffectListPtr effectsWidget = mEffectsBox->createWidget( + "MW_StatName", coord, MyGUI::Align::Left | MyGUI::Align::Top); effectsWidget->setEffectList(list); @@ -425,16 +462,17 @@ namespace MWGui void AlchemyWindow::removeIngredient(MyGUI::Widget* ingredient) { - for (int i=0; i<4; ++i) + for (int i = 0; i < 4; ++i) if (mIngredients[i] == ingredient) - mAlchemy->removeIngredient (i); + mAlchemy->removeIngredient(i); update(); } - void AlchemyWindow::addRepeatController(MyGUI::Widget *widget) + void AlchemyWindow::addRepeatController(MyGUI::Widget* widget) { - MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MyGUI::ControllerRepeatClick::getClassTypeName()); + MyGUI::ControllerItem* item + = MyGUI::ControllerManager::getInstance().createItem(MyGUI::ControllerRepeatClick::getClassTypeName()); MyGUI::ControllerRepeatClick* controller = static_cast(item); controller->eventRepeatClick += newDelegate(this, &AlchemyWindow::onRepeatClick); MyGUI::ControllerManager::getInstance().addItem(widget, controller); @@ -460,7 +498,7 @@ namespace MWGui onDecreaseButtonTriggered(); } - void AlchemyWindow::onCountButtonReleased(MyGUI::Widget *_sender, int _left, int _top, MyGUI::MouseButton _id) + void AlchemyWindow::onCountButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { MyGUI::ControllerManager::getInstance().removeItem(_sender); } @@ -478,13 +516,13 @@ namespace MWGui if (currentCount == std::numeric_limits::max()) return; - mBrewCountEdit->setValue(currentCount+1); + mBrewCountEdit->setValue(currentCount + 1); } void AlchemyWindow::onDecreaseButtonTriggered() { int currentCount = mBrewCountEdit->getValue(); if (currentCount > 1) - mBrewCountEdit->setValue(currentCount-1); + mBrewCountEdit->setValue(currentCount - 1); } } diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index 33bd1f974..681de0e31 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -4,18 +4,15 @@ #include #include -#include #include +#include #include #include #include "windowbase.hpp" -namespace MWMechanics -{ - class Alchemy; -} +#include "../mwmechanics/alchemy.hpp" namespace MWGui { @@ -34,12 +31,15 @@ namespace MWGui void onResChange(int, int) override { center(); } private: - static const float sCountChangeInitialPause; // in seconds static const float sCountChangeInterval; // in seconds std::string mSuggestedPotionName; - enum class FilterType { ByName, ByEffect }; + enum class FilterType + { + ByName, + ByEffect + }; FilterType mCurrentFilter; ItemView* mItemView; diff --git a/apps/openmw/mwgui/backgroundimage.cpp b/apps/openmw/mwgui/backgroundimage.cpp index 55c825ebb..e2e2c62cd 100644 --- a/apps/openmw/mwgui/backgroundimage.cpp +++ b/apps/openmw/mwgui/backgroundimage.cpp @@ -5,59 +5,59 @@ namespace MWGui { -void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRatio, bool stretch) -{ - if (mChild) + void BackgroundImage::setBackgroundImage(const std::string& image, bool fixedRatio, bool stretch) { - MyGUI::Gui::getInstance().destroyWidget(mChild); - mChild = nullptr; - } - if (!stretch) - { - setImageTexture("black"); + if (mChild) + { + MyGUI::Gui::getInstance().destroyWidget(mChild); + mChild = nullptr; + } + if (!stretch) + { + setImageTexture("black"); - if (fixedRatio) - mAspect = 4.0/3.0; + if (fixedRatio) + mAspect = 4.0 / 3.0; + else + mAspect = 0; // TODO + + mChild + = createWidgetReal("ImageBox", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Default); + mChild->setImageTexture(image); + + adjustSize(); + } else - mAspect = 0; // TODO + { + mAspect = 0; + setImageTexture(image); + } + } - mChild = createWidgetReal("ImageBox", - MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); - mChild->setImageTexture(image); + void BackgroundImage::adjustSize() + { + if (mAspect == 0) + return; + MyGUI::IntSize screenSize = getSize(); + + int leftPadding = std::max(0, static_cast(screenSize.width - screenSize.height * mAspect) / 2); + int topPadding = std::max(0, static_cast(screenSize.height - screenSize.width / mAspect) / 2); + + mChild->setCoord( + leftPadding, topPadding, screenSize.width - leftPadding * 2, screenSize.height - topPadding * 2); + } + + void BackgroundImage::setSize(const MyGUI::IntSize& _value) + { + MyGUI::Widget::setSize(_value); adjustSize(); } - else + + void BackgroundImage::setCoord(const MyGUI::IntCoord& _value) { - mAspect = 0; - setImageTexture(image); + MyGUI::Widget::setCoord(_value); + adjustSize(); } -} - -void BackgroundImage::adjustSize() -{ - if (mAspect == 0) - return; - - MyGUI::IntSize screenSize = getSize(); - - int leftPadding = std::max(0, static_cast(screenSize.width - screenSize.height * mAspect) / 2); - int topPadding = std::max(0, static_cast(screenSize.height - screenSize.width / mAspect) / 2); - - mChild->setCoord(leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2); -} - -void BackgroundImage::setSize (const MyGUI::IntSize& _value) -{ - MyGUI::Widget::setSize (_value); - adjustSize(); -} - -void BackgroundImage::setCoord (const MyGUI::IntCoord& _value) -{ - MyGUI::Widget::setCoord (_value); - adjustSize(); -} - } diff --git a/apps/openmw/mwgui/backgroundimage.hpp b/apps/openmw/mwgui/backgroundimage.hpp index 32cdf1a65..b151a26e2 100644 --- a/apps/openmw/mwgui/backgroundimage.hpp +++ b/apps/openmw/mwgui/backgroundimage.hpp @@ -11,19 +11,23 @@ namespace MWGui */ class BackgroundImage final : public MyGUI::ImageBox { - MYGUI_RTTI_DERIVED(BackgroundImage) + MYGUI_RTTI_DERIVED(BackgroundImage) public: - BackgroundImage() : mChild(nullptr), mAspect(0) {} + BackgroundImage() + : mChild(nullptr) + , mAspect(0) + { + } /** * @param fixedRatio Use a fixed ratio of 4:3, regardless of the image dimensions * @param stretch Stretch to fill the whole screen, or add black bars? */ - void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool stretch=true); + void setBackgroundImage(const std::string& image, bool fixedRatio = true, bool stretch = true); - void setSize (const MyGUI::IntSize &_value) override; - void setCoord (const MyGUI::IntCoord &_value) override; + void setSize(const MyGUI::IntSize& _value) override; + void setCoord(const MyGUI::IntCoord& _value) override; private: MyGUI::ImageBox* mChild; diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 41711f5e4..d9f78366a 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -1,25 +1,32 @@ #include "birth.hpp" -#include -#include #include +#include +#include #include +#include +#include +#include +#include + #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" +#include "ustring.hpp" #include "widgets.hpp" namespace { - bool sortBirthSigns(const std::pair& left, const std::pair& right) + bool sortBirthSigns(const std::pair& left, + const std::pair& right) { - return left.second->mName.compare (right.second->mName) < 0; + return left.second->mName.compare(right.second->mName) < 0; } } @@ -28,7 +35,7 @@ namespace MWGui { BirthDialog::BirthDialog() - : WindowModal("openmw_chargen_birth.layout") + : WindowModal("openmw_chargen_birth.layout") { // Centre dialog center(); @@ -48,7 +55,7 @@ namespace MWGui MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); + okButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onOkClicked); updateBirths(); @@ -61,9 +68,11 @@ namespace MWGui getWidget(okButton, "OKButton"); if (shown) - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } void BirthDialog::onOpen() @@ -74,21 +83,20 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList); // Show the current birthsign by default - const std::string &signId = - MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); + const auto& signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); if (!signId.empty()) setBirthId(signId); } - void BirthDialog::setBirthId(const std::string &birthId) + void BirthDialog::setBirthId(const ESM::RefId& birthId) { mCurrentBirthId = birthId; mBirthList->setIndexSelected(MyGUI::ITEM_NONE); size_t count = mBirthList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (Misc::StringUtils::ciEqual(*mBirthList->getItemDataAt(i), birthId)) + if (*mBirthList->getItemDataAt(i) == birthId) { mBirthList->setIndexSelected(i); break; @@ -102,15 +110,15 @@ namespace MWGui void BirthDialog::onOkClicked(MyGUI::Widget* _sender) { - if(mBirthList->getIndexSelected() == MyGUI::ITEM_NONE) + if (mBirthList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } - void BirthDialog::onAccept(MyGUI::ListBox *_sender, size_t _index) + void BirthDialog::onAccept(MyGUI::ListBox* _sender, size_t _index) { onSelectBirth(_sender, _index); - if(mBirthList->getIndexSelected() == MyGUI::ITEM_NONE) + if (mBirthList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } @@ -125,11 +133,11 @@ namespace MWGui if (_index == MyGUI::ITEM_NONE) return; - const std::string *birthId = mBirthList->getItemDataAt(_index); - if (Misc::StringUtils::ciEqual(mCurrentBirthId, *birthId)) + const ESM::RefId& birthId = *mBirthList->getItemDataAt(_index); + if (mCurrentBirthId == birthId) return; - mCurrentBirthId = *birthId; + mCurrentBirthId = birthId; updateSpells(); } @@ -139,11 +147,10 @@ namespace MWGui { mBirthList->removeAllItems(); - const MWWorld::Store &signs = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& signs = MWBase::Environment::get().getESMStore()->get(); // sort by name - std::vector < std::pair > birthSigns; + std::vector> birthSigns; for (const ESM::BirthSign& sign : signs) { @@ -160,7 +167,7 @@ namespace MWGui mBirthList->setIndexSelected(index); mCurrentBirthId = birthsignPair.first; } - else if (Misc::StringUtils::ciEqual(birthsignPair.first, mCurrentBirthId)) + else if (birthsignPair.first == mCurrentBirthId) { mBirthList->setIndexSelected(index); } @@ -181,25 +188,24 @@ namespace MWGui return; Widgets::MWSpellPtr spellWidget; - const int lineHeight = 18; - MyGUI::IntCoord coord(0, 0, mSpellArea->getWidth(), 18); + const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + MyGUI::IntCoord coord(0, 0, mSpellArea->getWidth(), lineHeight); - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const ESM::BirthSign *birth = - store.get().find(mCurrentBirthId); + const ESM::BirthSign* birth = store.get().find(mCurrentBirthId); - mBirthImage->setImageTexture(MWBase::Environment::get().getWindowManager()->correctTexturePath(birth->mTexture)); + mBirthImage->setImageTexture(Misc::ResourceHelpers::correctTexturePath( + birth->mTexture, MWBase::Environment::get().getResourceSystem()->getVFS())); - std::vector abilities, powers, spells; + std::vector abilities, powers, spells; - std::vector::const_iterator it = birth->mPowers.mList.begin(); - std::vector::const_iterator end = birth->mPowers.mList.end(); + std::vector::const_iterator it = birth->mPowers.mList.begin(); + std::vector::const_iterator end = birth->mPowers.mList.end(); for (; it != end; ++it) { - const std::string &spellId = *it; - const ESM::Spell *spell = store.get().search(spellId); + const ESM::RefId& spellId = *it; + const ESM::Spell* spell = store.get().search(spellId); if (!spell) continue; // Skip spells which cannot be found ESM::Spell::SpellType type = static_cast(spell->mData.mType); @@ -216,38 +222,39 @@ namespace MWGui int i = 0; - struct { - const std::vector &spells; - const char *label; - } - categories[3] = { - {abilities, "sBirthsignmenu1"}, - {powers, "sPowers"}, - {spells, "sBirthsignmenu2"} - }; + struct + { + const std::vector& spells; + std::string_view label; + } categories[3] = { { abilities, "sBirthsignmenu1" }, { powers, "sPowers" }, { spells, "sBirthsignmenu2" } }; - for (int category = 0; category < 3; ++category) + for (size_t category = 0; category < 3; ++category) { if (!categories[category].spells.empty()) { - MyGUI::TextBox* label = mSpellArea->createWidget("SandBrightText", coord, MyGUI::Align::Default, std::string("Label")); - label->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString(categories[category].label, "")); + MyGUI::TextBox* label + = mSpellArea->createWidget("SandBrightText", coord, MyGUI::Align::Default, "Label"); + label->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString( + categories[category].label, {}))); mSpellItems.push_back(label); coord.top += lineHeight; end = categories[category].spells.end(); for (it = categories[category].spells.begin(); it != end; ++it) { - const std::string &spellId = *it; - spellWidget = mSpellArea->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("Spell") + MyGUI::utility::toString(i)); + const ESM::RefId& spellId = *it; + spellWidget = mSpellArea->createWidget( + "MW_StatName", coord, MyGUI::Align::Default, "Spell" + MyGUI::utility::toString(i)); spellWidget->setSpellId(spellId); mSpellItems.push_back(spellWidget); coord.top += lineHeight; MyGUI::IntCoord spellCoord = coord; - spellCoord.height = 24; // TODO: This should be fetched from the skin somehow, or perhaps a widget in the layout as a template? - spellWidget->createEffectWidgets(mSpellItems, mSpellArea, spellCoord, (category == 0) ? Widgets::MWEffectList::EF_Constant : 0); + spellCoord.height = 24; // TODO: This should be fetched from the skin somehow, or perhaps a widget + // in the layout as a template? + spellWidget->createEffectWidgets( + mSpellItems, mSpellArea, spellCoord, (category == 0) ? Widgets::MWEffectList::EF_Constant : 0); coord.top = spellCoord.top; ++i; @@ -255,7 +262,8 @@ namespace MWGui } } - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mSpellArea->setVisibleVScroll(false); mSpellArea->setCanvasSize(MyGUI::IntSize(mSpellArea->getWidth(), std::max(mSpellArea->getHeight(), coord.top))); mSpellArea->setVisibleVScroll(true); diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index 9f9d4332f..5afc5e16e 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -2,6 +2,7 @@ #define MWGUI_BIRTH_H #include "windowbase.hpp" +#include namespace MWGui { @@ -16,8 +17,8 @@ namespace MWGui GM_Female }; - const std::string &getBirthId() const { return mCurrentBirthId; } - void setBirthId(const std::string &raceId); + const ESM::RefId& getBirthId() const { return mCurrentBirthId; } + void setBirthId(const ESM::RefId& raceId); void setNextButtonShow(bool shown); void onOpen() override; @@ -53,7 +54,7 @@ namespace MWGui MyGUI::ImageBox* mBirthImage; std::vector mSpellItems; - std::string mCurrentBirthId; + ESM::RefId mCurrentBirthId; }; } #endif diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index fba136f88..cd419f8cc 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -2,1075 +2,1102 @@ #include +#include "MyGUI_FactoryManager.h" +#include "MyGUI_FontManager.h" #include "MyGUI_RenderItem.h" #include "MyGUI_RenderManager.h" #include "MyGUI_TextureUtility.h" -#include "MyGUI_FactoryManager.h" #include - -#include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" +#include namespace MWGui { -struct TypesetBookImpl; -class PageDisplay; -class BookPageImpl; + struct TypesetBookImpl; + class PageDisplay; + class BookPageImpl; -static bool ucsSpace (int codePoint); -static bool ucsLineBreak (int codePoint); -static bool ucsCarriageReturn (int codePoint); -static bool ucsBreakingSpace (int codePoint); + 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 () {} }; - -struct TypesetBookImpl : TypesetBook -{ - typedef std::vector Content; - typedef std::list Contents; - typedef Utf8Stream::Point Utf8Point; - typedef std::pair Range; - - struct StyleImpl : BookTypesetter::Style + struct BookTypesetter::Style { - MyGUI::IFont* mFont; - MyGUI::Colour mHotColour; - MyGUI::Colour mActiveColour; - MyGUI::Colour mNormalColour; - InteractiveId mInteractiveId; - - bool match (MyGUI::IFont* tstFont, const MyGUI::Colour& tstHotColour, const MyGUI::Colour& tstActiveColour, - const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) - { - return (mFont == tstFont) && - partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); - } - - bool match (char const * tstFont, const MyGUI::Colour& tstHotColour, const MyGUI::Colour& tstActiveColour, - const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) - { - return (mFont->getResourceName () == tstFont) && - partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); - } - - bool partal_match (const MyGUI::Colour& tstHotColour, const MyGUI::Colour& tstActiveColour, - const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) - { - return - (mHotColour == tstHotColour ) && - (mActiveColour == tstActiveColour ) && - (mNormalColour == tstNormalColour ) && - (mInteractiveId == tstInteractiveId ) ; - } + virtual ~Style() {} }; - typedef std::list Styles; - - struct Run + struct TypesetBookImpl : TypesetBook { - StyleImpl* mStyle; - Range mRange; - int mLeft, mRight; - int mPrintableChars; - }; + typedef std::vector Content; + typedef std::list Contents; + typedef Utf8Stream::Point Utf8Point; + typedef std::pair Range; - typedef std::vector Runs; - - struct Line - { - Runs mRuns; - MyGUI::IntRect mRect; - }; - - typedef std::vector Lines; - - struct Section - { - Lines mLines; - MyGUI::IntRect mRect; - }; - - typedef std::vector

        Sections; - - // Holds "top" and "bottom" vertical coordinates in the source text. - // A page is basically a "window" into a portion of the source text, similar to a ScrollView. - typedef std::pair Page; - - typedef std::vector Pages; - - Pages mPages; - Sections mSections; - Contents mContents; - Styles mStyles; - MyGUI::IntRect mRect; - - virtual ~TypesetBookImpl () {} - - Range addContent (BookTypesetter::Utf8Span text) - { - Contents::iterator i = mContents.insert (mContents.end (), Content (text.first, text.second)); - - if (i->empty()) - return Range (Utf8Point (nullptr), Utf8Point (nullptr)); - - return Range (i->data(), i->data() + i->size()); - } - - size_t pageCount () const override { return mPages.size (); } - - std::pair getSize () const override - { - return std::make_pair (mRect.width (), mRect.height ()); - } - - template - void visitRuns (int top, int bottom, MyGUI::IFont* Font, Visitor const & visitor) const - { - for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) + struct StyleImpl : BookTypesetter::Style { - if (top >= mRect.bottom || bottom <= i->mRect.top) - continue; + MyGUI::IFont* mFont; + MyGUI::Colour mHotColour; + MyGUI::Colour mActiveColour; + MyGUI::Colour mNormalColour; + InteractiveId mInteractiveId; - for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) + bool match(MyGUI::IFont* tstFont, const MyGUI::Colour& tstHotColour, const MyGUI::Colour& tstActiveColour, + const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) { - if (top >= j->mRect.bottom || bottom <= j->mRect.top) + return (mFont == tstFont) + && partal_match(tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); + } + + bool match(std::string_view tstFont, const MyGUI::Colour& tstHotColour, + const MyGUI::Colour& tstActiveColour, const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) + { + return (mFont->getResourceName() == tstFont) + && partal_match(tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId); + } + + bool partal_match(const MyGUI::Colour& tstHotColour, const MyGUI::Colour& tstActiveColour, + const MyGUI::Colour& tstNormalColour, intptr_t tstInteractiveId) + { + return (mHotColour == tstHotColour) && (mActiveColour == tstActiveColour) + && (mNormalColour == tstNormalColour) && (mInteractiveId == tstInteractiveId); + } + }; + + typedef std::list Styles; + + struct Run + { + StyleImpl* mStyle; + Range mRange; + int mLeft, mRight; + int mPrintableChars; + }; + + typedef std::vector Runs; + + struct Line + { + Runs mRuns; + MyGUI::IntRect mRect; + }; + + typedef std::vector Lines; + + struct Section + { + Lines mLines; + MyGUI::IntRect mRect; + }; + + typedef std::vector
        Sections; + + // Holds "top" and "bottom" vertical coordinates in the source text. + // A page is basically a "window" into a portion of the source text, similar to a ScrollView. + typedef std::pair Page; + + typedef std::vector Pages; + + Pages mPages; + Sections mSections; + Contents mContents; + Styles mStyles; + MyGUI::IntRect mRect; + + virtual ~TypesetBookImpl() {} + + Range addContent(const BookTypesetter::Utf8Span& text) + { + Contents::iterator i = mContents.insert(mContents.end(), Content(text.first, text.second)); + + if (i->empty()) + return Range(Utf8Point(nullptr), Utf8Point(nullptr)); + + return Range(i->data(), i->data() + i->size()); + } + + size_t pageCount() const override { return mPages.size(); } + + std::pair getSize() const override + { + return std::make_pair(mRect.width(), mRect.height()); + } + + template + void visitRuns(int top, int bottom, MyGUI::IFont* Font, Visitor const& visitor) const + { + for (Sections::const_iterator i = mSections.begin(); i != mSections.end(); ++i) + { + if (top >= mRect.bottom || bottom <= i->mRect.top) continue; - for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k) - if (!Font || k->mStyle->mFont == Font) - visitor (*i, *j, *k); - } - } - } - - template - void visitRuns (int top, int bottom, Visitor const & visitor) const - { - visitRuns (top, bottom, nullptr, visitor); - } - - /// hit test with a margin for error. only hits on interactive text fragments are reported. - StyleImpl * hitTestWithMargin (int left, int top) - { - StyleImpl * hit = hitTest(left, top); - if (hit && hit->mInteractiveId != 0) - return hit; - - const int maxMargin = 10; - for (int margin=1; margin < maxMargin; ++margin) - { - for (int i=0; i<4; ++i) - { - if (i==0) - hit = hitTest(left, top-margin); - else if (i==1) - hit = hitTest(left, top+margin); - else if (i==2) - hit = hitTest(left-margin, top); - else - hit = hitTest(left+margin, top); - - if (hit && hit->mInteractiveId != 0) - return hit; - } - } - return nullptr; - } - - StyleImpl * hitTest (int left, int top) const - { - for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) - { - if (top < i->mRect.top || top >= i->mRect.bottom) - continue; - - int left1 = left - i->mRect.left; - - for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) - { - if (top < j->mRect.top || top >= j->mRect.bottom) - continue; - - int left2 = left1 - j->mRect.left; - - for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k) + for (Lines::const_iterator j = i->mLines.begin(); j != i->mLines.end(); ++j) { - if (left2 < k->mLeft || left2 >= k->mRight) + if (top >= j->mRect.bottom || bottom <= j->mRect.top) continue; - return k->mStyle; + for (Runs::const_iterator k = j->mRuns.begin(); k != j->mRuns.end(); ++k) + if (!Font || k->mStyle->mFont == Font) + visitor(*i, *j, *k); } } } - return nullptr; - } + template + void visitRuns(int top, int bottom, Visitor const& visitor) const + { + visitRuns(top, bottom, nullptr, visitor); + } - MyGUI::IFont* affectedFont (StyleImpl* style) - { - for (Styles::iterator i = mStyles.begin (); i != mStyles.end (); ++i) - if (&*i == style) - return i->mFont; - return nullptr; - } + /// hit test with a margin for error. only hits on interactive text fragments are reported. + StyleImpl* hitTestWithMargin(int left, int top) + { + StyleImpl* hit = hitTest(left, top); + if (hit && hit->mInteractiveId != 0) + return hit; - struct Typesetter; -}; + const int maxMargin = 10; + for (int margin = 1; margin < maxMargin; ++margin) + { + for (int i = 0; i < 4; ++i) + { + if (i == 0) + hit = hitTest(left, top - margin); + else if (i == 1) + hit = hitTest(left, top + margin); + else if (i == 2) + hit = hitTest(left - margin, top); + else + hit = hitTest(left + margin, top); -struct TypesetBookImpl::Typesetter : BookTypesetter -{ - struct PartialText { - StyleImpl *mStyle; - Utf8Stream::Point mBegin; - Utf8Stream::Point mEnd; - int mWidth; + if (hit && hit->mInteractiveId != 0) + return hit; + } + } + return nullptr; + } - PartialText( StyleImpl *style, Utf8Stream::Point begin, Utf8Stream::Point end, int width) : - mStyle(style), mBegin(begin), mEnd(end), mWidth(width) - {} + StyleImpl* hitTest(int left, int top) const + { + for (Sections::const_iterator i = mSections.begin(); i != mSections.end(); ++i) + { + if (top < i->mRect.top || top >= i->mRect.bottom) + continue; + + int left1 = left - i->mRect.left; + + for (Lines::const_iterator j = i->mLines.begin(); j != i->mLines.end(); ++j) + { + if (top < j->mRect.top || top >= j->mRect.bottom) + continue; + + int left2 = left1 - j->mRect.left; + + for (Runs::const_iterator k = j->mRuns.begin(); k != j->mRuns.end(); ++k) + { + if (left2 < k->mLeft || left2 >= k->mRight) + continue; + + return k->mStyle; + } + } + } + + return nullptr; + } + + MyGUI::IFont* affectedFont(StyleImpl* style) + { + for (Styles::iterator i = mStyles.begin(); i != mStyles.end(); ++i) + if (&*i == style) + return i->mFont; + return nullptr; + } + + struct Typesetter; }; - typedef TypesetBookImpl Book; - typedef std::shared_ptr BookPtr; - typedef std::vector::const_iterator PartialTextConstIterator; - - int mPageWidth; - int mPageHeight; - - BookPtr mBook; - Section * mSection; - Line * mLine; - Run * mRun; - - std::vector mSectionAlignment; - std::vector mPartialWhitespace; - std::vector mPartialWord; - - Book::Content const * mCurrentContent; - Alignment mCurrentAlignment; - - Typesetter (size_t width, size_t height) : - mPageWidth (width), mPageHeight(height), - mSection (nullptr), mLine (nullptr), mRun (nullptr), - mCurrentContent (nullptr), - mCurrentAlignment (AlignLeft) + struct TypesetBookImpl::Typesetter : BookTypesetter { - mBook = std::make_shared (); - } + struct PartialText + { + StyleImpl* mStyle; + Utf8Stream::Point mBegin; + Utf8Stream::Point mEnd; + int mWidth; - virtual ~Typesetter () - { - } + PartialText(StyleImpl* style, Utf8Stream::Point begin, Utf8Stream::Point end, int width) + : mStyle(style) + , mBegin(begin) + , mEnd(end) + , mWidth(width) + { + } + }; - Style * createStyle (const std::string& fontName, const Colour& fontColour, bool useBookFont) override - { - std::string fullFontName; - if (fontName.empty()) - fullFontName = MyGUI::FontManager::getInstance().getDefaultFont(); - else - fullFontName = fontName; + typedef TypesetBookImpl Book; + typedef std::shared_ptr BookPtr; + typedef std::vector::const_iterator PartialTextConstIterator; - if (useBookFont) - fullFontName = "Journalbook " + fullFontName; + int mPageWidth; + int mPageHeight; - for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) - if (i->match (fullFontName.c_str(), fontColour, fontColour, fontColour, 0)) - return &*i; + BookPtr mBook; + Section* mSection; + Line* mLine; + Run* mRun; - MyGUI::IFont* font = MyGUI::FontManager::getInstance().getByName(fullFontName); - if (!font) - throw std::runtime_error(std::string("can't find font ") + fullFontName); + std::vector mSectionAlignment; + std::vector mPartialWhitespace; + std::vector mPartialWord; - StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); - style.mFont = font; - style.mHotColour = fontColour; - style.mActiveColour = fontColour; - style.mNormalColour = fontColour; - style.mInteractiveId = 0; + Book::Content const* mCurrentContent; + Alignment mCurrentAlignment; - return &style; - } + Typesetter(size_t width, size_t height) + : mPageWidth(width) + , mPageHeight(height) + , mSection(nullptr) + , mLine(nullptr) + , mRun(nullptr) + , mCurrentContent(nullptr) + , mCurrentAlignment(AlignLeft) + { + mBook = std::make_shared(); + } - Style* createHotStyle (Style* baseStyle, const Colour& normalColour, const Colour& hoverColour, - const Colour& activeColour, InteractiveId id, bool unique) override - { - StyleImpl* BaseStyle = static_cast (baseStyle); + virtual ~Typesetter() {} - if (!unique) - for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) - if (i->match (BaseStyle->mFont, hoverColour, activeColour, normalColour, id)) + Style* createStyle(const std::string& fontName, const Colour& fontColour, bool useBookFont) override + { + std::string fullFontName; + if (fontName.empty()) + fullFontName = MyGUI::FontManager::getInstance().getDefaultFont(); + else + fullFontName = fontName; + + if (useBookFont) + fullFontName = "Journalbook " + fullFontName; + + for (Styles::iterator i = mBook->mStyles.begin(); i != mBook->mStyles.end(); ++i) + if (i->match(fullFontName, fontColour, fontColour, fontColour, 0)) return &*i; - StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); + MyGUI::IFont* font = MyGUI::FontManager::getInstance().getByName(fullFontName); + if (!font) + throw std::runtime_error(std::string("can't find font ") + fullFontName); - style.mFont = BaseStyle->mFont; - style.mHotColour = hoverColour; - style.mActiveColour = activeColour; - style.mNormalColour = normalColour; - style.mInteractiveId = id; + StyleImpl& style = *mBook->mStyles.insert(mBook->mStyles.end(), StyleImpl()); + style.mFont = font; + style.mHotColour = fontColour; + style.mActiveColour = fontColour; + style.mNormalColour = fontColour; + style.mInteractiveId = 0; - return &style; - } + return &style; + } - void write (Style * style, Utf8Span text) override - { - Range range = mBook->addContent (text); - - writeImpl (static_cast (style), range.first, range.second); - } - - intptr_t addContent (Utf8Span text, bool select) override - { - add_partial_text(); - - Contents::iterator i = mBook->mContents.insert (mBook->mContents.end (), Content (text.first, text.second)); - - if (select) - mCurrentContent = &(*i); - - return reinterpret_cast (&(*i)); - } - - void selectContent (intptr_t contentHandle) override - { - add_partial_text(); - - mCurrentContent = reinterpret_cast (contentHandle); - } - - void write (Style * style, size_t begin, size_t end) override - { - assert (mCurrentContent != nullptr); - assert (end <= mCurrentContent->size ()); - assert (begin <= mCurrentContent->size ()); - - Utf8Point begin_ = mCurrentContent->data() + begin; - Utf8Point end_ = mCurrentContent->data() + end; - - writeImpl (static_cast (style), begin_, end_); - } - - void lineBreak (float margin) override - { - assert (margin == 0); //TODO: figure out proper behavior here... - - add_partial_text(); - - mRun = nullptr; - mLine = nullptr; - } - - void sectionBreak (int margin) override - { - add_partial_text(); - - if (mBook->mSections.size () > 0) + Style* createHotStyle(Style* baseStyle, const Colour& normalColour, const Colour& hoverColour, + const Colour& activeColour, InteractiveId id, bool unique) override { + StyleImpl* BaseStyle = static_cast(baseStyle); + + if (!unique) + for (Styles::iterator i = mBook->mStyles.begin(); i != mBook->mStyles.end(); ++i) + if (i->match(BaseStyle->mFont, hoverColour, activeColour, normalColour, id)) + return &*i; + + StyleImpl& style = *mBook->mStyles.insert(mBook->mStyles.end(), StyleImpl()); + + style.mFont = BaseStyle->mFont; + style.mHotColour = hoverColour; + style.mActiveColour = activeColour; + style.mNormalColour = normalColour; + style.mInteractiveId = id; + + return &style; + } + + void write(Style* style, Utf8Span text) override + { + Range range = mBook->addContent(text); + + writeImpl(static_cast(style), range.first, range.second); + } + + intptr_t addContent(Utf8Span text, bool select) override + { + add_partial_text(); + + Contents::iterator i = mBook->mContents.insert(mBook->mContents.end(), Content(text.first, text.second)); + + if (select) + mCurrentContent = &(*i); + + return reinterpret_cast(&(*i)); + } + + void selectContent(intptr_t contentHandle) override + { + add_partial_text(); + + mCurrentContent = reinterpret_cast(contentHandle); + } + + void write(Style* style, size_t begin, size_t end) override + { + assert(mCurrentContent != nullptr); + assert(end <= mCurrentContent->size()); + assert(begin <= mCurrentContent->size()); + + Utf8Point begin_ = mCurrentContent->data() + begin; + Utf8Point end_ = mCurrentContent->data() + end; + + writeImpl(static_cast(style), begin_, end_); + } + + void lineBreak(float margin) override + { + assert(margin == 0); // TODO: figure out proper behavior here... + + add_partial_text(); + mRun = nullptr; mLine = nullptr; - mSection = nullptr; - - if (mBook->mRect.bottom < (mBook->mSections.back ().mRect.bottom + margin)) - mBook->mRect.bottom = (mBook->mSections.back ().mRect.bottom + margin); } - } - void setSectionAlignment (Alignment sectionAlignment) override - { - add_partial_text(); - - if (mSection != nullptr) - mSectionAlignment.back () = sectionAlignment; - mCurrentAlignment = sectionAlignment; - } - - TypesetBook::Ptr complete () override - { - int curPageStart = 0; - int curPageStop = 0; - - add_partial_text(); - - std::vector ::iterator sa = mSectionAlignment.begin (); - for (Sections::iterator i = mBook->mSections.begin (); i != mBook->mSections.end (); ++i, ++sa) + void sectionBreak(int margin) override { - // apply alignment to individual lines... - for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) - { - int width = j->mRect.width (); - int excess = mPageWidth - width; + add_partial_text(); - switch (*sa) + if (mBook->mSections.size() > 0) + { + mRun = nullptr; + mLine = nullptr; + mSection = nullptr; + + if (mBook->mRect.bottom < (mBook->mSections.back().mRect.bottom + margin)) + mBook->mRect.bottom = (mBook->mSections.back().mRect.bottom + margin); + } + } + + void setSectionAlignment(Alignment sectionAlignment) override + { + add_partial_text(); + + if (mSection != nullptr) + mSectionAlignment.back() = sectionAlignment; + mCurrentAlignment = sectionAlignment; + } + + TypesetBook::Ptr complete() override + { + int curPageStart = 0; + int curPageStop = 0; + + add_partial_text(); + + std::vector::iterator sa = mSectionAlignment.begin(); + for (Sections::iterator i = mBook->mSections.begin(); i != mBook->mSections.end(); ++i, ++sa) + { + // apply alignment to individual lines... + for (Lines::iterator j = i->mLines.begin(); j != i->mLines.end(); ++j) { - default: - case AlignLeft: j->mRect.left = 0; break; - case AlignCenter: j->mRect.left = excess/2; break; - case AlignRight: j->mRect.left = excess; break; + int width = j->mRect.width(); + int excess = mPageWidth - width; + + switch (*sa) + { + default: + case AlignLeft: + j->mRect.left = 0; + break; + case AlignCenter: + j->mRect.left = excess / 2; + break; + case AlignRight: + j->mRect.left = excess; + break; + } + + j->mRect.right = j->mRect.left + width; } - j->mRect.right = j->mRect.left + width; - } - - if (curPageStop == curPageStart) - { - curPageStart = i->mRect.top; - curPageStop = i->mRect.top; - } - - int spaceLeft = mPageHeight - (curPageStop - curPageStart); - int sectionHeight = i->mRect.height (); - - // This is NOT equal to i->mRect.height(), which doesn't account for section breaks. - int spaceRequired = (i->mRect.bottom - curPageStop); - if (curPageStart == curPageStop) // If this is a new page, the section break is not needed - spaceRequired = i->mRect.height(); - - if (spaceRequired <= mPageHeight) - { - if (spaceRequired > spaceLeft) + if (curPageStop == curPageStart) { - // The section won't completely fit on the current page. Finish the current page and start a new one. - assert (curPageStart != curPageStop); + curPageStart = i->mRect.top; + curPageStop = i->mRect.top; + } - mBook->mPages.push_back (Page (curPageStart, curPageStop)); + int spaceLeft = mPageHeight - (curPageStop - curPageStart); + int sectionHeight = i->mRect.height(); + + // This is NOT equal to i->mRect.height(), which doesn't account for section breaks. + int spaceRequired = (i->mRect.bottom - curPageStop); + if (curPageStart == curPageStop) // If this is a new page, the section break is not needed + spaceRequired = i->mRect.height(); + + if (spaceRequired <= mPageHeight) + { + if (spaceRequired > spaceLeft) + { + // The section won't completely fit on the current page. Finish the current page and start a new + // one. + assert(curPageStart != curPageStop); + + mBook->mPages.push_back(Page(curPageStart, curPageStop)); + + curPageStart = i->mRect.top; + curPageStop = i->mRect.bottom; + } + else + curPageStop = i->mRect.bottom; + } + else + { + // The section won't completely fit on the current page. Finish the current page and start a new + // one. + mBook->mPages.push_back(Page(curPageStart, curPageStop)); curPageStart = i->mRect.top; curPageStop = i->mRect.bottom; - } - else + + // split section + int sectionHeightLeft = sectionHeight; + while (sectionHeightLeft >= mPageHeight) + { + // Adjust to the top of the first line that does not fit on the current page anymore + int splitPos = curPageStop; + for (Lines::iterator j = i->mLines.begin(); j != i->mLines.end(); ++j) + { + if (j->mRect.bottom > curPageStart + mPageHeight) + { + splitPos = j->mRect.top; + break; + } + } + + mBook->mPages.push_back(Page(curPageStart, splitPos)); + curPageStart = splitPos; + curPageStop = splitPos; + + sectionHeightLeft = (i->mRect.bottom - splitPos); + } curPageStop = i->mRect.bottom; + } + } + + if (curPageStart != curPageStop) + mBook->mPages.push_back(Page(curPageStart, curPageStop)); + + return mBook; + } + + void writeImpl(StyleImpl* style, Utf8Stream::Point _begin, Utf8Stream::Point _end) + { + Utf8Stream stream(_begin, _end); + + while (!stream.eof()) + { + if (ucsLineBreak(stream.peek())) + { + add_partial_text(); + stream.consume(); + mLine = nullptr; + mRun = nullptr; + continue; + } + + if (ucsBreakingSpace(stream.peek()) && !mPartialWord.empty()) + add_partial_text(); + + int word_width = 0; + int space_width = 0; + + Utf8Stream::Point lead = stream.current(); + + while (!stream.eof() && !ucsLineBreak(stream.peek()) && ucsBreakingSpace(stream.peek())) + { + MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); + if (info.charFound) + space_width += static_cast(info.advance + info.bearingX); + stream.consume(); + } + + Utf8Stream::Point origin = stream.current(); + + while (!stream.eof() && !ucsLineBreak(stream.peek()) && !ucsBreakingSpace(stream.peek())) + { + MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); + if (info.charFound) + word_width += static_cast(info.advance + info.bearingX); + stream.consume(); + } + + Utf8Stream::Point extent = stream.current(); + + if (lead == extent) + break; + + if (lead != origin) + mPartialWhitespace.emplace_back(style, lead, origin, space_width); + if (origin != extent) + mPartialWord.emplace_back(style, origin, extent, word_width); + } + } + + void add_partial_text() + { + if (mPartialWhitespace.empty() && mPartialWord.empty()) + return; + + int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); + int space_width = 0; + int word_width = 0; + + for (PartialTextConstIterator i = mPartialWhitespace.begin(); i != mPartialWhitespace.end(); ++i) + space_width += i->mWidth; + for (PartialTextConstIterator i = mPartialWord.begin(); i != mPartialWord.end(); ++i) + word_width += i->mWidth; + + int left = mLine ? mLine->mRect.right : 0; + + if (left + space_width + word_width > mPageWidth) + { + mLine = nullptr; + mRun = nullptr; + left = 0; } else { - // The section won't completely fit on the current page. Finish the current page and start a new one. - mBook->mPages.push_back (Page (curPageStart, curPageStop)); - - curPageStart = i->mRect.top; - curPageStop = i->mRect.bottom; - - //split section - int sectionHeightLeft = sectionHeight; - while (sectionHeightLeft >= mPageHeight) + for (PartialTextConstIterator i = mPartialWhitespace.begin(); i != mPartialWhitespace.end(); ++i) { - // Adjust to the top of the first line that does not fit on the current page anymore - int splitPos = curPageStop; - for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) - { - if (j->mRect.bottom > curPageStart + mPageHeight) - { - splitPos = j->mRect.top; - break; - } - } + int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; - mBook->mPages.push_back (Page (curPageStart, splitPos)); - curPageStart = splitPos; - curPageStop = splitPos; + append_run(i->mStyle, i->mBegin, i->mEnd, 0, left + i->mWidth, top + fontHeight); - sectionHeightLeft = (i->mRect.bottom - splitPos); + left = mLine->mRect.right; } - curPageStop = i->mRect.bottom; - } - } - - if (curPageStart != curPageStop) - mBook->mPages.push_back (Page (curPageStart, curPageStop)); - - return mBook; - } - - void writeImpl (StyleImpl * style, Utf8Stream::Point _begin, Utf8Stream::Point _end) - { - Utf8Stream stream (_begin, _end); - - while (!stream.eof ()) - { - if (ucsLineBreak (stream.peek ())) - { - add_partial_text(); - stream.consume (); - mLine = nullptr, mRun = nullptr; - continue; } - if (ucsBreakingSpace (stream.peek ()) && !mPartialWord.empty()) - add_partial_text(); - - int word_width = 0; - int space_width = 0; - - Utf8Stream::Point lead = stream.current (); - - while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ())) - { - MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); - if (info.charFound) - space_width += static_cast(info.advance + info.bearingX); - stream.consume (); - } - - Utf8Stream::Point origin = stream.current (); - - while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ())) - { - MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); - if (info.charFound) - word_width += static_cast(info.advance + info.bearingX); - stream.consume (); - } - - Utf8Stream::Point extent = stream.current (); - - if (lead == extent) - break; - - if ( lead != origin ) - mPartialWhitespace.emplace_back(style, lead, origin, space_width); - if ( origin != extent ) - mPartialWord.emplace_back(style, origin, extent, word_width); - } - } - - void add_partial_text () - { - if (mPartialWhitespace.empty() && mPartialWord.empty()) - return; - - int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); - int space_width = 0; - int word_width = 0; - - for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) - space_width += i->mWidth; - for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) - word_width += i->mWidth; - - int left = mLine ? mLine->mRect.right : 0; - - if (left + space_width + word_width > mPageWidth) - { - mLine = nullptr, mRun = nullptr, left = 0; - } - else - { - for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) + for (PartialTextConstIterator i = mPartialWord.begin(); i != mPartialWord.end(); ++i) { int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; - append_run ( i->mStyle, i->mBegin, i->mEnd, 0, left + i->mWidth, top + fontHeight); + append_run(i->mStyle, i->mBegin, i->mEnd, i->mEnd - i->mBegin, left + i->mWidth, top + fontHeight); left = mLine->mRect.right; } + + mPartialWhitespace.clear(); + mPartialWord.clear(); } - for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) + void append_run(StyleImpl* style, Utf8Stream::Point begin, Utf8Stream::Point end, int pc, int right, int bottom) { - int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; - - append_run (i->mStyle, i->mBegin, i->mEnd, i->mEnd - i->mBegin, left + i->mWidth, top + fontHeight); - - left = mLine->mRect.right; - } - - mPartialWhitespace.clear(); - mPartialWord.clear(); - } - - void append_run (StyleImpl * style, Utf8Stream::Point begin, Utf8Stream::Point end, int pc, int right, int bottom) - { - if (mSection == nullptr) - { - mBook->mSections.push_back (Section ()); - mSection = &mBook->mSections.back (); - mSection->mRect = MyGUI::IntRect (0, mBook->mRect.bottom, 0, mBook->mRect.bottom); - mSectionAlignment.push_back (mCurrentAlignment); - } - - if (mLine == nullptr) - { - mSection->mLines.push_back (Line ()); - mLine = &mSection->mLines.back (); - mLine->mRect = MyGUI::IntRect (0, mSection->mRect.bottom, 0, mBook->mRect.bottom); - } - - if (mBook->mRect.right < right) - mBook->mRect.right = right; - - if (mBook->mRect.bottom < bottom) - mBook->mRect.bottom = bottom; - - if (mSection->mRect.right < right) - mSection->mRect.right = right; - - if (mSection->mRect.bottom < bottom) - mSection->mRect.bottom = bottom; - - if (mLine->mRect.right < right) - mLine->mRect.right = right; - - if (mLine->mRect.bottom < bottom) - mLine->mRect.bottom = bottom; - - if (mRun == nullptr || mRun->mStyle != style || mRun->mRange.second != begin) - { - int left = mRun ? mRun->mRight : mLine->mRect.left; - - mLine->mRuns.push_back (Run ()); - mRun = &mLine->mRuns.back (); - mRun->mStyle = style; - mRun->mLeft = left; - mRun->mRight = right; - mRun->mRange.first = begin; - mRun->mRange.second = end; - mRun->mPrintableChars = pc; - //Run->Locale = Locale; - } - else - { - mRun->mRight = right; - mRun->mRange.second = end; - mRun->mPrintableChars += pc; - } - } -}; - -BookTypesetter::Ptr BookTypesetter::create (int pageWidth, int pageHeight) -{ - return std::make_shared (pageWidth, pageHeight); -} - -namespace -{ - struct RenderXform - { - public: - - float clipTop; - float clipLeft; - float clipRight; - float clipBottom; - - float absoluteLeft; - float absoluteTop; - float leftOffset; - float topOffset; - - float pixScaleX; - float pixScaleY; - float hOffset; - float vOffset; - - RenderXform (MyGUI::ICroppedRectangle* croppedParent, MyGUI::RenderTargetInfo const & renderTargetInfo) - { - clipTop = static_cast(croppedParent->_getMarginTop()); - clipLeft = static_cast(croppedParent->_getMarginLeft ()); - clipRight = static_cast(croppedParent->getWidth () - croppedParent->_getMarginRight ()); - clipBottom = static_cast(croppedParent->getHeight() - croppedParent->_getMarginBottom()); - - absoluteLeft = static_cast(croppedParent->getAbsoluteLeft()); - absoluteTop = static_cast(croppedParent->getAbsoluteTop()); - leftOffset = static_cast(renderTargetInfo.leftOffset); - topOffset = static_cast(renderTargetInfo.topOffset); - - pixScaleX = renderTargetInfo.pixScaleX; - pixScaleY = renderTargetInfo.pixScaleY; - hOffset = renderTargetInfo.hOffset; - vOffset = renderTargetInfo.vOffset; - } - - bool clip (MyGUI::FloatRect & vr, MyGUI::FloatRect & tr) - { - if (vr.bottom <= clipTop || vr.right <= clipLeft || - vr.left >= clipRight || vr.top >= clipBottom ) - return false; - - if (vr.top < clipTop) + if (mSection == nullptr) { - tr.top += tr.height () * (clipTop - vr.top) / vr.height (); - vr.top = clipTop; + mBook->mSections.push_back(Section()); + mSection = &mBook->mSections.back(); + mSection->mRect = MyGUI::IntRect(0, mBook->mRect.bottom, 0, mBook->mRect.bottom); + mSectionAlignment.push_back(mCurrentAlignment); } - if (vr.left < clipLeft) + if (mLine == nullptr) { - tr.left += tr.width () * (clipLeft - vr.left) / vr.width (); - vr.left = clipLeft; + mSection->mLines.push_back(Line()); + mLine = &mSection->mLines.back(); + mLine->mRect = MyGUI::IntRect(0, mSection->mRect.bottom, 0, mBook->mRect.bottom); } - if (vr.right > clipRight) + if (mBook->mRect.right < right) + mBook->mRect.right = right; + + if (mBook->mRect.bottom < bottom) + mBook->mRect.bottom = bottom; + + if (mSection->mRect.right < right) + mSection->mRect.right = right; + + if (mSection->mRect.bottom < bottom) + mSection->mRect.bottom = bottom; + + if (mLine->mRect.right < right) + mLine->mRect.right = right; + + if (mLine->mRect.bottom < bottom) + mLine->mRect.bottom = bottom; + + if (mRun == nullptr || mRun->mStyle != style || mRun->mRange.second != begin) { - tr.right -= tr.width () * (vr.right - clipRight) / vr.width (); - vr.right = clipRight; - } + int left = mRun ? mRun->mRight : mLine->mRect.left; - if (vr.bottom > clipBottom) + mLine->mRuns.push_back(Run()); + mRun = &mLine->mRuns.back(); + mRun->mStyle = style; + mRun->mLeft = left; + mRun->mRight = right; + mRun->mRange.first = begin; + mRun->mRange.second = end; + mRun->mPrintableChars = pc; + // Run->Locale = Locale; + } + else { - tr.bottom -= tr.height () * (vr.bottom - clipBottom) / vr.height (); - vr.bottom = clipBottom; + mRun->mRight = right; + mRun->mRange.second = end; + mRun->mPrintableChars += pc; } - - return true; - } - - MyGUI::FloatPoint operator () (MyGUI::FloatPoint pt) - { - pt.left = absoluteLeft - leftOffset + pt.left; - pt.top = absoluteTop - topOffset + pt.top; - - pt.left = +(((pixScaleX * pt.left + hOffset) * 2.0f) - 1.0f); - pt.top = -(((pixScaleY * pt.top + vOffset) * 2.0f) - 1.0f); - - return pt; } }; - struct GlyphStream + BookTypesetter::Ptr BookTypesetter::create(int pageWidth, int pageHeight) { - float mZ; - uint32_t mC; - MyGUI::IFont* mFont; - MyGUI::FloatPoint mOrigin; - MyGUI::FloatPoint mCursor; - MyGUI::Vertex* mVertices; - RenderXform mRenderXform; - MyGUI::VertexColourType mVertexColourType; + return std::make_shared(pageWidth, pageHeight); + } - GlyphStream (MyGUI::IFont* font, float left, float top, float Z, - MyGUI::Vertex* vertices, RenderXform const & renderXform) : - mZ(Z), - mC(0), mFont (font), mOrigin (left, top), - mVertices (vertices), - mRenderXform (renderXform) + namespace + { + struct RenderXform { - assert(font != nullptr); - mVertexColourType = MyGUI::RenderManager::getInstance().getVertexFormat(); + public: + float clipTop; + float clipLeft; + float clipRight; + float clipBottom; + + float absoluteLeft; + float absoluteTop; + float leftOffset; + float topOffset; + + float pixScaleX; + float pixScaleY; + float hOffset; + float vOffset; + + RenderXform(MyGUI::ICroppedRectangle* croppedParent, MyGUI::RenderTargetInfo const& renderTargetInfo) + { + clipTop = static_cast(croppedParent->_getMarginTop()); + clipLeft = static_cast(croppedParent->_getMarginLeft()); + clipRight = static_cast(croppedParent->getWidth() - croppedParent->_getMarginRight()); + clipBottom = static_cast(croppedParent->getHeight() - croppedParent->_getMarginBottom()); + + absoluteLeft = static_cast(croppedParent->getAbsoluteLeft()); + absoluteTop = static_cast(croppedParent->getAbsoluteTop()); + leftOffset = static_cast(renderTargetInfo.leftOffset); + topOffset = static_cast(renderTargetInfo.topOffset); + + pixScaleX = renderTargetInfo.pixScaleX; + pixScaleY = renderTargetInfo.pixScaleY; + hOffset = renderTargetInfo.hOffset; + vOffset = renderTargetInfo.vOffset; + } + + bool clip(MyGUI::FloatRect& vr, MyGUI::FloatRect& tr) + { + if (vr.bottom <= clipTop || vr.right <= clipLeft || vr.left >= clipRight || vr.top >= clipBottom) + return false; + + if (vr.top < clipTop) + { + tr.top += tr.height() * (clipTop - vr.top) / vr.height(); + vr.top = clipTop; + } + + if (vr.left < clipLeft) + { + tr.left += tr.width() * (clipLeft - vr.left) / vr.width(); + vr.left = clipLeft; + } + + if (vr.right > clipRight) + { + tr.right -= tr.width() * (vr.right - clipRight) / vr.width(); + vr.right = clipRight; + } + + if (vr.bottom > clipBottom) + { + tr.bottom -= tr.height() * (vr.bottom - clipBottom) / vr.height(); + vr.bottom = clipBottom; + } + + return true; + } + + MyGUI::FloatPoint operator()(MyGUI::FloatPoint pt) + { + pt.left = absoluteLeft - leftOffset + pt.left; + pt.top = absoluteTop - topOffset + pt.top; + + pt.left = +(((pixScaleX * pt.left + hOffset) * 2.0f) - 1.0f); + pt.top = -(((pixScaleY * pt.top + vOffset) * 2.0f) - 1.0f); + + return pt; + } + }; + + struct GlyphStream + { + float mZ; + uint32_t mC; + MyGUI::IFont* mFont; + MyGUI::FloatPoint mOrigin; + MyGUI::FloatPoint mCursor; + MyGUI::Vertex* mVertices; + RenderXform mRenderXform; + MyGUI::VertexColourType mVertexColourType; + + GlyphStream(MyGUI::IFont* font, float left, float top, float Z, MyGUI::Vertex* vertices, + RenderXform const& renderXform) + : mZ(Z) + , mC(0) + , mFont(font) + , mOrigin(left, top) + , mVertices(vertices) + , mRenderXform(renderXform) + { + assert(font != nullptr); + mVertexColourType = MyGUI::RenderManager::getInstance().getVertexFormat(); + } + + ~GlyphStream() = default; + + MyGUI::Vertex* end() const { return mVertices; } + + void reset(float left, float top, MyGUI::Colour colour) + { + mC = MyGUI::texture_utility::toColourARGB(colour) | 0xFF000000; + MyGUI::texture_utility::convertColour(mC, mVertexColourType); + + mCursor.left = mOrigin.left + left; + mCursor.top = mOrigin.top + top; + } + + void emitGlyph(wchar_t ch) + { + MWGui::GlyphInfo info = GlyphInfo(mFont, ch); + + if (!info.charFound) + return; + + MyGUI::FloatRect vr; + + vr.left = mCursor.left + info.bearingX; + vr.top = mCursor.top + info.bearingY; + vr.right = vr.left + info.width; + vr.bottom = vr.top + info.height; + + MyGUI::FloatRect tr = info.uvRect; + + if (mRenderXform.clip(vr, tr)) + quad(vr, tr); + + mCursor.left += static_cast(info.bearingX + info.advance); + } + + void emitSpace(wchar_t ch) + { + MWGui::GlyphInfo info = GlyphInfo(mFont, ch); + + if (info.charFound) + mCursor.left += static_cast(info.bearingX + info.advance); + } + + private: + void quad(const MyGUI::FloatRect& vr, const MyGUI::FloatRect& tr) + { + vertex(vr.left, vr.top, tr.left, tr.top); + vertex(vr.right, vr.top, tr.right, tr.top); + vertex(vr.left, vr.bottom, tr.left, tr.bottom); + vertex(vr.right, vr.top, tr.right, tr.top); + vertex(vr.left, vr.bottom, tr.left, tr.bottom); + vertex(vr.right, vr.bottom, tr.right, tr.bottom); + } + + void vertex(float x, float y, float u, float v) + { + MyGUI::FloatPoint pt = mRenderXform(MyGUI::FloatPoint(x, y)); + + mVertices->x = pt.left; + mVertices->y = pt.top; + mVertices->z = mZ; + mVertices->u = u; + mVertices->v = v; + mVertices->colour = mC; + + ++mVertices; + } + }; + } + + class PageDisplay final : public MyGUI::ISubWidgetText + { + MYGUI_RTTI_DERIVED(PageDisplay) + protected: + typedef TypesetBookImpl::Section Section; + typedef TypesetBookImpl::Line Line; + typedef TypesetBookImpl::Run Run; + bool mIsPageReset; + size_t mPage; + + struct TextFormat : ISubWidget + { + typedef MyGUI::IFont* Id; + + Id mFont; + int mCountVertex; + MyGUI::ITexture* mTexture; + MyGUI::RenderItem* mRenderItem; + PageDisplay* mDisplay; + + TextFormat(MyGUI::IFont* id, PageDisplay* display) + : mFont(id) + , mCountVertex(0) + , mTexture(nullptr) + , mRenderItem(nullptr) + , mDisplay(display) + { + } + + void createDrawItem(MyGUI::ILayerNode* node) + { + assert(mRenderItem == nullptr); + + if (mTexture != nullptr) + { + mRenderItem = node->addToRenderItem(mTexture, false, false); + mRenderItem->addDrawItem(this, mCountVertex); + } + } + + void destroyDrawItem(MyGUI::ILayerNode* node) + { + assert(mTexture != nullptr ? mRenderItem != nullptr : mRenderItem == nullptr); + + if (mTexture != nullptr) + { + mRenderItem->removeDrawItem(this); + mRenderItem = nullptr; + } + } + + void doRender() override { mDisplay->doRender(*this); } + + // this isn't really a sub-widget, its just a "drawitem" which + // should have its own interface + void createDrawItem(MyGUI::ITexture* _texture, MyGUI::ILayerNode* _node) override {} + void destroyDrawItem() override {} + }; + + void resetPage() + { + mIsPageReset = true; + mPage = 0; } - ~GlyphStream () + void setPage(size_t page) { + mIsPageReset = false; + mPage = page; } - MyGUI::Vertex* end () const { return mVertices; } + bool isPageDifferent(size_t page) { return mIsPageReset || (mPage != page); } - void reset (float left, float top, MyGUI::Colour colour) + std::optional getAdjustedPos(int left, int top, bool move = false) { - mC = MyGUI::texture_utility::toColourARGB(colour) | 0xFF000000; - MyGUI::texture_utility::convertColour(mC, mVertexColourType); + if (!mBook) + return {}; - mCursor.left = mOrigin.left + left; - mCursor.top = mOrigin.top + top; + if (mPage >= mBook->mPages.size()) + return {}; + + MyGUI::IntPoint pos(left, top); + pos.left -= mCroppedParent->getAbsoluteLeft(); + pos.top -= mCroppedParent->getAbsoluteTop(); + pos.top += mViewTop; + return pos; } - void emitGlyph (wchar_t ch) - { - MWGui::GlyphInfo info = GlyphInfo(mFont, ch); + public: + typedef TypesetBookImpl::StyleImpl Style; + typedef std::map> ActiveTextFormats; - if (!info.charFound) + int mViewTop; + int mViewBottom; + + Style* mFocusItem; + bool mItemActive; + MyGUI::MouseButton mLastDown; + std::function mLinkClicked; + + std::shared_ptr mBook; + + MyGUI::ILayerNode* mNode; + ActiveTextFormats mActiveTextFormats; + + PageDisplay() + { + resetPage(); + mViewTop = 0; + mViewBottom = 0; + mFocusItem = nullptr; + mItemActive = false; + mNode = nullptr; + } + + void dirtyFocusItem() + { + if (mFocusItem != nullptr) + { + MyGUI::IFont* Font = mBook->affectedFont(mFocusItem); + + ActiveTextFormats::iterator i = mActiveTextFormats.find(Font); + + if (mNode) + mNode->outOfDate(i->second->mRenderItem); + } + } + + void onMouseLostFocus() + { + if (!mBook) return; - MyGUI::FloatRect vr; + if (mPage >= mBook->mPages.size()) + return; - vr.left = mCursor.left + info.bearingX; - vr.top = mCursor.top + info.bearingY; - vr.right = vr.left + info.width; - vr.bottom = vr.top + info.height; + dirtyFocusItem(); - MyGUI::FloatRect tr = info.uvRect; - - if (mRenderXform.clip (vr, tr)) - quad (vr, tr); - - mCursor.left += static_cast(info.bearingX + info.advance); + mFocusItem = nullptr; + mItemActive = false; } - void emitSpace (wchar_t ch) + void onMouseMove(int left, int top) { - MWGui::GlyphInfo info = GlyphInfo(mFont, ch); + Style* hit = nullptr; + if (auto pos = getAdjustedPos(left, top, true)) + if (pos->top <= mViewBottom) + hit = mBook->hitTestWithMargin(pos->left, pos->top); - if (info.charFound) - mCursor.left += static_cast(info.bearingX + info.advance); - } - - private: - - void quad (const MyGUI::FloatRect& vr, const MyGUI::FloatRect& tr) - { - vertex (vr.left, vr.top, tr.left, tr.top); - vertex (vr.right, vr.top, tr.right, tr.top); - vertex (vr.left, vr.bottom, tr.left, tr.bottom); - vertex (vr.right, vr.top, tr.right, tr.top); - vertex (vr.left, vr.bottom, tr.left, tr.bottom); - vertex (vr.right, vr.bottom, tr.right, tr.bottom); - } - - void vertex (float x, float y, float u, float v) - { - MyGUI::FloatPoint pt = mRenderXform (MyGUI::FloatPoint (x, y)); - - mVertices->x = pt.left; - mVertices->y = pt.top ; - mVertices->z = mZ; - mVertices->u = u; - mVertices->v = v; - mVertices->colour = mC; - - ++mVertices; - } - }; -} - -class PageDisplay final : public MyGUI::ISubWidgetText -{ - MYGUI_RTTI_DERIVED(PageDisplay) -protected: - - typedef TypesetBookImpl::Section Section; - typedef TypesetBookImpl::Line Line; - typedef TypesetBookImpl::Run Run; - bool mIsPageReset; - size_t mPage; - - struct TextFormat : ISubWidget - { - typedef MyGUI::IFont* Id; - - Id mFont; - int mCountVertex; - MyGUI::ITexture* mTexture; - MyGUI::RenderItem* mRenderItem; - PageDisplay * mDisplay; - - TextFormat (MyGUI::IFont* id, PageDisplay * display) : - mFont (id), - mCountVertex (0), - mTexture (nullptr), - mRenderItem (nullptr), - mDisplay (display) - { - } - - void createDrawItem (MyGUI::ILayerNode* node) - { - assert (mRenderItem == nullptr); - - if (mTexture != nullptr) + if (mLastDown == MyGUI::MouseButton::None) { - mRenderItem = node->addToRenderItem(mTexture, false, false); - mRenderItem->addDrawItem(this, mCountVertex); + if (hit != mFocusItem) + { + dirtyFocusItem(); + + mFocusItem = hit; + mItemActive = false; + + dirtyFocusItem(); + } + } + else if (mFocusItem != nullptr) + { + bool newItemActive = hit == mFocusItem; + + if (newItemActive != mItemActive) + { + mItemActive = newItemActive; + + dirtyFocusItem(); + } } } - void destroyDrawItem (MyGUI::ILayerNode* node) + void onMouseButtonPressed(int left, int top, MyGUI::MouseButton id) { - assert (mTexture != nullptr ? mRenderItem != nullptr : mRenderItem == nullptr); + auto pos = getAdjustedPos(left, top); - if (mTexture != nullptr) + if (pos && mLastDown == MyGUI::MouseButton::None) { - mRenderItem->removeDrawItem (this); - mRenderItem = nullptr; + mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin(pos->left, pos->top) : nullptr; + mItemActive = true; + + dirtyFocusItem(); + + mLastDown = id; } } - void doRender() override { mDisplay->doRender (*this); } - - // this isn't really a sub-widget, its just a "drawitem" which - // should have its own interface - void createDrawItem(MyGUI::ITexture* _texture, MyGUI::ILayerNode* _node) override {} - void destroyDrawItem() override {} - }; - - void resetPage() - { - mIsPageReset = true; - mPage = 0; - } - - void setPage(size_t page) - { - mIsPageReset = false; - mPage = page; - } - - bool isPageDifferent(size_t page) - { - return mIsPageReset || (mPage != page); - } - - std::optional getAdjustedPos(int left, int top, bool move = false) - { - if (!mBook) - return {}; - - if (mPage >= mBook->mPages.size()) - return {}; - - MyGUI::IntPoint pos (left, top); -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - // work around inconsistency in MyGUI where the mouse press coordinates aren't - // transformed by the current Layer (even though mouse *move* events are). - if(!move) - pos = mNode->getLayer()->getPosition(left, top); -#endif - pos.left -= mCroppedParent->getAbsoluteLeft (); - pos.top -= mCroppedParent->getAbsoluteTop (); - pos.top += mViewTop; - return pos; - } - -public: - - typedef TypesetBookImpl::StyleImpl Style; - typedef std::map > ActiveTextFormats; - - int mViewTop; - int mViewBottom; - - Style* mFocusItem; - bool mItemActive; - MyGUI::MouseButton mLastDown; - std::function mLinkClicked; - - - std::shared_ptr mBook; - - MyGUI::ILayerNode* mNode; - ActiveTextFormats mActiveTextFormats; - - PageDisplay () - { - resetPage (); - mViewTop = 0; - mViewBottom = 0; - mFocusItem = nullptr; - mItemActive = false; - mNode = nullptr; - } - - void dirtyFocusItem () - { - if (mFocusItem != nullptr) + void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) { - MyGUI::IFont* Font = mBook->affectedFont (mFocusItem); + auto pos = getAdjustedPos(left, top); - ActiveTextFormats::iterator i = mActiveTextFormats.find (Font); - - if (mNode) - mNode->outOfDate (i->second->mRenderItem); - } - } - - void onMouseLostFocus () - { - if (!mBook) - return; - - if (mPage >= mBook->mPages.size()) - return; - - dirtyFocusItem (); - - mFocusItem = nullptr; - mItemActive = false; - } - - void onMouseMove (int left, int top) - { - Style * hit = nullptr; - if(auto pos = getAdjustedPos(left, top, true)) - if(pos->top <= mViewBottom) - hit = mBook->hitTestWithMargin (pos->left, pos->top); - - if (mLastDown == MyGUI::MouseButton::None) - { - if (hit != mFocusItem) + if (pos && mLastDown == id) { - dirtyFocusItem (); + Style* item = pos->top <= mViewBottom ? mBook->hitTestWithMargin(pos->left, pos->top) : nullptr; + + bool clicked = mFocusItem == item; - mFocusItem = hit; mItemActive = false; - dirtyFocusItem (); + dirtyFocusItem(); + + mLastDown = MyGUI::MouseButton::None; + + if (clicked && mLinkClicked && item && item->mInteractiveId != 0) + mLinkClicked(item->mInteractiveId); } } - else - if (mFocusItem != nullptr) - { - bool newItemActive = hit == mFocusItem; - if (newItemActive != mItemActive) + void showPage(TypesetBook::Ptr book, size_t newPage) + { + std::shared_ptr newBook = std::dynamic_pointer_cast(book); + + if (mBook != newBook) { - mItemActive = newItemActive; + mFocusItem = nullptr; + mItemActive = 0; - dirtyFocusItem (); + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin(); i != mActiveTextFormats.end(); ++i) + { + if (mNode != nullptr && i->second != nullptr) + i->second->destroyDrawItem(mNode); + i->second.reset(); + } + + mActiveTextFormats.clear(); + + if (newBook != nullptr) + { + createActiveFormats(newBook); + + mBook = newBook; + setPage(newPage); + + if (newPage < mBook->mPages.size()) + { + mViewTop = mBook->mPages[newPage].first; + mViewBottom = mBook->mPages[newPage].second; + } + else + { + mViewTop = 0; + mViewBottom = 0; + } + } + else + { + mBook.reset(); + resetPage(); + mViewTop = 0; + mViewBottom = 0; + } } - } - } - - void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) - { - auto pos = getAdjustedPos(left, top); - - if (pos && mLastDown == MyGUI::MouseButton::None) - { - mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr; - mItemActive = true; - - dirtyFocusItem (); - - mLastDown = id; - } - } - - void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) - { - auto pos = getAdjustedPos(left, top); - - if (pos && mLastDown == id) - { - Style * item = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr; - - bool clicked = mFocusItem == item; - - mItemActive = false; - - dirtyFocusItem (); - - mLastDown = MyGUI::MouseButton::None; - - if (clicked && mLinkClicked && item && item->mInteractiveId != 0) - mLinkClicked (item->mInteractiveId); - } - } - - void showPage (TypesetBook::Ptr book, size_t newPage) - { - std::shared_ptr newBook = std::dynamic_pointer_cast (book); - - if (mBook != newBook) - { - mFocusItem = nullptr; - mItemActive = 0; - - for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) + else if (mBook && isPageDifferent(newPage)) { if (mNode != nullptr) - i->second->destroyDrawItem (mNode); - i->second.reset(); - } + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin(); i != mActiveTextFormats.end(); ++i) + mNode->outOfDate(i->second->mRenderItem); - mActiveTextFormats.clear (); + setPage(newPage); - if (newBook != nullptr) - { - createActiveFormats (newBook); - - mBook = newBook; - setPage (newPage); - - if (newPage < mBook->mPages.size ()) + if (newPage < mBook->mPages.size()) { - mViewTop = mBook->mPages [newPage].first; - mViewBottom = mBook->mPages [newPage].second; + mViewTop = mBook->mPages[newPage].first; + mViewBottom = mBook->mPages[newPage].second; } else { @@ -1078,333 +1105,301 @@ public: mViewBottom = 0; } } - else - { - mBook.reset (); - resetPage (); - mViewTop = 0; - mViewBottom = 0; - } } - else - if (mBook && isPageDifferent (newPage)) + + struct CreateActiveFormat { + PageDisplay* this_; + + CreateActiveFormat(PageDisplay* this_) + : this_(this_) + { + } + + void operator()(Section const& section, Line const& line, Run const& run) const + { + MyGUI::IFont* Font = run.mStyle->mFont; + + ActiveTextFormats::iterator j = this_->mActiveTextFormats.find(Font); + + if (j == this_->mActiveTextFormats.end()) + { + auto textFormat = std::make_unique(Font, this_); + + textFormat->mTexture = Font->getTextureFont(); + + j = this_->mActiveTextFormats.insert(std::make_pair(Font, std::move(textFormat))).first; + } + + j->second->mCountVertex += run.mPrintableChars * 6; + } + }; + + void createActiveFormats(std::shared_ptr newBook) + { + newBook->visitRuns(0, 0x7FFFFFFF, CreateActiveFormat(this)); + if (mNode != nullptr) - for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) - mNode->outOfDate(i->second->mRenderItem); + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin(); i != mActiveTextFormats.end(); ++i) + i->second->createDrawItem(mNode); + } - setPage (newPage); + void setVisible(bool newVisible) override + { + if (mVisible == newVisible) + return; - if (newPage < mBook->mPages.size ()) + mVisible = newVisible; + + if (mVisible) { - mViewTop = mBook->mPages [newPage].first; - mViewBottom = mBook->mPages [newPage].second; + // reset input state + mLastDown = MyGUI::MouseButton::None; + mFocusItem = nullptr; + mItemActive = 0; + } + + if (nullptr != mNode) + { + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin(); i != mActiveTextFormats.end(); ++i) + mNode->outOfDate(i->second->mRenderItem); + } + } + + void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) override + { + mNode = node; + + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin(); i != mActiveTextFormats.end(); ++i) + i->second->createDrawItem(node); + } + + struct RenderRun + { + PageDisplay* this_; + GlyphStream& glyphStream; + + RenderRun(PageDisplay* this_, GlyphStream& glyphStream) + : this_(this_) + , glyphStream(glyphStream) + { + } + + void operator()(Section const& section, Line const& line, Run const& run) const + { + bool isActive = run.mStyle->mInteractiveId && (run.mStyle == this_->mFocusItem); + + MyGUI::Colour colour = isActive + ? (this_->mItemActive ? run.mStyle->mActiveColour : run.mStyle->mHotColour) + : run.mStyle->mNormalColour; + + glyphStream.reset(static_cast(section.mRect.left + line.mRect.left + run.mLeft), + static_cast(line.mRect.top), colour); + + Utf8Stream stream(run.mRange); + + while (!stream.eof()) + { + Utf8Stream::UnicodeChar code_point = stream.consume(); + + if (ucsCarriageReturn(code_point)) + continue; + + if (!ucsSpace(code_point)) + glyphStream.emitGlyph(code_point); + else + glyphStream.emitSpace(code_point); + } + } + }; + + /* + queue up rendering operations for this text format + */ + void doRender(TextFormat& textFormat) + { + if (!mVisible) + return; + + MyGUI::Vertex* vertices = textFormat.mRenderItem->getCurrentVertexBuffer(); + + RenderXform renderXform(mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); + + float z = SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f; + + GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), + static_cast(mCoord.top - mViewTop), z /*mNode->getNodeDepth()*/, vertices, renderXform); + + int visit_top = (std::max)(mViewTop, mViewTop + int(renderXform.clipTop)); + int visit_bottom = (std::min)(mViewBottom, mViewTop + int(renderXform.clipBottom)); + + mBook->visitRuns(visit_top, visit_bottom, textFormat.mFont, RenderRun(this, glyphStream)); + + textFormat.mRenderItem->setLastVertexCount(glyphStream.end() - vertices); + } + + // ISubWidget should not necessarily be a drawitem + // in this case, it is not... + void doRender() override {} + + void _updateView() override + { + _checkMargin(); + + if (mNode != nullptr) + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin(); i != mActiveTextFormats.end(); ++i) + mNode->outOfDate(i->second->mRenderItem); + } + + void _correctView() override + { + _checkMargin(); + + if (mNode != nullptr) + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin(); i != mActiveTextFormats.end(); ++i) + mNode->outOfDate(i->second->mRenderItem); + } + + void destroyDrawItem() override + { + for (ActiveTextFormats::iterator i = mActiveTextFormats.begin(); i != mActiveTextFormats.end(); ++i) + i->second->destroyDrawItem(mNode); + + mNode = nullptr; + } + }; + + class BookPageImpl final : public BookPage + { + MYGUI_RTTI_DERIVED(BookPage) + public: + BookPageImpl() + : mPageDisplay(nullptr) + { + } + + void showPage(TypesetBook::Ptr book, size_t page) override { mPageDisplay->showPage(book, page); } + + void adviseLinkClicked(std::function linkClicked) override + { + mPageDisplay->mLinkClicked = linkClicked; + } + + void unadviseLinkClicked() override { mPageDisplay->mLinkClicked = std::function(); } + + protected: + void initialiseOverride() override + { + Base::initialiseOverride(); + + if (getSubWidgetText()) + { + mPageDisplay = getSubWidgetText()->castType(); } else { - mViewTop = 0; - mViewBottom = 0; + throw std::runtime_error("BookPage unable to find page display sub widget"); } } - } - struct CreateActiveFormat - { - PageDisplay * this_; - - CreateActiveFormat (PageDisplay * this_) : this_ (this_) {} - - void operator () (Section const & section, Line const & line, Run const & run) const + void onMouseLostFocus(Widget* _new) override { - MyGUI::IFont* Font = run.mStyle->mFont; - - ActiveTextFormats::iterator j = this_->mActiveTextFormats.find (Font); - - if (j == this_->mActiveTextFormats.end ()) - { - std::unique_ptr textFormat(new TextFormat (Font, this_)); - - textFormat->mTexture = Font->getTextureFont (); - - j = this_->mActiveTextFormats.insert (std::make_pair (Font, std::move(textFormat))).first; - } - - j->second->mCountVertex += run.mPrintableChars * 6; + // NOTE: MyGUI also fires eventMouseLostFocus for widgets that are about to be destroyed (if they had + // focus). Child widgets may already be destroyed! So be careful. + mPageDisplay->onMouseLostFocus(); } + + void onMouseMove(int left, int top) override { mPageDisplay->onMouseMove(left, top); } + + void onMouseButtonPressed(int left, int top, MyGUI::MouseButton id) override + { + mPageDisplay->onMouseButtonPressed(left, top, id); + } + + void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) override + { + mPageDisplay->onMouseButtonReleased(left, top, id); + } + + PageDisplay* mPageDisplay; }; - void createActiveFormats (std::shared_ptr newBook) + void BookPage::registerMyGUIComponents() { - newBook->visitRuns (0, 0x7FFFFFFF, CreateActiveFormat (this)); + MyGUI::FactoryManager& factory = MyGUI::FactoryManager::getInstance(); - if (mNode != nullptr) - for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) - i->second->createDrawItem (mNode); + factory.registerFactory("Widget"); + factory.registerFactory("BasisSkin"); } - void setVisible (bool newVisible) override + static bool ucsLineBreak(int codePoint) { - if (mVisible == newVisible) - return; + return codePoint == '\n'; + } - mVisible = newVisible; + static bool ucsCarriageReturn(int codePoint) + { + return codePoint == '\r'; + } - if (mVisible) + static bool ucsSpace(int codePoint) + { + switch (codePoint) { - // reset input state - mLastDown = MyGUI::MouseButton::None; - mFocusItem = nullptr; - mItemActive = 0; - } - - if (nullptr != mNode) - { - for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) - mNode->outOfDate(i->second->mRenderItem); + case 0x0020: // SPACE + case 0x00A0: // NO-BREAK SPACE + case 0x1680: // OGHAM SPACE MARK + case 0x180E: // MONGOLIAN VOWEL SEPARATOR + case 0x2000: // EN QUAD + case 0x2001: // EM QUAD + case 0x2002: // EN SPACE + case 0x2003: // EM SPACE + case 0x2004: // THREE-PER-EM SPACE + case 0x2005: // FOUR-PER-EM SPACE + case 0x2006: // SIX-PER-EM SPACE + case 0x2007: // FIGURE SPACE + case 0x2008: // PUNCTUATION SPACE + case 0x2009: // THIN SPACE + case 0x200A: // HAIR SPACE + case 0x200B: // ZERO WIDTH SPACE + case 0x202F: // NARROW NO-BREAK SPACE + case 0x205F: // MEDIUM MATHEMATICAL SPACE + case 0x3000: // IDEOGRAPHIC SPACE + case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE + return true; + default: + return false; } } - void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node) override + static bool ucsBreakingSpace(int codePoint) { - mNode = node; - - for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) - i->second->createDrawItem (node); - } - - struct RenderRun - { - PageDisplay * this_; - GlyphStream &glyphStream; - - RenderRun (PageDisplay * this_, GlyphStream &glyphStream) : - this_(this_), glyphStream (glyphStream) + switch (codePoint) { - } - - void operator () (Section const & section, Line const & line, Run const & run) const - { - bool isActive = run.mStyle->mInteractiveId && (run.mStyle == this_->mFocusItem); - - MyGUI::Colour colour = isActive ? (this_->mItemActive ? run.mStyle->mActiveColour: run.mStyle->mHotColour) : run.mStyle->mNormalColour; - - glyphStream.reset(static_cast(section.mRect.left + line.mRect.left + run.mLeft), static_cast(line.mRect.top), colour); - - Utf8Stream stream (run.mRange); - - while (!stream.eof ()) - { - Utf8Stream::UnicodeChar code_point = stream.consume (); - - if (ucsCarriageReturn (code_point)) - continue; - - if (!ucsSpace (code_point)) - glyphStream.emitGlyph (code_point); - else - glyphStream.emitSpace (code_point); - } - } - }; - - /* - queue up rendering operations for this text format - */ - void doRender(TextFormat & textFormat) - { - if (!mVisible) - return; - - MyGUI::Vertex* vertices = textFormat.mRenderItem->getCurrentVertexBuffer(); - - RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); - - GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), static_cast(mCoord.top - mViewTop), - -1 /*mNode->getNodeDepth()*/, vertices, renderXform); - - int visit_top = (std::max) (mViewTop, mViewTop + int (renderXform.clipTop )); - int visit_bottom = (std::min) (mViewBottom, mViewTop + int (renderXform.clipBottom)); - - mBook->visitRuns (visit_top, visit_bottom, textFormat.mFont, RenderRun (this, glyphStream)); - - textFormat.mRenderItem->setLastVertexCount(glyphStream.end () - vertices); - } - - // ISubWidget should not necessarily be a drawitem - // in this case, it is not... - void doRender() override { } - - void _updateView () override - { - _checkMargin(); - - if (mNode != nullptr) - for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) - mNode->outOfDate (i->second->mRenderItem); - } - - void _correctView() override - { - _checkMargin (); - - if (mNode != nullptr) - for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) - mNode->outOfDate (i->second->mRenderItem); - - } - - void destroyDrawItem() override - { - for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) - i->second->destroyDrawItem (mNode); - - mNode = nullptr; - } -}; - - -class BookPageImpl final : public BookPage -{ -MYGUI_RTTI_DERIVED(BookPage) -public: - - BookPageImpl() - : mPageDisplay(nullptr) - { - } - - void showPage (TypesetBook::Ptr book, size_t page) override - { - mPageDisplay->showPage (book, page); - } - - void adviseLinkClicked (std::function linkClicked) override - { - mPageDisplay->mLinkClicked = linkClicked; - } - - void unadviseLinkClicked () override - { - mPageDisplay->mLinkClicked = std::function (); - } - -protected: - - void initialiseOverride() override - { - Base::initialiseOverride(); - - if (getSubWidgetText()) - { - mPageDisplay = getSubWidgetText()->castType(); - } - else - { - throw std::runtime_error("BookPage unable to find page display sub widget"); + case 0x0020: // SPACE + // case 0x00A0: // NO-BREAK SPACE + case 0x1680: // OGHAM SPACE MARK + case 0x180E: // MONGOLIAN VOWEL SEPARATOR + case 0x2000: // EN QUAD + case 0x2001: // EM QUAD + case 0x2002: // EN SPACE + case 0x2003: // EM SPACE + case 0x2004: // THREE-PER-EM SPACE + case 0x2005: // FOUR-PER-EM SPACE + case 0x2006: // SIX-PER-EM SPACE + case 0x2007: // FIGURE SPACE + case 0x2008: // PUNCTUATION SPACE + case 0x2009: // THIN SPACE + case 0x200A: // HAIR SPACE + case 0x200B: // ZERO WIDTH SPACE + case 0x202F: // NARROW NO-BREAK SPACE + case 0x205F: // MEDIUM MATHEMATICAL SPACE + case 0x3000: // IDEOGRAPHIC SPACE + // case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE + return true; + default: + return false; } } - void onMouseLostFocus(Widget* _new) override - { - // NOTE: MyGUI also fires eventMouseLostFocus for widgets that are about to be destroyed (if they had focus). - // Child widgets may already be destroyed! So be careful. - mPageDisplay->onMouseLostFocus (); - } - - void onMouseMove(int left, int top) override - { - mPageDisplay->onMouseMove (left, top); - } - - void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) override - { - mPageDisplay->onMouseButtonPressed (left, top, id); - } - - void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) override - { - mPageDisplay->onMouseButtonReleased (left, top, id); - } - - PageDisplay* mPageDisplay; -}; - -void BookPage::registerMyGUIComponents () -{ - MyGUI::FactoryManager & factory = MyGUI::FactoryManager::getInstance(); - - factory.registerFactory("Widget"); - factory.registerFactory("BasisSkin"); -} - -static bool ucsLineBreak (int codePoint) -{ - return codePoint == '\n'; -} - -static bool ucsCarriageReturn (int codePoint) -{ - return codePoint == '\r'; -} - -static bool ucsSpace (int codePoint) -{ - switch (codePoint) - { - case 0x0020: // SPACE - case 0x00A0: // NO-BREAK SPACE - case 0x1680: // OGHAM SPACE MARK - case 0x180E: // MONGOLIAN VOWEL SEPARATOR - case 0x2000: // EN QUAD - case 0x2001: // EM QUAD - case 0x2002: // EN SPACE - case 0x2003: // EM SPACE - case 0x2004: // THREE-PER-EM SPACE - case 0x2005: // FOUR-PER-EM SPACE - case 0x2006: // SIX-PER-EM SPACE - case 0x2007: // FIGURE SPACE - case 0x2008: // PUNCTUATION SPACE - case 0x2009: // THIN SPACE - case 0x200A: // HAIR SPACE - case 0x200B: // ZERO WIDTH SPACE - case 0x202F: // NARROW NO-BREAK SPACE - case 0x205F: // MEDIUM MATHEMATICAL SPACE - case 0x3000: // IDEOGRAPHIC SPACE - case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE - return true; - default: - return false; - } -} - -static bool ucsBreakingSpace (int codePoint) -{ - switch (codePoint) - { - case 0x0020: // SPACE - //case 0x00A0: // NO-BREAK SPACE - case 0x1680: // OGHAM SPACE MARK - case 0x180E: // MONGOLIAN VOWEL SEPARATOR - case 0x2000: // EN QUAD - case 0x2001: // EM QUAD - case 0x2002: // EN SPACE - case 0x2003: // EM SPACE - case 0x2004: // THREE-PER-EM SPACE - case 0x2005: // FOUR-PER-EM SPACE - case 0x2006: // SIX-PER-EM SPACE - case 0x2007: // FIGURE SPACE - case 0x2008: // PUNCTUATION SPACE - case 0x2009: // THIN SPACE - case 0x200A: // HAIR SPACE - case 0x200B: // ZERO WIDTH SPACE - case 0x202F: // NARROW NO-BREAK SPACE - case 0x205F: // MEDIUM MATHEMATICAL SPACE - case 0x3000: // IDEOGRAPHIC SPACE - //case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE - return true; - default: - return false; - } -} - } diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 512a43992..af6d33ff2 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -2,12 +2,12 @@ #define MWGUI_BOOKPAGE_HPP #include "MyGUI_Colour.h" +#include "MyGUI_IFont.h" #include "MyGUI_Widget.h" -#include "MyGUI_FontManager.h" +#include #include #include -#include #include @@ -20,11 +20,11 @@ namespace MWGui /// the book page widget. struct TypesetBook { - typedef std::shared_ptr Ptr; + typedef std::shared_ptr Ptr; typedef intptr_t InteractiveId; /// Returns the number of pages in the document. - virtual size_t pageCount () const = 0; + virtual size_t pageCount() const = 0; /// Return the area covered by the document. The first /// integer is the maximum with of any line. This is not @@ -32,7 +32,7 @@ namespace MWGui /// it is the largest distance from the left edge to the /// right edge. The second integer is the height of all /// text combined prior to pagination. - virtual std::pair getSize () const = 0; + virtual std::pair getSize() const = 0; virtual ~TypesetBook() = default; }; @@ -55,14 +55,14 @@ namespace MWGui const MyGUI::GlyphInfo* gi = font->getGlyphInfo(ch); if (gi) { - const float scale = font->getDefaultHeight() / (float) fontHeight; + const float scale = font->getDefaultHeight() / (float)fontHeight; codePoint = gi->codePoint; - bearingX = (int) gi->bearingX / scale; - bearingY = (int) gi->bearingY / scale; - width = (int) gi->width / scale; - height = (int) gi->height / scale; - advance = (int) gi->advance / scale; + bearingX = (int)gi->bearingX / scale; + bearingY = (int)gi->bearingY / scale; + width = (int)gi->width / scale; + height = (int)gi->height / scale; + advance = (int)gi->advance / scale; uvRect = gi->uvRect; charFound = true; } @@ -82,18 +82,19 @@ namespace MWGui /// A factory class for creating a typeset book instance. struct BookTypesetter { - typedef std::shared_ptr Ptr; + typedef std::shared_ptr Ptr; typedef TypesetBook::InteractiveId InteractiveId; typedef MyGUI::Colour Colour; - typedef uint8_t const * Utf8Point; - typedef std::pair Utf8Span; + typedef uint8_t const* Utf8Point; + typedef std::pair Utf8Span; virtual ~BookTypesetter() = default; - enum Alignment { - AlignLeft = -1, + enum Alignment + { + AlignLeft = -1, AlignCenter = 0, - AlignRight = +1 + AlignRight = +1 }; /// Styles are used to control the character level formatting @@ -103,71 +104,71 @@ namespace MWGui struct Style; /// A factory function for creating the default implementation of a book typesetter - static Ptr create (int pageWidth, int pageHeight); + static Ptr create(int pageWidth, int pageHeight); /// Create a simple text style consisting of a font and a text color. - virtual Style* createStyle (const std::string& fontName, const Colour& colour, bool useBookFont=true) = 0; + virtual Style* createStyle(const std::string& fontName, const Colour& colour, bool useBookFont = true) = 0; /// Create a hyper-link style with a user-defined identifier based on an /// existing style. The unique flag forces a new instance of this style /// to be created even if an existing instance is present. - virtual Style* createHotStyle (Style * BaseStyle, const Colour& NormalColour, const Colour& HoverColour, - const Colour& ActiveColour, InteractiveId Id, bool Unique = true) = 0; + virtual Style* createHotStyle(Style* BaseStyle, const Colour& NormalColour, const Colour& HoverColour, + const Colour& ActiveColour, InteractiveId Id, bool Unique = true) + = 0; /// Insert a line break into the document. Newline characters in the input /// text have the same affect. The margin parameter adds additional space /// before the next line of text. - virtual void lineBreak (float margin = 0) = 0; + virtual void lineBreak(float margin = 0) = 0; /// Insert a section break into the document. This causes a new section /// to begin when additional text is inserted. Pagination attempts to keep /// sections together on a single page. The margin parameter adds additional space /// before the next line of text. - virtual void sectionBreak (int margin = 0) = 0; + virtual void sectionBreak(int margin = 0) = 0; /// Changes the alignment for the current section of text. - virtual void setSectionAlignment (Alignment sectionAlignment) = 0; + virtual void setSectionAlignment(Alignment sectionAlignment) = 0; // Layout a block of text with the specified style into the document. - virtual void write (Style * Style, Utf8Span Text) = 0; + virtual void write(Style* Style, Utf8Span Text) = 0; /// Adds a content block to the document without laying it out. An /// identifier is returned that can be used to refer to it. If select /// is true, the block is activated to be references by future writes. - virtual intptr_t addContent (Utf8Span Text, bool Select = true) = 0; + virtual intptr_t addContent(Utf8Span Text, bool Select = true) = 0; /// Select a previously created content block for future writes. - virtual void selectContent (intptr_t contentHandle) = 0; + virtual void selectContent(intptr_t contentHandle) = 0; /// Layout a span of the selected content block into the document /// using the specified style. - virtual void write (Style * Style, size_t Begin, size_t End) = 0; + virtual void write(Style* Style, size_t Begin, size_t End) = 0; /// Finalize the document layout, and return a pointer to it. - virtual TypesetBook::Ptr complete () = 0; + virtual TypesetBook::Ptr complete() = 0; }; /// An interface to the BookPage widget. class BookPage : public MyGUI::Widget { - MYGUI_RTTI_DERIVED(BookPage) + MYGUI_RTTI_DERIVED(BookPage) public: - typedef TypesetBook::InteractiveId InteractiveId; - typedef std::function ClickCallback; + typedef std::function ClickCallback; /// Make the widget display the specified page from the specified book. - virtual void showPage (TypesetBook::Ptr Book, size_t Page) = 0; + virtual void showPage(TypesetBook::Ptr Book, size_t Page) = 0; /// Set the callback for a clicking a hyper-link in the document. - virtual void adviseLinkClicked (ClickCallback callback) = 0; + virtual void adviseLinkClicked(ClickCallback callback) = 0; /// Clear the hyper-link click callback. - virtual void unadviseLinkClicked () = 0; + virtual void unadviseLinkClicked() = 0; /// Register the widget and associated sub-widget with MyGUI. Should be /// called once near the beginning of the program. - static void registerMyGUIComponents (); + static void registerMyGUIComponents(); }; } diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 86089051d..498a52c6d 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -1,12 +1,11 @@ #include "bookwindow.hpp" -#include #include +#include -#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -19,7 +18,7 @@ namespace MWGui { - BookWindow::BookWindow () + BookWindow::BookWindow() : BookWindowBase("openmw_book.layout") , mCurrentPage(0) , mTakeButtonShow(true) @@ -61,14 +60,15 @@ namespace MWGui if (mNextPageButton->getSize().width == 64) { // english button has a 7 pixel wide strip of garbage on its right edge - mNextPageButton->setSize(64-7, mNextPageButton->getSize().height); - mNextPageButton->setImageCoord(MyGUI::IntCoord(0,0,(64-7)*scale,mNextPageButton->getSize().height*scale)); + mNextPageButton->setSize(64 - 7, mNextPageButton->getSize().height); + mNextPageButton->setImageCoord( + MyGUI::IntCoord(0, 0, (64 - 7) * scale, mNextPageButton->getSize().height * scale)); } center(); } - void BookWindow::onMouseWheel(MyGUI::Widget *_sender, int _rel) + void BookWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { if (_rel < 0) nextPage(); @@ -81,7 +81,7 @@ namespace MWGui mPages.clear(); } - void BookWindow::setPtr (const MWWorld::Ptr& book) + void BookWindow::setPtr(const MWWorld::Ptr& book) { mBook = book; @@ -91,7 +91,7 @@ namespace MWGui clearPages(); mCurrentPage = 0; - MWWorld::LiveCellRef *ref = mBook.get(); + MWWorld::LiveCellRef* ref = mBook.get(); Formatting::BookFormatter formatter; mPages = formatter.markupToWidget(mLeftPage, ref->mBase->mText); @@ -110,7 +110,7 @@ namespace MWGui mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } - void BookWindow::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) + void BookWindow::onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character) { if (key == MyGUI::KeyCode::ArrowUp) prevPage(); @@ -124,38 +124,38 @@ namespace MWGui mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } - void BookWindow::onCloseButtonClicked (MyGUI::Widget* sender) + void BookWindow::onCloseButtonClicked(MyGUI::Widget* sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); } - void BookWindow::onTakeButtonClicked (MyGUI::Widget* sender) + void BookWindow::onTakeButtonClicked(MyGUI::Widget* sender) { - MWBase::Environment::get().getWindowManager()->playSound("Item Book Up"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Item Book Up")); MWWorld::ActionTake take(mBook); - take.execute (MWMechanics::getPlayer()); + take.execute(MWMechanics::getPlayer()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); } - void BookWindow::onNextPageButtonClicked (MyGUI::Widget* sender) + void BookWindow::onNextPageButtonClicked(MyGUI::Widget* sender) { nextPage(); } - void BookWindow::onPrevPageButtonClicked (MyGUI::Widget* sender) + void BookWindow::onPrevPageButtonClicked(MyGUI::Widget* sender) { prevPage(); } void BookWindow::updatePages() { - mLeftPageNumber->setCaption( MyGUI::utility::toString(mCurrentPage*2 + 1) ); - mRightPageNumber->setCaption( MyGUI::utility::toString(mCurrentPage*2 + 2) ); + mLeftPageNumber->setCaption(MyGUI::utility::toString(mCurrentPage * 2 + 1)); + mRightPageNumber->setCaption(MyGUI::utility::toString(mCurrentPage * 2 + 2)); MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - bool nextPageVisible = (mCurrentPage+1)*2 < mPages.size(); + bool nextPageVisible = (mCurrentPage + 1) * 2 < mPages.size(); mNextPageButton->setVisible(nextPageVisible); bool prevPageVisible = mCurrentPage != 0; mPrevPageButton->setVisible(prevPageVisible); @@ -168,17 +168,17 @@ namespace MWGui if (mPages.empty()) return; - MyGUI::Widget * paper; + MyGUI::Widget* paper; paper = mLeftPage->getChildAt(0); - paper->setCoord(paper->getPosition().left, -mPages[mCurrentPage*2].first, - paper->getWidth(), mPages[mCurrentPage*2].second); + paper->setCoord(paper->getPosition().left, -mPages[mCurrentPage * 2].first, paper->getWidth(), + mPages[mCurrentPage * 2].second); paper = mRightPage->getChildAt(0); - if ((mCurrentPage+1)*2 <= mPages.size()) + if ((mCurrentPage + 1) * 2 <= mPages.size()) { - paper->setCoord(paper->getPosition().left, -mPages[mCurrentPage*2+1].first, - paper->getWidth(), mPages[mCurrentPage*2+1].second); + paper->setCoord(paper->getPosition().left, -mPages[mCurrentPage * 2 + 1].first, paper->getWidth(), + mPages[mCurrentPage * 2 + 1].second); paper->setVisible(true); } else @@ -189,9 +189,9 @@ namespace MWGui void BookWindow::nextPage() { - if ((mCurrentPage+1)*2 < mPages.size()) + if ((mCurrentPage + 1) * 2 < mPages.size()) { - MWBase::Environment::get().getWindowManager()->playSound("book page2"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page2")); ++mCurrentPage; @@ -202,7 +202,7 @@ namespace MWGui { if (mCurrentPage > 0) { - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); --mCurrentPage; diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp index 116437f22..bf210c0b1 100644 --- a/apps/openmw/mwgui/bookwindow.hpp +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -11,51 +11,51 @@ namespace MWGui { class BookWindow : public BookWindowBase { - public: - BookWindow(); + public: + BookWindow(); - void setPtr(const MWWorld::Ptr& book) override; - void setInventoryAllowed(bool allowed); + void setPtr(const MWWorld::Ptr& book) override; + void setInventoryAllowed(bool allowed); - void onResChange(int, int) override { center(); } + void onResChange(int, int) override { center(); } - protected: - void onNextPageButtonClicked (MyGUI::Widget* sender); - void onPrevPageButtonClicked (MyGUI::Widget* sender); - void onCloseButtonClicked (MyGUI::Widget* sender); - void onTakeButtonClicked (MyGUI::Widget* sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void setTakeButtonShow(bool show); + protected: + void onNextPageButtonClicked(MyGUI::Widget* sender); + void onPrevPageButtonClicked(MyGUI::Widget* sender); + void onCloseButtonClicked(MyGUI::Widget* sender); + void onTakeButtonClicked(MyGUI::Widget* sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void setTakeButtonShow(bool show); - void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); + void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); - void nextPage(); - void prevPage(); + void nextPage(); + void prevPage(); - void updatePages(); - void clearPages(); + void updatePages(); + void clearPages(); - private: - typedef std::pair Page; - typedef std::vector Pages; + private: + typedef std::pair Page; + typedef std::vector Pages; - Gui::ImageButton* mCloseButton; - Gui::ImageButton* mTakeButton; - Gui::ImageButton* mNextPageButton; - Gui::ImageButton* mPrevPageButton; + Gui::ImageButton* mCloseButton; + Gui::ImageButton* mTakeButton; + Gui::ImageButton* mNextPageButton; + Gui::ImageButton* mPrevPageButton; - MyGUI::TextBox* mLeftPageNumber; - MyGUI::TextBox* mRightPageNumber; - MyGUI::Widget* mLeftPage; - MyGUI::Widget* mRightPage; + MyGUI::TextBox* mLeftPageNumber; + MyGUI::TextBox* mRightPageNumber; + MyGUI::Widget* mLeftPage; + MyGUI::Widget* mRightPage; - unsigned int mCurrentPage; // 0 is first page - Pages mPages; + unsigned int mCurrentPage; // 0 is first page + Pages mPages; - MWWorld::Ptr mBook; + MWWorld::Ptr mBook; - bool mTakeButtonShow; - bool mTakeButtonAllowed; + bool mTakeButtonShow; + bool mTakeButtonAllowed; }; } diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 12c3fd9e2..6d50b37fa 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -1,22 +1,25 @@ #include "charactercreation.hpp" +#include + #include #include #include #include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" +<<<<<<< HEAD /* Start of tes3mp addition @@ -31,9 +34,14 @@ #include "textinput.hpp" #include "race.hpp" #include "class.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "birth.hpp" -#include "review.hpp" +#include "class.hpp" #include "inventorywindow.hpp" +#include "race.hpp" +#include "review.hpp" +#include "textinput.hpp" namespace { @@ -54,15 +62,16 @@ namespace { number++; - std::string question = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_Question"); - std::string answer0 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerOne"); - std::string answer1 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerTwo"); - std::string answer2 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerThree"); + std::string question{ Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_Question") }; + std::string answer0{ Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerOne") }; + std::string answer1{ Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerTwo") }; + std::string answer2{ Fallback::Map::getString( + "Question_" + MyGUI::utility::toString(number) + "_AnswerThree") }; std::string sound = "vo\\misc\\chargen qa" + MyGUI::utility::toString(number) + ".wav"; - Response r0 = {answer0, ESM::Class::Combat}; - Response r1 = {answer1, ESM::Class::Magic}; - Response r2 = {answer2, ESM::Class::Stealth}; + Response r0 = { answer0, ESM::Class::Combat }; + Response r1 = { answer1, ESM::Class::Magic }; + Response r2 = { answer2, ESM::Class::Stealth }; // randomize order in which responses are displayed int order = Misc::Rng::rollDice(6); @@ -70,26 +79,19 @@ namespace switch (order) { case 0: - return {question, {r0, r1, r2}, sound}; + return { question, { r0, r1, r2 }, sound }; case 1: - return {question, {r0, r2, r1}, sound}; + return { question, { r0, r2, r1 }, sound }; case 2: - return {question, {r1, r0, r2}, sound}; + return { question, { r1, r0, r2 }, sound }; case 3: - return {question, {r1, r2, r0}, sound}; + return { question, { r1, r2, r0 }, sound }; case 4: - return {question, {r2, r0, r1}, sound}; + return { question, { r2, r0, r1 }, sound }; default: - return {question, {r2, r1, r0}, sound}; + return { question, { r2, r1, r0 }, sound }; } } - - void updatePlayerHealth() - { - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); - npcStats.updateHealth(); - } } namespace MWGui @@ -98,15 +100,6 @@ namespace MWGui CharacterCreation::CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem) : mParent(parent) , mResourceSystem(resourceSystem) - , mNameDialog(nullptr) - , mRaceDialog(nullptr) - , mClassChoiceDialog(nullptr) - , mGenerateClassQuestionDialog(nullptr) - , mGenerateClassResultDialog(nullptr) - , mPickClassDialog(nullptr) - , mCreateClassDialog(nullptr) - , mBirthSignDialog(nullptr) - , mReviewDialog(nullptr) , mGenerateClassStep(0) { mCreationStage = CSE_NotStarted; @@ -118,61 +111,48 @@ namespace MWGui mGenerateClassSpecializations[2] = 0; // Setup player stats - for (int i = 0; i < ESM::Attribute::Length; ++i) - mPlayerAttributes.emplace(ESM::Attribute::sAttributeIds[i], MWMechanics::AttributeValue()); + const auto& store = MWBase::Environment::get().getWorld()->getStore(); + for (const ESM::Attribute& attribute : store.get()) + mPlayerAttributes.emplace(attribute.mId, MWMechanics::AttributeValue()); - for (int i = 0; i < ESM::Skill::Length; ++i) - mPlayerSkillValues.emplace(ESM::Skill::sSkillIds[i], MWMechanics::SkillValue()); + for (const auto& skill : store.get()) + mPlayerSkillValues.emplace(skill.mId, MWMechanics::SkillValue()); } - void CharacterCreation::setValue (const std::string& id, const MWMechanics::AttributeValue& value) + void CharacterCreation::setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) { - static const char *ids[] = - { - "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", - "AttribVal5", "AttribVal6", "AttribVal7", "AttribVal8", 0 - }; - - for (int i=0; ids[i]; ++i) - { - if (ids[i]==id) - { - mPlayerAttributes[static_cast(i)] = value; - if (mReviewDialog) - mReviewDialog->setAttribute(static_cast(i), value); - - break; - } - } + mPlayerAttributes[id] = value; + if (mReviewDialog) + mReviewDialog->setAttribute(id, value); } - void CharacterCreation::setValue (const std::string& id, const MWMechanics::DynamicStat& value) + void CharacterCreation::setValue(std::string_view id, const MWMechanics::DynamicStat& value) { if (mReviewDialog) { if (id == "HBar") { - mReviewDialog->setHealth (value); + mReviewDialog->setHealth(value); } else if (id == "MBar") { - mReviewDialog->setMagicka (value); + mReviewDialog->setMagicka(value); } else if (id == "FBar") { - mReviewDialog->setFatigue (value); + mReviewDialog->setFatigue(value); } } } - void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) + void CharacterCreation::setValue(ESM::RefId id, const MWMechanics::SkillValue& value) { - mPlayerSkillValues[parSkill] = value; + mPlayerSkillValues[id] = value; if (mReviewDialog) - mReviewDialog->setSkillValue(parSkill, value); + mReviewDialog->setSkillValue(id, value); } - void CharacterCreation::configureSkills (const SkillList& major, const SkillList& minor) + void CharacterCreation::configureSkills(const std::vector& major, const std::vector& minor) { if (mReviewDialog) mReviewDialog->configureSkills(major, minor); @@ -194,10 +174,10 @@ namespace MWGui switch (id) { case GM_Name: - MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); - mNameDialog = nullptr; - mNameDialog = new TextInputDialog(); - mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mNameDialog)); + mNameDialog = std::make_unique(); + mNameDialog->setTextLabel( + MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); mNameDialog->setTextInput(mPlayerName); mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen); mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone); @@ -205,9 +185,8 @@ namespace MWGui break; case GM_Race: - MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); - mRaceDialog = nullptr; - mRaceDialog = new RaceDialog(mParent, mResourceSystem); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mRaceDialog)); + mRaceDialog = std::make_unique(mParent, mResourceSystem); mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen); mRaceDialog->setRaceId(mPlayerRaceId); mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone); @@ -218,19 +197,18 @@ namespace MWGui break; case GM_Class: - MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); - mClassChoiceDialog = nullptr; - mClassChoiceDialog = new ClassChoiceDialog(); - mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mClassChoiceDialog)); + mClassChoiceDialog = std::make_unique(); + mClassChoiceDialog->eventButtonSelected + += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); mClassChoiceDialog->setVisible(true); if (mCreationStage < CSE_RaceChosen) mCreationStage = CSE_RaceChosen; break; case GM_ClassPick: - MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); - mPickClassDialog = nullptr; - mPickClassDialog = new PickClassDialog(); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mPickClassDialog)); + mPickClassDialog = std::make_unique(); mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); mPickClassDialog->setClassId(mPlayerClass.mId); mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone); @@ -241,9 +219,8 @@ namespace MWGui break; case GM_Birth: - MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); - mBirthSignDialog = nullptr; - mBirthSignDialog = new BirthDialog(); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mBirthSignDialog)); + mBirthSignDialog = std::make_unique(); mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); mBirthSignDialog->setBirthId(mPlayerBirthSignId); mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone); @@ -254,11 +231,13 @@ namespace MWGui break; case GM_ClassCreate: - if (!mCreateClassDialog) + if (mCreateClassDialog == nullptr) { - mCreateClassDialog = new CreateClassDialog(); - mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); - mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); + mCreateClassDialog = std::make_unique(); + mCreateClassDialog->eventDone + += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); + mCreateClassDialog->eventBack + += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); } mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); mCreateClassDialog->setVisible(true); @@ -267,7 +246,7 @@ namespace MWGui break; case GM_ClassGenerate: mGenerateClassStep = 0; - mGenerateClass = ""; + mGenerateClass = ESM::RefId(); mGenerateClassSpecializations[0] = 0; mGenerateClassSpecializations[1] = 0; mGenerateClassSpecializations[2] = 0; @@ -276,17 +255,16 @@ namespace MWGui mCreationStage = CSE_RaceChosen; break; case GM_Review: - MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = nullptr; - mReviewDialog = new ReviewDialog(); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mReviewDialog)); + mReviewDialog = std::make_unique(); - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); - const ESM::NPC *playerNpc = world->getPlayerPtr().get()->mBase; + const ESM::NPC* playerNpc = world->getPlayerPtr().get()->mBase; const MWWorld::Player player = world->getPlayer(); - const ESM::Class *playerClass = world->getStore().get().find(playerNpc->mClass); + const ESM::Class* playerClass = world->getStore().get().find(playerNpc->mClass); mReviewDialog->setPlayerName(playerNpc->mName); mReviewDialog->setRace(playerNpc->mRace); @@ -301,17 +279,19 @@ namespace MWGui mReviewDialog->setFatigue(stats.getFatigue()); for (auto& attributePair : mPlayerAttributes) { - mReviewDialog->setAttribute(static_cast (attributePair.first), attributePair.second); + mReviewDialog->setAttribute( + static_cast(attributePair.first), attributePair.second); } - for (auto& skillPair : mPlayerSkillValues) + for (const auto& [skill, value] : mPlayerSkillValues) { - mReviewDialog->setSkillValue(static_cast (skillPair.first), skillPair.second); + mReviewDialog->setSkillValue(skill, value); } mReviewDialog->configureSkills(mPlayerMajorSkills, mPlayerMinorSkills); mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack); - mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog); + mReviewDialog->eventActivateDialog + += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog); mReviewDialog->setVisible(true); if (mCreationStage < CSE_BirthSignChosen) mCreationStage = CSE_BirthSignChosen; @@ -326,16 +306,13 @@ namespace MWGui void CharacterCreation::onReviewDialogDone(WindowBase* parWindow) { - MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = nullptr; - + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mReviewDialog)); MWBase::Environment::get().getWindowManager()->popGuiMode(); } void CharacterCreation::onReviewDialogBack() { - MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mReviewDialog)); mCreationStage = CSE_ReviewBack; MWBase::Environment::get().getWindowManager()->popGuiMode(); @@ -354,13 +331,12 @@ namespace MWGui void CharacterCreation::onReviewActivateDialog(int parDialog) { - MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mReviewDialog)); mCreationStage = CSE_ReviewNext; MWBase::Environment::get().getWindowManager()->popGuiMode(); - switch(parDialog) + switch (parDialog) { case ReviewDialog::NAME_DIALOG: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name); @@ -380,21 +356,17 @@ namespace MWGui { if (mPickClassDialog) { - const std::string &classId = mPickClassDialog->getClassId(); + const ESM::RefId& classId = mPickClassDialog->getClassId(); if (!classId.empty()) MWBase::Environment::get().getMechanicsManager()->setPlayerClass(classId); - const ESM::Class *klass = - MWBase::Environment::get().getWorld()->getStore().get().find(classId); + const ESM::Class* klass = MWBase::Environment::get().getESMStore()->get().find(classId); if (klass) { mPlayerClass = *klass; } - MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); - mPickClassDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mPickClassDialog)); } - - updatePlayerHealth(); } void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) @@ -424,12 +396,11 @@ namespace MWGui void CharacterCreation::onClassChoice(int _index) { - MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); - mClassChoiceDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mClassChoiceDialog)); MWBase::Environment::get().getWindowManager()->popGuiMode(); - switch(_index) + switch (_index) { case ClassChoiceDialog::Class_Generate: MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_ClassGenerate); @@ -453,7 +424,6 @@ namespace MWGui End of tes3mp addition */ break; - }; } @@ -475,8 +445,7 @@ namespace MWGui */ MWBase::Environment::get().getMechanicsManager()->setPlayerName(mPlayerName); - MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); - mNameDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mNameDialog)); } handleDialogDone(CSE_NameChosen, GM_Race); @@ -496,23 +465,17 @@ namespace MWGui { if (mRaceDialog) { - const ESM::NPC &data = mRaceDialog->getResult(); + const ESM::NPC& data = mRaceDialog->getResult(); mPlayerRaceId = data.mRace; - if (!mPlayerRaceId.empty()) { + if (!mPlayerRaceId.empty()) + { MWBase::Environment::get().getMechanicsManager()->setPlayerRace( - data.mRace, - data.isMale(), - data.mHead, - data.mHair - ); + data.mRace, data.isMale(), data.mHead, data.mHair); } MWBase::Environment::get().getWindowManager()->getInventoryWindow()->rebuildAvatar(); - MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); - mRaceDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mRaceDialog)); } - - updatePlayerHealth(); } void CharacterCreation::onRaceDialogBack() @@ -547,11 +510,8 @@ namespace MWGui mPlayerBirthSignId = mBirthSignDialog->getBirthId(); if (!mPlayerBirthSignId.empty()) MWBase::Environment::get().getMechanicsManager()->setPlayerBirthsign(mPlayerBirthSignId); - MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); - mBirthSignDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mBirthSignDialog)); } - - updatePlayerHealth(); } void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) @@ -598,20 +558,20 @@ namespace MWGui klass.mDescription = mCreateClassDialog->getDescription(); klass.mData.mSpecialization = mCreateClassDialog->getSpecializationId(); klass.mData.mIsPlayable = 0x1; + klass.mRecordFlags = 0; std::vector attributes = mCreateClassDialog->getFavoriteAttributes(); - assert(attributes.size() == 2); - klass.mData.mAttribute[0] = attributes[0]; - klass.mData.mAttribute[1] = attributes[1]; + assert(attributes.size() == klass.mData.mAttribute.size()); + std::copy(attributes.begin(), attributes.end(), klass.mData.mAttribute.begin()); - std::vector majorSkills = mCreateClassDialog->getMajorSkills(); - std::vector minorSkills = mCreateClassDialog->getMinorSkills(); - assert(majorSkills.size() >= sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0])); - assert(minorSkills.size() >= sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0])); - for (size_t i = 0; i < sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0]); ++i) + std::vector majorSkills = mCreateClassDialog->getMajorSkills(); + std::vector minorSkills = mCreateClassDialog->getMinorSkills(); + assert(majorSkills.size() >= klass.mData.mSkills.size()); + assert(minorSkills.size() >= klass.mData.mSkills.size()); + for (size_t i = 0; i < klass.mData.mSkills.size(); ++i) { - klass.mData.mSkills[i][1] = majorSkills[i]; - klass.mData.mSkills[i][0] = minorSkills[i]; + klass.mData.mSkills[i][1] = majorSkills[i].getIf()->getValue(); + klass.mData.mSkills[i][0] = minorSkills[i].getIf()->getValue(); } MWBase::Environment::get().getMechanicsManager()->setPlayerClass(klass); @@ -620,7 +580,6 @@ namespace MWGui // Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later mCreateClassDialog->setVisible(false); } - updatePlayerHealth(); } void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) @@ -663,8 +622,7 @@ namespace MWGui { MWBase::Environment::get().getSoundManager()->stopSay(); - MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassQuestionDialog); - mGenerateClassQuestionDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mGenerateClassQuestionDialog)); if (_index < 0 || _index >= 3) { @@ -691,100 +649,99 @@ namespace MWGui unsigned combat = mGenerateClassSpecializations[0]; unsigned magic = mGenerateClassSpecializations[1]; unsigned stealth = mGenerateClassSpecializations[2]; - + std::string_view className; if (combat > 7) { - mGenerateClass = "Warrior"; + className = "Warrior"; } else if (magic > 7) { - mGenerateClass = "Mage"; + className = "Mage"; } else if (stealth > 7) { - mGenerateClass = "Thief"; + className = "Thief"; } else { switch (combat) { case 4: - mGenerateClass = "Rogue"; + className = "Rogue"; break; case 5: if (stealth == 3) - mGenerateClass = "Scout"; + className = "Scout"; else - mGenerateClass = "Archer"; + className = "Archer"; break; case 6: if (stealth == 1) - mGenerateClass = "Barbarian"; + className = "Barbarian"; else if (stealth == 3) - mGenerateClass = "Crusader"; + className = "Crusader"; else - mGenerateClass = "Knight"; + className = "Knight"; break; case 7: - mGenerateClass = "Warrior"; + className = "Warrior"; break; default: switch (magic) { case 4: - mGenerateClass = "Spellsword"; + className = "Spellsword"; break; case 5: - mGenerateClass = "Witchhunter"; + className = "Witchhunter"; break; case 6: if (combat == 2) - mGenerateClass = "Sorcerer"; + className = "Sorcerer"; else if (combat == 3) - mGenerateClass = "Healer"; + className = "Healer"; else - mGenerateClass = "Battlemage"; + className = "Battlemage"; break; case 7: - mGenerateClass = "Mage"; + className = "Mage"; break; default: switch (stealth) { case 3: if (magic == 3) - mGenerateClass = "Bard"; // unreachable + className = "Bard"; // unreachable else - mGenerateClass = "Warrior"; + className = "Warrior"; break; case 5: if (magic == 3) - mGenerateClass = "Monk"; + className = "Monk"; else - mGenerateClass = "Pilgrim"; + className = "Pilgrim"; break; case 6: if (magic == 1) - mGenerateClass = "Agent"; + className = "Agent"; else if (magic == 3) - mGenerateClass = "Assassin"; + className = "Assassin"; else - mGenerateClass = "Acrobat"; + className = "Acrobat"; break; case 7: - mGenerateClass = "Thief"; + className = "Thief"; break; default: - mGenerateClass = "Warrior"; + className = "Warrior"; } } } } + mGenerateClass = ESM::RefId::stringRefId(className); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mGenerateClassResultDialog)); - MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); - mGenerateClassResultDialog = nullptr; - - mGenerateClassResultDialog = new GenerateClassResultDialog(); + mGenerateClassResultDialog = std::make_unique(); mGenerateClassResultDialog->setClassId(mGenerateClass); mGenerateClassResultDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onGenerateClassBack); mGenerateClassResultDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onGenerateClassDone); @@ -799,10 +756,9 @@ namespace MWGui return; } - MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassQuestionDialog); - mGenerateClassQuestionDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mGenerateClassQuestionDialog)); - mGenerateClassQuestionDialog = new InfoBoxDialog(); + mGenerateClassQuestionDialog = std::make_unique(); Step step = sGenerateClassSteps(mGenerateClassStep); mGenerateClassResponses[0] = step.mResponses[0].mSpecialization; @@ -815,7 +771,8 @@ namespace MWGui buttons.push_back(step.mResponses[1].mText); buttons.push_back(step.mResponses[2].mText); mGenerateClassQuestionDialog->setButtons(buttons); - mGenerateClassQuestionDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen); + mGenerateClassQuestionDialog->eventButtonSelected + += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen); mGenerateClassQuestionDialog->setVisible(true); MWBase::Environment::get().getSoundManager()->say(step.mSound); @@ -823,17 +780,13 @@ namespace MWGui void CharacterCreation::selectGeneratedClass() { - MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); - mGenerateClassResultDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mGenerateClassResultDialog)); MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); - const ESM::Class *klass = - MWBase::Environment::get().getWorld()->getStore().get().find(mGenerateClass); + const ESM::Class* klass = MWBase::Environment::get().getESMStore()->get().find(mGenerateClass); mPlayerClass = *klass; - - updatePlayerHealth(); } void CharacterCreation::onGenerateClassBack() @@ -861,18 +814,7 @@ namespace MWGui */ } - CharacterCreation::~CharacterCreation() - { - delete mNameDialog; - delete mRaceDialog; - delete mClassChoiceDialog; - delete mGenerateClassQuestionDialog; - delete mGenerateClassResultDialog; - delete mPickClassDialog; - delete mCreateClassDialog; - delete mBirthSignDialog; - delete mReviewDialog; - } + CharacterCreation::~CharacterCreation() = default; void CharacterCreation::handleDialogDone(CSE currentStage, int nextMode) { diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index beb8715fc..d590c0cfd 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -1,9 +1,10 @@ #ifndef CHARACTER_CREATION_HPP #define CHARACTER_CREATION_HPP -#include +#include #include +#include #include #include "statswatcher.hpp" @@ -37,99 +38,98 @@ namespace MWGui class CharacterCreation : public StatsListener { public: - typedef std::vector SkillList; + CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem); + virtual ~CharacterCreation(); - CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem); - virtual ~CharacterCreation(); + // Show a dialog + void spawnDialog(const char id); - //Show a dialog - void spawnDialog(const char id); + void setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) override; + void setValue(std::string_view id, const MWMechanics::DynamicStat& value) override; + void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) override; + void configureSkills(const std::vector& major, const std::vector& minor) override; - void setValue (const std::string& id, const MWMechanics::AttributeValue& value) override; - void setValue (const std::string& id, const MWMechanics::DynamicStat& value) override; - void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override; - void configureSkills(const SkillList& major, const SkillList& minor) override; - - void onFrame(float duration); + void onFrame(float duration); private: - osg::Group* mParent; - Resource::ResourceSystem* mResourceSystem; + osg::Group* mParent; + Resource::ResourceSystem* mResourceSystem; - SkillList mPlayerMajorSkills, mPlayerMinorSkills; - std::map mPlayerAttributes; - std::map mPlayerSkillValues; + std::vector mPlayerMajorSkills, mPlayerMinorSkills; + std::map mPlayerAttributes; + std::map mPlayerSkillValues; - //Dialogs - TextInputDialog* mNameDialog; - RaceDialog* mRaceDialog; - ClassChoiceDialog* mClassChoiceDialog; - InfoBoxDialog* mGenerateClassQuestionDialog; - GenerateClassResultDialog* mGenerateClassResultDialog; - PickClassDialog* mPickClassDialog; - CreateClassDialog* mCreateClassDialog; - BirthDialog* mBirthSignDialog; - ReviewDialog* mReviewDialog; + // Dialogs + std::unique_ptr mNameDialog; + std::unique_ptr mRaceDialog; + std::unique_ptr mClassChoiceDialog; + std::unique_ptr mGenerateClassQuestionDialog; + std::unique_ptr mGenerateClassResultDialog; + std::unique_ptr mPickClassDialog; + std::unique_ptr mCreateClassDialog; + std::unique_ptr mBirthSignDialog; + std::unique_ptr mReviewDialog; - //Player data - std::string mPlayerName; - std::string mPlayerRaceId; - std::string mPlayerBirthSignId; - ESM::Class mPlayerClass; + // Player data + std::string mPlayerName; + ESM::RefId mPlayerRaceId; + ESM::RefId mPlayerBirthSignId; + ESM::Class mPlayerClass; - //Class generation vars - unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog - ESM::Class::Specialization mGenerateClassResponses[3]; - unsigned mGenerateClassSpecializations[3]; // A counter for each specialization which is increased when an answer is chosen - std::string mGenerateClass; // In order: Combat, Magic, Stealth + // Class generation vars + unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog + ESM::Class::Specialization mGenerateClassResponses[3]; + unsigned mGenerateClassSpecializations[3]; // A counter for each specialization which is increased when an + // answer is chosen + ESM::RefId mGenerateClass; // In order: Combat, Magic, Stealth - ////Dialog events - //Name dialog - void onNameDialogDone(WindowBase* parWindow); + ////Dialog events + // Name dialog + void onNameDialogDone(WindowBase* parWindow); - //Race dialog - void onRaceDialogDone(WindowBase* parWindow); - void onRaceDialogBack(); - void selectRace(); + // Race dialog + void onRaceDialogDone(WindowBase* parWindow); + void onRaceDialogBack(); + void selectRace(); - //Class dialogs - void onClassChoice(int _index); - void onPickClassDialogDone(WindowBase* parWindow); - void onPickClassDialogBack(); - void onCreateClassDialogDone(WindowBase* parWindow); - void onCreateClassDialogBack(); - void showClassQuestionDialog(); - void onClassQuestionChosen(int _index); - void onGenerateClassBack(); - void onGenerateClassDone(WindowBase* parWindow); - void selectGeneratedClass(); - void selectCreatedClass(); - void selectPickedClass(); + // Class dialogs + void onClassChoice(int _index); + void onPickClassDialogDone(WindowBase* parWindow); + void onPickClassDialogBack(); + void onCreateClassDialogDone(WindowBase* parWindow); + void onCreateClassDialogBack(); + void showClassQuestionDialog(); + void onClassQuestionChosen(int _index); + void onGenerateClassBack(); + void onGenerateClassDone(WindowBase* parWindow); + void selectGeneratedClass(); + void selectCreatedClass(); + void selectPickedClass(); - //Birthsign dialog - void onBirthSignDialogDone(WindowBase* parWindow); - void onBirthSignDialogBack(); - void selectBirthSign(); + // Birthsign dialog + void onBirthSignDialogDone(WindowBase* parWindow); + void onBirthSignDialogBack(); + void selectBirthSign(); - //Review dialog - void onReviewDialogDone(WindowBase* parWindow); - void onReviewDialogBack(); - void onReviewActivateDialog(int parDialog); + // Review dialog + void onReviewDialogDone(WindowBase* parWindow); + void onReviewDialogBack(); + void onReviewActivateDialog(int parDialog); - enum CSE //Creation Stage Enum - { - CSE_NotStarted, - CSE_NameChosen, - CSE_RaceChosen, - CSE_ClassChosen, - CSE_BirthSignChosen, - CSE_ReviewBack, - CSE_ReviewNext - }; + enum CSE // Creation Stage Enum + { + CSE_NotStarted, + CSE_NameChosen, + CSE_RaceChosen, + CSE_ClassChosen, + CSE_BirthSignChosen, + CSE_ReviewBack, + CSE_ReviewNext + }; - CSE mCreationStage; // Which state the character creating is in, controls back/next/ok buttons + CSE mCreationStage; // Which state the character creating is in, controls back/next/ok buttons - void handleDialogDone(CSE currentStage, int nextMode); + void handleDialogDone(CSE currentStage, int nextMode); }; } diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index ee5fe5939..202f18ad3 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -1,25 +1,31 @@ #include "class.hpp" +#include #include #include -#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/esmstore.hpp" #include +#include +#include +#include +#include #include "tooltips.hpp" +#include "ustring.hpp" namespace { - bool sortClasses(const std::pair& left, const std::pair& right) + bool sortClasses(const std::pair& left, const std::pair& right) { return left.second.compare(right.second) < 0; } @@ -32,9 +38,10 @@ namespace MWGui /* GenerateClassResultDialog */ GenerateClassResultDialog::GenerateClassResultDialog() - : WindowModal("openmw_chargen_generate_class_result.layout") + : WindowModal("openmw_chargen_generate_class_result.layout") { - setText("ReflectT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sMessageQuestionAnswer1", "")); + setText("ReflectT", + MWBase::Environment::get().getWindowManager()->getGameSettingString("sMessageQuestionAnswer1", {})); getWidget(mClassImage, "ClassImage"); getWidget(mClassName, "ClassName"); @@ -52,18 +59,14 @@ namespace MWGui center(); } - std::string GenerateClassResultDialog::getClassId() const - { - return mClassName->getCaption(); - } - - void GenerateClassResultDialog::setClassId(const std::string &classId) + void GenerateClassResultDialog::setClassId(const ESM::RefId& classId) { mCurrentClassId = classId; setClassImage(mClassImage, mCurrentClassId); - mClassName->setCaption(MWBase::Environment::get().getWorld()->getStore().get().find(mCurrentClassId)->mName); + mClassName->setCaption( + MWBase::Environment::get().getESMStore()->get().find(mCurrentClassId)->mName); center(); } @@ -83,7 +86,7 @@ namespace MWGui /* PickClassDialog */ PickClassDialog::PickClassDialog() - : WindowModal("openmw_chargen_class.layout") + : WindowModal("openmw_chargen_class.layout") { // Centre dialog center(); @@ -93,9 +96,9 @@ namespace MWGui getWidget(mFavoriteAttribute[0], "FavoriteAttribute0"); getWidget(mFavoriteAttribute[1], "FavoriteAttribute1"); - for(int i = 0; i < 5; i++) + for (int i = 0; i < 5; i++) { - char theIndex = '0'+i; + char theIndex = '0' + i; getWidget(mMajorSkill[i], std::string("MajorSkill").append(1, theIndex)); getWidget(mMinorSkill[i], std::string("MinorSkill").append(1, theIndex)); } @@ -125,14 +128,16 @@ namespace MWGui getWidget(okButton, "OKButton"); if (shown) - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } void PickClassDialog::onOpen() { - WindowModal::onOpen (); + WindowModal::onOpen(); updateClasses(); updateStats(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList); @@ -140,21 +145,20 @@ namespace MWGui // Show the current class by default MWWorld::Ptr player = MWMechanics::getPlayer(); - const std::string &classId = - player.get()->mBase->mClass; + const ESM::RefId& classId = player.get()->mBase->mClass; if (!classId.empty()) setClassId(classId); } - void PickClassDialog::setClassId(const std::string &classId) + void PickClassDialog::setClassId(const ESM::RefId& classId) { mCurrentClassId = classId; mClassList->setIndexSelected(MyGUI::ITEM_NONE); size_t count = mClassList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (Misc::StringUtils::ciEqual(*mClassList->getItemDataAt(i), classId)) + if (*mClassList->getItemDataAt(i) == classId) { mClassList->setIndexSelected(i); break; @@ -168,7 +172,7 @@ namespace MWGui void PickClassDialog::onOkClicked(MyGUI::Widget* _sender) { - if(mClassList->getIndexSelected() == MyGUI::ITEM_NONE) + if (mClassList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } @@ -181,7 +185,7 @@ namespace MWGui void PickClassDialog::onAccept(MyGUI::ListBox* _sender, size_t _index) { onSelectClass(_sender, _index); - if(mClassList->getIndexSelected() == MyGUI::ITEM_NONE) + if (mClassList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } @@ -191,11 +195,11 @@ namespace MWGui if (_index == MyGUI::ITEM_NONE) return; - const std::string *classId = mClassList->getItemDataAt(_index); - if (Misc::StringUtils::ciEqual(mCurrentClassId, *classId)) + const ESM::RefId& classId = *mClassList->getItemDataAt(_index); + if (mCurrentClassId == classId) return; - mCurrentClassId = *classId; + mCurrentClassId = classId; updateStats(); } @@ -205,9 +209,9 @@ namespace MWGui { mClassList->removeAllItems(); - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - std::vector > items; // class id, class name + std::vector> items; // class id, class name for (const ESM::Class& classInfo : store.get()) { bool playable = (classInfo.mData.mIsPlayable != 0); @@ -224,14 +228,14 @@ namespace MWGui int index = 0; for (auto& itemPair : items) { - const std::string &id = itemPair.first; + const ESM::RefId& id = itemPair.first; mClassList->addItem(itemPair.second, id); if (mCurrentClassId.empty()) { mCurrentClassId = id; mClassList->setIndexSelected(index); } - else if (Misc::StringUtils::ciEqual(id, mCurrentClassId)) + else if (id == mCurrentClassId) { mClassList->setIndexSelected(index); } @@ -243,33 +247,32 @@ namespace MWGui { if (mCurrentClassId.empty()) return; - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Class *klass = store.get().search(mCurrentClassId); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::Class* klass = store.get().search(mCurrentClassId); if (!klass) return; - ESM::Class::Specialization specialization = static_cast(klass->mData.mSpecialization); + ESM::Class::Specialization specialization + = static_cast(klass->mData.mSpecialization); - static const char *specIds[3] = { - "sSpecializationCombat", - "sSpecializationMagic", - "sSpecializationStealth" - }; - std::string specName = MWBase::Environment::get().getWindowManager()->getGameSettingString(specIds[specialization], specIds[specialization]); + std::string specName{ MWBase::Environment::get().getWindowManager()->getGameSettingString( + ESM::Class::sGmstSpecializationIds[specialization], ESM::Class::sGmstSpecializationIds[specialization]) }; mSpecializationName->setCaption(specName); ToolTips::createSpecializationToolTip(mSpecializationName, specName, specialization); - mFavoriteAttribute[0]->setAttributeId(klass->mData.mAttribute[0]); - mFavoriteAttribute[1]->setAttributeId(klass->mData.mAttribute[1]); + mFavoriteAttribute[0]->setAttributeId(static_cast(klass->mData.mAttribute[0])); + mFavoriteAttribute[1]->setAttributeId(static_cast(klass->mData.mAttribute[1])); ToolTips::createAttributeToolTip(mFavoriteAttribute[0], mFavoriteAttribute[0]->getAttributeId()); ToolTips::createAttributeToolTip(mFavoriteAttribute[1], mFavoriteAttribute[1]->getAttributeId()); - for (int i = 0; i < 5; ++i) + for (size_t i = 0; i < klass->mData.mSkills.size(); ++i) { - mMinorSkill[i]->setSkillNumber(klass->mData.mSkills[i][0]); - mMajorSkill[i]->setSkillNumber(klass->mData.mSkills[i][1]); - ToolTips::createSkillToolTip(mMinorSkill[i], klass->mData.mSkills[i][0]); - ToolTips::createSkillToolTip(mMajorSkill[i], klass->mData.mSkills[i][1]); + ESM::RefId minor = ESM::Skill::indexToRefId(klass->mData.mSkills[i][0]); + ESM::RefId major = ESM::Skill::indexToRefId(klass->mData.mSkills[i][1]); + mMinorSkill[i]->setSkillId(minor); + mMajorSkill[i]->setSkillId(major); + ToolTips::createSkillToolTip(mMinorSkill[i], minor); + ToolTips::createSkillToolTip(mMajorSkill[i], major); } setClassImage(mClassImage, mCurrentClassId); @@ -303,7 +306,7 @@ namespace MWGui width = std::max(width, child->getWidth()); pos += child->getHeight() + margin; } - width += margin*2; + width += margin * 2; widget->setSize(width, pos); } @@ -318,7 +321,7 @@ namespace MWGui center(); } - void InfoBoxDialog::setText(const std::string &str) + void InfoBoxDialog::setText(const std::string& str) { mText->setCaption(str); mTextBox->setVisible(!str.empty()); @@ -330,7 +333,7 @@ namespace MWGui return mText->getCaption(); } - void InfoBoxDialog::setButtons(ButtonList &buttons) + void InfoBoxDialog::setButtons(ButtonList& buttons) { for (MyGUI::Button* button : this->mButtons) { @@ -341,9 +344,10 @@ namespace MWGui // TODO: The buttons should be generated from a template in the layout file, ie. cloning an existing widget MyGUI::Button* button; MyGUI::IntCoord coord = MyGUI::IntCoord(0, 0, mButtonBar->getWidth(), 10); - for (const std::string &text : buttons) + for (const std::string& text : buttons) { - button = mButtonBar->createWidget("MW_Button", coord, MyGUI::Align::Top | MyGUI::Align::HCenter, ""); + button = mButtonBar->createWidget( + "MW_Button", coord, MyGUI::Align::Top | MyGUI::Align::HCenter, {}); button->getSubWidgetText()->setWordWrap(true); button->setCaption(text); fitToText(button); @@ -383,44 +387,49 @@ namespace MWGui ClassChoiceDialog::ClassChoiceDialog() : InfoBoxDialog() { - setText(""); + setText({}); ButtonList buttons; - buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu1", "")); - buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu2", "")); - buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu3", "")); - buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBack", "")); + buttons.emplace_back( + MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu1", {})); + buttons.emplace_back( + MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu2", {})); + buttons.emplace_back( + MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu3", {})); + buttons.emplace_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBack", {})); setButtons(buttons); } /* CreateClassDialog */ CreateClassDialog::CreateClassDialog() - : WindowModal("openmw_chargen_create_class.layout") - , mSpecDialog(nullptr) - , mAttribDialog(nullptr) - , mSkillDialog(nullptr) - , mDescDialog(nullptr) - , mAffectedAttribute(nullptr) - , mAffectedSkill(nullptr) + : WindowModal("openmw_chargen_create_class.layout") + , mAffectedAttribute(nullptr) + , mAffectedSkill(nullptr) { // Centre dialog center(); - setText("SpecializationT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sChooseClassMenu1", "Specialization")); + setText("SpecializationT", + MWBase::Environment::get().getWindowManager()->getGameSettingString("sChooseClassMenu1", "Specialization")); getWidget(mSpecializationName, "SpecializationName"); - mSpecializationName->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationClicked); + mSpecializationName->eventMouseButtonClick + += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationClicked); - setText("FavoriteAttributesT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sChooseClassMenu2", "Favorite Attributes:")); + setText("FavoriteAttributesT", + MWBase::Environment::get().getWindowManager()->getGameSettingString( + "sChooseClassMenu2", "Favorite Attributes:")); getWidget(mFavoriteAttribute0, "FavoriteAttribute0"); getWidget(mFavoriteAttribute1, "FavoriteAttribute1"); mFavoriteAttribute0->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked); mFavoriteAttribute1->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked); - setText("MajorSkillT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillClassMajor", "")); - setText("MinorSkillT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillClassMinor", "")); - for(int i = 0; i < 5; i++) + setText( + "MajorSkillT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillClassMajor", {})); + setText( + "MinorSkillT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillClassMinor", {})); + for (int i = 0; i < 5; i++) { - char theIndex = '0'+i; + char theIndex = '0' + i; getWidget(mMajorSkill[i], std::string("MajorSkill").append(1, theIndex)); getWidget(mMinorSkill[i], std::string("MinorSkill").append(1, theIndex)); mSkills.push_back(mMajorSkill[i]); @@ -432,7 +441,7 @@ namespace MWGui skill->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onSkillClicked); } - setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "")); + setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", {})); getWidget(mEditName, "EditName"); // Make sure the edit box has focus @@ -471,13 +480,7 @@ namespace MWGui update(); } - CreateClassDialog::~CreateClassDialog() - { - delete mSpecDialog; - delete mAttribDialog; - delete mSkillDialog; - delete mDescDialog; - } + CreateClassDialog::~CreateClassDialog() = default; void CreateClassDialog::update() { @@ -514,22 +517,24 @@ namespace MWGui return v; } - std::vector CreateClassDialog::getMajorSkills() const + std::vector CreateClassDialog::getMajorSkills() const { - std::vector v; - for(int i = 0; i < 5; i++) + std::vector v; + v.reserve(mMajorSkill.size()); + for (const auto& widget : mMajorSkill) { - v.push_back(mMajorSkill[i]->getSkillId()); + v.push_back(widget->getSkillId()); } return v; } - std::vector CreateClassDialog::getMinorSkills() const + std::vector CreateClassDialog::getMinorSkills() const { - std::vector v; - for(int i=0; i < 5; i++) + std::vector v; + v.reserve(mMinorSkill.size()); + for (const auto& widget : mMinorSkill) { - v.push_back(mMinorSkill[i]->getSkillId()); + v.push_back(widget->getSkillId()); } return v; } @@ -540,32 +545,26 @@ namespace MWGui getWidget(okButton, "OKButton"); if (shown) - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } // widget controls void CreateClassDialog::onDialogCancel() { - MWBase::Environment::get().getWindowManager()->removeDialog(mSpecDialog); - mSpecDialog = nullptr; - - MWBase::Environment::get().getWindowManager()->removeDialog(mAttribDialog); - mAttribDialog = nullptr; - - MWBase::Environment::get().getWindowManager()->removeDialog(mSkillDialog); - mSkillDialog = nullptr; - - MWBase::Environment::get().getWindowManager()->removeDialog(mDescDialog); - mDescDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSpecDialog)); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mAttribDialog)); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSkillDialog)); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mDescDialog)); } void CreateClassDialog::onSpecializationClicked(MyGUI::Widget* _sender) { - delete mSpecDialog; - mSpecDialog = new SelectSpecializationDialog(); + mSpecDialog = std::make_unique(); mSpecDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); mSpecDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationSelected); mSpecDialog->setVisible(true); @@ -576,27 +575,22 @@ namespace MWGui mSpecializationId = mSpecDialog->getSpecializationId(); setSpecialization(mSpecializationId); - MWBase::Environment::get().getWindowManager()->removeDialog(mSpecDialog); - mSpecDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSpecDialog)); } void CreateClassDialog::setSpecialization(int id) { - mSpecializationId = (ESM::Class::Specialization) id; - static const char *specIds[3] = { - "sSpecializationCombat", - "sSpecializationMagic", - "sSpecializationStealth" - }; - std::string specName = MWBase::Environment::get().getWindowManager()->getGameSettingString(specIds[mSpecializationId], specIds[mSpecializationId]); + mSpecializationId = ESM::Class::Specialization(id); + std::string specName{ MWBase::Environment::get().getWindowManager()->getGameSettingString( + ESM::Class::sGmstSpecializationIds[mSpecializationId], + ESM::Class::sGmstSpecializationIds[mSpecializationId]) }; mSpecializationName->setCaption(specName); ToolTips::createSpecializationToolTip(mSpecializationName, specName, mSpecializationId); } void CreateClassDialog::onAttributeClicked(Widgets::MWAttributePtr _sender) { - delete mAttribDialog; - mAttribDialog = new SelectAttributeDialog(); + mAttribDialog = std::make_unique(); mAffectedAttribute = _sender; mAttribDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); mAttribDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeSelected); @@ -617,16 +611,14 @@ namespace MWGui mFavoriteAttribute0->setAttributeId(mFavoriteAttribute1->getAttributeId()); } mAffectedAttribute->setAttributeId(id); - MWBase::Environment::get().getWindowManager()->removeDialog(mAttribDialog); - mAttribDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mAttribDialog)); update(); } void CreateClassDialog::onSkillClicked(Widgets::MWSkillPtr _sender) { - delete mSkillDialog; - mSkillDialog = new SelectSkillDialog(); + mSkillDialog = std::make_unique(); mAffectedSkill = _sender; mSkillDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); mSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSkillSelected); @@ -635,7 +627,7 @@ namespace MWGui void CreateClassDialog::onSkillSelected() { - ESM::Skill::SkillEnum id = mSkillDialog->getSkillId(); + ESM::RefId id = mSkillDialog->getSkillId(); // Avoid duplicate skills by swapping any skill field that matches the selected one for (Widgets::MWSkillPtr& skill : mSkills) @@ -650,14 +642,13 @@ namespace MWGui } mAffectedSkill->setSkillId(mSkillDialog->getSkillId()); - MWBase::Environment::get().getWindowManager()->removeDialog(mSkillDialog); - mSkillDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSkillDialog)); update(); } void CreateClassDialog::onDescriptionClicked(MyGUI::Widget* _sender) { - mDescDialog = new DescriptionDialog(); + mDescDialog = std::make_unique(); mDescDialog->setTextInput(mDescription); mDescDialog->eventDone += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionEntered); mDescDialog->setVisible(true); @@ -666,13 +657,12 @@ namespace MWGui void CreateClassDialog::onDescriptionEntered(WindowBase* parWindow) { mDescription = mDescDialog->getTextInput(); - MWBase::Environment::get().getWindowManager()->removeDialog(mDescDialog); - mDescDialog = nullptr; + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mDescDialog)); } void CreateClassDialog::onOkClicked(MyGUI::Widget* _sender) { - if(getName().size() <= 0) + if (getName().size() <= 0) return; eventDone(this); } @@ -685,7 +675,7 @@ namespace MWGui /* SelectSpecializationDialog */ SelectSpecializationDialog::SelectSpecializationDialog() - : WindowModal("openmw_chargen_select_specialization.layout") + : WindowModal("openmw_chargen_select_specialization.layout") { // Centre dialog center(); @@ -693,16 +683,22 @@ namespace MWGui getWidget(mSpecialization0, "Specialization0"); getWidget(mSpecialization1, "Specialization1"); getWidget(mSpecialization2, "Specialization2"); - std::string combat = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Combat], ""); - std::string magic = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Magic], ""); - std::string stealth = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Stealth], ""); + std::string combat{ MWBase::Environment::get().getWindowManager()->getGameSettingString( + ESM::Class::sGmstSpecializationIds[ESM::Class::Combat], {}) }; + std::string magic{ MWBase::Environment::get().getWindowManager()->getGameSettingString( + ESM::Class::sGmstSpecializationIds[ESM::Class::Magic], {}) }; + std::string stealth{ MWBase::Environment::get().getWindowManager()->getGameSettingString( + ESM::Class::sGmstSpecializationIds[ESM::Class::Stealth], {}) }; mSpecialization0->setCaption(combat); - mSpecialization0->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); + mSpecialization0->eventMouseButtonClick + += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); mSpecialization1->setCaption(magic); - mSpecialization1->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); + mSpecialization1->eventMouseButtonClick + += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); mSpecialization2->setCaption(stealth); - mSpecialization2->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); + mSpecialization2->eventMouseButtonClick + += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); mSpecializationId = ESM::Class::Combat; ToolTips::createSpecializationToolTip(mSpecialization0, combat, ESM::Class::Combat); @@ -714,9 +710,7 @@ namespace MWGui cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onCancelClicked); } - SelectSpecializationDialog::~SelectSpecializationDialog() - { - } + SelectSpecializationDialog::~SelectSpecializationDialog() {} // widget controls @@ -748,38 +742,41 @@ namespace MWGui /* SelectAttributeDialog */ SelectAttributeDialog::SelectAttributeDialog() - : WindowModal("openmw_chargen_select_attribute.layout") - , mAttributeId(ESM::Attribute::Strength) + : WindowModal("openmw_chargen_select_attribute.layout") + , mAttributeId(ESM::Attribute::Strength) { // Centre dialog center(); - for (int i = 0; i < 8; ++i) + const auto& store = MWBase::Environment::get().getWorld()->getStore().get(); + MyGUI::ScrollView* attributes; + getWidget(attributes, "Attributes"); + MyGUI::IntCoord coord{ 0, 0, attributes->getWidth(), 18 }; + for (const ESM::Attribute& attribute : store) { - Widgets::MWAttributePtr attribute; - char theIndex = '0'+i; - - getWidget(attribute, std::string("Attribute").append(1, theIndex)); - attribute->setAttributeId(ESM::Attribute::sAttributeIds[i]); - attribute->eventClicked += MyGUI::newDelegate(this, &SelectAttributeDialog::onAttributeClicked); - ToolTips::createAttributeToolTip(attribute, attribute->getAttributeId()); + auto* widget + = attributes->createWidget("MW_StatNameButtonC", coord, MyGUI::Align::Default); + coord.top += coord.height; + widget->setAttributeId(attribute.mId); + widget->eventClicked += MyGUI::newDelegate(this, &SelectAttributeDialog::onAttributeClicked); + ToolTips::createAttributeToolTip(widget, attribute.mId); } + attributes->setVisibleVScroll(false); + attributes->setCanvasSize(MyGUI::IntSize(attributes->getWidth(), std::max(attributes->getHeight(), coord.top))); + attributes->setVisibleVScroll(true); + attributes->setViewOffset(MyGUI::IntPoint()); + MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectAttributeDialog::onCancelClicked); } - SelectAttributeDialog::~SelectAttributeDialog() - { - } - // widget controls void SelectAttributeDialog::onAttributeClicked(Widgets::MWAttributePtr _sender) { - // TODO: Change MWAttribute to set and get AttributeID enum instead of int - mAttributeId = static_cast(_sender->getAttributeId()); + mAttributeId = _sender->getAttributeId(); eventItemSelected(); } @@ -794,68 +791,42 @@ namespace MWGui return true; } - /* SelectSkillDialog */ SelectSkillDialog::SelectSkillDialog() - : WindowModal("openmw_chargen_select_skill.layout") - , mSkillId(ESM::Skill::Block) + : WindowModal("openmw_chargen_select_skill.layout") + , mSkillId(ESM::Skill::Block) { // Centre dialog center(); - for(int i = 0; i < 9; i++) + std::array, 3> specializations; + getWidget(specializations[ESM::Class::Combat].first, "CombatSkills"); + getWidget(specializations[ESM::Class::Magic].first, "MagicSkills"); + getWidget(specializations[ESM::Class::Stealth].first, "StealthSkills"); + for (auto& [widget, coord] : specializations) { - char theIndex = '0'+i; - getWidget(mCombatSkill[i], std::string("CombatSkill").append(1, theIndex)); - getWidget(mMagicSkill[i], std::string("MagicSkill").append(1, theIndex)); - getWidget(mStealthSkill[i], std::string("StealthSkill").append(1, theIndex)); + coord.width = widget->getCoord().width; + coord.height = 18; + while (widget->getChildCount() > 0) + MyGUI::Gui::getInstance().destroyWidget(widget->getChildAt(0)); } - - struct {Widgets::MWSkillPtr widget; ESM::Skill::SkillEnum skillId;} mSkills[3][9] = { - { - {mCombatSkill[0], ESM::Skill::Block}, - {mCombatSkill[1], ESM::Skill::Armorer}, - {mCombatSkill[2], ESM::Skill::MediumArmor}, - {mCombatSkill[3], ESM::Skill::HeavyArmor}, - {mCombatSkill[4], ESM::Skill::BluntWeapon}, - {mCombatSkill[5], ESM::Skill::LongBlade}, - {mCombatSkill[6], ESM::Skill::Axe}, - {mCombatSkill[7], ESM::Skill::Spear}, - {mCombatSkill[8], ESM::Skill::Athletics} - }, - { - {mMagicSkill[0], ESM::Skill::Enchant}, - {mMagicSkill[1], ESM::Skill::Destruction}, - {mMagicSkill[2], ESM::Skill::Alteration}, - {mMagicSkill[3], ESM::Skill::Illusion}, - {mMagicSkill[4], ESM::Skill::Conjuration}, - {mMagicSkill[5], ESM::Skill::Mysticism}, - {mMagicSkill[6], ESM::Skill::Restoration}, - {mMagicSkill[7], ESM::Skill::Alchemy}, - {mMagicSkill[8], ESM::Skill::Unarmored} - }, - { - {mStealthSkill[0], ESM::Skill::Security}, - {mStealthSkill[1], ESM::Skill::Sneak}, - {mStealthSkill[2], ESM::Skill::Acrobatics}, - {mStealthSkill[3], ESM::Skill::LightArmor}, - {mStealthSkill[4], ESM::Skill::ShortBlade}, - {mStealthSkill[5] ,ESM::Skill::Marksman}, - {mStealthSkill[6] ,ESM::Skill::Mercantile}, - {mStealthSkill[7] ,ESM::Skill::Speechcraft}, - {mStealthSkill[8] ,ESM::Skill::HandToHand} - } - }; - - for (int spec = 0; spec < 3; ++spec) + for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get()) { - for (int i = 0; i < 9; ++i) - { - mSkills[spec][i].widget->setSkillId(mSkills[spec][i].skillId); - mSkills[spec][i].widget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked); - ToolTips::createSkillToolTip(mSkills[spec][i].widget, mSkills[spec][i].widget->getSkillId()); - } + auto& [widget, coord] = specializations[skill.mData.mSpecialization]; + auto* skillWidget + = widget->createWidget("MW_StatNameButton", coord, MyGUI::Align::Default); + coord.top += coord.height; + skillWidget->setSkillId(skill.mId); + skillWidget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked); + ToolTips::createSkillToolTip(skillWidget, skill.mId); + } + for (const auto& [widget, coord] : specializations) + { + widget->setVisibleVScroll(false); + widget->setCanvasSize(MyGUI::IntSize(widget->getWidth(), std::max(widget->getHeight(), coord.top))); + widget->setVisibleVScroll(true); + widget->setViewOffset(MyGUI::IntPoint()); } MyGUI::Button* cancelButton; @@ -863,9 +834,7 @@ namespace MWGui cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSkillDialog::onCancelClicked); } - SelectSkillDialog::~SelectSkillDialog() - { - } + SelectSkillDialog::~SelectSkillDialog() {} // widget controls @@ -889,7 +858,7 @@ namespace MWGui /* DescriptionDialog */ DescriptionDialog::DescriptionDialog() - : WindowModal("openmw_chargen_class_description.layout") + : WindowModal("openmw_chargen_class_description.layout") { // Centre dialog center(); @@ -899,15 +868,14 @@ namespace MWGui MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DescriptionDialog::onOkClicked); - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sInputMenu1", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sInputMenu1", {}))); // Make sure the edit box has focus MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } - DescriptionDialog::~DescriptionDialog() - { - } + DescriptionDialog::~DescriptionDialog() {} // widget controls @@ -916,14 +884,23 @@ namespace MWGui eventDone(this); } - void setClassImage(MyGUI::ImageBox* imageBox, const std::string &classId) + void setClassImage(MyGUI::ImageBox* imageBox, const ESM::RefId& classId) { - std::string classImage = std::string("textures\\levelup\\") + classId + ".dds"; - if (!MWBase::Environment::get().getWindowManager()->textureExists(classImage)) + std::string_view fallback = "textures\\levelup\\warrior.dds"; + std::string classImage; + if (const auto* id = classId.getIf()) { - Log(Debug::Warning) << "No class image for " << classId << ", falling back to default"; - classImage = "textures\\levelup\\warrior.dds"; + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + classImage + = Misc::ResourceHelpers::correctTexturePath("textures\\levelup\\" + id->getValue() + ".dds", vfs); + if (!vfs->exists(classImage)) + { + Log(Debug::Warning) << "No class image for " << classId << ", falling back to default"; + classImage = fallback; + } } + else + classImage = fallback; imageBox->setImageTexture(classImage); } diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index bb34a0553..b231a64b1 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -1,14 +1,21 @@ #ifndef MWGUI_CLASS_H #define MWGUI_CLASS_H +#include +#include + +#include + #include -#include +#include +#include + #include "widgets.hpp" #include "windowbase.hpp" namespace MWGui { - void setClassImage(MyGUI::ImageBox* imageBox, const std::string& classId); + void setClassImage(MyGUI::ImageBox* imageBox, const ESM::RefId& classId); class InfoBoxDialog : public WindowModal { @@ -17,9 +24,9 @@ namespace MWGui typedef std::vector ButtonList; - void setText(const std::string &str); + void setText(const std::string& str); std::string getText() const; - void setButtons(ButtonList &buttons); + void setButtons(ButtonList& buttons); void onOpen() override; @@ -37,7 +44,6 @@ namespace MWGui void onButtonClicked(MyGUI::Widget* _sender); private: - void fitToText(MyGUI::TextBox* widget); void layoutVertically(MyGUI::Widget* widget, int margin); MyGUI::Widget* mTextBox; @@ -66,8 +72,7 @@ namespace MWGui public: GenerateClassResultDialog(); - std::string getClassId() const; - void setClassId(const std::string &classId); + void setClassId(const ESM::RefId& classId); bool exit() override { return false; } @@ -90,9 +95,9 @@ namespace MWGui private: MyGUI::ImageBox* mClassImage; - MyGUI::TextBox* mClassName; + MyGUI::TextBox* mClassName; - std::string mCurrentClassId; + ESM::RefId mCurrentClassId; }; class PickClassDialog : public WindowModal @@ -100,8 +105,8 @@ namespace MWGui public: PickClassDialog(); - const std::string &getClassId() const { return mCurrentClassId; } - void setClassId(const std::string &classId); + const ESM::RefId& getClassId() const { return mCurrentClassId; } + void setClassId(const ESM::RefId& classId); void setNextButtonShow(bool shown); void onOpen() override; @@ -133,13 +138,13 @@ namespace MWGui void updateStats(); MyGUI::ImageBox* mClassImage; - MyGUI::ListBox* mClassList; - MyGUI::TextBox* mSpecializationName; + MyGUI::ListBox* mClassList; + MyGUI::TextBox* mSpecializationName; Widgets::MWAttributePtr mFavoriteAttribute[2]; - Widgets::MWSkillPtr mMajorSkill[5]; - Widgets::MWSkillPtr mMinorSkill[5]; + Widgets::MWSkillPtr mMajorSkill[5]; + Widgets::MWSkillPtr mMinorSkill[5]; - std::string mCurrentClassId; + ESM::RefId mCurrentClassId; }; class SelectSpecializationDialog : public WindowModal @@ -179,7 +184,7 @@ namespace MWGui { public: SelectAttributeDialog(); - ~SelectAttributeDialog(); + ~SelectAttributeDialog() override = default; bool exit() override; @@ -214,7 +219,7 @@ namespace MWGui bool exit() override; - ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } + ESM::RefId getSkillId() const { return mSkillId; } // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; @@ -234,11 +239,7 @@ namespace MWGui void onCancelClicked(MyGUI::Widget* _sender); private: - Widgets::MWSkillPtr mCombatSkill[9]; - Widgets::MWSkillPtr mMagicSkill[9]; - Widgets::MWSkillPtr mStealthSkill[9]; - - ESM::Skill::SkillEnum mSkillId; + ESM::RefId mSkillId; }; class DescriptionDialog : public WindowModal @@ -248,7 +249,7 @@ namespace MWGui ~DescriptionDialog(); std::string getTextInput() const { return mTextEdit->getCaption(); } - void setTextInput(const std::string &text) { mTextEdit->setCaption(text); } + void setTextInput(const std::string& text) { mTextEdit->setCaption(text); } /** Event : Dialog finished, OK button clicked.\n signature : void method()\n @@ -274,8 +275,8 @@ namespace MWGui std::string getDescription() const; ESM::Class::Specialization getSpecializationId() const; std::vector getFavoriteAttributes() const; - std::vector getMajorSkills() const; - std::vector getMinorSkills() const; + std::vector getMajorSkills() const; + std::vector getMinorSkills() const; void setNextButtonShow(bool shown); @@ -311,23 +312,23 @@ namespace MWGui void update(); private: - MyGUI::EditBox* mEditName; - MyGUI::TextBox* mSpecializationName; - Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1; - Widgets::MWSkillPtr mMajorSkill[5]; - Widgets::MWSkillPtr mMinorSkill[5]; + MyGUI::EditBox* mEditName; + MyGUI::TextBox* mSpecializationName; + Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1; + std::array mMajorSkill; + std::array mMinorSkill; std::vector mSkills; - std::string mDescription; + std::string mDescription; - SelectSpecializationDialog *mSpecDialog; - SelectAttributeDialog *mAttribDialog; - SelectSkillDialog *mSkillDialog; - DescriptionDialog *mDescDialog; + std::unique_ptr mSpecDialog; + std::unique_ptr mAttribDialog; + std::unique_ptr mSkillDialog; + std::unique_ptr mDescDialog; - ESM::Class::Specialization mSpecializationId; + ESM::Class::Specialization mSpecializationId; - Widgets::MWAttributePtr mAffectedAttribute; - Widgets::MWSkillPtr mAffectedSkill; + Widgets::MWAttributePtr mAffectedAttribute; + Widgets::MWSkillPtr mAffectedSkill; }; } #endif diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index 3a272aa86..1172de589 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -7,7 +7,7 @@ namespace void modifyProfit(const MWWorld::Ptr& actor, int diff) { - std::string script = actor.getClass().getScript(actor); + const ESM::RefId& script = actor.getClass().getScript(actor); if (!script.empty()) { int profit = actor.getRefData().getLocals().getIntVar(script, "minimumprofit"); @@ -20,12 +20,12 @@ namespace namespace MWGui { - CompanionItemModel::CompanionItemModel(const MWWorld::Ptr &actor) + CompanionItemModel::CompanionItemModel(const MWWorld::Ptr& actor) : InventoryItemModel(actor) { } - MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) + MWWorld::Ptr CompanionItemModel::copyItem(const ItemStack& item, size_t count, bool allowAutoEquip) { if (hasProfit(mActor)) modifyProfit(mActor, item.mBase.getClass().getValue(item.mBase) * count); @@ -33,7 +33,7 @@ namespace MWGui return InventoryItemModel::copyItem(item, count, allowAutoEquip); } - void CompanionItemModel::removeItem (const ItemStack& item, size_t count) + void CompanionItemModel::removeItem(const ItemStack& item, size_t count) { if (hasProfit(mActor)) modifyProfit(mActor, -item.mBase.getClass().getValue(item.mBase) * count); @@ -41,9 +41,9 @@ namespace MWGui InventoryItemModel::removeItem(item, count); } - bool CompanionItemModel::hasProfit(const MWWorld::Ptr &actor) + bool CompanionItemModel::hasProfit(const MWWorld::Ptr& actor) { - std::string script = actor.getClass().getScript(actor); + const ESM::RefId& script = actor.getClass().getScript(actor); if (script.empty()) return false; return actor.getRefData().getLocals().hasVar(script, "minimumprofit"); diff --git a/apps/openmw/mwgui/companionitemmodel.hpp b/apps/openmw/mwgui/companionitemmodel.hpp index 1872dd156..0a9491c34 100644 --- a/apps/openmw/mwgui/companionitemmodel.hpp +++ b/apps/openmw/mwgui/companionitemmodel.hpp @@ -11,10 +11,10 @@ namespace MWGui class CompanionItemModel : public InventoryItemModel { public: - CompanionItemModel (const MWWorld::Ptr& actor); + CompanionItemModel(const MWWorld::Ptr& actor); - MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; - void removeItem (const ItemStack& item, size_t count) override; + MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; + void removeItem(const ItemStack& item, size_t count) override; bool hasProfit(const MWWorld::Ptr& actor); }; diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 926456219..1aa00d87c 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include "../mwbase/environment.hpp" @@ -9,21 +11,21 @@ #include "../mwworld/class.hpp" -#include "messagebox.hpp" -#include "itemview.hpp" -#include "sortfilteritemmodel.hpp" #include "companionitemmodel.hpp" -#include "draganddrop.hpp" #include "countdialog.hpp" -#include "widgets.hpp" +#include "draganddrop.hpp" +#include "itemview.hpp" +#include "messagebox.hpp" +#include "sortfilteritemmodel.hpp" #include "tooltips.hpp" +#include "widgets.hpp" namespace { int getProfit(const MWWorld::Ptr& actor) { - std::string script = actor.getClass().getScript(actor); + const ESM::RefId& script = actor.getClass().getScript(actor); if (!script.empty()) { return actor.getRefData().getLocals().getIntVar(script, "minimumprofit"); @@ -36,164 +38,166 @@ namespace namespace MWGui { -CompanionWindow::CompanionWindow(DragAndDrop *dragAndDrop, MessageBoxManager* manager) - : WindowBase("openmw_companion_window.layout") - , mSortModel(nullptr) - , mModel(nullptr) - , mSelectedItem(-1) - , mDragAndDrop(dragAndDrop) - , mMessageBoxManager(manager) -{ - getWidget(mCloseButton, "CloseButton"); - getWidget(mProfitLabel, "ProfitLabel"); - getWidget(mEncumbranceBar, "EncumbranceBar"); - getWidget(mFilterEdit, "FilterEdit"); - getWidget(mItemView, "ItemView"); - mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &CompanionWindow::onBackgroundSelected); - mItemView->eventItemClicked += MyGUI::newDelegate(this, &CompanionWindow::onItemSelected); - mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &CompanionWindow::onNameFilterChanged); - - mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CompanionWindow::onCloseButtonClicked); - - setCoord(200,0,600,300); -} - -void CompanionWindow::onItemSelected(int index) -{ - if (mDragAndDrop->mIsOnDragAndDrop) + CompanionWindow::CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager) + : WindowBase("openmw_companion_window.layout") + , mSortModel(nullptr) + , mModel(nullptr) + , mSelectedItem(-1) + , mDragAndDrop(dragAndDrop) + , mMessageBoxManager(manager) { - mDragAndDrop->drop(mModel, mItemView); - updateEncumbranceBar(); - return; + getWidget(mCloseButton, "CloseButton"); + getWidget(mProfitLabel, "ProfitLabel"); + getWidget(mEncumbranceBar, "EncumbranceBar"); + getWidget(mFilterEdit, "FilterEdit"); + getWidget(mItemView, "ItemView"); + mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &CompanionWindow::onBackgroundSelected); + mItemView->eventItemClicked += MyGUI::newDelegate(this, &CompanionWindow::onItemSelected); + mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &CompanionWindow::onNameFilterChanged); + + mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CompanionWindow::onCloseButtonClicked); + + setCoord(200, 0, 600, 300); } - const ItemStack& item = mSortModel->getItem(index); - - // We can't take conjured items from a companion NPC - if (item.mFlags & ItemStack::Flag_Bound) + void CompanionWindow::onItemSelected(int index) { - MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); - return; + if (mDragAndDrop->mIsOnDragAndDrop) + { + mDragAndDrop->drop(mModel, mItemView); + updateEncumbranceBar(); + return; + } + + const ItemStack& item = mSortModel->getItem(index); + + // We can't take conjured items from a companion NPC + if (item.mFlags & ItemStack::Flag_Bound) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); + return; + } + + MWWorld::Ptr object = item.mBase; + int count = item.mCount; + bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); + if (MyGUI::InputManager::getInstance().isControlPressed()) + count = 1; + + mSelectedItem = mSortModel->mapToSource(index); + + if (count > 1 && !shift) + { + CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); + std::string name{ object.getClass().getName(object) }; + name += MWGui::ToolTips::getSoulString(object.getCellRef()); + dialog->openCountDialog(name, "#{sTake}", count); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem); + } + else + dragItem(nullptr, count); } - MWWorld::Ptr object = item.mBase; - int count = item.mCount; - bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); - if (MyGUI::InputManager::getInstance().isControlPressed()) - count = 1; - - mSelectedItem = mSortModel->mapToSource(index); - - if (count > 1 && !shift) - { - CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - std::string name = object.getClass().getName(object) + MWGui::ToolTips::getSoulString(object.getCellRef()); - dialog->openCountDialog(name, "#{sTake}", count); - dialog->eventOkClicked.clear(); - dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem); - } - else - dragItem (nullptr, count); -} - -void CompanionWindow::onNameFilterChanged(MyGUI::EditBox* _sender) + void CompanionWindow::onNameFilterChanged(MyGUI::EditBox* _sender) { mSortModel->setNameFilter(_sender->getCaption()); mItemView->update(); } -void CompanionWindow::dragItem(MyGUI::Widget* sender, int count) -{ - mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); -} - -void CompanionWindow::onBackgroundSelected() -{ - if (mDragAndDrop->mIsOnDragAndDrop) + void CompanionWindow::dragItem(MyGUI::Widget* sender, int count) { - mDragAndDrop->drop(mModel, mItemView); + mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); + } + + void CompanionWindow::onBackgroundSelected() + { + if (mDragAndDrop->mIsOnDragAndDrop) + { + mDragAndDrop->drop(mModel, mItemView); + updateEncumbranceBar(); + } + } + + void CompanionWindow::setPtr(const MWWorld::Ptr& npc) + { + mPtr = npc; + updateEncumbranceBar(); + auto model = std::make_unique(npc); + mModel = model.get(); + auto sortModel = std::make_unique(std::move(model)); + mSortModel = sortModel.get(); + mFilterEdit->setCaption({}); + mItemView->setModel(std::move(sortModel)); + mItemView->resetScrollBars(); + + setTitle(npc.getClass().getName(npc)); + } + + void CompanionWindow::onFrame(float dt) + { + checkReferenceAvailable(); updateEncumbranceBar(); } -} -void CompanionWindow::setPtr(const MWWorld::Ptr& npc) -{ - mPtr = npc; - updateEncumbranceBar(); - - mModel = new CompanionItemModel(npc); - mSortModel = new SortFilterItemModel(mModel); - mFilterEdit->setCaption(std::string()); - mItemView->setModel(mSortModel); - mItemView->resetScrollBars(); - - setTitle(npc.getClass().getName(npc)); -} - -void CompanionWindow::onFrame(float dt) -{ - checkReferenceAvailable(); - updateEncumbranceBar(); -} - -void CompanionWindow::updateEncumbranceBar() -{ - if (mPtr.isEmpty()) - return; - float capacity = mPtr.getClass().getCapacity(mPtr); - float encumbrance = mPtr.getClass().getEncumbrance(mPtr); - mEncumbranceBar->setValue(std::ceil(encumbrance), static_cast(capacity)); - - if (mModel && mModel->hasProfit(mPtr)) + void CompanionWindow::updateEncumbranceBar() { - mProfitLabel->setCaptionWithReplacing("#{sProfitValue} " + MyGUI::utility::toString(getProfit(mPtr))); + if (mPtr.isEmpty()) + return; + float capacity = mPtr.getClass().getCapacity(mPtr); + float encumbrance = mPtr.getClass().getEncumbrance(mPtr); + mEncumbranceBar->setValue(std::ceil(encumbrance), static_cast(capacity)); + + if (mModel && mModel->hasProfit(mPtr)) + { + mProfitLabel->setCaptionWithReplacing("#{sProfitValue} " + MyGUI::utility::toString(getProfit(mPtr))); + } + else + mProfitLabel->setCaption({}); } - else - mProfitLabel->setCaption(""); -} -void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender) -{ - if (exit()) - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); -} - -bool CompanionWindow::exit() -{ - if (mModel && mModel->hasProfit(mPtr) && getProfit(mPtr) < 0) + void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender) { - std::vector buttons; - buttons.emplace_back("#{sCompanionWarningButtonOne}"); - buttons.emplace_back("#{sCompanionWarningButtonTwo}"); - mMessageBoxManager->createInteractiveMessageBox("#{sCompanionWarningMessage}", buttons); - mMessageBoxManager->eventButtonPressed += MyGUI::newDelegate(this, &CompanionWindow::onMessageBoxButtonClicked); - return false; + if (exit()) + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); } - return true; -} -void CompanionWindow::onMessageBoxButtonClicked(int button) -{ - if (button == 0) + bool CompanionWindow::exit() + { + if (mModel && mModel->hasProfit(mPtr) && getProfit(mPtr) < 0) + { + std::vector buttons; + buttons.emplace_back("#{sCompanionWarningButtonOne}"); + buttons.emplace_back("#{sCompanionWarningButtonTwo}"); + mMessageBoxManager->createInteractiveMessageBox("#{sCompanionWarningMessage}", buttons); + mMessageBoxManager->eventButtonPressed + += MyGUI::newDelegate(this, &CompanionWindow::onMessageBoxButtonClicked); + return false; + } + return true; + } + + void CompanionWindow::onMessageBoxButtonClicked(int button) + { + if (button == 0) + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); + // Important for Calvus' contract script to work properly + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + } + } + + void CompanionWindow::onReferenceUnavailable() { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); - // Important for Calvus' contract script to work properly - MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } -} - -void CompanionWindow::onReferenceUnavailable() -{ - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); -} - -void CompanionWindow::resetReference() -{ - ReferenceInterface::resetReference(); - mItemView->setModel(nullptr); - mModel = nullptr; - mSortModel = nullptr; -} + void CompanionWindow::resetReference() + { + ReferenceInterface::resetReference(); + mItemView->setModel(nullptr); + mModel = nullptr; + mSortModel = nullptr; + } } diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index 7a7c92e3d..40fac26eb 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_MWGUI_COMPANIONWINDOW_H #define OPENMW_MWGUI_COMPANIONWINDOW_H -#include "windowbase.hpp" #include "referenceinterface.hpp" +#include "windowbase.hpp" namespace MWGui { @@ -27,7 +27,7 @@ namespace MWGui void resetReference() override; void setPtr(const MWWorld::Ptr& npc) override; - void onFrame (float dt) override; + void onFrame(float dt) override; void clear() override { resetReference(); } private: diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 9ebaf3919..48b209f17 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -8,8 +8,8 @@ namespace MWGui { - ConfirmationDialog::ConfirmationDialog() : - WindowModal("openmw_confirmation_dialog.layout") + ConfirmationDialog::ConfirmationDialog() + : WindowModal("openmw_confirmation_dialog.layout") { getWidget(mMessage, "Message"); getWidget(mOkButton, "OkButton"); diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp index 2acefd54c..649e8ba1e 100644 --- a/apps/openmw/mwgui/confirmationdialog.hpp +++ b/apps/openmw/mwgui/confirmationdialog.hpp @@ -7,26 +7,26 @@ namespace MWGui { class ConfirmationDialog : public WindowModal { - public: - ConfirmationDialog(); - void askForConfirmation(const std::string& message); - bool exit() override; + public: + ConfirmationDialog(); + void askForConfirmation(const std::string& message); + bool exit() override; - typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; - /** Event : Ok button was clicked.\n - signature : void method()\n - */ - EventHandle_Void eventOkClicked; - EventHandle_Void eventCancelClicked; + /** Event : Ok button was clicked.\n + signature : void method()\n + */ + EventHandle_Void eventOkClicked; + EventHandle_Void eventCancelClicked; - private: - MyGUI::EditBox* mMessage; - MyGUI::Button* mOkButton; - MyGUI::Button* mCancelButton; + private: + MyGUI::EditBox* mMessage; + MyGUI::Button* mOkButton; + MyGUI::Button* mCancelButton; - void onCancelButtonClicked(MyGUI::Widget* _sender); - void onOkButtonClicked(MyGUI::Widget* _sender); + void onCancelButtonClicked(MyGUI::Widget* _sender); + void onOkButtonClicked(MyGUI::Widget* _sender); }; } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index f8dae518e..e04f43420 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -1,11 +1,12 @@ #include "console.hpp" +#include #include #include #include -#include -#include +#include +#include /* Start of tes3mp addition @@ -24,59 +25,62 @@ #include #include #include -#include #include +#include +#include #include +#include +#include #include "../mwscript/extensions.hpp" +#include "../mwscript/interpretercontext.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" namespace MWGui { class ConsoleInterpreterContext : public MWScript::InterpreterContext { - Console& mConsole; + Console& mConsole; - public: + public: + ConsoleInterpreterContext(Console& console, MWWorld::Ptr reference); - ConsoleInterpreterContext (Console& console, MWWorld::Ptr reference); - - void report (const std::string& message) override; + void report(const std::string& message) override; }; - ConsoleInterpreterContext::ConsoleInterpreterContext (Console& console, - MWWorld::Ptr reference) - : MWScript::InterpreterContext ( - reference.isEmpty() ? nullptr : &reference.getRefData().getLocals(), reference), - mConsole (console) - {} - - void ConsoleInterpreterContext::report (const std::string& message) + ConsoleInterpreterContext::ConsoleInterpreterContext(Console& console, MWWorld::Ptr reference) + : MWScript::InterpreterContext(reference.isEmpty() ? nullptr : &reference.getRefData().getLocals(), reference) + , mConsole(console) { - mConsole.printOK (message); } - bool Console::compile (const std::string& cmd, Compiler::Output& output) + void ConsoleInterpreterContext::report(const std::string& message) + { + mConsole.printOK(message); + } + + bool Console::compile(const std::string& cmd, Compiler::Output& output) { try { ErrorHandler::reset(); - std::istringstream input (cmd + '\n'); + std::istringstream input(cmd + '\n'); - Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions()); + Compiler::Scanner scanner(*this, input, mCompilerContext.getExtensions()); - Compiler::LineParser parser (*this, mCompilerContext, output.getLocals(), - output.getLiterals(), output.getCode(), true); + Compiler::LineParser parser( + *this, mCompilerContext, output.getLocals(), output.getLiterals(), output.getCode(), true); - scanner.scan (parser); + scanner.scan(parser); return isGood(); } @@ -86,24 +90,24 @@ namespace MWGui } catch (const std::exception& error) { - printError (std::string ("Error: ") + error.what()); + printError(std::string("Error: ") + error.what()); } return false; } - void Console::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) + void Console::report(const std::string& message, const Compiler::TokenLoc& loc, Type type) { std::ostringstream error; error << "column " << loc.mColumn << " (" << loc.mLiteral << "):"; - printError (error.str()); - printError ((type==ErrorMessage ? "error: " : "warning: ") + message); + printError(error.str()); + printError((type == ErrorMessage ? "error: " : "warning: ") + message); } - void Console::report (const std::string& message, Type type) + void Console::report(const std::string& message, Type type) { - printError ((type==ErrorMessage ? "error: " : "warning: ") + message); + printError((type == ErrorMessage ? "error: " : "warning: ") + message); } void Console::listNames() @@ -111,60 +115,80 @@ namespace MWGui if (mNames.empty()) { // keywords - std::istringstream input (""); + std::istringstream input; - Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions()); + Compiler::Scanner scanner(*this, input, mCompilerContext.getExtensions()); - scanner.listKeywords (mNames); + scanner.listKeywords(mNames); // identifier - const MWWorld::ESMStore& store = - MWBase::Environment::get().getWorld()->getStore(); - - for (MWWorld::ESMStore::iterator it = store.begin(); it != store.end(); ++it) + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + std::vector ids; + for (const auto* store : esmStore) { - it->second->listIdentifier (mNames); + store->listIdentifier(ids); + for (auto id : ids) + { + if (id.is()) + mNames.push_back(id.getRefIdString()); + } + ids.clear(); } // exterior cell names aren't technically identifiers, but since the COC function accepts them, // we should list them too - for (MWWorld::Store::iterator it = store.get().extBegin(); - it != store.get().extEnd(); ++it) + for (auto it = esmStore.get().extBegin(); it != esmStore.get().extEnd(); ++it) { if (!it->mName.empty()) mNames.push_back(it->mName); } // sort - std::sort (mNames.begin(), mNames.end()); + std::sort(mNames.begin(), mNames.end()); // remove duplicates - mNames.erase( std::unique( mNames.begin(), mNames.end() ), mNames.end() ); + mNames.erase(std::unique(mNames.begin(), mNames.end()), mNames.end()); } } - Console::Console(int w, int h, bool consoleOnlyScripts) - : WindowBase("openmw_console.layout"), - mCompilerContext (MWScript::CompilerContext::Type_Console), - mConsoleOnlyScripts (consoleOnlyScripts) + Console::Console(int w, int h, bool consoleOnlyScripts, Files::ConfigurationManager& cfgMgr) + : WindowBase("openmw_console.layout") + , mCompilerContext(MWScript::CompilerContext::Type_Console) + , mConsoleOnlyScripts(consoleOnlyScripts) + , mCfgMgr(cfgMgr) { - setCoord(10,10, w-10, h/2); + setCoord(10, 10, w - 10, h / 2); getWidget(mCommandLine, "edit_Command"); getWidget(mHistory, "list_History"); + getWidget(mSearchTerm, "edit_SearchTerm"); + getWidget(mNextButton, "button_Next"); + getWidget(mPreviousButton, "button_Previous"); // Set up the command line box - mCommandLine->eventEditSelectAccept += - newDelegate(this, &Console::acceptCommand); - mCommandLine->eventKeyButtonPressed += - newDelegate(this, &Console::keyPress); + mCommandLine->eventEditSelectAccept += newDelegate(this, &Console::acceptCommand); + mCommandLine->eventKeyButtonPressed += newDelegate(this, &Console::commandBoxKeyPress); + + // Set up the search term box + mSearchTerm->eventEditSelectAccept += newDelegate(this, &Console::acceptSearchTerm); + mNextButton->eventMouseButtonClick += newDelegate(this, &Console::findNextOccurrence); + mPreviousButton->eventMouseButtonClick += newDelegate(this, &Console::findPreviousOccurence); // Set up the log window mHistory->setOverflowToTheLeft(true); // compiler - Compiler::registerExtensions (mExtensions, mConsoleOnlyScripts); - mCompilerContext.setExtensions (&mExtensions); + Compiler::registerExtensions(mExtensions, mConsoleOnlyScripts); + mCompilerContext.setExtensions(&mExtensions); + + // command history file + initConsoleHistory(); + } + + Console::~Console() + { + if (mCommandHistoryFile && mCommandHistoryFile.is_open()) + mCommandHistoryFile.close(); } void Console::onOpen() @@ -175,39 +199,49 @@ namespace MWGui MyGUI::LayerManager::getInstance().upLayerItem(mMainWidget); } - void Console::print(const std::string &msg, const std::string& color) + void Console::print(const std::string& msg, std::string_view color) { - mHistory->addText(color + MyGUI::TextIterator::toTagsString(msg)); + mHistory->addText(std::string(color) + MyGUI::TextIterator::toTagsString(msg)); } - void Console::printOK(const std::string &msg) + void Console::printOK(const std::string& msg) { - print(msg + "\n", "#FF00FF"); + print(msg + "\n", MWBase::WindowManager::sConsoleColor_Success); } - void Console::printError(const std::string &msg) + void Console::printError(const std::string& msg) { - print(msg + "\n", "#FF2222"); + print(msg + "\n", MWBase::WindowManager::sConsoleColor_Error); } - void Console::execute (const std::string& command) + void Console::execute(const std::string& command) { // Log the command - print("> " + command + "\n"); + if (mConsoleMode.empty()) + print("> " + command + "\n"); + else + print(mConsoleMode + " " + command + "\n"); + + if (!mConsoleMode.empty() || (command.size() >= 3 && std::string_view(command).substr(0, 3) == "lua")) + { + MWBase::Environment::get().getLuaManager()->handleConsoleCommand(mConsoleMode, command, mPtr); + return; + } Compiler::Locals locals; if (!mPtr.isEmpty()) { - std::string script = mPtr.getClass().getScript(mPtr); + const ESM::RefId& script = mPtr.getClass().getScript(mPtr); if (!script.empty()) locals = MWBase::Environment::get().getScriptManager()->getLocals(script); } - Compiler::Output output (locals); + Compiler::Output output(locals); - if (compile (command + "\n", output)) + if (compile(command + "\n", output)) { try { +<<<<<<< HEAD ConsoleInterpreterContext interpreterContext (*this, mPtr); /* @@ -239,33 +273,35 @@ namespace MWGui End of tes3mp addition */ +======= + ConsoleInterpreterContext interpreterContext(*this, mPtr); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 Interpreter::Interpreter interpreter; - MWScript::installOpcodes (interpreter, mConsoleOnlyScripts); - std::vector code; - output.getCode (code); - interpreter.run (&code[0], code.size(), interpreterContext); + MWScript::installOpcodes(interpreter, mConsoleOnlyScripts); + const Interpreter::Program program = output.getProgram(); + interpreter.run(program, interpreterContext); } catch (const std::exception& error) { - printError (std::string ("Error: ") + error.what()); + printError(std::string("Error: ") + error.what()); } } } - void Console::executeFile (const std::string& path) + void Console::executeFile(const std::filesystem::path& path) { - namespace bfs = boost::filesystem; - bfs::ifstream stream ((bfs::path(path))); + std::ifstream stream(path); if (!stream.is_open()) - printError ("failed to open file: " + path); - else { - std::string line; - - while (std::getline (stream, line)) - execute (line); + printError("Failed to open script file \"" + Files::pathToUnicodeString(path) + + "\": " + std::generic_category().message(errno)); + return; } + + std::string line; + while (std::getline(stream, line)) + execute(line); } void Console::clear() @@ -278,24 +314,22 @@ namespace MWGui return c == ' ' || c == '\t'; } - void Console::keyPress(MyGUI::Widget* _sender, - MyGUI::KeyCode key, - MyGUI::Char _char) + void Console::commandBoxKeyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char) { - if(MyGUI::InputManager::getInstance().isControlPressed()) + if (MyGUI::InputManager::getInstance().isControlPressed()) { - if(key == MyGUI::KeyCode::W) + if (key == MyGUI::KeyCode::W) { const auto& caption = mCommandLine->getCaption(); - if(caption.empty()) + if (caption.empty()) return; size_t max = mCommandLine->getTextCursor(); - while(max > 0 && (isWhitespace(caption[max - 1]) || caption[max - 1] == '>')) + while (max > 0 && (isWhitespace(caption[max - 1]) || caption[max - 1] == '>')) max--; - while(max > 0 && !isWhitespace(caption[max - 1]) && caption[max - 1] != '>') + while (max > 0 && !isWhitespace(caption[max - 1]) && caption[max - 1] != '>') max--; size_t length = mCommandLine->getTextCursor() - max; - if(length > 0) + if (length > 0) { auto text = caption; text.erase(max, length); @@ -303,9 +337,9 @@ namespace MWGui mCommandLine->setTextCursor(max); } } - else if(key == MyGUI::KeyCode::U) + else if (key == MyGUI::KeyCode::U) { - if(mCommandLine->getTextCursor() > 0) + if (mCommandLine->getTextCursor() > 0) { auto text = mCommandLine->getCaption(); text.erase(0, mCommandLine->getTextCursor()); @@ -314,22 +348,22 @@ namespace MWGui } } } - else if(key == MyGUI::KeyCode::Tab) + else if (key == MyGUI::KeyCode::Tab && mConsoleMode.empty()) { std::vector matches; listNames(); std::string oldCaption = mCommandLine->getCaption(); - std::string newCaption = complete( mCommandLine->getOnlyText(), matches ); + std::string newCaption = complete(mCommandLine->getOnlyText(), matches); mCommandLine->setCaption(newCaption); // List candidates if repeatedly pressing tab if (oldCaption == newCaption && !matches.empty()) { int i = 0; - printOK(""); - for(std::string& match : matches) + printOK({}); + for (std::string& match : matches) { - if(i == 50) + if (i == 50) break; printOK(match); @@ -338,28 +372,29 @@ namespace MWGui } } - if(mCommandHistory.empty()) return; + if (mCommandHistory.empty()) + return; // Traverse history with up and down arrows - if(key == MyGUI::KeyCode::ArrowUp) + if (key == MyGUI::KeyCode::ArrowUp) { // If the user was editing a string, store it for later - if(mCurrent == mCommandHistory.end()) + if (mCurrent == mCommandHistory.end()) mEditString = mCommandLine->getOnlyText(); - if(mCurrent != mCommandHistory.begin()) + if (mCurrent != mCommandHistory.begin()) { --mCurrent; mCommandLine->setCaption(*mCurrent); } } - else if(key == MyGUI::KeyCode::ArrowDown) + else if (key == MyGUI::KeyCode::ArrowDown) { - if(mCurrent != mCommandHistory.end()) + if (mCurrent != mCommandHistory.end()) { ++mCurrent; - if(mCurrent != mCommandHistory.end()) + if (mCurrent != mCommandHistory.end()) mCommandLine->setCaption(*mCurrent); else // Restore the edit string @@ -370,25 +405,156 @@ namespace MWGui void Console::acceptCommand(MyGUI::EditBox* _sender) { - const std::string &cm = mCommandLine->getOnlyText(); - if(cm.empty()) return; + const std::string& cm = mCommandLine->getOnlyText(); + if (cm.empty()) + return; // Add the command to the history, and set the current pointer to // the end of the list if (mCommandHistory.empty() || mCommandHistory.back() != cm) + { mCommandHistory.push_back(cm); + + if (mCommandHistoryFile && mCommandHistoryFile.good()) + mCommandHistoryFile << cm << std::endl; + } mCurrent = mCommandHistory.end(); mEditString.clear(); + mHistory->setTextCursor(mHistory->getTextLength()); // Reset the command line before the command execution. - // It prevents the re-triggering of the acceptCommand() event for the same command + // It prevents the re-triggering of the acceptCommand() event for the same command // during the actual command execution - mCommandLine->setCaption(""); + mCommandLine->setCaption({}); - execute (cm); + execute(cm); } - std::string Console::complete( std::string input, std::vector &matches ) + void Console::acceptSearchTerm(MyGUI::EditBox* _sender) + { + const std::string& searchTerm = mSearchTerm->getOnlyText(); + + if (searchTerm.empty()) + { + return; + } + + mCurrentSearchTerm = Utf8Stream::lowerCaseUtf8(searchTerm); + mCurrentOccurrence = std::string::npos; + + findNextOccurrence(nullptr); + } + + void Console::findNextOccurrence(MyGUI::Widget* _sender) + { + if (mCurrentSearchTerm.empty()) + { + return; + } + + const auto historyText = Utf8Stream::lowerCaseUtf8(mHistory->getOnlyText().asUTF8()); + + // Search starts at the beginning + size_t startIndex = 0; + + // If this is not the first search, we start right AFTER the last occurrence. + if (mCurrentOccurrence != std::string::npos && historyText.length() - mCurrentOccurrence > 1) + { + startIndex = mCurrentOccurrence + 1; + } + + mCurrentOccurrence = historyText.find(mCurrentSearchTerm, startIndex); + + // If the last search did not find anything AND we didn't start at + // the beginning, we repeat the search one time for wrapping around the text. + if (mCurrentOccurrence == std::string::npos && startIndex != 0) + { + mCurrentOccurrence = historyText.find(mCurrentSearchTerm); + } + + // Only scroll & select if we actually found something + if (mCurrentOccurrence != std::string::npos) + { + markOccurrence(mCurrentOccurrence, mCurrentSearchTerm.length()); + } + else + { + markOccurrence(0, 0); + } + } + + void Console::findPreviousOccurence(MyGUI::Widget* _sender) + { + if (mCurrentSearchTerm.empty()) + { + return; + } + + const auto historyText = Utf8Stream::lowerCaseUtf8(mHistory->getOnlyText().asUTF8()); + + // Search starts at the end + size_t startIndex = historyText.length(); + + // If this is not the first search, we start right BEFORE the last occurrence. + if (mCurrentOccurrence != std::string::npos && mCurrentOccurrence > 1) + { + startIndex = mCurrentOccurrence - 1; + } + + mCurrentOccurrence = historyText.rfind(mCurrentSearchTerm, startIndex); + + // If the last search did not find anything AND we didn't start at + // the end, we repeat the search one time for wrapping around the text. + if (mCurrentOccurrence == std::string::npos && startIndex != historyText.length()) + { + mCurrentOccurrence = historyText.rfind(mCurrentSearchTerm, historyText.length()); + } + + // Only scroll & select if we actually found something + if (mCurrentOccurrence != std::string::npos) + { + markOccurrence(mCurrentOccurrence, mCurrentSearchTerm.length()); + } + else + { + markOccurrence(0, 0); + } + } + + void Console::markOccurrence(const size_t textPosition, const size_t length) + { + if (textPosition == 0 && length == 0) + { + mHistory->setTextSelection(0, 0); + mHistory->setVScrollPosition(mHistory->getVScrollRange()); + return; + } + + const auto historyText = mHistory->getOnlyText(); + const size_t upperLimit = std::min(historyText.length(), textPosition); + + // Since MyGUI::EditBox.setVScrollPosition() works on pixels instead of text positions + // we need to calculate the actual pixel position by counting lines. + size_t lineNumber = 0; + for (size_t i = 0; i < upperLimit; i++) + { + if (historyText[i] == '\n') + { + lineNumber++; + } + } + + // Make some space before the actual result + if (lineNumber >= 2) + { + lineNumber -= 2; + } + + mHistory->setTextSelection(textPosition, textPosition + length); + mHistory->setVScrollPosition(mHistory->getFontHeight() * lineNumber); + } + + std::string Console::complete(std::string input, std::vector& matches) { std::string output = input; std::string tmp = input; @@ -400,73 +566,87 @@ namespace MWGui size_t explicitPos = tmp.find("->"); if (explicitPos != std::string::npos) { - tmp.erase(0, explicitPos+2); + tmp.erase(0, explicitPos + 2); } /* Are there quotation marks? */ - if( tmp.find('"') != std::string::npos ) { - int numquotes=0; - for(std::string::iterator it=tmp.begin(); it < tmp.end(); ++it) { - if( *it == '"' ) + if (tmp.find('"') != std::string::npos) + { + int numquotes = 0; + for (std::string::iterator it = tmp.begin(); it < tmp.end(); ++it) + { + if (*it == '"') numquotes++; } /* Is it terminated?*/ - if( numquotes % 2 ) { - tmp.erase( 0, tmp.rfind('"')+1 ); + if (numquotes % 2) + { + tmp.erase(0, tmp.rfind('"') + 1); has_front_quote = true; } - else { + else + { size_t pos; - if( ( ((pos = tmp.rfind(' ')) != std::string::npos ) ) && ( pos > tmp.rfind('"') ) ) { - tmp.erase( 0, tmp.rfind(' ')+1); + if ((((pos = tmp.rfind(' ')) != std::string::npos)) && (pos > tmp.rfind('"'))) + { + tmp.erase(0, tmp.rfind(' ') + 1); } - else { + else + { tmp.clear(); } has_front_quote = false; } } /* No quotation marks. Are there spaces?*/ - else { + else + { size_t rpos; - if( (rpos=tmp.rfind(' ')) != std::string::npos ) { - if( rpos == 0 ) { + if ((rpos = tmp.rfind(' ')) != std::string::npos) + { + if (rpos == 0) + { tmp.clear(); } - else { - tmp.erase(0, rpos+1); + else + { + tmp.erase(0, rpos + 1); } } } /* Erase the input from the output string so we can easily append the completed form later. */ - output.erase(output.end()-tmp.length(), output.end()); + output.erase(output.end() - tmp.length(), output.end()); - /* Is there still something in the input string? If not just display all commands and return the unchanged input. */ - if( tmp.length() == 0 ) { - matches=mNames; + /* Is there still something in the input string? If not just display all commands and return the unchanged + * input. */ + if (tmp.length() == 0) + { + matches = mNames; return input; } /* Iterate through the vector. */ - for(std::string& name : mNames) + for (std::string& name : mNames) { - bool string_different=false; + bool string_different = false; /* Is the string shorter than the input string? If yes skip it. */ - if(name.length() < tmp.length()) + if (name.length() < tmp.length()) continue; /* Is the beginning of the string different from the input string? If yes skip it. */ - for( std::string::iterator iter=tmp.begin(), iter2=name.begin(); iter < tmp.end();++iter, ++iter2) { - if( Misc::StringUtils::toLower(*iter) != Misc::StringUtils::toLower(*iter2) ) { - string_different=true; + for (std::string::iterator iter = tmp.begin(), iter2 = name.begin(); iter < tmp.end(); ++iter, ++iter2) + { + if (Misc::StringUtils::toLower(*iter) != Misc::StringUtils::toLower(*iter2)) + { + string_different = true; break; } } - if( string_different ) + if (string_different) continue; /* The beginning of the string matches the input string, save it for the next test. */ @@ -474,23 +654,27 @@ namespace MWGui } /* There are no matches. Return the unchanged input. */ - if( matches.empty() ) + if (matches.empty()) { return input; } /* Only one match. We're done. */ - if( matches.size() == 1 ) { + if (matches.size() == 1) + { /* Adding quotation marks when the input string started with a quotation mark or has spaces in it*/ - if( ( matches.front().find(' ') != std::string::npos ) ) { - if( !has_front_quote ) - output.append(std::string("\"")); + if ((matches.front().find(' ') != std::string::npos)) + { + if (!has_front_quote) + output += '"'; return output.append(matches.front() + std::string("\" ")); } - else if( has_front_quote ) { - return output.append(matches.front() + std::string("\" ")); + else if (has_front_quote) + { + return output.append(matches.front() + std::string("\" ")); } - else { + else + { return output.append(matches.front() + std::string(" ")); } } @@ -498,11 +682,12 @@ namespace MWGui /* Check if all matching strings match further than input. If yes complete to this match. */ int i = tmp.length(); - for(std::string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); ++iter, ++i) + for (std::string::iterator iter = matches.front().begin() + tmp.length(); iter < matches.front().end(); + ++iter, ++i) { - for(std::string& match : matches) + for (std::string& match : matches) { - if(Misc::StringUtils::toLower(match[i]) != Misc::StringUtils::toLower(*iter)) + if (Misc::StringUtils::toLower(match[i]) != Misc::StringUtils::toLower(*iter)) { /* Append the longest match to the end of the output string*/ output.append(matches.front().substr(0, i)); @@ -517,7 +702,7 @@ namespace MWGui void Console::onResChange(int width, int height) { - setCoord(10,10, width-10, height/2); + setCoord(10, 10, width - 10, height / 2); } void Console::updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) @@ -531,11 +716,9 @@ namespace MWGui if (!object.isEmpty()) { if (object == mPtr) - { - setTitle("#{sConsoleTitle}"); - mPtr=MWWorld::Ptr(); - } + mPtr = MWWorld::Ptr(); else +<<<<<<< HEAD { /* Start of tes3mp change (major) @@ -549,16 +732,31 @@ namespace MWGui /* End of tes3mp change (major) */ +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mPtr = object; - } // User clicked on an object. Restore focus to the console command line. MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } else - { - setTitle("#{sConsoleTitle}"); mPtr = MWWorld::Ptr(); - } + updateConsoleTitle(); + } + + void Console::updateConsoleTitle() + { + std::string title = "#{OMWEngine:ConsoleWindow}"; + if (!mConsoleMode.empty()) + title = mConsoleMode + " " + title; + if (!mPtr.isEmpty()) + title.append(" (" + mPtr.getCellRef().getRefId().toDebugString() + ")"); + setTitle(title); + } + + void Console::setConsoleMode(std::string_view mode) + { + mConsoleMode = std::string(mode); + updateConsoleTitle(); } /* @@ -586,4 +784,44 @@ namespace MWGui ReferenceInterface::resetReference(); setSelectedObject(MWWorld::Ptr()); } + + void Console::initConsoleHistory() + { + const auto filePath = mCfgMgr.getUserConfigPath() / "console_history.txt"; + const size_t retrievalLimit = Settings::Manager::getSize("console history buffer size", "General"); + + // Read the previous session's commands from the file + if (retrievalLimit > 0) + { + std::ifstream historyFile(filePath); + std::string line; + while (std::getline(historyFile, line)) + { + // Truncate the list if it exceeds the retrieval limit + if (mCommandHistory.size() >= retrievalLimit) + mCommandHistory.pop_front(); + mCommandHistory.push_back(line); + } + historyFile.close(); + } + + mCurrent = mCommandHistory.end(); + try + { + mCommandHistoryFile.exceptions(std::fstream::failbit | std::fstream::badbit); + mCommandHistoryFile.open(filePath, std::ios_base::trunc); + + // Update the history file + for (const auto& histObj : mCommandHistory) + mCommandHistoryFile << histObj << std::endl; + mCommandHistoryFile.close(); + + mCommandHistoryFile.open(filePath, std::ios_base::app); + } + catch (const std::ios_base::failure& e) + { + Log(Debug::Error) << "Error: Failed to write to console history file " << filePath << " : " << e.what() + << " : " << std::generic_category().message(errno); + } + } } diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index a74906ddf..2844aaf3e 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -6,11 +6,13 @@ #include #include -#include #include +#include +#include + +#include "../mwbase/windowmanager.hpp" #include "../mwscript/compilercontext.hpp" -#include "../mwscript/interpretercontext.hpp" #include "referenceinterface.hpp" #include "windowbase.hpp" @@ -19,10 +21,12 @@ namespace MWGui { class Console : public WindowBase, private Compiler::ErrorHandler, public ReferenceInterface { - public: - /// Set the implicit object for script execution - void setSelectedObject(const MWWorld::Ptr& object); + public: + /// Set the implicit object for script execution + void setSelectedObject(const MWWorld::Ptr& object); + MWWorld::Ptr getSelectedObject() const { return mPtr; } +<<<<<<< HEAD /* Start of tes3mp addition @@ -37,73 +41,95 @@ namespace MWGui MyGUI::EditBox* mCommandLine; MyGUI::EditBox* mHistory; +======= + MyGUI::EditBox* mCommandLine; + MyGUI::EditBox* mHistory; + MyGUI::EditBox* mSearchTerm; + MyGUI::Button* mNextButton; + MyGUI::Button* mPreviousButton; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - typedef std::list StringList; + typedef std::list StringList; - // History of previous entered commands - StringList mCommandHistory; - StringList::iterator mCurrent; - std::string mEditString; + // History of previous entered commands + StringList mCommandHistory; + StringList::iterator mCurrent; + std::string mEditString; + std::ofstream mCommandHistoryFile; - Console(int w, int h, bool consoleOnlyScripts); + Console(int w, int h, bool consoleOnlyScripts, Files::ConfigurationManager& cfgMgr); + ~Console(); - void onOpen() override; + void onOpen() override; - void onResChange(int width, int height) override; + void onResChange(int width, int height) override; - // Print a message to the console, in specified color. - void print(const std::string &msg, const std::string& color = "#FFFFFF"); + // Print a message to the console, in specified color. + void print(const std::string& msg, std::string_view color = MWBase::WindowManager::sConsoleColor_Default); - // These are pre-colored versions that you should use. + // These are pre-colored versions that you should use. - /// Output from successful console command - void printOK(const std::string &msg); + /// Output from successful console command + void printOK(const std::string& msg); - /// Error message - void printError(const std::string &msg); + /// Error message + void printError(const std::string& msg); - void execute (const std::string& command); + void execute(const std::string& command); - void executeFile (const std::string& path); + void executeFile(const std::filesystem::path& path); - void updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr); + void updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr); - void clear() override; + void onFrame(float dt) override { checkReferenceAvailable(); } + void clear() override; - void resetReference () override; + void resetReference() override; - protected: + const std::string& getConsoleMode() const { return mConsoleMode; } + void setConsoleMode(std::string_view mode); - void onReferenceUnavailable() override; + protected: + void onReferenceUnavailable() override; - private: + private: + std::string mConsoleMode; - void keyPress(MyGUI::Widget* _sender, - MyGUI::KeyCode key, - MyGUI::Char _char); + void updateConsoleTitle(); - void acceptCommand(MyGUI::EditBox* _sender); + void commandBoxKeyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char); + void acceptCommand(MyGUI::EditBox* _sender); - std::string complete( std::string input, std::vector &matches ); + void acceptSearchTerm(MyGUI::EditBox* _sender); + void findNextOccurrence(MyGUI::Widget* _sender); + void findPreviousOccurence(MyGUI::Widget* _sender); + void markOccurrence(size_t textPosition, size_t length); + size_t mCurrentOccurrence = std::string::npos; + std::string mCurrentSearchTerm; - Compiler::Extensions mExtensions; - MWScript::CompilerContext mCompilerContext; - std::vector mNames; - bool mConsoleOnlyScripts; + std::string complete(std::string input, std::vector& matches); - bool compile (const std::string& cmd, Compiler::Output& output); + Compiler::Extensions mExtensions; + MWScript::CompilerContext mCompilerContext; + std::vector mNames; - /// Report error to the user. - void report (const std::string& message, const Compiler::TokenLoc& loc, Type type) override; + bool mConsoleOnlyScripts; + Files::ConfigurationManager& mCfgMgr; + bool compile(const std::string& cmd, Compiler::Output& output); - /// Report a file related error - void report (const std::string& message, Type type) override; + /// Report error to the user. + void report(const std::string& message, const Compiler::TokenLoc& loc, Type type) override; - /// Write all valid identifiers and keywords into mNames and sort them. - /// \note If mNames is not empty, this function is a no-op. - /// \note The list may contain duplicates (if a name is a keyword and an identifier at the same - /// time). - void listNames(); - }; + /// Report a file related error + void report(const std::string& message, Type type) override; + + /// Write all valid identifiers and keywords into mNames and sort them. + /// \note If mNames is not empty, this function is a no-op. + /// \note The list may contain duplicates (if a name is a keyword and an identifier at the same + /// time). + void listNames(); + + void initConsoleHistory(); + }; } #endif diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 25f8900f7..1a7548413 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -1,7 +1,7 @@ #include "container.hpp" -#include #include +#include /* Start of tes3mp addition @@ -19,10 +19,10 @@ */ #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -36,12 +36,12 @@ #include "countdialog.hpp" #include "inventorywindow.hpp" -#include "itemview.hpp" -#include "inventoryitemmodel.hpp" #include "containeritemmodel.hpp" -#include "sortfilteritemmodel.hpp" -#include "pickpocketitemmodel.hpp" #include "draganddrop.hpp" +#include "inventoryitemmodel.hpp" +#include "itemview.hpp" +#include "pickpocketitemmodel.hpp" +#include "sortfilteritemmodel.hpp" #include "tooltips.hpp" namespace MWGui @@ -53,6 +53,7 @@ namespace MWGui , mSortModel(nullptr) , mModel(nullptr) , mSelectedItem(-1) + , mTreatNextOpenAsLoot(false) { getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); getWidget(mTakeButton, "TakeButton"); @@ -62,11 +63,12 @@ namespace MWGui mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &ContainerWindow::onBackgroundSelected); mItemView->eventItemClicked += MyGUI::newDelegate(this, &ContainerWindow::onItemSelected); - mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked); + mDisposeCorpseButton->eventMouseButtonClick + += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked); mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked); - setCoord(200,0,600,300); + setCoord(200, 0, 600, 300); } void ContainerWindow::onItemSelected(int index) @@ -97,13 +99,14 @@ namespace MWGui if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - std::string name = object.getClass().getName(object) + MWGui::ToolTips::getSoulString(object.getCellRef()); + std::string name{ object.getClass().getName(object) }; + name += MWGui::ToolTips::getSoulString(object.getCellRef()); dialog->openCountDialog(name, "#{sTake}", count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem); } else - dragItem (nullptr, count); + dragItem(nullptr, count); } void ContainerWindow::dragItem(MyGUI::Widget* sender, int count) @@ -206,6 +209,7 @@ namespace MWGui void ContainerWindow::setPtr(const MWWorld::Ptr& container) { +<<<<<<< HEAD /* Start of tes3mp addition @@ -216,31 +220,37 @@ namespace MWGui End of tes3mp addition */ +======= + bool lootAnyway = mTreatNextOpenAsLoot; + mTreatNextOpenAsLoot = false; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mPtr = container; bool loot = mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead(); + std::unique_ptr model; if (mPtr.getClass().hasInventoryStore(mPtr)) { - if (mPtr.getClass().isNpc() && !loot) + if (mPtr.getClass().isNpc() && !loot && !lootAnyway) { // we are stealing stuff - mModel = new PickpocketItemModel(mPtr, new InventoryItemModel(container), - !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); + model = std::make_unique(mPtr, std::make_unique(container), + !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); } else - mModel = new InventoryItemModel(container); + model = std::make_unique(container); } else { - mModel = new ContainerItemModel(container); + model = std::make_unique(container); } mDisposeCorpseButton->setVisible(loot); + mModel = model.get(); + auto sortModel = std::make_unique(std::move(model)); + mSortModel = sortModel.get(); - mSortModel = new SortFilterItemModel(mModel); - - mItemView->setModel (mSortModel); + mItemView->setModel(std::move(sortModel)); mItemView->resetScrollBars(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); @@ -289,7 +299,7 @@ namespace MWGui void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender) { - if(mDragAndDrop != nullptr && mDragAndDrop->mIsOnDragAndDrop) + if (mDragAndDrop != nullptr && mDragAndDrop->mIsOnDragAndDrop) return; MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); @@ -348,25 +358,25 @@ namespace MWGui if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); - for (size_t i=0; igetItemCount(); ++i) + for (size_t i = 0; i < mModel->getItemCount(); ++i) { const ItemStack& item = mModel->getItem(i); if (invStore.isEquipped(item.mBase) == false) continue; - invStore.unequipItem(item.mBase, mPtr); + invStore.unequipItem(item.mBase); } } mModel->update(); - for (size_t i=0; igetItemCount(); ++i) + for (size_t i = 0; i < mModel->getItemCount(); ++i) { - if (i==0) + if (i == 0) { // play the sound of the first object MWWorld::Ptr item = mModel->getItem(i).mBase; - std::string sound = item.getClass().getUpSoundId(item); + const ESM::RefId& sound = item.getClass().getUpSoundId(item); MWBase::Environment::get().getWindowManager()->playSound(sound); } @@ -381,9 +391,9 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); } - void ContainerWindow::onDisposeCorpseButtonClicked(MyGUI::Widget *sender) + void ContainerWindow::onDisposeCorpseButtonClicked(MyGUI::Widget* sender) { - if(mDragAndDrop == nullptr || !mDragAndDrop->mIsOnDragAndDrop) + if (mDragAndDrop == nullptr || !mDragAndDrop->mIsOnDragAndDrop) { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); @@ -413,31 +423,33 @@ namespace MWGui creatureStats.setDeathAnimationFinished(true); MWBase::Environment::get().getMechanicsManager()->notifyDied(ptr); - const std::string script = ptr.getClass().getScript(ptr); + const ESM::RefId& script = ptr.getClass().getScript(ptr); if (!script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled()) { - MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); - MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + MWScript::InterpreterContext interpreterContext(&ptr.getRefData().getLocals(), ptr); + MWBase::Environment::get().getScriptManager()->run(script, interpreterContext); } // Clean up summoned creatures as well - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + auto& creatureMap = creatureStats.getSummonedCreatureMap(); for (const auto& creature : creatureMap) MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second); creatureMap.clear(); // Check if we are a summon and inform our master we've bit the dust - for(const auto& package : creatureStats.getAiSequence()) + for (const auto& package : creatureStats.getAiSequence()) { - if(package->followTargetThroughDoors() && !package->getTarget().isEmpty()) + if (package->followTargetThroughDoors() && !package->getTarget().isEmpty()) { const auto& summoner = package->getTarget(); auto& summons = summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap(); - auto it = std::find_if(summons.begin(), summons.end(), [&] (const auto& entry) { return entry.second == creatureStats.getActorId(); }); - if(it != summons.end()) + auto it = std::find_if(summons.begin(), summons.end(), + [&](const auto& entry) { return entry.second == creatureStats.getActorId(); }); + if (it != summons.end()) { - MWMechanics::purgeSummonEffect(summoner, *it); + auto summon = *it; summons.erase(it); + MWMechanics::purgeSummonEffect(summoner, summon); break; } } @@ -464,11 +476,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); } - bool ContainerWindow::onTakeItem(const ItemStack &item, int count) + bool ContainerWindow::onTakeItem(const ItemStack& item, int count) { return mModel->onTakeItem(item.mBase, count); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -513,4 +526,11 @@ namespace MWGui /* End of tes3mp addition */ +======= + void ContainerWindow::onDeleteCustomData(const MWWorld::Ptr& ptr) + { + if (mModel && mModel->usesContainer(ptr)) + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index d3947dbb1..7ce0be2ed 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -1,8 +1,8 @@ #ifndef MGUI_CONTAINER_H #define MGUI_CONTAINER_H -#include "windowbase.hpp" #include "referenceinterface.hpp" +#include "windowbase.hpp" #include "itemmodel.hpp" @@ -19,7 +19,6 @@ namespace MWGui class SortFilterItemModel; } - namespace MWGui { class ContainerWindow : public WindowBase, public ReferenceInterface @@ -35,6 +34,7 @@ namespace MWGui void resetReference() override; +<<<<<<< HEAD /* Start of tes3mp addition @@ -57,6 +57,11 @@ namespace MWGui /* End of tes3mp addition */ +======= + void onDeleteCustomData(const MWWorld::Ptr& ptr) override; + + void treatNextOpenAsLoot() { mTreatNextOpenAsLoot = true; } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 private: DragAndDrop* mDragAndDrop; @@ -65,7 +70,7 @@ namespace MWGui SortFilterItemModel* mSortModel; ItemModel* mModel; int mSelectedItem; - + bool mTreatNextOpenAsLoot; MyGUI::Button* mDisposeCorpseButton; MyGUI::Button* mTakeButton; MyGUI::Button* mCloseButton; diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index 9f288eec4..5787b5d87 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -2,6 +2,7 @@ #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -16,10 +17,13 @@ */ #include "../mwmechanics/creaturestats.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -29,15 +33,14 @@ namespace { - bool stacks (const MWWorld::Ptr& left, const MWWorld::Ptr& right) + bool stacks(const MWWorld::Ptr& left, const MWWorld::Ptr& right) { if (left == right) return true; // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure if (left.getContainerStore() && right.getContainerStore()) - return left.getContainerStore()->stacks(left, right) - && right.getContainerStore()->stacks(left, right); + return left.getContainerStore()->stacks(left, right) && right.getContainerStore()->stacks(left, right); if (left.getContainerStore()) return left.getContainerStore()->stacks(left, right); @@ -52,65 +55,42 @@ namespace namespace MWGui { -ContainerItemModel::ContainerItemModel(const std::vector& itemSources, const std::vector& worldItems) - : mWorldItems(worldItems) - , mTrading(true) -{ - assert (!itemSources.empty()); - // Tie resolution lifetimes to the ItemModel - mItemSources.reserve(itemSources.size()); - for(const MWWorld::Ptr& source : itemSources) + ContainerItemModel::ContainerItemModel( + const std::vector& itemSources, const std::vector& worldItems) + : mWorldItems(worldItems) + , mTrading(true) + { + assert(!itemSources.empty()); + // Tie resolution lifetimes to the ItemModel + mItemSources.reserve(itemSources.size()); + for (const MWWorld::Ptr& source : itemSources) + { + MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); + mItemSources.emplace_back(source, store.resolveTemporarily()); + } + } + + ContainerItemModel::ContainerItemModel(const MWWorld::Ptr& source) + : mTrading(false) { MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); mItemSources.emplace_back(source, store.resolveTemporarily()); } -} -ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) : mTrading(false) -{ - MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); - mItemSources.emplace_back(source, store.resolveTemporarily()); -} - -bool ContainerItemModel::allowedToUseItems() const -{ - if (mItemSources.empty()) - return true; - - MWWorld::Ptr ptr = MWMechanics::getPlayer(); - MWWorld::Ptr victim; - - // Check if the player is allowed to use items from opened container - MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); - return mm->isAllowedToUse(ptr, mItemSources[0].first, victim); -} - -ItemStack ContainerItemModel::getItem (ModelIndex index) -{ - if (index < 0) - throw std::runtime_error("Invalid index supplied"); - if (mItems.size() <= static_cast(index)) - throw std::runtime_error("Item index out of range"); - return mItems[index]; -} - -size_t ContainerItemModel::getItemCount() -{ - return mItems.size(); -} - -ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) -{ - size_t i = 0; - for (ItemStack& itemStack : mItems) + bool ContainerItemModel::allowedToUseItems() const { - if (itemStack == item) - return i; - ++i; - } - return -1; -} + if (mItemSources.empty()) + return true; + MWWorld::Ptr ptr = MWMechanics::getPlayer(); + MWWorld::Ptr victim; + + // Check if the player is allowed to use items from opened container + MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); + return mm->isAllowedToUse(ptr, mItemSources[0].first, victim); + } + +<<<<<<< HEAD MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { auto& source = mItemSources[0]; @@ -155,13 +135,54 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) int toRemove = count; for (auto& source : mItemSources) +======= + ItemStack ContainerItemModel::getItem(ModelIndex index) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); + if (index < 0) + throw std::runtime_error("Invalid index supplied"); + if (mItems.size() <= static_cast(index)) + throw std::runtime_error("Item index out of range"); + return mItems[index]; + } - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + size_t ContainerItemModel::getItemCount() + { + return mItems.size(); + } + + ItemModel::ModelIndex ContainerItemModel::getIndex(const ItemStack& item) + { + size_t i = 0; + for (ItemStack& itemStack : mItems) { - if (stacks(*it, item.mBase)) + if (itemStack == item) + return i; + ++i; + } + return -1; + } + + MWWorld::Ptr ContainerItemModel::copyItem(const ItemStack& item, size_t count, bool allowAutoEquip) + { + auto& source = mItemSources[0]; + MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); + if (item.mBase.getContainerStore() == &store) + throw std::runtime_error("Item to copy needs to be from a different container!"); + return *store.add(item.mBase, count, allowAutoEquip); + } + + void ContainerItemModel::removeItem(const ItemStack& item, size_t count) + { + int toRemove = count; + + for (auto& source : mItemSources) + { + MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -201,15 +222,45 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) End of tes3mp change (major) */ +======= + if (stacks(*it, item.mBase)) + { + int quantity = it->mRef->mData.getCount(false); + // If this is a restocking quantity, just don't remove it + if (quantity < 0 && mTrading) + toRemove += quantity; + else + toRemove -= store.remove(*it, toRemove); + if (toRemove <= 0) + return; + } + } + } + for (MWWorld::Ptr& source : mWorldItems) + { + if (stacks(source, item.mBase)) + { + int refCount = source.getRefData().getCount(); + if (refCount - toRemove <= 0) + MWBase::Environment::get().getWorld()->deleteObject(source); + else + source.getRefData().setCount(std::max(0, refCount - toRemove)); + toRemove -= refCount; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (toRemove <= 0) return; } } + + throw std::runtime_error("Not enough items to remove could be found"); } - for (MWWorld::Ptr& source : mWorldItems) + + void ContainerItemModel::update() { - if (stacks(source, item.mBase)) + mItems.clear(); + for (auto& source : mItemSources) { +<<<<<<< HEAD int refCount = source.getRefData().getCount(); if (refCount - toRemove <= 0) { @@ -235,31 +286,44 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) toRemove -= refCount; if (toRemove <= 0) return; +======= + MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + if (!(*it).getClass().showsInInventory(*it)) + continue; + + bool found = false; + for (ItemStack& itemStack : mItems) + { + if (stacks(*it, itemStack.mBase)) + { + // we already have an item stack of this kind, add to it + itemStack.mCount += it->getRefData().getCount(); + found = true; + break; + } + } + + if (!found) + { + // no stack yet, create one + ItemStack newItem(*it, this, it->getRefData().getCount()); + mItems.push_back(newItem); + } + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - } - - throw std::runtime_error("Not enough items to remove could be found"); -} - -void ContainerItemModel::update() -{ - mItems.clear(); - for (auto& source : mItemSources) - { - MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); - - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + for (MWWorld::Ptr& source : mWorldItems) { - if (!(*it).getClass().showsInInventory(*it)) - continue; - bool found = false; for (ItemStack& itemStack : mItems) { - if (stacks(*it, itemStack.mBase)) + if (stacks(source, itemStack.mBase)) { // we already have an item stack of this kind, add to it - itemStack.mCount += it->getRefData().getCount(); + itemStack.mCount += source.getRefData().getCount(); found = true; break; } @@ -268,78 +332,65 @@ void ContainerItemModel::update() if (!found) { // no stack yet, create one - ItemStack newItem (*it, this, it->getRefData().getCount()); + ItemStack newItem(source, this, source.getRefData().getCount()); mItems.push_back(newItem); } } } - for (MWWorld::Ptr& source : mWorldItems) + bool ContainerItemModel::onDropItem(const MWWorld::Ptr& item, int count) { - bool found = false; - for (ItemStack& itemStack : mItems) + if (mItemSources.empty()) + return false; + + MWWorld::Ptr target = mItemSources[0].first; + + if (target.getType() != ESM::Container::sRecordId) + return true; + + // check container organic flag + MWWorld::LiveCellRef* ref = target.get(); + if (ref->mBase->mFlags & ESM::Container::Organic) { - if (stacks(source, itemStack.mBase)) - { - // we already have an item stack of this kind, add to it - itemStack.mCount += source.getRefData().getCount(); - found = true; - break; - } + MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage2}"); + return false; } - if (!found) + // 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) { - // no stack yet, create one - ItemStack newItem (source, this, source.getRefData().getCount()); - mItems.push_back(newItem); + MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); + return false; } - } -} -bool ContainerItemModel::onDropItem(const MWWorld::Ptr &item, int count) -{ - if (mItemSources.empty()) - return false; - MWWorld::Ptr target = mItemSources[0].first; - - 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) + bool ContainerItemModel::onTakeItem(const MWWorld::Ptr& item, int count) { - MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); - return false; - } + if (mItemSources.empty()) + return false; - return true; -} + MWWorld::Ptr target = mItemSources[0].first; -bool ContainerItemModel::onTakeItem(const MWWorld::Ptr &item, int count) -{ - if (mItemSources.empty()) - return false; + // Looting a dead corpse is considered OK + if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) + return true; - MWWorld::Ptr target = mItemSources[0].first; + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, target, count); - // 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; -} + bool ContainerItemModel::usesContainer(const MWWorld::Ptr& container) + { + for (const auto& source : mItemSources) + { + if (source.first == container) + return true; + } + return false; + } } diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index c54f11314..1bdcec90f 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -16,26 +16,29 @@ namespace MWGui class ContainerItemModel : public ItemModel { public: - ContainerItemModel (const std::vector& itemSources, const std::vector& worldItems); - ///< @note The order of elements \a itemSources matters here. The first element has the highest priority for removal, + ContainerItemModel(const std::vector& itemSources, const std::vector& worldItems); + ///< @note The order of elements \a itemSources matters here. The first element has the highest priority for + ///< removal, /// while the last element will be used to add new items to. - ContainerItemModel (const MWWorld::Ptr& source); + ContainerItemModel(const MWWorld::Ptr& source); bool allowedToUseItems() const override; - bool onDropItem(const MWWorld::Ptr &item, int count) override; - bool onTakeItem(const MWWorld::Ptr &item, int count) override; + bool onDropItem(const MWWorld::Ptr& item, int count) override; + bool onTakeItem(const MWWorld::Ptr& item, int count) override; - ItemStack getItem (ModelIndex index) override; - ModelIndex getIndex (ItemStack item) override; + ItemStack getItem(ModelIndex index) override; + ModelIndex getIndex(const ItemStack& item) override; size_t getItemCount() override; - MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; - void removeItem (const ItemStack& item, size_t count) override; + MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; + void removeItem(const ItemStack& item, size_t count) override; void update() override; + bool usesContainer(const MWWorld::Ptr& container) override; + private: std::vector> mItemSources; std::vector mWorldItems; diff --git a/apps/openmw/mwgui/controllers.cpp b/apps/openmw/mwgui/controllers.cpp index f3932d3ce..f024def39 100644 --- a/apps/openmw/mwgui/controllers.cpp +++ b/apps/openmw/mwgui/controllers.cpp @@ -7,11 +7,9 @@ namespace MWGui { namespace Controllers { - void ControllerFollowMouse::prepareItem(MyGUI::Widget *_widget) - { - } + void ControllerFollowMouse::prepareItem(MyGUI::Widget* _widget) {} - bool ControllerFollowMouse::addTime(MyGUI::Widget *_widget, float _time) + bool ControllerFollowMouse::addTime(MyGUI::Widget* _widget, float _time) { _widget->setPosition(MyGUI::InputManager::getInstance().getMousePosition()); return true; diff --git a/apps/openmw/mwgui/controllers.hpp b/apps/openmw/mwgui/controllers.hpp index 416f104d9..a9e556518 100644 --- a/apps/openmw/mwgui/controllers.hpp +++ b/apps/openmw/mwgui/controllers.hpp @@ -1,8 +1,8 @@ #ifndef MWGUI_CONTROLLERS_H #define MWGUI_CONTROLLERS_H -#include #include +#include namespace MyGUI { @@ -10,16 +10,16 @@ namespace MyGUI } namespace MWGui::Controllers +{ + /// Automatically positions a widget below the mouse cursor. + class ControllerFollowMouse final : public MyGUI::ControllerItem { - /// Automatically positions a widget below the mouse cursor. - class ControllerFollowMouse final : public MyGUI::ControllerItem - { - MYGUI_RTTI_DERIVED( ControllerFollowMouse ) + MYGUI_RTTI_DERIVED(ControllerFollowMouse) - private: - bool addTime(MyGUI::Widget* _widget, float _time) override; - void prepareItem(MyGUI::Widget* _widget) override; - }; - } + private: + bool addTime(MyGUI::Widget* _widget, float _time) override; + void prepareItem(MyGUI::Widget* _widget) override; + }; +} #endif diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index baf3a43ab..2ca6657a1 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -1,8 +1,8 @@ #include "countdialog.hpp" #include -#include #include +#include #include @@ -11,8 +11,8 @@ namespace MWGui { - CountDialog::CountDialog() : - WindowModal("openmw_count_window.layout") + CountDialog::CountDialog() + : WindowModal("openmw_count_window.layout") { getWidget(mSlider, "CountSlider"); getWidget(mItemEdit, "ItemEdit"); @@ -40,16 +40,14 @@ namespace MWGui mSlider->setScrollRange(maxCount); mItemText->setCaption(item); - int width = std::max(mItemText->getTextSize().width + 128, 320); - setCoord(viewSize.width/2 - width/2, - viewSize.height/2 - mMainWidget->getHeight()/2, - width, - mMainWidget->getHeight()); + int width = std::max(mItemText->getTextSize().width + 160, 320); + setCoord(viewSize.width / 2 - width / 2, viewSize.height / 2 - mMainWidget->getHeight() / 2, width, + mMainWidget->getHeight()); // by default, the text edit field has the focus of the keyboard MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mItemEdit); - mSlider->setScrollPosition(maxCount-1); + mSlider->setScrollPosition(maxCount - 1); mItemEdit->setMinValue(1); mItemEdit->setMaxValue(maxCount); @@ -63,7 +61,7 @@ namespace MWGui void CountDialog::onOkButtonClicked(MyGUI::Widget* _sender) { - eventOkClicked(nullptr, mSlider->getScrollPosition()+1); + eventOkClicked(nullptr, mSlider->getScrollPosition() + 1); setVisible(false); } @@ -72,7 +70,7 @@ namespace MWGui // Enter key void CountDialog::onEnterKeyPressed(MyGUI::EditBox* _sender) { - eventOkClicked(nullptr, mSlider->getScrollPosition()+1); + eventOkClicked(nullptr, mSlider->getScrollPosition() + 1); setVisible(false); // To do not spam onEnterKeyPressed() again and again @@ -81,11 +79,11 @@ namespace MWGui void CountDialog::onEditValueChanged(int value) { - mSlider->setScrollPosition(value-1); + mSlider->setScrollPosition(value - 1); } void CountDialog::onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position) { - mItemEdit->setValue(_position+1); + mItemEdit->setValue(_position + 1); } } diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp index 766612f68..bbc6ed90f 100644 --- a/apps/openmw/mwgui/countdialog.hpp +++ b/apps/openmw/mwgui/countdialog.hpp @@ -12,30 +12,30 @@ namespace MWGui { class CountDialog : public WindowModal { - public: - CountDialog(); - void openCountDialog(const std::string& item, const std::string& message, const int maxCount); + public: + CountDialog(); + void openCountDialog(const std::string& item, const std::string& message, const int maxCount); - typedef MyGUI::delegates::CMultiDelegate2 EventHandle_WidgetInt; + typedef MyGUI::delegates::CMultiDelegate2 EventHandle_WidgetInt; - /** Event : Ok button was clicked.\n - signature : void method(MyGUI::Widget* _sender, int _count)\n - */ - EventHandle_WidgetInt eventOkClicked; + /** Event : Ok button was clicked.\n + signature : void method(MyGUI::Widget* _sender, int _count)\n + */ + EventHandle_WidgetInt eventOkClicked; - private: - MyGUI::ScrollBar* mSlider; - Gui::NumericEditBox* mItemEdit; - MyGUI::TextBox* mItemText; - MyGUI::TextBox* mLabelText; - MyGUI::Button* mOkButton; - MyGUI::Button* mCancelButton; + private: + MyGUI::ScrollBar* mSlider; + Gui::NumericEditBox* mItemEdit; + MyGUI::TextBox* mItemText; + MyGUI::TextBox* mLabelText; + MyGUI::Button* mOkButton; + MyGUI::Button* mCancelButton; - void onCancelButtonClicked(MyGUI::Widget* _sender); - void onOkButtonClicked(MyGUI::Widget* _sender); - void onEditValueChanged(int value); - void onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position); - void onEnterKeyPressed(MyGUI::EditBox* _sender); + void onCancelButtonClicked(MyGUI::Widget* _sender); + void onOkButtonClicked(MyGUI::Widget* _sender); + void onEditValueChanged(int value); + void onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position); + void onEnterKeyPressed(MyGUI::EditBox* _sender); }; } diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp index ed8a76eb8..7c95e2fd1 100644 --- a/apps/openmw/mwgui/cursor.cpp +++ b/apps/openmw/mwgui/cursor.cpp @@ -1,23 +1,20 @@ #include "cursor.hpp" -#include -#include -#include #include +#include +#include +#include namespace MWGui { - ResourceImageSetPointerFix::ResourceImageSetPointerFix() : mImageSet(nullptr) , mRotation(0) { } - ResourceImageSetPointerFix::~ResourceImageSetPointerFix() - { - } + ResourceImageSetPointerFix::~ResourceImageSetPointerFix() {} void ResourceImageSetPointerFix::deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) { @@ -56,7 +53,7 @@ namespace MWGui _image->setCoord(_point.left - mPoint.left, _point.top - mPoint.top, mSize.width, mSize.height); } - MyGUI::ResourceImageSetPtr ResourceImageSetPointerFix:: getImageSet() + MyGUI::ResourceImageSetPtr ResourceImageSetPointerFix::getImageSet() { return mImageSet; } diff --git a/apps/openmw/mwgui/cursor.hpp b/apps/openmw/mwgui/cursor.hpp index 7e1f9adba..b25db0ce0 100644 --- a/apps/openmw/mwgui/cursor.hpp +++ b/apps/openmw/mwgui/cursor.hpp @@ -9,12 +9,12 @@ namespace MWGui /// \brief Allows us to get the members of /// ResourceImageSetPointer that we need. - /// \example MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); + /// \example MyGUI::FactoryManager::getInstance().registerFactory("Resource", + /// "ResourceImageSetPointer"); /// MyGUI::ResourceManager::getInstance().load("core.xml"); - class ResourceImageSetPointerFix final : - public MyGUI::IPointer + class ResourceImageSetPointerFix final : public MyGUI::IPointer { - MYGUI_RTTI_DERIVED( ResourceImageSetPointerFix ) + MYGUI_RTTI_DERIVED(ResourceImageSetPointerFix) public: ResourceImageSetPointerFix(); @@ -25,8 +25,8 @@ namespace MWGui void setImage(MyGUI::ImageBox* _image) override; void setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point) override; - //and now for the whole point of this class, allow us to get - //the hot spot, the image and the size of the cursor. + // and now for the whole point of this class, allow us to get + // the hot spot, the image and the size of the cursor. MyGUI::ResourceImageSetPtr getImageSet(); MyGUI::IntPoint getHotSpot(); MyGUI::IntSize getSize(); diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index a29910f00..2a9ae2566 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -1,11 +1,17 @@ #include "debugwindow.hpp" +#include #include #include -#include -#include #include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" + +#include #ifndef BT_NO_PROFILE @@ -17,47 +23,56 @@ namespace if (pit->Is_Done()) return; - float accumulated_time=0,parent_time = pit->Is_Root() ? CProfileManager::Get_Time_Since_Reset() : pit->Get_Current_Parent_Total_Time(); - int i,j; + float accumulated_time = 0, + parent_time + = pit->Is_Root() ? CProfileManager::Get_Time_Since_Reset() : pit->Get_Current_Parent_Total_Time(); + int i, j; int frames_since_reset = CProfileManager::Get_Frame_Count_Since_Reset(); - for (i=0;iGet_Current_Parent_Name())+" (total running time: "+MyGUI::utility::toString(parent_time,3)+" ms) ---\n"; + for (i = 0; i < spacing; i++) + os << "."; + std::string s = "Profiling: " + std::string(pit->Get_Current_Parent_Name()) + + " (total running time: " + MyGUI::utility::toString(parent_time, 3) + " ms) ---\n"; os << s; - //float totalTime = 0.f; + // float totalTime = 0.f; int numChildren = 0; - for (i = 0; !pit->Is_Done(); i++,pit->Next()) + for (i = 0; !pit->Is_Done(); i++, pit->Next()) { numChildren++; float current_total_time = pit->Get_Current_Total_Time(); accumulated_time += current_total_time; float fraction = parent_time > SIMD_EPSILON ? (current_total_time / parent_time) * 100 : 0.f; - for (j=0;jGet_Current_Name()+" ("+MyGUI::utility::toString(fraction,2)+" %) :: "+MyGUI::utility::toString(ms,3)+" ms / frame ("+MyGUI::utility::toString(pit->Get_Current_Total_Calls())+" calls)\n"; + s = MyGUI::utility::toString(i) + " -- " + pit->Get_Current_Name() + " (" + + MyGUI::utility::toString(fraction, 2) + " %) :: " + MyGUI::utility::toString(ms, 3) + " ms / frame (" + + MyGUI::utility::toString(pit->Get_Current_Total_Calls()) + " calls)\n"; os << s; - //totalTime += current_total_time; - //recurse into children + // totalTime += current_total_time; + // recurse into children } if (parent_time < accumulated_time) { os << "what's wrong\n"; } - for (i=0;i SIMD_EPSILON ? ((parent_time - accumulated_time) / parent_time) * 100 : 0.f; - s = "Unaccounted: ("+MyGUI::utility::toString(unaccounted,3)+" %) :: "+MyGUI::utility::toString(parent_time - accumulated_time,3)+" ms\n"; + for (i = 0; i < spacing; i++) + os << "."; + double unaccounted = parent_time > SIMD_EPSILON ? ((parent_time - accumulated_time) / parent_time) * 100 : 0.f; + s = "Unaccounted: (" + MyGUI::utility::toString(unaccounted, 3) + + " %) :: " + MyGUI::utility::toString(parent_time - accumulated_time, 3) + " ms\n"; os << s; - for (i=0;iEnter_Child(i); - bulletDumpRecursive(pit, spacing+3, os); + bulletDumpRecursive(pit, spacing + 3, os); pit->Enter_Parent(); } } @@ -85,33 +100,134 @@ namespace MWGui // Ideas for other tabs: // - Texture / compositor texture viewer - // - Log viewer // - Material editor // - Shader editor + MyGUI::TabItem* itemLV = mTabControl->addItem("Log Viewer"); + itemLV->setCaptionWithReplacing(" #{OMWEngine:LogViewer} "); + mLogView + = itemLV->createWidgetReal("LogEdit", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Stretch); + mLogView->setEditReadOnly(true); + + MyGUI::TabItem* itemLuaProfiler = mTabControl->addItem("Lua Profiler"); + itemLuaProfiler->setCaptionWithReplacing(" #{OMWEngine:LuaProfiler} "); + mLuaProfiler = itemLuaProfiler->createWidgetReal( + "LogEdit", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Stretch); + mLuaProfiler->setEditReadOnly(true); + +#ifndef BT_NO_PROFILE MyGUI::TabItem* item = mTabControl->addItem("Physics Profiler"); - mBulletProfilerEdit = item->createWidgetReal - ("LogEdit", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Stretch); - - MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - mMainWidget->setSize(viewSize); - - + item->setCaptionWithReplacing(" #{OMWEngine:PhysicsProfiler} "); + mBulletProfilerEdit + = item->createWidgetReal("LogEdit", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Stretch); +#else + mBulletProfilerEdit = nullptr; +#endif } - void DebugWindow::onFrame(float dt) + static std::vector sLogCircularBuffer; + static std::mutex sBufferMutex; + static int64_t sLogStartIndex; + static int64_t sLogEndIndex; + + void DebugWindow::startLogRecording() + { + sLogCircularBuffer.resize(Settings::Manager::getSize("log buffer size", "General")); + Debug::setLogListener([](Debug::Level level, std::string_view prefix, std::string_view msg) { + if (sLogCircularBuffer.empty()) + return; // Log viewer is disabled. + std::string_view color; + switch (level) + { + case Debug::Error: + color = "#FF0000"; + break; + case Debug::Warning: + color = "#FFFF00"; + break; + case Debug::Info: + color = "#FFFFFF"; + break; + case Debug::Verbose: + case Debug::Debug: + color = "#666666"; + break; + default: + color = "#FFFFFF"; + } + bool bufferOverflow = false; + std::lock_guard lock(sBufferMutex); + const int64_t bufSize = sLogCircularBuffer.size(); + auto addChar = [&](char c) { + sLogCircularBuffer[sLogEndIndex++] = c; + if (sLogEndIndex == bufSize) + sLogEndIndex = 0; + bufferOverflow = bufferOverflow || sLogEndIndex == sLogStartIndex; + }; + auto addShieldedStr = [&](std::string_view s) { + for (char c : s) + { + addChar(c); + if (c == '#') + addChar(c); + } + }; + for (char c : color) + addChar(c); + addShieldedStr(prefix); + addShieldedStr(msg); + if (bufferOverflow) + sLogStartIndex = (sLogEndIndex + 1) % bufSize; + }); + } + + void DebugWindow::updateLogView() + { + std::lock_guard lock(sBufferMutex); + + if (!mLogView || sLogCircularBuffer.empty() || sLogStartIndex == sLogEndIndex) + return; + if (mLogView->isTextSelection()) + return; // Don't change text while player is trying to copy something + + std::string addition; + const int64_t bufSize = sLogCircularBuffer.size(); + { + if (sLogStartIndex < sLogEndIndex) + addition = std::string(sLogCircularBuffer.data() + sLogStartIndex, sLogEndIndex - sLogStartIndex); + else + { + addition = std::string(sLogCircularBuffer.data() + sLogStartIndex, bufSize - sLogStartIndex); + addition.append(sLogCircularBuffer.data(), sLogEndIndex); + } + sLogStartIndex = sLogEndIndex; + } + + size_t scrollPos = mLogView->getVScrollPosition(); + bool scrolledToTheEnd = scrollPos + 1 >= mLogView->getVScrollRange(); + int64_t newSizeEstimation = mLogView->getTextLength() + addition.size(); + if (newSizeEstimation > bufSize) + mLogView->eraseText(0, newSizeEstimation - bufSize); + mLogView->addText(addition); + if (scrolledToTheEnd && mLogView->getVScrollRange() > 0) + mLogView->setVScrollPosition(mLogView->getVScrollRange() - 1); + else + mLogView->setVScrollPosition(scrollPos); + } + + void DebugWindow::updateLuaProfile() + { + if (mLuaProfiler->isTextSelection()) + return; + + size_t previousPos = mLuaProfiler->getVScrollPosition(); + mLuaProfiler->setCaption(MWBase::Environment::get().getLuaManager()->formatResourceUsageStats()); + mLuaProfiler->setVScrollPosition(std::min(previousPos, mLuaProfiler->getVScrollRange() - 1)); + } + + void DebugWindow::updateBulletProfile() { #ifndef BT_NO_PROFILE - if (!isVisible()) - return; - - static float timer = 0; - timer -= dt; - - if (timer > 0) - return; - timer = 1; - std::stringstream stream; bulletDumpAll(stream); @@ -120,8 +236,30 @@ namespace MWGui size_t previousPos = mBulletProfilerEdit->getVScrollPosition(); mBulletProfilerEdit->setCaption(stream.str()); - mBulletProfilerEdit->setVScrollPosition(std::min(previousPos, mBulletProfilerEdit->getVScrollRange()-1)); + mBulletProfilerEdit->setVScrollPosition(std::min(previousPos, mBulletProfilerEdit->getVScrollRange() - 1)); #endif } + void DebugWindow::onFrame(float dt) + { + static float timer = 0; + timer -= dt; + if (timer > 0 || !isVisible()) + return; + timer = 0.25; + + switch (mTabControl->getIndexSelected()) + { + case 0: + updateLogView(); + break; + case 1: + updateLuaProfile(); + break; + case 2: + updateBulletProfile(); + break; + default:; + } + } } diff --git a/apps/openmw/mwgui/debugwindow.hpp b/apps/openmw/mwgui/debugwindow.hpp index 33647c078..7e65353c1 100644 --- a/apps/openmw/mwgui/debugwindow.hpp +++ b/apps/openmw/mwgui/debugwindow.hpp @@ -13,9 +13,16 @@ namespace MWGui void onFrame(float dt) override; - private: - MyGUI::TabControl* mTabControl; + static void startLogRecording(); + private: + void updateLogView(); + void updateLuaProfile(); + void updateBulletProfile(); + + MyGUI::TabControl* mTabControl; + MyGUI::EditBox* mLogView; + MyGUI::EditBox* mLuaProfiler; MyGUI::EditBox* mBulletProfilerEdit; }; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 283d9868b..94e65afc7 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -1,15 +1,18 @@ #include "dialogue.hpp" +#include #include -#include #include #include -#include +#include #include -#include +#include #include +#include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -27,14 +30,20 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "bookpage.hpp" #include "textcolours.hpp" @@ -43,35 +52,21 @@ namespace MWGui { - - class ResponseCallback : public MWBase::DialogueManager::ResponseCallback + void ResponseCallback::addResponse(std::string_view title, std::string_view text) { - public: - ResponseCallback(DialogueWindow* win, bool needMargin=true) - : mWindow(win) - , mNeedMargin(needMargin) - { + mWindow->addResponse(title, text, mNeedMargin); + } - } + void ResponseCallback::updateTopics() const + { + mWindow->updateTopics(); + } - void addResponse(const std::string& title, const std::string& text) override - { - mWindow->addResponse(title, text, mNeedMargin); - } - - void updateTopics() - { - mWindow->updateTopics(); - } - - private: - DialogueWindow* mWindow; - bool mNeedMargin; - }; - - PersuasionDialog::PersuasionDialog(ResponseCallback* callback) + PersuasionDialog::PersuasionDialog(std::unique_ptr callback) : WindowModal("openmw_persuasion_dialog.layout") - , mCallback(callback) + , mCallback(std::move(callback)) + , mInitialGoldLabelWidth(0) + , mInitialMainWidgetWidth(0) { getWidget(mCancelButton, "CancelButton"); getWidget(mAdmireButton, "AdmireButton"); @@ -81,6 +76,26 @@ namespace MWGui getWidget(mBribe100Button, "Bribe100Button"); getWidget(mBribe1000Button, "Bribe1000Button"); getWidget(mGoldLabel, "GoldLabel"); + getWidget(mActionsBox, "ActionsBox"); + + int totalHeight = 3; + adjustAction(mAdmireButton, totalHeight); + adjustAction(mIntimidateButton, totalHeight); + adjustAction(mTauntButton, totalHeight); + adjustAction(mBribe10Button, totalHeight); + adjustAction(mBribe100Button, totalHeight); + adjustAction(mBribe1000Button, totalHeight); + totalHeight += 3; + + int diff = totalHeight - mActionsBox->getSize().height; + if (diff > 0) + { + auto mainWidgetSize = mMainWidget->getSize(); + mMainWidget->setSize(mainWidgetSize.width, mainWidgetSize.height + diff); + } + + mInitialGoldLabelWidth = mActionsBox->getSize().width - mCancelButton->getSize().width - 8; + mInitialMainWidgetWidth = mMainWidget->getSize().width; mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onCancel); mAdmireButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade); @@ -91,17 +106,28 @@ namespace MWGui mBribe1000Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade); } - void PersuasionDialog::onCancel(MyGUI::Widget *sender) + void PersuasionDialog::adjustAction(MyGUI::Widget* action, int& totalHeight) + { + const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + auto currentCoords = action->getCoord(); + action->setCoord(currentCoords.left, totalHeight, currentCoords.width, lineHeight); + totalHeight += lineHeight; + } + + void PersuasionDialog::onCancel(MyGUI::Widget* sender) { setVisible(false); } - void PersuasionDialog::onPersuade(MyGUI::Widget *sender) + void PersuasionDialog::onPersuade(MyGUI::Widget* sender) { MWBase::MechanicsManager::PersuasionType type; - if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire; - else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate; - else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt; + if (sender == mAdmireButton) + type = MWBase::MechanicsManager::PT_Admire; + else if (sender == mIntimidateButton) + type = MWBase::MechanicsManager::PT_Intimidate; + else if (sender == mTauntButton) + type = MWBase::MechanicsManager::PT_Taunt; else if (sender == mBribe10Button) type = MWBase::MechanicsManager::PT_Bribe10; else if (sender == mBribe100Button) @@ -122,11 +148,18 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mBribe10Button->setEnabled (playerGold >= 10); - mBribe100Button->setEnabled (playerGold >= 100); - mBribe1000Button->setEnabled (playerGold >= 1000); + mBribe10Button->setEnabled(playerGold >= 10); + mBribe100Button->setEnabled(playerGold >= 100); + mBribe1000Button->setEnabled(playerGold >= 1000); mGoldLabel->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); + + int diff = mGoldLabel->getRequestedSize().width - mInitialGoldLabelWidth; + if (diff > 0) + mMainWidget->setSize(mInitialMainWidgetWidth + diff, mMainWidget->getSize().height); + else + mMainWidget->setSize(mInitialMainWidgetWidth, mMainWidget->getSize().height); + WindowModal::onOpen(); } @@ -137,21 +170,24 @@ namespace MWGui // -------------------------------------------------------------------------------------------------- - Response::Response(const std::string &text, const std::string &title, bool needMargin) - : mTitle(title), mNeedMargin(needMargin) + Response::Response(std::string_view text, std::string_view title, bool needMargin) + : mTitle(title) + , mNeedMargin(needMargin) { mText = text; } - void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const + void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, + std::map>& topicLinks) const { typesetter->sectionBreak(mNeedMargin ? 9 : 0); + auto windowManager = MWBase::Environment::get().getWindowManager(); - if (mTitle != "") + if (!mTitle.empty()) { - const MyGUI::Colour& headerColour = MWBase::Environment::get().getWindowManager()->getTextColours().header; - BookTypesetter::Style* title = typesetter->createStyle("", headerColour, false); - typesetter->write(title, to_utf8_span(mTitle.c_str())); + const MyGUI::Colour& headerColour = windowManager->getTextColours().header; + BookTypesetter::Style* title = typesetter->createStyle({}, headerColour, false); + typesetter->write(title, to_utf8_span(mTitle)); typesetter->sectionBreak(); } @@ -162,7 +198,7 @@ namespace MWGui std::string text = mText; size_t pos_end = std::string::npos; - for(;;) + for (;;) { size_t pos_begin = text.find('@'); if (pos_begin != std::string::npos) @@ -173,36 +209,37 @@ namespace MWGui std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1); const char specialPseudoAsteriskCharacter = 127; std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*'); - std::string topicName = MWBase::Environment::get().getWindowManager()-> - getTranslationDataStorage().topicStandardForm(link); + std::string topicName + = Misc::StringUtils::lowerCase(windowManager->getTranslationDataStorage().topicStandardForm(link)); std::string displayName = link; - while (displayName[displayName.size()-1] == '*') - displayName.erase(displayName.size()-1, 1); + while (displayName[displayName.size() - 1] == '*') + displayName.erase(displayName.size() - 1, 1); - text.replace(pos_begin, pos_end+1-pos_begin, displayName); + text.replace(pos_begin, pos_end + 1 - pos_begin, displayName); - if (topicLinks.find(Misc::StringUtils::lowerCase(topicName)) != topicLinks.end()) - hyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = intptr_t(topicLinks[Misc::StringUtils::lowerCase(topicName)]); + if (topicLinks.find(topicName) != topicLinks.end()) + hyperLinks[std::make_pair(pos_begin, pos_begin + displayName.size())] + = intptr_t(topicLinks[topicName].get()); } else break; } - typesetter->addContent(to_utf8_span(text.c_str())); + typesetter->addContent(to_utf8_span(text)); - if (hyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) + if (hyperLinks.size() + && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) { const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); - BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false); + BookTypesetter::Style* style = typesetter->createStyle({}, textColours.normal, false); size_t formatted = 0; // points to the first character that is not laid out yet for (auto& hyperLink : hyperLinks) { intptr_t topicId = hyperLink.second; - BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, textColours.link, - textColours.linkOver, textColours.linkPressed, - topicId); + BookTypesetter::Style* hotStyle = typesetter->createHotStyle( + style, textColours.link, textColours.linkOver, textColours.linkPressed, topicId); if (formatted < hyperLink.first.first) typesetter->write(style, formatted, hyperLink.first.first); typesetter->write(hotStyle, hyperLink.first.first, hyperLink.first.second); @@ -216,18 +253,18 @@ namespace MWGui std::vector matches; keywordSearch->highlightKeywords(text.begin(), text.end(), matches); - std::string::const_iterator i = text.begin (); + std::string::const_iterator i = text.begin(); for (KeywordSearchT::Match& match : matches) { if (i != match.mBeg) - addTopicLink (typesetter, 0, i - text.begin (), match.mBeg - text.begin ()); + addTopicLink(typesetter, 0, i - text.begin(), match.mBeg - text.begin()); - addTopicLink (typesetter, match.mValue, match.mBeg - text.begin (), match.mEnd - text.begin ()); + addTopicLink(typesetter, match.mValue, match.mBeg - text.begin(), match.mEnd - text.begin()); i = match.mEnd; } - if (i != text.end ()) - addTopicLink (typesetter, 0, i - text.begin (), text.size ()); + if (i != text.end()) + addTopicLink(typesetter, 0, i - text.begin(), text.size()); } } @@ -235,44 +272,45 @@ namespace MWGui { const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); - BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false); - + BookTypesetter::Style* style = typesetter->createStyle({}, textColours.normal, false); if (topicId) - style = typesetter->createHotStyle (style, textColours.link, textColours.linkOver, textColours.linkPressed, topicId); - typesetter->write (style, begin, end); + style = typesetter->createHotStyle( + style, textColours.link, textColours.linkOver, textColours.linkPressed, topicId); + typesetter->write(style, begin, end); } - Message::Message(const std::string& text) + Message::Message(std::string_view text) { mText = text; } - void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const + void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, + std::map>& topicLinks) const { const MyGUI::Colour& textColour = MWBase::Environment::get().getWindowManager()->getTextColours().notify; - BookTypesetter::Style* title = typesetter->createStyle("", textColour, false); + BookTypesetter::Style* title = typesetter->createStyle({}, textColour, false); typesetter->sectionBreak(9); - typesetter->write(title, to_utf8_span(mText.c_str())); + typesetter->write(title, to_utf8_span(mText)); } // -------------------------------------------------------------------------------------------------- void Choice::activated() { - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click")); eventChoiceActivated(mChoiceId); } void Topic::activated() { - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click")); eventTopicActivated(mTopicId); } void Goodbye::activated() { - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click")); eventActivated(); } @@ -282,19 +320,19 @@ namespace MWGui : WindowBase("openmw_dialogue_window.layout") , mIsCompanion(false) , mGoodbye(false) - , mPersuasionDialog(new ResponseCallback(this)) - , mCallback(new ResponseCallback(this)) - , mGreetingCallback(new ResponseCallback(this, false)) + , mPersuasionDialog(std::make_unique(this)) + , mCallback(std::make_unique(this)) + , mGreetingCallback(std::make_unique(this, false)) { // Centre dialog center(); mPersuasionDialog.setVisible(false); - //History view + // History view getWidget(mHistory, "History"); - //Topics list + // Topics list getWidget(mTopicsList, "TopicsList"); mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectListItem); @@ -302,32 +340,23 @@ namespace MWGui mGoodbyeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked); getWidget(mDispositionBar, "Disposition"); - getWidget(mDispositionText,"DispositionText"); + getWidget(mDispositionText, "DispositionText"); getWidget(mScrollBar, "VScroll"); mScrollBar->eventScrollChangePosition += MyGUI::newDelegate(this, &DialogueWindow::onScrollbarMoved); mHistory->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel); - BookPage::ClickCallback callback = std::bind (&DialogueWindow::notifyLinkClicked, this, std::placeholders::_1); + BookPage::ClickCallback callback = [this](TypesetBook::InteractiveId link) { notifyLinkClicked(link); }; mHistory->adviseLinkClicked(callback); - mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); - } - - DialogueWindow::~DialogueWindow() - { - deleteLater(); - for (Link* link : mLinks) - delete link; - for (const auto& link : mTopicLinks) - delete link.second; - for (auto history : mHistoryContents) - delete history; + mMainWidget->castType()->eventWindowChangeCoord + += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); } void DialogueWindow::onTradeComplete() { - addResponse("", MyGUI::LanguageManager::getInstance().replaceTags("#{sBarterDialog5}")); + MyGUI::UString message = MyGUI::LanguageManager::getInstance().replaceTags("#{sBarterDialog5}"); + addResponse({}, message.asUTF8()); } bool DialogueWindow::exit() @@ -348,7 +377,8 @@ namespace MWGui void DialogueWindow::onWindowResize(MyGUI::Window* _sender) { // if the window has only been moved, not resized, we don't need to update - if (mCurrentWindowSize == _sender->getSize()) return; + if (mCurrentWindowSize == _sender->getSize()) + return; mTopicsList->adjustSize(); updateHistory(); @@ -360,8 +390,8 @@ namespace MWGui { if (!mScrollBar->getVisible()) return; - mScrollBar->setScrollPosition(std::min(static_cast(mScrollBar->getScrollRange()-1), - std::max(0, static_cast(mScrollBar->getScrollPosition() - _rel*0.3)))); + mScrollBar->setScrollPosition( + std::clamp(mScrollBar->getScrollPosition() - _rel * 0.3, 0, mScrollBar->getScrollRange() - 1)); onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition()); } @@ -392,21 +422,22 @@ namespace MWGui if (mGoodbye || dialogueManager->isInChoice()) return; - const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); - const std::string sPersuasion = gmst.find("sPersuasion")->mValue.getString(); - const std::string sCompanionShare = gmst.find("sCompanionShare")->mValue.getString(); - const std::string sBarter = gmst.find("sBarter")->mValue.getString(); - const std::string sSpells = gmst.find("sSpells")->mValue.getString(); - const std::string sTravel = gmst.find("sTravel")->mValue.getString(); - const std::string sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->mValue.getString(); - const std::string sEnchanting = gmst.find("sEnchanting")->mValue.getString(); - const std::string sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->mValue.getString(); - const std::string sRepair = gmst.find("sRepair")->mValue.getString(); + const std::string& sPersuasion = gmst.find("sPersuasion")->mValue.getString(); + const std::string& sCompanionShare = gmst.find("sCompanionShare")->mValue.getString(); + const std::string& sBarter = gmst.find("sBarter")->mValue.getString(); + const std::string& sSpells = gmst.find("sSpells")->mValue.getString(); + const std::string& sTravel = gmst.find("sTravel")->mValue.getString(); + const std::string& sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->mValue.getString(); + const std::string& sEnchanting = gmst.find("sEnchanting")->mValue.getString(); + const std::string& sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->mValue.getString(); + const std::string& sRepair = gmst.find("sRepair")->mValue.getString(); - if (topic != sPersuasion && topic != sCompanionShare && topic != sBarter - && topic != sSpells && topic != sTravel && topic != sSpellMakingMenuTitle - && topic != sEnchanting && topic != sServiceTrainingTitle && topic != sRepair) + if (topic != sPersuasion && topic != sCompanionShare && topic != sBarter && topic != sSpells && topic != sTravel + && topic != sSpellMakingMenuTitle && topic != sEnchanting && topic != sServiceTrainingTitle + && topic != sRepair) { onTopicActivated(topic); if (mGoodbyeButton->getEnabled()) @@ -418,19 +449,26 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr); else if (!dialogueManager->checkServiceRefused(mCallback.get())) { - if (topic == sBarter && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Barter)) + if (topic == sBarter + && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Barter)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr); - else if (topic == sSpells && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spells)) + else if (topic == sSpells + && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spells)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr); - else if (topic == sTravel && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Travel)) + else if (topic == sTravel + && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Travel)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr); - else if (topic == sSpellMakingMenuTitle && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spellmaking)) + else if (topic == sSpellMakingMenuTitle + && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Spellmaking)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr); - else if (topic == sEnchanting && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Enchanting)) + else if (topic == sEnchanting + && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Enchanting)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr); - else if (topic == sServiceTrainingTitle && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Training)) + else if (topic == sServiceTrainingTitle + && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Training)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr); - else if (topic == sRepair && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Repair)) + else if (topic == sRepair + && !dialogueManager->checkServiceRefused(mCallback.get(), MWBase::DialogueManager::Repair)) MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr); } else @@ -522,8 +560,9 @@ namespace MWGui // The history is not reset here mKeywords.clear(); mTopicsList->clear(); - for (Link* link : mLinks) - mDeleteLater.push_back(link); // Links are not deleted right away to prevent issues with event handlers + for (auto& link : mLinks) + mDeleteLater.push_back( + std::move(link)); // Links are not deleted right away to prevent issues with event handlers mLinks.clear(); } @@ -555,8 +594,12 @@ namespace MWGui void DialogueWindow::restock() { - MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); - float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->mValue.getFloat(); + MWMechanics::CreatureStats& sellerStats = mPtr.getClass().getCreatureStats(mPtr); + float delay = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fBarterGoldResetDelay") + ->mValue.getFloat(); // Gold is restocked every 24h if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) @@ -585,8 +628,6 @@ namespace MWGui void DialogueWindow::deleteLater() { - for (Link* link : mDeleteLater) - delete link; mDeleteLater.clear(); } @@ -595,12 +636,10 @@ namespace MWGui if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Dialogue)) return; // Reset history - for (DialogueText* text : mHistoryContents) - delete text; mHistoryContents.clear(); } - bool DialogueWindow::setKeywords(std::list keyWords) + bool DialogueWindow::setKeywords(const std::list& keyWords) { if (mKeywords == keyWords && isCompanion() == mIsCompanion) return false; @@ -614,19 +653,20 @@ namespace MWGui { mTopicsList->clear(); for (auto& linkPair : mTopicLinks) - mDeleteLater.push_back(linkPair.second); + mDeleteLater.push_back(std::move(linkPair.second)); mTopicLinks.clear(); mKeywordSearch.clear(); int services = mPtr.getClass().getServices(mPtr); - bool travel = (mPtr.getTypeName() == typeid(ESM::NPC).name() && !mPtr.get()->mBase->getTransport().empty()) - || (mPtr.getTypeName() == typeid(ESM::Creature).name() && !mPtr.get()->mBase->getTransport().empty()); + bool travel = (mPtr.getType() == ESM::NPC::sRecordId && !mPtr.get()->mBase->getTransport().empty()) + || (mPtr.getType() == ESM::Creature::sRecordId + && !mPtr.get()->mBase->getTransport().empty()); - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + if (mPtr.getType() == ESM::NPC::sRecordId) mTopicsList->addItem(gmst.find("sPersuasion")->mValue.getString()); if (services & ESM::NPC::AllItems) @@ -656,12 +696,12 @@ namespace MWGui if (mTopicsList->getItemCount() > 0) mTopicsList->addSeparator(); - - for(const auto& keyword : mKeywords) + for (const auto& keyword : mKeywords) { std::string topicId = Misc::StringUtils::lowerCase(keyword); mTopicsList->addItem(keyword); +<<<<<<< HEAD Topic* t = new Topic(keyword); /* Start of tes3mp change (major) @@ -678,6 +718,12 @@ namespace MWGui mTopicLinks[topicId] = t; mKeywordSearch.seed(topicId, intptr_t(t)); +======= + auto t = std::make_unique(keyword); + mKeywordSearch.seed(topicId, intptr_t(t.get())); + t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); + mTopicLinks[topicId] = std::move(t); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } mTopicsList->adjustSize(); @@ -690,21 +736,21 @@ namespace MWGui { if (!scrollbar && mScrollBar->getVisible()) { - mHistory->setSize(mHistory->getSize()+MyGUI::IntSize(mScrollBar->getWidth(),0)); + mHistory->setSize(mHistory->getSize() + MyGUI::IntSize(mScrollBar->getWidth(), 0)); mScrollBar->setVisible(false); } if (scrollbar && !mScrollBar->getVisible()) { - mHistory->setSize(mHistory->getSize()-MyGUI::IntSize(mScrollBar->getWidth(),0)); + mHistory->setSize(mHistory->getSize() - MyGUI::IntSize(mScrollBar->getWidth(), 0)); mScrollBar->setVisible(true); } - BookTypesetter::Ptr typesetter = BookTypesetter::create (mHistory->getWidth(), std::numeric_limits::max()); + BookTypesetter::Ptr typesetter = BookTypesetter::create(mHistory->getWidth(), std::numeric_limits::max()); - for (DialogueText* text : mHistoryContents) + for (const auto& text : mHistoryContents) text->write(typesetter, &mKeywordSearch, mTopicLinks); - BookTypesetter::Style* body = typesetter->createStyle("", MyGUI::Colour::White, false); + BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::White, false); typesetter->sectionBreak(9); // choices @@ -712,29 +758,33 @@ namespace MWGui mChoices = MWBase::Environment::get().getDialogueManager()->getChoices(); for (std::pair& choice : mChoices) { - Choice* link = new Choice(choice.second); + auto link = std::make_unique(choice.second); link->eventChoiceActivated += MyGUI::newDelegate(this, &DialogueWindow::onChoiceActivated); - mLinks.push_back(link); + auto interactiveId = TypesetBook::InteractiveId(link.get()); + mLinks.push_back(std::move(link)); typesetter->lineBreak(); - BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver, - textColours.answerPressed, - TypesetBook::InteractiveId(link)); - typesetter->write(questionStyle, to_utf8_span(choice.first.c_str())); + BookTypesetter::Style* questionStyle = typesetter->createHotStyle( + body, textColours.answer, textColours.answerOver, textColours.answerPressed, interactiveId); + typesetter->write(questionStyle, to_utf8_span(choice.first)); } mGoodbye = MWBase::Environment::get().getDialogueManager()->isGoodbye(); if (mGoodbye) { - Goodbye* link = new Goodbye(); + auto link = std::make_unique(); link->eventActivated += MyGUI::newDelegate(this, &DialogueWindow::onGoodbyeActivated); - mLinks.push_back(link); - std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->mValue.getString(); - BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver, - textColours.answerPressed, - TypesetBook::InteractiveId(link)); + auto interactiveId = TypesetBook::InteractiveId(link.get()); + mLinks.push_back(std::move(link)); + const std::string& goodbye = MWBase::Environment::get() + .getESMStore() + ->get() + .find("sGoodbye") + ->mValue.getString(); + BookTypesetter::Style* questionStyle = typesetter->createHotStyle( + body, textColours.answer, textColours.answerOver, textColours.answerPressed, interactiveId); typesetter->lineBreak(); - typesetter->write(questionStyle, to_utf8_span(goodbye.c_str())); + typesetter->write(questionStyle, to_utf8_span(goodbye)); } TypesetBook::Ptr book = typesetter->complete(); @@ -747,9 +797,10 @@ namespace MWGui mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second)); size_t range = book->getSize().second - viewHeight; mScrollBar->setScrollRange(range); - mScrollBar->setScrollPosition(range-1); - mScrollBar->setTrackSize(static_cast(viewHeight / static_cast(book->getSize().second) * mScrollBar->getLineSize())); - onScrollbarMoved(mScrollBar, range-1); + mScrollBar->setScrollPosition(range - 1); + mScrollBar->setTrackSize( + static_cast(viewHeight / static_cast(book->getSize().second) * mScrollBar->getLineSize())); + onScrollbarMoved(mScrollBar, range - 1); } else { @@ -767,12 +818,12 @@ namespace MWGui mTopicsList->setEnabled(topicsEnabled); } - void DialogueWindow::notifyLinkClicked (TypesetBook::InteractiveId link) + void DialogueWindow::notifyLinkClicked(TypesetBook::InteractiveId link) { reinterpret_cast(link)->activated(); } - void DialogueWindow::onTopicActivated(const std::string &topicId) + void DialogueWindow::onTopicActivated(const std::string& topicId) { if (mGoodbye) return; @@ -799,20 +850,20 @@ namespace MWGui resetReference(); } - void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos) + void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar* sender, size_t pos) { mHistory->setPosition(0, static_cast(pos) * -1); } - void DialogueWindow::addResponse(const std::string &title, const std::string &text, bool needMargin) + void DialogueWindow::addResponse(std::string_view title, std::string_view text, bool needMargin) { - mHistoryContents.push_back(new Response(text, title, needMargin)); + mHistoryContents.push_back(std::make_unique(text, title, needMargin)); updateHistory(); } - void DialogueWindow::addMessageBox(const std::string& text) + void DialogueWindow::addMessageBox(std::string_view text) { - mHistoryContents.push_back(new Message(text)); + mHistoryContents.push_back(std::make_unique(text)); updateHistory(); } @@ -823,8 +874,11 @@ namespace MWGui { dispositionVisible = true; mDispositionBar->setProgressRange(100); - mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)); - mDispositionText->setCaption(MyGUI::utility::toString(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")); + mDispositionBar->setProgressPosition( + MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)); + mDispositionText->setCaption( + MyGUI::utility::toString(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)) + + std::string("/100")); } bool dispositionWasVisible = mDispositionBar->getVisible(); @@ -832,15 +886,15 @@ namespace MWGui if (dispositionVisible && !dispositionWasVisible) { mDispositionBar->setVisible(true); - int offset = mDispositionBar->getHeight()+5; - mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0,offset,0,-offset)); + int offset = mDispositionBar->getHeight() + 5; + mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0, offset, 0, -offset)); mTopicsList->adjustSize(); } else if (!dispositionVisible && dispositionWasVisible) { mDispositionBar->setVisible(false); - int offset = mDispositionBar->getHeight()+5; - mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0,offset,0,-offset)); + int offset = mDispositionBar->getHeight() + 5; + mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0, offset, 0, -offset)); mTopicsList->adjustSize(); } } @@ -860,7 +914,7 @@ namespace MWGui deleteLater(); if (mChoices != MWBase::Environment::get().getDialogueManager()->getChoices() - || mGoodbye != MWBase::Environment::get().getDialogueManager()->isGoodbye()) + || mGoodbye != MWBase::Environment::get().getDialogueManager()->isGoodbye()) updateHistory(); } @@ -869,12 +923,12 @@ namespace MWGui if (!Settings::Manager::getBool("color topic enable", "GUI")) return; - std::string specialColour = Settings::Manager::getString("color topic specific", "GUI"); - std::string oldColour = Settings::Manager::getString("color topic exhausted", "GUI"); + const std::string& specialColour = Settings::Manager::getString("color topic specific", "GUI"); + const std::string& oldColour = Settings::Manager::getString("color topic exhausted", "GUI"); for (const std::string& keyword : mKeywords) { - int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(keyword); + int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(ESM::RefId::stringRefId(keyword)); MyGUI::Button* button = mTopicsList->getItemWidget(keyword); if (!specialColour.empty() && flag & MWBase::DialogueManager::TopicType::Specific) @@ -902,7 +956,7 @@ namespace MWGui return false; return !actor.getClass().getScript(actor).empty() - && actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"); + && actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"); } } diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index e209ced77..3c60becaf 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -1,28 +1,49 @@ #ifndef MWGUI_DIALOGE_H #define MWGUI_DIALOGE_H -#include "windowbase.hpp" +#include + #include "referenceinterface.hpp" +#include "windowbase.hpp" #include "bookpage.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwdialogue/keywordsearch.hpp" #include namespace Gui { + class AutoSizedTextBox; class MWList; } namespace MWGui { - class ResponseCallback; + class DialogueWindow; + + class ResponseCallback : public MWBase::DialogueManager::ResponseCallback + { + DialogueWindow* mWindow; + bool mNeedMargin; + + public: + ResponseCallback(DialogueWindow* win, bool needMargin = true) + : mWindow(win) + , mNeedMargin(needMargin) + { + } + + void addResponse(std::string_view title, std::string_view text) override; + + void updateTopics() const; + }; class PersuasionDialog : public WindowModal { public: - PersuasionDialog(ResponseCallback* callback); + PersuasionDialog(std::unique_ptr callback); void onOpen() override; @@ -31,6 +52,9 @@ namespace MWGui private: std::unique_ptr mCallback; + int mInitialGoldLabelWidth; + int mInitialMainWidgetWidth; + MyGUI::Button* mCancelButton; MyGUI::Button* mAdmireButton; MyGUI::Button* mIntimidateButton; @@ -38,73 +62,83 @@ namespace MWGui MyGUI::Button* mBribe10Button; MyGUI::Button* mBribe100Button; MyGUI::Button* mBribe1000Button; - MyGUI::TextBox* mGoldLabel; + MyGUI::Widget* mActionsBox; + Gui::AutoSizedTextBox* mGoldLabel; - void onCancel (MyGUI::Widget* sender); - void onPersuade (MyGUI::Widget* sender); + void adjustAction(MyGUI::Widget* action, int& totalHeight); + + void onCancel(MyGUI::Widget* sender); + void onPersuade(MyGUI::Widget* sender); }; - struct Link { virtual ~Link() {} - virtual void activated () = 0; + virtual void activated() = 0; }; struct Topic : Link { typedef MyGUI::delegates::CMultiDelegate1 EventHandle_TopicId; EventHandle_TopicId eventTopicActivated; - Topic(const std::string& id) : mTopicId(id) {} + Topic(const std::string& id) + : mTopicId(id) + { + } std::string mTopicId; - void activated () override; + void activated() override; }; struct Choice : Link { typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ChoiceId; EventHandle_ChoiceId eventChoiceActivated; - Choice(int id) : mChoiceId(id) {} + Choice(int id) + : mChoiceId(id) + { + } int mChoiceId; - void activated () override; + void activated() override; }; struct Goodbye : Link { typedef MyGUI::delegates::CMultiDelegate0 Event_Activated; Event_Activated eventActivated; - void activated () override; + void activated() override; }; - typedef MWDialogue::KeywordSearch KeywordSearchT; + typedef MWDialogue::KeywordSearch KeywordSearchT; struct DialogueText { - virtual ~DialogueText() {} - virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const = 0; + virtual ~DialogueText() = default; + virtual void write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, + std::map>& topicLinks) const = 0; std::string mText; }; struct Response : DialogueText { - Response(const std::string& text, const std::string& title = "", bool needMargin = true); - void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const override; - void addTopicLink (BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const; + Response(std::string_view text, std::string_view title = {}, bool needMargin = true); + void write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, + std::map>& topicLinks) const override; + void addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const; std::string mTitle; bool mNeedMargin; }; struct Message : DialogueText { - Message(const std::string& text); - void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const override; + Message(std::string_view text); + void write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, + std::map>& topicLinks) const override; }; - class DialogueWindow: public WindowBase, public ReferenceInterface + class DialogueWindow : public WindowBase, public ReferenceInterface { public: DialogueWindow(); - ~DialogueWindow(); void onTradeComplete(); @@ -113,7 +147,7 @@ namespace MWGui // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; - void notifyLinkClicked (TypesetBook::InteractiveId link); + void notifyLinkClicked(TypesetBook::InteractiveId link); /* Start of tes3mp addition @@ -138,11 +172,11 @@ namespace MWGui void setPtr(const MWWorld::Ptr& actor) override; /// @return true if stale keywords were updated successfully - bool setKeywords(std::list keyWord); + bool setKeywords(const std::list& keyWord); - void addResponse (const std::string& title, const std::string& text, bool needMargin = true); + void addResponse(std::string_view title, std::string_view text, bool needMargin = true); - void addMessageBox(const std::string& text); + void addMessageBox(std::string_view text); void onFrame(float dt) override; void clear() override { resetReference(); } @@ -176,9 +210,9 @@ namespace MWGui void onChoiceActivated(int id); void onGoodbyeActivated(); - void onScrollbarMoved (MyGUI::ScrollBar* sender, size_t pos); + void onScrollbarMoved(MyGUI::ScrollBar* sender, size_t pos); - void updateHistory(bool scrollbar=false); + void updateHistory(bool scrollbar = false); void onReferenceUnavailable() override; @@ -190,22 +224,22 @@ namespace MWGui bool mIsCompanion; std::list mKeywords; - std::vector mHistoryContents; - std::vector > mChoices; + std::vector> mHistoryContents; + std::vector> mChoices; bool mGoodbye; - std::vector mLinks; - std::map mTopicLinks; + std::vector> mLinks; + std::map> mTopicLinks; - std::vector mDeleteLater; + std::vector> mDeleteLater; KeywordSearchT mKeywordSearch; BookPage* mHistory; - Gui::MWList* mTopicsList; + Gui::MWList* mTopicsList; MyGUI::ScrollBar* mScrollBar; MyGUI::ProgressBar* mDispositionBar; - MyGUI::TextBox* mDispositionText; + MyGUI::TextBox* mDispositionText; MyGUI::Button* mGoodbyeButton; PersuasionDialog mPersuasionDialog; diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp index aac772d6b..2143c5a75 100644 --- a/apps/openmw/mwgui/draganddrop.cpp +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -1,132 +1,124 @@ #include "draganddrop.hpp" -#include #include +#include -#include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" -#include "sortfilteritemmodel.hpp" -#include "inventorywindow.hpp" -#include "itemwidget.hpp" -#include "itemview.hpp" #include "controllers.hpp" +#include "inventorywindow.hpp" +#include "itemview.hpp" +#include "itemwidget.hpp" +#include "sortfilteritemmodel.hpp" namespace MWGui { - -DragAndDrop::DragAndDrop() - : mIsOnDragAndDrop(false) - , mDraggedWidget(nullptr) - , mSourceModel(nullptr) - , mSourceView(nullptr) - , mSourceSortModel(nullptr) - , mDraggedCount(0) -{ -} - -void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count) -{ - mItem = sourceModel->getItem(index); - mDraggedCount = count; - mSourceModel = sourceModel; - mSourceView = sourceView; - mSourceSortModel = sortModel; - - // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend - // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, - // but this is how it works in vanilla, and not doing so would break quests (BM_beasts for instance). - ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); - if (mSourceModel != playerModel) + DragAndDrop::DragAndDrop() + : mIsOnDragAndDrop(false) + , mDraggedWidget(nullptr) + , mSourceModel(nullptr) + , mSourceView(nullptr) + , mSourceSortModel(nullptr) + , mDraggedCount(0) { - MWWorld::Ptr item = mSourceModel->moveItem(mItem, mDraggedCount, playerModel); + } - playerModel->update(); + void DragAndDrop::startDrag( + int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count) + { + mItem = sourceModel->getItem(index); + mDraggedCount = count; + mSourceModel = sourceModel; + mSourceView = sourceView; + mSourceSortModel = sortModel; - ItemModel::ModelIndex newIndex = -1; - for (unsigned int i=0; igetItemCount(); ++i) + // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend + // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, + // but this is how it works in vanilla, and not doing so would break quests (BM_beasts for instance). + ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); + if (mSourceModel != playerModel) { - if (playerModel->getItem(i).mBase == item) + MWWorld::Ptr item = mSourceModel->moveItem(mItem, mDraggedCount, playerModel); + + playerModel->update(); + + ItemModel::ModelIndex newIndex = -1; + for (size_t i = 0; i < playerModel->getItemCount(); ++i) { - newIndex = i; - break; + if (playerModel->getItem(i).mBase == item) + { + newIndex = i; + break; + } } + mItem = playerModel->getItem(newIndex); + mSourceModel = playerModel; + + SortFilterItemModel* playerFilterModel + = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getSortFilterModel(); + mSourceSortModel = playerFilterModel; } - mItem = playerModel->getItem(newIndex); - mSourceModel = playerModel; - SortFilterItemModel* playerFilterModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getSortFilterModel(); - mSourceSortModel = playerFilterModel; + const ESM::RefId& sound = mItem.mBase.getClass().getUpSoundId(mItem.mBase); + MWBase::Environment::get().getWindowManager()->playSound(sound); + + if (mSourceSortModel) + { + mSourceSortModel->clearDragItems(); + mSourceSortModel->addDragItem(mItem.mBase, count); + } + + ItemWidget* baseWidget = MyGUI::Gui::getInstance().createWidget( + "MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop"); + + Controllers::ControllerFollowMouse* controller + = MyGUI::ControllerManager::getInstance() + .createItem(Controllers::ControllerFollowMouse::getClassTypeName()) + ->castType(); + MyGUI::ControllerManager::getInstance().addItem(baseWidget, controller); + + mDraggedWidget = baseWidget; + baseWidget->setItem(mItem.mBase); + baseWidget->setNeedMouseFocus(false); + baseWidget->setCount(count); + + sourceView->update(); + + MWBase::Environment::get().getWindowManager()->setDragDrop(true); + + mIsOnDragAndDrop = true; } - std::string sound = mItem.mBase.getClass().getUpSoundId(mItem.mBase); - MWBase::Environment::get().getWindowManager()->playSound (sound); - - if (mSourceSortModel) + void DragAndDrop::drop(ItemModel* targetModel, ItemView* targetView) { - mSourceSortModel->clearDragItems(); - mSourceSortModel->addDragItem(mItem.mBase, count); - } + const ESM::RefId& sound = mItem.mBase.getClass().getDownSoundId(mItem.mBase); + MWBase::Environment::get().getWindowManager()->playSound(sound); - ItemWidget* baseWidget = MyGUI::Gui::getInstance().createWidget("MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop"); + // We can't drop a conjured item to the ground; the target container should always be the source container + if (mItem.mFlags & ItemStack::Flag_Bound && targetModel != mSourceModel) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); + return; + } - Controllers::ControllerFollowMouse* controller = - MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerFollowMouse::getClassTypeName()) - ->castType(); - MyGUI::ControllerManager::getInstance().addItem(baseWidget, controller); + // If item is dropped where it was taken from, we don't need to do anything - + // otherwise, do the transfer + if (targetModel != mSourceModel) + { + mSourceModel->moveItem(mItem, mDraggedCount, targetModel); + } - mDraggedWidget = baseWidget; - baseWidget->setItem(mItem.mBase); - baseWidget->setNeedMouseFocus(false); - baseWidget->setCount(count); + mSourceModel->update(); - sourceView->update(); - - MWBase::Environment::get().getWindowManager()->setDragDrop(true); - - mIsOnDragAndDrop = true; -} - -void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView) -{ - std::string sound = mItem.mBase.getClass().getDownSoundId(mItem.mBase); - MWBase::Environment::get().getWindowManager()->playSound(sound); - - // We can't drop a conjured item to the ground; the target container should always be the source container - if (mItem.mFlags & ItemStack::Flag_Bound && targetModel != mSourceModel) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); - return; - } - - // If item is dropped where it was taken from, we don't need to do anything - - // otherwise, do the transfer - if (targetModel != mSourceModel) - { - mSourceModel->moveItem(mItem, mDraggedCount, targetModel); - } - - mSourceModel->update(); - - finish(); - if (targetView) - targetView->update(); - - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); - - // We need to update the view since an other item could be auto-equipped. - mSourceView->update(); -} - -void DragAndDrop::onFrame() -{ - if (mIsOnDragAndDrop && mItem.mBase.getRefData().getCount() == 0) finish(); -} + if (targetView) + targetView->update(); +<<<<<<< HEAD /* Start of tes3mp change (minor) @@ -158,10 +150,30 @@ void DragAndDrop::finish(bool deleteDragItems) // since mSourceView doesn't get updated in drag() MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); +======= + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); - mDraggedWidget = nullptr; - MWBase::Environment::get().getWindowManager()->setDragDrop(false); -} + // We need to update the view since an other item could be auto-equipped. + mSourceView->update(); + } + + void DragAndDrop::onFrame() + { + if (mIsOnDragAndDrop && mItem.mBase.getRefData().getCount() == 0) + finish(); + } + + void DragAndDrop::finish() + { + mIsOnDragAndDrop = false; + mSourceSortModel->clearDragItems(); + // since mSourceView doesn't get updated in drag() + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); + + MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); + mDraggedWidget = nullptr; + MWBase::Environment::get().getWindowManager()->setDragDrop(false); + } } diff --git a/apps/openmw/mwgui/draganddrop.hpp b/apps/openmw/mwgui/draganddrop.hpp index 20f753fbc..ec9cda009 100644 --- a/apps/openmw/mwgui/draganddrop.hpp +++ b/apps/openmw/mwgui/draganddrop.hpp @@ -27,8 +27,9 @@ namespace MWGui DragAndDrop(); - void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); - void drop (ItemModel* targetModel, ItemView* targetView); + void startDrag( + int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); + void drop(ItemModel* targetModel, ItemView* targetView); void onFrame(); /* diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 6647c5ff8..3d57e937a 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -3,11 +3,14 @@ #include #include -#include #include +#include +#include +#include #include -#include + +#include /* Start of tes3mp addition @@ -23,7 +26,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -35,15 +38,14 @@ #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" +#include "ustring.hpp" namespace MWGui { - EnchantingDialog::EnchantingDialog() : WindowBase("openmw_enchanting_dialog.layout") , EffectEditorBase(EffectEditorBase::Enchanting) - , mItemSelectionDialog(nullptr) { getWidget(mName, "NameEdit"); getWidget(mCancelButton, "CancelButton"); @@ -71,18 +73,13 @@ namespace MWGui mName->eventEditSelectAccept += MyGUI::newDelegate(this, &EnchantingDialog::onAccept); } - EnchantingDialog::~EnchantingDialog() - { - delete mItemSelectionDialog; - } - void EnchantingDialog::onOpen() { center(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mName); } - void EnchantingDialog::setSoulGem(const MWWorld::Ptr &gem) + void EnchantingDialog::setSoulGem(const MWWorld::Ptr& gem) { if (gem.isEmpty()) { @@ -93,13 +90,13 @@ namespace MWGui else { mSoulBox->setItem(gem); - mSoulBox->setUserString ("ToolTipType", "ItemPtr"); + mSoulBox->setUserString("ToolTipType", "ItemPtr"); mSoulBox->setUserData(MWWorld::Ptr(gem)); mEnchanting.setSoulGem(gem); } } - void EnchantingDialog::setItem(const MWWorld::Ptr &item) + void EnchantingDialog::setItem(const MWWorld::Ptr& item) { if (item.isEmpty()) { @@ -109,9 +106,10 @@ namespace MWGui } else { - mName->setCaption(item.getClass().getName(item)); + std::string_view name = item.getClass().getName(item); + mName->setCaption(toUString(name)); mItemBox->setItem(item); - mItemBox->setUserString ("ToolTipType", "ItemPtr"); + mItemBox->setUserString("ToolTipType", "ItemPtr"); mItemBox->setUserData(MWWorld::Ptr(item)); mEnchanting.setOldItem(item); } @@ -119,36 +117,41 @@ namespace MWGui void EnchantingDialog::updateLabels() { - mEnchantmentPoints->setCaption(std::to_string(static_cast(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue())); + mEnchantmentPoints->setCaption(std::to_string(static_cast(mEnchanting.getEnchantPoints(false))) + " / " + + std::to_string(mEnchanting.getMaxEnchantValue())); mCharge->setCaption(std::to_string(mEnchanting.getGemCharge())); - mSuccessChance->setCaption(std::to_string(std::max(0, std::min(100, mEnchanting.getEnchantChance())))); + mSuccessChance->setCaption(std::to_string(std::clamp(mEnchanting.getEnchantChance(), 0, 100))); mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost())); mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice())); - switch(mEnchanting.getCastStyle()) + switch (mEnchanting.getCastStyle()) { case ESM::Enchantment::CastOnce: - mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastOnce","Cast Once")); + mTypeButton->setCaption(toUString( + MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastOnce", "Cast Once"))); setConstantEffect(false); break; case ESM::Enchantment::WhenStrikes: - mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastWhenStrikes", "When Strikes")); + mTypeButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString( + "sItemCastWhenStrikes", "When Strikes"))); setConstantEffect(false); break; case ESM::Enchantment::WhenUsed: - mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastWhenUsed", "When Used")); + mTypeButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString( + "sItemCastWhenUsed", "When Used"))); setConstantEffect(false); break; case ESM::Enchantment::ConstantEffect: - mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastConstant", "Cast Constant")); + mTypeButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString( + "sItemCastConstant", "Cast Constant"))); setConstantEffect(true); break; } } - void EnchantingDialog::setPtr (const MWWorld::Ptr& ptr) + void EnchantingDialog::setPtr(const MWWorld::Ptr& ptr) { - mName->setCaption(""); + mName->setCaption({}); if (ptr.getClass().isActor()) { @@ -166,8 +169,7 @@ namespace MWGui mEnchanting.setSelfEnchanting(true); mEnchanting.setEnchanter(MWMechanics::getPlayer()); mBuyButton->setCaptionWithReplacing("#{sCreate}"); - bool enabled = Settings::Manager::getBool("show enchant chance","Game"); - mChanceLayout->setVisible(enabled); + mChanceLayout->setVisible(Settings::game().mShowEnchantChance); mPtr = MWMechanics::getPlayer(); setSoulGem(ptr); mPrice->setVisible(false); @@ -175,13 +177,13 @@ namespace MWGui } setItem(MWWorld::Ptr()); - startEditing (); + startEditing(); updateLabels(); } - void EnchantingDialog::onReferenceUnavailable () + void EnchantingDialog::onReferenceUnavailable() { - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Enchanting); resetReference(); } @@ -199,12 +201,11 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Enchanting); } - void EnchantingDialog::onSelectItem(MyGUI::Widget *sender) + void EnchantingDialog::onSelectItem(MyGUI::Widget* sender) { if (mEnchanting.getOldItem().isEmpty()) { - delete mItemSelectionDialog; - mItemSelectionDialog = new ItemSelectionDialog("#{sEnchantItems}"); + mItemSelectionDialog = std::make_unique("#{sEnchantItems}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onItemSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onItemCancel); mItemSelectionDialog->setVisible(true); @@ -238,9 +239,9 @@ namespace MWGui mItemSelectionDialog->setVisible(false); mEnchanting.setSoulGem(item); - if(mEnchanting.getGemCharge()==0) + if (mEnchanting.getGemCharge() == 0) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage32}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage32}"); return; } @@ -254,19 +255,18 @@ namespace MWGui mItemSelectionDialog->setVisible(false); } - void EnchantingDialog::onSelectSoul(MyGUI::Widget *sender) + void EnchantingDialog::onSelectSoul(MyGUI::Widget* sender) { if (mEnchanting.getGem().isEmpty()) { - delete mItemSelectionDialog; - mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}"); + mItemSelectionDialog = std::make_unique("#{sSoulGemsWithSouls}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onSoulSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onSoulCancel); mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); - //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); + // MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); } else { @@ -277,7 +277,7 @@ namespace MWGui } } - void EnchantingDialog::notifyEffectsChanged () + void EnchantingDialog::notifyEffectsChanged() { mEffectList.mList = mEffects; mEnchanting.setEffect(mEffectList); @@ -291,7 +291,7 @@ namespace MWGui updateEffectsView(); } - void EnchantingDialog::onAccept(MyGUI::EditBox *sender) + void EnchantingDialog::onAccept(MyGUI::EditBox* sender) { onBuyButtonClicked(sender); @@ -303,31 +303,31 @@ namespace MWGui { if (mEffects.size() <= 0) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu11}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sEnchantmentMenu11}"); return; } - if (mName->getCaption ().empty()) + if (mName->getCaption().empty()) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage10}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage10}"); return; } if (mEnchanting.soulEmpty()) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage52}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage52}"); return; } if (mEnchanting.itemEmpty()) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage11}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage11}"); return; } if (static_cast(mEnchanting.getEnchantPoints(false)) > mEnchanting.getMaxEnchantValue()) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage29}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage29}"); return; } @@ -338,25 +338,31 @@ namespace MWGui int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (mPtr != player && mEnchanting.getEnchantPrice() > playerGold) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage18}"); return; } // check if the player is attempting to use a soulstone or item that was stolen from this actor if (mPtr != player) { - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem(); - if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), mPtr)) + if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom( + item.getCellRef().getRefId(), mPtr)) { - std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->mValue.getString(); + std::string msg = MWBase::Environment::get() + .getESMStore() + ->get() + .find("sNotifyMessage49") + ->mValue.getString(); msg = Misc::StringUtils::format(msg, item.getClass().getName(item)); MWBase::Environment::get().getWindowManager()->messageBox(msg); - MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, item, mPtr, 1); + MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner( + player, item, mPtr, 1); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Enchanting); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); return; } @@ -365,8 +371,9 @@ namespace MWGui int result = mEnchanting.create(); - if(result==1) + if (result == 1) { +<<<<<<< HEAD MWBase::Environment::get().getWindowManager()->playSound("enchant success"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}"); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); @@ -404,6 +411,16 @@ namespace MWGui End of tes3mp addition */ +======= + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("enchant success")); + MWBase::Environment::get().getWindowManager()->messageBox("#{sEnchantmentMenu12}"); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Enchanting); + } + else + { + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("enchant fail")); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage34}"); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (!mEnchanting.getGem().isEmpty() && !mEnchanting.getGem().getRefData().getCount()) { setSoulGem(MWWorld::Ptr()); diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp index 3989ae076..f9d891b2d 100644 --- a/apps/openmw/mwgui/enchantingdialog.hpp +++ b/apps/openmw/mwgui/enchantingdialog.hpp @@ -1,31 +1,31 @@ #ifndef MWGUI_ENCHANTINGDIALOG_H #define MWGUI_ENCHANTINGDIALOG_H -#include "spellcreationdialog.hpp" +#include -#include "../mwbase/windowmanager.hpp" +#include "itemselection.hpp" +#include "spellcreationdialog.hpp" #include "../mwmechanics/enchanting.hpp" namespace MWGui { - class ItemSelectionDialog; class ItemWidget; class EnchantingDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase { public: EnchantingDialog(); - virtual ~EnchantingDialog(); + virtual ~EnchantingDialog() = default; void onOpen() override; void onFrame(float dt) override { checkReferenceAvailable(); } void clear() override { resetReference(); } - void setSoulGem (const MWWorld::Ptr& gem); - void setItem (const MWWorld::Ptr& item); + void setSoulGem(const MWWorld::Ptr& gem); + void setItem(const MWWorld::Ptr& item); /// Actor Ptr: buy enchantment from this actor /// Soulgem Ptr: player self-enchant @@ -38,8 +38,8 @@ namespace MWGui void notifyEffectsChanged() override; void onCancelButtonClicked(MyGUI::Widget* sender); - void onSelectItem (MyGUI::Widget* sender); - void onSelectSoul (MyGUI::Widget* sender); + void onSelectItem(MyGUI::Widget* sender); + void onSelectSoul(MyGUI::Widget* sender); void onItemSelected(MWWorld::Ptr item); void onItemCancel(); @@ -50,7 +50,7 @@ namespace MWGui void onTypeButtonClicked(MyGUI::Widget* sender); void onAccept(MyGUI::EditBox* sender); - ItemSelectionDialog* mItemSelectionDialog; + std::unique_ptr mItemSelectionDialog; MyGUI::Widget* mChanceLayout; diff --git a/apps/openmw/mwgui/exposedwindow.cpp b/apps/openmw/mwgui/exposedwindow.cpp index 90cfa09d3..36b47d98f 100644 --- a/apps/openmw/mwgui/exposedwindow.cpp +++ b/apps/openmw/mwgui/exposedwindow.cpp @@ -2,18 +2,18 @@ namespace MWGui { - MyGUI::VectorWidgetPtr Window::getSkinWidgetsByName (const std::string &name) + MyGUI::VectorWidgetPtr Window::getSkinWidgetsByName(const std::string& name) { - return MyGUI::Widget::getSkinWidgetsByName (name); + return MyGUI::Widget::getSkinWidgetsByName(name); } - MyGUI::Widget* Window::getSkinWidget(const std::string & _name, bool _throw) + MyGUI::Widget* Window::getSkinWidget(const std::string& _name, bool _throw) { - MyGUI::VectorWidgetPtr widgets = getSkinWidgetsByName (_name); + MyGUI::VectorWidgetPtr widgets = getSkinWidgetsByName(_name); if (widgets.empty()) { - MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' not found in skin of layout '" << getName() << "'"); + MYGUI_ASSERT(!_throw, "widget name '" << _name << "' not found in skin of layout '" << getName() << "'"); return nullptr; } else diff --git a/apps/openmw/mwgui/exposedwindow.hpp b/apps/openmw/mwgui/exposedwindow.hpp index f1f5d3c2f..d192a3db0 100644 --- a/apps/openmw/mwgui/exposedwindow.hpp +++ b/apps/openmw/mwgui/exposedwindow.hpp @@ -14,13 +14,12 @@ namespace MWGui MYGUI_RTTI_DERIVED(Window) public: - MyGUI::VectorWidgetPtr getSkinWidgetsByName (const std::string &name); + MyGUI::VectorWidgetPtr getSkinWidgetsByName(const std::string& name); - MyGUI::Widget* getSkinWidget(const std::string & _name, bool _throw = true); + MyGUI::Widget* getSkinWidget(const std::string& _name, bool _throw = true); ///< Get a widget defined in the inner skin of this window. }; } #endif - diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 156dc5b0d..0c193b498 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -1,492 +1,512 @@ #include "formatting.hpp" +#include +#include + +#include #include #include -#include #include -// correctBookartPath +#include +#include +#include +#include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" - -#include -#include -#include - #include "../mwscript/interpretercontext.hpp" -namespace MWGui +namespace MWGui::Formatting { - namespace Formatting + /* BookTextParser */ + BookTextParser::BookTextParser(const std::string& text) + : mIndex(0) + , mText(text) + , mIgnoreNewlineTags(true) + , mIgnoreLineEndings(true) + , mClosingTag(false) { - /* BookTextParser */ - BookTextParser::BookTextParser(const std::string & text) - : mIndex(0), mText(text), mIgnoreNewlineTags(true), mIgnoreLineEndings(true), mClosingTag(false) + MWScript::InterpreterContext interpreterContext( + nullptr, MWWorld::Ptr()); // empty arguments, because there is no locals or actor + mText = Interpreter::fixDefinesBook(mText, interpreterContext); + + Misc::StringUtils::replaceAll(mText, "\r", {}); + + // vanilla game does not show any text after the last EOL tag. + const std::string lowerText = Misc::StringUtils::lowerCase(mText); + size_t brIndex = lowerText.rfind("
        "); + size_t pIndex = lowerText.rfind("

        "); + mPlainTextEnd = 0; + if (brIndex != pIndex) { - MWScript::InterpreterContext interpreterContext(nullptr, MWWorld::Ptr()); // empty arguments, because there is no locals or actor - mText = Interpreter::fixDefinesBook(mText, interpreterContext); + if (brIndex != std::string::npos && pIndex != std::string::npos) + mPlainTextEnd = std::max(brIndex, pIndex); + else if (brIndex != std::string::npos) + mPlainTextEnd = brIndex; + else + mPlainTextEnd = pIndex; + } - Misc::StringUtils::replaceAll(mText, "\r", ""); + registerTag("br", Event_BrTag); + registerTag("p", Event_PTag); + registerTag("img", Event_ImgTag); + registerTag("div", Event_DivTag); + registerTag("font", Event_FontTag); + } - // vanilla game does not show any text after the last EOL tag. - const std::string lowerText = Misc::StringUtils::lowerCase(mText); - size_t brIndex = lowerText.rfind("
        "); - size_t pIndex = lowerText.rfind("

        "); - mPlainTextEnd = 0; - if (brIndex != pIndex) + void BookTextParser::registerTag(const std::string& tag, BookTextParser::Events type) + { + mTagTypes[tag] = type; + } + + std::string BookTextParser::getReadyText() const + { + return mReadyText; + } + + BookTextParser::Events BookTextParser::next() + { + while (mIndex < mText.size()) + { + char ch = mText[mIndex]; + if (ch == '<') { - if (brIndex != std::string::npos && pIndex != std::string::npos) - mPlainTextEnd = std::max(brIndex, pIndex); - else if (brIndex != std::string::npos) - mPlainTextEnd = brIndex; - else - mPlainTextEnd = pIndex; - } + const size_t tagStart = mIndex + 1; + const size_t tagEnd = mText.find('>', tagStart); + if (tagEnd == std::string::npos) + throw std::runtime_error("BookTextParser Error: Tag is not terminated"); + parseTag(mText.substr(tagStart, tagEnd - tagStart)); + mIndex = tagEnd; - registerTag("br", Event_BrTag); - registerTag("p", Event_PTag); - registerTag("img", Event_ImgTag); - registerTag("div", Event_DivTag); - registerTag("font", Event_FontTag); - } - - void BookTextParser::registerTag(const std::string & tag, BookTextParser::Events type) - { - mTagTypes[tag] = type; - } - - std::string BookTextParser::getReadyText() const - { - return mReadyText; - } - - BookTextParser::Events BookTextParser::next() - { - while (mIndex < mText.size()) - { - char ch = mText[mIndex]; - if (ch == '<') + if (auto it = mTagTypes.find(mTag); it != mTagTypes.end()) { - const size_t tagStart = mIndex + 1; - const size_t tagEnd = mText.find('>', tagStart); - if (tagEnd == std::string::npos) - throw std::runtime_error("BookTextParser Error: Tag is not terminated"); - parseTag(mText.substr(tagStart, tagEnd - tagStart)); - mIndex = tagEnd; + Events type = it->second; - if (mTagTypes.find(mTag) != mTagTypes.end()) + if (type == Event_BrTag || type == Event_PTag) { - Events type = mTagTypes.at(mTag); - - if (type == Event_BrTag || type == Event_PTag) + if (!mIgnoreNewlineTags) { - if (!mIgnoreNewlineTags) + if (type == Event_BrTag) + mBuffer.push_back('\n'); + else { - if (type == Event_BrTag) - mBuffer.push_back('\n'); - else - { - mBuffer.append("\n\n"); - } + mBuffer.append("\n\n"); } - mIgnoreLineEndings = true; } - else - flushBuffer(); - - if (type == Event_ImgTag) - { - mIgnoreNewlineTags = false; - } - - ++mIndex; - return type; - } - } - else - { - if (!mIgnoreLineEndings || ch != '\n') - { - if (mIndex < mPlainTextEnd) - mBuffer.push_back(ch); - mIgnoreLineEndings = false; - mIgnoreNewlineTags = false; - } - } - - ++mIndex; - } - - flushBuffer(); - return Event_EOF; - } - - void BookTextParser::flushBuffer() - { - mReadyText = mBuffer; - mBuffer.clear(); - } - - const BookTextParser::Attributes & BookTextParser::getAttributes() const - { - return mAttributes; - } - - bool BookTextParser::isClosingTag() const - { - return mClosingTag; - } - - void BookTextParser::parseTag(std::string tag) - { - size_t tagNameEndPos = tag.find(' '); - mAttributes.clear(); - mTag = tag.substr(0, tagNameEndPos); - Misc::StringUtils::lowerCaseInPlace(mTag); - if (mTag.empty()) - return; - - mClosingTag = (mTag[0] == '/'); - if (mClosingTag) - { - mTag.erase(mTag.begin()); - return; - } - - if (tagNameEndPos == std::string::npos) - return; - tag.erase(0, tagNameEndPos+1); - - while (!tag.empty()) - { - size_t sepPos = tag.find('='); - if (sepPos == std::string::npos) - return; - - std::string key = tag.substr(0, sepPos); - Misc::StringUtils::lowerCaseInPlace(key); - tag.erase(0, sepPos+1); - - std::string value; - - if (tag.empty()) - return; - - if (tag[0] == '"') - { - size_t quoteEndPos = tag.find('"', 1); - if (quoteEndPos == std::string::npos) - throw std::runtime_error("BookTextParser Error: Missing end quote in tag"); - value = tag.substr(1, quoteEndPos-1); - tag.erase(0, quoteEndPos+2); - } - else - { - size_t valEndPos = tag.find(' '); - if (valEndPos == std::string::npos) - { - value = tag; - tag.erase(); + mIgnoreLineEndings = true; } else + flushBuffer(); + + if (type == Event_ImgTag) { - value = tag.substr(0, valEndPos); - tag.erase(0, valEndPos+1); + mIgnoreNewlineTags = false; } + + ++mIndex; + return type; } - - mAttributes[key] = value; } - } - - /* BookFormatter */ - Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight) - { - Paginator pag(pageWidth, pageHeight); - - while (parent->getChildCount()) + else { - MyGUI::Gui::getInstance().destroyWidget(parent->getChildAt(0)); - } - - mTextStyle = TextStyle(); - mBlockStyle = BlockStyle(); - - MyGUI::Widget * paper = parent->createWidget("Widget", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); - paper->setNeedMouseFocus(false); - - BookTextParser parser(markup); - - bool brBeforeLastTag = false; - bool isPrevImg = false; - for (;;) - { - BookTextParser::Events event = parser.next(); - if (event == BookTextParser::Event_BrTag || event == BookTextParser::Event_PTag) - continue; - - std::string plainText = parser.getReadyText(); - - // for cases when linebreaks are used to cause a shift to the next page - // if the split text block ends in an empty line, proceeding text block(s) should have leading empty lines removed - if (pag.getIgnoreLeadingEmptyLines()) + if (!mIgnoreLineEndings || ch != '\n') { - while (!plainText.empty()) - { - if (plainText[0] == '\n') - plainText.erase(plainText.begin()); - else - { - pag.setIgnoreLeadingEmptyLines(false); - break; - } - } - } - - if (plainText.empty()) - brBeforeLastTag = true; - else - { - // Each block of text (between two tags / boundary and tag) will be displayed in a separate editbox widget, - // which means an additional linebreak will be created between them. - // ^ This is not what vanilla MW assumes, so we must deal with line breaks around tags appropriately. - bool brAtStart = (plainText[0] == '\n'); - bool brAtEnd = (plainText[plainText.size()-1] == '\n'); - - if (brAtStart && !brBeforeLastTag && !isPrevImg) - plainText.erase(plainText.begin()); - - if (plainText.size() && brAtEnd) - plainText.erase(plainText.end()-1); - - if (!plainText.empty() || brBeforeLastTag || isPrevImg) - { - TextElement elem(paper, pag, mBlockStyle, - mTextStyle, plainText); - elem.paginate(); - } - - brBeforeLastTag = brAtEnd; - } - - if (event == BookTextParser::Event_EOF) - break; - - isPrevImg = (event == BookTextParser::Event_ImgTag); - - switch (event) - { - case BookTextParser::Event_ImgTag: - { - const BookTextParser::Attributes & attr = parser.getAttributes(); - - if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) - continue; - - std::string src = attr.at("src"); - int width = MyGUI::utility::parseInt(attr.at("width")); - int height = MyGUI::utility::parseInt(attr.at("height")); - - bool exists; - std::string correctedSrc = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, height, &exists); - - if (!exists) - { - Log(Debug::Warning) << "Warning: Could not find \"" << src << "\" referenced by an tag."; - break; - } - - pag.setIgnoreLeadingEmptyLines(false); - - ImageElement elem(paper, pag, mBlockStyle, - correctedSrc, width, height); - elem.paginate(); - break; - } - case BookTextParser::Event_FontTag: - if (parser.isClosingTag()) - resetFontProperties(); - else - handleFont(parser.getAttributes()); - break; - case BookTextParser::Event_DivTag: - handleDiv(parser.getAttributes()); - break; - default: - break; + if (mIndex < mPlainTextEnd) + mBuffer.push_back(ch); + mIgnoreLineEndings = false; + mIgnoreNewlineTags = false; } } - // insert last page - if (pag.getStartTop() != pag.getCurrentTop()) - pag << Paginator::Page(pag.getStartTop(), pag.getStartTop() + pag.getPageHeight()); - - paper->setSize(paper->getWidth(), pag.getCurrentTop()); - - return pag.getPages(); + ++mIndex; } - Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget * parent, const std::string & markup) + flushBuffer(); + return Event_EOF; + } + + void BookTextParser::flushBuffer() + { + mReadyText = mBuffer; + mBuffer.clear(); + } + + const BookTextParser::Attributes& BookTextParser::getAttributes() const + { + return mAttributes; + } + + bool BookTextParser::isClosingTag() const + { + return mClosingTag; + } + + void BookTextParser::parseTag(std::string tag) + { + size_t tagNameEndPos = tag.find(' '); + mAttributes.clear(); + mTag = tag.substr(0, tagNameEndPos); + Misc::StringUtils::lowerCaseInPlace(mTag); + if (mTag.empty()) + return; + + mClosingTag = (mTag[0] == '/'); + if (mClosingTag) { - return markupToWidget(parent, markup, parent->getWidth(), parent->getHeight()); + mTag.erase(mTag.begin()); + return; } - void BookFormatter::resetFontProperties() - { - mTextStyle = TextStyle(); - } + if (tagNameEndPos == std::string::npos) + return; + tag.erase(0, tagNameEndPos + 1); - void BookFormatter::handleDiv(const BookTextParser::Attributes & attr) + while (!tag.empty()) { - if (attr.find("align") == attr.end()) + size_t sepPos = tag.find('='); + if (sepPos == std::string::npos) return; - std::string align = attr.at("align"); + std::string key = tag.substr(0, sepPos); + Misc::StringUtils::lowerCaseInPlace(key); + tag.erase(0, sepPos + 1); - if (Misc::StringUtils::ciEqual(align, "center")) - mBlockStyle.mAlign = MyGUI::Align::HCenter; - else if (Misc::StringUtils::ciEqual(align, "left")) - mBlockStyle.mAlign = MyGUI::Align::Left; - else if (Misc::StringUtils::ciEqual(align, "right")) - mBlockStyle.mAlign = MyGUI::Align::Right; - } + std::string value; - void BookFormatter::handleFont(const BookTextParser::Attributes & attr) - { - if (attr.find("color") != attr.end()) + if (tag.empty()) + return; + + if (tag[0] == '"') { - unsigned int color; - std::stringstream ss; - ss << attr.at("color"); - ss >> std::hex >> color; - - mTextStyle.mColour = MyGUI::Colour( - (color>>16 & 0xFF) / 255.f, - (color>>8 & 0xFF) / 255.f, - (color & 0xFF) / 255.f); + size_t quoteEndPos = tag.find('"', 1); + if (quoteEndPos == std::string::npos) + throw std::runtime_error("BookTextParser Error: Missing end quote in tag"); + value = tag.substr(1, quoteEndPos - 1); + tag.erase(0, quoteEndPos + 2); } - if (attr.find("face") != attr.end()) + else { - std::string face = attr.at("face"); - mTextStyle.mFont = "Journalbook "+face; - } - if (attr.find("size") != attr.end()) - { - /// \todo - } - } - - /* GraphicElement */ - GraphicElement::GraphicElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle) - : mParent(parent), mPaginator(pag), mBlockStyle(blockStyle) - { - } - - void GraphicElement::paginate() - { - int newTop = mPaginator.getCurrentTop() + getHeight(); - while (newTop-mPaginator.getStartTop() > mPaginator.getPageHeight()) - { - int newStartTop = pageSplit(); - mPaginator << Paginator::Page(mPaginator.getStartTop(), newStartTop); - mPaginator.setStartTop(newStartTop); - } - - mPaginator.setCurrentTop(newTop); - } - - int GraphicElement::pageSplit() - { - return mPaginator.getStartTop() + mPaginator.getPageHeight(); - } - - /* TextElement */ - TextElement::TextElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle, - const TextStyle & textStyle, const std::string & text) - : GraphicElement(parent, pag, blockStyle), - mTextStyle(textStyle) - { - Gui::EditBox* box = parent->createWidget("NormalText", - MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, - parent->getName() + MyGUI::utility::toString(parent->getChildCount())); - box->setEditStatic(true); - box->setEditMultiLine(true); - box->setEditWordWrap(true); - box->setNeedMouseFocus(false); - box->setNeedKeyFocus(false); - box->setMaxTextLength(text.size()); - box->setTextAlign(mBlockStyle.mAlign); - box->setTextColour(mTextStyle.mColour); - box->setFontName(mTextStyle.mFont); - box->setCaption(MyGUI::TextIterator::toTagsString(text)); - box->setSize(box->getSize().width, box->getTextSize().height); - mEditBox = box; - } - - int TextElement::getHeight() - { - return mEditBox->getTextSize().height; - } - - int TextElement::pageSplit() - { - // split lines - const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); - unsigned int lastLine = (mPaginator.getStartTop() + mPaginator.getPageHeight() - mPaginator.getCurrentTop()); - if (lineHeight > 0) - lastLine /= lineHeight; - int ret = mPaginator.getCurrentTop() + lastLine * lineHeight; - - // first empty lines that would go to the next page should be ignored - mPaginator.setIgnoreLeadingEmptyLines(true); - - const MyGUI::VectorLineInfo & lines = mEditBox->getSubWidgetText()->castType()->getLineInfo(); - for (unsigned int i = lastLine; i < lines.size(); ++i) - { - if (lines[i].width == 0) - ret += lineHeight; + size_t valEndPos = tag.find(' '); + if (valEndPos == std::string::npos) + { + value = tag; + tag.erase(); + } else { - mPaginator.setIgnoreLeadingEmptyLines(false); - break; + value = tag.substr(0, valEndPos); + tag.erase(0, valEndPos + 1); } } - return ret; - } - /* ImageElement */ - ImageElement::ImageElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle, - const std::string & src, int width, int height) - : GraphicElement(parent, pag, blockStyle), - mImageHeight(height) - { - int left = 0; - if (mBlockStyle.mAlign.isHCenter()) - left += (pag.getPageWidth() - width) / 2; - else if (mBlockStyle.mAlign.isLeft()) - left = 0; - else if (mBlockStyle.mAlign.isRight()) - left += pag.getPageWidth() - width; - - mImageBox = parent->createWidget ("ImageBox", - MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, - parent->getName() + MyGUI::utility::toString(parent->getChildCount())); - - mImageBox->setImageTexture(src); - mImageBox->setProperty("NeedMouse", "false"); - } - - int ImageElement::getHeight() - { - return mImageHeight; - } - - int ImageElement::pageSplit() - { - // if the image is larger than the page, fall back to the default pageSplit implementation - if (mImageHeight > mPaginator.getPageHeight()) - return GraphicElement::pageSplit(); - return mPaginator.getCurrentTop(); + mAttributes[key] = value; } } + + /* BookFormatter */ + Paginator::Pages BookFormatter::markupToWidget( + MyGUI::Widget* parent, const std::string& markup, const int pageWidth, const int pageHeight) + { + Paginator pag(pageWidth, pageHeight); + + while (parent->getChildCount()) + { + MyGUI::Gui::getInstance().destroyWidget(parent->getChildAt(0)); + } + + mTextStyle = TextStyle(); + mBlockStyle = BlockStyle(); + + MyGUI::Widget* paper = parent->createWidget("Widget", + MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); + paper->setNeedMouseFocus(false); + + BookTextParser parser(markup); + + bool brBeforeLastTag = false; + bool isPrevImg = false; + for (;;) + { + BookTextParser::Events event = parser.next(); + if (event == BookTextParser::Event_BrTag || event == BookTextParser::Event_PTag) + continue; + + std::string plainText = parser.getReadyText(); + + // for cases when linebreaks are used to cause a shift to the next page + // if the split text block ends in an empty line, proceeding text block(s) should have leading empty lines + // removed + if (pag.getIgnoreLeadingEmptyLines()) + { + while (!plainText.empty()) + { + if (plainText[0] == '\n') + plainText.erase(plainText.begin()); + else + { + pag.setIgnoreLeadingEmptyLines(false); + break; + } + } + } + + if (plainText.empty()) + brBeforeLastTag = true; + else + { + // Each block of text (between two tags / boundary and tag) will be displayed in a separate editbox + // widget, which means an additional linebreak will be created between them. ^ This is not what vanilla + // MW assumes, so we must deal with line breaks around tags appropriately. + bool brAtStart = (plainText[0] == '\n'); + bool brAtEnd = (plainText[plainText.size() - 1] == '\n'); + + if (brAtStart && !brBeforeLastTag && !isPrevImg) + plainText.erase(plainText.begin()); + + if (plainText.size() && brAtEnd) + plainText.erase(plainText.end() - 1); + + if (!plainText.empty() || brBeforeLastTag || isPrevImg) + { + TextElement elem(paper, pag, mBlockStyle, mTextStyle, plainText); + elem.paginate(); + } + + brBeforeLastTag = brAtEnd; + } + + if (event == BookTextParser::Event_EOF) + break; + + isPrevImg = (event == BookTextParser::Event_ImgTag); + + switch (event) + { + case BookTextParser::Event_ImgTag: + { + const BookTextParser::Attributes& attr = parser.getAttributes(); + + auto srcIt = attr.find("src"); + if (srcIt == attr.end()) + continue; + auto widthIt = attr.find("width"); + if (widthIt == attr.end()) + continue; + auto heightIt = attr.find("height"); + if (heightIt == attr.end()) + continue; + + const std::string& src = srcIt->second; + int width = MyGUI::utility::parseInt(widthIt->second); + int height = MyGUI::utility::parseInt(heightIt->second); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + std::string correctedSrc = Misc::ResourceHelpers::correctBookartPath(src, width, height, vfs); + bool exists = vfs->exists(correctedSrc); + + if (!exists) + { + Log(Debug::Warning) << "Warning: Could not find \"" << src << "\" referenced by an tag."; + break; + } + + pag.setIgnoreLeadingEmptyLines(false); + + ImageElement elem(paper, pag, mBlockStyle, correctedSrc, width, height); + elem.paginate(); + break; + } + case BookTextParser::Event_FontTag: + if (parser.isClosingTag()) + resetFontProperties(); + else + handleFont(parser.getAttributes()); + break; + case BookTextParser::Event_DivTag: + handleDiv(parser.getAttributes()); + break; + default: + break; + } + } + + // insert last page + if (pag.getStartTop() != pag.getCurrentTop()) + pag << Paginator::Page(pag.getStartTop(), pag.getStartTop() + pag.getPageHeight()); + + paper->setSize(paper->getWidth(), pag.getCurrentTop()); + + return pag.getPages(); + } + + Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget* parent, const std::string& markup) + { + return markupToWidget(parent, markup, parent->getWidth(), parent->getHeight()); + } + + void BookFormatter::resetFontProperties() + { + mTextStyle = TextStyle(); + } + + void BookFormatter::handleDiv(const BookTextParser::Attributes& attr) + { + auto it = attr.find("align"); + if (it == attr.end()) + return; + + const std::string& align = it->second; + + if (Misc::StringUtils::ciEqual(align, "center")) + mBlockStyle.mAlign = MyGUI::Align::HCenter; + else if (Misc::StringUtils::ciEqual(align, "left")) + mBlockStyle.mAlign = MyGUI::Align::Left; + else if (Misc::StringUtils::ciEqual(align, "right")) + mBlockStyle.mAlign = MyGUI::Align::Right; + } + + void BookFormatter::handleFont(const BookTextParser::Attributes& attr) + { + auto it = attr.find("color"); + if (it != attr.end()) + { + const auto& colorString = it->second; + unsigned int color = 0; + std::from_chars(colorString.data(), colorString.data() + colorString.size(), color, 16); + + mTextStyle.mColour + = MyGUI::Colour((color >> 16 & 0xFF) / 255.f, (color >> 8 & 0xFF) / 255.f, (color & 0xFF) / 255.f); + } + it = attr.find("face"); + if (it != attr.end()) + { + const std::string& face = it->second; + std::string name{ Gui::FontLoader::getFontForFace(face) }; + + mTextStyle.mFont = "Journalbook " + name; + } + if (attr.find("size") != attr.end()) + { + /// \todo + } + } + + /* GraphicElement */ + GraphicElement::GraphicElement(MyGUI::Widget* parent, Paginator& pag, const BlockStyle& blockStyle) + : mParent(parent) + , mPaginator(pag) + , mBlockStyle(blockStyle) + { + } + + void GraphicElement::paginate() + { + int newTop = mPaginator.getCurrentTop() + getHeight(); + while (newTop - mPaginator.getStartTop() > mPaginator.getPageHeight()) + { + int newStartTop = pageSplit(); + mPaginator << Paginator::Page(mPaginator.getStartTop(), newStartTop); + mPaginator.setStartTop(newStartTop); + } + + mPaginator.setCurrentTop(newTop); + } + + int GraphicElement::pageSplit() + { + return mPaginator.getStartTop() + mPaginator.getPageHeight(); + } + + /* TextElement */ + TextElement::TextElement(MyGUI::Widget* parent, Paginator& pag, const BlockStyle& blockStyle, + const TextStyle& textStyle, const std::string& text) + : GraphicElement(parent, pag, blockStyle) + , mTextStyle(textStyle) + { + Gui::EditBox* box = parent->createWidget("NormalText", + MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, + parent->getName() + MyGUI::utility::toString(parent->getChildCount())); + box->setEditStatic(true); + box->setEditMultiLine(true); + box->setEditWordWrap(true); + box->setNeedMouseFocus(false); + box->setNeedKeyFocus(false); + box->setMaxTextLength(text.size()); + box->setTextAlign(mBlockStyle.mAlign); + box->setTextColour(mTextStyle.mColour); + box->setFontName(mTextStyle.mFont); + box->setCaption(MyGUI::TextIterator::toTagsString(text)); + box->setSize(box->getSize().width, box->getTextSize().height); + mEditBox = box; + } + + int TextElement::getHeight() + { + return mEditBox->getTextSize().height; + } + + int TextElement::pageSplit() + { + // split lines + const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); + unsigned int lastLine = (mPaginator.getStartTop() + mPaginator.getPageHeight() - mPaginator.getCurrentTop()); + if (lineHeight > 0) + lastLine /= lineHeight; + int ret = mPaginator.getCurrentTop() + lastLine * lineHeight; + + // first empty lines that would go to the next page should be ignored + mPaginator.setIgnoreLeadingEmptyLines(true); + + const MyGUI::VectorLineInfo& lines = mEditBox->getSubWidgetText()->castType()->getLineInfo(); + for (size_t i = lastLine; i < lines.size(); ++i) + { + if (lines[i].width == 0) + ret += lineHeight; + else + { + mPaginator.setIgnoreLeadingEmptyLines(false); + break; + } + } + return ret; + } + + /* ImageElement */ + ImageElement::ImageElement(MyGUI::Widget* parent, Paginator& pag, const BlockStyle& blockStyle, + const std::string& src, int width, int height) + : GraphicElement(parent, pag, blockStyle) + , mImageHeight(height) + { + int left = 0; + if (mBlockStyle.mAlign.isHCenter()) + left += (pag.getPageWidth() - width) / 2; + else if (mBlockStyle.mAlign.isLeft()) + left = 0; + else if (mBlockStyle.mAlign.isRight()) + left += pag.getPageWidth() - width; + + mImageBox = parent->createWidget("ImageBox", + MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, + parent->getName() + MyGUI::utility::toString(parent->getChildCount())); + + mImageBox->setImageTexture(src); + mImageBox->setProperty("NeedMouse", "false"); + } + + int ImageElement::getHeight() + { + return mImageHeight; + } + + int ImageElement::pageSplit() + { + // if the image is larger than the page, fall back to the default pageSplit implementation + if (mImageHeight > mPaginator.getPageHeight()) + return GraphicElement::pageSplit(); + return mPaginator.getCurrentTop(); + } } diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index 12a3d46ca..421bda6f1 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -12,9 +12,9 @@ namespace MWGui { struct TextStyle { - TextStyle() : - mColour(0,0,0) - , mFont("Journalbook Magic Cards") + TextStyle() + : mColour(0, 0, 0) + , mFont("Journalbook DefaultFont") , mTextSize(16) { } @@ -26,8 +26,8 @@ namespace MWGui struct BlockStyle { - BlockStyle() : - mAlign(MyGUI::Align::Left | MyGUI::Align::Top) + BlockStyle() + : mAlign(MyGUI::Align::Left | MyGUI::Align::Top) { } @@ -36,140 +36,144 @@ namespace MWGui class BookTextParser { - public: - typedef std::map Attributes; - enum Events - { - Event_None = -2, - Event_EOF = -1, - Event_BrTag, - Event_PTag, - Event_ImgTag, - Event_DivTag, - Event_FontTag - }; + public: + typedef std::map Attributes; + enum Events + { + Event_None = -2, + Event_EOF = -1, + Event_BrTag, + Event_PTag, + Event_ImgTag, + Event_DivTag, + Event_FontTag + }; - BookTextParser(const std::string & text); + BookTextParser(const std::string& text); - Events next(); + Events next(); - const Attributes & getAttributes() const; - std::string getReadyText() const; - bool isClosingTag() const; + const Attributes& getAttributes() const; + std::string getReadyText() const; + bool isClosingTag() const; - private: - void registerTag(const std::string & tag, Events type); - void flushBuffer(); - void parseTag(std::string tag); + private: + void registerTag(const std::string& tag, Events type); + void flushBuffer(); + void parseTag(std::string tag); - size_t mIndex; - std::string mText; - std::string mReadyText; + size_t mIndex; + std::string mText; + std::string mReadyText; - bool mIgnoreNewlineTags; - bool mIgnoreLineEndings; - Attributes mAttributes; - std::string mTag; - bool mClosingTag; - std::map mTagTypes; - std::string mBuffer; + bool mIgnoreNewlineTags; + bool mIgnoreLineEndings; + Attributes mAttributes; + std::string mTag; + bool mClosingTag; + std::map mTagTypes; + std::string mBuffer; - size_t mPlainTextEnd; + size_t mPlainTextEnd; }; class Paginator { - public: - typedef std::pair Page; - typedef std::vector Pages; + public: + typedef std::pair Page; + typedef std::vector Pages; - Paginator(int pageWidth, int pageHeight) - : mStartTop(0), mCurrentTop(0), - mPageWidth(pageWidth), mPageHeight(pageHeight), - mIgnoreLeadingEmptyLines(false) - { - } + Paginator(int pageWidth, int pageHeight) + : mStartTop(0) + , mCurrentTop(0) + , mPageWidth(pageWidth) + , mPageHeight(pageHeight) + , mIgnoreLeadingEmptyLines(false) + { + } - int getStartTop() const { return mStartTop; } - int getCurrentTop() const { return mCurrentTop; } - int getPageWidth() const { return mPageWidth; } - int getPageHeight() const { return mPageHeight; } - bool getIgnoreLeadingEmptyLines() const { return mIgnoreLeadingEmptyLines; } - Pages getPages() const { return mPages; } + int getStartTop() const { return mStartTop; } + int getCurrentTop() const { return mCurrentTop; } + int getPageWidth() const { return mPageWidth; } + int getPageHeight() const { return mPageHeight; } + bool getIgnoreLeadingEmptyLines() const { return mIgnoreLeadingEmptyLines; } + Pages getPages() const { return mPages; } - void setStartTop(int top) { mStartTop = top; } - void setCurrentTop(int top) { mCurrentTop = top; } - void setIgnoreLeadingEmptyLines(bool ignore) { mIgnoreLeadingEmptyLines = ignore; } + void setStartTop(int top) { mStartTop = top; } + void setCurrentTop(int top) { mCurrentTop = top; } + void setIgnoreLeadingEmptyLines(bool ignore) { mIgnoreLeadingEmptyLines = ignore; } - Paginator & operator<<(const Page & page) - { - mPages.push_back(page); - return *this; - } + Paginator& operator<<(const Page& page) + { + mPages.push_back(page); + return *this; + } - private: - int mStartTop, mCurrentTop; - int mPageWidth, mPageHeight; - bool mIgnoreLeadingEmptyLines; - Pages mPages; + private: + int mStartTop, mCurrentTop; + int mPageWidth, mPageHeight; + bool mIgnoreLeadingEmptyLines; + Pages mPages; }; /// \brief utilities for parsing book/scroll text as mygui widgets class BookFormatter { - public: - Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup, const int pageWidth, const int pageHeight); - Paginator::Pages markupToWidget(MyGUI::Widget * parent, const std::string & markup); + public: + Paginator::Pages markupToWidget( + MyGUI::Widget* parent, const std::string& markup, const int pageWidth, const int pageHeight); + Paginator::Pages markupToWidget(MyGUI::Widget* parent, const std::string& markup); - private: - void resetFontProperties(); + private: + void resetFontProperties(); - void handleDiv(const BookTextParser::Attributes & attr); - void handleFont(const BookTextParser::Attributes & attr); + void handleDiv(const BookTextParser::Attributes& attr); + void handleFont(const BookTextParser::Attributes& attr); - TextStyle mTextStyle; - BlockStyle mBlockStyle; + TextStyle mTextStyle; + BlockStyle mBlockStyle; }; class GraphicElement { - public: - GraphicElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle); - virtual int getHeight() = 0; - virtual void paginate(); - virtual int pageSplit(); + public: + GraphicElement(MyGUI::Widget* parent, Paginator& pag, const BlockStyle& blockStyle); + virtual int getHeight() = 0; + virtual void paginate(); + virtual int pageSplit(); - protected: - virtual ~GraphicElement() {} - MyGUI::Widget * mParent; - Paginator & mPaginator; - BlockStyle mBlockStyle; + protected: + virtual ~GraphicElement() {} + MyGUI::Widget* mParent; + Paginator& mPaginator; + BlockStyle mBlockStyle; }; class TextElement : public GraphicElement { - public: - TextElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle, - const TextStyle & textStyle, const std::string & text); - int getHeight() override; - int pageSplit() override; - private: - int currentFontHeight() const; - TextStyle mTextStyle; - Gui::EditBox * mEditBox; + public: + TextElement(MyGUI::Widget* parent, Paginator& pag, const BlockStyle& blockStyle, const TextStyle& textStyle, + const std::string& text); + int getHeight() override; + int pageSplit() override; + + private: + int currentFontHeight() const; + TextStyle mTextStyle; + Gui::EditBox* mEditBox; }; class ImageElement : public GraphicElement { - public: - ImageElement(MyGUI::Widget * parent, Paginator & pag, const BlockStyle & blockStyle, - const std::string & src, int width, int height); - int getHeight() override; - int pageSplit() override; + public: + ImageElement(MyGUI::Widget* parent, Paginator& pag, const BlockStyle& blockStyle, const std::string& src, + int width, int height); + int getHeight() override; + int pageSplit() override; - private: - int mImageHeight; - MyGUI::ImageBox * mImageBox; + private: + int mImageHeight; + MyGUI::ImageBox* mImageBox; }; } } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 1a397ca90..1f09510dd 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -1,12 +1,13 @@ #include "hud.hpp" -#include -#include #include -#include #include +#include +#include +#include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -20,6 +21,12 @@ End of tes3mp addition */ +======= +#include +#include +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include #include @@ -30,14 +37,13 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" -#include "inventorywindow.hpp" -#include "spellicons.hpp" -#include "itemmodel.hpp" #include "draganddrop.hpp" +#include "inventorywindow.hpp" +#include "itemmodel.hpp" +#include "spellicons.hpp" #include "itemwidget.hpp" @@ -50,9 +56,13 @@ namespace MWGui class WorldItemModel : public ItemModel { public: - WorldItemModel(float left, float top) : mLeft(left), mTop(top) {} + WorldItemModel(float left, float top) + : mLeft(left) + , mTop(top) + { + } virtual ~WorldItemModel() override {} - MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool /*allowAutoEquip*/) override + MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool /*allowAutoEquip*/) override { MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -61,7 +71,7 @@ namespace MWGui dropped = world->placeObject(item.mBase, mLeft, mTop, count); else dropped = world->dropObjectOnGround(world->getPlayerPtr(), item.mBase, count); - dropped.getCellRef().setOwner(""); + dropped.getCellRef().setOwner(ESM::RefId()); /* Start of tes3mp addition @@ -92,11 +102,15 @@ namespace MWGui return dropped; } - void removeItem (const ItemStack& item, size_t count) override { throw std::runtime_error("removeItem not implemented"); } - ModelIndex getIndex (ItemStack item) override { throw std::runtime_error("getIndex not implemented"); } + void removeItem(const ItemStack& item, size_t count) override + { + throw std::runtime_error("removeItem not implemented"); + } + ModelIndex getIndex(const ItemStack& item) override { throw std::runtime_error("getIndex not implemented"); } void update() override {} size_t getItemCount() override { return 0; } - ItemStack getItem (ModelIndex index) override { throw std::runtime_error("getItem not implemented"); } + ItemStack getItem(ModelIndex index) override { throw std::runtime_error("getItem not implemented"); } + bool usesContainer(const MWWorld::Ptr&) override { return false; } private: // Where to drop the item @@ -104,8 +118,7 @@ namespace MWGui float mTop; }; - - HUD::HUD(CustomMarkerCollection &customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) + HUD::HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) : WindowBase("openmw_hud.layout") , LocalMapBase(customMarkers, localMapRender, Settings::Manager::getBool("local map hud fog of war", "Map")) , mHealth(nullptr) @@ -120,7 +133,7 @@ namespace MWGui , mMinimap(nullptr) , mCrosshair(nullptr) , mCellNameBox(nullptr) - , mDrowningFrame(nullptr) + , mDrowningBar(nullptr) , mDrowningFlash(nullptr) , mHealthManaStaminaBaseLeft(0) , mWeapBoxBaseLeft(0) @@ -139,8 +152,6 @@ namespace MWGui , mIsDrowning(false) , mDrowningFlashTheta(0.f) { - mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - // Energy bars getWidget(mHealthFrame, "HealthFrame"); getWidget(mHealth, "Health"); @@ -157,7 +168,8 @@ namespace MWGui magickaFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); - //Drowning bar + // Drowning bar + getWidget(mDrowningBar, "DrowningBar"); getWidget(mDrowningFrame, "DrowningFrame"); getWidget(mDrowning, "Drowning"); getWidget(mDrowningFlash, "Flash"); @@ -202,7 +214,7 @@ namespace MWGui mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); mMainWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &HUD::onWorldMouseLostFocus); - mSpellIcons = new SpellIcons(); + mSpellIcons = std::make_unique(); } HUD::~HUD() @@ -210,15 +222,12 @@ namespace MWGui mMainWidget->eventMouseLostFocus.clear(); mMainWidget->eventMouseMove.clear(); mMainWidget->eventMouseButtonClick.clear(); - - delete mSpellIcons; } - void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) + void HUD::setValue(std::string_view id, const MWMechanics::DynamicStat& value) { int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); - // Fatigue can be negative if (id != "FBar") current = std::max(0, current); @@ -263,27 +272,26 @@ namespace MWGui void HUD::setDrowningBarVisible(bool visible) { - mDrowningFrame->setVisible(visible); + mDrowningBar->setVisible(visible); } void HUD::onWorldClicked(MyGUI::Widget* _sender) { - if (!MWBase::Environment::get().getWindowManager ()->isGuiMode ()) + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); if (mDragAndDrop->mIsOnDragAndDrop) { // drop item into the gameworld - MWBase::Environment::get().getWorld()->breakInvisibility( - MWMechanics::getPlayer()); + MWBase::Environment::get().getWorld()->breakInvisibility(MWMechanics::getPlayer()); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); float mouseX = cursorPosition.left / float(viewSize.width); float mouseY = cursorPosition.top / float(viewSize.height); - WorldItemModel drop (mouseX, mouseY); + WorldItemModel drop(mouseX, mouseY); mDragAndDrop->drop(&drop, nullptr); winMgr->changePointer("arrow"); @@ -299,7 +307,7 @@ namespace MWGui if (winMgr->isConsoleMode()) winMgr->setConsoleSelectedObject(object); - else //if ((mode == GM_Container) || (mode == GM_Inventory)) + else // if ((mode == GM_Container) || (mode == GM_Inventory)) { // pick up object if (!object.isEmpty()) @@ -347,7 +355,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->changePointer("drop_ground"); else MWBase::Environment::get().getWindowManager()->changePointer("arrow"); - } else { @@ -423,12 +430,9 @@ namespace MWGui if (mEnemyHealth->getVisible() && mEnemyHealthTimer < 0) { mEnemyHealth->setVisible(false); - mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20)); + mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0, 20)); } - if (mIsDrowning) - mDrowningFlashTheta += dt * osg::PI*2; - mSpellIcons->updateWidgets(mEffectBox, true); if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) @@ -436,20 +440,25 @@ namespace MWGui updateEnemyHealthBar(); } + if (mDrowningBar->getVisible()) + mDrowningBar->setPosition( + mMainWidget->getWidth() / 2 - mDrowningFrame->getWidth() / 2, mMainWidget->getTop()); + if (mIsDrowning) { + mDrowningFlashTheta += dt * osg::PI * 2; + float intensity = (cos(mDrowningFlashTheta) + 2.0f) / 3.0f; mDrowningFlash->setAlpha(intensity); } } - void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) + void HUD::setSelectedSpell(const ESM::RefId& spellId, int successChancePercent) { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); - std::string spellName = spell->mName; + const std::string& spellName = spell->mName; if (spellName != mSpellName && mSpellVisible) { mWeaponSpellTimer = 5.0f; @@ -462,23 +471,24 @@ namespace MWGui mSpellStatus->setProgressPosition(successChancePercent); mSpellBox->setUserString("ToolTipType", "Spell"); - mSpellBox->setUserString("Spell", spellId); + mSpellBox->setUserString("Spell", spellId.serialize()); // use the icon of the first effect - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(spell->mEffects.mList.front().mEffectID); + const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( + spell->mEffects.mList.front().mEffectID); std::string icon = effect->mIcon; + std::replace(icon.begin(), icon.end(), '/', '\\'); int slashPos = icon.rfind('\\'); - icon.insert(slashPos+1, "b_"); - icon = MWBase::Environment::get().getWindowManager()->correctIconPath(icon); + icon.insert(slashPos + 1, "b_"); + icon = Misc::ResourceHelpers::correctIconPath(icon, MWBase::Environment::get().getResourceSystem()->getVFS()); mSpellImage->setSpellIcon(icon); } void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) { - std::string itemName = item.getClass().getName(item); + std::string_view itemName = item.getClass().getName(item); if (itemName != mSpellName && mSpellVisible) { mWeaponSpellTimer = 5.0f; @@ -498,7 +508,7 @@ namespace MWGui void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) { - std::string itemName = item.getClass().getName(item); + std::string_view itemName = item.getClass().getName(item); if (itemName != mWeaponName && mWeaponVisible) { mWeaponSpellTimer = 5.0f; @@ -519,7 +529,7 @@ namespace MWGui void HUD::unsetSelectedSpell() { - std::string spellName = "#{sNone}"; + std::string_view spellName = "#{Interface:None}"; if (spellName != mSpellName && mSpellVisible) { mWeaponSpellTimer = 5.0f; @@ -548,11 +558,12 @@ namespace MWGui mWeapStatus->setProgressRange(100); mWeapStatus->setProgressPosition(0); - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); mWeapImage->setItem(MWWorld::Ptr()); - std::string icon = (player.getClass().getNpcStats(player).isWerewolf()) ? "icons\\k\\tx_werewolf_hand.dds" : "icons\\k\\stealth_handtohand.dds"; + std::string icon = (player.getClass().getNpcStats(player).isWerewolf()) ? "icons\\k\\tx_werewolf_hand.dds" + : "icons\\k\\stealth_handtohand.dds"; mWeapImage->setIcon(icon); mWeapBox->clearUserStrings(); @@ -564,12 +575,12 @@ namespace MWGui void HUD::setCrosshairVisible(bool visible) { - mCrosshair->setVisible (visible); + mCrosshair->setVisible(visible); } - + void HUD::setCrosshairOwned(bool owned) { - if(owned) + if (owned) { mCrosshair->changeWidgetSkin("HUD_Crosshair_Owned"); } @@ -578,7 +589,7 @@ namespace MWGui mCrosshair->changeWidgetSkin("HUD_Crosshair"); } } - + void HUD::setHmsVisible(bool visible) { mHealth->setVisible(visible); @@ -607,13 +618,13 @@ namespace MWGui void HUD::setEffectVisible(bool visible) { - mEffectBox->setVisible (visible); + mEffectBox->setVisible(visible); updatePositions(); } void HUD::setMinimapVisible(bool visible) { - mMinimapBox->setVisible (visible); + mMinimapBox->setVisible(visible); updatePositions(); } @@ -645,14 +656,15 @@ namespace MWGui // effect box can have variable width -> variable left coordinate int effectsDx = 0; - if (!mMinimapBox->getVisible ()) + if (!mMinimapBox->getVisible()) effectsDx = mEffectBoxBaseRight - mMinimapBoxBaseRight; - mMapVisible = mMinimapBox->getVisible (); + mMapVisible = mMinimapBox->getVisible(); if (!mMapVisible) mCellNameBox->setVisible(false); - mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); + mEffectBox->setPosition( + (viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); } void HUD::updateEnemyHealthBar() @@ -664,20 +676,27 @@ namespace MWGui mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) - mEnemyHealth->setProgressPosition(static_cast(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100)); + mEnemyHealth->setProgressPosition(static_cast(stats.getHealth().getRatio() * 100)); - static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); + static const float fNPCHealthBarFade = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fNPCHealthBarFade") + ->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) - mEnemyHealth->setAlpha(std::max(0.f, std::min(1.f, mEnemyHealthTimer/fNPCHealthBarFade))); - + mEnemyHealth->setAlpha(std::clamp(mEnemyHealthTimer / fNPCHealthBarFade, 0.f, 1.f)); } - void HUD::setEnemy(const MWWorld::Ptr &enemy) + void HUD::setEnemy(const MWWorld::Ptr& enemy) { mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId(); - mEnemyHealthTimer = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarTime")->mValue.getFloat(); + mEnemyHealthTimer = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fNPCHealthBarTime") + ->mValue.getFloat(); if (!mEnemyHealth->getVisible()) - mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); + mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0, 20)); mEnemyHealth->setVisible(true); updateEnemyHealthBar(); } @@ -695,12 +714,12 @@ namespace MWGui resetEnemy(); } - void HUD::customMarkerCreated(MyGUI::Widget *marker) + void HUD::customMarkerCreated(MyGUI::Widget* marker) { marker->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); } - void HUD::doorMarkerCreated(MyGUI::Widget *marker) + void HUD::doorMarkerCreated(MyGUI::Widget* marker) { marker->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 8a89320d8..1dd9cdb52 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -1,7 +1,10 @@ #ifndef OPENMW_GAME_MWGUI_HUD_H #define OPENMW_GAME_MWGUI_HUD_H +#include + #include "mapwindow.hpp" +#include "spellicons.hpp" #include "statswatcher.hpp" namespace MWWorld @@ -12,7 +15,6 @@ namespace MWWorld namespace MWGui { class DragAndDrop; - class SpellIcons; class ItemWidget; class SpellWidget; @@ -21,7 +23,7 @@ namespace MWGui public: HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender); virtual ~HUD(); - void setValue (const std::string& id, const MWMechanics::DynamicStat& value) override; + void setValue(std::string_view id, const MWMechanics::DynamicStat& value) override; /// Set time left for the player to start drowning /// @param time time left to start drowning @@ -37,7 +39,7 @@ namespace MWGui void setEffectVisible(bool visible); void setMinimapVisible(bool visible); - void setSelectedSpell(const std::string& spellId, int successChancePercent); + void setSelectedSpell(const ESM::RefId& spellId, int successChancePercent); void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent); const MWWorld::Ptr& getSelectedEnchantItem(); void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent); @@ -64,8 +66,8 @@ namespace MWGui MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning; MyGUI::Widget* mHealthFrame; MyGUI::Widget *mWeapBox, *mSpellBox, *mSneakBox; - ItemWidget *mWeapImage; - SpellWidget *mSpellImage; + ItemWidget* mWeapImage; + SpellWidget* mSpellImage; MyGUI::ProgressBar *mWeapStatus, *mSpellStatus; MyGUI::Widget *mEffectBox, *mMinimapBox; MyGUI::Button* mMinimapButton; @@ -73,7 +75,7 @@ namespace MWGui MyGUI::ImageBox* mCrosshair; MyGUI::TextBox* mCellNameBox; MyGUI::TextBox* mWeaponSpellBox; - MyGUI::Widget *mDrowningFrame, *mDrowningFlash; + MyGUI::Widget *mDrowningBar, *mDrowningFrame, *mDrowningFlash; // bottom left elements int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft; @@ -95,12 +97,12 @@ namespace MWGui bool mWorldMouseOver; - SpellIcons* mSpellIcons; + std::unique_ptr mSpellIcons; int mEnemyActorId; float mEnemyHealthTimer; - bool mIsDrowning; + bool mIsDrowning; float mDrowningFlashTheta; void onWorldClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index f2ff64aa1..bcd51cd1b 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -5,8 +5,8 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwbase/environment.hpp" @@ -15,120 +15,127 @@ namespace MWGui { -InventoryItemModel::InventoryItemModel(const MWWorld::Ptr &actor) - : mActor(actor) -{ -} - -ItemStack InventoryItemModel::getItem (ModelIndex index) -{ - if (index < 0) - throw std::runtime_error("Invalid index supplied"); - if (mItems.size() <= static_cast(index)) - throw std::runtime_error("Item index out of range"); - return mItems[index]; -} - -size_t InventoryItemModel::getItemCount() -{ - return mItems.size(); -} - -ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item) -{ - size_t i = 0; - for (ItemStack& itemStack : mItems) + InventoryItemModel::InventoryItemModel(const MWWorld::Ptr& actor) + : mActor(actor) { - if (itemStack == item) - return i; - ++i; - } - return -1; -} - -MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) -{ - if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) - throw std::runtime_error("Item to copy needs to be from a different container!"); - return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, allowAutoEquip); -} - -void InventoryItemModel::removeItem (const ItemStack& item, size_t count) -{ - int removed = 0; - // Re-equipping makes sense only if a target has inventory - if (mActor.getClass().hasInventoryStore(mActor)) - { - MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor); - removed = store.remove(item.mBase, count, mActor, true); - } - else - { - MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); - removed = store.remove(item.mBase, count, mActor); } - std::stringstream error; - - if (removed == 0) + ItemStack InventoryItemModel::getItem(ModelIndex index) { - error << "Item '" << item.mBase.getCellRef().getRefId() << "' was not found in container store to remove"; - throw std::runtime_error(error.str()); + if (index < 0) + throw std::runtime_error("Invalid index supplied"); + if (mItems.size() <= static_cast(index)) + throw std::runtime_error("Item index out of range"); + return mItems[index]; } - else if (removed < static_cast(count)) + + size_t InventoryItemModel::getItemCount() { - error << "Not enough items '" << item.mBase.getCellRef().getRefId() << "' in the stack to remove (" << static_cast(count) << " requested, " << removed << " found)"; - throw std::runtime_error(error.str()); + return mItems.size(); } -} -MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) -{ - // Can't move conjured items: This is a general fix that also takes care of issues with taking conjured items via the 'Take All' button. - if (item.mFlags & ItemStack::Flag_Bound) - return MWWorld::Ptr(); - - MWWorld::Ptr ret = otherModel->copyItem(item, count); - removeItem(item, count); - return ret; -} - -void InventoryItemModel::update() -{ - MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); - - mItems.clear(); - - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + ItemModel::ModelIndex InventoryItemModel::getIndex(const ItemStack& item) { - MWWorld::Ptr item = *it; + size_t i = 0; + for (ItemStack& itemStack : mItems) + { + if (itemStack == item) + return i; + ++i; + } + return -1; + } - if (!item.getClass().showsInInventory(item)) - continue; - - ItemStack newItem (item, this, item.getRefData().getCount()); + MWWorld::Ptr InventoryItemModel::copyItem(const ItemStack& item, size_t count, bool allowAutoEquip) + { + if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) + throw std::runtime_error("Item to copy needs to be from a different container!"); + return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, allowAutoEquip); + } + void InventoryItemModel::removeItem(const ItemStack& item, size_t count) + { + int removed = 0; + // Re-equipping makes sense only if a target has inventory if (mActor.getClass().hasInventoryStore(mActor)) { - MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor); - if (invStore.isEquipped(newItem.mBase)) - newItem.mType = ItemStack::Type_Equipped; + MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor); + removed = store.remove(item.mBase, count, true); + } + else + { + MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); + removed = store.remove(item.mBase, count); } - mItems.push_back(newItem); + std::stringstream error; + + if (removed == 0) + { + error << "Item '" << item.mBase.getCellRef().getRefId() << "' was not found in container store to remove"; + throw std::runtime_error(error.str()); + } + else if (removed < static_cast(count)) + { + error << "Not enough items '" << item.mBase.getCellRef().getRefId() << "' in the stack to remove (" + << static_cast(count) << " requested, " << removed << " found)"; + throw std::runtime_error(error.str()); + } } -} -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()) + MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack& item, size_t count, ItemModel* otherModel) + { + // Can't move conjured items: This is a general fix that also takes care of issues with taking conjured items + // via the 'Take All' button. + if (item.mFlags & ItemStack::Flag_Bound) + return MWWorld::Ptr(); + + MWWorld::Ptr ret = otherModel->copyItem(item, count); + removeItem(item, count); + return ret; + } + + void InventoryItemModel::update() + { + MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); + + mItems.clear(); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + MWWorld::Ptr item = *it; + + if (!item.getClass().showsInInventory(item)) + continue; + + ItemStack newItem(item, this, item.getRefData().getCount()); + + if (mActor.getClass().hasInventoryStore(mActor)) + { + MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor); + if (invStore.isEquipped(newItem.mBase)) + newItem.mType = ItemStack::Type_Equipped; + } + + mItems.push_back(newItem); + } + } + + 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; + } - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, mActor, count); - - return true; -} + bool InventoryItemModel::usesContainer(const MWWorld::Ptr& container) + { + return mActor == container; + } } diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index 30d17f3e6..b99bfc544 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -9,24 +9,27 @@ namespace MWGui class InventoryItemModel : public ItemModel { public: - InventoryItemModel (const MWWorld::Ptr& actor); + InventoryItemModel(const MWWorld::Ptr& actor); - ItemStack getItem (ModelIndex index) override; - ModelIndex getIndex (ItemStack item) override; + ItemStack getItem(ModelIndex index) override; + ModelIndex getIndex(const ItemStack& item) override; size_t getItemCount() override; - bool onTakeItem(const MWWorld::Ptr &item, int count) override; + bool onTakeItem(const MWWorld::Ptr& item, int count) override; - MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; - void removeItem (const ItemStack& item, size_t count) override; + MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; + void removeItem(const ItemStack& item, size_t count) override; /// Move items from this model to \a otherModel. - MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel) override; + MWWorld::Ptr moveItem(const ItemStack& item, size_t count, ItemModel* otherModel) override; void update() override; + bool usesContainer(const MWWorld::Ptr& container) override; + protected: MWWorld::Ptr mActor; + private: std::vector mItems; }; diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 2102c163b..c18e05e97 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -3,21 +3,22 @@ #include #include -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include -#include +#include #include -#include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -33,33 +34,36 @@ */ #include "../mwbase/world.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/actionequip.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwmechanics/actorutil.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" -#include "itemview.hpp" -#include "inventoryitemmodel.hpp" -#include "sortfilteritemmodel.hpp" -#include "tradeitemmodel.hpp" #include "countdialog.hpp" -#include "tradewindow.hpp" #include "draganddrop.hpp" -#include "widgets.hpp" +#include "inventoryitemmodel.hpp" +#include "itemview.hpp" +#include "settings.hpp" +#include "sortfilteritemmodel.hpp" #include "tooltips.hpp" +#include "tradeitemmodel.hpp" +#include "tradewindow.hpp" namespace { bool isRightHandWeapon(const MWWorld::Ptr& item) { - if (item.getClass().getTypeName() != typeid(ESM::Weapon).name()) + if (item.getClass().getType() != ESM::Weapon::sRecordId) return false; std::vector equipmentSlots = item.getClass().getEquipmentSlots(item).first; return (!equipmentSlots.empty() && equipmentSlots.front() == MWWorld::InventoryStore::Slot_CarriedRight); @@ -69,8 +73,26 @@ namespace namespace MWGui { + namespace + { + WindowSettingValues getModeSettings(GuiMode mode) + { + switch (mode) + { + case GM_Container: + return makeInventoryContainerWindowSettingValues(); + case GM_Companion: + return makeInventoryCompanionWindowSettingValues(); + case GM_Barter: + return makeInventoryBarterWindowSettingValues(); + default: + return makeInventoryWindowSettingValues(); + } + } + } - InventoryWindow::InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem) + InventoryWindow::InventoryWindow( + DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem) : WindowPinnableBase("openmw_inventory_window.layout") , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) @@ -79,14 +101,16 @@ namespace MWGui , mGuiMode(GM_Inventory) , mLastXSize(0) , mLastYSize(0) - , mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer())) + , mPreview(std::make_unique(parent, resourceSystem, MWMechanics::getPlayer())) , mTrading(false) , mUpdateTimer(0.f) { - mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); + mPreviewTexture + = std::make_unique(mPreview->getTexture(), mPreview->getTextureStateSet()); mPreview->rebuild(); - mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); + mMainWidget->castType()->eventWindowChangeCoord + += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); getWidget(mAvatar, "Avatar"); getWidget(mAvatarImage, "AvatarImage"); @@ -127,27 +151,28 @@ namespace MWGui { const float aspect = 0.5; // fixed aspect ratio for the avatar image int leftPaneWidth = static_cast((mMainWidget->getSize().height - 44 - mArmorRating->getHeight()) * aspect); - mLeftPane->setSize( leftPaneWidth, mMainWidget->getSize().height-44 ); - mRightPane->setCoord( mLeftPane->getPosition().left + leftPaneWidth + 4, - mRightPane->getPosition().top, - mMainWidget->getSize().width - 12 - leftPaneWidth - 15, - mMainWidget->getSize().height-44 ); + mLeftPane->setSize(leftPaneWidth, mMainWidget->getSize().height - 44); + mRightPane->setCoord(mLeftPane->getPosition().left + leftPaneWidth + 4, mRightPane->getPosition().top, + mMainWidget->getSize().width - 12 - leftPaneWidth - 15, mMainWidget->getSize().height - 44); } void InventoryWindow::updatePlayer() { - mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); + mPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + auto tradeModel = std::make_unique(std::make_unique(mPtr), MWWorld::Ptr()); + mTradeModel = tradeModel.get(); if (mSortModel) // reuse existing SortModel when possible to keep previous category/filter settings - mSortModel->setSourceModel(mTradeModel); + mSortModel->setSourceModel(std::move(tradeModel)); else - mSortModel = new SortFilterItemModel(mTradeModel); + { + auto sortModel = std::make_unique(std::move(tradeModel)); + mSortModel = sortModel.get(); + mItemView->setModel(std::move(sortModel)); + } mSortModel->setNameFilter(mFilterEdit->getCaption()); - mItemView->setModel(mSortModel); - mFilterAll->setStateSelected(true); mFilterWeapon->setStateSelected(false); mFilterApparel->setStateSelected(false); @@ -177,24 +202,18 @@ namespace MWGui void InventoryWindow::toggleMaximized() { - std::string setting = getModeSetting(); - - bool maximized = !Settings::Manager::getBool(setting + " maximized", "Windows"); - if (maximized) - setting += " maximized"; + const WindowSettingValues settings = getModeSettings(mGuiMode); + const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mRegular : settings.mMaximized; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - float x = Settings::Manager::getFloat(setting + " x", "Windows") * float(viewSize.width); - float y = Settings::Manager::getFloat(setting + " y", "Windows") * float(viewSize.height); - float w = Settings::Manager::getFloat(setting + " w", "Windows") * float(viewSize.width); - float h = Settings::Manager::getFloat(setting + " h", "Windows") * float(viewSize.height); + const float x = rect.mX * viewSize.width; + const float y = rect.mY * viewSize.height; + const float w = rect.mW * viewSize.width; + const float h = rect.mH * viewSize.height; MyGUI::Window* window = mMainWidget->castType(); window->setCoord(x, y, w, h); - if (maximized) - Settings::Manager::setBool(setting, "Windows", maximized); - else - Settings::Manager::setBool(setting + " maximized", "Windows", maximized); + settings.mIsMaximized.set(!settings.mIsMaximized); adjustPanes(); updatePreviewSize(); @@ -203,17 +222,14 @@ namespace MWGui void InventoryWindow::setGuiMode(GuiMode mode) { mGuiMode = mode; - std::string setting = getModeSetting(); + const WindowSettingValues settings = getModeSettings(mGuiMode); setPinButtonVisible(mode == GM_Inventory); - if (Settings::Manager::getBool(setting + " maximized", "Windows")) - setting += " maximized"; + const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width), - static_cast(Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height)); - MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width), - static_cast(Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height)); + MyGUI::IntPoint pos(static_cast(rect.mX * viewSize.width), static_cast(rect.mY * viewSize.height)); + MyGUI::IntSize size(static_cast(rect.mW * viewSize.width), static_cast(rect.mH * viewSize.height)); bool needUpdate = (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()); @@ -247,12 +263,12 @@ namespace MWGui mDragAndDrop->drop(mTradeModel, mItemView); } - void InventoryWindow::onItemSelected (int index) + void InventoryWindow::onItemSelected(int index) { - onItemSelectedFromSourceModel (mSortModel->mapToSource(index)); + onItemSelectedFromSourceModel(mSortModel->mapToSource(index)); } - void InventoryWindow::onItemSelectedFromSourceModel (int index) + void InventoryWindow::onItemSelectedFromSourceModel(int index) { if (mDragAndDrop->mIsOnDragAndDrop) { @@ -261,7 +277,7 @@ namespace MWGui } const ItemStack& item = mTradeModel->getItem(index); - std::string sound = item.mBase.getClass().getDownSoundId(item.mBase); + const ESM::RefId& sound = item.mBase.getClass().getDownSoundId(item.mBase); MWWorld::Ptr object = item.mBase; int count = item.mCount; @@ -285,8 +301,7 @@ namespace MWGui if (!object.getClass().canSell(object, services)) { MWBase::Environment::get().getWindowManager()->playSound(sound); - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sBarterDialog4}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog4}"); return; } } @@ -294,7 +309,7 @@ namespace MWGui // If we unequip weapon during attack, it can lead to unexpected behaviour if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr)) { - bool isWeapon = item.mBase.getTypeName() == typeid(ESM::Weapon).name(); + bool isWeapon = item.mBase.getType() == ESM::Weapon::sRecordId; MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); if (isWeapon && invStore.isEquipped(item.mBase)) @@ -308,7 +323,8 @@ namespace MWGui { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}"; - std::string name = object.getClass().getName(object) + MWGui::ToolTips::getSoulString(object.getCellRef()); + std::string name{ object.getClass().getName(object) }; + name += MWGui::ToolTips::getSoulString(object.getCellRef()); dialog->openCountDialog(name, message, count); dialog->eventOkClicked.clear(); if (mTrading) @@ -321,9 +337,9 @@ namespace MWGui { mSelectedItem = index; if (mTrading) - sellItem (nullptr, count); + sellItem(nullptr, count); else - dragItem (nullptr, count); + dragItem(nullptr, count); } } @@ -333,17 +349,17 @@ namespace MWGui if (item.mType == ItemStack::Type_Equipped) { MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::Ptr newStack = *invStore.unequipItemQuantity(item.mBase, mPtr, count); + MWWorld::Ptr newStack = *invStore.unequipItemQuantity(item.mBase, count); // The unequipped item was re-stacked. We have to update the index // since the item pointed does not exist anymore. if (item.mBase != newStack) { - updateItemView(); // Unequipping can produce a new stack, not yet in the window... + updateItemView(); // Unequipping can produce a new stack, not yet in the window... // newIndex will store the index of the ItemStack the item was stacked on int newIndex = -1; - for (size_t i=0; i < mTradeModel->getItemCount(); ++i) + for (size_t i = 0; i < mTradeModel->getItemCount(); ++i) { if (mTradeModel->getItem(i).mBase == newStack) { @@ -371,7 +387,7 @@ namespace MWGui { ensureSelectedItemUnequipped(count); const ItemStack& item = mTradeModel->getItem(mSelectedItem); - std::string sound = item.mBase.getClass().getUpSoundId(item.mBase); + const ESM::RefId& sound = item.mBase.getClass().getUpSoundId(item.mBase); MWBase::Environment::get().getWindowManager()->playSound(sound); if (item.mType == ItemStack::Type_Barter) @@ -416,44 +432,18 @@ namespace MWGui adjustPanes(); } - std::string InventoryWindow::getModeSetting() const - { - std::string setting = "inventory"; - switch(mGuiMode) - { - case GM_Container: - setting += " container"; - break; - case GM_Companion: - setting += " companion"; - break; - case GM_Barter: - setting += " barter"; - break; - default: - break; - } - - return setting; - } - void InventoryWindow::onWindowResize(MyGUI::Window* _sender) { adjustPanes(); - std::string setting = getModeSetting(); + const WindowSettingValues settings = getModeSettings(mGuiMode); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - float x = _sender->getPosition().left / float(viewSize.width); - float y = _sender->getPosition().top / float(viewSize.height); - float w = _sender->getSize().width / float(viewSize.width); - float h = _sender->getSize().height / float(viewSize.height); - Settings::Manager::setFloat(setting + " x", "Windows", x); - Settings::Manager::setFloat(setting + " y", "Windows", y); - Settings::Manager::setFloat(setting + " w", "Windows", w); - Settings::Manager::setFloat(setting + " h", "Windows", h); - bool maximized = Settings::Manager::getBool(setting + " maximized", "Windows"); - if (maximized) - Settings::Manager::setBool(setting + " maximized", "Windows", false); + + settings.mRegular.mX.set(_sender->getPosition().left / static_cast(viewSize.width)); + settings.mRegular.mY.set(_sender->getPosition().top / static_cast(viewSize.height)); + settings.mRegular.mW.set(_sender->getSize().width / static_cast(viewSize.width)); + settings.mRegular.mH.set(_sender->getSize().height / static_cast(viewSize.height)); + settings.mIsMaximized.set(false); if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize) { @@ -467,22 +457,20 @@ namespace MWGui void InventoryWindow::updateArmorRating() { - mArmorRating->setCaptionWithReplacing ("#{sArmor}: " - + MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); + mArmorRating->setCaptionWithReplacing( + "#{sArmor}: " + MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) - mArmorRating->setCaptionWithReplacing (MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); + mArmorRating->setCaptionWithReplacing( + MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); } void InventoryWindow::updatePreviewSize() { - MyGUI::IntSize size = mAvatarImage->getSize(); - int width = std::min(mPreview->getTextureWidth(), size.width); - int height = std::min(mPreview->getTextureHeight(), size.height); - float scalingFactor = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - mPreview->setViewport(int(width*scalingFactor), int(height*scalingFactor)); - - mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, - width*scalingFactor/float(mPreview->getTextureWidth()), height*scalingFactor/float(mPreview->getTextureHeight()))); + const MyGUI::IntSize viewport = getPreviewViewportSize(); + mPreview->setViewport(viewport.width, viewport.height); + mAvatarImage->getSubWidgetMain()->_setUVSet( + MyGUI::FloatRect(0.f, 0.f, viewport.width / float(mPreview->getTextureWidth()), + viewport.height / float(mPreview->getTextureHeight()))); } void InventoryWindow::onNameFilterChanged(MyGUI::EditBox* _sender) @@ -516,7 +504,7 @@ namespace MWGui void InventoryWindow::onPinToggled() { - Settings::Manager::setBool("inventory pin", "Windows", mPinned); + Settings::windows().mInventoryPin.set(mPinned); MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned); } @@ -529,9 +517,9 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory); } - void InventoryWindow::useItem(const MWWorld::Ptr &ptr, bool force) + void InventoryWindow::useItem(const MWWorld::Ptr& ptr, bool force) { - const std::string& script = ptr.getClass().getScript(ptr); + const ESM::RefId& script = ptr.getClass().getScript(ptr); if (!script.empty()) { // Don't try to equip the item if PCSkipEquip is set to 1 @@ -545,7 +533,8 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); - // early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that case + // early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that + // case if (!ptr.getClass().getEquipmentSlots(ptr).first.empty()) { if (ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) @@ -557,7 +546,7 @@ namespace MWGui if (!force) { - std::pair canEquip = ptr.getClass().canBeEquipped(ptr, player); + auto canEquip = ptr.getClass().canBeEquipped(ptr, player); if (canEquip.first == 0) { @@ -572,16 +561,16 @@ namespace MWGui if (!script.empty()) { // Ingredients, books and repair hammers must not have OnPCEquip set to 1 here - const std::string& type = ptr.getTypeName(); - bool isBook = type == typeid(ESM::Book).name(); - if (!isBook && type != typeid(ESM::Ingredient).name() && type != typeid(ESM::Repair).name()) + auto type = ptr.getType(); + bool isBook = type == ESM::Book::sRecordId; + if (!isBook && type != ESM::Ingredient::sRecordId && type != ESM::Repair::sRecordId) ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); // Books must have PCSkipEquip set to 1 instead else if (isBook) ptr.getRefData().getLocals().setVarByInt(script, "pcskipequip", 1); } - std::shared_ptr action = ptr.getClass().use(ptr, force); + std::unique_ptr action = ptr.getClass().use(ptr, force); action->execute(player); if (isVisible()) @@ -604,9 +593,11 @@ namespace MWGui if (mDragAndDrop->mSourceModel != mTradeModel) { // Move item to the player's inventory - ptr = mDragAndDrop->mSourceModel->moveItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount, mTradeModel); + ptr = mDragAndDrop->mSourceModel->moveItem( + mDragAndDrop->mItem, mDragAndDrop->mDraggedCount, mTradeModel); } +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -618,10 +609,34 @@ namespace MWGui /* End of tes3mp change (major) */ +======= + // Handles partial equipping + const std::pair, bool> slots = ptr.getClass().getEquipmentSlots(ptr); + if (!slots.first.empty() && slots.second) + { + int equippedStackableCount = 0; + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator slotIt = invStore.getSlot(slots.first.front()); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 item - if ((ptr.getTypeName() == typeid(ESM::Potion).name() || - ptr.getTypeName() == typeid(ESM::Ingredient).name()) + // Get the count before useItem() + if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId()) + equippedStackableCount = slotIt->getRefData().getCount(); + + useItem(ptr); + int unequipCount = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - equippedStackableCount; + if (unequipCount > 0) + { + invStore.unequipItemQuantity(ptr, unequipCount); + updateItemView(); + } + } + else + useItem(ptr); + + // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 + // item + if ((ptr.getType() == ESM::Potion::sRecordId || ptr.getType() == ESM::Ingredient::sRecordId) && mDragAndDrop->mDraggedCount > 1) { // Item can be provided from other window for example container. @@ -632,14 +647,15 @@ namespace MWGui } else { - MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left); - MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition (); + MyGUI::IntPoint mousePos + = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); + MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition(); - MWWorld::Ptr itemSelected = getAvatarSelectedItem (relPos.left, relPos.top); - if (itemSelected.isEmpty ()) + MWWorld::Ptr itemSelected = getAvatarSelectedItem(relPos.left, relPos.top); + if (itemSelected.isEmpty()) return; - for (size_t i=0; i < mTradeModel->getItemCount (); ++i) + for (size_t i = 0; i < mTradeModel->getItemCount(); ++i) { if (mTradeModel->getItem(i).mBase == itemSelected) { @@ -653,21 +669,14 @@ namespace MWGui MWWorld::Ptr InventoryWindow::getAvatarSelectedItem(int x, int y) { - // convert to OpenGL lower-left origin - y = (mAvatarImage->getHeight()-1) - y; - - // Scale coordinates - float scalingFactor = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - x = static_cast(x*scalingFactor); - y = static_cast(y*scalingFactor); - - int slot = mPreview->getSlotSelected (x, y); + const osg::Vec2f viewport_coords = mapPreviewWindowToViewport(x, y); + int slot = mPreview->getSlotSelected(viewport_coords.x(), viewport_coords.y()); if (slot == -1) return MWWorld::Ptr(); MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); - if(invStore.getSlot(slot) != invStore.end()) + if (invStore.getSlot(slot) != invStore.end()) { MWWorld::Ptr item = *invStore.getSlot(slot); if (!item.getClass().showsInInventory(item)) @@ -726,31 +735,23 @@ namespace MWGui // update the spell window just in case new enchanted items were added to inventory MWBase::Environment::get().getWindowManager()->updateSpellWindow(); - MWBase::Environment::get().getMechanicsManager()->updateMagicEffects( - MWMechanics::getPlayer()); + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(MWMechanics::getPlayer()); dirtyPreview(); } - void InventoryWindow::pickUpObject (MWWorld::Ptr object) + void InventoryWindow::pickUpObject(MWWorld::Ptr object) { // If the inventory is not yet enabled, don't pick anything up if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory)) return; // make sure the object is of a type that can be picked up - std::string type = object.getTypeName(); - if ( (type != typeid(ESM::Apparatus).name()) - && (type != typeid(ESM::Armor).name()) - && (type != typeid(ESM::Book).name()) - && (type != typeid(ESM::Clothing).name()) - && (type != typeid(ESM::Ingredient).name()) - && (type != typeid(ESM::Light).name()) - && (type != typeid(ESM::Miscellaneous).name()) - && (type != typeid(ESM::Lockpick).name()) - && (type != typeid(ESM::Probe).name()) - && (type != typeid(ESM::Repair).name()) - && (type != typeid(ESM::Weapon).name()) - && (type != typeid(ESM::Potion).name())) + auto type = object.getType(); + if ((type != ESM::Apparatus::sRecordId) && (type != ESM::Armor::sRecordId) && (type != ESM::Book::sRecordId) + && (type != ESM::Clothing::sRecordId) && (type != ESM::Ingredient::sRecordId) + && (type != ESM::Light::sRecordId) && (type != ESM::Miscellaneous::sRecordId) + && (type != ESM::Lockpick::sRecordId) && (type != ESM::Probe::sRecordId) && (type != ESM::Repair::sRecordId) + && (type != ESM::Weapon::sRecordId) && (type != ESM::Potion::sRecordId)) return; // An object that can be picked up must have a tooltip. @@ -763,15 +764,22 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->breakInvisibility(player); - + if (!object.getRefData().activate()) return; + // Player must not be paralyzed, knocked down, or dead to pick up an item. + const MWMechanics::NpcStats& playerStats = player.getClass().getNpcStats(player); + bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); + if ((!godmode && playerStats.isParalyzed()) || playerStats.getKnockedDown() || playerStats.isDead()) + 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); + MWWorld::Ptr newObject + = *player.getClass().getContainerStore(player).add(object, object.getRefData().getCount()); /* Start of tes3mp addition @@ -789,12 +797,12 @@ namespace MWGui */ // remove from world - MWBase::Environment::get().getWorld()->deleteObject (object); + MWBase::Environment::get().getWorld()->deleteObject(object); // get ModelIndex to the item mTradeModel->update(); - size_t i=0; - for (; igetItemCount(); ++i) + size_t i = 0; + for (; i < mTradeModel->getItemCount(); ++i) { if (mTradeModel->getItem(i).mBase == newObject) break; @@ -813,20 +821,20 @@ namespace MWGui if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)) return; - const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); + const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) return; ItemModel::ModelIndex selected = -1; // not using mSortFilterModel as we only need sorting, not filtering - SortFilterItemModel model(new InventoryItemModel(player)); + SortFilterItemModel model(std::make_unique(player)); model.setSortByType(false); model.update(); if (model.getItemCount() == 0) return; - for (ItemModel::ModelIndex i=0; irebuild(); } + + MyGUI::IntSize InventoryWindow::getPreviewViewportSize() const + { + const MyGUI::IntSize previewWindowSize = mAvatarImage->getSize(); + const float scale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); + + return MyGUI::IntSize(std::min(mPreview->getTextureWidth(), previewWindowSize.width * scale), + std::min(mPreview->getTextureHeight(), previewWindowSize.height * scale)); + } + + osg::Vec2f InventoryWindow::mapPreviewWindowToViewport(int x, int y) const + { + const MyGUI::IntSize previewWindowSize = mAvatarImage->getSize(); + const float normalisedX = x / std::max(1.0f, previewWindowSize.width); + const float normalisedY = y / std::max(1.0f, previewWindowSize.height); + + const MyGUI::IntSize viewport = getPreviewViewportSize(); + return osg::Vec2f(normalisedX * float(viewport.width - 1), (1.0 - normalisedY) * float(viewport.height - 1)); + } } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 214245767..c302925fb 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -1,11 +1,11 @@ #ifndef MGUI_Inventory_H #define MGUI_Inventory_H -#include "windowpinnablebase.hpp" #include "mode.hpp" +#include "windowpinnablebase.hpp" -#include "../mwworld/ptr.hpp" #include "../mwrender/characterpreview.hpp" +#include "../mwworld/ptr.hpp" namespace osg { @@ -32,108 +32,110 @@ namespace MWGui class InventoryWindow : public WindowPinnableBase { - public: - InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem); + public: + InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem); - void onOpen() override; + void onOpen() override; - /// start trading, disables item drag&drop - void setTrading(bool trading); + /// start trading, disables item drag&drop + void setTrading(bool trading); - void onFrame(float dt) override; + void onFrame(float dt) override; - void pickUpObject (MWWorld::Ptr object); + void pickUpObject(MWWorld::Ptr object); - MWWorld::Ptr getAvatarSelectedItem(int x, int y); + MWWorld::Ptr getAvatarSelectedItem(int x, int y); - void rebuildAvatar(); + void rebuildAvatar(); - SortFilterItemModel* getSortFilterModel(); - TradeItemModel* getTradeModel(); - ItemModel* getModel(); + SortFilterItemModel* getSortFilterModel(); + TradeItemModel* getTradeModel(); + ItemModel* getModel(); - void updateItemView(); + void updateItemView(); - void updatePlayer(); + void updatePlayer(); - void clear() override; + void clear() override; - void useItem(const MWWorld::Ptr& ptr, bool force=false); + void useItem(const MWWorld::Ptr& ptr, bool force = false); - void setGuiMode(GuiMode mode); + void setGuiMode(GuiMode mode); - /// Cycle to previous/next weapon - void cycle(bool next); + /// Cycle to previous/next weapon + void cycle(bool next); - protected: - void onTitleDoubleClicked() override; + protected: + void onTitleDoubleClicked() override; - private: - DragAndDrop* mDragAndDrop; + private: + DragAndDrop* mDragAndDrop; - int mSelectedItem; + int mSelectedItem; - MWWorld::Ptr mPtr; + MWWorld::Ptr mPtr; - MWGui::ItemView* mItemView; - SortFilterItemModel* mSortModel; - TradeItemModel* mTradeModel; + MWGui::ItemView* mItemView; + SortFilterItemModel* mSortModel; + TradeItemModel* mTradeModel; - MyGUI::Widget* mAvatar; - MyGUI::ImageBox* mAvatarImage; - MyGUI::TextBox* mArmorRating; - Widgets::MWDynamicStat* mEncumbranceBar; + MyGUI::Widget* mAvatar; + MyGUI::ImageBox* mAvatarImage; + MyGUI::TextBox* mArmorRating; + Widgets::MWDynamicStat* mEncumbranceBar; - MyGUI::Widget* mLeftPane; - MyGUI::Widget* mRightPane; + MyGUI::Widget* mLeftPane; + MyGUI::Widget* mRightPane; - MyGUI::Button* mFilterAll; - MyGUI::Button* mFilterWeapon; - MyGUI::Button* mFilterApparel; - MyGUI::Button* mFilterMagic; - MyGUI::Button* mFilterMisc; - - MyGUI::EditBox* mFilterEdit; + MyGUI::Button* mFilterAll; + MyGUI::Button* mFilterWeapon; + MyGUI::Button* mFilterApparel; + MyGUI::Button* mFilterMagic; + MyGUI::Button* mFilterMisc; - GuiMode mGuiMode; + MyGUI::EditBox* mFilterEdit; - int mLastXSize; - int mLastYSize; + GuiMode mGuiMode; - std::unique_ptr mPreviewTexture; - std::unique_ptr mPreview; + int mLastXSize; + int mLastYSize; - bool mTrading; - float mUpdateTimer; + std::unique_ptr mPreviewTexture; + std::unique_ptr mPreview; - void toggleMaximized(); + bool mTrading; + float mUpdateTimer; - void onItemSelected(int index); - void onItemSelectedFromSourceModel(int index); + void toggleMaximized(); - void onBackgroundSelected(); + void onItemSelected(int index); + void onItemSelectedFromSourceModel(int index); - std::string getModeSetting() const; + void onBackgroundSelected(); - void sellItem(MyGUI::Widget* sender, int count); - void dragItem(MyGUI::Widget* sender, int count); + void sellItem(MyGUI::Widget* sender, int count); + void dragItem(MyGUI::Widget* sender, int count); - void onWindowResize(MyGUI::Window* _sender); - void onFilterChanged(MyGUI::Widget* _sender); - void onNameFilterChanged(MyGUI::EditBox* _sender); - void onAvatarClicked(MyGUI::Widget* _sender); - void onPinToggled() override; + void onWindowResize(MyGUI::Window* _sender); + void onFilterChanged(MyGUI::Widget* _sender); + void onNameFilterChanged(MyGUI::EditBox* _sender); + void onAvatarClicked(MyGUI::Widget* _sender); + void onPinToggled() override; - void updateEncumbranceBar(); - void notifyContentChanged(); - void dirtyPreview(); - void updatePreviewSize(); - void updateArmorRating(); + void updateEncumbranceBar(); + void notifyContentChanged(); + void dirtyPreview(); + void updatePreviewSize(); + void updateArmorRating(); - void adjustPanes(); + MyGUI::IntSize getPreviewViewportSize() const; + osg::Vec2f mapPreviewWindowToViewport(int x, int y) const; - /// Unequips count items from mSelectedItem, if it is equipped, and then updates mSelectedItem in case the items were re-stacked - void ensureSelectedItemUnequipped(int count); + void adjustPanes(); + + /// Unequips count items from mSelectedItem, if it is equipped, and then updates mSelectedItem in case the items + /// were re-stacked + void ensureSelectedItemUnequipped(int count); }; } diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index 44fa94f3a..f50db3cb6 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -2,27 +2,30 @@ #include -#include -#include -#include #include +#include +#include +#include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwmechanics/spellutil.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "itemmodel.hpp" #include "itemwidget.hpp" +#include "ustring.hpp" namespace MWGui { ItemChargeView::ItemChargeView() - : mScrollView(nullptr), - mDisplayMode(DisplayMode_Health) + : mScrollView(nullptr) + , mDisplayMode(DisplayMode_Health) { } @@ -91,17 +94,20 @@ namespace MWGui Line line; line.mItemPtr = stack.mBase; - line.mText = mScrollView->createWidget("SandText", MyGUI::IntCoord(), MyGUI::Align::Default); + line.mText + = mScrollView->createWidget("SandText", MyGUI::IntCoord(), MyGUI::Align::Default); line.mText->setNeedMouseFocus(false); - line.mIcon = mScrollView->createWidget("MW_ItemIconSmall", MyGUI::IntCoord(), MyGUI::Align::Default); + line.mIcon = mScrollView->createWidget( + "MW_ItemIconSmall", MyGUI::IntCoord(), MyGUI::Align::Default); line.mIcon->setItem(line.mItemPtr); line.mIcon->setUserString("ToolTipType", "ItemPtr"); line.mIcon->setUserData(MWWorld::Ptr(line.mItemPtr)); line.mIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemChargeView::onIconClicked); line.mIcon->eventMouseWheel += MyGUI::newDelegate(this, &ItemChargeView::onMouseWheelMoved); - line.mCharge = mScrollView->createWidget("MW_ChargeBar", MyGUI::IntCoord(), MyGUI::Align::Default); + line.mCharge = mScrollView->createWidget( + "MW_ChargeBar", MyGUI::IntCoord(), MyGUI::Align::Default); line.mCharge->setNeedMouseFocus(false); updateLine(line); @@ -132,17 +138,19 @@ namespace MWGui for (Line& line : mLines) { - line.mText->setCoord(8, currentY, mScrollView->getWidth()-8, 18); + line.mText->setCoord(8, currentY, mScrollView->getWidth() - 8, 18); currentY += 19; line.mIcon->setCoord(16, currentY, 32, 32); - line.mCharge->setCoord(72, currentY+2, std::max(199, mScrollView->getWidth()-72-38), 20); + line.mCharge->setCoord(72, currentY + 2, std::max(199, mScrollView->getWidth() - 72 - 38), 20); currentY += 32 + 4; } - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mScrollView->setVisibleVScroll(false); - mScrollView->setCanvasSize(MyGUI::IntSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), currentY))); + mScrollView->setCanvasSize( + MyGUI::IntSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), currentY))); mScrollView->setVisibleVScroll(true); } @@ -169,7 +177,8 @@ namespace MWGui void ItemChargeView::updateLine(const ItemChargeView::Line& line) { - line.mText->setCaption(line.mItemPtr.getClass().getName(line.mItemPtr)); + std::string_view name = line.mItemPtr.getClass().getName(line.mItemPtr); + line.mText->setCaption(toUString(name)); line.mCharge->setVisible(false); switch (mDisplayMode) @@ -180,19 +189,20 @@ namespace MWGui line.mCharge->setVisible(true); line.mCharge->setValue(line.mItemPtr.getClass().getItemHealth(line.mItemPtr), - line.mItemPtr.getClass().getItemMaxHealth(line.mItemPtr)); + line.mItemPtr.getClass().getItemMaxHealth(line.mItemPtr)); break; case DisplayMode_EnchantmentCharge: - std::string enchId = line.mItemPtr.getClass().getEnchantment(line.mItemPtr); + const ESM::RefId& enchId = line.mItemPtr.getClass().getEnchantment(line.mItemPtr); if (enchId.empty()) break; - const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(enchId); + const ESM::Enchantment* ench + = MWBase::Environment::get().getESMStore()->get().search(enchId); if (!ench) break; line.mCharge->setVisible(true); line.mCharge->setValue(static_cast(line.mItemPtr.getCellRef().getEnchantmentCharge()), - ench->mData.mCharge); + MWMechanics::getEnchantmentCharge(*ench)); break; } } @@ -204,9 +214,10 @@ namespace MWGui void ItemChargeView::onMouseWheelMoved(MyGUI::Widget* /*sender*/, int rel) { - if (mScrollView->getViewOffset().top + rel*0.3f > 0) + if (mScrollView->getViewOffset().top + rel * 0.3f > 0) mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); else - mScrollView->setViewOffset(MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + rel*0.3f))); + mScrollView->setViewOffset( + MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + rel * 0.3f))); } } diff --git a/apps/openmw/mwgui/itemchargeview.hpp b/apps/openmw/mwgui/itemchargeview.hpp index 039dcaf4e..8c266c4a2 100644 --- a/apps/openmw/mwgui/itemchargeview.hpp +++ b/apps/openmw/mwgui/itemchargeview.hpp @@ -1,8 +1,8 @@ #ifndef MWGUI_ITEMCHARGEVIEW_H #define MWGUI_ITEMCHARGEVIEW_H -#include #include +#include #include @@ -24,54 +24,54 @@ namespace MWGui class ItemChargeView final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(ItemChargeView) - public: - enum DisplayMode - { - DisplayMode_Health, - DisplayMode_EnchantmentCharge - }; + public: + enum DisplayMode + { + DisplayMode_Health, + DisplayMode_EnchantmentCharge + }; - ItemChargeView(); + ItemChargeView(); - /// Register needed components with MyGUI's factory manager - static void registerComponents(); + /// Register needed components with MyGUI's factory manager + static void registerComponents(); - void initialiseOverride() override; + void initialiseOverride() override; - /// Takes ownership of \a model - void setModel(ItemModel* model); + /// Takes ownership of \a model + void setModel(ItemModel* model); - void setDisplayMode(DisplayMode type); + void setDisplayMode(DisplayMode type); - void update(); - void layoutWidgets(); - void resetScrollbars(); + void update(); + void layoutWidgets(); + void resetScrollbars(); - void setSize(const MyGUI::IntSize& value) override; - void setCoord(const MyGUI::IntCoord& value) override; + void setSize(const MyGUI::IntSize& value) override; + void setCoord(const MyGUI::IntCoord& value) override; - MyGUI::delegates::CMultiDelegate2 eventItemClicked; + MyGUI::delegates::CMultiDelegate2 eventItemClicked; - private: - struct Line - { - MWWorld::Ptr mItemPtr; - MyGUI::TextBox* mText; - ItemWidget* mIcon; - Widgets::MWDynamicStatPtr mCharge; - }; + private: + struct Line + { + MWWorld::Ptr mItemPtr; + MyGUI::TextBox* mText; + ItemWidget* mIcon; + Widgets::MWDynamicStatPtr mCharge; + }; - void updateLine(const Line& line); + void updateLine(const Line& line); - void onIconClicked(MyGUI::Widget* sender); - void onMouseWheelMoved(MyGUI::Widget* sender, int rel); + void onIconClicked(MyGUI::Widget* sender); + void onMouseWheelMoved(MyGUI::Widget* sender, int rel); - typedef std::vector Lines; - Lines mLines; + typedef std::vector Lines; + Lines mLines; - std::unique_ptr mModel; - MyGUI::ScrollView* mScrollView; - DisplayMode mDisplayMode; + std::unique_ptr mModel; + MyGUI::ScrollView* mScrollView; + DisplayMode mDisplayMode; }; } diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 4e4d77da4..9b9815c98 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -9,14 +9,14 @@ namespace MWGui { - ItemStack::ItemStack(const MWWorld::Ptr &base, ItemModel *creator, size_t count) + ItemStack::ItemStack(const MWWorld::Ptr& base, ItemModel* creator, size_t count) : mType(Type_Normal) , mFlags(0) , mCreator(creator) , mCount(count) , mBase(base) { - if (base.getClass().getEnchantment(base) != "") + if (!base.getClass().getEnchantment(base).empty()) mFlags |= Flag_Enchanted; if (MWBase::Environment::get().getMechanicsManager()->isBoundItem(base)) @@ -31,18 +31,18 @@ namespace MWGui { } - bool operator == (const ItemStack& left, const ItemStack& right) + bool operator==(const ItemStack& left, const ItemStack& right) { if (left.mType != right.mType) return false; - if(left.mBase == right.mBase) + if (left.mBase == right.mBase) return true; // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure if (left.mBase.getContainerStore() && right.mBase.getContainerStore()) return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase) - && right.mBase.getContainerStore()->stacks(left.mBase, right.mBase); + && right.mBase.getContainerStore()->stacks(left.mBase, right.mBase); if (left.mBase.getContainerStore()) return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase); @@ -53,12 +53,12 @@ namespace MWGui return store.stacks(left.mBase, right.mBase); } - ItemModel::ItemModel() - { - } + ItemModel::ItemModel() {} - MWWorld::Ptr ItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) + MWWorld::Ptr ItemModel::moveItem(const ItemStack& item, size_t count, ItemModel* otherModel) { + // TODO(#6148): moving an item should preserve RefNum and Lua scripts (unless the item stack is merged with + // already existing stack). MWWorld::Ptr ret = otherModel->copyItem(item, count); removeItem(item, count); return ret; @@ -69,46 +69,35 @@ namespace MWGui return true; } - bool ItemModel::onDropItem(const MWWorld::Ptr &item, int count) + bool ItemModel::onDropItem(const MWWorld::Ptr& item, int count) { return true; } - bool ItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + bool ItemModel::onTakeItem(const MWWorld::Ptr& item, int count) { return true; } - - ProxyItemModel::ProxyItemModel() - : mSourceModel(nullptr) - { - } - - ProxyItemModel::~ProxyItemModel() - { - delete mSourceModel; - } - bool ProxyItemModel::allowedToUseItems() const { return mSourceModel->allowedToUseItems(); } - MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) + MWWorld::Ptr ProxyItemModel::copyItem(const ItemStack& item, size_t count, bool allowAutoEquip) { - return mSourceModel->copyItem (item, count, allowAutoEquip); + return mSourceModel->copyItem(item, count, allowAutoEquip); } - void ProxyItemModel::removeItem (const ItemStack& item, size_t count) + void ProxyItemModel::removeItem(const ItemStack& item, size_t count) { - mSourceModel->removeItem (item, count); + mSourceModel->removeItem(item, count); } - ItemModel::ModelIndex ProxyItemModel::mapToSource (ModelIndex index) + ItemModel::ModelIndex ProxyItemModel::mapToSource(ModelIndex index) { const ItemStack& itemToSearch = getItem(index); - for (size_t i=0; igetItemCount(); ++i) + for (size_t i = 0; i < mSourceModel->getItemCount(); ++i) { const ItemStack& item = mSourceModel->getItem(i); if (item.mBase == itemToSearch.mBase) @@ -117,10 +106,10 @@ namespace MWGui return -1; } - ItemModel::ModelIndex ProxyItemModel::mapFromSource (ModelIndex index) + ItemModel::ModelIndex ProxyItemModel::mapFromSource(ModelIndex index) { const ItemStack& itemToSearch = mSourceModel->getItem(index); - for (size_t i=0; igetIndex(item); } - void ProxyItemModel::setSourceModel(ItemModel *sourceModel) + void ProxyItemModel::setSourceModel(std::unique_ptr sourceModel) { - if (mSourceModel == sourceModel) - return; - - if (mSourceModel) - { - delete mSourceModel; - mSourceModel = nullptr; - } - - mSourceModel = sourceModel; + mSourceModel = std::move(sourceModel); } void ProxyItemModel::onClose() @@ -153,13 +133,18 @@ namespace MWGui mSourceModel->onClose(); } - bool ProxyItemModel::onDropItem(const MWWorld::Ptr &item, int count) + bool ProxyItemModel::onDropItem(const MWWorld::Ptr& item, int count) { return mSourceModel->onDropItem(item, count); } - bool ProxyItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + bool ProxyItemModel::onTakeItem(const MWWorld::Ptr& item, int count) { return mSourceModel->onTakeItem(item, count); } + + bool ProxyItemModel::usesContainer(const MWWorld::Ptr& container) + { + return mSourceModel->usesContainer(container); + } } diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index e120dde0f..c7143b4b9 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_ITEM_MODEL_H #define MWGUI_ITEM_MODEL_H +#include + #include "../mwworld/ptr.hpp" namespace MWGui @@ -11,7 +13,7 @@ namespace MWGui /// @brief A single item stack managed by an item model struct ItemStack { - ItemStack (const MWWorld::Ptr& base, ItemModel* creator, size_t count); + ItemStack(const MWWorld::Ptr& base, ItemModel* creator, size_t count); ItemStack(); ///< like operator==, only without checking mType @@ -25,8 +27,8 @@ namespace MWGui enum Flags { - Flag_Enchanted = (1<<0), - Flag_Bound = (1<<1) + Flag_Enchanted = (1 << 0), + Flag_Bound = (1 << 1) }; int mFlags; @@ -35,8 +37,7 @@ namespace MWGui MWWorld::Ptr mBase; }; - bool operator == (const ItemStack& left, const ItemStack& right); - + bool operator==(const ItemStack& left, const ItemStack& right); /// @brief The base class that all item models should derive from. class ItemModel @@ -48,63 +49,66 @@ namespace MWGui typedef int ModelIndex; // -1 means invalid index /// Throws for invalid index or out of range index - virtual ItemStack getItem (ModelIndex index) = 0; + virtual ItemStack getItem(ModelIndex index) = 0; /// The number of items in the model, this specifies the range of indices you can pass to /// the getItem function (but this range is only valid until the next call to update()) virtual size_t getItemCount() = 0; /// Returns an invalid index if the item was not found - virtual ModelIndex getIndex (ItemStack item) = 0; + virtual ModelIndex getIndex(const ItemStack& item) = 0; /// Rebuild the item model, this will invalidate existing model indices virtual void update() = 0; /// Move items from this model to \a otherModel. /// @note Derived implementations may return an empty Ptr if the move was unsuccessful. - virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); + virtual MWWorld::Ptr moveItem(const ItemStack& item, size_t count, ItemModel* otherModel); - virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) = 0; - virtual void removeItem (const ItemStack& item, size_t count) = 0; + virtual MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) = 0; + virtual void removeItem(const ItemStack& item, size_t count) = 0; /// Is the player allowed to use items from this item model? (default true) virtual 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 void onClose() {} + virtual bool onDropItem(const MWWorld::Ptr& item, int count); + virtual bool onTakeItem(const MWWorld::Ptr& item, int count); + + virtual bool usesContainer(const MWWorld::Ptr& container) = 0; private: ItemModel(const ItemModel&); ItemModel& operator=(const ItemModel&); }; - /// @brief A proxy item model can be used to filter or rearrange items from a source model (or even add new items to it). - /// The neat thing is that this does not actually alter the source model. + /// @brief A proxy item model can be used to filter or rearrange items from a source model (or even add new items to + /// it). The neat thing is that this does not actually alter the source model. class ProxyItemModel : public ItemModel { public: - ProxyItemModel(); - virtual ~ProxyItemModel(); + ProxyItemModel() = default; + virtual ~ProxyItemModel() = default; bool allowedToUseItems() const override; void onClose() override; - bool onDropItem(const MWWorld::Ptr &item, int count) override; - bool onTakeItem(const MWWorld::Ptr &item, int count) override; + bool onDropItem(const MWWorld::Ptr& item, int count) override; + bool onTakeItem(const MWWorld::Ptr& item, int count) override; - MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; - void removeItem (const ItemStack& item, size_t count) override; - ModelIndex getIndex (ItemStack item) override; + MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; + void removeItem(const ItemStack& item, size_t count) override; + ModelIndex getIndex(const ItemStack& item) override; /// @note Takes ownership of the passed pointer. - void setSourceModel(ItemModel* sourceModel); + void setSourceModel(std::unique_ptr sourceModel); + + ModelIndex mapToSource(ModelIndex index); + ModelIndex mapFromSource(ModelIndex index); + + bool usesContainer(const MWWorld::Ptr& container) override; - ModelIndex mapToSource (ModelIndex index); - ModelIndex mapFromSource (ModelIndex index); protected: - ItemModel* mSourceModel; + std::unique_ptr mSourceModel; }; } diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp index 23f1398ea..4fe40ce69 100644 --- a/apps/openmw/mwgui/itemselection.cpp +++ b/apps/openmw/mwgui/itemselection.cpp @@ -1,26 +1,25 @@ #include "itemselection.hpp" -#include #include +#include -#include "itemview.hpp" #include "inventoryitemmodel.hpp" +#include "itemview.hpp" #include "sortfilteritemmodel.hpp" namespace MWGui { - ItemSelectionDialog::ItemSelectionDialog(const std::string &label) + ItemSelectionDialog::ItemSelectionDialog(const std::string& label) : WindowModal("openmw_itemselection_dialog.layout") , mSortModel(nullptr) - , mModel(nullptr) { getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &ItemSelectionDialog::onSelectedItem); MyGUI::TextBox* l; getWidget(l, "Label"); - l->setCaptionWithReplacing (label); + l->setCaptionWithReplacing(label); MyGUI::Button* cancelButton; getWidget(cancelButton, "CancelButton"); @@ -37,9 +36,9 @@ namespace MWGui void ItemSelectionDialog::openContainer(const MWWorld::Ptr& container) { - mModel = new InventoryItemModel(container); - mSortModel = new SortFilterItemModel(mModel); - mItemView->setModel(mSortModel); + auto sortModel = std::make_unique(std::make_unique(container)); + mSortModel = sortModel.get(); + mItemView->setModel(std::move(sortModel)); mItemView->resetScrollBars(); } diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp index 6132bac7a..d418f5dbd 100644 --- a/apps/openmw/mwgui/itemselection.hpp +++ b/apps/openmw/mwgui/itemselection.hpp @@ -14,7 +14,6 @@ namespace MWGui { class ItemView; class SortFilterItemModel; - class InventoryItemModel; class ItemSelectionDialog : public WindowModal { @@ -29,14 +28,13 @@ namespace MWGui EventHandle_Item eventItemSelected; EventHandle_Void eventDialogCanceled; - void openContainer (const MWWorld::Ptr& container); + void openContainer(const MWWorld::Ptr& container); void setCategory(int category); void setFilter(int filter); private: ItemView* mItemView; SortFilterItemModel* mSortModel; - InventoryItemModel* mModel; void onSelectedItem(int index); diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index 14f2c1dd9..ff05a8b2d 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -7,172 +7,162 @@ #include #include -#include "../mwworld/class.hpp" - #include "itemmodel.hpp" #include "itemwidget.hpp" namespace MWGui { -ItemView::ItemView() - : mModel(nullptr) - , mScrollView(nullptr) -{ -} - -ItemView::~ItemView() -{ - delete mModel; -} - -void ItemView::setModel(ItemModel *model) -{ - if (mModel == model) - return; - - delete mModel; - mModel = model; - - update(); -} - -void ItemView::initialiseOverride() -{ - Base::initialiseOverride(); - - assignWidget(mScrollView, "ScrollView"); - if (mScrollView == nullptr) - throw std::runtime_error("Item view needs a scroll view"); - - mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); -} - -void ItemView::layoutWidgets() -{ - if (!mScrollView->getChildCount()) - return; - - int x = 0; - int y = 0; - MyGUI::Widget* dragArea = mScrollView->getChildAt(0); - int maxHeight = mScrollView->getHeight(); - - int rows = maxHeight/42; - rows = std::max(rows, 1); - bool showScrollbar = int(std::ceil(dragArea->getChildCount()/float(rows))) > mScrollView->getWidth()/42; - if (showScrollbar) - maxHeight -= 18; - - for (unsigned int i=0; igetChildCount(); ++i) + ItemView::ItemView() + : mScrollView(nullptr) { - MyGUI::Widget* w = dragArea->getChildAt(i); + } - w->setPosition(x, y); + void ItemView::setModel(std::unique_ptr model) + { + mModel = std::move(model); - y += 42; + update(); + } - if (y > maxHeight-42 && i < dragArea->getChildCount()-1) + void ItemView::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mScrollView, "ScrollView"); + if (mScrollView == nullptr) + throw std::runtime_error("Item view needs a scroll view"); + + mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); + } + + void ItemView::layoutWidgets() + { + if (!mScrollView->getChildCount()) + return; + + int x = 0; + int y = 0; + MyGUI::Widget* dragArea = mScrollView->getChildAt(0); + int maxHeight = mScrollView->getHeight(); + + int rows = maxHeight / 42; + rows = std::max(rows, 1); + bool showScrollbar = int(std::ceil(dragArea->getChildCount() / float(rows))) > mScrollView->getWidth() / 42; + if (showScrollbar) + maxHeight -= 18; + + for (unsigned int i = 0; i < dragArea->getChildCount(); ++i) { - x += 42; - y = 0; + MyGUI::Widget* w = dragArea->getChildAt(i); + + w->setPosition(x, y); + + y += 42; + + if (y > maxHeight - 42 && i < dragArea->getChildCount() - 1) + { + x += 42; + y = 0; + } } + x += 42; + + MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height); + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden + mScrollView->setVisibleVScroll(false); + mScrollView->setVisibleHScroll(false); + mScrollView->setCanvasSize(size); + mScrollView->setVisibleVScroll(true); + mScrollView->setVisibleHScroll(true); + dragArea->setSize(size); } - x += 42; - MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height); - - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden - mScrollView->setVisibleVScroll(false); - mScrollView->setVisibleHScroll(false); - mScrollView->setCanvasSize(size); - mScrollView->setVisibleVScroll(true); - mScrollView->setVisibleHScroll(true); - dragArea->setSize(size); -} - -void ItemView::update() -{ - while (mScrollView->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); - - if (!mModel) - return; - - mModel->update(); - - MyGUI::Widget* dragArea = mScrollView->createWidget("",0,0,mScrollView->getWidth(),mScrollView->getHeight(), - MyGUI::Align::Stretch); - dragArea->setNeedMouseFocus(true); - dragArea->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedBackground); - dragArea->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheelMoved); - - for (ItemModel::ModelIndex i=0; i(mModel->getItemCount()); ++i) + void ItemView::update() { - const ItemStack& item = mModel->getItem(i); + while (mScrollView->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); - ItemWidget* itemWidget = dragArea->createWidget("MW_ItemIcon", - MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); - itemWidget->setUserString("ToolTipType", "ItemModelIndex"); - itemWidget->setUserData(std::make_pair(i, mModel)); - ItemWidget::ItemState state = ItemWidget::None; - if (item.mType == ItemStack::Type_Barter) - state = ItemWidget::Barter; - if (item.mType == ItemStack::Type_Equipped) - state = ItemWidget::Equip; - itemWidget->setItem(item.mBase, state); - itemWidget->setCount(item.mCount); + if (!mModel) + return; - itemWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem); - itemWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheelMoved); + mModel->update(); + + MyGUI::Widget* dragArea = mScrollView->createWidget( + {}, 0, 0, mScrollView->getWidth(), mScrollView->getHeight(), MyGUI::Align::Stretch); + dragArea->setNeedMouseFocus(true); + dragArea->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedBackground); + dragArea->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheelMoved); + + for (ItemModel::ModelIndex i = 0; i < static_cast(mModel->getItemCount()); ++i) + { + const ItemStack& item = mModel->getItem(i); + + ItemWidget* itemWidget = dragArea->createWidget( + "MW_ItemIcon", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); + itemWidget->setUserString("ToolTipType", "ItemModelIndex"); + itemWidget->setUserData(std::make_pair(i, mModel.get())); + ItemWidget::ItemState state = ItemWidget::None; + if (item.mType == ItemStack::Type_Barter) + state = ItemWidget::Barter; + if (item.mType == ItemStack::Type_Equipped) + state = ItemWidget::Equip; + itemWidget->setItem(item.mBase, state); + itemWidget->setCount(item.mCount); + + itemWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem); + itemWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheelMoved); + } + + layoutWidgets(); } - layoutWidgets(); -} - -void ItemView::resetScrollBars() -{ - mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); -} - -void ItemView::onSelectedItem(MyGUI::Widget *sender) -{ - ItemModel::ModelIndex index = (*sender->getUserData >()).first; - eventItemClicked(index); -} - -void ItemView::onSelectedBackground(MyGUI::Widget *sender) -{ - eventBackgroundClicked(); -} - -void ItemView::onMouseWheelMoved(MyGUI::Widget *_sender, int _rel) -{ - if (mScrollView->getViewOffset().left + _rel*0.3f > 0) + void ItemView::resetScrollBars() + { mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); - else - mScrollView->setViewOffset(MyGUI::IntPoint(static_cast(mScrollView->getViewOffset().left + _rel*0.3f), 0)); -} + } -void ItemView::setSize(const MyGUI::IntSize &_value) -{ - bool changed = (_value.width != getWidth() || _value.height != getHeight()); - Base::setSize(_value); - if (changed) - layoutWidgets(); -} + void ItemView::onSelectedItem(MyGUI::Widget* sender) + { + ItemModel::ModelIndex index = (*sender->getUserData>()).first; + eventItemClicked(index); + } -void ItemView::setCoord(const MyGUI::IntCoord &_value) -{ - bool changed = (_value.width != getWidth() || _value.height != getHeight()); - Base::setCoord(_value); - if (changed) - layoutWidgets(); -} + void ItemView::onSelectedBackground(MyGUI::Widget* sender) + { + eventBackgroundClicked(); + } -void ItemView::registerComponents() -{ - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); -} + void ItemView::onMouseWheelMoved(MyGUI::Widget* _sender, int _rel) + { + if (mScrollView->getViewOffset().left + _rel * 0.3f > 0) + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mScrollView->setViewOffset( + MyGUI::IntPoint(static_cast(mScrollView->getViewOffset().left + _rel * 0.3f), 0)); + } + + void ItemView::setSize(const MyGUI::IntSize& _value) + { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); + Base::setSize(_value); + if (changed) + layoutWidgets(); + } + + void ItemView::setCoord(const MyGUI::IntCoord& _value) + { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); + Base::setCoord(_value); + if (changed) + layoutWidgets(); + } + + void ItemView::registerComponents() + { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + } } diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp index 4074e55e4..7fb242f51 100644 --- a/apps/openmw/mwgui/itemview.hpp +++ b/apps/openmw/mwgui/itemview.hpp @@ -10,16 +10,15 @@ namespace MWGui class ItemView final : public MyGUI::Widget { - MYGUI_RTTI_DERIVED(ItemView) + MYGUI_RTTI_DERIVED(ItemView) public: ItemView(); - ~ItemView() override; /// Register needed components with MyGUI's factory manager - static void registerComponents (); + static void registerComponents(); /// Takes ownership of \a model - void setModel (ItemModel* model); + void setModel(std::unique_ptr model); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ModelIndex; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; @@ -40,13 +39,12 @@ namespace MWGui void setSize(const MyGUI::IntSize& _value) override; void setCoord(const MyGUI::IntCoord& _value) override; - void onSelectedItem (MyGUI::Widget* sender); - void onSelectedBackground (MyGUI::Widget* sender); + void onSelectedItem(MyGUI::Widget* sender); + void onSelectedBackground(MyGUI::Widget* sender); void onMouseWheelMoved(MyGUI::Widget* _sender, int _rel); - ItemModel* mModel; + std::unique_ptr mModel; MyGUI::ScrollView* mScrollView; - }; } diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index d2dfa827b..49d47a8d5 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -7,6 +7,7 @@ #include // correctIconPath +#include #include #include @@ -19,15 +20,34 @@ namespace { std::string getCountString(int count) { + static const int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); + if (count == 1) - return ""; + return {}; + + // With small text size we can use up to 4 characters, while with large ones - only up to 3. + if (fontHeight > 16) + { + if (count > 999999999) + return MyGUI::utility::toString(count / 1000000000) + "b"; + else if (count > 99999999) + return ">9m"; + else if (count > 999999) + return MyGUI::utility::toString(count / 1000000) + "m"; + else if (count > 99999) + return ">9k"; + else if (count > 999) + return MyGUI::utility::toString(count / 1000) + "k"; + else + return MyGUI::utility::toString(count); + } if (count > 999999999) - return MyGUI::utility::toString(count/1000000000) + "b"; + return MyGUI::utility::toString(count / 1000000000) + "b"; else if (count > 999999) - return MyGUI::utility::toString(count/1000000) + "m"; + return MyGUI::utility::toString(count / 1000000) + "m"; else if (count > 9999) - return MyGUI::utility::toString(count/1000) + "k"; + return MyGUI::utility::toString(count / 1000) + "k"; else return MyGUI::utility::toString(count); } @@ -43,7 +63,6 @@ namespace MWGui , mFrame(nullptr) , mText(nullptr) { - } void ItemWidget::registerComponents() @@ -77,7 +96,7 @@ namespace MWGui mText->setCaption(getCountString(count)); } - void ItemWidget::setIcon(const std::string &icon) + void ItemWidget::setIcon(const std::string& icon) { if (mCurrentIcon != icon) { @@ -90,7 +109,7 @@ namespace MWGui } } - void ItemWidget::setFrame(const std::string &frame, const MyGUI::IntCoord &coord) + void ItemWidget::setFrame(const std::string& frame, const MyGUI::IntCoord& coord) { if (mFrame) { @@ -105,22 +124,23 @@ namespace MWGui } } - void ItemWidget::setIcon(const MWWorld::Ptr &ptr) + void ItemWidget::setIcon(const MWWorld::Ptr& ptr) { - std::string invIcon = ptr.getClass().getInventoryIcon(ptr); - if (invIcon.empty()) - invIcon = "default icon.tga"; - invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon); - if (!MWBase::Environment::get().getResourceSystem()->getVFS()->exists(invIcon)) + std::string_view icon = ptr.getClass().getInventoryIcon(ptr); + if (icon.empty()) + icon = "default icon.tga"; + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + std::string invIcon = Misc::ResourceHelpers::correctIconPath(icon, vfs); + if (!vfs->exists(invIcon)) { - Log(Debug::Error) << "Failed to open image: '" << invIcon << "' not found, falling back to 'default-icon.tga'"; - invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath("default icon.tga"); + Log(Debug::Error) << "Failed to open image: '" << invIcon + << "' not found, falling back to 'default-icon.tga'"; + invIcon = Misc::ResourceHelpers::correctIconPath("default icon.tga", vfs); } setIcon(invIcon); } - - void ItemWidget::setItem(const MWWorld::Ptr &ptr, ItemState state) + void ItemWidget::setItem(const MWWorld::Ptr& ptr, ItemState state) { if (!mItem) return; @@ -128,11 +148,11 @@ namespace MWGui if (ptr.isEmpty()) { if (mFrame) - mFrame->setImageTexture(""); + mFrame->setImageTexture({}); if (mItemShadow) - mItemShadow->setImageTexture(""); - mItem->setImageTexture(""); - mText->setCaption(""); + mItemShadow->setImageTexture({}); + mItem->setImageTexture({}); + mText->setCaption({}); mCurrentIcon.clear(); mCurrentFrame.clear(); return; @@ -146,7 +166,7 @@ namespace MWGui if (state == None) { if (!isMagic) - backgroundTex = ""; + backgroundTex.clear(); } else if (state == Equip) { @@ -155,7 +175,7 @@ namespace MWGui else if (state == Barter) backgroundTex += "_barter"; - if (backgroundTex != "") + if (!backgroundTex.empty()) backgroundTex += ".dds"; float scale = 1.f; @@ -178,9 +198,9 @@ namespace MWGui } if (state == Barter && !isMagic) - setFrame(backgroundTex, MyGUI::IntCoord(2*scale,2*scale,44*scale,44*scale)); + setFrame(backgroundTex, MyGUI::IntCoord(2 * scale, 2 * scale, 44 * scale, 44 * scale)); else - setFrame(backgroundTex, MyGUI::IntCoord(0,0,44*scale,44*scale)); + setFrame(backgroundTex, MyGUI::IntCoord(0, 0, 44 * scale, 44 * scale)); setIcon(ptr); } @@ -190,7 +210,7 @@ namespace MWGui if (mFrame && !mCurrentFrame.empty()) { mCurrentFrame.clear(); - mFrame->setImageTexture(""); + mFrame->setImageTexture({}); } if (mCurrentIcon != icon) { diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp index 550d73643..29b006320 100644 --- a/apps/openmw/mwgui/itemwidget.hpp +++ b/apps/openmw/mwgui/itemwidget.hpp @@ -14,12 +14,12 @@ namespace MWGui /// @brief A widget that shows an icon for an MWWorld::Ptr class ItemWidget : public MyGUI::Widget { - MYGUI_RTTI_DERIVED(ItemWidget) + MYGUI_RTTI_DERIVED(ItemWidget) public: ItemWidget(); /// Register needed components with MyGUI's factory manager - static void registerComponents (); + static void registerComponents(); enum ItemState { @@ -33,12 +33,12 @@ namespace MWGui void setCount(int count); /// \a ptr may be empty - void setItem (const MWWorld::Ptr& ptr, ItemState state = None); + void setItem(const MWWorld::Ptr& ptr, ItemState state = None); // Set icon and frame manually - void setIcon (const std::string& icon); - void setIcon (const MWWorld::Ptr& ptr); - void setFrame (const std::string& frame, const MyGUI::IntCoord& coord); + void setIcon(const std::string& icon); + void setIcon(const MWWorld::Ptr& ptr); + void setFrame(const std::string& frame, const MyGUI::IntCoord& coord); protected: void initialiseOverride() override; @@ -56,10 +56,9 @@ namespace MWGui class SpellWidget : public ItemWidget { - MYGUI_RTTI_DERIVED(SpellWidget) + MYGUI_RTTI_DERIVED(SpellWidget) public: - - void setSpellIcon (const std::string& icon); + void setSpellIcon(const std::string& icon); }; } diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 3e643ef4b..483baf5eb 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -12,28 +12,29 @@ */ #include +#include -#include "../mwbase/windowmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/store.hpp" -#include "../mwworld/class.hpp" #include "jailscreen.hpp" namespace MWGui { JailScreen::JailScreen() - : WindowBase("openmw_jail_screen.layout"), - mDays(1), - mFadeTimeRemaining(0), - mTimeAdvancer(0.01f) + : WindowBase("openmw_jail_screen.layout") + , mDays(1) + , mFadeTimeRemaining(0) + , mTimeAdvancer(0.01f) { getWidget(mProgressBar, "ProgressBar"); @@ -51,7 +52,7 @@ namespace MWGui mFadeTimeRemaining = 0.5; setVisible(false); - mProgressBar->setScrollRange(100+1); + mProgressBar->setScrollRange(100 + 1); mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(0); @@ -79,6 +80,7 @@ namespace MWGui if (mFadeTimeRemaining <= 0) { MWWorld::Ptr player = MWMechanics::getPlayer(); +<<<<<<< HEAD /* Start of tes3mp change (minor) @@ -93,6 +95,12 @@ namespace MWGui /* End of tes3mp change (minor) */ +======= + MWBase::Environment::get().getWorld()->teleportToClosestMarker( + player, ESM::RefId::stringRefId("prisonmarker")); + MWBase::Environment::get().getWindowManager()->fadeScreenOut( + 0.f); // override fade-in caused by cell transition +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 setVisible(true); mTimeAdvancer.run(100); @@ -102,7 +110,8 @@ namespace MWGui void JailScreen::onJailProgressChanged(int cur, int /*total*/) { mProgressBar->setScrollPosition(0); - mProgressBar->setTrackSize(static_cast(cur / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); + mProgressBar->setTrackSize( + static_cast(cur / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); } void JailScreen::onJailFinished() @@ -135,17 +144,17 @@ namespace MWGui */ // We should not worsen corprus when in prison - for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells()) - { - spell.second.mNextWorsening += mDays * 24; - } + player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24); - std::set skills; - for (int day=0; dayget(); + std::set skills; + for (int day = 0; day < mDays; ++day) { - int skill = Misc::Rng::rollDice(ESM::Skill::Length); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Skill* skill = skillStore.searchRandom({}, prng); skills.insert(skill); +<<<<<<< HEAD MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill); /* @@ -159,12 +168,17 @@ namespace MWGui /* End of tes3mp change (minor) */ +======= + MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill->mId); + if (skill->mId == ESM::Skill::Security || skill->mId == ESM::Skill::Sneak) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 value.setBase(std::min(100.f, value.getBase() + 1)); else - value.setBase(std::max(0.f, value.getBase()-1)); + value.setBase(std::max(0.f, value.getBase() - 1)); } - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); std::string message; if (mDays == 1) @@ -185,11 +199,11 @@ namespace MWGui message = Misc::StringUtils::format(message, mDays); - for (const int& skill : skills) + for (const ESM::Skill* skill : skills) { - std::string skillName = gmst.find(ESM::Skill::sSkillNameIds[skill])->mValue.getString(); - int skillValue = player.getClass().getNpcStats(player).getSkill(skill).getBase(); + int skillValue = player.getClass().getNpcStats(player).getSkill(skill->mId).getBase(); std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString(); +<<<<<<< HEAD /* Start of tes3mp change (minor) @@ -201,9 +215,12 @@ namespace MWGui /* End of tes3mp change (minor) */ +======= + if (skill->mId == ESM::Skill::Sneak || skill->mId == ESM::Skill::Security) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 skillMsg = gmst.find("sNotifyMessage39")->mValue.getString(); - skillMsg = Misc::StringUtils::format(skillMsg, skillName, skillValue); + skillMsg = Misc::StringUtils::format(skillMsg, skill->mName, skillValue); message += "\n" + skillMsg; } @@ -221,7 +238,7 @@ namespace MWGui */ std::vector buttons; - buttons.emplace_back("#{sOk}"); + buttons.emplace_back("#{Interface:OK}"); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); } } diff --git a/apps/openmw/mwgui/jailscreen.hpp b/apps/openmw/mwgui/jailscreen.hpp index 871a861d7..42a70abf4 100644 --- a/apps/openmw/mwgui/jailscreen.hpp +++ b/apps/openmw/mwgui/jailscreen.hpp @@ -1,32 +1,32 @@ #ifndef MWGUI_JAILSCREEN_H #define MWGUI_JAILSCREEN_H -#include "windowbase.hpp" #include "timeadvancer.hpp" +#include "windowbase.hpp" namespace MWGui { class JailScreen : public WindowBase { - public: - JailScreen(); - void goToJail(int days); + public: + JailScreen(); + void goToJail(int days); - void onFrame(float dt) override; + void onFrame(float dt) override; - bool exit() override { return false; } + bool exit() override { return false; } - private: - int mDays; + private: + int mDays; - float mFadeTimeRemaining; + float mFadeTimeRemaining; - MyGUI::ScrollBar* mProgressBar; + MyGUI::ScrollBar* mProgressBar; - void onJailProgressChanged(int cur, int total); - void onJailFinished(); + void onJailProgressChanged(int cur, int total); + void onJailFinished(); - TimeAdvancer mTimeAdvancer; + TimeAdvancer mTimeAdvancer; }; } diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index 40053b3c8..5163135cf 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -3,7 +3,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include #include #include "textcolours.hpp" @@ -15,29 +14,30 @@ namespace MWGui::BookTypesetter::Ptr mTypesetter; MWGui::BookTypesetter::Style* mBodyStyle; - AddContent (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : - mTypesetter (typesetter), mBodyStyle (body_style) + AddContent(MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) + : mTypesetter(typesetter) + , mBodyStyle(body_style) { } }; struct AddSpan : AddContent { - AddSpan (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : - AddContent (typesetter, body_style) + AddSpan(MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) + : AddContent(typesetter, body_style) { } - void operator () (intptr_t topicId, size_t begin, size_t end) + void operator()(intptr_t topicId, size_t begin, size_t end) { MWGui::BookTypesetter::Style* style = mBodyStyle; const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); if (topicId) - style = mTypesetter->createHotStyle (mBodyStyle, textColours.journalLink, textColours.journalLinkOver, - textColours.journalLinkPressed, topicId); + style = mTypesetter->createHotStyle(mBodyStyle, textColours.journalLink, textColours.journalLinkOver, + textColours.journalLinkPressed, topicId); - mTypesetter->write (style, begin, end); + mTypesetter->write(style, begin, end); } }; @@ -46,16 +46,17 @@ namespace MWGui::BookTypesetter::Ptr mTypesetter; MWGui::BookTypesetter::Style* mBodyStyle; - AddEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) : - mTypesetter (typesetter), mBodyStyle (body_style) + AddEntry(MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) + : mTypesetter(typesetter) + , mBodyStyle(body_style) { } - void operator () (MWGui::JournalViewModel::Entry const & entry) + void operator()(MWGui::JournalViewModel::Entry const& entry) { - mTypesetter->addContent (entry.body ()); + mTypesetter->addContent(entry.body()); - entry.visitSpans (AddSpan (mTypesetter, mBodyStyle)); + entry.visitSpans(AddSpan(mTypesetter, mBodyStyle)); } }; @@ -64,25 +65,25 @@ namespace bool mAddHeader; MWGui::BookTypesetter::Style* mHeaderStyle; - AddJournalEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style, - MWGui::BookTypesetter::Style* header_style, bool add_header) : - AddEntry (typesetter, body_style), - mAddHeader (add_header), - mHeaderStyle (header_style) + AddJournalEntry(MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style, + MWGui::BookTypesetter::Style* header_style, bool add_header) + : AddEntry(typesetter, body_style) + , mAddHeader(add_header) + , mHeaderStyle(header_style) { } - void operator () (MWGui::JournalViewModel::JournalEntry const & entry) + void operator()(MWGui::JournalViewModel::JournalEntry const& entry) { if (mAddHeader) { - mTypesetter->write (mHeaderStyle, entry.timestamp ()); - mTypesetter->lineBreak (); + mTypesetter->write(mHeaderStyle, entry.timestamp()); + mTypesetter->lineBreak(); } - AddEntry::operator () (entry); + AddEntry::operator()(entry); - mTypesetter->sectionBreak (30); + mTypesetter->sectionBreak(30); } }; @@ -91,51 +92,53 @@ namespace intptr_t mContentId; MWGui::BookTypesetter::Style* mHeaderStyle; - AddTopicEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style, - MWGui::BookTypesetter::Style* header_style, intptr_t contentId) : - AddEntry (typesetter, body_style), mContentId (contentId), mHeaderStyle (header_style) + AddTopicEntry(MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style, + MWGui::BookTypesetter::Style* header_style, intptr_t contentId) + : AddEntry(typesetter, body_style) + , mContentId(contentId) + , mHeaderStyle(header_style) { } - void operator () (MWGui::JournalViewModel::TopicEntry const & entry) + void operator()(MWGui::JournalViewModel::TopicEntry const& entry) { - mTypesetter->write (mBodyStyle, entry.source ()); - mTypesetter->write (mBodyStyle, 0, 3);// begin + mTypesetter->write(mBodyStyle, entry.source()); + mTypesetter->write(mBodyStyle, 0, 3); // begin - AddEntry::operator() (entry); + AddEntry::operator()(entry); - mTypesetter->selectContent (mContentId); - mTypesetter->write (mBodyStyle, 2, 3);// end quote + mTypesetter->selectContent(mContentId); + mTypesetter->write(mBodyStyle, 2, 3); // end quote - mTypesetter->sectionBreak (30); + mTypesetter->sectionBreak(30); } }; struct AddTopicName : AddContent { - AddTopicName (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : - AddContent (typesetter, style) + AddTopicName(MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) + : AddContent(typesetter, style) { } - void operator () (MWGui::JournalViewModel::Utf8Span topicName) + void operator()(MWGui::JournalViewModel::Utf8Span topicName) { - mTypesetter->write (mBodyStyle, topicName); - mTypesetter->sectionBreak (); + mTypesetter->write(mBodyStyle, topicName); + mTypesetter->sectionBreak(); } }; struct AddQuestName : AddContent { - AddQuestName (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) : - AddContent (typesetter, style) + AddQuestName(MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) + : AddContent(typesetter, style) { } - void operator () (MWGui::JournalViewModel::Utf8Span topicName) + void operator()(MWGui::JournalViewModel::Utf8Span topicName) { - mTypesetter->write (mBodyStyle, topicName); - mTypesetter->sectionBreak (); + mTypesetter->write(mBodyStyle, topicName); + mTypesetter->sectionBreak(); } }; } @@ -143,176 +146,184 @@ namespace namespace MWGui { -MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) -{ - typedef MWGui::BookTypesetter::Utf8Point point; - - point begin = reinterpret_cast (text); - - return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text)); -} - -typedef TypesetBook::Ptr book; - -JournalBooks::JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding) : - mModel (model), mEncoding(encoding), mIndexPagesCount(0) -{ -} - -book JournalBooks::createEmptyJournalBook () -{ - BookTypesetter::Ptr typesetter = createTypesetter (); - - BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f)); - BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); - - typesetter->write (header, to_utf8_span ("You have no journal entries!")); - typesetter->lineBreak (); - typesetter->write (body, to_utf8_span ("You should have gone though the starting quest and got an initial quest.")); - - return typesetter->complete (); -} - -book JournalBooks::createJournalBook () -{ - BookTypesetter::Ptr typesetter = createTypesetter (); - - BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f)); - BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); - - mModel->visitJournalEntries ("", AddJournalEntry (typesetter, body, header, true)); - - return typesetter->complete (); -} - -book JournalBooks::createTopicBook (uintptr_t topicId) -{ - BookTypesetter::Ptr typesetter = createTypesetter (); - - BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f)); - BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); - - mModel->visitTopicName (topicId, AddTopicName (typesetter, header)); - - intptr_t contentId = typesetter->addContent (to_utf8_span (", \"")); - - mModel->visitTopicEntries (topicId, AddTopicEntry (typesetter, body, header, contentId)); - - return typesetter->complete (); -} - -book JournalBooks::createQuestBook (const std::string& questName) -{ - BookTypesetter::Ptr typesetter = createTypesetter (); - - BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f)); - BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); - - AddQuestName addName (typesetter, header); - addName(to_utf8_span(questName.c_str())); - - mModel->visitJournalEntries (questName, AddJournalEntry (typesetter, body, header, false)); - - return typesetter->complete (); -} - -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, 260); - - typesetter->setSectionAlignment (BookTypesetter::AlignCenter); - - // Latin journal index always has two columns for now. - mIndexPagesCount = 2; - - char ch = 'A'; - - BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); - for (int i = 0; i < 26; ++i) + MWGui::BookTypesetter::Utf8Span to_utf8_span(std::string_view text) { - char buffer [32]; - sprintf (buffer, "( %c )", ch); + typedef MWGui::BookTypesetter::Utf8Point point; - const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); - BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, - textColours.journalTopicOver, - textColours.journalTopicPressed, (Utf8Stream::UnicodeChar) ch); + point begin = reinterpret_cast(text.data()); - if (i == 13) - typesetter->sectionBreak (); - - typesetter->write (style, to_utf8_span (buffer)); - typesetter->lineBreak (); - - ch++; + return MWGui::BookTypesetter::Utf8Span(begin, begin + text.length()); } - return typesetter; -} + typedef TypesetBook::Ptr book; -BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex () -{ - BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 260); - - typesetter->setSectionAlignment (BookTypesetter::AlignCenter); - - BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black); - - int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); - - // for small font size split alphabet to two columns (2x15 characers), for big font size split it to three colums (3x10 characters). - int sectionBreak = 10; - mIndexPagesCount = 3; - if (fontHeight < 18) + JournalBooks::JournalBooks(JournalViewModel::Ptr model, ToUTF8::FromType encoding) + : mModel(model) + , mEncoding(encoding) + , mIndexPagesCount(0) { - sectionBreak = 15; + } + + book JournalBooks::createEmptyJournalBook() + { + BookTypesetter::Ptr typesetter = createTypesetter(); + + BookTypesetter::Style* header = typesetter->createStyle({}, MyGUI::Colour(0.60f, 0.00f, 0.00f)); + BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black); + + typesetter->write(header, to_utf8_span("You have no journal entries!")); + typesetter->lineBreak(); + typesetter->write( + body, to_utf8_span("You should have gone though the starting quest and got an initial quest.")); + + return typesetter->complete(); + } + + book JournalBooks::createJournalBook() + { + BookTypesetter::Ptr typesetter = createTypesetter(); + + BookTypesetter::Style* header = typesetter->createStyle({}, MyGUI::Colour(0.60f, 0.00f, 0.00f)); + BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black); + + mModel->visitJournalEntries({}, AddJournalEntry(typesetter, body, header, true)); + + return typesetter->complete(); + } + + book JournalBooks::createTopicBook(uintptr_t topicId) + { + BookTypesetter::Ptr typesetter = createTypesetter(); + + BookTypesetter::Style* header = typesetter->createStyle({}, MyGUI::Colour(0.60f, 0.00f, 0.00f)); + BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black); + + mModel->visitTopicName(topicId, AddTopicName(typesetter, header)); + + intptr_t contentId = typesetter->addContent(to_utf8_span(", \"")); + + mModel->visitTopicEntries(topicId, AddTopicEntry(typesetter, body, header, contentId)); + + return typesetter->complete(); + } + + book JournalBooks::createQuestBook(std::string_view questName) + { + BookTypesetter::Ptr typesetter = createTypesetter(); + + BookTypesetter::Style* header = typesetter->createStyle({}, MyGUI::Colour(0.60f, 0.00f, 0.00f)); + BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black); + + AddQuestName addName(typesetter, header); + addName(to_utf8_span(questName)); + + mModel->visitJournalEntries(questName, AddJournalEntry(typesetter, body, header, false)); + + return typesetter->complete(); + } + + 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, 260); + + typesetter->setSectionAlignment(BookTypesetter::AlignCenter); + + // Latin journal index always has two columns for now. mIndexPagesCount = 2; + + char ch = 'A'; + std::string buffer; + + BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black); + for (int i = 0; i < 26; ++i) + { + buffer = "( "; + buffer += ch; + buffer += " )"; + + const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); + BookTypesetter::Style* style = typesetter->createHotStyle(body, textColours.journalTopic, + textColours.journalTopicOver, textColours.journalTopicPressed, (Utf8Stream::UnicodeChar)ch); + + if (i == 13) + typesetter->sectionBreak(); + + typesetter->write(style, to_utf8_span(buffer)); + typesetter->lineBreak(); + + ch++; + } + + return typesetter; } - unsigned char ch[3] = {0xd0, 0x90, 0x00}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8 - - for (int i = 0; i < 32; ++i) + BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex() { - char buffer [32]; - sprintf(buffer, "( %c%c )", ch[0], ch[1]); + BookTypesetter::Ptr typesetter = BookTypesetter::create(92, 260); - Utf8Stream stream ((char*) ch); - Utf8Stream::UnicodeChar first = stream.peek(); + typesetter->setSectionAlignment(BookTypesetter::AlignCenter); - const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); - BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic, - textColours.journalTopicOver, - textColours.journalTopicPressed, first); + BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black); - ch[1]++; + int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); - // Words can not be started with these characters - if (i == 26 || i == 28) - continue; + // for small font size split alphabet to two columns (2x15 characers), for big font size split it to three + // colums (3x10 characters). + int sectionBreak = 10; + mIndexPagesCount = 3; + if (fontHeight < 18) + { + sectionBreak = 15; + mIndexPagesCount = 2; + } - if (i % sectionBreak == 0) - typesetter->sectionBreak (); + unsigned char ch[3] = { 0xd0, 0x90, 0x00 }; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8 - typesetter->write (style, to_utf8_span (buffer)); - typesetter->lineBreak (); + std::string buffer; + + for (int i = 0; i < 32; ++i) + { + buffer = "( "; + buffer += ch[0]; + buffer += ch[1]; + buffer += " )"; + + Utf8Stream stream(ch, ch + 2); + Utf8Stream::UnicodeChar 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 % sectionBreak == 0) + typesetter->sectionBreak(); + + typesetter->write(style, to_utf8_span(buffer)); + typesetter->lineBreak(); + } + + return typesetter; } - return typesetter; -} - -BookTypesetter::Ptr JournalBooks::createTypesetter () -{ - //TODO: determine page size from layout... - return BookTypesetter::create (240, 320); -} + BookTypesetter::Ptr JournalBooks::createTypesetter() + { + // TODO: determine page size from layout... + return BookTypesetter::create(240, 320); + } } diff --git a/apps/openmw/mwgui/journalbooks.hpp b/apps/openmw/mwgui/journalbooks.hpp index 05eda6e22..1970830ea 100644 --- a/apps/openmw/mwgui/journalbooks.hpp +++ b/apps/openmw/mwgui/journalbooks.hpp @@ -8,29 +8,28 @@ namespace MWGui { - MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text); + MWGui::BookTypesetter::Utf8Span to_utf8_span(std::string_view text); struct JournalBooks { typedef TypesetBook::Ptr Book; JournalViewModel::Ptr mModel; - JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding); + JournalBooks(JournalViewModel::Ptr model, ToUTF8::FromType encoding); - Book createEmptyJournalBook (); - Book createJournalBook (); - Book createTopicBook (uintptr_t topicId); - Book createTopicBook (const std::string& topicId); - Book createQuestBook (const std::string& questName); - Book createTopicIndexBook (); + Book createEmptyJournalBook(); + Book createJournalBook(); + Book createTopicBook(uintptr_t topicId); + Book createQuestBook(std::string_view questName); + Book createTopicIndexBook(); ToUTF8::FromType mEncoding; int mIndexPagesCount; private: - BookTypesetter::Ptr createTypesetter (); - BookTypesetter::Ptr createLatinJournalIndex (); - BookTypesetter::Ptr createCyrillicJournalIndex (); + BookTypesetter::Ptr createTypesetter(); + BookTypesetter::Ptr createLatinJournalIndex(); + BookTypesetter::Ptr createCyrillicJournalIndex(); }; } diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index 6b38cd0d9..57c768aea 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -1,361 +1,356 @@ #include "journalviewmodel.hpp" #include -#include #include +#include #include -#include -#include "../mwbase/world.hpp" -#include "../mwbase/journal.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/journal.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwdialogue/keywordsearch.hpp" -namespace MWGui { - -struct JournalViewModelImpl; - -struct JournalViewModelImpl : JournalViewModel +namespace MWGui { - typedef MWDialogue::KeywordSearch KeywordSearchT; - mutable bool mKeywordSearchLoaded; - mutable KeywordSearchT mKeywordSearch; + struct JournalViewModelImpl; - JournalViewModelImpl () + struct JournalViewModelImpl : JournalViewModel { - mKeywordSearchLoaded = false; - } + typedef MWDialogue::KeywordSearch KeywordSearchT; - virtual ~JournalViewModelImpl () - { - } + mutable bool mKeywordSearchLoaded; + mutable KeywordSearchT mKeywordSearch; - /// \todo replace this nasty BS - static Utf8Span toUtf8Span (std::string const & str) - { - if (str.size () == 0) - return Utf8Span (Utf8Point (nullptr), Utf8Point (nullptr)); + JournalViewModelImpl() { mKeywordSearchLoaded = false; } - Utf8Point point = reinterpret_cast (str.c_str ()); + virtual ~JournalViewModelImpl() {} - return Utf8Span (point, point + str.size ()); - } - - void load () override - { - } - - void unload () override - { - mKeywordSearch.clear (); - mKeywordSearchLoaded = false; - } - - void ensureKeyWordSearchLoaded () const - { - if (!mKeywordSearchLoaded) + /// \todo replace this nasty BS + static Utf8Span toUtf8Span(std::string_view str) { - MWBase::Journal * journal = MWBase::Environment::get().getJournal(); + if (str.size() == 0) + return Utf8Span(Utf8Point(nullptr), Utf8Point(nullptr)); - for(MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd (); ++i) - mKeywordSearch.seed (i->first, intptr_t (&i->second)); + Utf8Point point = reinterpret_cast(str.data()); - mKeywordSearchLoaded = true; + return Utf8Span(point, point + str.size()); } - } - bool isEmpty () const override - { - MWBase::Journal * journal = MWBase::Environment::get().getJournal(); + void load() override {} - return journal->begin () == journal->end (); - } - - template - struct BaseEntry : Interface - { - typedef t_iterator iterator_t; - - iterator_t itr; - JournalViewModelImpl const * mModel; - - BaseEntry (JournalViewModelImpl const * model, iterator_t itr) : - itr (itr), mModel (model), loaded (false) - {} - - virtual ~BaseEntry () {} - - mutable bool loaded; - mutable std::string utf8text; - - typedef std::pair Range; - - // hyperlinks in @link# notation - mutable std::map mHyperLinks; - - virtual std::string getText () const = 0; - - void ensureLoaded () const + void unload() override { - if (!loaded) + mKeywordSearch.clear(); + mKeywordSearchLoaded = false; + } + + void ensureKeyWordSearchLoaded() const + { + if (!mKeywordSearchLoaded) { - mModel->ensureKeyWordSearchLoaded (); + MWBase::Journal* journal = MWBase::Environment::get().getJournal(); - utf8text = getText (); + for (MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd(); ++i) + mKeywordSearch.seed(i->second.getName(), intptr_t(&i->second)); - size_t pos_end = 0; - for(;;) - { - size_t pos_begin = utf8text.find('@'); - if (pos_begin != std::string::npos) - pos_end = utf8text.find('#', pos_begin); - - if (pos_begin != std::string::npos && pos_end != std::string::npos) - { - std::string link = utf8text.substr(pos_begin + 1, pos_end - pos_begin - 1); - const char specialPseudoAsteriskCharacter = 127; - std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*'); - std::string topicName = MWBase::Environment::get().getWindowManager()-> - getTranslationDataStorage().topicStandardForm(link); - - std::string displayName = link; - while (displayName[displayName.size()-1] == '*') - displayName.erase(displayName.size()-1, 1); - - utf8text.replace(pos_begin, pos_end+1-pos_begin, displayName); - - intptr_t value = 0; - if (mModel->mKeywordSearch.containsKeyword(topicName, value)) - mHyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = value; - } - else - break; - } - - loaded = true; + mKeywordSearchLoaded = true; } } - Utf8Span body () const override + bool isEmpty() const override { - ensureLoaded (); + MWBase::Journal* journal = MWBase::Environment::get().getJournal(); - return toUtf8Span (utf8text); + return journal->begin() == journal->end(); } - void visitSpans (std::function < void (TopicId, size_t, size_t)> visitor) const override + template + struct BaseEntry : Interface { - ensureLoaded (); - mModel->ensureKeyWordSearchLoaded (); + typedef t_iterator iterator_t; - if (mHyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) + iterator_t itr; + JournalViewModelImpl const* mModel; + + BaseEntry(JournalViewModelImpl const* model, iterator_t itr) + : itr(itr) + , mModel(model) + , loaded(false) { - size_t formatted = 0; // points to the first character that is not laid out yet - for (std::map::const_iterator it = mHyperLinks.begin(); it != mHyperLinks.end(); ++it) + } + + virtual ~BaseEntry() {} + + mutable bool loaded; + mutable std::string utf8text; + + typedef std::pair Range; + + // hyperlinks in @link# notation + mutable std::map mHyperLinks; + + virtual std::string getText() const = 0; + + void ensureLoaded() const + { + if (!loaded) { - intptr_t topicId = it->second; - if (formatted < it->first.first) - visitor (0, formatted, it->first.first); - visitor (topicId, it->first.first, it->first.second); - formatted = it->first.second; + mModel->ensureKeyWordSearchLoaded(); + + utf8text = getText(); + + size_t pos_end = 0; + for (;;) + { + size_t pos_begin = utf8text.find('@'); + if (pos_begin != std::string::npos) + pos_end = utf8text.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) + { + std::string link = utf8text.substr(pos_begin + 1, pos_end - pos_begin - 1); + const char specialPseudoAsteriskCharacter = 127; + std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*'); + std::string_view topicName = MWBase::Environment::get() + .getWindowManager() + ->getTranslationDataStorage() + .topicStandardForm(link); + + std::string displayName = link; + while (displayName[displayName.size() - 1] == '*') + displayName.erase(displayName.size() - 1, 1); + + utf8text.replace(pos_begin, pos_end + 1 - pos_begin, displayName); + + intptr_t value = 0; + if (mModel->mKeywordSearch.containsKeyword(topicName, value)) + mHyperLinks[std::make_pair(pos_begin, pos_begin + displayName.size())] = value; + } + else + break; + } + + loaded = true; + } + } + + Utf8Span body() const override + { + ensureLoaded(); + + return toUtf8Span(utf8text); + } + + void visitSpans(std::function visitor) const override + { + ensureLoaded(); + mModel->ensureKeyWordSearchLoaded(); + + if (mHyperLinks.size() + && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) + { + size_t formatted = 0; // points to the first character that is not laid out yet + for (std::map::const_iterator it = mHyperLinks.begin(); it != mHyperLinks.end(); + ++it) + { + intptr_t topicId = it->second; + if (formatted < it->first.first) + visitor(0, formatted, it->first.first); + visitor(topicId, it->first.first, it->first.second); + formatted = it->first.second; + } + if (formatted < utf8text.size()) + visitor(0, formatted, utf8text.size()); + } + else + { + std::vector matches; + mModel->mKeywordSearch.highlightKeywords(utf8text.begin(), utf8text.end(), matches); + + std::string::const_iterator i = utf8text.begin(); + for (std::vector::const_iterator it = matches.begin(); it != matches.end(); + ++it) + { + const KeywordSearchT::Match& match = *it; + + if (i != match.mBeg) + visitor(0, i - utf8text.begin(), match.mBeg - utf8text.begin()); + + visitor(match.mValue, match.mBeg - utf8text.begin(), match.mEnd - utf8text.begin()); + + i = match.mEnd; + } + + if (i != utf8text.end()) + visitor(0, i - utf8text.begin(), utf8text.size()); + } + } + }; + + void visitQuestNames(bool active_only, std::function visitor) const override + { + MWBase::Journal* journal = MWBase::Environment::get().getJournal(); + + std::set> visitedQuests; + + // Note that for purposes of the journal GUI, quests are identified by the name, not the ID, so several + // different quest IDs can end up in the same quest log. A quest log should be considered finished + // when any quest ID in that log is finished. + for (MWBase::Journal::TQuestIter i = journal->questBegin(); i != journal->questEnd(); ++i) + { + const MWDialogue::Quest& quest = i->second; + + bool isFinished = false; + for (MWBase::Journal::TQuestIter j = journal->questBegin(); j != journal->questEnd(); ++j) + { + if (quest.getName() == j->second.getName() && j->second.isFinished()) + isFinished = true; + } + + if (active_only && isFinished) + continue; + + // Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal. + // Note that even with Tribunal, some quests still don't have quest names. I'm assuming those are not + // supposed to appear in the quest book. + if (!quest.getName().empty()) + { + // Don't list the same quest name twice + if (visitedQuests.find(quest.getName()) != visitedQuests.end()) + continue; + + visitor(quest.getName(), isFinished); + + visitedQuests.emplace(quest.getName()); + } + } + } + + template + struct JournalEntryImpl : BaseEntry + { + using BaseEntry::itr; + + mutable std::string timestamp_buffer; + + JournalEntryImpl(JournalViewModelImpl const* model, iterator_t itr) + : BaseEntry(model, itr) + { + } + + std::string getText() const override { return itr->getText(); } + + Utf8Span timestamp() const override + { + if (timestamp_buffer.empty()) + { + std::string dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}"); + + std::ostringstream os; + + os << itr->mDayOfMonth << ' ' << MWBase::Environment::get().getWorld()->getMonthName(itr->mMonth) + << " (" << dayStr << " " << (itr->mDay) << ')'; + + timestamp_buffer = os.str(); + } + + return toUtf8Span(timestamp_buffer); + } + }; + + void visitJournalEntries( + std::string_view questName, std::function visitor) const override + { + MWBase::Journal* journal = MWBase::Environment::get().getJournal(); + + if (!questName.empty()) + { + std::vector quests; + for (MWBase::Journal::TQuestIter questIt = journal->questBegin(); questIt != journal->questEnd(); + ++questIt) + { + if (Misc::StringUtils::ciEqual(questIt->second.getName(), questName)) + quests.push_back(&questIt->second); + } + + for (MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end(); ++i) + { + for (std::vector::iterator questIt = quests.begin(); + questIt != quests.end(); ++questIt) + { + MWDialogue::Quest const* quest = *questIt; + for (MWDialogue::Topic::TEntryIter j = quest->begin(); j != quest->end(); ++j) + { + if (i->mInfoId == j->mInfoId) + visitor(JournalEntryImpl(this, i)); + } + } } - if (formatted < utf8text.size()) - visitor (0, formatted, utf8text.size()); } else { - std::vector matches; - mModel->mKeywordSearch.highlightKeywords(utf8text.begin(), utf8text.end(), matches); - - std::string::const_iterator i = utf8text.begin (); - for (std::vector::const_iterator it = matches.begin(); it != matches.end(); ++it) - { - const KeywordSearchT::Match& match = *it; - - if (i != match.mBeg) - visitor (0, i - utf8text.begin (), match.mBeg - utf8text.begin ()); - - visitor (match.mValue, match.mBeg - utf8text.begin (), match.mEnd - utf8text.begin ()); - - i = match.mEnd; - } - - if (i != utf8text.end ()) - visitor (0, i - utf8text.begin (), utf8text.size ()); + for (MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end(); ++i) + visitor(JournalEntryImpl(this, i)); } } - }; - - void visitQuestNames (bool active_only, std::function visitor) const override - { - MWBase::Journal * journal = MWBase::Environment::get ().getJournal (); - - std::set visitedQuests; - - // Note that for purposes of the journal GUI, quests are identified by the name, not the ID, so several - // different quest IDs can end up in the same quest log. A quest log should be considered finished - // when any quest ID in that log is finished. - for (MWBase::Journal::TQuestIter i = journal->questBegin (); i != journal->questEnd (); ++i) + void visitTopicName(TopicId topicId, std::function visitor) const override { - const MWDialogue::Quest& quest = i->second; + MWDialogue::Topic const& topic = *reinterpret_cast(topicId); + visitor(toUtf8Span(topic.getName())); + } - bool isFinished = false; - for (MWBase::Journal::TQuestIter j = journal->questBegin (); j != journal->questEnd (); ++j) + void visitTopicNamesStartingWith( + Utf8Stream::UnicodeChar character, std::function visitor) const override + { + MWBase::Journal* journal = MWBase::Environment::get().getJournal(); + + for (MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd(); ++i) { - if (quest.getName() == j->second.getName() && j->second.isFinished()) - isFinished = true; - } + Utf8Stream stream(i->second.getName()); + Utf8Stream::UnicodeChar first = Utf8Stream::toLowerUtf8(stream.peek()); - if (active_only && isFinished) - continue; - - // Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal. - // Note that even with Tribunal, some quests still don't have quest names. I'm assuming those are not supposed - // to appear in the quest book. - if (!quest.getName().empty()) - { - // Don't list the same quest name twice - if (visitedQuests.find(quest.getName()) != visitedQuests.end()) + if (first != Utf8Stream::toLowerUtf8(character)) continue; - visitor (quest.getName(), isFinished); - - visitedQuests.insert(quest.getName()); + visitor(i->second.getName()); } } - } - template - struct JournalEntryImpl : BaseEntry - { - using BaseEntry ::itr; - - mutable std::string timestamp_buffer; - - JournalEntryImpl (JournalViewModelImpl const * model, iterator_t itr) : - BaseEntry (model, itr) - {} - - std::string getText () const override + struct TopicEntryImpl : BaseEntry { - return itr->getText(); - } + MWDialogue::Topic const& mTopic; - Utf8Span timestamp () const override - { - if (timestamp_buffer.empty ()) + TopicEntryImpl(JournalViewModelImpl const* model, MWDialogue::Topic const& topic, iterator_t itr) + : BaseEntry(model, itr) + , mTopic(topic) { - std::string dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}"); - - std::ostringstream os; - - os - << itr->mDayOfMonth << ' ' - << MWBase::Environment::get().getWorld()->getMonthName (itr->mMonth) - << " (" << dayStr << " " << (itr->mDay) << ')'; - - timestamp_buffer = os.str (); } - return toUtf8Span (timestamp_buffer); + std::string getText() const override { return itr->getText(); } + + Utf8Span source() const override { return toUtf8Span(itr->mActorName); } + }; + + void visitTopicEntries(TopicId topicId, std::function visitor) const override + { + typedef MWDialogue::Topic::TEntryIter iterator_t; + + MWDialogue::Topic const& topic = *reinterpret_cast(topicId); + + for (iterator_t i = topic.begin(); i != topic.end(); ++i) + visitor(TopicEntryImpl(this, topic, i)); } }; - void visitJournalEntries (const std::string& questName, std::function visitor) const override + JournalViewModel::Ptr JournalViewModel::create() { - MWBase::Journal * journal = MWBase::Environment::get().getJournal(); - - if (!questName.empty()) - { - std::vector quests; - for (MWBase::Journal::TQuestIter questIt = journal->questBegin(); questIt != journal->questEnd(); ++questIt) - { - if (Misc::StringUtils::ciEqual(questIt->second.getName(), questName)) - quests.push_back(&questIt->second); - } - - for(MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end (); ++i) - { - for (std::vector::iterator questIt = quests.begin(); questIt != quests.end(); ++questIt) - { - MWDialogue::Quest const* quest = *questIt; - for (MWDialogue::Topic::TEntryIter j = quest->begin (); j != quest->end (); ++j) - { - if (i->mInfoId == j->mInfoId) - visitor (JournalEntryImpl (this, i)); - } - } - } - } - else - { - for(MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end (); ++i) - visitor (JournalEntryImpl (this, i)); - } + return std::make_shared(); } - void visitTopicName (TopicId topicId, std::function visitor) const override - { - MWDialogue::Topic const & topic = * reinterpret_cast (topicId); - visitor (toUtf8Span (topic.getName())); - } - - void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const override - { - MWBase::Journal * journal = MWBase::Environment::get().getJournal(); - - for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) - { - Utf8Stream stream (i->first.c_str()); - Utf8Stream::UnicodeChar first = Misc::StringUtils::toLowerUtf8(stream.peek()); - - if (first != Misc::StringUtils::toLowerUtf8(character)) - continue; - - visitor (i->second.getName()); - } - } - - struct TopicEntryImpl : BaseEntry - { - MWDialogue::Topic const & mTopic; - - TopicEntryImpl (JournalViewModelImpl const * model, MWDialogue::Topic const & topic, iterator_t itr) : - BaseEntry (model, itr), mTopic (topic) - {} - - std::string getText () const override - { - return itr->getText(); - } - - Utf8Span source () const override - { - return toUtf8Span (itr->mActorName); - } - - }; - - void visitTopicEntries (TopicId topicId, std::function visitor) const override - { - typedef MWDialogue::Topic::TEntryIter iterator_t; - - MWDialogue::Topic const & topic = * reinterpret_cast (topicId); - - for (iterator_t i = topic.begin (); i != topic.end (); ++i) - visitor (TopicEntryImpl (this, topic, i)); - } -}; - -JournalViewModel::Ptr JournalViewModel::create () -{ - return std::make_shared (); -} - } diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 3a9372130..376a8e115 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -1,10 +1,10 @@ #ifndef MWGUI_JOURNALVIEWMODEL_HPP #define MWGUI_JOURNALVIEWMODEL_HPP -#include -#include +#include #include -#include +#include +#include #include @@ -18,12 +18,12 @@ namespace MWGui /// game data store. struct JournalViewModel { - typedef std::shared_ptr Ptr; + typedef std::shared_ptr Ptr; typedef intptr_t QuestId; typedef intptr_t TopicId; - typedef uint8_t const * Utf8Point; - typedef std::pair Utf8Span; + typedef uint8_t const* Utf8Point; + typedef std::pair Utf8Span; /// The base interface for both journal entries and topics. struct Entry @@ -33,12 +33,12 @@ namespace MWGui /// This function returns a borrowed reference to the body of the /// journal entry. The returned reference becomes invalid when the /// entry is destroyed. - virtual Utf8Span body () const = 0; + virtual Utf8Span body() const = 0; /// Visits each subset of text in the body, delivering the beginning /// and end of the span relative to the body, and a valid topic ID if /// the span represents a keyword, or zero if not. - virtual void visitSpans (std::function visitor) const = 0; + virtual void visitSpans(std::function visitor) const = 0; virtual ~Entry() = default; }; @@ -48,7 +48,7 @@ namespace MWGui { /// Returns a pre-formatted span of UTF8 encoded text representing /// the name of the NPC this portion of dialog was heard from. - virtual Utf8Span source () const = 0; + virtual Utf8Span source() const = 0; virtual ~TopicEntry() = default; }; @@ -58,38 +58,40 @@ namespace MWGui { /// Returns a pre-formatted span of UTF8 encoded text representing /// the in-game date this entry was added to the journal. - virtual Utf8Span timestamp () const = 0; + virtual Utf8Span timestamp() const = 0; virtual ~JournalEntry() = default; }; /// called prior to journal opening - virtual void load () = 0; + virtual void load() = 0; /// called prior to journal closing - virtual void unload () = 0; + virtual void unload() = 0; /// returns true if their are no journal entries to display - virtual bool isEmpty () const = 0; + virtual bool isEmpty() const = 0; /// walks the active and optionally completed, quests providing the name and completed status - virtual void visitQuestNames (bool active_only, std::function visitor) const = 0; + virtual void visitQuestNames(bool active_only, std::function visitor) const = 0; /// walks over the journal entries related to all quests with the given name /// If \a questName is empty, simply visits all journal entries - virtual void visitJournalEntries (const std::string& questName, std::function visitor) const = 0; + virtual void visitJournalEntries( + std::string_view questName, std::function visitor) const = 0; /// provides the name of the topic specified by its id - virtual void visitTopicName (TopicId topicId, std::function visitor) const = 0; + virtual void visitTopicName(TopicId topicId, std::function visitor) const = 0; /// walks over the topics whose names start with the character - virtual void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const = 0; + virtual void visitTopicNamesStartingWith( + Utf8Stream::UnicodeChar character, std::function 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; + virtual void visitTopicEntries(TopicId topicId, std::function visitor) const = 0; // create an instance of the default journal view model implementation - static Ptr create (); + static Ptr create(); virtual ~JournalViewModel() = default; }; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 96c42549a..46adcd977 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -5,45 +5,45 @@ #include #include -#include #include #include +#include -#include +#include #include #include #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/journal.hpp" +#include "../mwbase/windowmanager.hpp" #include "bookpage.hpp" -#include "windowbase.hpp" -#include "journalviewmodel.hpp" #include "journalbooks.hpp" +#include "journalviewmodel.hpp" +#include "windowbase.hpp" namespace { - static char const OptionsOverlay [] = "OptionsOverlay"; - static char const OptionsBTN [] = "OptionsBTN"; - static char const PrevPageBTN [] = "PrevPageBTN"; - static char const NextPageBTN [] = "NextPageBTN"; - static char const CloseBTN [] = "CloseBTN"; - static char const JournalBTN [] = "JournalBTN"; - static char const TopicsBTN [] = "TopicsBTN"; - static char const QuestsBTN [] = "QuestsBTN"; - static char const CancelBTN [] = "CancelBTN"; - static char const ShowAllBTN [] = "ShowAllBTN"; - static char const ShowActiveBTN [] = "ShowActiveBTN"; - static char const PageOneNum [] = "PageOneNum"; - static char const PageTwoNum [] = "PageTwoNum"; - static char const TopicsList [] = "TopicsList"; - static char const QuestsList [] = "QuestsList"; - static char const LeftBookPage [] = "LeftBookPage"; - static char const RightBookPage [] = "RightBookPage"; - static char const LeftTopicIndex [] = "LeftTopicIndex"; - static char const CenterTopicIndex [] = "CenterTopicIndex"; - static char const RightTopicIndex [] = "RightTopicIndex"; + static constexpr std::string_view OptionsOverlay = "OptionsOverlay"; + static constexpr std::string_view OptionsBTN = "OptionsBTN"; + static constexpr std::string_view PrevPageBTN = "PrevPageBTN"; + static constexpr std::string_view NextPageBTN = "NextPageBTN"; + static constexpr std::string_view CloseBTN = "CloseBTN"; + static constexpr std::string_view JournalBTN = "JournalBTN"; + static constexpr std::string_view TopicsBTN = "TopicsBTN"; + static constexpr std::string_view QuestsBTN = "QuestsBTN"; + static constexpr std::string_view CancelBTN = "CancelBTN"; + static constexpr std::string_view ShowAllBTN = "ShowAllBTN"; + static constexpr std::string_view ShowActiveBTN = "ShowActiveBTN"; + static constexpr std::string_view PageOneNum = "PageOneNum"; + static constexpr std::string_view PageTwoNum = "PageTwoNum"; + static constexpr std::string_view TopicsList = "TopicsList"; + static constexpr std::string_view QuestsList = "QuestsList"; + static constexpr std::string_view LeftBookPage = "LeftBookPage"; + static constexpr std::string_view RightBookPage = "RightBookPage"; + static constexpr std::string_view LeftTopicIndex = "LeftTopicIndex"; + static constexpr std::string_view CenterTopicIndex = "CenterTopicIndex"; + static constexpr std::string_view RightTopicIndex = "RightTopicIndex"; struct JournalWindowImpl : MWGui::JournalBooks, MWGui::JournalWindow { @@ -53,7 +53,7 @@ namespace Book mBook; }; - typedef std::stack DisplayStateStack; + typedef std::stack DisplayStateStack; DisplayStateStack mStates; Book mTopicIndexBook; @@ -63,66 +63,58 @@ namespace bool mAllQuests; template - T * getWidget (char const * name) + T* getWidget(std::string_view name) { - T * widget; - WindowBase::getWidget (widget, name); + T* widget; + WindowBase::getWidget(widget, name); return widget; } template - void setText (char const * name, value_type const & value) + void setText(std::string_view name, value_type const& value) { - getWidget (name) -> - setCaption (MyGUI::utility::toString (value)); + getWidget(name)->setCaption(MyGUI::utility::toString(value)); } - void setVisible (char const * name, bool visible) + void setVisible(std::string_view name, bool visible) { getWidget(name)->setVisible(visible); } + + void adviseButtonClick(std::string_view name, void (JournalWindowImpl::*handler)(MyGUI::Widget*)) { - getWidget (name) -> - setVisible (visible); + getWidget(name)->eventMouseButtonClick += newDelegate(this, handler); } - void adviseButtonClick (char const * name, void (JournalWindowImpl::*Handler) (MyGUI::Widget* _sender)) + void adviseKeyPress( + std::string_view name, void (JournalWindowImpl::*handler)(MyGUI::Widget*, MyGUI::KeyCode, MyGUI::Char)) { - getWidget (name) -> - eventMouseButtonClick += newDelegate(this, Handler); + getWidget(name)->eventKeyButtonPressed += newDelegate(this, handler); } - void adviseKeyPress (char const * name, void (JournalWindowImpl::*Handler) (MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character)) - { - getWidget (name) -> - eventKeyButtonPressed += newDelegate(this, Handler); - } + MWGui::BookPage* getPage(std::string_view name) { return getWidget(name); } - MWGui::BookPage* getPage (char const * name) - { - return getWidget (name); - } - - JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) - : JournalBooks (Model, encoding), JournalWindow() + JournalWindowImpl(MWGui::JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) + : JournalBooks(Model, encoding) + , JournalWindow() { center(); - adviseButtonClick (OptionsBTN, &JournalWindowImpl::notifyOptions ); - adviseButtonClick (PrevPageBTN, &JournalWindowImpl::notifyPrevPage ); - adviseButtonClick (NextPageBTN, &JournalWindowImpl::notifyNextPage ); - adviseButtonClick (CloseBTN, &JournalWindowImpl::notifyClose ); - adviseButtonClick (JournalBTN, &JournalWindowImpl::notifyJournal ); + adviseButtonClick(OptionsBTN, &JournalWindowImpl::notifyOptions); + adviseButtonClick(PrevPageBTN, &JournalWindowImpl::notifyPrevPage); + adviseButtonClick(NextPageBTN, &JournalWindowImpl::notifyNextPage); + adviseButtonClick(CloseBTN, &JournalWindowImpl::notifyClose); + adviseButtonClick(JournalBTN, &JournalWindowImpl::notifyJournal); - adviseButtonClick (TopicsBTN, &JournalWindowImpl::notifyTopics ); - adviseButtonClick (QuestsBTN, &JournalWindowImpl::notifyQuests ); - adviseButtonClick (CancelBTN, &JournalWindowImpl::notifyCancel ); + adviseButtonClick(TopicsBTN, &JournalWindowImpl::notifyTopics); + adviseButtonClick(QuestsBTN, &JournalWindowImpl::notifyQuests); + adviseButtonClick(CancelBTN, &JournalWindowImpl::notifyCancel); - adviseButtonClick (ShowAllBTN, &JournalWindowImpl::notifyShowAll ); - adviseButtonClick (ShowActiveBTN, &JournalWindowImpl::notifyShowActive); + adviseButtonClick(ShowAllBTN, &JournalWindowImpl::notifyShowAll); + adviseButtonClick(ShowActiveBTN, &JournalWindowImpl::notifyShowActive); - adviseKeyPress (OptionsBTN, &JournalWindowImpl::notifyKeyPress); - adviseKeyPress (PrevPageBTN, &JournalWindowImpl::notifyKeyPress); - adviseKeyPress (NextPageBTN, &JournalWindowImpl::notifyKeyPress); - adviseKeyPress (CloseBTN, &JournalWindowImpl::notifyKeyPress); - adviseKeyPress (JournalBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress(OptionsBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress(PrevPageBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress(NextPageBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress(CloseBTN, &JournalWindowImpl::notifyKeyPress); + adviseKeyPress(JournalBTN, &JournalWindowImpl::notifyKeyPress); Gui::MWList* list = getWidget(QuestsList); list->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyQuestClicked); @@ -131,25 +123,24 @@ namespace topicsList->eventItemSelected += MyGUI::newDelegate(this, &JournalWindowImpl::notifyTopicSelected); { - MWGui::BookPage::ClickCallback callback; - - callback = std::bind (&JournalWindowImpl::notifyTopicClicked, this, std::placeholders::_1); + MWGui::BookPage::ClickCallback callback = [this](intptr_t linkId) { notifyTopicClicked(linkId); }; - getPage (LeftBookPage)->adviseLinkClicked (callback); - getPage (RightBookPage)->adviseLinkClicked (callback); + getPage(LeftBookPage)->adviseLinkClicked(callback); + getPage(RightBookPage)->adviseLinkClicked(callback); - getPage (LeftBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); - getPage (RightBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); + getPage(LeftBookPage)->eventMouseWheel + += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); + getPage(RightBookPage)->eventMouseWheel + += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); } { - MWGui::BookPage::ClickCallback callback; - - callback = std::bind(&JournalWindowImpl::notifyIndexLinkClicked, this, std::placeholders::_1); + MWGui::BookPage::ClickCallback callback + = [this](MWGui::TypesetBook::InteractiveId index) { notifyIndexLinkClicked(index); }; - getPage (LeftTopicIndex)->adviseLinkClicked (callback); - getPage (CenterTopicIndex)->adviseLinkClicked (callback); - getPage (RightTopicIndex)->adviseLinkClicked (callback); + getPage(LeftTopicIndex)->adviseLinkClicked(callback); + getPage(CenterTopicIndex)->adviseLinkClicked(callback); + getPage(RightTopicIndex)->adviseLinkClicked(callback); } adjustButton(PrevPageBTN); @@ -167,8 +158,9 @@ namespace 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)*nextButtonScale,nextButton->getSize().height*nextButtonScale)); + nextButton->setSize(64 - 7, nextButton->getSize().height); + nextButton->setImageCoord( + MyGUI::IntCoord(0, 0, (64 - 7) * nextButtonScale, nextButton->getSize().height * nextButtonScale)); } if (!questList) @@ -203,20 +195,26 @@ namespace adjustButton(TopicsBTN); int topicsWidth = getWidget(TopicsBTN)->getSize().width; int cancelLeft = getWidget(CancelBTN)->getPosition().left; - int cancelRight = getWidget(CancelBTN)->getPosition().left + getWidget(CancelBTN)->getSize().width; + int cancelRight = getWidget(CancelBTN)->getPosition().left + + getWidget(CancelBTN)->getSize().width; - getWidget(QuestsBTN)->setPosition(cancelRight, getWidget(QuestsBTN)->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. + // 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); + 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); + getWidget(TopicsBTN)->setPosition( + questLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); } } @@ -228,28 +226,28 @@ namespace void onOpen() override { - if (!MWBase::Environment::get().getWindowManager ()->getJournalAllowed ()) + if (!MWBase::Environment::get().getWindowManager()->getJournalAllowed()) { - MWBase::Environment::get().getWindowManager()->popGuiMode (); + MWBase::Environment::get().getWindowManager()->popGuiMode(); } - mModel->load (); + mModel->load(); - setBookMode (); + setBookMode(); Book journalBook; - if (mModel->isEmpty ()) - journalBook = createEmptyJournalBook (); + if (mModel->isEmpty()) + journalBook = createEmptyJournalBook(); else - journalBook = createJournalBook (); + journalBook = createJournalBook(); - pushBook (journalBook, 0); + pushBook(journalBook, 0); // fast forward to the last page - if (!mStates.empty ()) + if (!mStates.empty()) { - unsigned int & page = mStates.top ().mPage; - page = mStates.top().mBook->pageCount()-1; - if (page%2) + unsigned int& page = mStates.top().mPage; + page = mStates.top().mBook->pageCount() - 1; + if (page % 2) --page; } updateShowingPages(); @@ -259,57 +257,54 @@ namespace void onClose() override { - mModel->unload (); + mModel->unload(); - getPage (LeftBookPage)->showPage (Book (), 0); - getPage (RightBookPage)->showPage (Book (), 0); + getPage(LeftBookPage)->showPage(Book(), 0); + getPage(RightBookPage)->showPage(Book(), 0); - while (!mStates.empty ()) - mStates.pop (); + while (!mStates.empty()) + mStates.pop(); - mTopicIndexBook.reset (); + mTopicIndexBook.reset(); } - void setVisible (bool newValue) override - { - WindowBase::setVisible (newValue); - } + void setVisible(bool newValue) override { WindowBase::setVisible(newValue); } - void setBookMode () + void setBookMode() { mOptionsMode = false; mTopicsMode = false; - setVisible (OptionsBTN, true); - setVisible (OptionsOverlay, false); + setVisible(OptionsBTN, true); + setVisible(OptionsOverlay, false); - updateShowingPages (); - updateCloseJournalButton (); + updateShowingPages(); + updateCloseJournalButton(); } - void setOptionsMode () + void setOptionsMode() { mOptionsMode = true; mTopicsMode = false; - setVisible (OptionsBTN, false); - setVisible (OptionsOverlay, true); + setVisible(OptionsBTN, false); + setVisible(OptionsOverlay, true); - setVisible (PrevPageBTN, false); - setVisible (NextPageBTN, false); - setVisible (CloseBTN, false); - setVisible (JournalBTN, false); + setVisible(PrevPageBTN, false); + setVisible(NextPageBTN, false); + setVisible(CloseBTN, false); + setVisible(JournalBTN, false); - setVisible (TopicsList, false); - setVisible (QuestsList, mQuestMode); - setVisible (LeftTopicIndex, !mQuestMode); - setVisible (CenterTopicIndex, !mQuestMode); - setVisible (RightTopicIndex, !mQuestMode); - setVisible (ShowAllBTN, mQuestMode && !mAllQuests); - setVisible (ShowActiveBTN, mQuestMode && mAllQuests); + setVisible(TopicsList, false); + setVisible(QuestsList, mQuestMode); + setVisible(LeftTopicIndex, !mQuestMode); + setVisible(CenterTopicIndex, !mQuestMode); + setVisible(RightTopicIndex, !mQuestMode); + setVisible(ShowAllBTN, mQuestMode && !mAllQuests); + setVisible(ShowActiveBTN, mQuestMode && mAllQuests); - //TODO: figure out how to make "options" page overlay book page - // correctly, so that text may show underneath - getPage (RightBookPage)->showPage (Book (), 0); + // TODO: figure out how to make "options" page overlay book page + // correctly, so that text may show underneath + getPage(RightBookPage)->showPage(Book(), 0); // If in quest mode, ensure the quest list is updated if (mQuestMode) @@ -318,48 +313,48 @@ namespace notifyTopics(getWidget(TopicsList)); } - void pushBook (Book book, unsigned int page) + void pushBook(Book book, unsigned int page) { DisplayState bs; bs.mPage = page; bs.mBook = book; - mStates.push (bs); - updateShowingPages (); - updateCloseJournalButton (); + mStates.push(bs); + updateShowingPages(); + updateCloseJournalButton(); } - void replaceBook (Book book, unsigned int page) + void replaceBook(Book book, unsigned int page) { - assert (!mStates.empty ()); - mStates.top ().mBook = book; - mStates.top ().mPage = page; - updateShowingPages (); + assert(!mStates.empty()); + mStates.top().mBook = book; + mStates.top().mPage = page; + updateShowingPages(); } - void popBook () + void popBook() { - mStates.pop (); - updateShowingPages (); - updateCloseJournalButton (); + mStates.pop(); + updateShowingPages(); + updateCloseJournalButton(); } - void updateCloseJournalButton () + void updateCloseJournalButton() { - setVisible (CloseBTN, mStates.size () < 2); - setVisible (JournalBTN, mStates.size () >= 2); + setVisible(CloseBTN, mStates.size() < 2); + setVisible(JournalBTN, mStates.size() >= 2); } - void updateShowingPages () + void updateShowingPages() { Book book; unsigned int page; unsigned int relPages; - if (!mStates.empty ()) + if (!mStates.empty()) { - book = mStates.top ().mBook; - page = mStates.top ().mPage; - relPages = book->pageCount () - page; + book = mStates.top().mBook; + page = mStates.top().mPage; + relPages = book->pageCount() - page; } else { @@ -381,14 +376,14 @@ namespace else if (focus == prevPageBtn && !prevPageVisible && nextPageVisible) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nextPageBtn); - setVisible (PageOneNum, relPages > 0); - setVisible (PageTwoNum, relPages > 1); + setVisible(PageOneNum, relPages > 0); + setVisible(PageTwoNum, relPages > 1); - getPage (LeftBookPage)->showPage ((relPages > 0) ? book : Book (), page+0); - getPage (RightBookPage)->showPage ((relPages > 0) ? book : Book (), page+1); + getPage(LeftBookPage)->showPage((relPages > 0) ? book : Book(), page + 0); + getPage(RightBookPage)->showPage((relPages > 0) ? book : Book(), page + 1); - setText (PageOneNum, page + 1); - setText (PageTwoNum, page + 2); + setText(PageOneNum, page + 1); + setText(PageTwoNum, page + 2); } void notifyKeyPress(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character) @@ -399,90 +394,91 @@ namespace notifyNextPage(sender); } - void notifyTopicClicked (intptr_t linkId) + void notifyTopicClicked(intptr_t linkId) { - Book topicBook = createTopicBook (linkId); + Book topicBook = createTopicBook(linkId); - if (mStates.size () > 1) - replaceBook (topicBook, 0); + if (mStates.size() > 1) + replaceBook(topicBook, 0); else - pushBook (topicBook, 0); + pushBook(topicBook, 0); - setVisible (OptionsOverlay, false); - setVisible (OptionsBTN, true); - setVisible (JournalBTN, true); + setVisible(OptionsOverlay, false); + setVisible(OptionsBTN, true); + setVisible(JournalBTN, true); mOptionsMode = false; mTopicsMode = false; - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } - void notifyTopicSelected (const std::string& topic, int id) + void notifyTopicSelected(const std::string& topicIdString, int id) { + ESM::RefId topic = ESM::RefId::stringRefId(topicIdString); const MWBase::Journal* journal = MWBase::Environment::get().getJournal(); intptr_t topicId = 0; /// \todo get rid of intptr ids - for(MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd (); ++i) + for (MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd(); ++i) { - if (Misc::StringUtils::ciEqual(i->first, topic)) - topicId = intptr_t (&i->second); + if (i->first == topic) + topicId = intptr_t(&i->second); } notifyTopicClicked(topicId); } - void notifyQuestClicked (const std::string& name, int id) + void notifyQuestClicked(const std::string& name, int id) { - Book book = createQuestBook (name); + Book book = createQuestBook(name); - if (mStates.size () > 1) - replaceBook (book, 0); + if (mStates.size() > 1) + replaceBook(book, 0); else - pushBook (book, 0); + pushBook(book, 0); - setVisible (OptionsOverlay, false); - setVisible (OptionsBTN, true); - setVisible (JournalBTN, true); + setVisible(OptionsOverlay, false); + setVisible(OptionsBTN, true); + setVisible(JournalBTN, true); mOptionsMode = false; - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } void notifyOptions(MyGUI::Widget* _sender) { - setOptionsMode (); + setOptionsMode(); if (!mTopicIndexBook) - mTopicIndexBook = createTopicIndexBook (); + mTopicIndexBook = createTopicIndexBook(); if (mIndexPagesCount == 3) { - getPage (LeftTopicIndex)->showPage (mTopicIndexBook, 0); - getPage (CenterTopicIndex)->showPage (mTopicIndexBook, 1); - getPage (RightTopicIndex)->showPage (mTopicIndexBook, 2); + getPage(LeftTopicIndex)->showPage(mTopicIndexBook, 0); + getPage(CenterTopicIndex)->showPage(mTopicIndexBook, 1); + getPage(RightTopicIndex)->showPage(mTopicIndexBook, 2); } else { - getPage (LeftTopicIndex)->showPage (mTopicIndexBook, 0); - getPage (RightTopicIndex)->showPage (mTopicIndexBook, 1); + getPage(LeftTopicIndex)->showPage(mTopicIndexBook, 0); + getPage(RightTopicIndex)->showPage(mTopicIndexBook, 1); } } void notifyJournal(MyGUI::Widget* _sender) { - assert (mStates.size () > 1); - popBook (); + assert(mStates.size() > 1); + popBook(); - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } - void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId index) + void notifyIndexLinkClicked(MWGui::TypesetBook::InteractiveId index) { - setVisible (LeftTopicIndex, false); - setVisible (CenterTopicIndex, false); - setVisible (RightTopicIndex, false); - setVisible (TopicsList, true); + setVisible(LeftTopicIndex, false); + setVisible(CenterTopicIndex, false); + setVisible(RightTopicIndex, false); + setVisible(TopicsList, true); mTopicsMode = true; @@ -495,40 +491,43 @@ namespace list->adjustSize(); - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } void notifyTopics(MyGUI::Widget* _sender) { mQuestMode = false; mTopicsMode = false; - setVisible (LeftTopicIndex, true); - setVisible (CenterTopicIndex, true); - setVisible (RightTopicIndex, true); - setVisible (TopicsList, false); - setVisible (QuestsList, false); - setVisible (ShowAllBTN, false); - setVisible (ShowActiveBTN, false); + setVisible(LeftTopicIndex, true); + setVisible(CenterTopicIndex, true); + setVisible(RightTopicIndex, true); + setVisible(TopicsList, false); + setVisible(QuestsList, false); + setVisible(ShowAllBTN, false); + setVisible(ShowActiveBTN, false); - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } struct AddNamesToList { - AddNamesToList(Gui::MWList* list) : mList(list) {} + AddNamesToList(Gui::MWList* list) + : mList(list) + { + } Gui::MWList* mList; - void operator () (const std::string& name, bool finished=false) - { - mList->addItem(name); - } + void operator()(std::string_view name, bool finished = false) { mList->addItem(name); } }; struct SetNamesInactive { - SetNamesInactive(Gui::MWList* list) : mList(list) {} + SetNamesInactive(Gui::MWList* list) + : mList(list) + { + } Gui::MWList* mList; - void operator () (const std::string& name, bool finished) + void operator()(std::string_view name, bool finished) { if (finished) { @@ -541,13 +540,13 @@ namespace { mQuestMode = true; - setVisible (LeftTopicIndex, false); - setVisible (CenterTopicIndex, false); - setVisible (RightTopicIndex, false); - setVisible (TopicsList, false); - setVisible (QuestsList, true); - setVisible (ShowAllBTN, !mAllQuests); - setVisible (ShowActiveBTN, mAllQuests); + setVisible(LeftTopicIndex, false); + setVisible(CenterTopicIndex, false); + setVisible(RightTopicIndex, false); + setVisible(TopicsList, false); + setVisible(QuestsList, true); + setVisible(ShowAllBTN, !mAllQuests); + setVisible(ShowActiveBTN, mAllQuests); Gui::MWList* list = getWidget(QuestsList); list->clear(); @@ -556,6 +555,7 @@ namespace mModel->visitQuestNames(!mAllQuests, add); + list->sort(); list->adjustSize(); if (mAllQuests) @@ -564,7 +564,7 @@ namespace mModel->visitQuestNames(false, setInactive); } - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } void notifyShowAll(MyGUI::Widget* _sender) @@ -588,15 +588,14 @@ namespace else { setBookMode(); - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); } - } void notifyClose(MyGUI::Widget* _sender) { - MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); - winMgr->playSound("book close"); + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); + winMgr->playSound(ESM::RefId::stringRefId("book close")); winMgr->popGuiMode(); } @@ -612,17 +611,17 @@ namespace { if (mOptionsMode) return; - if (!mStates.empty ()) + if (!mStates.empty()) { - unsigned int & page = mStates.top ().mPage; - Book book = mStates.top ().mBook; + unsigned int& page = mStates.top().mPage; + Book book = mStates.top().mBook; - if (page+2 < book->pageCount()) + if (page + 2 < book->pageCount()) { - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); page += 2; - updateShowingPages (); + updateShowingPages(); } } } @@ -631,16 +630,16 @@ namespace { if (mOptionsMode) return; - if (!mStates.empty ()) + if (!mStates.empty()) { - unsigned int & page = mStates.top ().mPage; + unsigned int& page = mStates.top().mPage; - if(page >= 2) + if (page >= 2) { - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page")); page -= 2; - updateShowingPages (); + updateShowingPages(); } } } @@ -648,13 +647,13 @@ namespace } // glue the implementation to the interface -MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) +std::unique_ptr MWGui::JournalWindow::create( + JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding) { - return new JournalWindowImpl (Model, questList, encoding); + return std::make_unique(Model, questList, encoding); } MWGui::JournalWindow::JournalWindow() : BookWindowBase("openmw_journal.layout") { - } diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index c18e6e4c0..1c73e37a3 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -7,7 +7,10 @@ #include -namespace MWBase { class WindowManager; } +namespace MWBase +{ + class WindowManager; +} namespace MWGui { @@ -18,13 +21,14 @@ namespace MWGui JournalWindow(); /// construct a new instance of the one JournalWindow implementation - static JournalWindow * create (std::shared_ptr Model, bool questList, ToUTF8::FromType encoding); + static std::unique_ptr create( + std::shared_ptr Model, bool questList, ToUTF8::FromType encoding); /// destroy this instance of the JournalWindow implementation - virtual ~JournalWindow () {} + virtual ~JournalWindow() {} /// show/hide the journal window - void setVisible (bool newValue) override = 0; + void setVisible(bool newValue) override = 0; }; } diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index f7d54adf6..0760d3616 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -1,13 +1,13 @@ #include "keyboardnavigation.hpp" +#include #include #include -#include -#include #include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -20,103 +20,42 @@ */ #include "../mwbase/windowmanager.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" namespace MWGui { -bool shouldAcceptKeyFocus(MyGUI::Widget* w) -{ - return w && !w->castType(false) && w->getInheritedEnabled() && w->getInheritedVisible() && w->getVisible() && w->getEnabled(); -} - -/// Recursively get all child widgets that accept keyboard input -void getKeyFocusWidgets(MyGUI::Widget* parent, std::vector& results) -{ - assert(parent != nullptr); - - if (!parent->getVisible() || !parent->getEnabled()) - return; - - MyGUI::EnumeratorWidgetPtr enumerator = parent->getEnumerator(); - while (enumerator.next()) + bool shouldAcceptKeyFocus(MyGUI::Widget* w) { - MyGUI::Widget* w = enumerator.current(); - if (!w->getVisible() || !w->getEnabled()) - continue; - if (w->getNeedKeyFocus() && shouldAcceptKeyFocus(w)) - results.push_back(w); - else - getKeyFocusWidgets(w, results); + return w && !w->castType(false) && w->getInheritedEnabled() && w->getInheritedVisible() + && w->getVisible() && w->getEnabled(); } -} -KeyboardNavigation::KeyboardNavigation() - : mCurrentFocus(nullptr) - , mModalWindow(nullptr) - , mEnabled(true) -{ - MyGUI::WidgetManager::getInstance().registerUnlinker(this); -} + /// Recursively get all child widgets that accept keyboard input + void getKeyFocusWidgets(MyGUI::Widget* parent, std::vector& results) + { + assert(parent != nullptr); -KeyboardNavigation::~KeyboardNavigation() -{ - try - { - MyGUI::WidgetManager::getInstance().unregisterUnlinker(this); - } - catch(const MyGUI::Exception& e) - { - Log(Debug::Error) << "Error in the destructor: " << e.what(); - } -} + if (!parent->getVisible() || !parent->getEnabled()) + return; -void KeyboardNavigation::saveFocus(int mode) -{ - MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - if (shouldAcceptKeyFocus(focus)) - { - mKeyFocus[mode] = focus; - } - else if(shouldAcceptKeyFocus(mCurrentFocus)) - { - mKeyFocus[mode] = mCurrentFocus; - } -} - -void KeyboardNavigation::restoreFocus(int mode) -{ - std::map::const_iterator found = mKeyFocus.find(mode); - if (found != mKeyFocus.end()) - { - MyGUI::Widget* w = found->second; - if (w && w->getVisible() && w->getEnabled()) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(found->second); - } -} - -void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget) -{ - for (std::pair& w : mKeyFocus) - if (w.second == widget) - w.second = nullptr; - if (widget == mCurrentFocus) - mCurrentFocus = nullptr; -} - -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) -void styleFocusedButton(MyGUI::Widget* w) -{ - if (w) - { - if (MyGUI::Button* b = w->castType(false)) + MyGUI::EnumeratorWidgetPtr enumerator = parent->getEnumerator(); + while (enumerator.next()) { - b->_setWidgetState("highlighted"); + MyGUI::Widget* w = enumerator.current(); + if (!w->getVisible() || !w->getEnabled()) + continue; + if (w->getNeedKeyFocus() && shouldAcceptKeyFocus(w)) + results.push_back(w); + else + getKeyFocusWidgets(w, results); } } -} -#endif +<<<<<<< HEAD bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) { while (widget && widget->getParent()) @@ -138,210 +77,264 @@ void KeyboardNavigation::onFrame() /* End of tes3mp change (major) */ +======= + KeyboardNavigation::KeyboardNavigation() + : mCurrentFocus(nullptr) + , mModalWindow(nullptr) + , mEnabled(true) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); - return; + MyGUI::WidgetManager::getInstance().registerUnlinker(this); } - MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - - if (focus == mCurrentFocus) + KeyboardNavigation::~KeyboardNavigation() { -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - styleFocusedButton(mCurrentFocus); -#endif - return; - } - - // workaround incorrect key focus resets (fix in MyGUI TBD) - if (!shouldAcceptKeyFocus(focus) && shouldAcceptKeyFocus(mCurrentFocus) && (!mModalWindow || isRootParent(mCurrentFocus, mModalWindow))) - { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCurrentFocus); - focus = mCurrentFocus; - } - - if (focus != mCurrentFocus) - { -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - if (mCurrentFocus) + try { - if (MyGUI::Button* b = mCurrentFocus->castType(false)) - b->_setWidgetState("normal"); + MyGUI::WidgetManager::getInstance().unregisterUnlinker(this); + } + catch (const MyGUI::Exception& e) + { + Log(Debug::Error) << "Error in the destructor: " << e.what(); } -#endif - mCurrentFocus = focus; } -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - styleFocusedButton(mCurrentFocus); -#endif -} - -void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus) -{ - MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - if (!focus || !shouldAcceptKeyFocus(focus)) + void KeyboardNavigation::saveFocus(int mode) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (shouldAcceptKeyFocus(focus)) + { + mKeyFocus[mode] = focus; + } + else if (shouldAcceptKeyFocus(mCurrentFocus)) + { + mKeyFocus[mode] = mCurrentFocus; + } } - else + + void KeyboardNavigation::restoreFocus(int mode) { - if (!isRootParent(focus, window)) + std::map::const_iterator found = mKeyFocus.find(mode); + if (found != mKeyFocus.end()) + { + MyGUI::Widget* w = found->second; + if (w && w->getVisible() && w->getEnabled() && w->getInheritedVisible() && w->getInheritedEnabled()) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(found->second); + } + } + + void KeyboardNavigation::_unlinkWidget(MyGUI::Widget* widget) + { + for (std::pair& w : mKeyFocus) + if (w.second == widget) + w.second = nullptr; + if (widget == mCurrentFocus) + mCurrentFocus = nullptr; + } + + bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) + { + while (widget && widget->getParent()) + widget = widget->getParent(); + return widget == root; + } + + void KeyboardNavigation::onFrame() + { + if (!mEnabled) + return; + + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); + return; + } + + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + + if (focus == mCurrentFocus) + { + return; + } + + // workaround incorrect key focus resets (fix in MyGUI TBD) + if (!shouldAcceptKeyFocus(focus) && shouldAcceptKeyFocus(mCurrentFocus) + && (!mModalWindow || isRootParent(mCurrentFocus, mModalWindow))) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCurrentFocus); + focus = mCurrentFocus; + } + + if (focus != mCurrentFocus) + { + mCurrentFocus = focus; + } + } + + void KeyboardNavigation::setDefaultFocus(MyGUI::Widget* window, MyGUI::Widget* defaultFocus) + { + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (!focus || !shouldAcceptKeyFocus(focus)) + { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); - } -} - -void KeyboardNavigation::setModalWindow(MyGUI::Widget *window) -{ - mModalWindow = window; -} - -void KeyboardNavigation::setEnabled(bool enabled) -{ - mEnabled = enabled; -} - -enum Direction -{ - D_Left, - D_Up, - D_Right, - D_Down, - D_Next, - D_Prev -}; - -bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) -{ - if (!mEnabled) - return false; - - switch (key.getValue()) - { - case MyGUI::KeyCode::ArrowLeft: - return switchFocus(D_Left, false); - case MyGUI::KeyCode::ArrowRight: - return switchFocus(D_Right, false); - case MyGUI::KeyCode::ArrowUp: - return switchFocus(D_Up, false); - case MyGUI::KeyCode::ArrowDown: - return switchFocus(D_Down, false); - case MyGUI::KeyCode::Tab: - return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); - case MyGUI::KeyCode::Return: - case MyGUI::KeyCode::NumpadEnter: - case MyGUI::KeyCode::Space: - { - // We should disable repeating for activation keys - MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::None); - if (repeat) - return true; - - return accept(); - } - default: - return false; - } -} - -bool KeyboardNavigation::switchFocus(int direction, bool wrap) -{ - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - return false; - - MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - - bool isCycle = (direction == D_Prev || direction == D_Next); - - if ((focus && focus->getTypeName().find("Button") == std::string::npos) && !isCycle) - return false; - - if (focus && isCycle && focus->getUserString("AcceptTab") == "true") - return false; - - if ((!focus || !focus->getNeedKeyFocus()) && isCycle) - { - // if nothing is selected, select the first widget - return selectFirstWidget(); - } - if (!focus) - return false; - - MyGUI::Widget* window = focus; - while (window && window->getParent()) - window = window->getParent(); - MyGUI::VectorWidgetPtr keyFocusList; - getKeyFocusWidgets(window, keyFocusList); - - if (keyFocusList.empty()) - return false; - - MyGUI::VectorWidgetPtr::iterator found = std::find(keyFocusList.begin(), keyFocusList.end(), focus); - if (found == keyFocusList.end()) - { - if (isCycle) - return selectFirstWidget(); + } else + { + if (!isRootParent(focus, window)) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); + } + } + + void KeyboardNavigation::setModalWindow(MyGUI::Widget* window) + { + mModalWindow = window; + } + + void KeyboardNavigation::setEnabled(bool enabled) + { + mEnabled = enabled; + } + + enum Direction + { + D_Left, + D_Up, + D_Right, + D_Down, + D_Next, + D_Prev + }; + + bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) + { + if (!mEnabled) return false; + + switch (key.getValue()) + { + case MyGUI::KeyCode::ArrowLeft: + return switchFocus(D_Left, false); + case MyGUI::KeyCode::ArrowRight: + return switchFocus(D_Right, false); + case MyGUI::KeyCode::ArrowUp: + return switchFocus(D_Up, false); + case MyGUI::KeyCode::ArrowDown: + return switchFocus(D_Down, false); + case MyGUI::KeyCode::Tab: + return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); + case MyGUI::KeyCode::Return: + case MyGUI::KeyCode::NumpadEnter: + case MyGUI::KeyCode::Space: + { + // We should disable repeating for activation keys + MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::None); + if (repeat) + return true; + + return accept(); + } + default: + return false; + } } - bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); - - int index = found - keyFocusList.begin(); - index = forward ? (index+1) : (index-1); - if (wrap) - index = (index + keyFocusList.size())%keyFocusList.size(); - else - index = std::min(std::max(0, index), static_cast(keyFocusList.size())-1); - - MyGUI::Widget* next = keyFocusList[index]; - int vertdiff = next->getTop() - focus->getTop(); - int horizdiff = next->getLeft() - focus->getLeft(); - bool isVertical = std::abs(vertdiff) > std::abs(horizdiff); - if (direction == D_Right && (horizdiff <= 0 || isVertical)) - return false; - else if (direction == D_Left && (horizdiff >= 0 || isVertical)) - return false; - else if (direction == D_Down && (vertdiff <= 0 || !isVertical)) - return false; - else if (direction == D_Up && (vertdiff >= 0 || !isVertical)) - return false; - - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[index]); - return true; -} - -bool KeyboardNavigation::selectFirstWidget() -{ - MyGUI::VectorWidgetPtr keyFocusList; - MyGUI::EnumeratorWidgetPtr enumerator = MyGUI::Gui::getInstance().getEnumerator(); - if (mModalWindow) - enumerator = mModalWindow->getEnumerator(); - while (enumerator.next()) - getKeyFocusWidgets(enumerator.current(), keyFocusList); - - if (!keyFocusList.empty()) + bool KeyboardNavigation::switchFocus(int direction, bool wrap) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[0]); + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) + return false; + + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + + bool isCycle = (direction == D_Prev || direction == D_Next); + + if ((focus && focus->getTypeName().find("Button") == std::string::npos) && !isCycle) + return false; + + if (focus && isCycle && focus->getUserString("AcceptTab") == "true") + return false; + + if ((!focus || !focus->getNeedKeyFocus()) && isCycle) + { + // if nothing is selected, select the first widget + return selectFirstWidget(); + } + if (!focus) + return false; + + MyGUI::Widget* window = focus; + while (window && window->getParent()) + window = window->getParent(); + MyGUI::VectorWidgetPtr keyFocusList; + getKeyFocusWidgets(window, keyFocusList); + + if (keyFocusList.empty()) + return false; + + MyGUI::VectorWidgetPtr::iterator found = std::find(keyFocusList.begin(), keyFocusList.end(), focus); + if (found == keyFocusList.end()) + { + if (isCycle) + return selectFirstWidget(); + else + return false; + } + + bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); + + int index = found - keyFocusList.begin(); + index = forward ? (index + 1) : (index - 1); + if (wrap) + index = (index + keyFocusList.size()) % keyFocusList.size(); + else + index = std::clamp(index, 0, keyFocusList.size() - 1); + + MyGUI::Widget* next = keyFocusList[index]; + int vertdiff = next->getTop() - focus->getTop(); + int horizdiff = next->getLeft() - focus->getLeft(); + bool isVertical = std::abs(vertdiff) > std::abs(horizdiff); + if (direction == D_Right && (horizdiff <= 0 || isVertical)) + return false; + else if (direction == D_Left && (horizdiff >= 0 || isVertical)) + return false; + else if (direction == D_Down && (vertdiff <= 0 || !isVertical)) + return false; + else if (direction == D_Up && (vertdiff >= 0 || !isVertical)) + return false; + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[index]); return true; } - return false; -} -bool KeyboardNavigation::accept() -{ - MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - if (!focus) - return false; - //MyGUI::Button* button = focus->castType(false); - //if (button && button->getEnabled()) - if (focus->getTypeName().find("Button") != std::string::npos && focus->getEnabled()) + bool KeyboardNavigation::selectFirstWidget() { - focus->eventMouseButtonClick(focus); - return true; + MyGUI::VectorWidgetPtr keyFocusList; + MyGUI::EnumeratorWidgetPtr enumerator = MyGUI::Gui::getInstance().getEnumerator(); + if (mModalWindow) + enumerator = mModalWindow->getEnumerator(); + while (enumerator.next()) + getKeyFocusWidgets(enumerator.current(), keyFocusList); + + if (!keyFocusList.empty()) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[0]); + return true; + } + return false; } - return false; -} + bool KeyboardNavigation::accept() + { + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (!focus) + return false; + // MyGUI::Button* button = focus->castType(false); + // if (button && button->getEnabled()) + if (focus->getTypeName().find("Button") != std::string::npos && focus->getEnabled()) + { + focus->eventMouseButtonClick(focus); + return true; + } + return false; + } } diff --git a/apps/openmw/mwgui/keyboardnavigation.hpp b/apps/openmw/mwgui/keyboardnavigation.hpp index d5159c24a..e37fe9088 100644 --- a/apps/openmw/mwgui/keyboardnavigation.hpp +++ b/apps/openmw/mwgui/keyboardnavigation.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_MWGUI_KEYBOARDNAVIGATION_H #define OPENMW_MWGUI_KEYBOARDNAVIGATION_H -#include #include +#include namespace MWGui { diff --git a/apps/openmw/mwgui/layout.cpp b/apps/openmw/mwgui/layout.cpp index 9b9b9537f..fb0fb5e1c 100644 --- a/apps/openmw/mwgui/layout.cpp +++ b/apps/openmw/mwgui/layout.cpp @@ -1,36 +1,34 @@ #include "layout.hpp" -#include -#include #include +#include #include +#include #include +#include "ustring.hpp" + namespace MWGui { - void Layout::initialise(const std::string& _layout, MyGUI::Widget* _parent) + void Layout::initialise(std::string_view _layout) { - const std::string MAIN_WINDOW = "_Main"; + const auto MAIN_WINDOW = "_Main"; mLayoutName = _layout; - if (mLayoutName.empty()) - mMainWidget = _parent; - else - { - mPrefix = MyGUI::utility::toString(this, "_"); - mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix, _parent); + mPrefix = MyGUI::utility::toString(this, "_"); + mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix); - const std::string main_name = mPrefix + MAIN_WINDOW; - for (MyGUI::Widget* widget : mListWindowRoot) - { - if (widget->getName() == main_name) - { - mMainWidget = widget; - break; - } - } - MYGUI_ASSERT(mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found."); + const std::string main_name = mPrefix + MAIN_WINDOW; + for (MyGUI::Widget* widget : mListWindowRoot) + { + if (widget->getName() == main_name) + mMainWidget = widget; + + // Force the alignment to update immediately + widget->_setAlign(widget->getSize(), widget->getParentSize()); } + MYGUI_ASSERT( + mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found."); } void Layout::shutdown() @@ -42,7 +40,7 @@ namespace MWGui void Layout::setCoord(int x, int y, int w, int h) { - mMainWidget->setCoord(x,y,w,h); + mMainWidget->setCoord(x, y, w, h); } void Layout::setVisible(bool b) @@ -50,26 +48,29 @@ namespace MWGui mMainWidget->setVisible(b); } - void Layout::setText(const std::string &name, const std::string &caption) + void Layout::setText(std::string_view name, std::string_view caption) { MyGUI::Widget* pt; getWidget(pt, name); - static_cast(pt)->setCaption(caption); + static_cast(pt)->setCaption(toUString(caption)); } - void Layout::setTitle(const std::string& title) + void Layout::setTitle(std::string_view title) { MyGUI::Window* window = static_cast(mMainWidget); + MyGUI::UString uTitle = toUString(title); - if (window->getCaption() != title) - window->setCaptionWithReplacing(title); + if (window->getCaption() != uTitle) + window->setCaptionWithReplacing(uTitle); } - MyGUI::Widget* Layout::getWidget(const std::string &_name) + MyGUI::Widget* Layout::getWidget(std::string_view _name) { + std::string target = mPrefix; + target += _name; for (MyGUI::Widget* widget : mListWindowRoot) { - MyGUI::Widget* find = widget->findWidget(mPrefix + _name); + MyGUI::Widget* find = widget->findWidget(target); if (nullptr != find) { return find; diff --git a/apps/openmw/mwgui/layout.hpp b/apps/openmw/mwgui/layout.hpp index ea51bf541..07a3d1e66 100644 --- a/apps/openmw/mwgui/layout.hpp +++ b/apps/openmw/mwgui/layout.hpp @@ -2,74 +2,77 @@ #define OPENMW_MWGUI_LAYOUT_H #include -#include +#include + #include #include namespace MWGui { - /** The Layout class is an utility class used to load MyGUI layouts - from xml files, and to manipulate member widgets. - */ - class Layout - { - public: - Layout(const std::string & _layout, MyGUI::Widget* _parent = nullptr) - : mMainWidget(nullptr) - { initialise(_layout, _parent); } - virtual ~Layout() + /** The Layout class is an utility class used to load MyGUI layouts + from xml files, and to manipulate member widgets. + */ + class Layout { - try + public: + Layout(std::string_view layout) + : mMainWidget(nullptr) { - shutdown(); + initialise(layout); + assert(mMainWidget); } - catch(const MyGUI::Exception& e) + + virtual ~Layout() { - Log(Debug::Error) << "Error in the destructor: " << e.what(); + try + { + shutdown(); + } + catch (const MyGUI::Exception& e) + { + Log(Debug::Error) << "Error in the destructor: " << e.what(); + } } - } - MyGUI::Widget* getWidget(const std::string& _name); + MyGUI::Widget* getWidget(std::string_view name); - template - void getWidget(T * & _widget, const std::string & _name) - { - MyGUI::Widget* w = getWidget(_name); - T* cast = w->castType(false); - if (!cast) + template + void getWidget(T*& _widget, std::string_view _name) { - MYGUI_EXCEPT("Error cast : dest type = '" << T::getClassTypeName() - << "' source name = '" << w->getName() - << "' source type = '" << w->getTypeName() << "' in layout '" << mLayoutName << "'"); + MyGUI::Widget* w = getWidget(_name); + T* cast = w->castType(false); + if (!cast) + { + MYGUI_EXCEPT("Error cast : dest type = '" << T::getClassTypeName() << "' source name = '" + << w->getName() << "' source type = '" << w->getTypeName() + << "' in layout '" << mLayoutName << "'"); + } + else + _widget = cast; } - else - _widget = cast; - } - private: - void initialise(const std::string & _layout, - MyGUI::Widget* _parent = nullptr); + private: + void initialise(std::string_view layout); - void shutdown(); + void shutdown(); - public: - void setCoord(int x, int y, int w, int h); + public: + void setCoord(int x, int y, int w, int h); - virtual void setVisible(bool b); + virtual void setVisible(bool b); - void setText(const std::string& name, const std::string& caption); + void setText(std::string_view name, std::string_view caption); - // NOTE: this assume that mMainWidget is of type Window. - void setTitle(const std::string& title); + // NOTE: this assume that mMainWidget is of type Window. + void setTitle(std::string_view title); - MyGUI::Widget* mMainWidget; + MyGUI::Widget* mMainWidget; - protected: - - std::string mPrefix; - std::string mLayoutName; - MyGUI::VectorWidgetPtr mListWindowRoot; - }; + protected: + std::string mPrefix; + std::string mLayoutName; + MyGUI::VectorWidgetPtr mListWindowRoot; + }; } #endif diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 14de3fd27..b13fdbeeb 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -1,30 +1,39 @@ #include "levelupdialog.hpp" #include -#include #include +#include +#include +#include #include +#include -#include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/actorutil.hpp" #include "class.hpp" +#include "ustring.hpp" +namespace +{ + constexpr unsigned int sMaxCoins = 3; + constexpr int sColumnOffsets[] = { 32, 218 }; +} namespace MWGui { - const unsigned int LevelupDialog::sMaxCoins = 3; LevelupDialog::LevelupDialog() - : WindowBase("openmw_levelup_dialog.layout"), - mCoinCount(sMaxCoins) + : WindowBase("openmw_levelup_dialog.layout") + , mCoinCount(sMaxCoins) { getWidget(mOkButton, "OkButton"); getWidget(mClassImage, "ClassImage"); @@ -35,26 +44,47 @@ namespace MWGui mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onOkButtonClicked); - for (int i=1; i<9; ++i) { - MyGUI::TextBox* t; - getWidget(t, "AttribVal" + MyGUI::utility::toString(i)); - mAttributeValues.push_back(t); + const auto& store = MWBase::Environment::get().getESMStore()->get(); + const size_t perCol + = static_cast(std::ceil(store.getSize() / static_cast(std::size(sColumnOffsets)))); + size_t i = 0; + for (const ESM::Attribute& attribute : store) + { + const int offset = sColumnOffsets[i / perCol]; + const int row = static_cast(i % perCol); + Widgets widgets; + widgets.mMultiplier = mAssignWidget->createWidget( + "SandTextVCenter", { offset, 20 * row, 100, 20 }, MyGUI::Align::Default); + auto* hbox = mAssignWidget->createWidget( + {}, { offset + 20, 20 * row, 200, 20 }, MyGUI::Align::Default); + widgets.mButton = hbox->createWidget("SandTextButton", {}, MyGUI::Align::Default); + widgets.mButton->setUserData(attribute.mId); + widgets.mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onAttributeClicked); + widgets.mButton->setUserString("TextPadding", "0 0"); + widgets.mButton->setUserString("ToolTipType", "Layout"); + widgets.mButton->setUserString("ToolTipLayout", "AttributeToolTip"); + widgets.mButton->setUserString("Caption_AttributeName", attribute.mName); + widgets.mButton->setUserString("Caption_AttributeDescription", attribute.mDescription); + widgets.mButton->setUserString("ImageTexture_AttributeImage", attribute.mIcon); + widgets.mButton->setCaption(attribute.mName); + widgets.mValue = hbox->createWidget("SandText", {}, MyGUI::Align::Default); + mAttributeWidgets.emplace(attribute.mId, widgets); + ++i; + } - MyGUI::Button* b; - getWidget(b, "Attrib" + MyGUI::utility::toString(i)); - b->setUserData (i-1); - b->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onAttributeClicked); - mAttributes.push_back(b); - - getWidget(t, "AttribMultiplier" + MyGUI::utility::toString(i)); - mAttributeMultipliers.push_back(t); + mAssignWidget->setVisibleVScroll(false); + mAssignWidget->setCanvasSize(MyGUI::IntSize( + mAssignWidget->getWidth(), std::max(mAssignWidget->getHeight(), static_cast(20 * perCol)))); + mAssignWidget->setVisibleVScroll(true); + mAssignWidget->setViewOffset(MyGUI::IntPoint()); } - for (unsigned int i = 0; i < mCoinCount; ++i) + for (unsigned int i = 0; i < sMaxCoins; ++i) { - MyGUI::ImageBox* image = mCoinBox->createWidget("ImageBox", MyGUI::IntCoord(0,0,16,16), MyGUI::Align::Default); - image->setImageTexture ("icons\\tx_goldicon.dds"); + MyGUI::ImageBox* image = mCoinBox->createWidget( + "ImageBox", MyGUI::IntCoord(0, 0, 16, 16), MyGUI::Align::Default); + image->setImageTexture("icons\\tx_goldicon.dds"); mCoins.push_back(image); } @@ -63,31 +93,30 @@ namespace MWGui void LevelupDialog::setAttributeValues() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); - MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); - for (int i = 0; i < 8; ++i) + for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) { - int val = creatureStats.getAttribute(i).getBase(); - if (std::find(mSpentAttributes.begin(), mSpentAttributes.end(), i) != mSpentAttributes.end()) + int val = creatureStats.getAttribute(attribute.mId).getBase(); + if (std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute.mId) != mSpentAttributes.end()) { - val += pcStats.getLevelupAttributeMultiplier(i); + val += pcStats.getLevelupAttributeMultiplier(attribute.mId); } if (val >= 100) val = 100; - mAttributeValues[i]->setCaption(MyGUI::utility::toString(val)); + mAttributeWidgets[attribute.mId].mValue->setCaption(MyGUI::utility::toString(val)); } } - void LevelupDialog::resetCoins() { - const int coinSpacing = 33; - int curX = mCoinBox->getWidth()/2 - (coinSpacing*(mCoinCount - 1) + 16*mCoinCount)/2; - for (unsigned int i=0; igetWidth() / 2 - (coinSpacing * (mCoinCount - 1) + 16 * mCoinCount) / 2; + for (unsigned int i = 0; i < sMaxCoins; ++i) { MyGUI::ImageBox* image = mCoins[i]; image->detachFromWidget(); @@ -95,8 +124,8 @@ namespace MWGui if (i < mCoinCount) { mCoins[i]->setVisible(true); - image->setCoord(MyGUI::IntCoord(curX,0,16,16)); - curX += 16+coinSpacing; + image->setCoord(MyGUI::IntCoord(curX, 0, 16, 16)); + curX += 16 + coinSpacing; } else mCoins[i]->setVisible(false); @@ -106,18 +135,21 @@ namespace MWGui void LevelupDialog::assignCoins() { resetCoins(); - for (unsigned int i=0; idetachFromWidget(); image->attachToWidget(mAssignWidget); - int attribute = mSpentAttributes[i]; + const auto& attribute = mSpentAttributes[i]; + const auto& widgets = mAttributeWidgets[attribute]; - int xdiff = mAttributeMultipliers[attribute]->getCaption() == "" ? 0 : 20; + const int xdiff = widgets.mMultiplier->getCaption().empty() ? 0 : 20; + const auto* hbox = widgets.mButton->getParent(); - MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mAssignWidget->getAbsolutePosition() - MyGUI::IntPoint(22+xdiff,0); - pos.top += (mAttributes[attribute]->getHeight() - image->getHeight())/2; + MyGUI::IntPoint pos = hbox->getPosition(); + pos.left -= 22 + xdiff; + pos.top += (hbox->getHeight() - image->getHeight()) / 2; image->setPosition(pos); } @@ -126,46 +158,49 @@ namespace MWGui void LevelupDialog::onOpen() { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); - MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); + const MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); + const MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); - setClassImage(mClassImage, getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0), - pcStats.getSkillIncreasesForSpecialization(1), - pcStats.getSkillIncreasesForSpecialization(2))); + setClassImage(mClassImage, + ESM::RefId::stringRefId(getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0), + pcStats.getSkillIncreasesForSpecialization(1), pcStats.getSkillIncreasesForSpecialization(2)))); - int level = creatureStats.getLevel ()+1; + int level = creatureStats.getLevel() + 1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); - std::string levelupdescription; - levelupdescription = Fallback::Map::getString("Level_Up_Level"+MyGUI::utility::toString(level)); + std::string_view levelupdescription; + levelupdescription = Fallback::Map::getString("Level_Up_Level" + MyGUI::utility::toString(level)); - if (levelupdescription == "") + if (levelupdescription.empty()) levelupdescription = Fallback::Map::getString("Level_Up_Default"); - mLevelDescription->setCaption (levelupdescription); + mLevelDescription->setCaption(toUString(levelupdescription)); unsigned int availableAttributes = 0; - for (int i = 0; i < 8; ++i) + for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) { - MyGUI::TextBox* text = mAttributeMultipliers[i]; - if (pcStats.getAttribute(i).getBase() < 100) + const auto& widgets = mAttributeWidgets[attribute.mId]; + if (pcStats.getAttribute(attribute.mId).getBase() < 100) { - mAttributes[i]->setEnabled(true); - mAttributeValues[i]->setEnabled(true); + widgets.mButton->setEnabled(true); + widgets.mValue->setEnabled(true); availableAttributes++; - float mult = pcStats.getLevelupAttributeMultiplier (i); - mult = std::min(mult, 100-pcStats.getAttribute(i).getBase()); - text->setCaption(mult <= 1 ? "" : "x" + MyGUI::utility::toString(mult)); + float mult = pcStats.getLevelupAttributeMultiplier(attribute.mId); + mult = std::min(mult, 100 - pcStats.getAttribute(attribute.mId).getBase()); + if (mult <= 1) + widgets.mMultiplier->setCaption({}); + else + widgets.mMultiplier->setCaption("x" + MyGUI::utility::toString(mult)); } else { - mAttributes[i]->setEnabled(false); - mAttributeValues[i]->setEnabled(false); + widgets.mButton->setEnabled(false); + widgets.mValue->setEnabled(false); - text->setCaption(""); + widgets.mMultiplier->setCaption({}); } } @@ -185,7 +220,7 @@ namespace MWGui void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) { MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); if (mSpentAttributes.size() < mCoinCount) MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage36}"); @@ -206,14 +241,13 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Levelup); } - } - void LevelupDialog::onAttributeClicked(MyGUI::Widget *sender) + void LevelupDialog::onAttributeClicked(MyGUI::Widget* sender) { - int attribute = *sender->getUserData(); + auto attribute = *sender->getUserData(); - std::vector::iterator found = std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute); + auto found = std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute); if (found != mSpentAttributes.end()) mSpentAttributes.erase(found); else @@ -226,9 +260,10 @@ namespace MWGui assignCoins(); } - std::string LevelupDialog::getLevelupClassImage(const int combatIncreases, const int magicIncreases, const int stealthIncreases) + std::string_view LevelupDialog::getLevelupClassImage( + const int combatIncreases, const int magicIncreases, const int stealthIncreases) { - std::string ret = "acrobat"; + std::string_view ret = "acrobat"; int total = combatIncreases + magicIncreases + stealthIncreases; if (total == 0) diff --git a/apps/openmw/mwgui/levelupdialog.hpp b/apps/openmw/mwgui/levelupdialog.hpp index 6c9182609..f3a80ebb9 100644 --- a/apps/openmw/mwgui/levelupdialog.hpp +++ b/apps/openmw/mwgui/levelupdialog.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_LEVELUPDIALOG_H #define MWGUI_LEVELUPDIALOG_H +#include + #include "windowbase.hpp" namespace MWGui @@ -14,23 +16,26 @@ namespace MWGui void onOpen() override; private: + struct Widgets + { + MyGUI::Button* mButton; + MyGUI::TextBox* mValue; + MyGUI::TextBox* mMultiplier; + }; MyGUI::Button* mOkButton; MyGUI::ImageBox* mClassImage; MyGUI::TextBox* mLevelText; MyGUI::EditBox* mLevelDescription; MyGUI::Widget* mCoinBox; - MyGUI::Widget* mAssignWidget; + MyGUI::ScrollView* mAssignWidget; - std::vector mAttributes; - std::vector mAttributeValues; - std::vector mAttributeMultipliers; + std::map mAttributeWidgets; std::vector mCoins; - std::vector mSpentAttributes; + std::vector mSpentAttributes; unsigned int mCoinCount; - static const unsigned int sMaxCoins; void onOkButtonClicked(MyGUI::Widget* sender); void onAttributeClicked(MyGUI::Widget* sender); @@ -40,7 +45,8 @@ namespace MWGui void setAttributeValues(); - std::string getLevelupClassImage(const int combatIncreases, const int magicIncreases, const int stealthIncreases); + std::string_view getLevelupClassImage( + const int combatIncreases, const int magicIncreases, const int stealthIncreases); }; } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 61fcacca4..3574333fd 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,29 +1,27 @@ #include "loadingscreen.hpp" #include -#include #include #include -#include -#include -#include #include +#include #include -#include #include +#include +#include #include +#include #include #include -#include #include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/inputmanager.hpp" #include "backgroundimage.hpp" @@ -44,82 +42,60 @@ namespace MWGui , mProgress(0) , mShowWallpaper(true) { - mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); getWidget(mLoadingBox, "LoadingBox"); + getWidget(mSceneImage, "Scene"); + getWidget(mSplashImage, "Splash"); mProgressBar->setScrollViewPage(1); - mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); - mSceneImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Scene"); - findSplashScreens(); } - LoadingScreen::~LoadingScreen() - { - } + LoadingScreen::~LoadingScreen() {} void LoadingScreen::findSplashScreens() { - const std::map& index = mResourceSystem->getVFS()->getIndex(); - std::string pattern = "Splash/"; - mResourceSystem->getVFS()->normalizeFilename(pattern); + auto isSupportedExtension = [](const std::string_view& ext) { + static const std::array supported_extensions{ { "tga", "dds", "ktx", "png", "bmp", "jpeg", + "jpg" } }; + return !ext.empty() + && std::find(supported_extensions.begin(), supported_extensions.end(), ext) + != supported_extensions.end(); + }; - /* priority given to the left */ - const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; - - auto found = index.lower_bound(pattern); - while (found != index.end()) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) { - const std::string& name = found->first; - if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos) - { - for(auto const& extension: supported_extensions) - { - if (name.compare(pos, name.size() - pos, extension) == 0) - { - mSplashScreens.push_back(found->first); - break; /* based on priority */ - } - } - } - } - else - break; - ++found; + if (isSupportedExtension(Misc::getFileExtension(name))) + mSplashScreens.push_back(name); } if (mSplashScreens.empty()) Log(Debug::Warning) << "Warning: no splash screens found!"; } - void LoadingScreen::setLabel(const std::string &label, bool important) + void LoadingScreen::setLabel(const std::string& label, bool important) { mImportantLabel = important; mLoadingText->setCaptionWithReplacing(label); int padding = mLoadingBox->getWidth() - mLoadingText->getWidth(); - MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight()); + MyGUI::IntSize size(mLoadingText->getTextSize().width + padding, mLoadingBox->getHeight()); size.width = std::max(300, size.width); mLoadingBox->setSize(size); if (MWBase::Environment::get().getWindowManager()->getMessagesCount() > 0) - mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2); + mLoadingBox->setPosition(mMainWidget->getWidth() / 2 - mLoadingBox->getWidth() / 2, + mMainWidget->getHeight() / 2 - mLoadingBox->getHeight() / 2); else - mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8); + mLoadingBox->setPosition(mMainWidget->getWidth() / 2 - mLoadingBox->getWidth() / 2, + mMainWidget->getHeight() - mLoadingBox->getHeight() - 8); } void LoadingScreen::setVisible(bool visible) { WindowBase::setVisible(visible); - mBackgroundImage->setVisible(visible); + mSplashImage->setVisible(visible); mSceneImage->setVisible(visible); } @@ -141,7 +117,7 @@ namespace MWGui { } - void operator () (osg::RenderInfo& renderInfo) const override + void operator()(osg::RenderInfo& renderInfo) const override { int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); @@ -150,10 +126,7 @@ namespace MWGui mOneshot = false; } - void reset() - { - mOneshot = true; - } + void reset() { mOneshot = true; } private: mutable bool mOneshot; @@ -174,11 +147,13 @@ namespace MWGui mLoadingOnTime = mTimer.time_m(); - // Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading - // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() + // Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after + // each frame of loading We are already using node masks to avoid the scene from being updated/rendered, but + // node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); - if (const osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) { + if (const osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) + { mOldIcoMin = ico->getMinimumTimeAvailableForGLCompileAndDeletePerFrame(); mOldIcoMax = ico->getMaximumNumOfObjectsToCompilePerFrame(); } @@ -208,7 +183,7 @@ namespace MWGui { if (--mNestedLoadingCount > 0) return; - mLoadingBox->setVisible(true); // restore + mLoadingBox->setVisible(true); // restore if (mLastRenderTime < mLoadingOnTime) { @@ -216,7 +191,7 @@ namespace MWGui // we may still want to show the label if the caller requested it if (mImportantLabel) { - MWBase::Environment::get().getWindowManager()->messageBox(mLoadingText->getCaption()); + MWBase::Environment::get().getWindowManager()->messageBox(mLoadingText->getCaption().asUTF8()); mImportantLabel = false; } } @@ -226,7 +201,6 @@ namespace MWGui mViewer->getSceneData()->setComputeBoundingSphereCallback(nullptr); mViewer->getSceneData()->dirtyBound(); - //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; setVisible(false); if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) @@ -239,55 +213,59 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper); } - void LoadingScreen::changeWallpaper () + void LoadingScreen::changeWallpaper() { if (!mSplashScreens.empty()) { - std::string const & randomSplash = mSplashScreens.at(Misc::Rng::rollDice(mSplashScreens.size())); + std::string const& randomSplash = mSplashScreens.at(Misc::Rng::rollDice(mSplashScreens.size())); // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3 - // we can't do this by default, because the Morrowind splash screens are 1024x1024, but should be displayed as 4:3 + // we can't do this by default, because the Morrowind splash screens are 1024x1024, but should be displayed + // as 4:3 bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); - mBackgroundImage->setVisible(true); - mBackgroundImage->setBackgroundImage(randomSplash, true, stretch); + mSplashImage->setVisible(true); + mSplashImage->setBackgroundImage(randomSplash, true, stretch); } - mSceneImage->setBackgroundImage(""); + mSceneImage->setBackgroundImage({}); mSceneImage->setVisible(false); } - void LoadingScreen::setProgressRange (size_t range) + void LoadingScreen::setProgressRange(size_t range) { - mProgressBar->setScrollRange(range+1); + mProgressBar->setScrollRange(range + 1); mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(0); mProgress = 0; } - void LoadingScreen::setProgress (size_t value) + void LoadingScreen::setProgress(size_t value) { // skip expensive update if there isn't enough visible progress - if (mProgressBar->getWidth() <= 0 || value - mProgress < mProgressBar->getScrollRange()/mProgressBar->getWidth()) + if (mProgressBar->getWidth() <= 0 + || value - mProgress < mProgressBar->getScrollRange() / mProgressBar->getWidth()) return; - value = std::min(value, mProgressBar->getScrollRange()-1); + value = std::min(value, mProgressBar->getScrollRange() - 1); mProgress = value; mProgressBar->setScrollPosition(0); - mProgressBar->setTrackSize(static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); + mProgressBar->setTrackSize( + static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); draw(); } - void LoadingScreen::increaseProgress (size_t increase) + void LoadingScreen::increaseProgress(size_t increase) { mProgressBar->setScrollPosition(0); size_t value = mProgress + increase; - value = std::min(value, mProgressBar->getScrollRange()-1); + value = std::min(value, mProgressBar->getScrollRange() - 1); mProgress = value; - mProgressBar->setTrackSize(static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); + mProgressBar->setTrackSize( + static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); draw(); } bool LoadingScreen::needToDrawLoadingScreen() { - if ( mTimer.time_m() <= mLastRenderTime + (1.0/getTargetFrameRate()) * 1000.0) + if (mTimer.time_m() <= mLastRenderTime + (1.0 / getTargetFrameRate()) * 1000.0) return false; // the minimal delay before a loading screen shows @@ -303,7 +281,7 @@ namespace MWGui diff -= mProgress / static_cast(mProgressBar->getScrollRange()) * 100.f; } - if (!mShowWallpaper && diff < initialDelay*1000) + if (!mShowWallpaper && diff < initialDelay * 1000) return false; return true; } @@ -317,13 +295,15 @@ namespace MWGui if (!mTexture) { mTexture = new osg::Texture2D; + mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mTexture->setInternalFormat(GL_RGB); mTexture->setResizeNonPowerOfTwoHint(false); } if (!mGuiTexture.get()) { - mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture)); + mGuiTexture = std::make_unique(mTexture); } if (!mCopyFramebufferToTextureCallback) @@ -331,16 +311,12 @@ namespace MWGui mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture); } -#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback); -#else - mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback); -#endif mCopyFramebufferToTextureCallback->reset(); - mBackgroundImage->setBackgroundImage(""); - mBackgroundImage->setVisible(false); + mSplashImage->setBackgroundImage({}); + mSplashImage->setVisible(false); mSceneImage->setRenderItemTexture(mGuiTexture.get()); mSceneImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); @@ -352,7 +328,7 @@ namespace MWGui if (mVisible && !needToDrawLoadingScreen()) return; - if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) + if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000 * 1) { mLastWallpaperChangeTime = mTimer.time_m(); changeWallpaper(); @@ -368,7 +344,7 @@ namespace MWGui mResourceSystem->reportStats(mViewer->getFrameStamp()->getFrameNumber(), mViewer->getViewerStats()); if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) { - ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(1.f/getTargetFrameRate()); + ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(1.f / getTargetFrameRate()); ico->setMaximumNumOfObjectsToCompilePerFrame(1000); } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index c64396534..2cd3f7357 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -37,12 +37,12 @@ namespace MWGui virtual ~LoadingScreen(); /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details - void setLabel (const std::string& label, bool important) override; - void loadingOn(bool visible=true) override; + void setLabel(const std::string& label, bool important) override; + void loadingOn(bool visible = true) override; void loadingOff() override; - void setProgressRange (size_t range) override; - void setProgress (size_t value) override; - void increaseProgress (size_t increase=1) override; + void setProgressRange(size_t range) override; + void setProgress(size_t value) override; + void increaseProgress(size_t increase = 1) override; void setVisible(bool visible) override; @@ -79,7 +79,7 @@ namespace MWGui MyGUI::TextBox* mLoadingText; MyGUI::ScrollBar* mProgressBar; - BackgroundImage* mBackgroundImage; + BackgroundImage* mSplashImage; BackgroundImage* mSceneImage; std::vector mSplashScreens; @@ -95,5 +95,4 @@ namespace MWGui } - #endif diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 6fb3198a4..e9a5be414 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,21 +1,23 @@ #include "mainmenu.hpp" -#include #include #include +#include -#include #include #include +#include #include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/statemanager.hpp" -#include "savegamedialog.hpp" -#include "confirmationdialog.hpp" +#include "../mwworld/globals.hpp" + #include "backgroundimage.hpp" +#include "confirmationdialog.hpp" +#include "savegamedialog.hpp" #include "videowidget.hpp" namespace MWGui @@ -23,12 +25,13 @@ namespace MWGui MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription) : WindowBase("openmw_mainmenu.layout") - , mWidth (w), mHeight (h) - , mVFS(vfs), mButtonBox(nullptr) + , mWidth(w) + , mHeight(h) + , mVFS(vfs) + , mButtonBox(nullptr) , mBackground(nullptr) , mVideoBackground(nullptr) , mVideo(nullptr) - , mSaveGameDialog(nullptr) { getWidget(mVersionText, "VersionText"); mVersionText->setCaption(versionDescription); @@ -38,11 +41,6 @@ namespace MWGui updateMenu(); } - MainMenu::~MainMenu() - { - delete mSaveGameDialog; - } - void MainMenu::onResChange(int w, int h) { mWidth = w; @@ -51,14 +49,13 @@ namespace MWGui updateMenu(); } - void MainMenu::setVisible (bool visible) + void MainMenu::setVisible(bool visible) { if (visible) updateMenu(); - bool isMainMenu = - MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && - MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; + bool isMainMenu = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) + && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; showBackground(isMainMenu); @@ -75,12 +72,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["return"]); } - Layout::setVisible (visible); + Layout::setVisible(visible); } void MainMenu::onNewGameConfirmed() { - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu); MWBase::Environment::get().getStateManager()->newGame(); } @@ -89,18 +86,18 @@ namespace MWGui MWBase::Environment::get().getStateManager()->requestQuit(); } - void MainMenu::onButtonClicked(MyGUI::Widget *sender) + void MainMenu::onButtonClicked(MyGUI::Widget* sender) { - MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); - std::string name = *sender->getUserData(); - winMgr->playSound("Menu Click"); + const std::string& name = *sender->getUserData(); + winMgr->playSound(ESM::RefId::stringRefId("Menu Click")); if (name == "return") { - winMgr->removeGuiMode (GM_MainMenu); + winMgr->removeGuiMode(GM_MainMenu); } else if (name == "options") - winMgr->pushGuiMode (GM_Settings); + winMgr->pushGuiMode(GM_Settings); else if (name == "credits") winMgr->playVideo("mw_credits.bik", true); else if (name == "exitgame") @@ -110,7 +107,7 @@ namespace MWGui else { ConfirmationDialog* dialog = winMgr->getConfirmationDialog(); - dialog->askForConfirmation("#{sMessage2}"); + dialog->askForConfirmation("#{OMWEngine:QuitGameConfirmation}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &MainMenu::onExitConfirmed); dialog->eventCancelClicked.clear(); @@ -123,7 +120,7 @@ namespace MWGui else { ConfirmationDialog* dialog = winMgr->getConfirmationDialog(); - dialog->askForConfirmation("#{sNotifyMessage54}"); + dialog->askForConfirmation("#{OMWEngine:NewGameConfirmation}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &MainMenu::onNewGameConfirmed); dialog->eventCancelClicked.clear(); @@ -133,7 +130,7 @@ namespace MWGui else { if (!mSaveGameDialog) - mSaveGameDialog = new SaveGameDialog(); + mSaveGameDialog = std::make_unique(); if (name == "loadgame") mSaveGameDialog->setLoadOrSave(true); else if (name == "savegame") @@ -166,12 +163,12 @@ namespace MWGui if (!mVideo) { // Use black background to correct aspect ratio - mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Default, "Menu"); + mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal( + "ImageBox", 0, 0, 1, 1, MyGUI::Align::Default, "MainMenuBackground"); mVideoBackground->setImageTexture("black"); - mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); + mVideo = mVideoBackground->createWidget( + "ImageBox", 0, 0, 1, 1, MyGUI::Align::Stretch, "MainMenuBackground"); mVideo->setVFS(mVFS); mVideo->playVideo("video\\menu_background.bik"); @@ -190,8 +187,8 @@ namespace MWGui { if (!mBackground) { - mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); + mBackground = MyGUI::Gui::getInstance().createWidgetReal( + "ImageBox", 0, 0, 1, 1, MyGUI::Align::Stretch, "MainMenuBackground"); mBackground->setBackgroundImage("textures\\menu_morrowind.dds", true, stretch); } mBackground->setVisible(true); @@ -217,10 +214,11 @@ namespace MWGui void MainMenu::updateMenu() { - setCoord(0,0, mWidth, mHeight); + setCoord(0, 0, mWidth, mHeight); if (!mButtonBox) - mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); + mButtonBox + = mMainWidget->createWidget({}, MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); int curH = 0; @@ -230,7 +228,7 @@ namespace MWGui std::vector buttons; - if (state==MWBase::StateManager::State_Running) + if (state == MWBase::StateManager::State_Running) buttons.emplace_back("return"); /* @@ -246,14 +244,19 @@ namespace MWGui //buttons.emplace_back("newgame"); - if (state==MWBase::StateManager::State_Running && - MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1 && - MWBase::Environment::get().getWindowManager()->isSavingAllowed()) + if (state == MWBase::StateManager::State_Running + && MWBase::Environment::get().getWorld()->getGlobalInt(MWWorld::Globals::sCharGenState) == -1 + && MWBase::Environment::get().getWindowManager()->isSavingAllowed()) buttons.emplace_back("savegame"); +<<<<<<< HEAD /* if (MWBase::Environment::get().getStateManager()->characterBegin()!= MWBase::Environment::get().getStateManager()->characterEnd()) +======= + if (MWBase::Environment::get().getStateManager()->characterBegin() + != MWBase::Environment::get().getStateManager()->characterEnd()) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 buttons.emplace_back("loadgame"); */ @@ -263,31 +266,30 @@ namespace MWGui buttons.emplace_back("options"); - if (state==MWBase::StateManager::State_NoGame) + if (state == MWBase::StateManager::State_NoGame) buttons.emplace_back("credits"); buttons.emplace_back("exitgame"); // Create new buttons if needed - std::vector allButtons { "return", "newgame", "savegame", "loadgame", "options", "credits", "exitgame"}; - for (std::string& buttonId : allButtons) + for (std::string_view id : { "return", "newgame", "savegame", "loadgame", "options", "credits", "exitgame" }) { - if (mButtons.find(buttonId) == mButtons.end()) + if (mButtons.find(id) == mButtons.end()) { - Gui::ImageButton* button = mButtonBox->createWidget - ("ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default); + Gui::ImageButton* button = mButtonBox->createWidget( + "ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default); + const std::string& buttonId = mButtons.emplace(id, button).first->first; button->setProperty("ImageHighlighted", "textures\\menu_" + buttonId + "_over.dds"); button->setProperty("ImageNormal", "textures\\menu_" + buttonId + ".dds"); button->setProperty("ImagePushed", "textures\\menu_" + buttonId + "_pressed.dds"); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked); - button->setUserData(std::string(buttonId)); - mButtons[buttonId] = button; + button->setUserData(buttonId); } } // Start by hiding all buttons int maxwidth = 0; - for (auto& buttonPair : mButtons) + for (const auto& buttonPair : mButtons) { buttonPair.second->setVisible(false); MyGUI::IntSize requested = buttonPair.second->getRequestedSize(); @@ -296,10 +298,11 @@ namespace MWGui } // Now show and position the ones we want - for (std::string& buttonId : buttons) + for (const std::string& buttonId : buttons) { - assert(mButtons.find(buttonId) != mButtons.end()); - Gui::ImageButton* button = mButtons[buttonId]; + auto it = mButtons.find(buttonId); + assert(it != mButtons.end()); + Gui::ImageButton* button = it->second; button->setVisible(true); // By default, assume that all menu buttons textures should have 64 height. @@ -311,19 +314,19 @@ namespace MWGui // Trim off some of the excessive padding // TODO: perhaps do this within ImageButton? int height = requested.height; - button->setImageTile(MyGUI::IntSize(requested.width, requested.height-16*scale)); - button->setCoord((maxwidth-requested.width/scale) / 2, curH, requested.width/scale, height/scale-16); - curH += height/scale-16; + button->setImageTile(MyGUI::IntSize(requested.width, requested.height - 16 * scale)); + button->setCoord( + (maxwidth - requested.width / scale) / 2, curH, requested.width / scale, height / scale - 16); + curH += height / scale - 16; } if (state == MWBase::StateManager::State_NoGame) { // Align with the background image - int bottomPadding=24; - mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight - curH - bottomPadding, maxwidth, curH); + int bottomPadding = 24; + mButtonBox->setCoord(mWidth / 2 - maxwidth / 2, mHeight - curH - bottomPadding, maxwidth, curH); } else - mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight/2 - curH/2, maxwidth, curH); - + mButtonBox->setCoord(mWidth / 2 - maxwidth / 2, mHeight / 2 - curH / 2, maxwidth, curH); } } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 560eb93dc..ee6a5cdb1 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -1,6 +1,9 @@ #ifndef OPENMW_GAME_MWGUI_MAINMENU_H #define OPENMW_GAME_MWGUI_MAINMENU_H +#include + +#include "savegamedialog.hpp" #include "windowbase.hpp" namespace Gui @@ -17,51 +20,48 @@ namespace MWGui { class BackgroundImage; - class SaveGameDialog; class VideoWidget; class MainMenu : public WindowBase { - int mWidth; - int mHeight; + int mWidth; + int mHeight; - bool mHasAnimatedMenu; + bool mHasAnimatedMenu; - public: + public: + MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription); - MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription); - ~MainMenu(); + void onResChange(int w, int h) override; - void onResChange(int w, int h) override; + void setVisible(bool visible) override; - void setVisible (bool visible) override; + void onFrame(float dt) override; - void onFrame(float dt) override; + bool exit() override; - bool exit() override; + private: + const VFS::Manager* mVFS; - private: - const VFS::Manager* mVFS; + MyGUI::Widget* mButtonBox; + MyGUI::TextBox* mVersionText; - MyGUI::Widget* mButtonBox; - MyGUI::TextBox* mVersionText; + BackgroundImage* mBackground; - BackgroundImage* mBackground; + MyGUI::ImageBox* mVideoBackground; + VideoWidget* mVideo; // For animated main menus - MyGUI::ImageBox* mVideoBackground; - VideoWidget* mVideo; // For animated main menus + std::map> mButtons; - std::map mButtons; + void onButtonClicked(MyGUI::Widget* sender); + void onNewGameConfirmed(); + void onExitConfirmed(); - void onButtonClicked (MyGUI::Widget* sender); - void onNewGameConfirmed(); - void onExitConfirmed(); + void showBackground(bool show); - void showBackground(bool show); + void updateMenu(); - void updateMenu(); - - SaveGameDialog* mSaveGameDialog; + std::unique_ptr mSaveGameDialog; }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index cc62f4ebb..9a87d0553 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -2,15 +2,18 @@ #include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -25,26 +28,34 @@ #include #include #include +======= +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include +#include +#include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwrender/globalmap.hpp" #include "../mwrender/localmap.hpp" #include "confirmationdialog.hpp" -#include "tooltips.hpp" + +#include namespace { const int cellSize = Constants::CellSizeInUnits; + constexpr float speed = 1.08f; // the zoom speed, it should be greater than 1 enum LocalMapWidgetDepth { @@ -63,7 +74,6 @@ namespace Global_MapLayer = 3 }; - /// @brief A widget that changes its color when hovered. class MarkerWidget final : public MyGUI::Widget { @@ -76,38 +86,46 @@ namespace setColour(colour); } - void setHoverColour(const MyGUI::Colour& colour) - { - mHoverColour = colour; - } + void setHoverColour(const MyGUI::Colour& colour) { mHoverColour = colour; } private: MyGUI::Colour mNormalColour; MyGUI::Colour mHoverColour; - void onMouseLostFocus(MyGUI::Widget* _new) override - { - setColour(mNormalColour); - } + void onMouseLostFocus(MyGUI::Widget* _new) override { setColour(mNormalColour); } - void onMouseSetFocus(MyGUI::Widget* _old) override - { - setColour(mHoverColour); - } + void onMouseSetFocus(MyGUI::Widget* _old) override { setColour(mHoverColour); } }; + + MyGUI::IntRect createRect(const MyGUI::IntPoint& center, int radius) + { + return { center.left - radius, center.top + radius, center.left + radius, center.top - radius }; + } + + int getLocalViewingDistance() + { + if (!Settings::Manager::getBool("allow zooming", "Map")) + return Constants::CellGridRadius; + if (!Settings::Manager::getBool("distant terrain", "Terrain")) + return Constants::CellGridRadius; + const int maxLocalViewingDistance + = std::max(Settings::Manager::getInt("max local viewing distance", "Map"), Constants::CellGridRadius); + const int viewingDistanceInCells = Settings::camera().mViewingDistance / Constants::CellSizeInUnits; + return std::clamp(viewingDistanceInCells, Constants::CellGridRadius, maxLocalViewingDistance); + } } namespace MWGui { - void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent) + void CustomMarkerCollection::addMarker(const ESM::CustomMarker& marker, bool triggerEvent) { mMarkers.insert(std::make_pair(marker.mCell, marker)); if (triggerEvent) eventMarkersChanged(); } - void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker) + void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker& marker) { std::pair range = mMarkers.equal_range(marker.mCell); @@ -123,7 +141,7 @@ namespace MWGui throw std::runtime_error("can't find marker to delete"); } - void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote) + void CustomMarkerCollection::updateMarker(const ESM::CustomMarker& marker, const std::string& newNote) { std::pair range = mMarkers.equal_range(marker.mCell); @@ -155,7 +173,7 @@ namespace MWGui return mMarkers.end(); } - CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::CellId &cellId) const + CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::RefId& cellId) const { return mMarkers.equal_range(cellId); } @@ -167,7 +185,8 @@ namespace MWGui // ------------------------------------------------------ - LocalMapBase::LocalMapBase(CustomMarkerCollection &markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) + LocalMapBase::LocalMapBase( + CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) : mLocalMapRender(localMapRender) , mCurX(0) , mCurY(0) @@ -178,7 +197,7 @@ namespace MWGui , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) , mMapWidgetSize(0) - , mNumCells(0) + , mNumCells(1) , mCellDistance(0) , mCustomMarkers(markers) , mMarkerUpdateTimer(0.0f) @@ -214,30 +233,30 @@ namespace MWGui */ } - void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass) + void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance) { mLocalMap = widget; mCompass = compass; mMapWidgetSize = std::max(1, Settings::Manager::getInt("local map widget size", "Map")); - mCellDistance = Constants::CellGridRadius; + mCellDistance = cellDistance; mNumCells = mCellDistance * 2 + 1; - mLocalMap->setCanvasSize(mMapWidgetSize*mNumCells, mMapWidgetSize*mNumCells); + mLocalMap->setCanvasSize(mMapWidgetSize * mNumCells, mMapWidgetSize * mNumCells); mCompass->setDepth(Local_CompassLayer); mCompass->setNeedMouseFocus(false); - for (int mx=0; mxcreateWidget("ImageBox", - MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), + MyGUI::IntCoord(mx * mMapWidgetSize, my * mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), MyGUI::Align::Top | MyGUI::Align::Left); map->setDepth(Local_MapLayer); MyGUI::ImageBox* fog = mLocalMap->createWidget("ImageBox", - MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), + MyGUI::IntCoord(mx * mMapWidgetSize, my * mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), MyGUI::Align::Top | MyGUI::Align::Left); fog->setDepth(Local_FogLayer); fog->setColour(MyGUI::Colour(0, 0, 0)); @@ -265,65 +284,99 @@ namespace MWGui void LocalMapBase::applyFogOfWar() { - for (int mx=0; mxsetImageTexture(""); - entry.mFogTexture.reset(); - continue; - } + entry.mFogWidget->setImageTexture({}); + entry.mFogTexture.reset(); } } redraw(); } - MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) + MyGUI::IntPoint LocalMapBase::getPosition(int cellX, int cellY, float nX, float nY) const { - MyGUI::IntPoint widgetPos; // normalized cell coordinates - float nX,nY; + auto mapWidgetSize = getWidgetSize(); + return MyGUI::IntPoint(std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mCurX)) * mapWidgetSize), + std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mCurY)) * mapWidgetSize)); + } + + MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const + { + osg::Vec2i cellIndex; + // normalized cell coordinates + float nX, nY; if (!mInterior) { - int cellX, cellY; - MWBase::Environment::get().getWorld()->positionToIndex(worldX, worldY, cellX, cellY); - nX = (worldX - cellSize * cellX) / cellSize; + ESM::ExteriorCellLocation cellPos = ESM::positionToExteriorCellLocation(worldX, worldY); + cellIndex.x() = cellPos.mX; + cellIndex.y() = cellPos.mY; + + nX = (worldX - cellSize * cellIndex.x()) / cellSize; // Image space is -Y up, cells are Y up - nY = 1 - (worldY - cellSize * cellY) / cellSize; - - float cellDx = static_cast(cellX - mCurX); - float cellDy = static_cast(cellY - mCurY); - - markerPos.cellX = cellX; - markerPos.cellY = cellY; - - widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + cellDx) * mMapWidgetSize), - static_cast(nY * mMapWidgetSize + (mCellDistance - cellDy) * mMapWidgetSize)); + nY = 1 - (worldY - cellSize * cellIndex.y()) / cellSize; } else - { - int cellX, cellY; - osg::Vec2f worldPos (worldX, worldY); - mLocalMapRender->worldToInteriorMapPosition(worldPos, nX, nY, cellX, cellY); - - markerPos.cellX = cellX; - markerPos.cellY = cellY; - - // Image space is -Y up, cells are Y up - widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + (cellX - mCurX)) * mMapWidgetSize), - static_cast(nY * mMapWidgetSize + (mCellDistance - (cellY - mCurY)) * mMapWidgetSize)); - } + mLocalMapRender->worldToInteriorMapPosition({ worldX, worldY }, nX, nY, cellIndex.x(), cellIndex.y()); + markerPos.cellX = cellIndex.x(); + markerPos.cellY = cellIndex.y(); markerPos.nX = nX; markerPos.nY = nY; - return widgetPos; + return getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY); + } + + MyGUI::IntCoord LocalMapBase::getMarkerCoordinates( + float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const + { + int halfMarkerSize = markerSize / 2; + auto position = getMarkerPosition(worldX, worldY, markerPos); + return MyGUI::IntCoord(position.left - halfMarkerSize, position.top - halfMarkerSize, markerSize, markerSize); + } + + MyGUI::Widget* LocalMapBase::createDoorMarker( + const std::string& name, const MyGUI::VectorString& notes, float x, float y) const + { + MarkerUserData data(mLocalMapRender); + data.notes = notes; + data.caption = name; + MarkerWidget* markerWidget = mLocalMap->createWidget( + "MarkerButton", getMarkerCoordinates(x, y, data, 8), MyGUI::Align::Default); + markerWidget->setNormalColour( + MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); + markerWidget->setHoverColour( + MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); + markerWidget->setDepth(Local_MarkerLayer); + markerWidget->setNeedMouseFocus(true); + // Used by tooltips to not show the tooltip if marker is hidden by fog of war + markerWidget->setUserString("ToolTipType", "MapMarker"); + + markerWidget->setUserData(data); + return markerWidget; + } + + void LocalMapBase::centerView() + { + MyGUI::IntPoint pos = mCompass->getPosition() + MyGUI::IntPoint{ 16, 16 }; + MyGUI::IntSize viewsize = mLocalMap->getSize(); + MyGUI::IntPoint viewOffset((viewsize.width / 2) - pos.left, (viewsize.height / 2) - pos.top); + mLocalMap->setViewOffset(viewOffset); + } + + MyGUI::IntCoord LocalMapBase::getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const + { + MarkerUserData& markerPos(*widget->getUserData()); + auto position = getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY); + return MyGUI::IntCoord(position.left - markerSize / 2, position.top - markerSize / 2, markerSize, markerSize); + } + + std::vector& LocalMapBase::currentDoorMarkersWidgets() + { + return mInterior ? mInteriorDoorMarkerWidgets : mExteriorDoorMarkerWidgets; } void LocalMapBase::updateCustomMarkers() @@ -334,27 +387,19 @@ namespace MWGui for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) { - for (int dY =-mCellDistance; dY <= mCellDistance; ++dY) + for (int dY = -mCellDistance; dY <= mCellDistance; ++dY) { - ESM::CellId cellId; - cellId.mPaged = !mInterior; - cellId.mWorldspace = (mInterior ? mPrefix : ESM::CellId::sDefaultWorldspace); - cellId.mIndex.mX = mCurX+dX; - cellId.mIndex.mY = mCurY+dY; + ESM::RefId cellRefId = ESM::Cell::generateIdForCell(!mInterior, mPrefix, mCurX + dX, mCurY + dY); - CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); - for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId); + for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; + ++it) { const ESM::CustomMarker& marker = it->second; - MarkerUserData markerPos (mLocalMapRender); - MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); - - MyGUI::IntCoord widgetCoord(widgetPos.left - 8, - widgetPos.top - 8, - 16, 16); + MarkerUserData markerPos(mLocalMapRender); MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", - widgetCoord, MyGUI::Align::Default); + getMarkerCoordinates(marker.mWorldX, marker.mWorldY, markerPos, 16), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); @@ -402,19 +447,51 @@ namespace MWGui void LocalMapBase::setActiveCell(const int x, const int y, bool interior) { - if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) + if (x == mCurX && y == mCurY && mInterior == interior && !mChanged) return; // don't do anything if we're still in the same cell + if (!interior && !(x == mCurX && y == mCurY)) + { + const MyGUI::IntRect intersection + = { std::max(x, mCurX) - mCellDistance, std::max(y, mCurY) - mCellDistance, + std::min(x, mCurX) + mCellDistance, std::min(y, mCurY) + mCellDistance }; + + const MyGUI::IntRect activeGrid = createRect({ x, y }, Constants::CellGridRadius); + const MyGUI::IntRect currentView = createRect({ x, y }, mCellDistance); + + mExteriorDoorMarkerWidgets.clear(); + for (auto& [coord, doors] : mExteriorDoorsByCell) + { + if (!mHasALastActiveCell || !currentView.inside({ coord.first, coord.second }) + || activeGrid.inside({ coord.first, coord.second })) + { + mDoorMarkersToRecycle.insert(mDoorMarkersToRecycle.end(), doors.begin(), doors.end()); + doors.clear(); + } + else + mExteriorDoorMarkerWidgets.insert(mExteriorDoorMarkerWidgets.end(), doors.begin(), doors.end()); + } + + for (auto& widget : mDoorMarkersToRecycle) + widget->setVisible(false); + + for (auto const& cell : mMaps) + { + if (mHasALastActiveCell && !intersection.inside({ cell.mCellX, cell.mCellY })) + mLocalMapRender->removeExteriorCell(cell.mCellX, cell.mCellY); + } + } + mCurX = x; mCurY = y; mInterior = interior; mChanged = false; - for (int mx=0; mxsetRenderItemTexture(nullptr); entry.mFogWidget->setRenderItemTexture(nullptr); entry.mMapTexture.reset(); @@ -429,11 +506,17 @@ namespace MWGui // If we don't do this, door markers that should be disabled will still appear on the map. mNeedDoorMarkersUpdate = true; + for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) + widget->setCoord(getMarkerCoordinates(widget, 8)); + + if (!mInterior) + mHasALastActiveCell = true; + updateMagicMarkers(); updateCustomMarkers(); } - void LocalMapBase::requestMapRender(const MWWorld::CellStore *cell) + void LocalMapBase::requestMapRender(const MWWorld::CellStore* cell) { mLocalMapRender->requestMap(cell); } @@ -444,21 +527,26 @@ namespace MWGui mLocalMap->getParent()->_updateChilds(); } + float LocalMapBase::getWidgetSize() const + { + return mLocalMapZoom * mMapWidgetSize; + } + void LocalMapBase::setPlayerPos(int cellX, int cellY, const float nx, const float ny) { - MyGUI::IntPoint pos(static_cast(mMapWidgetSize * mCellDistance + nx*mMapWidgetSize - 16), static_cast(mMapWidgetSize * mCellDistance + ny*mMapWidgetSize - 16)); - pos.left += (cellX - mCurX) * mMapWidgetSize; - pos.top -= (cellY - mCurY) * mMapWidgetSize; + MyGUI::IntPoint pos = getPosition(cellX, cellY, nx, ny) - MyGUI::IntPoint{ 16, 16 }; if (pos != mCompass->getPosition()) { - notifyPlayerUpdate (); + notifyPlayerUpdate(); mCompass->setPosition(pos); - MyGUI::IntPoint middle (pos.left+16, pos.top+16); - MyGUI::IntCoord viewsize = mLocalMap->getCoord(); - MyGUI::IntPoint viewOffset((viewsize.width / 2) - middle.left, (viewsize.height / 2) - middle.top); - mLocalMap->setViewOffset(viewOffset); + } + osg::Vec2f curPos((cellX + nx) * cellSize, (cellY + 1 - ny) * cellSize); + if ((curPos - mCurPos).length2() > 0.001) + { + mCurPos = curPos; + centerView(); } } @@ -467,12 +555,12 @@ namespace MWGui if (x == mLastDirectionX && y == mLastDirectionY) return; - notifyPlayerUpdate (); + notifyPlayerUpdate(); MyGUI::ISubWidget* main = mCompass->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); - rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); - float angle = std::atan2(x,y); + rotatingSubskin->setCenter(MyGUI::IntPoint(16, 16)); + float angle = std::atan2(x, y); rotatingSubskin->setAngle(angle); mLastDirectionX = x; @@ -483,9 +571,7 @@ namespace MWGui { std::vector markers; MWBase::World* world = MWBase::Environment::get().getWorld(); - world->listDetectedReferences( - world->getPlayerPtr(), - markers, MWBase::World::DetectionType(type)); + world->listDetectedReferences(world->getPlayerPtr(), markers, MWBase::World::DetectionType(type)); if (markers.empty()) return; @@ -503,22 +589,17 @@ namespace MWGui markerTexture = "textures\\detect_enchantment_icon.dds"; } - int counter = 0; for (const MWWorld::Ptr& ptr : markers) { const ESM::Position& worldPos = ptr.getRefData().getPosition(); - MarkerUserData markerPos (mLocalMapRender); - MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos); - MyGUI::IntCoord widgetCoord(widgetPos.left - 4, - widgetPos.top - 4, - 8, 8); - ++counter; + MarkerUserData markerPos(mLocalMapRender); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", - widgetCoord, MyGUI::Align::Default); + getMarkerCoordinates(worldPos.pos[0], worldPos.pos[1], markerPos, 8), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture(markerTexture); - markerWidget->setImageCoord(MyGUI::IntCoord(0,0,8,8)); + markerWidget->setImageCoord(MyGUI::IntCoord(0, 0, 8, 8)); markerWidget->setNeedMouseFocus(false); + markerWidget->setUserData(markerPos); mMagicMarkerWidgets.push_back(markerWidget); } } @@ -568,32 +649,33 @@ namespace MWGui if (!entry.mMapTexture) { if (!mInterior) - requestMapRender(MWBase::Environment::get().getWorld()->getExterior (entry.mCellX, entry.mCellY)); + requestMapRender(&MWBase::Environment::get().getWorldModel()->getExterior( + ESM::ExteriorCellLocation(entry.mCellX, entry.mCellY, ESM::Cell::sDefaultWorldspaceId))); osg::ref_ptr texture = mLocalMapRender->getMapTexture(entry.mCellX, entry.mCellY); if (texture) { - entry.mMapTexture.reset(new osgMyGUI::OSGTexture(texture)); + entry.mMapTexture = std::make_unique(texture); entry.mMapWidget->setRenderItemTexture(entry.mMapTexture.get()); entry.mMapWidget->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); needRedraw = true; } else - entry.mMapTexture.reset(new osgMyGUI::OSGTexture("", nullptr)); + entry.mMapTexture = std::make_unique(std::string(), nullptr); } if (!entry.mFogTexture && mFogOfWarToggled && mFogOfWarEnabled) { osg::ref_ptr tex = mLocalMapRender->getFogOfWarTexture(entry.mCellX, entry.mCellY); if (tex) { - entry.mFogTexture.reset(new osgMyGUI::OSGTexture(tex)); + entry.mFogTexture = std::make_unique(tex); entry.mFogWidget->setRenderItemTexture(entry.mFogTexture.get()); entry.mFogWidget->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } else { entry.mFogWidget->setImageTexture("black"); - entry.mFogTexture.reset(new osgMyGUI::OSGTexture("", nullptr)); + entry.mFogTexture = std::make_unique(std::string(), nullptr); } needRedraw = true; } @@ -604,63 +686,71 @@ namespace MWGui void LocalMapBase::updateDoorMarkers() { - // clear all previous door markers - for (MyGUI::Widget* widget : mDoorMarkerWidgets) - MyGUI::Gui::getInstance().destroyWidget(widget); - mDoorMarkerWidgets.clear(); - - MWBase::World* world = MWBase::Environment::get().getWorld(); - - // Retrieve the door markers we want to show std::vector doors; + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel(); + + mDoorMarkersToRecycle.insert( + mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end()); + mInteriorDoorMarkerWidgets.clear(); + if (mInterior) { - MWWorld::CellStore* cell = world->getInterior (mPrefix); + for (MyGUI::Widget* widget : mExteriorDoorMarkerWidgets) + widget->setVisible(false); + + MWWorld::CellStore& cell = worldModel->getInterior(mPrefix); world->getDoorMarkers(cell, doors); } else { - for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) + for (MapEntry& entry : mMaps) { - for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) - { - MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); - world->getDoorMarkers(cell, doors); - } + if (!entry.mMapTexture && !widgetCropped(entry.mMapWidget, mLocalMap)) + world->getDoorMarkers(worldModel->getExterior(ESM::ExteriorCellLocation( + entry.mCellX, entry.mCellY, ESM::Cell::sDefaultWorldspaceId)), + doors); } + if (doors.empty()) + return; } // Create a widget for each marker - int counter = 0; for (MWBase::World::DoorMarker& marker : doors) { std::vector destNotes; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest); - for (CustomMarkerCollection::ContainerType::const_iterator iter = markers.first; iter != markers.second; ++iter) + for (CustomMarkerCollection::ContainerType::const_iterator iter = markers.first; iter != markers.second; + ++iter) destNotes.push_back(iter->second.mNote); - MarkerUserData data (mLocalMapRender); - data.notes = destNotes; - data.caption = marker.name; - MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, data); - MyGUI::IntCoord widgetCoord(widgetPos.left - 4, - widgetPos.top - 4, - 8, 8); - ++counter; - MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", - widgetCoord, MyGUI::Align::Default); - markerWidget->setNormalColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); - markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); - markerWidget->setDepth(Local_MarkerLayer); - markerWidget->setNeedMouseFocus(true); - // Used by tooltips to not show the tooltip if marker is hidden by fog of war - markerWidget->setUserString("ToolTipType", "MapMarker"); + MyGUI::Widget* markerWidget = nullptr; + MarkerUserData* data; + if (mDoorMarkersToRecycle.empty()) + { + markerWidget = createDoorMarker(marker.name, destNotes, marker.x, marker.y); + data = markerWidget->getUserData(); + doorMarkerCreated(markerWidget); + } + else + { + markerWidget = (MarkerWidget*)mDoorMarkersToRecycle.back(); + mDoorMarkersToRecycle.pop_back(); - markerWidget->setUserData(data); - doorMarkerCreated(markerWidget); + data = markerWidget->getUserData(); + data->notes = destNotes; + data->caption = marker.name; + markerWidget->setCoord(getMarkerCoordinates(marker.x, marker.y, *data, 8)); + markerWidget->setVisible(true); + } - mDoorMarkerWidgets.push_back(markerWidget); + currentDoorMarkersWidgets().push_back(markerWidget); + if (!mInterior) + mExteriorDoorsByCell[{ data->cellX, data->cellY }].push_back(markerWidget); } + + for (auto& widget : mDoorMarkersToRecycle) + widget->setVisible(false); } void LocalMapBase::updateMagicMarkers() @@ -679,28 +769,57 @@ namespace MWGui ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); if (markedCell && markedCell->isExterior() == !mInterior - && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->mName, mPrefix))) + && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->getNameId(), mPrefix))) { - MarkerUserData markerPos (mLocalMapRender); - MyGUI::IntPoint widgetPos = getMarkerPosition(markedPosition.pos[0], markedPosition.pos[1], markerPos); - MyGUI::IntCoord widgetCoord(widgetPos.left - 4, - widgetPos.top - 4, - 8, 8); + MarkerUserData markerPos(mLocalMapRender); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", - widgetCoord, MyGUI::Align::Default); + getMarkerCoordinates(markedPosition.pos[0], markedPosition.pos[1], markerPos, 8), + MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture("textures\\menu_map_smark.dds"); markerWidget->setNeedMouseFocus(false); + markerWidget->setUserData(markerPos); mMagicMarkerWidgets.push_back(markerWidget); } redraw(); } - // ------------------------------------------------------------------------------------------ + void LocalMapBase::updateLocalMap() + { + auto mapWidgetSize = getWidgetSize(); + mLocalMap->setCanvasSize(mapWidgetSize * mNumCells, mapWidgetSize * mNumCells); - MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) + const auto size = MyGUI::IntSize(std::ceil(mapWidgetSize), std::ceil(mapWidgetSize)); + for (auto& entry : mMaps) + { + const auto position = getPosition(entry.mCellX, entry.mCellY, 0, 0); + entry.mMapWidget->setCoord({ position, size }); + entry.mFogWidget->setCoord({ position, size }); + } + + MarkerUserData markerPos(mLocalMapRender); + for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) + widget->setCoord(getMarkerCoordinates(widget, 8)); + + for (MyGUI::Widget* widget : mCustomMarkerWidgets) + { + const auto& marker = *widget->getUserData(); + widget->setCoord(getMarkerCoordinates(marker.mWorldX, marker.mWorldY, markerPos, 16)); + } + + for (MyGUI::Widget* widget : mMagicMarkerWidgets) + widget->setCoord(getMarkerCoordinates(widget, 8)); + } + + // ------------------------------------------------------------------------------------------ + MapWindow::MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, + SceneUtil::WorkQueue* workQueue) +#ifdef USE_OPENXR + : WindowPinnableBase("openmw_map_window_vr.layout") +#else : WindowPinnableBase("openmw_map_window.layout") +#endif , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) , mGlobalMap(nullptr) @@ -709,8 +828,9 @@ namespace MWGui , mGlobal(Settings::Manager::getBool("global", "Map")) , mEventBoxGlobal(nullptr) , mEventBoxLocal(nullptr) - , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue)) + , mGlobalMapRender(std::make_unique(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() + , mAllowZooming(Settings::Manager::getBool("allow zooming", "Map")) { static bool registered = false; if (!registered) @@ -723,7 +843,7 @@ namespace MWGui mEditNoteDialog.eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditOk); mEditNoteDialog.eventDeleteClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDelete); - setCoord(500,0,320,300); + setCoord(500, 0, 320, 300); getWidget(mLocalMap, "LocalMap"); getWidget(mGlobalMap, "GlobalMap"); @@ -740,23 +860,27 @@ namespace MWGui mLastScrollWindowCoordinates = mLocalMap->getCoord(); mLocalMap->eventChangeCoord += MyGUI::newDelegate(this, &MapWindow::onChangeScrollWindowCoord); - mGlobalMap->setVisible (false); + mGlobalMap->setVisible(false); getWidget(mButton, "WorldButton"); mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); - mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : "#{sWorld}"); + mButton->setCaptionWithReplacing(mGlobal ? "#{sLocal}" : "#{sWorld}"); getWidget(mEventBoxGlobal, "EventBoxGlobal"); mEventBoxGlobal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxGlobal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + if (mAllowZooming) + mEventBoxGlobal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); mEventBoxGlobal->setDepth(Global_ExploreOverlayLayer); getWidget(mEventBoxLocal, "EventBoxLocal"); mEventBoxLocal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); + if (mAllowZooming) + mEventBoxLocal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); - LocalMapBase::init(mLocalMap, mPlayerArrowLocal); + LocalMapBase::init(mLocalMap, mPlayerArrowLocal, getLocalViewingDistance()); mGlobalMap->setVisible(mGlobal); mLocalMap->setVisible(!mGlobal); @@ -791,7 +915,7 @@ namespace MWGui mEditNoteDialog.setVisible(false); } - void MapWindow::onCustomMarkerDoubleClicked(MyGUI::Widget *sender) + void MapWindow::onCustomMarkerDoubleClicked(MyGUI::Widget* sender) { mEditingMarker = *sender->getUserData(); mEditNoteDialog.setText(mEditingMarker.mNote); @@ -799,15 +923,16 @@ namespace MWGui mEditNoteDialog.setVisible(true); } - void MapWindow::onMapDoubleClicked(MyGUI::Widget *sender) + void MapWindow::onMapDoubleClicked(MyGUI::Widget* sender) { MyGUI::IntPoint clickedPos = MyGUI::InputManager::getInstance().getMousePosition(); MyGUI::IntPoint widgetPos = clickedPos - mEventBoxLocal->getAbsolutePosition(); - int x = int(widgetPos.left/float(mMapWidgetSize))-mCellDistance; - int y = (int(widgetPos.top/float(mMapWidgetSize))-mCellDistance)*-1; - float nX = widgetPos.left/float(mMapWidgetSize) - int(widgetPos.left/float(mMapWidgetSize)); - float nY = widgetPos.top/float(mMapWidgetSize) - int(widgetPos.top/float(mMapWidgetSize)); + auto mapWidgetSize = getWidgetSize(); + int x = int(widgetPos.left / float(mapWidgetSize)) - mCellDistance; + int y = (int(widgetPos.top / float(mapWidgetSize)) - mCellDistance) * -1; + float nX = widgetPos.left / float(mapWidgetSize) - int(widgetPos.left / float(mapWidgetSize)); + float nY = widgetPos.top / float(mapWidgetSize) - int(widgetPos.top / float(mapWidgetSize)); x += mCurX; y += mCurY; @@ -819,33 +944,126 @@ namespace MWGui else { worldPos.x() = (x + nX) * cellSize; - worldPos.y() = (y + (1.0f-nY)) * cellSize; + worldPos.y() = (y + (1.0f - nY)) * cellSize; } mEditingMarker.mWorldX = worldPos.x(); mEditingMarker.mWorldY = worldPos.y(); + ESM::RefId clickedId = ESM::Cell::generateIdForCell(!mInterior, LocalMapBase::mPrefix, x, y); - mEditingMarker.mCell.mPaged = !mInterior; - if (mInterior) - mEditingMarker.mCell.mWorldspace = LocalMapBase::mPrefix; - else - { - mEditingMarker.mCell.mWorldspace = ESM::CellId::sDefaultWorldspace; - mEditingMarker.mCell.mIndex.mX = x; - mEditingMarker.mCell.mIndex.mY = y; - } + mEditingMarker.mCell = clickedId; mEditNoteDialog.setVisible(true); mEditNoteDialog.showDeleteButton(false); - mEditNoteDialog.setText(""); + mEditNoteDialog.setText({}); + } + + void MapWindow::onMapZoomed(MyGUI::Widget* sender, int rel) + { + const static int localWidgetSize = Settings::Manager::getInt("local map widget size", "Map"); + const static int globalCellSize = Settings::Manager::getInt("global map cell size", "Map"); + + const bool zoomOut = rel < 0; + const bool zoomIn = !zoomOut; + const double speedDiff = zoomOut ? 1.0 / speed : speed; + const float localMapSizeInUnits = localWidgetSize * mNumCells; + + const float currentMinLocalMapZoom = std::max({ (float(globalCellSize) * 4.f) / float(localWidgetSize), + float(mLocalMap->getWidth()) / localMapSizeInUnits, float(mLocalMap->getHeight()) / localMapSizeInUnits }); + + if (mGlobal) + { + const float currentGlobalZoom = mGlobalMapZoom; + const float currentMinGlobalMapZoom + = std::min(float(mGlobalMap->getWidth()) / float(mGlobalMapRender->getWidth()), + float(mGlobalMap->getHeight()) / float(mGlobalMapRender->getHeight())); + + mGlobalMapZoom *= speedDiff; + + if (zoomIn && mGlobalMapZoom > 4.f) + { + mGlobalMapZoom = currentGlobalZoom; + mLocalMapZoom = currentMinLocalMapZoom; + onWorldButtonClicked(nullptr); + updateLocalMap(); + return; // the zoom in is too big + } + + if (zoomOut && mGlobalMapZoom < currentMinGlobalMapZoom) + { + mGlobalMapZoom = currentGlobalZoom; + return; // the zoom out is too big, we have reach the borders of the widget + } + } + else + { + auto const currentLocalZoom = mLocalMapZoom; + mLocalMapZoom *= speedDiff; + + if (zoomIn && mLocalMapZoom > 4.0f) + { + mLocalMapZoom = currentLocalZoom; + return; // the zoom in is too big + } + + if (zoomOut && mLocalMapZoom < currentMinLocalMapZoom) + { + mLocalMapZoom = currentLocalZoom; + + float zoomRatio = 4.f / mGlobalMapZoom; + mGlobalMapZoom = 4.f; + onWorldButtonClicked(nullptr); + + zoomOnCursor(zoomRatio); + return; // the zoom out is too big, we switch to the global map + } + + if (zoomOut) + mNeedDoorMarkersUpdate = true; + } + zoomOnCursor(speedDiff); + } + + void MapWindow::zoomOnCursor(float speedDiff) + { + auto map = mGlobal ? mGlobalMap : mLocalMap; + auto cursor = MyGUI::InputManager::getInstance().getMousePosition() - map->getAbsolutePosition(); + auto centerView = map->getViewOffset() - cursor; + + mGlobal ? updateGlobalMap() : updateLocalMap(); + + map->setViewOffset(MyGUI::IntPoint(std::round(centerView.left * speedDiff) + cursor.left, + std::round(centerView.top * speedDiff) + cursor.top)); + } + + void MapWindow::updateGlobalMap() + { + resizeGlobalMap(); + + float x = mCurPos.x(), y = mCurPos.y(); + if (mInterior) + { + auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); + x = pos.x(); + y = pos.y(); + } + setGlobalMapPlayerPosition(x, y); + + for (auto& [marker, col] : mGlobalMapMarkers) + { + marker.widget->setCoord(createMarkerCoords(marker.position.x(), marker.position.y(), col.size())); + marker.widget->setVisible(marker.widget->getHeight() >= 6); + } } void MapWindow::onChangeScrollWindowCoord(MyGUI::Widget* sender) { MyGUI::IntCoord currentCoordinates = sender->getCoord(); - MyGUI::IntPoint currentViewPortCenter = MyGUI::IntPoint(currentCoordinates.width / 2, currentCoordinates.height / 2); - MyGUI::IntPoint lastViewPortCenter = MyGUI::IntPoint(mLastScrollWindowCoordinates.width / 2, mLastScrollWindowCoordinates.height / 2); + MyGUI::IntPoint currentViewPortCenter + = MyGUI::IntPoint(currentCoordinates.width / 2, currentCoordinates.height / 2); + MyGUI::IntPoint lastViewPortCenter + = MyGUI::IntPoint(mLastScrollWindowCoordinates.width / 2, mLastScrollWindowCoordinates.height / 2); MyGUI::IntPoint viewPortCenterDiff = currentViewPortCenter - lastViewPortCenter; mLocalMap->setViewOffset(mLocalMap->getViewOffset() + viewPortCenterDiff); @@ -863,20 +1081,50 @@ namespace MWGui void MapWindow::renderGlobalMap() { mGlobalMapRender->render(); - mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); + resizeGlobalMap(); } - MapWindow::~MapWindow() - { - delete mGlobalMapRender; - } + MapWindow::~MapWindow() {} void MapWindow::setCellName(const std::string& cellName) { setTitle("#{sCell=" + cellName + "}"); } + MyGUI::IntCoord MapWindow::createMarkerCoords(float x, float y, float agregatedWeight) const + { + float worldX, worldY; + worldPosToGlobalMapImageSpace( + (x + 0.5f) * Constants::CellSizeInUnits, (y + 0.5f) * Constants::CellSizeInUnits, worldX, worldY); + + const float markerSize = getMarkerSize(agregatedWeight); + const float halfMarkerSize = markerSize / 2.0f; + return MyGUI::IntCoord(static_cast(worldX - halfMarkerSize), static_cast(worldY - halfMarkerSize), + markerSize, markerSize); + } + + MyGUI::Widget* MapWindow::createMarker(const std::string& name, float x, float y, float agregatedWeight) + { + MyGUI::Widget* markerWidget = mGlobalMap->createWidget( + "MarkerButton", createMarkerCoords(x, y, agregatedWeight), MyGUI::Align::Default); + markerWidget->setVisible(markerWidget->getHeight() >= 6.0); + markerWidget->setUserString("Caption_TextOneLine", "#{sCell=" + name + "}"); + setGlobalMapMarkerTooltip(markerWidget, x, y); + + markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); + + markerWidget->setNeedMouseFocus(true); + markerWidget->setColour( + MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); + markerWidget->setDepth(Global_MarkerLayer); + markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); + if (mAllowZooming) + markerWidget->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); + markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + + return markerWidget; + } + void MapWindow::addVisitedLocation(const std::string& name, int x, int y) { CellId cell; @@ -884,31 +1132,34 @@ namespace MWGui cell.second = y; if (mMarkers.insert(cell).second) { - float worldX, worldY; - mGlobalMapRender->cellTopLeftCornerToImageSpace (x, y, worldX, worldY); + MapMarkerType mapMarkerWidget = { osg::Vec2f(x, y), createMarker(name, x, y, 0) }; + mGlobalMapMarkers.emplace(mapMarkerWidget, std::vector()); - int markerSize = 12; - int offset = mGlobalMapRender->getCellSize()/2 - markerSize/2; - MyGUI::IntCoord widgetCoord( - static_cast(worldX * mGlobalMapRender->getWidth()+offset), - static_cast(worldY * mGlobalMapRender->getHeight() + offset), - markerSize, markerSize); + std::string name_ = name.substr(0, name.find(',')); + auto& entry = mGlobalMapMarkersByName[name_]; + if (!entry.widget) + { + entry = { osg::Vec2f(x, y), entry.widget }; // update the coords - MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", - widgetCoord, MyGUI::Align::Default); + entry.widget = createMarker(name_, entry.position.x(), entry.position.y(), 1); + mGlobalMapMarkers.emplace(entry, std::vector{ entry }); + } + else + { + auto it = mGlobalMapMarkers.find(entry); + auto& marker = const_cast(it->first); + auto& elements = it->second; + elements.emplace_back(mapMarkerWidget); - markerWidget->setUserString("Caption_TextOneLine", "#{sCell=" + name + "}"); + // we compute the barycenter of the entry elements => it will be the place on the world map for the + // agregated widget + marker.position = std::accumulate(elements.begin(), elements.end(), osg::Vec2f(0.f, 0.f), + [](const auto& left, const auto& right) { return left + right.position; }) + / float(elements.size()); - setGlobalMapMarkerTooltip(markerWidget, x, y); - - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - - markerWidget->setNeedMouseFocus(true); - markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); - markerWidget->setDepth(Global_MarkerLayer); - markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); - markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); - mGlobalMapMarkers[std::make_pair(x,y)] = markerWidget; + marker.widget->setCoord(createMarkerCoords(marker.position.x(), marker.position.y(), elements.size())); + marker.widget->setVisible(marker.widget->getHeight() >= 6); + } } } @@ -926,20 +1177,16 @@ namespace MWGui void MapWindow::setGlobalMapMarkerTooltip(MyGUI::Widget* markerWidget, int x, int y) { - ESM::CellId cellId; - cellId.mIndex.mX = x; - cellId.mIndex.mY = y; - cellId.mWorldspace = ESM::CellId::sDefaultWorldspace; - cellId.mPaged = true; - CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); + ESM::RefId cellRefId = ESM::RefId::esm3ExteriorCell(x, y); + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId); std::vector destNotes; for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) destNotes.push_back(it->second.mNote); if (!destNotes.empty()) { - MarkerUserData data (nullptr); - data.notes = destNotes; + MarkerUserData data(nullptr); + std::swap(data.notes, destNotes); data.caption = markerWidget->getUserString("Caption_TextOneLine"); markerWidget->setUserData(data); markerWidget->setUserString("ToolTipType", "MapMarker"); @@ -950,6 +1197,7 @@ namespace MWGui } } +<<<<<<< HEAD /* Start of tes3mp addition @@ -963,36 +1211,60 @@ namespace MWGui /* End of tes3mp addition */ +======= + float MapWindow::getMarkerSize(size_t agregatedWeight) const + { + float markerSize = 12.f * mGlobalMapZoom; + if (mGlobalMapZoom < 1) + return markerSize * std::sqrt(agregatedWeight); // we want to see agregated object + return agregatedWeight ? 0 : markerSize; // we want to see only original markers (i.e. non agregated) + } + + void MapWindow::resizeGlobalMap() + { + mGlobalMap->setCanvasSize( + mGlobalMapRender->getWidth() * mGlobalMapZoom, mGlobalMapRender->getHeight() * mGlobalMapZoom); + mGlobalMapImage->setSize( + mGlobalMapRender->getWidth() * mGlobalMapZoom, mGlobalMapRender->getHeight() * mGlobalMapZoom); + } + + void MapWindow::worldPosToGlobalMapImageSpace(float x, float y, float& imageX, float& imageY) const + { + mGlobalMapRender->worldPosToImageSpace(x, y, imageX, imageY); + imageX *= mGlobalMapZoom; + imageY *= mGlobalMapZoom; + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 void MapWindow::updateCustomMarkers() { LocalMapBase::updateCustomMarkers(); - for (auto& widgetPair : mGlobalMapMarkers) - { - int x = widgetPair.first.first; - int y = widgetPair.first.second; - MyGUI::Widget* markerWidget = widgetPair.second; - setGlobalMapMarkerTooltip(markerWidget, x, y); - } + for (auto& [widgetPair, ignore] : mGlobalMapMarkers) + setGlobalMapMarkerTooltip(widgetPair.widget, widgetPair.position.x(), widgetPair.position.y()); } void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { - if (_id!=MyGUI::MouseButton::Left) return; + if (_id != MyGUI::MouseButton::Left) + return; mLastDragPos = MyGUI::IntPoint(_left, _top); } void MapWindow::onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { - if (_id!=MyGUI::MouseButton::Left) return; + if (_id != MyGUI::MouseButton::Left) + return; MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; if (!mGlobal) - mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff ); + { + mNeedDoorMarkersUpdate = true; + mLocalMap->setViewOffset(mLocalMap->getViewOffset() + diff); + } else - mGlobalMap->setViewOffset( mGlobalMap->getViewOffset() + diff ); + mGlobalMap->setViewOffset(mGlobalMap->getViewOffset() + diff); mLastDragPos = MyGUI::IntPoint(_left, _top); } @@ -1005,16 +1277,12 @@ namespace MWGui Settings::Manager::setBool("global", "Map", mGlobal); - mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : - "#{sWorld}"); - - if (mGlobal) - globalMapUpdatePlayer (); + mButton->setCaptionWithReplacing(mGlobal ? "#{sLocal}" : "#{sWorld}"); } void MapWindow::onPinToggled() { - Settings::Manager::setBool("map pin", "Windows", mPinned); + Settings::windows().mMapPin.set(mPinned); MWBase::Environment::get().getWindowManager()->setMinimapVisibility(!mPinned); } @@ -1034,44 +1302,47 @@ namespace MWGui globalMapUpdatePlayer(); } - void MapWindow::globalMapUpdatePlayer () + void MapWindow::globalMapUpdatePlayer() { // For interiors, position is set by WindowManager via setGlobalMapPlayerPosition - if (MWBase::Environment::get().getWorld ()->isCellExterior ()) + if (MWBase::Environment::get().getWorld()->isCellExterior()) { - osg::Vec3f pos = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData().getPosition().asVec3(); + osg::Vec3f pos = MWBase::Environment::get().getWorld()->getPlayerPtr().getRefData().getPosition().asVec3(); setGlobalMapPlayerPosition(pos.x(), pos.y()); } } - void MapWindow::notifyPlayerUpdate () + void MapWindow::notifyPlayerUpdate() { - globalMapUpdatePlayer (); + globalMapUpdatePlayer(); setGlobalMapPlayerDir(mLastDirectionX, mLastDirectionY); } + void MapWindow::centerView() + { + LocalMapBase::centerView(); + // set the view offset so that player is in the center + MyGUI::IntSize viewsize = mGlobalMap->getSize(); + MyGUI::IntPoint pos = mPlayerArrowGlobal->getPosition() + MyGUI::IntPoint{ 16, 16 }; + MyGUI::IntPoint viewoffs( + static_cast(viewsize.width * 0.5f - pos.left), static_cast(viewsize.height * 0.5f - pos.top)); + mGlobalMap->setViewOffset(viewoffs); + } + void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY) { float x, y; - mGlobalMapRender->worldPosToImageSpace (worldX, worldY, x, y); - x *= mGlobalMapRender->getWidth(); - y *= mGlobalMapRender->getHeight(); - + worldPosToGlobalMapImageSpace(worldX, worldY, x, y); mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(static_cast(x - 16), static_cast(y - 16))); - - // set the view offset so that player is in the center - MyGUI::IntSize viewsize = mGlobalMap->getSize(); - MyGUI::IntPoint viewoffs(static_cast(viewsize.width * 0.5f - x), static_cast(viewsize.height *0.5 - y)); - mGlobalMap->setViewOffset(viewoffs); } void MapWindow::setGlobalMapPlayerDir(const float x, const float y) { MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); - rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); - float angle = std::atan2(x,y); + rotatingSubskin->setCenter(MyGUI::IntPoint(16, 16)); + float angle = std::atan2(x, y); rotatingSubskin->setAngle(angle); } @@ -1079,11 +1350,11 @@ namespace MWGui { if (!mGlobalMapTexture.get()) { - mGlobalMapTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getBaseTexture())); + mGlobalMapTexture = std::make_unique(mGlobalMapRender->getBaseTexture()); mGlobalMapImage->setRenderItemTexture(mGlobalMapTexture.get()); mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); - mGlobalMapOverlayTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getOverlayTexture())); + mGlobalMapOverlayTexture = std::make_unique(mGlobalMapRender->getOverlayTexture()); mGlobalMapOverlay->setRenderItemTexture(mGlobalMapOverlayTexture.get()); mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); @@ -1100,11 +1371,12 @@ namespace MWGui mChanged = true; for (auto& widgetPair : mGlobalMapMarkers) - MyGUI::Gui::getInstance().destroyWidget(widgetPair.second); + MyGUI::Gui::getInstance().destroyWidget(widgetPair.first.widget); mGlobalMapMarkers.clear(); + mGlobalMapMarkersByName.clear(); } - void MapWindow::write(ESM::ESMWriter &writer, Loading::Listener& progress) + void MapWindow::write(ESM::ESMWriter& writer, Loading::Listener& progress) { ESM::GlobalMap map; mGlobalMapRender->write(map); @@ -1116,7 +1388,7 @@ namespace MWGui writer.endRecord(ESM::REC_GMAP); } - void MapWindow::readRecord(ESM::ESMReader &reader, uint32_t type) + void MapWindow::readRecord(ESM::ESMReader& reader, uint32_t type) { if (type == ESM::REC_GMAP) { @@ -1127,7 +1399,8 @@ namespace MWGui for (const ESM::GlobalMap::CellId& cellId : map.mMarkers) { - const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(cellId.first, cellId.second); + const ESM::Cell* cell + = MWBase::Environment::get().getESMStore()->get().search(cellId.first, cellId.second); if (cell && !cell->mName.empty()) addVisitedLocation(cell->mName, cellId.first, cellId.second); } @@ -1143,17 +1416,26 @@ namespace MWGui entry.mMapWidget->setVisible(alpha == 1); } - void MapWindow::customMarkerCreated(MyGUI::Widget *marker) + void MapWindow::customMarkerCreated(MyGUI::Widget* marker) { marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); marker->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onCustomMarkerDoubleClicked); + if (mAllowZooming) + marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } - void MapWindow::doorMarkerCreated(MyGUI::Widget *marker) + void MapWindow::doorMarkerCreated(MyGUI::Widget* marker) { marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + if (mAllowZooming) + marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); + } + + void MapWindow::asyncPrepareSaveMap() + { + mGlobalMapRender->asyncWritePng(); } // ------------------------------------------------------------------- @@ -1181,7 +1463,7 @@ namespace MWGui return mDeleteButton->getVisible(); } - void EditNoteDialog::setText(const std::string &text) + void EditNoteDialog::setText(const std::string& text) { mTextEdit->setCaption(MyGUI::TextIterator::toTagsString(text)); } @@ -1198,17 +1480,17 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } - void EditNoteDialog::onCancelButtonClicked(MyGUI::Widget *sender) + void EditNoteDialog::onCancelButtonClicked(MyGUI::Widget* sender) { setVisible(false); } - void EditNoteDialog::onOkButtonClicked(MyGUI::Widget *sender) + void EditNoteDialog::onOkButtonClicked(MyGUI::Widget* sender) { eventOkClicked(); } - void EditNoteDialog::onDeleteButtonClicked(MyGUI::Widget *sender) + void EditNoteDialog::onDeleteButtonClicked(MyGUI::Widget* sender) { eventDeleteClicked(); } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index f49821cd1..e30b4d041 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -1,14 +1,17 @@ #ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H -#include +#include #include +#include + +#include + #include "windowpinnablebase.hpp" -#include - -#include +#include +#include /* Start of tes3mp addition @@ -56,22 +59,22 @@ namespace MWGui class CustomMarkerCollection { public: - void addMarker(const ESM::CustomMarker& marker, bool triggerEvent=true); - void deleteMarker (const ESM::CustomMarker& marker); + void addMarker(const ESM::CustomMarker& marker, bool triggerEvent = true); + void deleteMarker(const ESM::CustomMarker& marker); void updateMarker(const ESM::CustomMarker& marker, const std::string& newNote); void clear(); size_t size() const; - typedef std::multimap ContainerType; + typedef std::multimap ContainerType; typedef std::pair RangeType; ContainerType::const_iterator begin() const; ContainerType::const_iterator end() const; - RangeType getMarkers(const ESM::CellId& cellId) const; + RangeType getMarkers(const ESM::RefId& cellId) const; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; EventHandle_Void eventMarkersChanged; @@ -94,10 +97,10 @@ namespace MWGui public: LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled = true); virtual ~LocalMapBase(); - void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass); + void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance = Constants::CellGridRadius); void setCellPrefix(const std::string& prefix); - void setActiveCell(const int x, const int y, bool interior=false); + void setActiveCell(const int x, const int y, bool interior = false); void requestMapRender(const MWWorld::CellStore* cell); void setPlayerDir(const float x, const float y); void setPlayerPos(int cellX, int cellY, const float nx, const float ny); @@ -129,9 +132,15 @@ namespace MWGui }; protected: + void updateLocalMap(); + + float mLocalMapZoom = 1.f; MWRender::LocalMap* mLocalMapRender; - int mCurX, mCurY; + int mCurX, mCurY; // the position of the active cell on the global map (in cell coords) + bool mHasALastActiveCell = false; + osg::Vec2f mCurPos; // the position of the player in the world (in cell coords) + bool mInterior; MyGUI::ScrollView* mLocalMap; MyGUI::ImageBox* mCompass; @@ -151,21 +160,31 @@ namespace MWGui struct MapEntry { MapEntry(MyGUI::ImageBox* mapWidget, MyGUI::ImageBox* fogWidget) - : mMapWidget(mapWidget), mFogWidget(fogWidget), mCellX(0), mCellY(0) {} + : mMapWidget(mapWidget) + , mFogWidget(fogWidget) + , mCellX(0) + , mCellY(0) + { + } MyGUI::ImageBox* mMapWidget; MyGUI::ImageBox* mFogWidget; - std::shared_ptr mMapTexture; - std::shared_ptr mFogTexture; + std::unique_ptr mMapTexture; + std::unique_ptr mFogTexture; int mCellX; int mCellY; }; std::vector mMaps; // Keep track of created marker widgets, just to easily remove them later. - std::vector mDoorMarkerWidgets; + std::vector mExteriorDoorMarkerWidgets; + std::map, std::vector> mExteriorDoorsByCell; + std::vector mInteriorDoorMarkerWidgets; std::vector mMagicMarkerWidgets; std::vector mCustomMarkerWidgets; + std::vector mDoorMarkersToRecycle; + + std::vector& currentDoorMarkersWidgets(); /* Start of tes3mp addition @@ -191,9 +210,16 @@ namespace MWGui void applyFogOfWar(); - MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerUserData& markerPos); + MyGUI::IntPoint getPosition(int cellX, int cellY, float nx, float ny) const; + MyGUI::IntPoint getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const; + MyGUI::IntCoord getMarkerCoordinates( + float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const; + MyGUI::Widget* createDoorMarker( + const std::string& name, const MyGUI::VectorString& notes, float x, float y) const; + MyGUI::IntCoord getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const; virtual void notifyPlayerUpdate() {} + virtual void centerView(); virtual void notifyMapChanged() {} virtual void customMarkerCreated(MyGUI::Widget* marker) {} @@ -205,15 +231,17 @@ namespace MWGui void addDetectionMarkers(int type); void redraw(); + float getWidgetSize() const; float mMarkerUpdateTimer; float mLastDirectionX; float mLastDirectionY; + bool mNeedDoorMarkersUpdate; + private: void updateDoorMarkers(); - bool mNeedDoorMarkersUpdate; }; class EditNoteDialog : public MWGui::WindowModal @@ -256,7 +284,8 @@ namespace MWGui End of tes3mp addition */ public: - MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue); + MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, + SceneUtil::WorkQueue* workQueue); virtual ~MapWindow(); void setCellName(const std::string& cellName); @@ -273,7 +302,7 @@ namespace MWGui // reveals this cell's map on the global map void cellExplored(int x, int y); - void setGlobalMapPlayerPosition (float worldX, float worldY); + void setGlobalMapPlayerPosition(float worldX, float worldY); void setGlobalMapPlayerDir(const float x, const float y); /* @@ -308,14 +337,19 @@ namespace MWGui /// Clear all savegame-specific data void clear() override; - void write (ESM::ESMWriter& writer, Loading::Listener& progress); - void readRecord (ESM::ESMReader& reader, uint32_t type); + void write(ESM::ESMWriter& writer, Loading::Listener& progress); + void readRecord(ESM::ESMReader& reader, uint32_t type); + + void asyncPrepareSaveMap(); private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onWorldButtonClicked(MyGUI::Widget* _sender); void onMapDoubleClicked(MyGUI::Widget* sender); + void onMapZoomed(MyGUI::Widget* sender, int rel); + void zoomOnCursor(float speedDiff); + void updateGlobalMap(); void onCustomMarkerDoubleClicked(MyGUI::Widget* sender); void onNoteEditOk(); void onNoteEditDelete(); @@ -324,6 +358,11 @@ namespace MWGui void onChangeScrollWindowCoord(MyGUI::Widget* sender); void globalMapUpdatePlayer(); void setGlobalMapMarkerTooltip(MyGUI::Widget* widget, int x, int y); + float getMarkerSize(size_t agregatedWeight) const; + void resizeGlobalMap(); + void worldPosToGlobalMapImageSpace(float x, float z, float& imageX, float& imageY) const; + MyGUI::IntCoord createMarkerCoords(float x, float y, float agregatedWeight) const; + MyGUI::Widget* createMarker(const std::string& name, float x, float y, float agregatedWeight); MyGUI::ScrollView* mGlobalMap; std::unique_ptr mGlobalMapTexture; @@ -345,21 +384,33 @@ namespace MWGui MyGUI::Button* mEventBoxGlobal; MyGUI::Button* mEventBoxLocal; - MWRender::GlobalMap* mGlobalMapRender; + float mGlobalMapZoom = 1.0f; + std::unique_ptr mGlobalMapRender; - std::map, MyGUI::Widget*> mGlobalMapMarkers; + struct MapMarkerType + { + osg::Vec2f position; + MyGUI::Widget* widget = nullptr; + + bool operator<(const MapMarkerType& right) const { return widget < right.widget; } + }; + + std::map mGlobalMapMarkersByName; + std::map> mGlobalMapMarkers; EditNoteDialog mEditNoteDialog; ESM::CustomMarker mEditingMarker; + bool mAllowZooming; void onPinToggled() override; void onTitleDoubleClicked() override; void doorMarkerCreated(MyGUI::Widget* marker) override; - void customMarkerCreated(MyGUI::Widget *marker) override; + void customMarkerCreated(MyGUI::Widget* marker) override; void notifyPlayerUpdate() override; + void centerView() override; }; } #endif diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 3f857668f..3fe528dab 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -1,11 +1,12 @@ #include "merchantrepair.hpp" -#include +#include #include -#include #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -19,12 +20,15 @@ */ #include "../mwbase/world.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -32,120 +36,123 @@ namespace MWGui { -MerchantRepair::MerchantRepair() - : WindowBase("openmw_merchantrepair.layout") -{ - getWidget(mList, "RepairView"); - getWidget(mOkButton, "OkButton"); - getWidget(mGoldLabel, "PlayerGold"); - - mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onOkButtonClick); -} - -void MerchantRepair::setPtr(const MWWorld::Ptr &actor) -{ - mActor = actor; - - while (mList->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mList->getChildAt(0)); - - int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; - int currentY = 0; - - MWWorld::Ptr player = MWMechanics::getPlayer(); - int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - - MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); - int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; - for (MWWorld::ContainerStoreIterator iter (store.begin(categories)); iter!=store.end(); ++iter) + MerchantRepair::MerchantRepair() + : WindowBase("openmw_merchantrepair.layout") { - if (iter->getClass().hasItemHealth(*iter)) - { - int maxDurability = iter->getClass().getItemMaxHealth(*iter); - int durability = iter->getClass().getItemHealth(*iter); - if (maxDurability == durability || maxDurability == 0) - continue; + getWidget(mList, "RepairView"); + getWidget(mOkButton, "OkButton"); + getWidget(mGoldLabel, "PlayerGold"); - int basePrice = iter->getClass().getValue(*iter); - float fRepairMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fRepairMult")->mValue.getFloat(); - - float p = static_cast(std::max(1, basePrice)); - float r = static_cast(std::max(1, static_cast(maxDurability / p))); - - int x = static_cast((maxDurability - durability) / r); - x = static_cast(fRepairMult * x); - x = std::max(1, x); - - int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mActor, x, true); - - std::string name = iter->getClass().getName(*iter) - + " - " + MyGUI::utility::toString(price) - + MWBase::Environment::get().getWorld()->getStore().get() - .find("sgp")->mValue.getString(); - - MyGUI::Button* button = - mList->createWidget(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip - 0, - currentY, - 0, - lineHeight, - MyGUI::Align::Default - ); - - currentY += lineHeight; - - button->setUserString("Price", MyGUI::utility::toString(price)); - button->setUserData(MWWorld::Ptr(*iter)); - button->setCaptionWithReplacing(name); - button->setSize(mList->getWidth(), lineHeight); - button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); - button->setUserString("ToolTipType", "ItemPtr"); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); - } + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onOkButtonClick); } - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden - mList->setVisibleVScroll(false); - mList->setCanvasSize (MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY))); - mList->setVisibleVScroll(true); - mGoldLabel->setCaptionWithReplacing("#{sGold}: " - + MyGUI::utility::toString(playerGold)); -} + void MerchantRepair::setPtr(const MWWorld::Ptr& actor) + { + mActor = actor; -void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel) -{ - if (mList->getViewOffset().top + _rel*0.3f > 0) + while (mList->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mList->getChildAt(0)); + + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + int currentY = 0; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); + int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; + for (MWWorld::ContainerStoreIterator iter(store.begin(categories)); iter != store.end(); ++iter) + { + if (iter->getClass().hasItemHealth(*iter)) + { + int maxDurability = iter->getClass().getItemMaxHealth(*iter); + int durability = iter->getClass().getItemHealth(*iter); + if (maxDurability == durability || maxDurability == 0) + continue; + + int basePrice = iter->getClass().getValue(*iter); + float fRepairMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fRepairMult") + ->mValue.getFloat(); + + float p = static_cast(std::max(1, basePrice)); + float r = static_cast(std::max(1, static_cast(maxDurability / p))); + + int x = static_cast((maxDurability - durability) / r); + x = static_cast(fRepairMult * x); + x = std::max(1, x); + + int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mActor, x, true); + + std::string name{ iter->getClass().getName(*iter) }; + name += " - " + MyGUI::utility::toString(price) + + MWBase::Environment::get().getESMStore()->get().find("sgp")->mValue.getString(); + + MyGUI::Button* button = mList->createWidget(price <= playerGold + ? "SandTextButton" + : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip + 0, currentY, 0, lineHeight, MyGUI::Align::Default); + + currentY += lineHeight; + + button->setUserString("Price", MyGUI::utility::toString(price)); + button->setUserData(MWWorld::Ptr(*iter)); + button->setCaptionWithReplacing(name); + button->setSize(mList->getWidth(), lineHeight); + button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); + button->setUserString("ToolTipType", "ItemPtr"); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); + } + } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden + mList->setVisibleVScroll(false); + mList->setCanvasSize(MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY))); + mList->setVisibleVScroll(true); + + mGoldLabel->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); + } + + void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel) + { + if (mList->getViewOffset().top + _rel * 0.3f > 0) + mList->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mList->setViewOffset(MyGUI::IntPoint(0, static_cast(mList->getViewOffset().top + _rel * 0.3f))); + } + + void MerchantRepair::onOpen() + { + center(); + // Reset scrollbars mList->setViewOffset(MyGUI::IntPoint(0, 0)); - else - mList->setViewOffset(MyGUI::IntPoint(0, static_cast(mList->getViewOffset().top + _rel*0.3f))); -} + } -void MerchantRepair::onOpen() -{ - center(); - // Reset scrollbars - mList->setViewOffset(MyGUI::IntPoint(0, 0)); -} + void MerchantRepair::onRepairButtonClick(MyGUI::Widget* sender) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); -void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) -{ - MWWorld::Ptr player = MWMechanics::getPlayer(); + int price = MyGUI::utility::parseInt(sender->getUserString("Price")); + if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) + return; - int price = MyGUI::utility::parseInt(sender->getUserString("Price")); - if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) - return; + // repair + MWWorld::Ptr item = *sender->getUserData(); + item.getCellRef().setCharge(item.getClass().getItemMaxHealth(item)); - // repair - MWWorld::Ptr item = *sender->getUserData(); - item.getCellRef().setCharge(item.getClass().getItemMaxHealth(item)); + player.getClass().getContainerStore(player).restack(item); - player.getClass().getContainerStore(player).restack(item); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair")); - MWBase::Environment::get().getWindowManager()->playSound("Repair"); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor); + actorStats.setGoldPool(actorStats.getGoldPool() + price); +<<<<<<< HEAD // add gold to NPC trading gold pool MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor); @@ -165,13 +172,14 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) /* End of tes3mp change (major) */ +======= + setPtr(mActor); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - setPtr(mActor); -} - -void MerchantRepair::onOkButtonClick(MyGUI::Widget *sender) -{ - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair); -} + void MerchantRepair::onOkButtonClick(MyGUI::Widget* sender) + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair); + } } diff --git a/apps/openmw/mwgui/merchantrepair.hpp b/apps/openmw/mwgui/merchantrepair.hpp index f5276d7f6..c2fa1cc28 100644 --- a/apps/openmw/mwgui/merchantrepair.hpp +++ b/apps/openmw/mwgui/merchantrepair.hpp @@ -1,33 +1,33 @@ #ifndef OPENMW_MWGUI_MERCHANTREPAIR_H #define OPENMW_MWGUI_MERCHANTREPAIR_H -#include "windowbase.hpp" #include "../mwworld/ptr.hpp" +#include "windowbase.hpp" namespace MWGui { -class MerchantRepair : public WindowBase -{ -public: - MerchantRepair(); + class MerchantRepair : public WindowBase + { + public: + MerchantRepair(); - void onOpen() override; + void onOpen() override; - void setPtr(const MWWorld::Ptr& actor) override; + void setPtr(const MWWorld::Ptr& actor) override; -private: - MyGUI::ScrollView* mList; - MyGUI::Button* mOkButton; - MyGUI::TextBox* mGoldLabel; + private: + MyGUI::ScrollView* mList; + MyGUI::Button* mOkButton; + MyGUI::TextBox* mGoldLabel; - MWWorld::Ptr mActor; + MWWorld::Ptr mActor; -protected: - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void onRepairButtonClick(MyGUI::Widget* sender); - void onOkButtonClick(MyGUI::Widget* sender); -}; + protected: + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onRepairButtonClick(MyGUI::Widget* sender); + void onOkButtonClick(MyGUI::Widget* sender); + }; } diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 6efe1592f..c1f9b22bc 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -1,12 +1,12 @@ #include "messagebox.hpp" -#include -#include -#include #include +#include +#include +#include #include -#include +#include /* Start of tes3mp addition @@ -21,24 +21,20 @@ */ #include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/windowmanager.hpp" -#undef MessageBox - namespace MWGui { - MessageBoxManager::MessageBoxManager (float timePerChar) + MessageBoxManager::MessageBoxManager(float timePerChar) { - mInterMessageBoxe = nullptr; mStaticMessageBox = nullptr; mLastButtonPressed = -1; mMessageBoxSpeed = timePerChar; } - MessageBoxManager::~MessageBoxManager () + MessageBoxManager::~MessageBoxManager() { MessageBoxManager::clear(); } @@ -53,31 +49,22 @@ namespace MWGui if (mInterMessageBoxe) { mInterMessageBoxe->setVisible(false); - - delete mInterMessageBoxe; - mInterMessageBoxe = nullptr; + mInterMessageBoxe.reset(); } - for (MessageBox* messageBox : mMessageBoxes) - { - if (messageBox == mStaticMessageBox) - mStaticMessageBox = nullptr; - delete messageBox; - } mMessageBoxes.clear(); + mStaticMessageBox = nullptr; mLastButtonPressed = -1; } - void MessageBoxManager::onFrame (float frameDuration) + void MessageBoxManager::onFrame(float frameDuration) { - std::vector::iterator it; - for(it = mMessageBoxes.begin(); it != mMessageBoxes.end();) + for (auto it = mMessageBoxes.begin(); it != mMessageBoxes.end();) { (*it)->mCurrentTime += frameDuration; - if((*it)->mCurrentTime >= (*it)->mMaxTime && *it != mStaticMessageBox) + if ((*it)->mCurrentTime >= (*it)->mMaxTime && it->get() != mStaticMessageBox) { - delete *it; it = mMessageBoxes.erase(it); } else @@ -85,15 +72,16 @@ namespace MWGui } float height = 0; - it = mMessageBoxes.begin(); - while(it != mMessageBoxes.end()) + auto it = mMessageBoxes.begin(); + while (it != mMessageBoxes.end()) { (*it)->update(static_cast(height)); height += (*it)->getHeight(); ++it; } - if(mInterMessageBoxe != nullptr && mInterMessageBoxe->mMarkedToDelete) { + if (mInterMessageBoxe != nullptr && mInterMessageBoxe->mMarkedToDelete) + { mLastButtonPressed = mInterMessageBoxe->readPressedButton(); /* @@ -108,44 +96,46 @@ namespace MWGui */ mInterMessageBoxe->setVisible(false); - delete mInterMessageBoxe; - mInterMessageBoxe = nullptr; + mInterMessageBoxe.reset(); MWBase::Environment::get().getInputManager()->changeInputMode( - MWBase::Environment::get().getWindowManager()->isGuiMode()); + MWBase::Environment::get().getWindowManager()->isGuiMode()); } } - void MessageBoxManager::createMessageBox (const std::string& message, bool stat) + void MessageBoxManager::createMessageBox(std::string_view message, bool stat) { - MessageBox *box = new MessageBox(*this, message); + auto box = std::make_unique(*this, message); box->mCurrentTime = 0; - std::string realMessage = MyGUI::LanguageManager::getInstance().replaceTags(message); - box->mMaxTime = realMessage.length()*mMessageBoxSpeed; + auto realMessage = MyGUI::LanguageManager::getInstance().replaceTags({ message.data(), message.size() }); + box->mMaxTime = realMessage.length() * mMessageBoxSpeed; - if(stat) - mStaticMessageBox = box; + if (stat) + mStaticMessageBox = box.get(); - mMessageBoxes.push_back(box); + box->setVisible(mVisible); - if(mMessageBoxes.size() > 3) { - delete *mMessageBoxes.begin(); + mMessageBoxes.push_back(std::move(box)); + + if (mMessageBoxes.size() > 3) + { mMessageBoxes.erase(mMessageBoxes.begin()); } int height = 0; - for (MessageBox* messageBox : mMessageBoxes) + for (const auto& messageBox : mMessageBoxes) { messageBox->update(height); height += messageBox->getHeight(); } } - void MessageBoxManager::removeStaticMessageBox () + void MessageBoxManager::removeStaticMessageBox() { removeMessageBox(mStaticMessageBox); mStaticMessageBox = nullptr; } +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -156,15 +146,18 @@ namespace MWGui /* End of tes3mp change (major) */ +======= + bool MessageBoxManager::createInteractiveMessageBox( + std::string_view message, const std::vector& buttons) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { if (mInterMessageBoxe != nullptr) { Log(Debug::Warning) << "Warning: replacing an interactive message box that was not answered yet"; mInterMessageBoxe->setVisible(false); - delete mInterMessageBoxe; - mInterMessageBoxe = nullptr; } +<<<<<<< HEAD mInterMessageBoxe = new InteractiveMessageBox(*this, message, buttons); /* Start of tes3mp addition @@ -175,25 +168,25 @@ namespace MWGui /* End of tes3mp addition */ +======= + mInterMessageBoxe = std::make_unique(*this, std::string{ message }, buttons); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mLastButtonPressed = -1; return true; } - bool MessageBoxManager::isInteractiveMessageBox () + bool MessageBoxManager::isInteractiveMessageBox() { return mInterMessageBoxe != nullptr; } - - bool MessageBoxManager::removeMessageBox (MessageBox *msgbox) + bool MessageBoxManager::removeMessageBox(MessageBox* msgbox) { - std::vector::iterator it; - for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) + for (auto it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) { - if((*it) == msgbox) + if (it->get() == msgbox) { - delete (*it); mMessageBoxes.erase(it); return true; } @@ -201,7 +194,12 @@ namespace MWGui return false; } - int MessageBoxManager::readPressedButton (bool reset) + const std::vector>& MessageBoxManager::getActiveMessageBoxes() const + { + return mMessageBoxes; + } + + int MessageBoxManager::readPressedButton(bool reset) { int pressed = mLastButtonPressed; if (reset) @@ -209,15 +207,19 @@ namespace MWGui return pressed; } + void MessageBoxManager::setVisible(bool value) + { + mVisible = value; + for (const auto& messageBox : mMessageBoxes) + messageBox->setVisible(value); + } - - - MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message) - : Layout("openmw_messagebox.layout") - , mCurrentTime(0) - , mMaxTime(0) - , mMessageBoxManager(parMessageBoxManager) - , mMessage(message) + MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, std::string_view message) + : Layout("openmw_messagebox.layout") + , mCurrentTime(0) + , mMaxTime(0) + , mMessageBoxManager(parMessageBoxManager) + , mMessage(message) { // defines mBottomPadding = 48; @@ -228,27 +230,33 @@ namespace MWGui mMessageWidget->setCaptionWithReplacing(mMessage); } - void MessageBox::update (int height) + void MessageBox::update(int height) { MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint pos; - pos.left = (gameWindowSize.width - mMainWidget->getWidth())/2; + pos.left = (gameWindowSize.width - mMainWidget->getWidth()) / 2; pos.top = (gameWindowSize.height - mMainWidget->getHeight() - height - mBottomPadding); mMainWidget->setPosition(pos); } - int MessageBox::getHeight () + int MessageBox::getHeight() { - return mMainWidget->getHeight()+mNextBoxPadding; + return mMainWidget->getHeight() + mNextBoxPadding; } + void MessageBox::setVisible(bool value) + { + mMainWidget->setVisible(value); + } - - InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons) - : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") - , mMessageBoxManager(parMessageBoxManager) - , mButtonPressed(-1) + InteractiveMessageBox::InteractiveMessageBox( + MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons) + : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() + ? "openmw_interactive_messagebox_notransp.layout" + : "openmw_interactive_messagebox.layout") + , mMessageBoxManager(parMessageBoxManager) + , mButtonPressed(-1) { int textPadding = 10; // padding between text-widget and main-widget int textButtonPadding = 10; // padding between the text-widget und the button-widget @@ -260,7 +268,6 @@ namespace MWGui mMarkedToDelete = false; - getWidget(mMessageWidget, "message"); getWidget(mButtonsWidget, "buttons"); @@ -277,13 +284,10 @@ namespace MWGui int buttonHeight = 0; MyGUI::IntCoord dummyCoord(0, 0, 0, 0); - for(const std::string& buttonId : buttons) + for (const std::string& buttonId : buttons) { MyGUI::Button* button = mButtonsWidget->createWidget( - MyGUI::WidgetStyle::Child, - std::string("MW_Button"), - dummyCoord, - MyGUI::Align::Default); + MyGUI::WidgetStyle::Child, std::string("MW_Button"), dummyCoord, MyGUI::Align::Default); button->setCaptionWithReplacing(buttonId); button->eventMouseButtonClick += MyGUI::newDelegate(this, &InteractiveMessageBox::mousePressed); @@ -293,41 +297,42 @@ namespace MWGui if (buttonsWidth != 0) buttonsWidth += buttonLeftPadding; - int buttonWidth = button->getTextSize().width + 2*buttonLabelLeftPadding; + int buttonWidth = button->getTextSize().width + 2 * buttonLabelLeftPadding; buttonsWidth += buttonWidth; - buttonHeight = button->getTextSize().height + 2*buttonLabelTopPadding; + buttonHeight = button->getTextSize().height + 2 * buttonLabelTopPadding; if (buttonsHeight != 0) buttonsHeight += buttonTopPadding; buttonsHeight += buttonHeight; - if(buttonWidth > biggestButtonWidth) + if (buttonWidth > biggestButtonWidth) { biggestButtonWidth = buttonWidth; } } MyGUI::IntSize mainWidgetSize; - if(buttonsWidth < textSize.width) + if (buttonsWidth < textSize.width) { // on one line - mainWidgetSize.width = textSize.width + 3*textPadding; - mainWidgetSize.height = textPadding + textSize.height + textButtonPadding + buttonHeight + buttonMainPadding; + mainWidgetSize.width = textSize.width + 3 * textPadding; + mainWidgetSize.height + = textPadding + textSize.height + textButtonPadding + buttonHeight + buttonMainPadding; MyGUI::IntSize realSize = mainWidgetSize + - // To account for borders - (mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize()); + // To account for borders + (mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize()); MyGUI::IntPoint absPos; - absPos.left = (gameWindowSize.width - realSize.width)/2; - absPos.top = (gameWindowSize.height - realSize.height)/2; + absPos.left = (gameWindowSize.width - realSize.width) / 2; + absPos.top = (gameWindowSize.height - realSize.height) / 2; mMainWidget->setPosition(absPos); mMainWidget->setSize(realSize); MyGUI::IntCoord messageWidgetCoord; - messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; + messageWidgetCoord.left = (mainWidgetSize.width - textSize.width) / 2; messageWidgetCoord.top = textPadding; mMessageWidget->setCoord(messageWidgetCoord); @@ -335,15 +340,15 @@ namespace MWGui MyGUI::IntCoord buttonCord; MyGUI::IntSize buttonSize(0, buttonHeight); - int left = (mainWidgetSize.width - buttonsWidth)/2; + int left = (mainWidgetSize.width - buttonsWidth) / 2; - for(MyGUI::Button* button : mButtons) + for (MyGUI::Button* button : mButtons) { buttonCord.left = left; buttonCord.top = messageWidgetCoord.top + textSize.height + textButtonPadding; - buttonSize.width = button->getTextSize().width + 2*buttonLabelLeftPadding; - buttonSize.height = button->getTextSize().height + 2*buttonLabelTopPadding; + buttonSize.width = button->getTextSize().width + 2 * buttonLabelLeftPadding; + buttonSize.height = button->getTextSize().height + 2 * buttonLabelTopPadding; button->setCoord(buttonCord); button->setSize(buttonSize); @@ -354,11 +359,13 @@ namespace MWGui else { // among each other - if(biggestButtonWidth > textSize.width) { - mainWidgetSize.width = biggestButtonWidth + buttonTopPadding*2; + if (biggestButtonWidth > textSize.width) + { + mainWidgetSize.width = biggestButtonWidth + buttonTopPadding * 2; } - else { - mainWidgetSize.width = textSize.width + 3*textPadding; + else + { + mainWidgetSize.width = textSize.width + 3 * textPadding; } MyGUI::IntCoord buttonCord; @@ -366,13 +373,13 @@ namespace MWGui int top = textPadding + textSize.height + textButtonPadding; - for(MyGUI::Button* button : mButtons) + for (MyGUI::Button* button : mButtons) { - buttonSize.width = button->getTextSize().width + buttonLabelLeftPadding*2; - buttonSize.height = button->getTextSize().height + buttonLabelTopPadding*2; + buttonSize.width = button->getTextSize().width + buttonLabelLeftPadding * 2; + buttonSize.height = button->getTextSize().height + buttonLabelTopPadding * 2; buttonCord.top = top; - buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2; + buttonCord.left = (mainWidgetSize.width - buttonSize.width) / 2; button->setCoord(buttonCord); button->setSize(buttonSize); @@ -380,19 +387,20 @@ namespace MWGui top += buttonSize.height + buttonTopPadding; } - mainWidgetSize.height = textPadding + textSize.height + textButtonPadding + buttonsHeight + buttonMainPadding; + mainWidgetSize.height + = textPadding + textSize.height + textButtonPadding + buttonsHeight + buttonMainPadding; mMainWidget->setSize(mainWidgetSize + - // To account for borders - (mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize())); + // To account for borders + (mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize())); MyGUI::IntPoint absPos; - absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; - absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; + absPos.left = (gameWindowSize.width - mainWidgetSize.width) / 2; + absPos.top = (gameWindowSize.height - mainWidgetSize.height) / 2; mMainWidget->setPosition(absPos); MyGUI::IntCoord messageWidgetCoord; - messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; + messageWidgetCoord.left = (mainWidgetSize.width - textSize.width) / 2; messageWidgetCoord.top = textPadding; messageWidgetCoord.width = textSize.width; messageWidgetCoord.height = textSize.height; @@ -404,12 +412,14 @@ namespace MWGui MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus() { - std::vector keywords { "sOk", "sYes" }; - for(MyGUI::Button* button : mButtons) + std::vector keywords{ "sOk", "sYes" }; + for (MyGUI::Button* button : mButtons) { for (const std::string& keyword : keywords) { - if(Misc::StringUtils::ciEqual(MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}"), button->getCaption())) + if (Misc::StringUtils::ciEqual( + MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}").asUTF8(), + button->getCaption().asUTF8())) { return button; } @@ -418,18 +428,18 @@ namespace MWGui return nullptr; } - void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed) + void InteractiveMessageBox::mousePressed(MyGUI::Widget* pressed) { - buttonActivated (pressed); + buttonActivated(pressed); } - void InteractiveMessageBox::buttonActivated (MyGUI::Widget* pressed) + void InteractiveMessageBox::buttonActivated(MyGUI::Widget* pressed) { mMarkedToDelete = true; int index = 0; - for(const MyGUI::Button* button : mButtons) + for (const MyGUI::Button* button : mButtons) { - if(button == pressed) + if (button == pressed) { mButtonPressed = index; mMessageBoxManager.onButtonPressed(mButtonPressed); @@ -439,7 +449,7 @@ namespace MWGui } } - int InteractiveMessageBox::readPressedButton () + int InteractiveMessageBox::readPressedButton() { return mButtonPressed; } diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 6255c314b..b3a7a7f55 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -1,9 +1,9 @@ #ifndef MWGUI_MESSAGE_BOX_H #define MWGUI_MESSAGE_BOX_H -#include "windowbase.hpp" +#include -#undef MessageBox +#include "windowbase.hpp" namespace MyGUI { @@ -19,6 +19,7 @@ namespace MWGui class MessageBox; class MessageBoxManager { +<<<<<<< HEAD public: MessageBoxManager (float timePerChar); ~MessageBoxManager (); @@ -36,66 +37,88 @@ namespace MWGui End of tes3mp change (major) */ bool isInteractiveMessageBox (); +======= + public: + MessageBoxManager(float timePerChar); + ~MessageBoxManager(); + void onFrame(float frameDuration); + void createMessageBox(std::string_view message, bool stat = false); + void removeStaticMessageBox(); + bool createInteractiveMessageBox(std::string_view message, const std::vector& buttons); + bool isInteractiveMessageBox(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - int getMessagesCount(); + int getMessagesCount(); - const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; } + const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe.get(); } - /// Remove all message boxes - void clear(); + /// Remove all message boxes + void clear(); - bool removeMessageBox (MessageBox *msgbox); + bool removeMessageBox(MessageBox* msgbox); - /// @param reset Reset the pressed button to -1 after reading it. - int readPressedButton (bool reset=true); + /// @param reset Reset the pressed button to -1 after reading it. + int readPressedButton(bool reset = true); - typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; - // Note: this delegate unassigns itself after it was fired, i.e. works once. - EventHandle_Int eventButtonPressed; + // Note: this delegate unassigns itself after it was fired, i.e. works once. + EventHandle_Int eventButtonPressed; - void onButtonPressed(int button) { eventButtonPressed(button); eventButtonPressed.clear(); } + void onButtonPressed(int button) + { + eventButtonPressed(button); + eventButtonPressed.clear(); + } - private: - std::vector mMessageBoxes; - InteractiveMessageBox* mInterMessageBoxe; - MessageBox* mStaticMessageBox; - float mMessageBoxSpeed; - int mLastButtonPressed; + void setVisible(bool value); + + const std::vector>& getActiveMessageBoxes() const; + + private: + std::vector> mMessageBoxes; + std::unique_ptr mInterMessageBoxe; + MessageBox* mStaticMessageBox; + float mMessageBoxSpeed; + int mLastButtonPressed; + bool mVisible = true; }; class MessageBox : public Layout { - public: - MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message); - void setMessage (const std::string& message); - int getHeight (); - void update (int height); + public: + MessageBox(MessageBoxManager& parMessageBoxManager, std::string_view message); + const std::string& getMessage() { return mMessage; } + int getHeight(); + void update(int height); + void setVisible(bool value); - float mCurrentTime; - float mMaxTime; + float mCurrentTime; + float mMaxTime; - protected: - MessageBoxManager& mMessageBoxManager; - const std::string& mMessage; - MyGUI::EditBox* mMessageWidget; - int mBottomPadding; - int mNextBoxPadding; + protected: + MessageBoxManager& mMessageBoxManager; + std::string mMessage; + MyGUI::EditBox* mMessageWidget; + int mBottomPadding; + int mNextBoxPadding; }; class InteractiveMessageBox : public WindowModal { - public: - InteractiveMessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons); - void mousePressed (MyGUI::Widget* _widget); - int readPressedButton (); + public: + InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, + const std::vector& buttons); + void mousePressed(MyGUI::Widget* _widget); + int readPressedButton(); - MyGUI::Widget* getDefaultKeyFocus() override; + MyGUI::Widget* getDefaultKeyFocus() override; - bool exit() override { return false; } + bool exit() override { return false; } - bool mMarkedToDelete; + bool mMarkedToDelete; +<<<<<<< HEAD /* Start of tes3mp addition @@ -108,13 +131,17 @@ namespace MWGui private: void buttonActivated (MyGUI::Widget* _widget); +======= + private: + void buttonActivated(MyGUI::Widget* _widget); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - MessageBoxManager& mMessageBoxManager; - MyGUI::EditBox* mMessageWidget; - MyGUI::Widget* mButtonsWidget; - std::vector mButtons; + MessageBoxManager& mMessageBoxManager; + MyGUI::EditBox* mMessageWidget; + MyGUI::Widget* mButtonsWidget; + std::vector mButtons; - int mButtonPressed; + int mButtonPressed; }; } diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index 62d739657..63f81e8b4 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -3,63 +3,63 @@ namespace MWGui { - enum GuiMode + enum GuiMode { - GM_None, - GM_Settings, // Settings window - GM_Inventory, // Inventory mode - GM_Container, - GM_Companion, - GM_MainMenu, // Main menu mode + GM_None, + GM_Settings, // Settings window + GM_Inventory, // Inventory mode + GM_Container, + GM_Companion, + GM_MainMenu, // Main menu mode - GM_Journal, // Journal mode + GM_Journal, // Journal mode - GM_Scroll, // Read scroll - GM_Book, // Read book - GM_Alchemy, // Make potions - GM_Repair, + GM_Scroll, // Read scroll + GM_Book, // Read book + GM_Alchemy, // Make potions + GM_Repair, - GM_Dialogue, // NPC interaction - GM_Barter, - GM_Rest, - GM_SpellBuying, - GM_Travel, - GM_SpellCreation, - GM_Enchanting, - GM_Recharge, - GM_Training, - GM_MerchantRepair, + GM_Dialogue, // NPC interaction + GM_Barter, + GM_Rest, + GM_SpellBuying, + GM_Travel, + GM_SpellCreation, + GM_Enchanting, + GM_Recharge, + GM_Training, + GM_MerchantRepair, - GM_Levelup, + GM_Levelup, - // Startup character creation dialogs - GM_Name, - GM_Race, - GM_Birth, - GM_Class, - GM_ClassGenerate, - GM_ClassPick, - GM_ClassCreate, - GM_Review, - - GM_Loading, - GM_LoadingWallpaper, - GM_Jail, + // Startup character creation dialogs + GM_Name, + GM_Race, + GM_Birth, + GM_Class, + GM_ClassGenerate, + GM_ClassPick, + GM_ClassCreate, + GM_Review, - GM_QuickKeysMenu + GM_Loading, + GM_LoadingWallpaper, + GM_Jail, + + GM_QuickKeysMenu }; - // Windows shown in inventory mode - enum GuiWindow + // Windows shown in inventory mode + enum GuiWindow { - GW_None = 0, + GW_None = 0, - GW_Map = 0x01, - GW_Inventory = 0x02, - GW_Magic = 0x04, - GW_Stats = 0x08, + GW_Map = 0x01, + GW_Inventory = 0x02, + GW_Magic = 0x04, + GW_Stats = 0x08, - GW_ALL = 0xFF + GW_ALL = 0xFF }; } diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 5daea8f3f..bfaf01183 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -1,7 +1,7 @@ #include "pickpocketitemmodel.hpp" +#include #include -#include #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -12,25 +12,28 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" namespace MWGui { - PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& actor, ItemModel *sourceModel, bool hideItems) - : mActor(actor), mPickpocketDetected(false) + PickpocketItemModel::PickpocketItemModel( + const MWWorld::Ptr& actor, std::unique_ptr sourceModel, bool hideItems) + : mActor(actor) + , mPickpocketDetected(false) { MWWorld::Ptr player = MWMechanics::getPlayer(); - mSourceModel = sourceModel; + mSourceModel = std::move(sourceModel); float chance = player.getClass().getSkill(player, ESM::Skill::Sneak); mSourceModel->update(); - // build list of items that player is unable to find when attempts to pickpocket. if (hideItems) { - for (size_t i = 0; igetItemCount(); ++i) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + for (size_t i = 0; i < mSourceModel->getItemCount(); ++i) { - if (Misc::Rng::roll0to99() > chance) + if (Misc::Rng::roll0to99(prng) > chance) mHiddenItems.push_back(mSourceModel->getItem(i)); } } @@ -41,7 +44,7 @@ namespace MWGui return false; } - ItemStack PickpocketItemModel::getItem (ModelIndex index) + ItemStack PickpocketItemModel::getItem(ModelIndex index) { if (index < 0) throw std::runtime_error("Invalid index supplied"); @@ -59,7 +62,7 @@ namespace MWGui { mSourceModel->update(); mItems.clear(); - for (size_t i = 0; igetItemCount(); ++i) + for (size_t i = 0; i < mSourceModel->getItemCount(); ++i) { const ItemStack& item = mSourceModel->getItem(i); @@ -68,17 +71,17 @@ namespace MWGui continue; if (std::find(mHiddenItems.begin(), mHiddenItems.end(), item) == mHiddenItems.end() - && item.mType != ItemStack::Type_Equipped) + && item.mType != ItemStack::Type_Equipped) mItems.push_back(item); } } - void PickpocketItemModel::removeItem (const ItemStack &item, size_t count) + void PickpocketItemModel::removeItem(const ItemStack& item, size_t count) { ProxyItemModel::removeItem(item, count); } - bool PickpocketItemModel::onDropItem(const MWWorld::Ptr &item, int count) + bool PickpocketItemModel::onDropItem(const MWWorld::Ptr& item, int count) { // don't allow "reverse pickpocket" (it will be handled by scripts after 1.0) return false; @@ -88,8 +91,8 @@ namespace MWGui { // 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) + // If it was already detected while taking an item, no need to check now + || mPickpocketDetected) return; MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -97,13 +100,13 @@ namespace MWGui if (pickpocket.finish()) { MWBase::Environment::get().getMechanicsManager()->commitCrime( - player, mActor, MWBase::MechanicsManager::OT_Pickpocket, std::string(), 0, true); + player, mActor, MWBase::MechanicsManager::OT_Pickpocket, ESM::RefId(), 0, true); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); mPickpocketDetected = true; } } - bool PickpocketItemModel::onTakeItem(const MWWorld::Ptr &item, int count) + bool PickpocketItemModel::onTakeItem(const MWWorld::Ptr& item, int count) { if (mActor.getClass().getCreatureStats(mActor).getKnockedDown()) return mSourceModel->onTakeItem(item, count); @@ -118,14 +121,14 @@ namespace MWGui return success; } - bool PickpocketItemModel::stealItem(const MWWorld::Ptr &item, int count) + 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, std::string(), 0, true); + player, mActor, MWBase::MechanicsManager::OT_Pickpocket, ESM::RefId(), 0, true); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); mPickpocketDetected = true; return false; diff --git a/apps/openmw/mwgui/pickpocketitemmodel.hpp b/apps/openmw/mwgui/pickpocketitemmodel.hpp index e28af73d7..3ed1786d8 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.hpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.hpp @@ -6,25 +6,26 @@ namespace MWGui { - /// @brief The pickpocket item model randomly hides item stacks based on a specified chance. Equipped items are always hidden. + /// @brief The pickpocket item model randomly hides item stacks based on a specified chance. Equipped items are + /// always hidden. class PickpocketItemModel : public ProxyItemModel { public: - PickpocketItemModel (const MWWorld::Ptr& thief, ItemModel* sourceModel, bool hideItems=true); + PickpocketItemModel(const MWWorld::Ptr& thief, std::unique_ptr sourceModel, bool hideItems = true); bool allowedToUseItems() const override; - ItemStack getItem (ModelIndex index) override; + ItemStack getItem(ModelIndex index) override; size_t getItemCount() override; void update() override; - void removeItem (const ItemStack& item, size_t count) override; + void removeItem(const ItemStack& item, size_t count) override; void onClose() override; - bool onDropItem(const MWWorld::Ptr &item, int count) override; - bool onTakeItem(const MWWorld::Ptr &item, int count) override; + bool onDropItem(const MWWorld::Ptr& item, int count) override; + bool onTakeItem(const MWWorld::Ptr& item, int count) override; protected: MWWorld::Ptr mActor; bool mPickpocketDetected; - bool stealItem(const MWWorld::Ptr &item, int count); + bool stealItem(const MWWorld::Ptr& item, int count); private: std::vector mHiddenItems; diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp new file mode 100644 index 000000000..e54704d6d --- /dev/null +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -0,0 +1,483 @@ +#include "postprocessorhud.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "../mwrender/postprocessor.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +namespace MWGui +{ + void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) + { + if (MyGUI::InputManager::getInstance().isShiftPressed() + && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) + return; + + MyGUI::ListBox::onKeyButtonPressed(key, ch); + } + + PostProcessorHud::PostProcessorHud() + : WindowBase("openmw_postprocessor_hud.layout") + { + getWidget(mActiveList, "ActiveList"); + getWidget(mInactiveList, "InactiveList"); + getWidget(mConfigLayout, "ConfigLayout"); + getWidget(mFilter, "Filter"); + getWidget(mButtonActivate, "ButtonActivate"); + getWidget(mButtonDeactivate, "ButtonDeactivate"); + getWidget(mButtonUp, "ButtonUp"); + getWidget(mButtonDown, "ButtonDown"); + + mButtonActivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyActivatePressed); + mButtonDeactivate->eventMouseButtonClick + += MyGUI::newDelegate(this, &PostProcessorHud::notifyDeactivatePressed); + mButtonUp->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderUpPressed); + mButtonDown->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderDownPressed); + + mActiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); + mInactiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); + + mActiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); + mInactiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); + + mFilter->eventEditTextChange += MyGUI::newDelegate(this, &PostProcessorHud::notifyFilterChanged); + + mMainWidget->castType()->eventWindowChangeCoord + += MyGUI::newDelegate(this, &PostProcessorHud::notifyWindowResize); + + mShaderInfo = mConfigLayout->createWidget("HeaderText", {}, MyGUI::Align::Default); + mShaderInfo->setUserString("VStretch", "true"); + mShaderInfo->setUserString("HStretch", "true"); + mShaderInfo->setTextAlign(MyGUI::Align::Left | MyGUI::Align::Top); + mShaderInfo->setEditReadOnly(true); + mShaderInfo->setEditWordWrap(true); + mShaderInfo->setEditMultiLine(true); + mShaderInfo->setNeedMouseFocus(false); + + mConfigLayout->setVisibleVScroll(true); + + mConfigArea = mConfigLayout->createWidget({}, {}, MyGUI::Align::Default); + + mConfigLayout->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); + mConfigArea->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); + } + + void PostProcessorHud::notifyFilterChanged(MyGUI::EditBox* sender) + { + updateTechniques(); + } + + void PostProcessorHud::notifyWindowResize(MyGUI::Window* sender) + { + layout(); + } + + void PostProcessorHud::notifyResetButtonClicked(MyGUI::Widget* sender) + { + for (size_t i = 1; i < mConfigArea->getChildCount(); ++i) + { + if (auto* child = dynamic_cast(mConfigArea->getChildAt(i))) + child->toDefault(); + } + } + + void PostProcessorHud::notifyListChangePosition(MyGUI::ListBox* sender, size_t index) + { + if (sender == mActiveList) + mInactiveList->clearIndexSelected(); + else if (sender == mInactiveList) + mActiveList->clearIndexSelected(); + + if (index >= sender->getItemCount()) + return; + + updateConfigView(sender->getItemNameAt(index)); + } + + void PostProcessorHud::toggleTechnique(bool enabled) + { + auto* list = enabled ? mInactiveList : mActiveList; + + size_t selected = list->getIndexSelected(); + + if (selected != MyGUI::ITEM_NONE) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + mOverrideHint = list->getItemNameAt(selected); + + auto technique = *list->getItemDataAt>(selected); + if (technique->getDynamic()) + return; + + if (enabled) + processor->enableTechnique(technique); + else + processor->disableTechnique(technique); + processor->saveChain(); + } + } + + void PostProcessorHud::notifyActivatePressed(MyGUI::Widget* sender) + { + toggleTechnique(true); + } + + void PostProcessorHud::notifyDeactivatePressed(MyGUI::Widget* sender) + { + toggleTechnique(false); + } + + void PostProcessorHud::moveShader(Direction direction) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + size_t selected = mActiveList->getIndexSelected(); + + if (selected == MyGUI::ITEM_NONE) + return; + + int index = direction == Direction::Up ? static_cast(selected) - 1 : selected + 1; + index = std::clamp(index, 0, mActiveList->getItemCount() - 1); + + if (static_cast(index) != selected) + { + auto technique = *mActiveList->getItemDataAt>(selected); + if (technique->getDynamic()) + return; + + if (processor->enableTechnique(technique, index) != MWRender::PostProcessor::Status_Error) + processor->saveChain(); + } + } + + void PostProcessorHud::notifyShaderUpPressed(MyGUI::Widget* sender) + { + moveShader(Direction::Up); + } + + void PostProcessorHud::notifyShaderDownPressed(MyGUI::Widget* sender) + { + moveShader(Direction::Down); + } + + void PostProcessorHud::notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch) + { + MyGUI::ListBox* list = static_cast(sender); + + if (list->getIndexSelected() == MyGUI::ITEM_NONE) + return; + + if (key == MyGUI::KeyCode::ArrowLeft && list == mActiveList) + { + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + toggleTechnique(false); + } + else + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mInactiveList); + mActiveList->clearIndexSelected(); + select(mInactiveList, 0); + } + } + else if (key == MyGUI::KeyCode::ArrowRight && list == mInactiveList) + { + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + toggleTechnique(true); + } + else + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mActiveList); + mInactiveList->clearIndexSelected(); + select(mActiveList, 0); + } + } + else if (list == mActiveList && MyGUI::InputManager::getInstance().isShiftPressed() + && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) + { + moveShader(key == MyGUI::KeyCode::ArrowUp ? Direction::Up : Direction::Down); + } + } + + void PostProcessorHud::onOpen() + { + toggleMode(Settings::ShaderManager::Mode::Debug); + updateTechniques(); + } + + void PostProcessorHud::onClose() + { + toggleMode(Settings::ShaderManager::Mode::Normal); + } + + void PostProcessorHud::layout() + { + constexpr int padding = 12; + constexpr int padding2 = padding * 2; + mShaderInfo->setCoord( + padding, padding, mConfigLayout->getSize().width - padding2 - padding, mShaderInfo->getTextSize().height); + + int totalHeight = mShaderInfo->getTop() + mShaderInfo->getTextSize().height + padding; + + mConfigArea->setCoord({ padding, totalHeight, mShaderInfo->getSize().width, mConfigLayout->getHeight() }); + + int childHeights = 0; + MyGUI::EnumeratorWidgetPtr enumerator = mConfigArea->getEnumerator(); + while (enumerator.next()) + { + enumerator.current()->setCoord(padding, childHeights + padding, mShaderInfo->getSize().width - padding2, + enumerator.current()->getHeight()); + childHeights += enumerator.current()->getHeight() + padding; + } + totalHeight += childHeights; + + mConfigArea->setSize(mConfigArea->getWidth(), childHeights); + + mConfigLayout->setCanvasSize(mConfigLayout->getWidth() - padding2, totalHeight); + mConfigLayout->setSize(mConfigLayout->getWidth(), mConfigLayout->getParentSize().height - padding2); + } + + void PostProcessorHud::notifyMouseWheel(MyGUI::Widget* sender, int rel) + { + int offset = mConfigLayout->getViewOffset().top + rel * 0.3; + if (offset > 0) + mConfigLayout->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mConfigLayout->setViewOffset(MyGUI::IntPoint(0, static_cast(offset))); + } + + void PostProcessorHud::select(ListWrapper* list, size_t index) + { + list->setIndexSelected(index); + notifyListChangePosition(list, index); + } + + void PostProcessorHud::toggleMode(Settings::ShaderManager::Mode mode) + { + Settings::ShaderManager::get().setMode(mode); + + MWBase::Environment::get().getWorld()->getPostProcessor()->toggleMode(); + + if (!isVisible()) + return; + + if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) + updateConfigView(mInactiveList->getItemNameAt(mInactiveList->getIndexSelected())); + else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) + updateConfigView(mActiveList->getItemNameAt(mActiveList->getIndexSelected())); + } + + void PostProcessorHud::updateConfigView(const std::string& name) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + auto technique = processor->loadTechnique(name); + + if (!technique || technique->getStatus() == fx::Technique::Status::File_Not_exists) + return; + + while (mConfigArea->getChildCount() > 0) + MyGUI::Gui::getInstance().destroyWidget(mConfigArea->getChildAt(0)); + + mShaderInfo->setCaption({}); + + std::ostringstream ss; + + const std::string_view NA = "#{Interface:NotAvailableShort}"; + const char endl = '\n'; + + std::string_view author = technique->getAuthor().empty() ? NA : technique->getAuthor(); + std::string_view version = technique->getVersion().empty() ? NA : technique->getVersion(); + std::string_view description = technique->getDescription().empty() ? NA : technique->getDescription(); + + auto serializeBool = [](bool value) { return value ? "#{Interface:Yes}" : "#{Interface:No}"; }; + + const auto flags = technique->getFlags(); + + const auto flag_interior = serializeBool(!(flags & fx::Technique::Flag_Disable_Interiors)); + const auto flag_exterior = serializeBool(!(flags & fx::Technique::Flag_Disable_Exteriors)); + const auto flag_underwater = serializeBool(!(flags & fx::Technique::Flag_Disable_Underwater)); + const auto flag_abovewater = serializeBool(!(flags & fx::Technique::Flag_Disable_Abovewater)); + + switch (technique->getStatus()) + { + case fx::Technique::Status::Success: + case fx::Technique::Status::Uncompiled: + { + if (technique->getDynamic()) + ss << "#{fontcolourhtml=header}#{OMWShaders:ShaderLocked}: #{fontcolourhtml=normal} " + "#{OMWShaders:ShaderLockedDescription}" + << endl + << endl; + ss << "#{fontcolourhtml=header}#{OMWShaders:Author}: #{fontcolourhtml=normal} " << author << endl + << endl + << "#{fontcolourhtml=header}#{OMWShaders:Version}: #{fontcolourhtml=normal} " << version << endl + << endl + << "#{fontcolourhtml=header}#{OMWShaders:Description}: #{fontcolourhtml=normal} " << description + << endl + << endl + << "#{fontcolourhtml=header}#{OMWShaders:InInteriors}: #{fontcolourhtml=normal} " << flag_interior + << "#{fontcolourhtml=header} #{OMWShaders:InExteriors}: #{fontcolourhtml=normal} " << flag_exterior + << "#{fontcolourhtml=header} #{OMWShaders:Underwater}: #{fontcolourhtml=normal} " + << flag_underwater + << "#{fontcolourhtml=header} #{OMWShaders:Abovewater}: #{fontcolourhtml=normal} " + << flag_abovewater; + break; + } + case fx::Technique::Status::Parse_Error: + ss << "#{fontcolourhtml=negative}Shader Compile Error: #{fontcolourhtml=normal} <" + << std::string(technique->getName()) << "> failed to compile." << endl + << endl + << technique->getLastError(); + break; + case fx::Technique::Status::File_Not_exists: + break; + } + + mShaderInfo->setCaptionWithReplacing(ss.str()); + + if (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug) + { + if (technique->getUniformMap().size() > 0) + { + MyGUI::Button* resetButton + = mConfigArea->createWidget("MW_Button", { 0, 0, 0, 24 }, MyGUI::Align::Default); + resetButton->setCaptionWithReplacing("#{OMWShaders:ResetShader}"); + resetButton->setTextAlign(MyGUI::Align::Center); + resetButton->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); + resetButton->eventMouseButtonClick + += MyGUI::newDelegate(this, &PostProcessorHud::notifyResetButtonClicked); + } + + for (const auto& uniform : technique->getUniformMap()) + { + if (!uniform->mStatic || uniform->mSamplerType) + continue; + + if (!uniform->mHeader.empty()) + { + Gui::AutoSizedTextBox* divider = mConfigArea->createWidget( + "MW_UniformGroup", { 0, 0, 0, 34 }, MyGUI::Align::Default); + divider->setNeedMouseFocus(false); + divider->setCaptionWithReplacing(uniform->mHeader); + } + + fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget( + "MW_UniformEdit", { 0, 0, 0, 22 }, MyGUI::Align::Default); + uwidget->init(uniform); + uwidget->getLabel()->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); + } + } + + layout(); + } + + void PostProcessorHud::updateTechniques() + { + if (!isVisible()) + return; + + std::string hint; + ListWrapper* hintWidget = nullptr; + if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) + { + hint = mInactiveList->getItemNameAt(mInactiveList->getIndexSelected()); + hintWidget = mInactiveList; + } + else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) + { + hint = mActiveList->getItemNameAt(mActiveList->getIndexSelected()); + hintWidget = mActiveList; + } + + mInactiveList->removeAllItems(); + mActiveList->removeAllItems(); + + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + for (const auto& [name, _] : processor->getTechniqueMap()) + { + auto technique = processor->loadTechnique(name); + + if (!technique) + continue; + + if (!technique->getHidden() && !processor->isTechniqueEnabled(technique)) + { + std::string lowerName = Utf8Stream::lowerCaseUtf8(name); + std::string lowerCaption = mFilter->getCaption(); + lowerCaption = Utf8Stream::lowerCaseUtf8(lowerCaption); + if (lowerName.find(lowerCaption) != std::string::npos) + mInactiveList->addItem(name, technique); + } + } + + for (auto technique : processor->getTechniques()) + { + if (!technique->getHidden()) + mActiveList->addItem(technique->getName(), technique); + } + + auto tryFocus = [this](ListWrapper* widget, const std::string& hint) { + MyGUI::Widget* oldFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (oldFocus == mFilter) + return; + size_t index = widget->findItemIndexWith(hint); + + if (index != MyGUI::ITEM_NONE) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(widget); + select(widget, index); + } + }; + + if (!mOverrideHint.empty()) + { + tryFocus(mActiveList, mOverrideHint); + tryFocus(mInactiveList, mOverrideHint); + + mOverrideHint.clear(); + } + else if (hintWidget && !hint.empty()) + tryFocus(hintWidget, hint); + } + + void PostProcessorHud::registerMyGUIComponents() + { + MyGUI::FactoryManager& factory = MyGUI::FactoryManager::getInstance(); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + } +} diff --git a/apps/openmw/mwgui/postprocessorhud.hpp b/apps/openmw/mwgui/postprocessorhud.hpp new file mode 100644 index 000000000..e08d20e35 --- /dev/null +++ b/apps/openmw/mwgui/postprocessorhud.hpp @@ -0,0 +1,102 @@ +#ifndef MYGUI_POSTPROCESSOR_HUD_H +#define MYGUI_POSTPROCESSOR_HUD_H + +#include "windowbase.hpp" + +#include + +#include + +namespace MyGUI +{ + class ScrollView; + class EditBox; + class TabItem; +} +namespace Gui +{ + class AutoSizedButton; + class AutoSizedEditBox; +} + +namespace MWGui +{ + class PostProcessorHud : public WindowBase + { + class ListWrapper final : public MyGUI::ListBox + { + MYGUI_RTTI_DERIVED(ListWrapper) + protected: + void onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) override; + }; + + public: + PostProcessorHud(); + + void onOpen() override; + + void onClose() override; + + void updateTechniques(); + + void toggleMode(Settings::ShaderManager::Mode mode); + + static void registerMyGUIComponents(); + + private: + void notifyWindowResize(MyGUI::Window* sender); + + void notifyFilterChanged(MyGUI::EditBox* sender); + + void updateConfigView(const std::string& name); + + void notifyResetButtonClicked(MyGUI::Widget* sender); + + void notifyListChangePosition(MyGUI::ListBox* sender, size_t index); + + void notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch); + + void notifyActivatePressed(MyGUI::Widget* sender); + + void notifyDeactivatePressed(MyGUI::Widget* sender); + + void notifyShaderUpPressed(MyGUI::Widget* sender); + + void notifyShaderDownPressed(MyGUI::Widget* sender); + + void notifyMouseWheel(MyGUI::Widget* sender, int rel); + + enum class Direction + { + Up, + Down + }; + + void moveShader(Direction direction); + + void toggleTechnique(bool enabled); + + void select(ListWrapper* list, size_t index); + + void layout(); + + ListWrapper* mActiveList; + ListWrapper* mInactiveList; + + Gui::AutoSizedButton* mButtonActivate; + Gui::AutoSizedButton* mButtonDeactivate; + Gui::AutoSizedButton* mButtonDown; + Gui::AutoSizedButton* mButtonUp; + + MyGUI::ScrollView* mConfigLayout; + + MyGUI::Widget* mConfigArea; + + MyGUI::EditBox* mFilter; + Gui::AutoSizedEditBox* mShaderInfo; + + std::string mOverrideHint; + }; +} + +#endif diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index e19690b7d..2049bd717 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -1,14 +1,18 @@ #include "quickkeysmenu.hpp" -#include #include +#include #include #include #include -#include -#include +#include +#include +#include +#include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -22,24 +26,26 @@ */ #include "../mwworld/inventorystore.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwmechanics/spellutil.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellutil.hpp" #include "itemselection.hpp" -#include "spellview.hpp" #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" - +#include "spellview.hpp" namespace MWGui { @@ -49,25 +55,21 @@ namespace MWGui , mKey(std::vector(10)) , mSelected(nullptr) , mActivated(nullptr) - , mAssignDialog(nullptr) - , mItemSelectionDialog(nullptr) - , mMagicSelectionDialog(nullptr) { getWidget(mOkButton, "OKButton"); getWidget(mInstructionLabel, "InstructionLabel"); mMainWidget->setSize(mMainWidget->getWidth(), - mMainWidget->getHeight() + - (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight())); + mMainWidget->getHeight() + (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight())); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onOkButtonClicked); center(); for (int i = 0; i < 10; ++i) { - mKey[i].index = i+1; - getWidget(mKey[i].button, "QuickKey" + MyGUI::utility::toString(i+1)); + mKey[i].index = i + 1; + getWidget(mKey[i].button, "QuickKey" + MyGUI::utility::toString(i + 1)); mKey[i].button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); unassign(&mKey[i]); @@ -78,57 +80,65 @@ namespace MWGui { mActivated = nullptr; - for (int i=0; i<10; ++i) + for (int i = 0; i < 10; ++i) { unassign(&mKey[i]); } } - QuickKeysMenu::~QuickKeysMenu() + inline void QuickKeysMenu::validate(int index) { - delete mAssignDialog; - delete mItemSelectionDialog; - delete mMagicSelectionDialog; + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + switch (mKey[index].type) + { + case ESM::QuickKeys::Type::Unassigned: + case ESM::QuickKeys::Type::HandToHand: + case ESM::QuickKeys::Type::Magic: + break; + case ESM::QuickKeys::Type::Item: + case ESM::QuickKeys::Type::MagicItem: + { + MWWorld::Ptr item = *mKey[index].button->getUserData(); + // Make sure the item is available and is not broken + if (item.isEmpty() || item.getRefData().getCount() < 1 + || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) + { + // Try searching for a compatible replacement + item = store.findReplacement(mKey[index].id); + + if (!item.isEmpty()) + mKey[index].button->setUserData(MWWorld::Ptr(item)); + + break; + } + } + } } void QuickKeysMenu::onOpen() { WindowBase::onOpen(); - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - - // Check if quick keys are still valid - for (int i=0; i<10; ++i) + // Quick key index + for (int index = 0; index < 10; ++index) { - switch (mKey[i].type) - { - case Type_Unassigned: - case Type_HandToHand: - case Type_Magic: - break; - case Type_Item: - case Type_MagicItem: - { - MWWorld::Ptr item = *mKey[i].button->getUserData(); - // Make sure the item is available and is not broken - if (!item || item.getRefData().getCount() < 1 || - (item.getClass().hasItemHealth(item) && - item.getClass().getItemHealth(item) <= 0)) - { - // Try searching for a compatible replacement - item = store.findReplacement(mKey[i].id); - - if (item) - mKey[i].button->setUserData(MWWorld::Ptr(item)); - - break; - } - } - } + validate(index); } } + void QuickKeysMenu::onClose() + { + WindowBase::onClose(); + + if (mAssignDialog) + mAssignDialog->setVisible(false); + if (mItemSelectionDialog) + mItemSelectionDialog->setVisible(false); + if (mMagicSelectionDialog) + mMagicSelectionDialog->setVisible(false); + } + void QuickKeysMenu::unassign(keyData* key) { key->button->clearUserStrings(); @@ -139,22 +149,22 @@ namespace MWGui if (key->index == 10) { - key->type = Type_HandToHand; + key->type = ESM::QuickKeys::Type::HandToHand; - MyGUI::ImageBox* image = key->button->createWidget("ImageBox", - MyGUI::IntCoord(14, 13, 32, 32), MyGUI::Align::Default); + MyGUI::ImageBox* image = key->button->createWidget( + "ImageBox", MyGUI::IntCoord(14, 13, 32, 32), MyGUI::Align::Default); image->setImageTexture("icons\\k\\stealth_handtohand.dds"); image->setNeedMouseFocus(false); } else { - key->type = Type_Unassigned; - key->id = ""; - key->name = ""; + key->type = ESM::QuickKeys::Type::Unassigned; + key->id = ESM::RefId(); + key->name.clear(); - MyGUI::TextBox* textBox = key->button->createWidgetReal("SandText", - MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); + MyGUI::TextBox* textBox = key->button->createWidgetReal( + "SandText", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Default); textBox->setTextAlign(MyGUI::Align::Center); textBox->setCaption(MyGUI::utility::toString(key->index)); @@ -210,18 +220,18 @@ namespace MWGui mSelected = &mKey[index]; - // prevent reallocation of zero key from Type_HandToHand - if(mSelected->index == 10) + // prevent reallocation of zero key from ESM::QuickKeys::Type::HandToHand + if (mSelected->index == 10) return; // open assign dialog if (!mAssignDialog) - mAssignDialog = new QuickKeysMenuAssign(this); + mAssignDialog = std::make_unique(this); mAssignDialog->setVisible(true); } - void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) + void QuickKeysMenu::onOkButtonClicked(MyGUI::Widget* sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_QuickKeysMenu); } @@ -230,7 +240,7 @@ namespace MWGui { if (!mItemSelectionDialog) { - mItemSelectionDialog = new ItemSelectionDialog("#{sQuickMenu6}"); + mItemSelectionDialog = std::make_unique("#{sQuickMenu6}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItem); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItemCancel); } @@ -245,7 +255,7 @@ namespace MWGui { if (!mMagicSelectionDialog) { - mMagicSelectionDialog = new MagicSelectionDialog(this); + mMagicSelectionDialog = std::make_unique(this); } mMagicSelectionDialog->setVisible(true); @@ -270,7 +280,7 @@ namespace MWGui while (mSelected->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - mSelected->type = Type_Item; + mSelected->type = ESM::QuickKeys::Type::Item; mSelected->id = item.getCellRef().getRefId(); mSelected->name = item.getClass().getName(item); @@ -306,16 +316,18 @@ namespace MWGui while (mSelected->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - mSelected->type = Type_MagicItem; + mSelected->type = ESM::QuickKeys::Type::MagicItem; mSelected->id = item.getCellRef().getRefId(); mSelected->name = item.getClass().getName(item); float scale = 1.f; - MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture("textures\\menu_icon_select_magic_magic.dds"); + MyGUI::ITexture* texture + = MyGUI::RenderManager::getInstance().getTexture("textures\\menu_icon_select_magic_magic.dds"); if (texture) scale = texture->getHeight() / 64.f; - mSelected->button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(0, 0, 44*scale, 44*scale)); + mSelected->button->setFrame( + "textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(0, 0, 44 * scale, 44 * scale)); mSelected->button->setIcon(item); mSelected->button->setUserString("ToolTipType", "ItemPtr"); @@ -336,37 +348,40 @@ namespace MWGui */ } - void QuickKeysMenu::onAssignMagic(const std::string& spellId) + void QuickKeysMenu::onAssignMagic(const ESM::RefId& spellId) { assert(mSelected); while (mSelected->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); const ESM::Spell* spell = esmStore.get().find(spellId); - mSelected->type = Type_Magic; + mSelected->type = ESM::QuickKeys::Type::Magic; mSelected->id = spellId; mSelected->name = spell->mName; mSelected->button->setItem(MWWorld::Ptr()); mSelected->button->setUserString("ToolTipType", "Spell"); - mSelected->button->setUserString("Spell", spellId); + mSelected->button->setUserString("Spell", spellId.serialize()); // use the icon of the first effect const ESM::MagicEffect* effect = esmStore.get().find(spell->mEffects.mList.front().mEffectID); std::string path = effect->mIcon; + std::replace(path.begin(), path.end(), '/', '\\'); int slashPos = path.rfind('\\'); - path.insert(slashPos+1, "b_"); - path = MWBase::Environment::get().getWindowManager()->correctIconPath(path); + path.insert(slashPos + 1, "b_"); + path = Misc::ResourceHelpers::correctIconPath(path, MWBase::Environment::get().getResourceSystem()->getVFS()); float scale = 1.f; - MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture("textures\\menu_icon_select_magic.dds"); + MyGUI::ITexture* texture + = MyGUI::RenderManager::getInstance().getTexture("textures\\menu_icon_select_magic.dds"); if (texture) scale = texture->getHeight() / 64.f; - mSelected->button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(0, 0, 44*scale, 44*scale)); + mSelected->button->setFrame( + "textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(0, 0, 44 * scale, 44 * scale)); mSelected->button->setIcon(path); if (mMagicSelectionDialog) @@ -402,26 +417,27 @@ namespace MWGui { assert(index >= 1 && index <= 10); - keyData *key = &mKey[index-1]; + keyData* key = &mKey[index - 1]; MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); + const MWMechanics::CreatureStats& playerStats = player.getClass().getCreatureStats(player); + + validate(index - 1); // Delay action executing, // if player is busy for now (casting a spell, attacking someone, etc.) bool isDelayNeeded = MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player) - || playerStats.getKnockedDown() - || playerStats.getHitRecovery(); + || playerStats.getKnockedDown() || playerStats.getHitRecovery(); bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); bool isReturnNeeded = (!godmode && playerStats.isParalyzed()) || playerStats.isDead(); - if (isReturnNeeded && key->type != Type_Item) + if (isReturnNeeded) { return; } - else if (isDelayNeeded && key->type != Type_Item) + else if (isDelayNeeded) { mActivated = key; return; @@ -431,7 +447,7 @@ namespace MWGui mActivated = nullptr; } - if (key->type == Type_Item || key->type == Type_MagicItem) + if (key->type == ESM::QuickKeys::Type::Item || key->type == ESM::QuickKeys::Type::MagicItem) { MWWorld::Ptr item = *key->button->getUserData(); @@ -445,22 +461,22 @@ namespace MWGui item = nullptr; // check the item is available and not broken - if (!item || item.getRefData().getCount() < 1 || - (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) + if (item.isEmpty() || item.getRefData().getCount() < 1 + || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { item = store.findReplacement(key->id); - if (!item || item.getRefData().getCount() < 1) + if (item.isEmpty() || item.getRefData().getCount() < 1) { - MWBase::Environment::get().getWindowManager()->messageBox( - "#{sQuickMenu5} " + key->name); + MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); return; } } - if (key->type == Type_Item) + if (key->type == ESM::QuickKeys::Type::Item) { +<<<<<<< HEAD bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name(); @@ -484,13 +500,16 @@ namespace MWGui */ /* +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (!store.isEquipped(item)) MWBase::Environment::get().getWindowManager()->useItem(item); - MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + MWWorld::ConstContainerStoreIterator rightHand + = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // change draw state only if the item is in player's right hand if (rightHand != store.end() && item == *rightHand) { - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Weapon); } */ @@ -504,7 +523,7 @@ namespace MWGui End of tes3mp change (major) */ } - else if (key->type == Type_MagicItem) + else if (key->type == ESM::QuickKeys::Type::MagicItem) { // equip, if it can be equipped and isn't yet equipped @@ -525,6 +544,7 @@ namespace MWGui } store.setSelectedEnchantItem(it); +<<<<<<< HEAD MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); */ @@ -532,11 +552,14 @@ namespace MWGui /* End of tes3mp change (major) */ +======= + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Spell); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } - else if (key->type == Type_Magic) + else if (key->type == ESM::QuickKeys::Type::Magic) { - std::string spellId = key->id; + const ESM::RefId& spellId = key->id; // Make sure the player still has this spell MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); @@ -549,6 +572,7 @@ namespace MWGui } store.setSelectedEnchantItem(store.end()); +<<<<<<< HEAD MWBase::Environment::get().getWindowManager() ->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); @@ -562,11 +586,16 @@ namespace MWGui /* End of tes3mp addition */ +======= + MWBase::Environment::get().getWindowManager()->setSelectedSpell( + spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Spell); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - else if (key->type == Type_HandToHand) + else if (key->type == ESM::QuickKeys::Type::HandToHand) { - store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, player); - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight); + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Weapon); } } @@ -585,7 +614,7 @@ namespace MWGui // --------------------------------------------------------------------------------------------------------- - QuickKeysMenuAssign::QuickKeysMenuAssign (QuickKeysMenu* parent) + QuickKeysMenuAssign::QuickKeysMenuAssign(QuickKeysMenu* parent) : WindowModal("openmw_quickkeys_menu_assign.layout") , mParent(parent) { @@ -600,67 +629,57 @@ namespace MWGui mUnassignButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onUnassignButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onCancelButtonClicked); - - int maxWidth = mLabel->getTextSize ().width + 24; - maxWidth = std::max(maxWidth, mItemButton->getTextSize ().width + 24); - maxWidth = std::max(maxWidth, mMagicButton->getTextSize ().width + 24); - maxWidth = std::max(maxWidth, mUnassignButton->getTextSize ().width + 24); - maxWidth = std::max(maxWidth, mCancelButton->getTextSize ().width + 24); + int maxWidth = mLabel->getTextSize().width + 24; + maxWidth = std::max(maxWidth, mItemButton->getTextSize().width + 24); + maxWidth = std::max(maxWidth, mMagicButton->getTextSize().width + 24); + maxWidth = std::max(maxWidth, mUnassignButton->getTextSize().width + 24); + maxWidth = std::max(maxWidth, mCancelButton->getTextSize().width + 24); mMainWidget->setSize(maxWidth + 24, mMainWidget->getHeight()); mLabel->setSize(maxWidth, mLabel->getHeight()); - mItemButton->setCoord((maxWidth - mItemButton->getTextSize().width-24)/2 + 8, - mItemButton->getTop(), - mItemButton->getTextSize().width + 24, - mItemButton->getHeight()); - mMagicButton->setCoord((maxWidth - mMagicButton->getTextSize().width-24)/2 + 8, - mMagicButton->getTop(), - mMagicButton->getTextSize().width + 24, - mMagicButton->getHeight()); - mUnassignButton->setCoord((maxWidth - mUnassignButton->getTextSize().width-24)/2 + 8, - mUnassignButton->getTop(), - mUnassignButton->getTextSize().width + 24, - mUnassignButton->getHeight()); - mCancelButton->setCoord((maxWidth - mCancelButton->getTextSize().width-24)/2 + 8, - mCancelButton->getTop(), - mCancelButton->getTextSize().width + 24, - mCancelButton->getHeight()); + mItemButton->setCoord((maxWidth - mItemButton->getTextSize().width - 24) / 2 + 8, mItemButton->getTop(), + mItemButton->getTextSize().width + 24, mItemButton->getHeight()); + mMagicButton->setCoord((maxWidth - mMagicButton->getTextSize().width - 24) / 2 + 8, mMagicButton->getTop(), + mMagicButton->getTextSize().width + 24, mMagicButton->getHeight()); + mUnassignButton->setCoord((maxWidth - mUnassignButton->getTextSize().width - 24) / 2 + 8, + mUnassignButton->getTop(), mUnassignButton->getTextSize().width + 24, mUnassignButton->getHeight()); + mCancelButton->setCoord((maxWidth - mCancelButton->getTextSize().width - 24) / 2 + 8, mCancelButton->getTop(), + mCancelButton->getTextSize().width + 24, mCancelButton->getHeight()); center(); } - void QuickKeysMenu::write(ESM::ESMWriter &writer) + void QuickKeysMenu::write(ESM::ESMWriter& writer) { writer.startRecord(ESM::REC_KEYS); ESM::QuickKeys keys; // NB: The quick key with index 9 always has Hand-to-Hand type and must not be saved - for (int i=0; i<9; ++i) + for (int i = 0; i < 9; ++i) { ItemWidget* button = mKey[i].button; - int type = mKey[i].type; + const ESM::QuickKeys::Type type = mKey[i].type; ESM::QuickKeys::QuickKey key; key.mType = type; switch (type) { - case Type_Unassigned: - case Type_HandToHand: + case ESM::QuickKeys::Type::Unassigned: + case ESM::QuickKeys::Type::HandToHand: break; - case Type_Item: - case Type_MagicItem: + case ESM::QuickKeys::Type::Item: + case ESM::QuickKeys::Type::MagicItem: { MWWorld::Ptr item = *button->getUserData(); key.mId = item.getCellRef().getRefId(); break; } - case Type_Magic: - std::string spellId = button->getUserString("Spell"); - key.mId = spellId; + case ESM::QuickKeys::Type::Magic: + key.mId = ESM::RefId::deserialize(button->getUserString("Spell")); break; } @@ -672,7 +691,7 @@ namespace MWGui writer.endRecord(ESM::REC_KEYS); } - void QuickKeysMenu::readRecord(ESM::ESMReader &reader, uint32_t type) + void QuickKeysMenu::readRecord(ESM::ESMReader& reader, uint32_t type) { if (type != ESM::REC_KEYS) return; @@ -683,7 +702,7 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - int i=0; + int i = 0; for (ESM::QuickKeys::QuickKey& quickKey : keys.mKeys) { // NB: The quick key with index 9 always has Hand-to-Hand type and must not be loaded @@ -694,32 +713,32 @@ namespace MWGui switch (quickKey.mType) { - case Type_Magic: - if (MWBase::Environment::get().getWorld()->getStore().get().search(quickKey.mId)) - onAssignMagic(quickKey.mId); - break; - case Type_Item: - case Type_MagicItem: - { - // Find the item by id - MWWorld::Ptr item = store.findReplacement(quickKey.mId); - - if (item.isEmpty()) - unassign(mSelected); - else + case ESM::QuickKeys::Type::Magic: + if (MWBase::Environment::get().getESMStore()->get().search(quickKey.mId)) + onAssignMagic(quickKey.mId); + break; + case ESM::QuickKeys::Type::Item: + case ESM::QuickKeys::Type::MagicItem: { - if (quickKey.mType == Type_Item) - onAssignItem(item); - else // if (quickKey.mType == Type_MagicItem) - onAssignMagicItem(item); - } + // Find the item by id + MWWorld::Ptr item = store.findReplacement(quickKey.mId); - break; - } - case Type_Unassigned: - case Type_HandToHand: - unassign(mSelected); - break; + if (item.isEmpty()) + unassign(mSelected); + else + { + if (quickKey.mType == ESM::QuickKeys::Type::Item) + onAssignItem(item); + else // if (quickKey.mType == ESM::QuickKeys::Type::MagicItem) + onAssignMagicItem(item); + } + + break; + } + case ESM::QuickKeys::Type::Unassigned: + case ESM::QuickKeys::Type::HandToHand: + unassign(mSelected); + break; } ++i; @@ -743,7 +762,7 @@ namespace MWGui center(); } - void MagicSelectionDialog::onCancelButtonClicked (MyGUI::Widget *sender) + void MagicSelectionDialog::onCancelButtonClicked(MyGUI::Widget* sender) { exit(); } @@ -754,7 +773,7 @@ namespace MWGui return true; } - void MagicSelectionDialog::onOpen () + void MagicSelectionDialog::onOpen() { WindowModal::onOpen(); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index e1e28be46..4d6c3babf 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -1,15 +1,18 @@ #ifndef MWGUI_QUICKKEYS_H #define MWGUI_QUICKKEYS_H -#include "windowbase.hpp" +#include +#include "components/esm3/quickkeys.hpp" + +#include "itemselection.hpp" #include "spellmodel.hpp" +#include "windowbase.hpp" namespace MWGui { class QuickKeysMenuAssign; - class ItemSelectionDialog; class MagicSelectionDialog; class ItemWidget; class SpellView; @@ -18,7 +21,6 @@ namespace MWGui { public: QuickKeysMenu(); - ~QuickKeysMenu(); void onResChange(int, int) override { center(); } @@ -27,16 +29,18 @@ namespace MWGui void onUnassignButtonClicked(MyGUI::Widget* sender); void onCancelButtonClicked(MyGUI::Widget* sender); - void onAssignItem (MWWorld::Ptr item); - void onAssignItemCancel (); - void onAssignMagicItem (MWWorld::Ptr item); - void onAssignMagic (const std::string& spellId); - void onAssignMagicCancel (); + void onAssignItem(MWWorld::Ptr item); + void onAssignItemCancel(); + void onAssignMagicItem(MWWorld::Ptr item); + void onAssignMagic(const ESM::RefId& spellId); + void onAssignMagicCancel(); void onOpen() override; + void onClose() override; void activateQuickKey(int index); void updateActivatedQuickKey(); +<<<<<<< HEAD /* Start of tes3mp addition @@ -72,15 +76,20 @@ namespace MWGui */ - private: +======= + void write(ESM::ESMWriter& writer); + void readRecord(ESM::ESMReader& reader, uint32_t type); + void clear() override; - struct keyData { - int index; - ItemWidget* button; - QuickKeysMenu::QuickKeyType type; - std::string id; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 + private: + struct keyData + { + int index = -1; + ItemWidget* button = nullptr; + ESM::QuickKeys::Type type = ESM::QuickKeys::Type::Unassigned; + ESM::RefId id; std::string name; - keyData(): index(-1), button(nullptr), type(Type_Unassigned), id(""), name("") {} }; std::vector mKey; @@ -90,13 +99,14 @@ namespace MWGui MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; - QuickKeysMenuAssign* mAssignDialog; - ItemSelectionDialog* mItemSelectionDialog; - MagicSelectionDialog* mMagicSelectionDialog; + std::unique_ptr mAssignDialog; + std::unique_ptr mItemSelectionDialog; + std::unique_ptr mMagicSelectionDialog; void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); - + // Check if quick key is still valid + inline void validate(int index); void unassign(keyData* key); }; @@ -129,10 +139,9 @@ namespace MWGui QuickKeysMenu* mParent; - void onCancelButtonClicked (MyGUI::Widget* sender); + void onCancelButtonClicked(MyGUI::Widget* sender); void onModelIndexSelected(SpellModel::ModelIndex index); }; } - #endif diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 5f1cb6e59..534ba609f 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -1,21 +1,26 @@ #include "race.hpp" -#include -#include #include +#include +#include +#include #include #include #include -#include "../mwworld/esmstore.hpp" +#include +#include + #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwrender/characterpreview.hpp" +#include "../mwworld/esmstore.hpp" #include "tooltips.hpp" +#include "ustring.hpp" namespace { @@ -29,7 +34,7 @@ namespace return index; } - bool sortRaces(const std::pair& left, const std::pair& right) + bool sortRaces(const std::pair& left, const std::pair& right) { return left.second.compare(right.second) < 0; } @@ -40,21 +45,24 @@ namespace MWGui { RaceDialog::RaceDialog(osg::Group* parent, Resource::ResourceSystem* resourceSystem) - : WindowModal("openmw_chargen_race.layout") - , mParent(parent) - , mResourceSystem(resourceSystem) - , mGenderIndex(0) - , mFaceIndex(0) - , mHairIndex(0) - , mCurrentAngle(0) - , mPreviewDirty(true) + : WindowModal("openmw_chargen_race.layout") + , mParent(parent) + , mResourceSystem(resourceSystem) + , mGenderIndex(0) + , mFaceIndex(0) + , mHairIndex(0) + , mCurrentAngle(0) + , mPreviewDirty(true) { // Centre dialog center(); - setText("AppearanceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu1", "Appearance")); + setText("AppearanceT", + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu1", "Appearance")); getWidget(mPreviewImage, "PreviewImage"); + mPreviewImage->eventMouseWheel += MyGUI::newDelegate(this, &RaceDialog::onPreviewScroll); + getWidget(mHeadRotate, "HeadRotate"); mHeadRotate->setScrollRange(1000); @@ -67,19 +75,22 @@ namespace MWGui // Set up next/previous buttons MyGUI::Button *prevButton, *nextButton; - setText("GenderChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu2", "Change Sex")); + setText("GenderChoiceT", + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu2", "Change Sex")); getWidget(prevButton, "PrevGenderButton"); getWidget(nextButton, "NextGenderButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousGender); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextGender); - setText("FaceChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu3", "Change Face")); + setText("FaceChoiceT", + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu3", "Change Face")); getWidget(prevButton, "PrevFaceButton"); getWidget(nextButton, "NextFaceButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousFace); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextFace); - setText("HairChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu4", "Change Hair")); + setText("HairChoiceT", + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu4", "Change Hair")); getWidget(prevButton, "PrevHairButton"); getWidget(nextButton, "NextHairButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousHair); @@ -91,9 +102,11 @@ namespace MWGui mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onAccept); mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); - setText("SkillsT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sBonusSkillTitle", "Skill Bonus")); + setText("SkillsT", + MWBase::Environment::get().getWindowManager()->getGameSettingString("sBonusSkillTitle", "Skill Bonus")); getWidget(mSkillList, "SkillList"); - setText("SpellPowerT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu7", "Specials")); + setText("SpellPowerT", + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu7", "Specials")); getWidget(mSpellPowerList, "SpellPowerList"); MyGUI::Button* backButton; @@ -113,7 +126,7 @@ namespace MWGui MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); + okButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked); updateRaces(); @@ -127,9 +140,11 @@ namespace MWGui getWidget(okButton, "OKButton"); if (shown) - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } void RaceDialog::onOpen() @@ -145,11 +160,12 @@ namespace MWGui mPreview.reset(nullptr); mPreviewTexture.reset(nullptr); - mPreview.reset(new MWRender::RaceSelectionPreview(mParent, mResourceSystem)); + mPreview = std::make_unique(mParent, mResourceSystem); mPreview->rebuild(); - mPreview->setAngle (mCurrentAngle); + mPreview->setAngle(mCurrentAngle); - mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); + mPreviewTexture + = std::make_unique(mPreview->getTexture(), mPreview->getTextureStateSet()); mPreviewImage->setRenderItemTexture(mPreviewTexture.get()); mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); @@ -158,35 +174,35 @@ namespace MWGui setGender(proto.isMale() ? GM_Male : GM_Female); recountParts(); - for (unsigned int i=0; igetScrollRange()/2+mHeadRotate->getScrollRange()/10; + size_t initialPos = mHeadRotate->getScrollRange() / 2 + mHeadRotate->getScrollRange() / 10; mHeadRotate->setScrollPosition(initialPos); onHeadRotate(mHeadRotate, initialPos); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mRaceList); } - void RaceDialog::setRaceId(const std::string &raceId) + void RaceDialog::setRaceId(const ESM::RefId& raceId) { mCurrentRaceId = raceId; mRaceList->setIndexSelected(MyGUI::ITEM_NONE); size_t count = mRaceList->getItemCount(); for (size_t i = 0; i < count; ++i) { - if (Misc::StringUtils::ciEqual(*mRaceList->getItemDataAt(i), raceId)) + if (*mRaceList->getItemDataAt(i) == raceId) { mRaceList->setIndexSelected(i); break; @@ -211,7 +227,7 @@ namespace MWGui void RaceDialog::onOkClicked(MyGUI::Widget* _sender) { - if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) + if (mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } @@ -221,10 +237,23 @@ namespace MWGui eventBack(); } + void RaceDialog::onPreviewScroll(MyGUI::Widget*, int _delta) + { + size_t oldPos = mHeadRotate->getScrollPosition(); + size_t maxPos = mHeadRotate->getScrollRange() - 1; + size_t scrollPage = mHeadRotate->getScrollWheelPage(); + if (_delta < 0) + mHeadRotate->setScrollPosition(oldPos + std::min(maxPos - oldPos, scrollPage)); + else + mHeadRotate->setScrollPosition(oldPos - std::min(oldPos, scrollPage)); + + onHeadRotate(mHeadRotate, mHeadRotate->getScrollPosition()); + } + void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { - float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * osg::PI * 2; - mPreview->setAngle (angle); + float angle = (float(_position) / (scroll->getScrollRange() - 1) - 0.5f) * osg::PI * 2; + mPreview->setAngle(angle); mCurrentAngle = angle; } @@ -274,11 +303,11 @@ namespace MWGui if (_index == MyGUI::ITEM_NONE) return; - const std::string *raceId = mRaceList->getItemDataAt(_index); - if (Misc::StringUtils::ciEqual(mCurrentRaceId, *raceId)) + ESM::RefId& raceId = *mRaceList->getItemDataAt(_index); + if (mCurrentRaceId == raceId) return; - mCurrentRaceId = *raceId; + mCurrentRaceId = raceId; recountParts(); @@ -287,19 +316,18 @@ namespace MWGui updateSpellPowers(); } - void RaceDialog::onAccept(MyGUI::ListBox *_sender, size_t _index) + void RaceDialog::onAccept(MyGUI::ListBox* _sender, size_t _index) { onSelectRace(_sender, _index); - if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) + if (mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } - void RaceDialog::getBodyParts (int part, std::vector& out) + void RaceDialog::getBodyParts(int part, std::vector& out) { out.clear(); - const MWWorld::Store &store = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); for (const ESM::BodyPart& bodypart : store) { @@ -311,13 +339,9 @@ namespace MWGui continue; if (mGenderIndex != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) continue; - bool firstPerson = (bodypart.mId.size() >= 3) - && bodypart.mId[bodypart.mId.size()-3] == '1' - && bodypart.mId[bodypart.mId.size()-2] == 's' - && bodypart.mId[bodypart.mId.size()-1] == 't'; - if (firstPerson) + if (ESM::isFirstPersonBodyPart(bodypart)) continue; - if (Misc::StringUtils::ciEqual(bodypart.mRace, mCurrentRaceId)) + if (bodypart.mRace == mCurrentRaceId) out.push_back(bodypart.mId); } } @@ -359,10 +383,9 @@ namespace MWGui { mRaceList->removeAllItems(); - const MWWorld::Store &races = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& races = MWBase::Environment::get().getESMStore()->get(); - std::vector > items; // ID, name + std::vector> items; // ID, name for (const ESM::Race& race : races) { bool playable = race.mData.mFlags & ESM::Race::Playable; @@ -377,7 +400,7 @@ namespace MWGui for (auto& item : items) { mRaceList->addItem(item.second, item.first); - if (Misc::StringUtils::ciEqual(item.first, mCurrentRaceId)) + if (item.first == mCurrentRaceId) mRaceList->setIndexSelected(index); ++index; } @@ -395,24 +418,21 @@ namespace MWGui return; Widgets::MWSkillPtr skillWidget; - const int lineHeight = 18; + const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; MyGUI::IntCoord coord1(0, 0, mSkillList->getWidth(), 18); - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Race *race = store.get().find(mCurrentRaceId); - int count = sizeof(race->mData.mBonus)/sizeof(race->mData.mBonus[0]); // TODO: Find a portable macro for this ARRAYSIZE? - for (int i = 0; i < count; ++i) + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::Race* race = store.get().find(mCurrentRaceId); + for (const auto& bonus : race->mData.mBonus) { - int skillId = race->mData.mBonus[i].mSkill; - if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes + ESM::RefId skill = ESM::Skill::indexToRefId(bonus.mSkill); + if (skill.empty()) // Skip unknown skill indexes continue; - skillWidget = mSkillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default, - std::string("Skill") + MyGUI::utility::toString(i)); - skillWidget->setSkillNumber(skillId); - skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast(race->mData.mBonus[i].mBonus))); - ToolTips::createSkillToolTip(skillWidget, skillId); - + skillWidget = mSkillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default); + skillWidget->setSkillId(skill); + skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast(bonus.mBonus), 0.f)); + ToolTips::createSkillToolTip(skillWidget, skill); mSkillItems.push_back(skillWidget); @@ -431,19 +451,20 @@ namespace MWGui if (mCurrentRaceId.empty()) return; - const int lineHeight = 18; - MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), 18); + const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), lineHeight); - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Race *race = store.get().find(mCurrentRaceId); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::Race* race = store.get().find(mCurrentRaceId); int i = 0; - for (const std::string& spellpower : race->mPowers.mList) + for (const ESM::RefId& spellpower : race->mPowers.mList) { - Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + MyGUI::utility::toString(i)); + Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget( + "MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + MyGUI::utility::toString(i)); spellPowerWidget->setSpellId(spellpower); spellPowerWidget->setUserString("ToolTipType", "Spell"); - spellPowerWidget->setUserString("Spell", spellpower); + spellPowerWidget->setUserString("Spell", spellpower.serialize()); mSpellPowerItems.push_back(spellPowerWidget); diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 170c1dbce..aa18c2b17 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -1,11 +1,9 @@ #ifndef MWGUI_RACE_H #define MWGUI_RACE_H -#include - #include "windowbase.hpp" -#include - +#include +#include namespace MWRender { @@ -40,11 +38,11 @@ namespace MWGui GM_Female }; - const ESM::NPC &getResult() const; - const std::string &getRaceId() const { return mCurrentRaceId; } + const ESM::NPC& getResult() const; + const ESM::RefId& getRaceId() const { return mCurrentRaceId; } Gender getGender() const { return mGenderIndex == 0 ? GM_Male : GM_Female; } - void setRaceId(const std::string &raceId); + void setRaceId(const ESM::RefId& raceId); void setGender(Gender gender) { mGenderIndex = gender == GM_Male ? 0 : 1; } void setNextButtonShow(bool shown); @@ -67,6 +65,7 @@ namespace MWGui EventHandle_WindowBase eventDone; protected: + void onPreviewScroll(MyGUI::Widget* _sender, int _delta); void onHeadRotate(MyGUI::ScrollBar* _sender, size_t _position); void onSelectPreviousGender(MyGUI::Widget* _sender); @@ -91,16 +90,16 @@ namespace MWGui void updatePreview(); void recountParts(); - void getBodyParts (int part, std::vector& out); + void getBodyParts(int part, std::vector& out); osg::Group* mParent; Resource::ResourceSystem* mResourceSystem; - std::vector mAvailableHeads; - std::vector mAvailableHairs; + std::vector mAvailableHeads; + std::vector mAvailableHairs; - MyGUI::ImageBox* mPreviewImage; - MyGUI::ListBox* mRaceList; + MyGUI::ImageBox* mPreviewImage; + MyGUI::ListBox* mRaceList; MyGUI::ScrollBar* mHeadRotate; MyGUI::Widget* mSkillList; @@ -111,7 +110,7 @@ namespace MWGui int mGenderIndex, mFaceIndex, mHairIndex; - std::string mCurrentRaceId; + ESM::RefId mCurrentRaceId; float mCurrentAngle; diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index df6962c78..96ca425c6 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -4,136 +4,134 @@ #include -#include +#include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/recharge.hpp" +#include "inventoryitemmodel.hpp" +#include "itemchargeview.hpp" #include "itemselection.hpp" #include "itemwidget.hpp" -#include "itemchargeview.hpp" #include "sortfilteritemmodel.hpp" -#include "inventoryitemmodel.hpp" namespace MWGui { -Recharge::Recharge() - : WindowBase("openmw_recharge_dialog.layout") - , mItemSelectionDialog(nullptr) -{ - getWidget(mBox, "Box"); - getWidget(mGemBox, "GemBox"); - getWidget(mGemIcon, "GemIcon"); - getWidget(mChargeLabel, "ChargeLabel"); - getWidget(mCancelButton, "CancelButton"); - - mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel); - mBox->eventItemClicked += MyGUI::newDelegate(this, &Recharge::onItemClicked); - - mBox->setDisplayMode(ItemChargeView::DisplayMode_EnchantmentCharge); - - mGemIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onSelectItem); -} - -void Recharge::onOpen() -{ - center(); - - SortFilterItemModel * model = new SortFilterItemModel(new InventoryItemModel(MWMechanics::getPlayer())); - model->setFilter(SortFilterItemModel::Filter_OnlyRechargable); - mBox->setModel(model); - - // Reset scrollbars - mBox->resetScrollbars(); -} - -void Recharge::setPtr (const MWWorld::Ptr &item) -{ - mGemIcon->setItem(item); - mGemIcon->setUserString("ToolTipType", "ItemPtr"); - mGemIcon->setUserData(MWWorld::Ptr(item)); - - updateView(); -} - -void Recharge::updateView() -{ - MWWorld::Ptr gem = *mGemIcon->getUserData(); - - std::string soul = gem.getCellRef().getSoul(); - const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); - - mChargeLabel->setCaptionWithReplacing("#{sCharges} " + MyGUI::utility::toString(creature->mData.mSoul)); - - bool toolBoxVisible = (gem.getRefData().getCount() != 0); - mGemBox->setVisible(toolBoxVisible); - mGemBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); - - if (!toolBoxVisible) + Recharge::Recharge() + : WindowBase("openmw_recharge_dialog.layout") { - mGemIcon->setItem(MWWorld::Ptr()); - mGemIcon->clearUserStrings(); + getWidget(mBox, "Box"); + getWidget(mGemBox, "GemBox"); + getWidget(mGemIcon, "GemIcon"); + getWidget(mChargeLabel, "ChargeLabel"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel); + mBox->eventItemClicked += MyGUI::newDelegate(this, &Recharge::onItemClicked); + + mBox->setDisplayMode(ItemChargeView::DisplayMode_EnchantmentCharge); + + mGemIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onSelectItem); } - mBox->update(); + void Recharge::onOpen() + { + center(); - Gui::Box* box = dynamic_cast(mMainWidget); - if (box == nullptr) - throw std::runtime_error("main widget must be a box"); + SortFilterItemModel* model + = new SortFilterItemModel(std::make_unique(MWMechanics::getPlayer())); + model->setFilter(SortFilterItemModel::Filter_OnlyRechargable); + mBox->setModel(model); - box->notifyChildrenSizeChanged(); - center(); -} + // Reset scrollbars + mBox->resetScrollbars(); + } -void Recharge::onCancel(MyGUI::Widget *sender) -{ - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); -} + void Recharge::setPtr(const MWWorld::Ptr& item) + { + mGemIcon->setItem(item); + mGemIcon->setUserString("ToolTipType", "ItemPtr"); + mGemIcon->setUserData(MWWorld::Ptr(item)); -void Recharge::onSelectItem(MyGUI::Widget *sender) -{ - delete mItemSelectionDialog; - mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}"); - mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &Recharge::onItemSelected); - mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &Recharge::onItemCancel); - mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); - mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); -} + updateView(); + } -void Recharge::onItemSelected(MWWorld::Ptr item) -{ - mItemSelectionDialog->setVisible(false); + void Recharge::updateView() + { + MWWorld::Ptr gem = *mGemIcon->getUserData(); - mGemIcon->setItem(item); - mGemIcon->setUserString ("ToolTipType", "ItemPtr"); - mGemIcon->setUserData(item); + const ESM::RefId& soul = gem.getCellRef().getSoul(); + const ESM::Creature* creature = MWBase::Environment::get().getESMStore()->get().find(soul); - MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); - updateView(); -} + mChargeLabel->setCaptionWithReplacing("#{sCharges} " + MyGUI::utility::toString(creature->mData.mSoul)); -void Recharge::onItemCancel() -{ - mItemSelectionDialog->setVisible(false); -} + bool toolBoxVisible = (gem.getRefData().getCount() != 0); + mGemBox->setVisible(toolBoxVisible); + mGemBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); -void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item) -{ - MWWorld::Ptr gem = *mGemIcon->getUserData(); - if (!MWMechanics::rechargeItem(item, gem)) - return; + if (!toolBoxVisible) + { + mGemIcon->setItem(MWWorld::Ptr()); + mGemIcon->clearUserStrings(); + } - updateView(); -} + mBox->update(); + + Gui::Box* box = dynamic_cast(mMainWidget); + if (box == nullptr) + throw std::runtime_error("main widget must be a box"); + + box->notifyChildrenSizeChanged(); + center(); + } + + void Recharge::onCancel(MyGUI::Widget* sender) + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); + } + + void Recharge::onSelectItem(MyGUI::Widget* sender) + { + mItemSelectionDialog = std::make_unique("#{sSoulGemsWithSouls}"); + mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &Recharge::onItemSelected); + mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &Recharge::onItemCancel); + mItemSelectionDialog->setVisible(true); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); + } + + void Recharge::onItemSelected(MWWorld::Ptr item) + { + mItemSelectionDialog->setVisible(false); + + mGemIcon->setItem(item); + mGemIcon->setUserString("ToolTipType", "ItemPtr"); + mGemIcon->setUserData(item); + + MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); + updateView(); + } + + void Recharge::onItemCancel() + { + mItemSelectionDialog->setVisible(false); + } + + void Recharge::onItemClicked(MyGUI::Widget* sender, const MWWorld::Ptr& item) + { + MWWorld::Ptr gem = *mGemIcon->getUserData(); + if (!MWMechanics::rechargeItem(item, gem)) + return; + + updateView(); + } } diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp index c260b1554..f7afde457 100644 --- a/apps/openmw/mwgui/recharge.hpp +++ b/apps/openmw/mwgui/recharge.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWGUI_RECHARGE_H #define OPENMW_MWGUI_RECHARGE_H +#include + #include "windowbase.hpp" namespace MWWorld @@ -11,44 +13,43 @@ namespace MWWorld namespace MWGui { -class ItemSelectionDialog; -class ItemWidget; -class ItemChargeView; + class ItemSelectionDialog; + class ItemWidget; + class ItemChargeView; -class Recharge : public WindowBase -{ -public: - Recharge(); + class Recharge : public WindowBase + { + public: + Recharge(); - void onOpen() override; + void onOpen() override; - void setPtr (const MWWorld::Ptr& gem) override; + void setPtr(const MWWorld::Ptr& gem) override; -protected: - ItemChargeView* mBox; + protected: + ItemChargeView* mBox; - MyGUI::Widget* mGemBox; + MyGUI::Widget* mGemBox; - ItemWidget* mGemIcon; + ItemWidget* mGemIcon; - ItemSelectionDialog* mItemSelectionDialog; + std::unique_ptr mItemSelectionDialog; - MyGUI::TextBox* mChargeLabel; + MyGUI::TextBox* mChargeLabel; - MyGUI::Button* mCancelButton; + MyGUI::Button* mCancelButton; - void updateView(); + void updateView(); - void onSelectItem(MyGUI::Widget* sender); + void onSelectItem(MyGUI::Widget* sender); - void onItemSelected(MWWorld::Ptr item); - void onItemCancel(); + void onItemSelected(MWWorld::Ptr item); + void onItemCancel(); - void onItemClicked (MyGUI::Widget* sender, const MWWorld::Ptr& item); - void onCancel (MyGUI::Widget* sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - -}; + void onItemClicked(MyGUI::Widget* sender, const MWWorld::Ptr& item); + void onCancel(MyGUI::Widget* sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + }; } diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index 83221c4f4..de7c93d86 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -2,13 +2,9 @@ namespace MWGui { - ReferenceInterface::ReferenceInterface() - { - } + ReferenceInterface::ReferenceInterface() {} - ReferenceInterface::~ReferenceInterface() - { - } + ReferenceInterface::~ReferenceInterface() {} void ReferenceInterface::checkReferenceAvailable() { diff --git a/apps/openmw/mwgui/referenceinterface.hpp b/apps/openmw/mwgui/referenceinterface.hpp index 6428a5b54..e8b60693e 100644 --- a/apps/openmw/mwgui/referenceinterface.hpp +++ b/apps/openmw/mwgui/referenceinterface.hpp @@ -8,7 +8,8 @@ namespace MWGui /// \brief this class is intended for GUI interfaces that access an MW-Reference /// for example dialogue window accesses an NPC, or Container window accesses a Container /// these classes have to be automatically closed if the reference becomes unavailable - /// make sure that checkReferenceAvailable() is called every frame and that onReferenceUnavailable() has been overridden + /// make sure that checkReferenceAvailable() is called every frame and that onReferenceUnavailable() has been + /// overridden class ReferenceInterface { public: diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 351b97603..dc559f03b 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -6,148 +6,146 @@ #include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "inventoryitemmodel.hpp" +#include "itemchargeview.hpp" #include "itemselection.hpp" #include "itemwidget.hpp" -#include "itemchargeview.hpp" #include "sortfilteritemmodel.hpp" -#include "inventoryitemmodel.hpp" namespace MWGui { -Repair::Repair() - : WindowBase("openmw_repair.layout") - , mItemSelectionDialog(nullptr) -{ - getWidget(mRepairBox, "RepairBox"); - getWidget(mToolBox, "ToolBox"); - getWidget(mToolIcon, "ToolIcon"); - getWidget(mUsesLabel, "UsesLabel"); - getWidget(mQualityLabel, "QualityLabel"); - getWidget(mCancelButton, "CancelButton"); - - mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onCancel); - - mRepairBox->eventItemClicked += MyGUI::newDelegate(this, &Repair::onRepairItem); - mRepairBox->setDisplayMode(ItemChargeView::DisplayMode_Health); - - mToolIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onSelectItem); -} - -void Repair::onOpen() -{ - center(); - - SortFilterItemModel * model = new SortFilterItemModel(new InventoryItemModel(MWMechanics::getPlayer())); - model->setFilter(SortFilterItemModel::Filter_OnlyRepairable); - mRepairBox->setModel(model); - - // Reset scrollbars - mRepairBox->resetScrollbars(); -} - -void Repair::setPtr(const MWWorld::Ptr &item) -{ - MWBase::Environment::get().getWindowManager()->playSound("Item Repair Up"); - - mRepair.setTool(item); - - mToolIcon->setItem(item); - mToolIcon->setUserString("ToolTipType", "ItemPtr"); - mToolIcon->setUserData(MWWorld::Ptr(item)); - - updateRepairView(); -} - -void Repair::updateRepairView() -{ - MWWorld::LiveCellRef *ref = - mRepair.getTool().get(); - - int uses = mRepair.getTool().getClass().getItemHealth(mRepair.getTool()); - - float quality = ref->mBase->mData.mQuality; - - mToolIcon->setUserData(mRepair.getTool()); - - std::stringstream qualityStr; - qualityStr << std::setprecision(3) << quality; - - mUsesLabel->setCaptionWithReplacing("#{sUses} " + MyGUI::utility::toString(uses)); - mQualityLabel->setCaptionWithReplacing("#{sQuality} " + qualityStr.str()); - - bool toolBoxVisible = (mRepair.getTool().getRefData().getCount() != 0); - mToolBox->setVisible(toolBoxVisible); - mToolBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); - - if (!toolBoxVisible) + Repair::Repair() + : WindowBase("openmw_repair.layout") { - mToolIcon->setItem(MWWorld::Ptr()); - mToolIcon->clearUserStrings(); + getWidget(mRepairBox, "RepairBox"); + getWidget(mToolBox, "ToolBox"); + getWidget(mToolIcon, "ToolIcon"); + getWidget(mUsesLabel, "UsesLabel"); + getWidget(mQualityLabel, "QualityLabel"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onCancel); + + mRepairBox->eventItemClicked += MyGUI::newDelegate(this, &Repair::onRepairItem); + mRepairBox->setDisplayMode(ItemChargeView::DisplayMode_Health); + + mToolIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onSelectItem); } - mRepairBox->update(); + void Repair::onOpen() + { + center(); - Gui::Box* box = dynamic_cast(mMainWidget); - if (box == nullptr) - throw std::runtime_error("main widget must be a box"); + SortFilterItemModel* model + = new SortFilterItemModel(std::make_unique(MWMechanics::getPlayer())); + model->setFilter(SortFilterItemModel::Filter_OnlyRepairable); + mRepairBox->setModel(model); - box->notifyChildrenSizeChanged(); - center(); -} + // Reset scrollbars + mRepairBox->resetScrollbars(); + } -void Repair::onSelectItem(MyGUI::Widget *sender) -{ - delete mItemSelectionDialog; - mItemSelectionDialog = new ItemSelectionDialog("#{sRepair}"); - mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &Repair::onItemSelected); - mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &Repair::onItemCancel); - mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); - mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyRepairTools); -} + void Repair::setPtr(const MWWorld::Ptr& item) + { + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Item Repair Up")); -void Repair::onItemSelected(MWWorld::Ptr item) -{ - mItemSelectionDialog->setVisible(false); + mRepair.setTool(item); - mToolIcon->setItem(item); - mToolIcon->setUserString ("ToolTipType", "ItemPtr"); - mToolIcon->setUserData(item); + mToolIcon->setItem(item); + mToolIcon->setUserString("ToolTipType", "ItemPtr"); + mToolIcon->setUserData(MWWorld::Ptr(item)); - mRepair.setTool(item); + updateRepairView(); + } - MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); - updateRepairView(); -} + void Repair::updateRepairView() + { + MWWorld::LiveCellRef* ref = mRepair.getTool().get(); -void Repair::onItemCancel() -{ - mItemSelectionDialog->setVisible(false); -} + int uses = mRepair.getTool().getClass().getItemHealth(mRepair.getTool()); -void Repair::onCancel(MyGUI::Widget* /*sender*/) -{ - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Repair); -} + float quality = ref->mBase->mData.mQuality; -void Repair::onRepairItem(MyGUI::Widget* /*sender*/, const MWWorld::Ptr& ptr) -{ - if (!mRepair.getTool().getRefData().getCount()) - return; + mToolIcon->setUserData(mRepair.getTool()); - mRepair.repair(ptr); + std::stringstream qualityStr; + qualityStr << std::setprecision(3) << quality; - updateRepairView(); -} + mUsesLabel->setCaptionWithReplacing("#{sUses} " + MyGUI::utility::toString(uses)); + mQualityLabel->setCaptionWithReplacing("#{sQuality} " + qualityStr.str()); + + bool toolBoxVisible = (mRepair.getTool().getRefData().getCount() != 0); + mToolBox->setVisible(toolBoxVisible); + mToolBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); + + if (!toolBoxVisible) + { + mToolIcon->setItem(MWWorld::Ptr()); + mToolIcon->clearUserStrings(); + } + + mRepairBox->update(); + + Gui::Box* box = dynamic_cast(mMainWidget); + if (box == nullptr) + throw std::runtime_error("main widget must be a box"); + + box->notifyChildrenSizeChanged(); + center(); + } + + void Repair::onSelectItem(MyGUI::Widget* sender) + { + mItemSelectionDialog = std::make_unique("#{sRepair}"); + mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &Repair::onItemSelected); + mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &Repair::onItemCancel); + mItemSelectionDialog->setVisible(true); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyRepairTools); + } + + void Repair::onItemSelected(MWWorld::Ptr item) + { + mItemSelectionDialog->setVisible(false); + + mToolIcon->setItem(item); + mToolIcon->setUserString("ToolTipType", "ItemPtr"); + mToolIcon->setUserData(item); + + mRepair.setTool(item); + + MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); + updateRepairView(); + } + + void Repair::onItemCancel() + { + mItemSelectionDialog->setVisible(false); + } + + void Repair::onCancel(MyGUI::Widget* /*sender*/) + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Repair); + } + + void Repair::onRepairItem(MyGUI::Widget* /*sender*/, const MWWorld::Ptr& ptr) + { + if (!mRepair.getTool().getRefData().getCount()) + return; + + mRepair.repair(ptr); + + updateRepairView(); + } } diff --git a/apps/openmw/mwgui/repair.hpp b/apps/openmw/mwgui/repair.hpp index 701009f54..db608d9b8 100644 --- a/apps/openmw/mwgui/repair.hpp +++ b/apps/openmw/mwgui/repair.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWGUI_REPAIR_H #define OPENMW_MWGUI_REPAIR_H +#include + #include "windowbase.hpp" #include "../mwmechanics/repair.hpp" @@ -8,46 +10,45 @@ namespace MWGui { -class ItemSelectionDialog; -class ItemWidget; -class ItemChargeView; + class ItemSelectionDialog; + class ItemWidget; + class ItemChargeView; -class Repair : public WindowBase -{ -public: - Repair(); + class Repair : public WindowBase + { + public: + Repair(); - void onOpen() override; + void onOpen() override; - void setPtr (const MWWorld::Ptr& item) override; + void setPtr(const MWWorld::Ptr& item) override; -protected: - ItemChargeView* mRepairBox; + protected: + ItemChargeView* mRepairBox; - MyGUI::Widget* mToolBox; + MyGUI::Widget* mToolBox; - ItemWidget* mToolIcon; + ItemWidget* mToolIcon; - ItemSelectionDialog* mItemSelectionDialog; + std::unique_ptr mItemSelectionDialog; - MyGUI::TextBox* mUsesLabel; - MyGUI::TextBox* mQualityLabel; + MyGUI::TextBox* mUsesLabel; + MyGUI::TextBox* mQualityLabel; - MyGUI::Button* mCancelButton; + MyGUI::Button* mCancelButton; - MWMechanics::Repair mRepair; + MWMechanics::Repair mRepair; - void updateRepairView(); + void updateRepairView(); - void onSelectItem(MyGUI::Widget* sender); + void onSelectItem(MyGUI::Widget* sender); - void onItemSelected(MWWorld::Ptr item); - void onItemCancel(); + void onItemSelected(MWWorld::Ptr item); + void onItemCancel(); - void onRepairItem(MyGUI::Widget* sender, const MWWorld::Ptr& ptr); - void onCancel(MyGUI::Widget* sender); - -}; + void onRepairItem(MyGUI::Widget* sender, const MWWorld::Ptr& ptr); + void onCancel(MyGUI::Widget* sender); + }; } diff --git a/apps/openmw/mwgui/resourceskin.cpp b/apps/openmw/mwgui/resourceskin.cpp index 21ca2de54..ea081dd17 100644 --- a/apps/openmw/mwgui/resourceskin.cpp +++ b/apps/openmw/mwgui/resourceskin.cpp @@ -2,7 +2,7 @@ #include -#include +#include namespace MWGui { @@ -23,7 +23,7 @@ namespace MWGui MyGUI::IntCoord coord(0, 0, texture->getWidth(), texture->getHeight()); MyGUI::xml::ElementEnumerator basis = _node->getElementEnumerator(); - const std::string textureSize = std::to_string(coord.width) + " " + std::to_string(coord.height); + const std::string textureSize = std::to_string(coord.width) + " " + std::to_string(coord.height); _node->addAttribute("size", textureSize); while (basis.next()) { @@ -33,9 +33,9 @@ namespace MWGui const std::string basisSkinType = basis->findAttribute("type"); if (Misc::StringUtils::ciEqual(basisSkinType, "SimpleText")) continue; + bool isTileRect = Misc::StringUtils::ciEqual(basisSkinType, "TileRect"); - const std::string offset = basis->findAttribute("offset"); - if (!offset.empty()) + if (!basis->findAttribute("offset").empty()) continue; basis->addAttribute("offset", coord); @@ -45,19 +45,17 @@ namespace MWGui { if (state->getName() == "State") { - const std::string stateOffset = state->findAttribute("offset"); - if (!stateOffset.empty()) + if (!state->findAttribute("offset").empty()) continue; state->addAttribute("offset", coord); - if (Misc::StringUtils::ciEqual(basisSkinType, "TileRect")) + if (isTileRect) { MyGUI::xml::ElementEnumerator property = state->getElementEnumerator(); bool hasTileSize = false; while (property.next("Property")) { - const std::string key = property->findAttribute("key"); - if (key != "TileSize") + if (property->findAttribute("key") != "TileSize") continue; hasTileSize = true; diff --git a/apps/openmw/mwgui/resourceskin.hpp b/apps/openmw/mwgui/resourceskin.hpp index fd1977e66..1b66c22fb 100644 --- a/apps/openmw/mwgui/resourceskin.hpp +++ b/apps/openmw/mwgui/resourceskin.hpp @@ -7,7 +7,7 @@ namespace MWGui { class AutoSizedResourceSkin final : public MyGUI::ResourceSkin { - MYGUI_RTTI_DERIVED( AutoSizedResourceSkin ) + MYGUI_RTTI_DERIVED(AutoSizedResourceSkin) public: void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) override; diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 101b6956d..39ab84246 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -2,21 +2,27 @@ #include -#include -#include +#include #include +#include +#include + +#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/esmstore.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/autocalcspell.hpp" +#include "../mwworld/esmstore.hpp" #include "tooltips.hpp" +#include "ustring.hpp" namespace { - void adjustButtonSize(MyGUI::Button *button) + void adjustButtonSize(MyGUI::Button* button) { // adjust size of button to fit its text MyGUI::IntSize size = button->getTextSize(); @@ -27,8 +33,8 @@ namespace namespace MWGui { ReviewDialog::ReviewDialog() - : WindowModal("openmw_chargen_review.layout"), - mUpdateSkillArea(false) + : WindowModal("openmw_chargen_review.layout") + , mUpdateSkillArea(false) { // Centre dialog center(); @@ -57,36 +63,45 @@ namespace MWGui // Setup dynamic stats getWidget(mHealth, "Health"); - mHealth->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sHealth", "")); + mHealth->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sHealth", {})); mHealth->setValue(45, 45); getWidget(mMagicka, "Magicka"); - mMagicka->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sMagic", "")); + mMagicka->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sMagic", {})); mMagicka->setValue(50, 50); getWidget(mFatigue, "Fatigue"); - mFatigue->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFatigue", "")); + mFatigue->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFatigue", {})); mFatigue->setValue(160, 160); // Setup attributes - Widgets::MWAttributePtr attribute; - for (int idx = 0; idx < ESM::Attribute::Length; ++idx) + MyGUI::Widget* attributes = getWidget("Attributes"); + const auto& store = MWBase::Environment::get().getWorld()->getStore().get(); + MyGUI::IntCoord coord{ 8, 4, 250, 18 }; + for (const ESM::Attribute& attribute : store) { - getWidget(attribute, std::string("Attribute") + MyGUI::utility::toString(idx)); - mAttributeWidgets.insert(std::make_pair(static_cast(ESM::Attribute::sAttributeIds[idx]), attribute)); - attribute->setAttributeId(ESM::Attribute::sAttributeIds[idx]); - attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue()); + auto* widget + = attributes->createWidget("MW_StatNameValue", coord, MyGUI::Align::Default); + mAttributeWidgets.emplace(attribute.mId, widget); + widget->setUserString("ToolTipType", "Layout"); + widget->setUserString("ToolTipLayout", "AttributeToolTip"); + widget->setUserString("Caption_AttributeName", attribute.mName); + widget->setUserString("Caption_AttributeDescription", attribute.mDescription); + widget->setUserString("ImageTexture_AttributeImage", attribute.mIcon); + widget->setAttributeId(attribute.mId); + widget->setAttributeValue(Widgets::MWAttribute::AttributeValue()); + coord.top += coord.height; } // Setup skills getWidget(mSkillView, "SkillView"); mSkillView->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - for (int i = 0; i < ESM::Skill::Length; ++i) + for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get()) { - mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); - mSkillWidgetMap.insert(std::make_pair(i, static_cast (nullptr))); + mSkillValues.emplace(skill.mId, MWMechanics::SkillValue()); + mSkillWidgetMap.emplace(skill.mId, static_cast(nullptr)); } MyGUI::Button* backButton; @@ -113,17 +128,16 @@ namespace MWGui } } - void ReviewDialog::setPlayerName(const std::string &name) + void ReviewDialog::setPlayerName(const std::string& name) { mNameWidget->setCaption(name); } - void ReviewDialog::setRace(const std::string &raceId) + void ReviewDialog::setRace(const ESM::RefId& raceId) { mRaceId = raceId; - const ESM::Race *race = - MWBase::Environment::get().getWorld()->getStore().get().search(mRaceId); + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().search(mRaceId); if (race) { ToolTips::createRaceToolTip(mRaceWidget, race); @@ -140,12 +154,12 @@ namespace MWGui ToolTips::createClassToolTip(mClassWidget, mKlass); } - void ReviewDialog::setBirthSign(const std::string& signId) + void ReviewDialog::setBirthSign(const ESM::RefId& signId) { mBirthSignId = signId; - const ESM::BirthSign *sign = - MWBase::Environment::get().getWorld()->getStore().get().search(mBirthSignId); + const ESM::BirthSign* sign + = MWBase::Environment::get().getESMStore()->get().search(mBirthSignId); if (sign) { mBirthSignWidget->setCaption(sign->mName); @@ -161,7 +175,7 @@ namespace MWGui int modified = static_cast(value.getModified()); mHealth->setValue(current, modified); - std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); + std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); mHealth->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } @@ -171,7 +185,7 @@ namespace MWGui int modified = static_cast(value.getModified()); mMagicka->setValue(current, modified); - std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); + std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); mMagicka->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } @@ -181,13 +195,13 @@ namespace MWGui int modified = static_cast(value.getModified()); mFatigue->setValue(current, modified); - std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); + std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value) { - std::map::iterator attr = mAttributeWidgets.find(static_cast(attributeId)); + auto attr = mAttributeWidgets.find(attributeId); if (attr == mAttributeWidgets.end()) return; @@ -198,13 +212,14 @@ namespace MWGui } } - void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value) + void ReviewDialog::setSkillValue(ESM::RefId id, const MWMechanics::SkillValue& value) { - mSkillValues[skillId] = value; - MyGUI::TextBox* widget = mSkillWidgetMap[skillId]; + mSkillValues[id] = value; + MyGUI::TextBox* widget = mSkillWidgetMap[id]; if (widget) { - float modified = static_cast(value.getModified()), base = static_cast(value.getBase()); + float modified = value.getModified(); + float base = value.getBase(); std::string text = MyGUI::utility::toString(std::floor(modified)); std::string state = "normal"; if (modified > base) @@ -219,28 +234,30 @@ namespace MWGui mUpdateSkillArea = true; } - void ReviewDialog::configureSkills(const std::vector& major, const std::vector& minor) + void ReviewDialog::configureSkills(const std::vector& major, const std::vector& minor) { mMajorSkills = major; mMinorSkills = minor; // Update misc skills with the remaining skills not in major or minor - std::set skillSet; + std::set skillSet; std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); mMiscSkills.clear(); - for (const int skill : ESM::Skill::sSkillIds) + const auto& store = MWBase::Environment::get().getWorld()->getStore().get(); + for (const ESM::Skill& skill : store) { - if (skillSet.find(skill) == skillSet.end()) - mMiscSkills.push_back(skill); + if (!skillSet.contains(skill.mId)) + mMiscSkills.push_back(skill.mId); } mUpdateSkillArea = true; } - void ReviewDialog::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + void ReviewDialog::addSeparator(MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { - MyGUI::ImageBox* separator = mSkillView->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default); + MyGUI::ImageBox* separator = mSkillView->createWidget( + "MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default); separator->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); mSkillWidgets.push_back(separator); @@ -249,11 +266,12 @@ namespace MWGui coord2.top += separator->getHeight(); } - void ReviewDialog::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + void ReviewDialog::addGroup(std::string_view label, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { - MyGUI::TextBox* groupWidget = mSkillView->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default); + MyGUI::TextBox* groupWidget = mSkillView->createWidget("SandBrightText", + MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default); groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - groupWidget->setCaption(label); + groupWidget->setCaption(toUString(label)); mSkillWidgets.push_back(groupWidget); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; @@ -261,13 +279,14 @@ namespace MWGui coord2.top += lineHeight; } - MyGUI::TextBox* ReviewDialog::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + MyGUI::TextBox* ReviewDialog::addValueItem(std::string_view text, const std::string& value, + const std::string& state, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { MyGUI::TextBox* skillNameWidget; MyGUI::TextBox* skillValueWidget; skillNameWidget = mSkillView->createWidget("SandText", coord1, MyGUI::Align::Default); - skillNameWidget->setCaption(text); + skillNameWidget->setCaption(toUString(text)); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); skillValueWidget = mSkillView->createWidget("SandTextRight", coord2, MyGUI::Align::Default); @@ -285,11 +304,12 @@ namespace MWGui return skillValueWidget; } - void ReviewDialog::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + void ReviewDialog::addItem(const std::string& text, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { MyGUI::TextBox* skillNameWidget; - skillNameWidget = mSkillView->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); + skillNameWidget = mSkillView->createWidget( + "SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); @@ -302,10 +322,11 @@ namespace MWGui void ReviewDialog::addItem(const ESM::Spell* spell, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { - Widgets::MWSpellPtr widget = mSkillView->createWidget("MW_StatName", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); + Widgets::MWSpellPtr widget = mSkillView->createWidget( + "MW_StatName", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); widget->setSpellId(spell->mId); widget->setUserString("ToolTipType", "Spell"); - widget->setUserString("Spell", spell->mId); + widget->setUserString("Spell", spell->mId.serialize()); widget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); mSkillWidgets.push_back(widget); @@ -315,7 +336,8 @@ namespace MWGui coord2.top += lineHeight; } - void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + void ReviewDialog::addSkills(const std::vector& skills, const std::string& titleId, + const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { // Add a line separator if there are items above if (!mSkillWidgets.empty()) @@ -323,15 +345,15 @@ namespace MWGui addSeparator(coord1, coord2); } - addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); + addGroup( + MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); - for (const int& skillId : skills) + for (const ESM::RefId& skillId : skills) { - if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes + const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().search(skillId); + if (!skill) // Skip unknown skills continue; - assert(skillId >= 0 && skillId < ESM::Skill::Length); - const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; - const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second; + const MWMechanics::SkillValue& stat = mSkillValues.find(skill->mId)->second; int base = stat.getBase(); int modified = stat.getModified(); @@ -340,14 +362,15 @@ namespace MWGui state = "increased"; else if (modified < base) state = "decreased"; - MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), MyGUI::utility::toString(static_cast(modified)), state, coord1, coord2); + MyGUI::TextBox* widget = addValueItem( + skill->mName, MyGUI::utility::toString(static_cast(modified)), state, coord1, coord2); - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { - ToolTips::createSkillToolTip(mSkillWidgets[mSkillWidgets.size()-1-i], skillId); + ToolTips::createSkillToolTip(mSkillWidgets[mSkillWidgets.size() - 1 - i], skill->mId); } - mSkillWidgetMap[skillId] = widget; + mSkillWidgetMap[skill->mId] = widget; } } @@ -373,80 +396,78 @@ namespace MWGui addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); // starting spells - std::vector spells; + std::vector spells; const ESM::Race* race = nullptr; if (!mRaceId.empty()) - race = MWBase::Environment::get().getWorld()->getStore().get().find(mRaceId); + race = MWBase::Environment::get().getESMStore()->get().find(mRaceId); - int skills[ESM::Skill::Length]; - for (int i=0; isecond.getBase(); + std::map attributes; + for (const auto& [key, value] : mAttributeWidgets) + attributes[key] = value->getAttributeValue(); - int attributes[ESM::Attribute::Length]; - for (int i=0; igetAttributeValue().getBase(); - - std::vector selectedSpells = MWMechanics::autoCalcPlayerSpells(skills, attributes, race); - for (std::string& spellId : selectedSpells) + std::vector selectedSpells = MWMechanics::autoCalcPlayerSpells(mSkillValues, attributes, race); + for (ESM::RefId& spellId : selectedSpells) { - std::string lower = Misc::StringUtils::lowerCase(spellId); - if (std::find(spells.begin(), spells.end(), lower) == spells.end()) - spells.push_back(lower); + if (std::find(spells.begin(), spells.end(), spellId) == spells.end()) + spells.push_back(spellId); } if (race) { - for (const std::string& spellId : race->mPowers.mList) + for (const ESM::RefId& spellId : race->mPowers.mList) { - std::string lower = Misc::StringUtils::lowerCase(spellId); - if (std::find(spells.begin(), spells.end(), lower) == spells.end()) - spells.push_back(lower); + if (std::find(spells.begin(), spells.end(), spellId) == spells.end()) + spells.push_back(spellId); } } if (!mBirthSignId.empty()) { - const ESM::BirthSign* sign = MWBase::Environment::get().getWorld()->getStore().get().find(mBirthSignId); - for (const std::string& spellId : sign->mPowers.mList) + const ESM::BirthSign* sign + = MWBase::Environment::get().getESMStore()->get().find(mBirthSignId); + for (const auto& spellId : sign->mPowers.mList) { - std::string lower = Misc::StringUtils::lowerCase(spellId); - if (std::find(spells.begin(), spells.end(), lower) == spells.end()) - spells.push_back(lower); + if (std::find(spells.begin(), spells.end(), spellId) == spells.end()) + spells.push_back(spellId); } } if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); - addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeAbility", "Abilities"), coord1, coord2); - for (std::string& spellId : spells) + addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeAbility", "Abilities"), + coord1, coord2); + for (auto& spellId : spells) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Ability) addItem(spell, coord1, coord2); } addSeparator(coord1, coord2); - addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypePower", "Powers"), coord1, coord2); - for (std::string& spellId : spells) + addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypePower", "Powers"), coord1, + coord2); + for (auto& spellId : spells) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Power) addItem(spell, coord1, coord2); } addSeparator(coord1, coord2); - addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeSpell", "Spells"), coord1, coord2); - for (std::string& spellId : spells) + addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeSpell", "Spells"), coord1, + coord2); + for (auto& spellId : spells) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Spell) addItem(spell, coord1, coord2); } - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mSkillView->setVisibleVScroll(false); - mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); + mSkillView->setCanvasSize(mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); mSkillView->setVisibleVScroll(true); } @@ -484,10 +505,11 @@ namespace MWGui void ReviewDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (mSkillView->getViewOffset().top + _rel*0.3 > 0) + if (mSkillView->getViewOffset().top + _rel * 0.3 > 0) mSkillView->setViewOffset(MyGUI::IntPoint(0, 0)); else - mSkillView->setViewOffset(MyGUI::IntPoint(0, static_cast(mSkillView->getViewOffset().top + _rel*0.3))); + mSkillView->setViewOffset( + MyGUI::IntPoint(0, static_cast(mSkillView->getViewOffset().top + _rel * 0.3))); } } diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index cb847536d..fe2a509fa 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -1,10 +1,11 @@ #ifndef MWGUI_REVIEW_H #define MWGUI_REVIEW_H -#include -#include -#include "windowbase.hpp" #include "widgets.hpp" +#include "windowbase.hpp" +#include +#include +#include namespace ESM { @@ -16,22 +17,22 @@ namespace MWGui class ReviewDialog : public WindowModal { public: - enum Dialogs { + enum Dialogs + { NAME_DIALOG, RACE_DIALOG, CLASS_DIALOG, BIRTHSIGN_DIALOG }; - typedef std::vector SkillList; ReviewDialog(); bool exit() override { return false; } - void setPlayerName(const std::string &name); - void setRace(const std::string &raceId); + void setPlayerName(const std::string& name); + void setRace(const ESM::RefId& raceId); void setClass(const ESM::Class& class_); - void setBirthSign (const std::string &signId); + void setBirthSign(const ESM::RefId& signId); void setHealth(const MWMechanics::DynamicStat& value); void setMagicka(const MWMechanics::DynamicStat& value); @@ -39,8 +40,8 @@ namespace MWGui void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value); - void configureSkills(const SkillList& major, const SkillList& minor); - void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value); + void configureSkills(const std::vector& major, const std::vector& minor); + void setSkillValue(ESM::RefId id, const MWMechanics::SkillValue& value); void onOpen() override; @@ -74,12 +75,14 @@ namespace MWGui void onMouseWheel(MyGUI::Widget* _sender, int _rel); private: - void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - MyGUI::TextBox* addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - void addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - void addItem(const ESM::Spell* spell, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); + void addSkills(const std::vector& skills, const std::string& titleId, + const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + void addSeparator(MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + void addGroup(std::string_view label, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + MyGUI::TextBox* addValueItem(std::string_view text, const std::string& value, const std::string& state, + MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + void addItem(const std::string& text, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + void addItem(const ESM::Spell* spell, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); void updateSkillArea(); MyGUI::TextBox *mNameWidget, *mRaceWidget, *mClassWidget, *mBirthSignWidget; @@ -87,12 +90,13 @@ namespace MWGui Widgets::MWDynamicStatPtr mHealth, mMagicka, mFatigue; - std::map mAttributeWidgets; + std::map mAttributeWidgets; - SkillList mMajorSkills, mMinorSkills, mMiscSkills; - std::map mSkillValues; - std::map mSkillWidgetMap; - std::string mName, mRaceId, mBirthSignId; + std::vector mMajorSkills, mMinorSkills, mMiscSkills; + std::map mSkillValues; + std::map mSkillWidgetMap; + ESM::RefId mRaceId, mBirthSignId; + std::string mName; ESM::Class mKlass; std::vector mSkillWidgets; //< Skills and other information diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index c4d608443..84b7240b2 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -1,36 +1,40 @@ #include "savegamedialog.hpp" -#include #include +#include #include #include -#include #include #include -#include #include +#include #include #include -#include +#include #include +#include #include +#include + +#include -#include "../mwbase/statemanager.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "../mwstate/character.hpp" #include "confirmationdialog.hpp" +#include "ustring.hpp" namespace MWGui { @@ -48,7 +52,6 @@ namespace MWGui getWidget(mDeleteButton, "DeleteButton"); getWidget(mSaveList, "SaveList"); getWidget(mSaveNameEdit, "SaveNameEdit"); - getWidget(mSpacer, "Spacer"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked); @@ -65,7 +68,7 @@ namespace MWGui mDeleteButton->setNeedKeyFocus(false); } - void SaveGameDialog::onSlotActivated(MyGUI::ListBox *sender, size_t pos) + void SaveGameDialog::onSlotActivated(MyGUI::ListBox* sender, size_t pos) { onSlotSelected(sender, pos); accept(); @@ -82,7 +85,7 @@ namespace MWGui void SaveGameDialog::confirmDeleteSave() { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->askForConfirmation("#{sMessage3}"); + dialog->askForConfirmation("#{OMWEngine:DeleteGameConfirmation}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); dialog->eventCancelClicked.clear(); @@ -91,7 +94,7 @@ namespace MWGui void SaveGameDialog::onDeleteSlotConfirmed() { - MWBase::Environment::get().getStateManager()->deleteGame (mCurrentCharacter, mCurrentSlot); + MWBase::Environment::get().getStateManager()->deleteGame(mCurrentCharacter, mCurrentSlot); mSaveList->removeItemAt(mSaveList->getIndexSelected()); onSlotSelected(mSaveList, mSaveList->getIndexSelected()); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); @@ -103,12 +106,12 @@ namespace MWGui mCharacterSelection->removeItemAt(previousIndex); if (mCharacterSelection->getItemCount()) { - size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount()-1); + size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount() - 1); mCharacterSelection->setIndexSelected(nextCharacter); onCharacterSelected(mCharacterSelection, nextCharacter); } else - fillSaveList(); + mCharacterSelection->setIndexSelected(MyGUI::ITEM_NONE); } } @@ -117,14 +120,14 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); } - void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender) + void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox* sender) { // This might have previously been a save slot from the list. If so, that is no longer the case mSaveList->setIndexSelected(MyGUI::ITEM_NONE); onSlotSelected(mSaveList, MyGUI::ITEM_NONE); } - void SaveGameDialog::onEditSelectAccept(MyGUI::EditBox *sender) + void SaveGameDialog::onEditSelectAccept(MyGUI::EditBox* sender) { accept(); @@ -143,7 +146,7 @@ namespace MWGui { WindowModal::onOpen(); - mSaveNameEdit->setCaption (""); + mSaveNameEdit->setCaption({}); if (mSaving) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit); else @@ -151,7 +154,7 @@ namespace MWGui center(); - mCharacterSelection->setCaption(""); + mCharacterSelection->setCaption({}); mCharacterSelection->removeAllItems(); mCurrentCharacter = nullptr; mCurrentSlot = nullptr; @@ -164,54 +167,56 @@ namespace MWGui mCurrentCharacter = mgr->getCurrentCharacter(); - std::string directory = - Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves")); + const std::string& directory = Settings::Manager::getString("character", "Saves"); size_t selectedIndex = MyGUI::ITEM_NONE; for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) { - if (it->begin()!=it->end()) + if (it->begin() != it->end()) { + const ESM::SavedGame& signature = it->getSignature(); + std::stringstream title; - title << it->getSignature().mPlayerName; + title << signature.mPlayerName; // For a custom class, we will not find it in the store (unless we loaded the savegame first). // Fall back to name stored in savegame header in that case. - std::string className; - if (it->getSignature().mPlayerClassId.empty()) - className = it->getSignature().mPlayerClassName; + std::string_view className; + if (signature.mPlayerClassId.empty()) + className = signature.mPlayerClassName; else { // Find the localised name for this class from the store - const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().search( - it->getSignature().mPlayerClassId); + const ESM::Class* class_ + = MWBase::Environment::get().getESMStore()->get().search(signature.mPlayerClassId); if (class_) className = class_->mName; else className = "?"; // From an older savegame format that did not support custom classes properly. } - title << " (#{sLevel} " << it->getSignature().mPlayerLevel << " " << MyGUI::TextIterator::toTagsString(className) << ")"; + title << " (#{sLevel} " << signature.mPlayerLevel << " " + << MyGUI::TextIterator::toTagsString(toUString(className)) << ")"; - mCharacterSelection->addItem (MyGUI::LanguageManager::getInstance().replaceTags(title.str())); + mCharacterSelection->addItem(MyGUI::LanguageManager::getInstance().replaceTags(title.str())); - if (mCurrentCharacter == &*it || - (!mCurrentCharacter && !mSaving && directory==Misc::StringUtils::lowerCase ( - it->begin()->mPath.parent_path().filename().string()))) + if (mCurrentCharacter == &*it + || (!mCurrentCharacter && !mSaving + && Misc::StringUtils::ciEqual( + directory, Files::pathToUnicodeString(it->begin()->mPath.parent_path().filename())))) { mCurrentCharacter = &*it; - selectedIndex = mCharacterSelection->getItemCount()-1; + selectedIndex = mCharacterSelection->getItemCount() - 1; } } } mCharacterSelection->setIndexSelected(selectedIndex); if (selectedIndex == MyGUI::ITEM_NONE) - mCharacterSelection->setCaption("Select Character ..."); + mCharacterSelection->setCaptionWithReplacing("#{OMWEngine:SelectCharacter}"); fillSaveList(); - } void SaveGameDialog::setLoadOrSave(bool load) @@ -220,7 +225,6 @@ namespace MWGui mSaveNameEdit->setVisible(!load); mCharacterSelection->setUserString("Hidden", load ? "false" : "true"); mCharacterSelection->setVisible(load); - mSpacer->setUserString("Hidden", load ? "false" : "true"); mDeleteButton->setUserString("Hidden", load ? "false" : "true"); mDeleteButton->setVisible(load); @@ -233,12 +237,12 @@ namespace MWGui center(); } - void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget *sender) + void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget* sender) { setVisible(false); } - void SaveGameDialog::onDeleteButtonClicked(MyGUI::Widget *sender) + void SaveGameDialog::onDeleteButtonClicked(MyGUI::Widget* sender) { if (mCurrentSlot) confirmDeleteSave(); @@ -262,7 +266,7 @@ namespace MWGui if (mCurrentSlot != nullptr && !reallySure) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->askForConfirmation("#{sMessage4}"); + dialog->askForConfirmation("#{OMWEngine:OverwriteGameConfirmation}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); dialog->eventCancelClicked.clear(); @@ -271,7 +275,7 @@ namespace MWGui } if (mSaveNameEdit->getCaption().empty()) { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{OMWEngine:EmptySaveNameError}"); return; } } @@ -283,7 +287,7 @@ namespace MWGui if (state == MWBase::StateManager::State_Running && !reallySure) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->askForConfirmation("#{sMessage1}"); + dialog->askForConfirmation("#{OMWEngine:LoadGameConfirmation}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); dialog->eventCancelClicked.clear(); @@ -293,16 +297,16 @@ namespace MWGui } setVisible(false); - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu); if (mSaving) { - MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), mCurrentSlot); + MWBase::Environment::get().getStateManager()->saveGame(mSaveNameEdit->getCaption(), mCurrentSlot); } else { - assert (mCurrentCharacter && mCurrentSlot); - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot->mPath.string()); + assert(mCurrentCharacter && mCurrentSlot); + MWBase::Environment::get().getStateManager()->loadGame(mCurrentCharacter, mCurrentSlot->mPath); } } @@ -312,16 +316,16 @@ namespace MWGui confirmDeleteSave(); } - void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender) + void SaveGameDialog::onOkButtonClicked(MyGUI::Widget* sender) { accept(); } - void SaveGameDialog::onCharacterSelected(MyGUI::ComboBox *sender, size_t pos) + void SaveGameDialog::onCharacterSelected(MyGUI::ComboBox* sender, size_t pos) { MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); - unsigned int i=0; + unsigned int i = 0; const MWState::Character* character = nullptr; for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it, ++i) { @@ -376,7 +380,7 @@ namespace MWGui return stream.str(); } - void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) + void SaveGameDialog::onSlotSelected(MyGUI::ListBox* sender, size_t pos) { mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving); mDeleteButton->setEnabled(pos != MyGUI::ITEM_NONE); @@ -384,8 +388,8 @@ namespace MWGui if (pos == MyGUI::ITEM_NONE || !mCurrentCharacter) { mCurrentSlot = nullptr; - mInfoText->setCaption(""); - mScreenshot->setImageTexture(""); + mInfoText->setCaption({}); + mScreenshot->setImageTexture({}); return; } @@ -393,8 +397,9 @@ namespace MWGui mSaveNameEdit->setCaption(sender->getItemNameAt(pos)); mCurrentSlot = nullptr; - unsigned int i=0; - for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i) + unsigned int i = 0; + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); + ++it, ++i) { if (i == pos) mCurrentSlot = &*it; @@ -403,53 +408,65 @@ namespace MWGui throw std::runtime_error("Can't find selected slot"); std::stringstream text; - time_t time = mCurrentSlot->mTimeStamp; - struct tm* timeinfo; - timeinfo = localtime(&time); - text << std::put_time(timeinfo, "%Y.%m.%d %T") << "\n"; + text << Misc::fileTimeToString(mCurrentSlot->mTimeStamp, "%Y.%m.%d %T") << "\n"; text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; - text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n"; + text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCellName << "}\n"; int hour = int(mCurrentSlot->mProfile.mInGameTime.mGameHour); bool pm = hour >= 12; - if (hour >= 13) hour -= 12; - if (hour == 0) hour = 12; + if (hour >= 13) + hour -= 12; + if (hour == 0) + hour = 12; - text - << mCurrentSlot->mProfile.mInGameTime.mDay << " " - << MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth) - << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); + text << mCurrentSlot->mProfile.mInGameTime.mDay << " " + << MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth) << " " + << hour << " " << (pm ? "#{Calendar:pm}" : "#{Calendar:am}"); - if (Settings::Manager::getBool("timeplayed","Saves")) + if (Settings::Manager::getBool("timeplayed", "Saves")) { - text << "\n" << "Time played: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); + text << "\n" + << "#{OMWEngine:TimePlayed}: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); } mInfoText->setCaptionWithReplacing(text.str()); + // Reset the image for the case we're unable to recover a screenshot + mScreenshotTexture.reset(); + mScreenshot->setRenderItemTexture(nullptr); + mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); // Decode screenshot const std::vector& data = mCurrentSlot->mProfile.mScreenshot; - Files::IMemStream instream (&data[0], data.size()); + if (!data.size()) + { + Log(Debug::Warning) << "Selected save file '" << Files::pathToUnicodeString(mCurrentSlot->mPath.filename()) + << "' has no savegame screenshot"; + return; + } + + Files::IMemStream instream(data.data(), data.size()); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); if (!readerwriter) { - Log(Debug::Error) << "Error: Can't open savegame screenshot, no jpg readerwriter found"; + Log(Debug::Error) << "Can't open savegame screenshot, no jpg readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(instream); if (!result.success()) { - Log(Debug::Error) << "Error: Failed to read savegame screenshot: " << result.message() << " code " << result.status(); + Log(Debug::Error) << "Failed to read savegame screenshot: " << result.message() << " code " + << result.status(); return; } - osg::ref_ptr texture (new osg::Texture2D); + osg::ref_ptr texture(new osg::Texture2D); texture->setImage(result.getImage()); + texture->setInternalFormat(GL_RGB); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); @@ -457,9 +474,7 @@ namespace MWGui texture->setResizeNonPowerOfTwoHint(false); texture->setUnRefImageDataAfterApply(true); - mScreenshotTexture.reset(new osgMyGUI::OSGTexture(texture)); - + mScreenshotTexture = std::make_unique(texture); mScreenshot->setRenderItemTexture(mScreenshotTexture.get()); - mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } } diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index c22d86fd1..35e65fbed 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -28,27 +28,27 @@ namespace MWGui void confirmDeleteSave(); void onKeyButtonPressed(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char character); - void onCancelButtonClicked (MyGUI::Widget* sender); - void onOkButtonClicked (MyGUI::Widget* sender); - void onDeleteButtonClicked (MyGUI::Widget* sender); - void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); + void onCancelButtonClicked(MyGUI::Widget* sender); + void onOkButtonClicked(MyGUI::Widget* sender); + void onDeleteButtonClicked(MyGUI::Widget* sender); + void onCharacterSelected(MyGUI::ComboBox* sender, size_t pos); void onCharacterAccept(MyGUI::ComboBox* sender, size_t pos); // Slot selected (mouse click or arrow keys) - void onSlotSelected (MyGUI::ListBox* sender, size_t pos); + void onSlotSelected(MyGUI::ListBox* sender, size_t pos); // Slot activated (double click or enter key) - void onSlotActivated (MyGUI::ListBox* sender, size_t pos); + void onSlotActivated(MyGUI::ListBox* sender, size_t pos); // Slot clicked with mouse void onSlotMouseClick(MyGUI::ListBox* sender, size_t pos); void onDeleteSlotConfirmed(); void onDeleteSlotCancel(); - void onEditSelectAccept (MyGUI::EditBox* sender); - void onSaveNameChanged (MyGUI::EditBox* sender); + void onEditSelectAccept(MyGUI::EditBox* sender); + void onSaveNameChanged(MyGUI::EditBox* sender); void onConfirmationGiven(); void onConfirmationCancel(); - void accept(bool reallySure=false); + void accept(bool reallySure = false); void fillSaveList(); @@ -63,11 +63,9 @@ namespace MWGui MyGUI::Button* mDeleteButton; MyGUI::ListBox* mSaveList; MyGUI::EditBox* mSaveNameEdit; - MyGUI::Widget* mSpacer; const MWState::Character* mCurrentCharacter; const MWState::Slot* mCurrentSlot; - }; } diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index 619852a22..e22517a36 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -1,20 +1,19 @@ #include "screenfader.hpp" -#include -#include #include +#include namespace MWGui { - FadeOp::FadeOp(ScreenFader * fader, float time, float targetAlpha, float delay) - : mFader(fader), - mRemainingTime(time+delay), - mTargetTime(time), - mTargetAlpha(targetAlpha), - mStartAlpha(0.f), - mDelay(delay), - mRunning(false) + FadeOp::FadeOp(ScreenFader* fader, float time, float targetAlpha, float delay) + : mFader(fader) + , mRemainingTime(time + delay) + , mTargetTime(time) + , mTargetAlpha(targetAlpha) + , mStartAlpha(0.f) + , mDelay(delay) + , mRunning(false) { } @@ -61,13 +60,13 @@ namespace MWGui float currentAlpha = mFader->getCurrentAlpha(); if (mStartAlpha > mTargetAlpha) { - currentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha); + currentAlpha -= dt / mTargetTime * (mStartAlpha - mTargetAlpha); if (currentAlpha < mTargetAlpha) currentAlpha = mTargetAlpha; } else { - currentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha); + currentAlpha += dt / mTargetTime * (mTargetAlpha - mStartAlpha); if (currentAlpha > mTargetAlpha) currentAlpha = mTargetAlpha; } @@ -83,7 +82,8 @@ namespace MWGui mFader->notifyOperationFinished(); } - ScreenFader::ScreenFader(const std::string & texturePath, const std::string& layout, const MyGUI::FloatCoord& texCoordOverride) + ScreenFader::ScreenFader( + const std::string& texturePath, const std::string& layout, const MyGUI::FloatCoord& texCoordOverride) : WindowBase(layout) , mCurrentAlpha(0.f) , mFactor(1.f) @@ -91,17 +91,14 @@ namespace MWGui { MyGUI::Gui::getInstance().eventFrameStart += MyGUI::newDelegate(this, &ScreenFader::onFrameStart); - mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - MyGUI::ImageBox* imageBox = mMainWidget->castType(false); if (imageBox) { imageBox->setImageTexture(texturePath); const MyGUI::IntSize imageSize = imageBox->getImageSize(); - imageBox->setImageCoord(MyGUI::IntCoord(texCoordOverride.left * imageSize.width, - texCoordOverride.top * imageSize.height, - texCoordOverride.width * imageSize.width, - texCoordOverride.height * imageSize.height)); + imageBox->setImageCoord( + MyGUI::IntCoord(texCoordOverride.left * imageSize.width, texCoordOverride.top * imageSize.height, + texCoordOverride.width * imageSize.width, texCoordOverride.height * imageSize.height)); } } @@ -111,7 +108,7 @@ namespace MWGui { MyGUI::Gui::getInstance().eventFrameStart -= MyGUI::newDelegate(this, &ScreenFader::onFrameStart); } - catch(const MyGUI::Exception& e) + catch (const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } @@ -130,7 +127,7 @@ namespace MWGui void ScreenFader::applyAlpha() { setVisible(true); - mMainWidget->setAlpha(1.f-((1.f-mCurrentAlpha) * mFactor)); + mMainWidget->setAlpha(1.f - ((1.f - mCurrentAlpha) * mFactor)); } void ScreenFader::fadeIn(float time, float delay) @@ -145,7 +142,7 @@ namespace MWGui void ScreenFader::fadeTo(const int percent, const float time, float delay) { - queue(time, percent/100.f, delay); + queue(time, percent / 100.f, delay); } void ScreenFader::clear() @@ -197,7 +194,7 @@ namespace MWGui mCurrentAlpha = alpha; - if (1.f-((1.f-mCurrentAlpha) * mFactor) == 0.f) + if (1.f - ((1.f - mCurrentAlpha) * mFactor) == 0.f) mMainWidget->setVisible(false); else applyAlpha(); diff --git a/apps/openmw/mwgui/screenfader.hpp b/apps/openmw/mwgui/screenfader.hpp index 8eb8a6859..4ef1ae2f4 100644 --- a/apps/openmw/mwgui/screenfader.hpp +++ b/apps/openmw/mwgui/screenfader.hpp @@ -15,7 +15,7 @@ namespace MWGui public: typedef std::shared_ptr Ptr; - FadeOp(ScreenFader * fader, float time, float targetAlpha, float delay); + FadeOp(ScreenFader* fader, float time, float targetAlpha, float delay); bool isRunning(); @@ -24,7 +24,7 @@ namespace MWGui void finish(); private: - ScreenFader * mFader; + ScreenFader* mFader; float mRemainingTime; float mTargetTime; float mTargetAlpha; @@ -36,18 +36,19 @@ namespace MWGui class ScreenFader : public WindowBase { public: - ScreenFader(const std::string & texturePath, const std::string& layout = "openmw_screen_fader.layout", const MyGUI::FloatCoord& texCoordOverride = MyGUI::FloatCoord(0,0,1,1)); + ScreenFader(const std::string& texturePath, const std::string& layout = "openmw_screen_fader.layout", + const MyGUI::FloatCoord& texCoordOverride = MyGUI::FloatCoord(0, 0, 1, 1)); ~ScreenFader(); void onFrameStart(float dt); - void fadeIn(const float time, float delay=0); - void fadeOut(const float time, float delay=0); - void fadeTo(const int percent, const float time, float delay=0); + void fadeIn(const float time, float delay = 0); + void fadeOut(const float time, float delay = 0); + void fadeTo(const int percent, const float time, float delay = 0); void clear() override; - void setFactor (float factor); + void setFactor(float factor); void setRepeat(bool repeat); void queue(float time, float targetAlpha, float delay); diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index f2c967da4..70a05403c 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -2,11 +2,10 @@ #include -#include +#include #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -19,7 +18,7 @@ namespace MWGui { - ScrollWindow::ScrollWindow () + ScrollWindow::ScrollWindow() : BookWindowBase("openmw_scroll.layout") , mTakeButtonShow(true) , mTakeButtonAllowed(true) @@ -41,20 +40,21 @@ namespace MWGui center(); } - void ScrollWindow::setPtr (const MWWorld::Ptr& scroll) + void ScrollWindow::setPtr(const MWWorld::Ptr& scroll) { mScroll = scroll; MWWorld::Ptr player = MWMechanics::getPlayer(); bool showTakeButton = scroll.getContainerStore() != &player.getClass().getContainerStore(player); - MWWorld::LiveCellRef *ref = mScroll.get(); + MWWorld::LiveCellRef* ref = mScroll.get(); Formatting::BookFormatter formatter; formatter.markupToWidget(mTextView, ref->mBase->mText, 390, mTextView->getHeight()); MyGUI::IntSize size = mTextView->getChildAt(0)->getSize(); - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mTextView->setVisibleVScroll(false); if (size.height > mTextView->getSize().height) mTextView->setCanvasSize(mTextView->getWidth(), size.height); @@ -62,14 +62,14 @@ namespace MWGui mTextView->setCanvasSize(mTextView->getWidth(), mTextView->getSize().height); mTextView->setVisibleVScroll(true); - mTextView->setViewOffset(MyGUI::IntPoint(0,0)); + mTextView->setViewOffset(MyGUI::IntPoint(0, 0)); setTakeButtonShow(showTakeButton); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); } - void ScrollWindow::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) + void ScrollWindow::onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character) { int scroll = 0; if (key == MyGUI::KeyCode::ArrowUp) @@ -93,17 +93,17 @@ namespace MWGui mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed); } - void ScrollWindow::onCloseButtonClicked (MyGUI::Widget* _sender) + void ScrollWindow::onCloseButtonClicked(MyGUI::Widget* _sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); } - void ScrollWindow::onTakeButtonClicked (MyGUI::Widget* _sender) + void ScrollWindow::onTakeButtonClicked(MyGUI::Widget* _sender) { - MWBase::Environment::get().getWindowManager()->playSound("Item Book Up"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Item Book Up")); MWWorld::ActionTake take(mScroll); - take.execute (MWMechanics::getPlayer()); + take.execute(MWMechanics::getPlayer()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll, true); } diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp index 8a1e323ab..a8d22a64b 100644 --- a/apps/openmw/mwgui/scrollwindow.hpp +++ b/apps/openmw/mwgui/scrollwindow.hpp @@ -14,30 +14,29 @@ namespace MWGui { class ScrollWindow : public BookWindowBase { - public: - ScrollWindow (); + public: + ScrollWindow(); - void setPtr (const MWWorld::Ptr& scroll) override; - void setInventoryAllowed(bool allowed); + void setPtr(const MWWorld::Ptr& scroll) override; + void setInventoryAllowed(bool allowed); - void onResChange(int, int) override { center(); } + void onResChange(int, int) override { center(); } - protected: - void onCloseButtonClicked (MyGUI::Widget* _sender); - void onTakeButtonClicked (MyGUI::Widget* _sender); - void setTakeButtonShow(bool show); - void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); + protected: + void onCloseButtonClicked(MyGUI::Widget* _sender); + void onTakeButtonClicked(MyGUI::Widget* _sender); + void setTakeButtonShow(bool show); + void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character); - private: - Gui::ImageButton* mCloseButton; - Gui::ImageButton* mTakeButton; - MyGUI::ScrollView* mTextView; + private: + Gui::ImageButton* mCloseButton; + Gui::ImageButton* mTakeButton; + MyGUI::ScrollView* mTextView; - MWWorld::Ptr mScroll; - - bool mTakeButtonShow; - bool mTakeButtonAllowed; + MWWorld::Ptr mScroll; + bool mTakeButtonShow; + bool mTakeButtonAllowed; }; } diff --git a/apps/openmw/mwgui/settings.cpp b/apps/openmw/mwgui/settings.cpp new file mode 100644 index 000000000..fb1068ca4 --- /dev/null +++ b/apps/openmw/mwgui/settings.cpp @@ -0,0 +1,292 @@ +#include "settings.hpp" + +#include "components/settings/values.hpp" + +namespace MWGui +{ + WindowSettingValues makeAlchemyWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mAlchemyX, + .mY = Settings::windows().mAlchemyY, + .mW = Settings::windows().mAlchemyW, + .mH = Settings::windows().mAlchemyH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mAlchemyMaximizedX, + .mY = Settings::windows().mAlchemyMaximizedY, + .mW = Settings::windows().mAlchemyMaximizedW, + .mH = Settings::windows().mAlchemyMaximizedH, + }, + .mIsMaximized = Settings::windows().mAlchemyMaximized, + }; + } + + WindowSettingValues makeBarterWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mBarterX, + .mY = Settings::windows().mBarterY, + .mW = Settings::windows().mBarterW, + .mH = Settings::windows().mBarterH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mBarterMaximizedX, + .mY = Settings::windows().mBarterMaximizedY, + .mW = Settings::windows().mBarterMaximizedW, + .mH = Settings::windows().mBarterMaximizedH, + }, + .mIsMaximized = Settings::windows().mBarterMaximized, + }; + } + + WindowSettingValues makeCompanionWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mCompanionX, + .mY = Settings::windows().mCompanionY, + .mW = Settings::windows().mCompanionW, + .mH = Settings::windows().mCompanionH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mCompanionMaximizedX, + .mY = Settings::windows().mCompanionMaximizedY, + .mW = Settings::windows().mCompanionMaximizedW, + .mH = Settings::windows().mCompanionMaximizedH, + }, + .mIsMaximized = Settings::windows().mCompanionMaximized, + }; + } + + WindowSettingValues makeConsoleWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mConsoleX, + .mY = Settings::windows().mConsoleY, + .mW = Settings::windows().mConsoleW, + .mH = Settings::windows().mConsoleH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mConsoleMaximizedX, + .mY = Settings::windows().mConsoleMaximizedY, + .mW = Settings::windows().mConsoleMaximizedW, + .mH = Settings::windows().mConsoleMaximizedH, + }, + .mIsMaximized = Settings::windows().mConsoleMaximized, + }; + } + + WindowSettingValues makeContainerWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mContainerX, + .mY = Settings::windows().mContainerY, + .mW = Settings::windows().mContainerW, + .mH = Settings::windows().mContainerH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mContainerMaximizedX, + .mY = Settings::windows().mContainerMaximizedY, + .mW = Settings::windows().mContainerMaximizedW, + .mH = Settings::windows().mContainerMaximizedH, + }, + .mIsMaximized = Settings::windows().mContainerMaximized, + }; + } + + WindowSettingValues makeDialogueWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mDialogueX, + .mY = Settings::windows().mDialogueY, + .mW = Settings::windows().mDialogueW, + .mH = Settings::windows().mDialogueH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mDialogueMaximizedX, + .mY = Settings::windows().mDialogueMaximizedY, + .mW = Settings::windows().mDialogueMaximizedW, + .mH = Settings::windows().mDialogueMaximizedH, + }, + .mIsMaximized = Settings::windows().mDialogueMaximized, + }; + } + + WindowSettingValues makeInventoryWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mInventoryX, + .mY = Settings::windows().mInventoryY, + .mW = Settings::windows().mInventoryW, + .mH = Settings::windows().mInventoryH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mInventoryMaximizedX, + .mY = Settings::windows().mInventoryMaximizedY, + .mW = Settings::windows().mInventoryMaximizedW, + .mH = Settings::windows().mInventoryMaximizedH, + }, + .mIsMaximized = Settings::windows().mInventoryMaximized, + }; + } + + WindowSettingValues makeInventoryBarterWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mInventoryBarterX, + .mY = Settings::windows().mInventoryBarterY, + .mW = Settings::windows().mInventoryBarterW, + .mH = Settings::windows().mInventoryBarterH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mInventoryBarterMaximizedX, + .mY = Settings::windows().mInventoryBarterMaximizedY, + .mW = Settings::windows().mInventoryBarterMaximizedW, + .mH = Settings::windows().mInventoryBarterMaximizedH, + }, + .mIsMaximized = Settings::windows().mInventoryBarterMaximized, + }; + } + + WindowSettingValues makeInventoryCompanionWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mInventoryCompanionX, + .mY = Settings::windows().mInventoryCompanionY, + .mW = Settings::windows().mInventoryCompanionW, + .mH = Settings::windows().mInventoryCompanionH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mInventoryCompanionMaximizedX, + .mY = Settings::windows().mInventoryCompanionMaximizedY, + .mW = Settings::windows().mInventoryCompanionMaximizedW, + .mH = Settings::windows().mInventoryCompanionMaximizedH, + }, + .mIsMaximized = Settings::windows().mInventoryCompanionMaximized, + }; + } + + WindowSettingValues makeInventoryContainerWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mInventoryContainerX, + .mY = Settings::windows().mInventoryContainerY, + .mW = Settings::windows().mInventoryContainerW, + .mH = Settings::windows().mInventoryContainerH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mInventoryContainerMaximizedX, + .mY = Settings::windows().mInventoryContainerMaximizedY, + .mW = Settings::windows().mInventoryContainerMaximizedW, + .mH = Settings::windows().mInventoryContainerMaximizedH, + }, + .mIsMaximized = Settings::windows().mInventoryContainerMaximized, + }; + } + + WindowSettingValues makeMapWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mMapX, + .mY = Settings::windows().mMapY, + .mW = Settings::windows().mMapW, + .mH = Settings::windows().mMapH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mMapMaximizedX, + .mY = Settings::windows().mMapMaximizedY, + .mW = Settings::windows().mMapMaximizedW, + .mH = Settings::windows().mMapMaximizedH, + }, + .mIsMaximized = Settings::windows().mMapMaximized, + }; + } + + WindowSettingValues makePostprocessorWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mPostprocessorX, + .mY = Settings::windows().mPostprocessorY, + .mW = Settings::windows().mPostprocessorW, + .mH = Settings::windows().mPostprocessorH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mPostprocessorMaximizedX, + .mY = Settings::windows().mPostprocessorMaximizedY, + .mW = Settings::windows().mPostprocessorMaximizedW, + .mH = Settings::windows().mPostprocessorMaximizedH, + }, + .mIsMaximized = Settings::windows().mPostprocessorMaximized, + }; + } + + WindowSettingValues makeSettingsWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mSettingsX, + .mY = Settings::windows().mSettingsY, + .mW = Settings::windows().mSettingsW, + .mH = Settings::windows().mSettingsH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mSettingsMaximizedX, + .mY = Settings::windows().mSettingsMaximizedY, + .mW = Settings::windows().mSettingsMaximizedW, + .mH = Settings::windows().mSettingsMaximizedH, + }, + .mIsMaximized = Settings::windows().mSettingsMaximized, + }; + } + + WindowSettingValues makeSpellsWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mSpellsX, + .mY = Settings::windows().mSpellsY, + .mW = Settings::windows().mSpellsW, + .mH = Settings::windows().mSpellsH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mSpellsMaximizedX, + .mY = Settings::windows().mSpellsMaximizedY, + .mW = Settings::windows().mSpellsMaximizedW, + .mH = Settings::windows().mSpellsMaximizedH, + }, + .mIsMaximized = Settings::windows().mSpellsMaximized, + }; + } + + WindowSettingValues makeStatsWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mStatsX, + .mY = Settings::windows().mStatsY, + .mW = Settings::windows().mStatsW, + .mH = Settings::windows().mStatsH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mStatsMaximizedX, + .mY = Settings::windows().mStatsMaximizedY, + .mW = Settings::windows().mStatsMaximizedW, + .mH = Settings::windows().mStatsMaximizedH, + }, + .mIsMaximized = Settings::windows().mStatsMaximized, + }; + } + +} diff --git a/apps/openmw/mwgui/settings.hpp b/apps/openmw/mwgui/settings.hpp new file mode 100644 index 000000000..8d1cda37d --- /dev/null +++ b/apps/openmw/mwgui/settings.hpp @@ -0,0 +1,40 @@ +#ifndef OPENMW_APPS_OPENMW_MWGUI_SETTINGS_H +#define OPENMW_APPS_OPENMW_MWGUI_SETTINGS_H + +#include "components/settings/settingvalue.hpp" + +namespace MWGui +{ + struct WindowRectSettingValues + { + Settings::SettingValue& mX; + Settings::SettingValue& mY; + Settings::SettingValue& mW; + Settings::SettingValue& mH; + }; + + struct WindowSettingValues + { + WindowRectSettingValues mRegular; + WindowRectSettingValues mMaximized; + Settings::SettingValue& mIsMaximized; + }; + + WindowSettingValues makeAlchemyWindowSettingValues(); + WindowSettingValues makeBarterWindowSettingValues(); + WindowSettingValues makeCompanionWindowSettingValues(); + WindowSettingValues makeConsoleWindowSettingValues(); + WindowSettingValues makeContainerWindowSettingValues(); + WindowSettingValues makeDialogueWindowSettingValues(); + WindowSettingValues makeInventoryWindowSettingValues(); + WindowSettingValues makeInventoryBarterWindowSettingValues(); + WindowSettingValues makeInventoryCompanionWindowSettingValues(); + WindowSettingValues makeInventoryContainerWindowSettingValues(); + WindowSettingValues makeMapWindowSettingValues(); + WindowSettingValues makePostprocessorWindowSettingValues(); + WindowSettingValues makeSettingsWindowSettingValues(); + WindowSettingValues makeSpellsWindowSettingValues(); + WindowSettingValues makeStatsWindowSettingValues(); +} + +#endif diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 4540cc0a7..82edb176a 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -1,70 +1,101 @@ #include "settingswindow.hpp" -#include -#include +#include +#include +#include +#include + +#include + #include -#include #include +#include +#include +#include #include +#include #include -#include -#include -#include - #include -#include +#include #include -#include -#include +#include +#include +#include #include #include #include +#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "confirmationdialog.hpp" +#include "ustring.hpp" namespace { - std::string textureMipmappingToStr(const std::string& val) { - if (val == "linear") return "Trilinear"; - if (val == "nearest") return "Bilinear"; - if (val != "none") - Log(Debug::Warning) << "Warning: Invalid texture mipmap option: "<< val; + if (val == "linear") + return "#{OMWEngine:TextureFilteringTrilinear}"; + if (val == "nearest") + return "#{OMWEngine:TextureFilteringBilinear}"; + if (val == "none") + return "#{OMWEngine:TextureFilteringDisabled}"; - return "Other"; + Log(Debug::Warning) << "Warning: Invalid texture mipmap option: " << val; + return "#{OMWEngine:TextureFilteringOther}"; } - void parseResolution (int &x, int &y, const std::string& str) + std::string lightingMethodToStr(SceneUtil::LightingMethod method) + { + std::string result; + switch (method) + { + case SceneUtil::LightingMethod::FFP: + result = "#{OMWEngine:LightingMethodLegacy}"; + break; + case SceneUtil::LightingMethod::PerObjectUniform: + result = "#{OMWEngine:LightingMethodShadersCompatibility}"; + break; + case SceneUtil::LightingMethod::SingleUBO: + default: + result = "#{OMWEngine:LightingMethodShaders}"; + break; + } + + return MyGUI::LanguageManager::getInstance().replaceTags(result); + } + + void parseResolution(int& x, int& y, const std::string& str) { std::vector split; - Misc::StringUtils::split (str, split, "@(x"); - assert (split.size() >= 2); + Misc::StringUtils::split(str, split, "@(x"); + assert(split.size() >= 2); Misc::StringUtils::trim(split[0]); Misc::StringUtils::trim(split[1]); - x = MyGUI::utility::parseInt (split[0]); - y = MyGUI::utility::parseInt (split[1]); + x = MyGUI::utility::parseInt(split[0]); + y = MyGUI::utility::parseInt(split[1]); } - bool sortResolutions (std::pair left, std::pair right) + bool sortResolutions(std::pair left, std::pair right) { if (left.first == right.first) return left.second > right.second; return left.first > right.first; } - std::string getAspect (int x, int y) + std::string getAspect(int x, int y) { - int gcd = std::gcd (x, y); + int gcd = std::gcd(x, y); if (gcd == 0) return std::string(); @@ -76,25 +107,25 @@ namespace return MyGUI::utility::toString(xaspect) + " : " + MyGUI::utility::toString(yaspect); } - const char* checkButtonType = "CheckButton"; - const char* sliderType = "Slider"; + const std::string_view checkButtonType = "CheckButton"; + const std::string_view sliderType = "Slider"; - std::string getSettingType(MyGUI::Widget* widget) + std::string_view getSettingType(MyGUI::Widget* widget) { return widget->getUserString("SettingType"); } - std::string getSettingName(MyGUI::Widget* widget) + std::string_view getSettingName(MyGUI::Widget* widget) { return widget->getUserString("SettingName"); } - std::string getSettingCategory(MyGUI::Widget* widget) + std::string_view getSettingCategory(MyGUI::Widget* widget) { return widget->getUserString("SettingCategory"); } - std::string getSettingValueType(MyGUI::Widget* widget) + std::string_view getSettingValueType(MyGUI::Widget* widget) { return widget->getUserString("SettingValueType"); } @@ -119,7 +150,7 @@ namespace int maxLights = Settings::Manager::getInt("max lights", "Shaders"); // show increments of 8 in dropdown if (maxLights >= min && maxLights <= max && !(maxLights % increment)) - box->setIndexSelected((maxLights / increment)-1); + box->setIndexSelected((maxLights / increment) - 1); else box->setIndexSelected(MyGUI::ITEM_NONE); } @@ -134,12 +165,12 @@ namespace MWGui { MyGUI::Widget* current = widgets.current(); - std::string type = getSettingType(current); + std::string_view type = getSettingType(current); if (type == checkButtonType) { - std::string initialValue = Settings::Manager::getBool(getSettingName(current), - getSettingCategory(current)) - ? "#{sOn}" : "#{sOff}"; + const std::string initialValue + = Settings::get(getSettingCategory(current), getSettingName(current)) ? "#{Interface:On}" + : "#{Interface:Off}"; current->castType()->setCaptionWithReplacing(initialValue); if (init) current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); @@ -163,42 +194,49 @@ namespace MWGui MyGUI::ScrollBar* scroll = current->castType(); std::string valueStr; - std::string valueType = getSettingValueType(current); + std::string_view valueType = getSettingValueType(current); if (valueType == "Float" || valueType == "Integer" || valueType == "Cell") { // TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget - float min,max; + float min, max; getSettingMinMax(scroll, min, max); - float value = Settings::Manager::getFloat(getSettingName(current), getSettingCategory(current)); + float value; if (valueType == "Cell") { + value = Settings::get(getSettingCategory(current), getSettingName(current)); std::stringstream ss; - ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; + ss << std::fixed << std::setprecision(2) << value / Constants::CellSizeInUnits; valueStr = ss.str(); } else if (valueType == "Float") { + value = Settings::get(getSettingCategory(current), getSettingName(current)); std::stringstream ss; ss << std::fixed << std::setprecision(2) << value; valueStr = ss.str(); } else - valueStr = MyGUI::utility::toString(int(value)); + { + const int intValue = Settings::get(getSettingCategory(current), getSettingName(current)); + valueStr = MyGUI::utility::toString(intValue); + value = static_cast(intValue); + } - value = std::max(min, std::min(value, max)); - value = (value-min)/(max-min); + value = std::clamp(value, min, max); + value = (value - min) / (max - min); scroll->setScrollPosition(static_cast(value * (scroll->getScrollRange() - 1))); } else { - int value = Settings::Manager::getInt(getSettingName(current), getSettingCategory(current)); + const int value = Settings::get(getSettingCategory(current), getSettingName(current)); valueStr = MyGUI::utility::toString(value); scroll->setScrollPosition(value); } if (init) - scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + scroll->eventScrollChangePosition + += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); if (scroll->getVisible()) updateSliderLabel(scroll, valueStr); } @@ -207,7 +245,7 @@ namespace MWGui } } - void SettingsWindow::updateSliderLabel(MyGUI::ScrollBar *scroller, const std::string& value) + void SettingsWindow::updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value) { std::string labelWidgetName = scroller->getUserString("SettingLabelWidget"); if (!labelWidgetName.empty()) @@ -220,43 +258,55 @@ namespace MWGui } } - SettingsWindow::SettingsWindow() : - WindowBase("openmw_settings_window.layout"), - mKeyboardMode(true) + SettingsWindow::SettingsWindow() + : WindowBase("openmw_settings_window.layout") + , mKeyboardMode(true) + , mCurrentPage(-1) { bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); - const std::string widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; + const std::string_view widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; MyGUI::Widget* unusedSlider; getWidget(unusedSlider, widgetName); unusedSlider->setVisible(false); configureWidgets(mMainWidget, true); - setTitle("#{sOptions}"); + setTitle("#{OMWEngine:SettingsWindow}"); getWidget(mSettingsTab, "SettingsTab"); getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); - getWidget(mFullscreenButton, "FullscreenButton"); + getWidget(mWindowModeList, "WindowModeList"); + getWidget(mVSyncModeList, "VSyncModeList"); getWidget(mWindowBorderButton, "WindowBorderButton"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); - getWidget(mAnisotropyBox, "AnisotropyBox"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); + getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail"); + getWidget(mPrimaryLanguage, "PrimaryLanguage"); + getWidget(mSecondaryLanguage, "SecondaryLanguage"); + getWidget(mGmstOverridesL10n, "GmstOverridesL10nButton"); + getWidget(mWindowModeHint, "WindowModeHint"); getWidget(mLightingMethodButton, "LightingMethodButton"); getWidget(mLightsResetButton, "LightsResetButton"); getWidget(mMaxLights, "MaxLights"); + getWidget(mScriptFilter, "ScriptFilter"); + getWidget(mScriptList, "ScriptList"); + getWidget(mScriptBox, "ScriptBox"); + getWidget(mScriptView, "ScriptView"); + getWidget(mScriptAdapter, "ScriptAdapter"); + getWidget(mScriptDisabled, "ScriptDisabled"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux - MyGUI::ScrollBar *gammaSlider; + MyGUI::ScrollBar* gammaSlider; getWidget(gammaSlider, "GammaSlider"); gammaSlider->setVisible(false); - MyGUI::TextBox *textBox; + MyGUI::TextBox* textBox; getWidget(textBox, "GammaText"); textBox->setVisible(false); getWidget(textBox, "GammaTextDark"); @@ -265,31 +315,53 @@ namespace MWGui textBox->setVisible(false); #endif - mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SettingsWindow::onWindowResize); + mMainWidget->castType()->eventWindowChangeCoord + += MyGUI::newDelegate(this, &SettingsWindow::onWindowResize); mSettingsTab->eventTabChangeSelect += MyGUI::newDelegate(this, &SettingsWindow::onTabChanged); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); - mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); + mTextureFilteringButton->eventComboChangePosition + += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); - mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); - mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); + mWaterTextureSize->eventComboChangePosition + += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); + mWaterReflectionDetail->eventComboChangePosition + += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); + mWaterRainRippleDetail->eventComboChangePosition + += MyGUI::newDelegate(this, &SettingsWindow::onWaterRainRippleDetailChanged); - mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged); - mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); + mLightingMethodButton->eventComboChangePosition + += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged); + mLightsResetButton->eventMouseButtonClick + += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); mMaxLights->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onMaxLightsChanged); + mWindowModeList->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWindowModeChanged); + mVSyncModeList->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onVSyncModeChanged); + mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); - mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); + mControllerSwitch->eventMouseButtonClick + += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); + + mPrimaryLanguage->eventComboChangePosition + += MyGUI::newDelegate(this, &SettingsWindow::onPrimaryLanguageChanged); + mSecondaryLanguage->eventComboChangePosition + += MyGUI::newDelegate(this, &SettingsWindow::onSecondaryLanguageChanged); + mGmstOverridesL10n->eventMouseButtonClick + += MyGUI::newDelegate(this, &SettingsWindow::onGmstOverridesL10nChanged); + + computeMinimumWindowSize(); center(); - mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); + mResetControlsButton->eventMouseButtonClick + += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); // fill resolution list int screen = Settings::Manager::getInt("screen", "Video"); int numDisplayModes = SDL_GetNumDisplayModes(screen); - std::vector < std::pair > resolutions; + std::vector> resolutions; for (int i = 0; i < numDisplayModes; i++) { SDL_DisplayMode mode; @@ -299,18 +371,19 @@ namespace MWGui std::sort(resolutions.begin(), resolutions.end(), sortResolutions); for (std::pair& resolution : resolutions) { - std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second); + std::string str + = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second); std::string aspect = getAspect(resolution.first, resolution.second); if (!aspect.empty()) - str = str + " (" + aspect + ")"; + str = str + " (" + aspect + ")"; if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); } highlightCurrentResolution(); - std::string tmip = Settings::Manager::getString("texture mipmap", "General"); - mTextureFilteringButton->setCaption(textureMipmappingToStr(tmip)); + const std::string& tmip = Settings::Manager::getString("texture mipmap", "General"); + mTextureFilteringButton->setCaptionWithReplacing(textureMipmappingToStr(tmip)); int waterTextureSize = Settings::Manager::getInt("rtt size", "Water"); if (waterTextureSize >= 512) @@ -320,16 +393,78 @@ namespace MWGui if (waterTextureSize >= 2048) mWaterTextureSize->setIndexSelected(2); - int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); + int waterReflectionDetail = std::clamp(Settings::Manager::getInt("reflection detail", "Water"), 0, 5); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); + int waterRainRippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); + mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail); + updateMaxLightsComboBox(mMaxLights); - mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); + Settings::WindowMode windowMode + = static_cast(Settings::Manager::getInt("window mode", "Video")); + mWindowBorderButton->setEnabled( + windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::WindowedFullscreen); + + mWindowModeHint->setVisible(windowMode == Settings::WindowMode::WindowedFullscreen); mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); + + mScriptFilter->eventEditTextChange += MyGUI::newDelegate(this, &SettingsWindow::onScriptFilterChange); + mScriptList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SettingsWindow::onScriptListSelection); + + std::vector availableLanguages; + const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + for (const auto& path : vfs->getRecursiveDirectoryIterator("l10n/")) + { + if (Misc::getFileExtension(path) == "yaml") + { + std::string localeName(Misc::stemFile(path)); + if (localeName == "gmst") + continue; // fake locale to get gmst strings from content files + if (std::find(availableLanguages.begin(), availableLanguages.end(), localeName) + == availableLanguages.end()) + availableLanguages.push_back(localeName); + } + } + + std::sort(availableLanguages.begin(), availableLanguages.end()); + + std::vector currentLocales = Settings::Manager::getStringArray("preferred locales", "General"); + if (currentLocales.empty()) + currentLocales.push_back("en"); + + icu::Locale primaryLocale(currentLocales[0].c_str()); + + mPrimaryLanguage->removeAllItems(); + mPrimaryLanguage->setIndexSelected(MyGUI::ITEM_NONE); + + mSecondaryLanguage->removeAllItems(); + mSecondaryLanguage->addItem( + MyGUI::LanguageManager::getInstance().replaceTags("#{Interface:None}"), std::string()); + mSecondaryLanguage->setIndexSelected(0); + + size_t i = 0; + for (const auto& language : availableLanguages) + { + icu::Locale locale(language.c_str()); + + icu::UnicodeString str(language.c_str()); + locale.getDisplayName(primaryLocale, str); + std::string localeString; + str.toUTF8String(localeString); + + mPrimaryLanguage->addItem(localeString, language); + mSecondaryLanguage->addItem(localeString, language); + + if (language == currentLocales[0]) + mPrimaryLanguage->setIndexSelected(i); + if (currentLocales.size() > 1 && language == currentLocales[1]) + mSecondaryLanguage->setIndexSelected(i + 1); + + i++; + } } void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/) @@ -348,7 +483,7 @@ namespace MWGui return; ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->askForConfirmation("#{sNotifyMessage67}"); + dialog->askForConfirmation("#{OMWEngine:ConfirmResolution}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionAccept); dialog->eventCancelClicked.clear(); @@ -357,9 +492,9 @@ namespace MWGui void SettingsWindow::onResolutionAccept() { - std::string resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); + const std::string& resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); int resX, resY; - parseResolution (resX, resY, resStr); + parseResolution(resX, resY, resStr); Settings::Manager::setInt("resolution x", "Video", resX); Settings::Manager::setInt("resolution y", "Video", resY); @@ -379,10 +514,10 @@ namespace MWGui int currentX = Settings::Manager::getInt("resolution x", "Video"); int currentY = Settings::Manager::getInt("resolution y", "Video"); - for (size_t i=0; igetItemCount(); ++i) + for (size_t i = 0; i < mResolutionList->getItemCount(); ++i) { int resX, resY; - parseResolution (resX, resY, mResolutionList->getItemNameAt(i)); + parseResolution(resX, resY, mResolutionList->getItemNameAt(i)); if (resX == currentX && resY == currentY) { @@ -407,20 +542,94 @@ namespace MWGui void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min((unsigned int)5, (unsigned int)pos); + unsigned int level = static_cast(std::min(pos, 5)); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } + void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos) + { + unsigned int level = static_cast(std::min(pos, 2)); + Settings::Manager::setInt("rain ripple detail", "Water", level); + apply(); + } + void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; - std::string message = "This change requires a restart to take effect."; - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, {"#{sOK}"}, true); + _sender->setCaptionWithReplacing(_sender->getItemNameAt(_sender->getIndexSelected())); - Settings::Manager::setString("lighting method", "Shaders", _sender->getItemNameAt(pos)); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox( + "#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true); + + Settings::Manager::setString("lighting method", "Shaders", *_sender->getItemDataAt(pos)); + apply(); + } + + void SettingsWindow::onLanguageChanged(size_t langPriority, MyGUI::ComboBox* _sender, size_t pos) + { + if (pos == MyGUI::ITEM_NONE) + return; + + _sender->setCaptionWithReplacing(_sender->getItemNameAt(_sender->getIndexSelected())); + + MWBase::Environment::get().getWindowManager()->interactiveMessageBox( + "#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true); + + std::vector currentLocales = Settings::Manager::getStringArray("preferred locales", "General"); + if (currentLocales.size() <= langPriority) + currentLocales.resize(langPriority + 1, "en"); + + const auto& languageCode = *_sender->getItemDataAt(pos); + if (!languageCode.empty()) + currentLocales[langPriority] = languageCode; + else + currentLocales.resize(1); + + Settings::Manager::setStringArray("preferred locales", "General", currentLocales); + } + + void SettingsWindow::onGmstOverridesL10nChanged(MyGUI::Widget*) + { + MWBase::Environment::get().getWindowManager()->interactiveMessageBox( + "#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true); + } + + void SettingsWindow::onVSyncModeChanged(MyGUI::ComboBox* _sender, size_t pos) + { + if (pos == MyGUI::ITEM_NONE) + return; + + int index = static_cast(_sender->getIndexSelected()); + Settings::Manager::setInt("vsync mode", "Video", index); + apply(); + } + + void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos) + { + if (pos == MyGUI::ITEM_NONE) + return; + + int index = static_cast(_sender->getIndexSelected()); + if (index == static_cast(Settings::WindowMode::WindowedFullscreen)) + { + mResolutionList->setEnabled(false); + mWindowModeHint->setVisible(true); + } + else + { + mResolutionList->setEnabled(true); + mWindowModeHint->setVisible(false); + } + + if (index == static_cast(Settings::WindowMode::Windowed)) + mWindowBorderButton->setEnabled(true); + else + mWindowBorderButton->setEnabled(false); + + Settings::Manager::setInt("window mode", "Video", index); apply(); } @@ -435,25 +644,24 @@ namespace MWGui void SettingsWindow::onLightsResetButtonClicked(MyGUI::Widget* _sender) { - std::vector buttons = {"#{sYes}", "#{sNo}"}; - std::string message = "Resets to default values, would you like to continue? Changes to lighting method will require a restart."; - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true); + std::vector buttons = { "#{Interface:Yes}", "#{Interface:No}" }; + MWBase::Environment::get().getWindowManager()->interactiveMessageBox( + "#{OMWEngine:LightingResetToDefaults}", buttons, true); int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); if (selectedButton == 1 || selectedButton == -1) return; - constexpr std::array settings = { - "light bounds multiplier", - "maximum light distance", - "light fade start", - "minimum interior brightness", - "max lights", - "lighting method", - }; - for (const auto& setting : settings) - Settings::Manager::setString(setting, "Shaders", Settings::Manager::mDefaultSettings[{"Shaders", setting}]); + Settings::shaders().mLightBoundsMultiplier.reset(); + Settings::shaders().mMaximumLightDistance.reset(); + Settings::shaders().mLightFadeStart.reset(); + Settings::shaders().mMinimumInteriorBrightness.reset(); + Settings::shaders().mMaxLights.reset(); + Settings::shaders().mLightingMethod.reset(); - mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(Settings::Manager::mDefaultSettings[{"Shaders", "lighting method"}])); + const SceneUtil::LightingMethod lightingMethod + = SceneUtil::LightManager::getLightingMethodFromString(Settings::shaders().mLightingMethod); + const std::size_t lightIndex = mLightingMethodButton->findItemIndexWith(lightingMethodToStr(lightingMethod)); + mLightingMethodButton->setIndexSelected(lightIndex); updateMaxLightsComboBox(mMaxLights); apply(); @@ -462,11 +670,12 @@ namespace MWGui void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { - std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); - std::string off = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "On"); + MyGUI::UString on = toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On")); bool newState; if (_sender->castType()->getCaption() == on) { + MyGUI::UString off + = toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "Off")); _sender->castType()->setCaption(off); newState = false; } @@ -476,52 +685,9 @@ namespace MWGui newState = true; } - if (_sender == mFullscreenButton) - { - // check if this resolution is supported in fullscreen - if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) - { - std::string resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); - int resX, resY; - parseResolution (resX, resY, resStr); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); - } - - bool supported = false; - int fallbackX = 0, fallbackY = 0; - for (unsigned int i=0; igetItemCount(); ++i) - { - std::string resStr = mResolutionList->getItemNameAt(i); - int resX, resY; - parseResolution (resX, resY, resStr); - - if (i == 0) - { - fallbackX = resX; - fallbackY = resY; - } - - if (resX == Settings::Manager::getInt("resolution x", "Video") - && resY == Settings::Manager::getInt("resolution y", "Video")) - supported = true; - } - - if (!supported && mResolutionList->getItemCount()) - { - if (fallbackX != 0 && fallbackY != 0) - { - Settings::Manager::setInt("resolution x", "Video", fallbackX); - Settings::Manager::setInt("resolution y", "Video", fallbackY); - } - } - - mWindowBorderButton->setEnabled(!newState); - } - if (getSettingType(_sender) == checkButtonType) { - Settings::Manager::setBool(getSettingName(_sender), getSettingCategory(_sender), newState); + Settings::get(getSettingCategory(_sender), getSettingName(_sender)).set(newState); apply(); return; } @@ -529,51 +695,59 @@ namespace MWGui void SettingsWindow::onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos) { - if(pos == 0) + if (pos == 0) Settings::Manager::setString("texture mipmap", "General", "nearest"); - else if(pos == 1) + else if (pos == 1) Settings::Manager::setString("texture mipmap", "General", "linear"); else Log(Debug::Warning) << "Unexpected option pos " << pos; apply(); } + void SettingsWindow::onResChange(int width, int height) + { + center(); + highlightCurrentResolution(); + } + void SettingsWindow::onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos) { if (getSettingType(scroller) == "Slider") { std::string valueStr; - std::string valueType = getSettingValueType(scroller); + std::string_view valueType = getSettingValueType(scroller); if (valueType == "Float" || valueType == "Integer" || valueType == "Cell") { - float value = pos / float(scroller->getScrollRange()-1); + float value = pos / float(scroller->getScrollRange() - 1); - float min,max; + float min, max; getSettingMinMax(scroller, min, max); - value = min + (max-min) * value; - if (valueType == "Float") - Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); - else - Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), (int)value); + value = min + (max - min) * value; if (valueType == "Cell") { + Settings::get(getSettingCategory(scroller), getSettingName(scroller)).set(value); std::stringstream ss; - ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; + ss << std::fixed << std::setprecision(2) << value / Constants::CellSizeInUnits; valueStr = ss.str(); } else if (valueType == "Float") { + Settings::get(getSettingCategory(scroller), getSettingName(scroller)).set(value); std::stringstream ss; ss << std::fixed << std::setprecision(2) << value; valueStr = ss.str(); } else + { + Settings::get(getSettingCategory(scroller), getSettingName(scroller)) + .set(static_cast(value)); valueStr = MyGUI::utility::toString(int(value)); + } } else { - Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), pos); + Settings::get(getSettingCategory(scroller), getSettingName(scroller)).set(pos); valueStr = MyGUI::utility::toString(pos); } updateSliderLabel(scroller, valueStr); @@ -595,7 +769,7 @@ namespace MWGui void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender) { - if(mKeyboardMode) + if (mKeyboardMode) return; mKeyboardMode = true; mKeyboardSwitch->setStateSelected(true); @@ -606,7 +780,7 @@ namespace MWGui void SettingsWindow::onControllerSwitchClicked(MyGUI::Widget* _sender) { - if(!mKeyboardMode) + if (!mKeyboardMode) return; mKeyboardMode = false; mKeyboardSwitch->setStateSelected(false); @@ -621,30 +795,30 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(mControlsBox->getChildAt(0)); MWBase::Environment::get().getWindowManager()->removeStaticMessageBox(); - std::vector actions; - if(mKeyboardMode) - actions = MWBase::Environment::get().getInputManager()->getActionKeySorting(); - else - actions = MWBase::Environment::get().getInputManager()->getActionControllerSorting(); + const auto inputManager = MWBase::Environment::get().getInputManager(); + const auto& actions + = mKeyboardMode ? inputManager->getActionKeySorting() : inputManager->getActionControllerSorting(); for (const int& action : actions) { - std::string desc = MWBase::Environment::get().getInputManager()->getActionDescription (action); - if (desc == "") + std::string desc{ inputManager->getActionDescription(action) }; + if (desc.empty()) continue; std::string binding; - if(mKeyboardMode) - binding = MWBase::Environment::get().getInputManager()->getActionKeyBindingName(action); + if (mKeyboardMode) + binding = inputManager->getActionKeyBindingName(action); else - binding = MWBase::Environment::get().getInputManager()->getActionControllerBindingName(action); + binding = inputManager->getActionControllerBindingName(action); - Gui::SharedStateButton* leftText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(), MyGUI::Align::Default); + Gui::SharedStateButton* leftText = mControlsBox->createWidget( + "SandTextButton", MyGUI::IntCoord(), MyGUI::Align::Default); leftText->setCaptionWithReplacing(desc); - Gui::SharedStateButton* rightText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(), MyGUI::Align::Default); + Gui::SharedStateButton* rightText = mControlsBox->createWidget( + "SandTextButton", MyGUI::IntCoord(), MyGUI::Align::Default); rightText->setCaptionWithReplacing(binding); - rightText->setTextAlign (MyGUI::Align::Right); + rightText->setTextAlign(MyGUI::Align::Right); rightText->setUserData(action); // save the action id for callbacks rightText->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onRebindAction); rightText->eventMouseWheel += MyGUI::newDelegate(this, &SettingsWindow::onInputTabMouseWheel); @@ -661,7 +835,7 @@ namespace MWGui void SettingsWindow::updateLightSettings() { auto lightingMethod = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getLightingMethod(); - std::string lightingMethodStr = SceneUtil::LightManager::getLightingMethodString(lightingMethod); + std::string lightingMethodStr = lightingMethodToStr(lightingMethod); mLightingMethodButton->removeAllItems(); @@ -676,56 +850,221 @@ namespace MWGui if (!MWBase::Environment::get().getResourceSystem()->getSceneManager()->isSupportedLightingMethod(method)) continue; - mLightingMethodButton->addItem(SceneUtil::LightManager::getLightingMethodString(method)); + mLightingMethodButton->addItem( + lightingMethodToStr(method), SceneUtil::LightManager::getLightingMethodString(method)); + } + mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(lightingMethodStr)); + } + + void SettingsWindow::updateWindowModeSettings() + { + size_t index = static_cast(Settings::Manager::getInt("window mode", "Video")); + + if (index > static_cast(Settings::WindowMode::Windowed)) + index = MyGUI::ITEM_NONE; + + mWindowModeList->setIndexSelected(index); + + if (index != static_cast(Settings::WindowMode::Windowed) && index != MyGUI::ITEM_NONE) + { + // check if this resolution is supported in fullscreen + if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) + { + const std::string& resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); + int resX, resY; + parseResolution(resX, resY, resStr); + Settings::Manager::setInt("resolution x", "Video", resX); + Settings::Manager::setInt("resolution y", "Video", resY); + } + + bool supported = false; + int fallbackX = 0, fallbackY = 0; + for (size_t i = 0; i < mResolutionList->getItemCount(); ++i) + { + const std::string& resStr = mResolutionList->getItemNameAt(i); + int resX, resY; + parseResolution(resX, resY, resStr); + + if (i == 0) + { + fallbackX = resX; + fallbackY = resY; + } + + if (resX == Settings::Manager::getInt("resolution x", "Video") + && resY == Settings::Manager::getInt("resolution y", "Video")) + supported = true; + } + + if (!supported && mResolutionList->getItemCount()) + { + if (fallbackX != 0 && fallbackY != 0) + { + Settings::Manager::setInt("resolution x", "Video", fallbackX); + Settings::Manager::setInt("resolution y", "Video", fallbackY); + } + } + + mWindowBorderButton->setEnabled(false); } - mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(lightingMethodStr)); + if (index == static_cast(Settings::WindowMode::WindowedFullscreen)) + mResolutionList->setEnabled(false); + } + + void SettingsWindow::updateVSyncModeSettings() + { + int index = static_cast(Settings::Manager::getInt("vsync mode", "Video")); + + if (index < 0 || index > 2) + index = 0; + + mVSyncModeList->setIndexSelected(index); } void SettingsWindow::layoutControlsBox() { - const int h = 18; + const int h = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; const int w = mControlsBox->getWidth() - 28; const int noWidgetsInRow = 2; const int totalH = mControlsBox->getChildCount() / noWidgetsInRow * h; for (size_t i = 0; i < mControlsBox->getChildCount(); i++) { - MyGUI::Widget * widget = mControlsBox->getChildAt(i); + MyGUI::Widget* widget = mControlsBox->getChildAt(i); widget->setCoord(0, i / noWidgetsInRow * h, w, h); } - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mControlsBox->setVisibleVScroll(false); - mControlsBox->setCanvasSize (mControlsBox->getWidth(), std::max(totalH, mControlsBox->getHeight())); + mControlsBox->setCanvasSize(mControlsBox->getWidth(), std::max(totalH, mControlsBox->getHeight())); mControlsBox->setVisibleVScroll(true); } + namespace + { + std::string escapeRegex(const std::string& str) + { + static const std::regex specialChars(R"r([\^\.\[\$\(\)\|\*\+\?\{])r", std::regex_constants::extended); + return std::regex_replace(str, specialChars, R"(\$&)"); + } + + std::regex wordSearch(const std::string& query) + { + static const std::regex wordsRegex(R"([^[:space:]]+)", std::regex_constants::extended); + auto wordsBegin = std::sregex_iterator(query.begin(), query.end(), wordsRegex); + auto wordsEnd = std::sregex_iterator(); + std::string searchRegex("("); + for (auto it = wordsBegin; it != wordsEnd; ++it) + { + if (it != wordsBegin) + searchRegex += '|'; + searchRegex += escapeRegex(query.substr(it->position(), it->length())); + } + searchRegex += ')'; + // query had only whitespace characters + if (searchRegex == "()") + searchRegex = "^(.*)$"; + return std::regex(searchRegex, std::regex_constants::extended | std::regex_constants::icase); + } + + double weightedSearch(const std::regex& regex, const std::string& text) + { + std::smatch matches; + std::regex_search(text, matches, regex); + // need a signed value, so cast to double (not an integer type to guarantee no overflow) + return static_cast(matches.size()); + } + } + + void SettingsWindow::renderScriptSettings() + { + mScriptAdapter->detach(); + + mScriptList->removeAllItems(); + mScriptView->setCanvasSize({ 0, 0 }); + + struct WeightedPage + { + size_t mIndex; + std::string mName; + double mNameWeight; + double mHintWeight; + + constexpr auto tie() const { return std::tie(mNameWeight, mHintWeight, mName); } + + constexpr bool operator<(const WeightedPage& rhs) const { return tie() < rhs.tie(); } + }; + + std::regex searchRegex = wordSearch(mScriptFilter->getCaption()); + std::vector weightedPages; + weightedPages.reserve(LuaUi::scriptSettingsPageCount()); + for (size_t i = 0; i < LuaUi::scriptSettingsPageCount(); ++i) + { + LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i); + double nameWeight = weightedSearch(searchRegex, page.mName); + double hintWeight = weightedSearch(searchRegex, page.mSearchHints); + if ((nameWeight + hintWeight) > 0) + weightedPages.push_back({ i, page.mName, -nameWeight, -hintWeight }); + } + std::sort(weightedPages.begin(), weightedPages.end()); + for (const WeightedPage& weightedPage : weightedPages) + mScriptList->addItem(weightedPage.mName, weightedPage.mIndex); + + // Hide script settings when the game world isn't loaded + bool disabled = LuaUi::scriptSettingsPageCount() == 0; + mScriptFilter->setVisible(!disabled); + mScriptList->setVisible(!disabled); + mScriptBox->setVisible(!disabled); + mScriptDisabled->setVisible(disabled); + + LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); + mScriptView->setCanvasSize(mScriptAdapter->getSize()); + } + + void SettingsWindow::onScriptFilterChange(MyGUI::EditBox*) + { + renderScriptSettings(); + } + + void SettingsWindow::onScriptListSelection(MyGUI::ListBox*, size_t index) + { + mScriptAdapter->detach(); + mCurrentPage = -1; + if (index < mScriptList->getItemCount()) + { + mCurrentPage = *mScriptList->getItemDataAt(index); + LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); + } + mScriptView->setCanvasSize(mScriptAdapter->getSize()); + } + void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) { int actionId = *_sender->getUserData(); - _sender->castType()->setCaptionWithReplacing("#{sNone}"); + _sender->castType()->setCaptionWithReplacing("#{Interface:None}"); - MWBase::Environment::get().getWindowManager ()->staticMessageBox ("#{sControlsMenu3}"); - MWBase::Environment::get().getWindowManager ()->disallowMouse(); - - MWBase::Environment::get().getInputManager ()->enableDetectingBindingMode (actionId, mKeyboardMode); + MWBase::Environment::get().getWindowManager()->staticMessageBox("#{OMWEngine:RebindAction}"); + MWBase::Environment::get().getWindowManager()->disallowMouse(); + MWBase::Environment::get().getInputManager()->enableDetectingBindingMode(actionId, mKeyboardMode); } void SettingsWindow::onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (mControlsBox->getViewOffset().top + _rel*0.3f > 0) + if (mControlsBox->getViewOffset().top + _rel * 0.3f > 0) mControlsBox->setViewOffset(MyGUI::IntPoint(0, 0)); else - mControlsBox->setViewOffset(MyGUI::IntPoint(0, static_cast(mControlsBox->getViewOffset().top + _rel*0.3f))); + mControlsBox->setViewOffset( + MyGUI::IntPoint(0, static_cast(mControlsBox->getViewOffset().top + _rel * 0.3f))); } void SettingsWindow::onResetDefaultBindings(MyGUI::Widget* _sender) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->askForConfirmation("#{sNotifyMessage66}"); + dialog->askForConfirmation("#{OMWEngine:ConfirmResetBindings}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindingsAccept); dialog->eventCancelClicked.clear(); @@ -733,11 +1072,11 @@ namespace MWGui void SettingsWindow::onResetDefaultBindingsAccept() { - if(mKeyboardMode) - MWBase::Environment::get().getInputManager ()->resetToDefaultKeyBindings (); + if (mKeyboardMode) + MWBase::Environment::get().getInputManager()->resetToDefaultKeyBindings(); else MWBase::Environment::get().getInputManager()->resetToDefaultControllerBindings(); - updateControlsBox (); + updateControlsBox(); } void SettingsWindow::onOpen() @@ -745,15 +1084,44 @@ namespace MWGui highlightCurrentResolution(); updateControlsBox(); updateLightSettings(); + updateWindowModeSettings(); + updateVSyncModeSettings(); resetScrollbars(); + renderScriptSettings(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); } - void SettingsWindow::onWindowResize(MyGUI::Window *_sender) + void SettingsWindow::onWindowResize(MyGUI::Window* _sender) { layoutControlsBox(); } + void SettingsWindow::computeMinimumWindowSize() + { + auto* window = mMainWidget->castType(); + auto minSize = window->getMinSize(); + + // Window should be at minimum wide enough to show all tabs. + int tabBarWidth = 0; + for (uint32_t i = 0; i < mSettingsTab->getItemCount(); i++) + { + tabBarWidth += mSettingsTab->getButtonWidthAt(i); + } + + // Need to include window margins + int margins = mMainWidget->getWidth() - mSettingsTab->getWidth(); + int minimumWindowWidth = tabBarWidth + margins; + + if (minimumWindowWidth > minSize.width) + { + minSize.width = minimumWindowWidth; + window->setMinSize(minSize); + + // Make a dummy call to setSize so MyGUI can apply any resize resulting from the change in MinSize + mMainWidget->setSize(mMainWidget->getSize()); + } + } + void SettingsWindow::resetScrollbars() { mResolutionList->setScrollPosition(0); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 9c28733f9..ecf9bf0d9 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -1,83 +1,119 @@ #ifndef MWGUI_SETTINGS_H #define MWGUI_SETTINGS_H +#include + #include "windowbase.hpp" namespace MWGui { class SettingsWindow : public WindowBase { - public: - SettingsWindow(); + public: + SettingsWindow(); - void onOpen() override; + void onOpen() override; - void updateControlsBox(); + void updateControlsBox(); - void updateLightSettings(); + void updateLightSettings(); - void onResChange(int, int) override { center(); } + void updateVSyncModeSettings(); + + void updateWindowModeSettings(); + + void onResChange(int, int) override; protected: - MyGUI::TabControl* mSettingsTab; - MyGUI::Button* mOkButton; + MyGUI::TabControl* mSettingsTab; + MyGUI::Button* mOkButton; - // graphics - MyGUI::ListBox* mResolutionList; - MyGUI::Button* mFullscreenButton; - MyGUI::Button* mWindowBorderButton; - MyGUI::ComboBox* mTextureFilteringButton; - MyGUI::Widget* mAnisotropyBox; + // graphics + MyGUI::ListBox* mResolutionList; + MyGUI::ComboBox* mWindowModeList; + MyGUI::ComboBox* mVSyncModeList; + MyGUI::Button* mWindowBorderButton; + MyGUI::ComboBox* mTextureFilteringButton; - MyGUI::ComboBox* mWaterTextureSize; - MyGUI::ComboBox* mWaterReflectionDetail; + MyGUI::ComboBox* mWaterTextureSize; + MyGUI::ComboBox* mWaterReflectionDetail; + MyGUI::ComboBox* mWaterRainRippleDetail; - MyGUI::ComboBox* mMaxLights; - MyGUI::ComboBox* mLightingMethodButton; - MyGUI::Button* mLightsResetButton; + MyGUI::ComboBox* mMaxLights; + MyGUI::ComboBox* mLightingMethodButton; + MyGUI::Button* mLightsResetButton; - // controls - MyGUI::ScrollView* mControlsBox; - MyGUI::Button* mResetControlsButton; - MyGUI::Button* mKeyboardSwitch; - MyGUI::Button* mControllerSwitch; - bool mKeyboardMode; //if true, setting up the keyboard. Otherwise, it's controller + MyGUI::ComboBox* mPrimaryLanguage; + MyGUI::ComboBox* mSecondaryLanguage; + MyGUI::Button* mGmstOverridesL10n; - void onTabChanged(MyGUI::TabControl* _sender, size_t index); - void onOkButtonClicked(MyGUI::Widget* _sender); - void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); - void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos); - void onButtonToggled(MyGUI::Widget* _sender); - void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); - void onResolutionAccept(); - void onResolutionCancel(); - void highlightCurrentResolution(); + MyGUI::Widget* mWindowModeHint; - void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); - void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); + // controls + MyGUI::ScrollView* mControlsBox; + MyGUI::Button* mResetControlsButton; + MyGUI::Button* mKeyboardSwitch; + MyGUI::Button* mControllerSwitch; + bool mKeyboardMode; // if true, setting up the keyboard. Otherwise, it's controller - void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos); - void onLightsResetButtonClicked(MyGUI::Widget* _sender); - void onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos); + MyGUI::EditBox* mScriptFilter; + MyGUI::ListBox* mScriptList; + MyGUI::Widget* mScriptBox; + MyGUI::Widget* mScriptDisabled; + MyGUI::ScrollView* mScriptView; + LuaUi::LuaAdapter* mScriptAdapter; + int mCurrentPage; - void onRebindAction(MyGUI::Widget* _sender); - void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); - void onResetDefaultBindings(MyGUI::Widget* _sender); - void onResetDefaultBindingsAccept (); - void onKeyboardSwitchClicked(MyGUI::Widget* _sender); - void onControllerSwitchClicked(MyGUI::Widget* _sender); + void onTabChanged(MyGUI::TabControl* _sender, size_t index); + void onOkButtonClicked(MyGUI::Widget* _sender); + void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); + void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos); + void onButtonToggled(MyGUI::Widget* _sender); + void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); + void onResolutionAccept(); + void onResolutionCancel(); + void highlightCurrentResolution(); - void onWindowResize(MyGUI::Window* _sender); + void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); + void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); + void onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos); - void apply(); + void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos); + void onLightsResetButtonClicked(MyGUI::Widget* _sender); + void onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos); - void configureWidgets(MyGUI::Widget* widget, bool init); - void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); + void onPrimaryLanguageChanged(MyGUI::ComboBox* _sender, size_t pos) { onLanguageChanged(0, _sender, pos); } + void onSecondaryLanguageChanged(MyGUI::ComboBox* _sender, size_t pos) { onLanguageChanged(1, _sender, pos); } + void onLanguageChanged(size_t langPriority, MyGUI::ComboBox* _sender, size_t pos); + void onGmstOverridesL10nChanged(MyGUI::Widget* _sender); - void layoutControlsBox(); - - private: - void resetScrollbars(); + void onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos); + void onVSyncModeChanged(MyGUI::ComboBox* _sender, size_t pos); + + void onRebindAction(MyGUI::Widget* _sender); + void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); + void onResetDefaultBindings(MyGUI::Widget* _sender); + void onResetDefaultBindingsAccept(); + void onKeyboardSwitchClicked(MyGUI::Widget* _sender); + void onControllerSwitchClicked(MyGUI::Widget* _sender); + + void onWindowResize(MyGUI::Window* _sender); + + void onScriptFilterChange(MyGUI::EditBox*); + void onScriptListSelection(MyGUI::ListBox*, size_t index); + + void apply(); + + void configureWidgets(MyGUI::Widget* widget, bool init); + void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); + + void layoutControlsBox(); + void renderScriptSettings(); + + void computeMinimumWindowSize(); + + private: + void resetScrollbars(); }; } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 28b13cdf0..81074d7fc 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -1,60 +1,79 @@ #include "sortfilteritemmodel.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwworld/class.hpp" -#include "../mwworld/nullaction.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/nullaction.hpp" #include "../mwmechanics/alchemy.hpp" +#include "../mwmechanics/spellutil.hpp" namespace { - bool compareType(const std::string& type1, const std::string& type2) + unsigned int getTypeOrder(unsigned int type) { - // this defines the sorting order of types. types that are first in the vector appear before other types. - std::vector mapping; - mapping.emplace_back(typeid(ESM::Weapon).name() ); - mapping.emplace_back(typeid(ESM::Armor).name() ); - mapping.emplace_back(typeid(ESM::Clothing).name() ); - mapping.emplace_back(typeid(ESM::Potion).name() ); - mapping.emplace_back(typeid(ESM::Ingredient).name() ); - mapping.emplace_back(typeid(ESM::Apparatus).name() ); - mapping.emplace_back(typeid(ESM::Book).name() ); - mapping.emplace_back(typeid(ESM::Light).name() ); - mapping.emplace_back(typeid(ESM::Miscellaneous).name() ); - mapping.emplace_back(typeid(ESM::Lockpick).name() ); - mapping.emplace_back(typeid(ESM::Repair).name() ); - mapping.emplace_back(typeid(ESM::Probe).name() ); + switch (type) + { + case ESM::Weapon::sRecordId: + return 0; + case ESM::Armor::sRecordId: + return 1; + case ESM::Clothing::sRecordId: + return 2; + case ESM::Potion::sRecordId: + return 3; + case ESM::Ingredient::sRecordId: + return 4; + case ESM::Apparatus::sRecordId: + return 5; + case ESM::Book::sRecordId: + return 6; + case ESM::Light::sRecordId: + return 7; + case ESM::Miscellaneous::sRecordId: + return 8; + case ESM::Lockpick::sRecordId: + return 9; + case ESM::Repair::sRecordId: + return 10; + case ESM::Probe::sRecordId: + return 11; + } + assert(false && "Invalid type value"); + return std::numeric_limits::max(); + } - assert( std::find(mapping.begin(), mapping.end(), type1) != mapping.end() ); - assert( std::find(mapping.begin(), mapping.end(), type2) != mapping.end() ); - - return std::find(mapping.begin(), mapping.end(), type1) < std::find(mapping.begin(), mapping.end(), type2); + bool compareType(unsigned int type1, unsigned int type2) + { + return getTypeOrder(type1) < getTypeOrder(type2); } struct Compare { bool mSortByType; - Compare() : mSortByType(true) {} - bool operator() (const MWGui::ItemStack& left, const MWGui::ItemStack& right) + Compare() + : mSortByType(true) + { + } + bool operator()(const MWGui::ItemStack& left, const MWGui::ItemStack& right) { if (mSortByType && left.mType != right.mType) return left.mType < right.mType; @@ -62,15 +81,15 @@ namespace float result = 0; // compare items by type - std::string leftName = left.mBase.getTypeName(); - std::string rightName = right.mBase.getTypeName(); + auto leftType = left.mBase.getType(); + auto rightType = right.mBase.getType(); - if (leftName != rightName) - return compareType(leftName, rightName); + if (leftType != rightType) + return compareType(leftType, rightType); // compare items by name - leftName = Misc::StringUtils::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase)); - rightName = Misc::StringUtils::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase)); + std::string leftName = Utf8Stream::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase)); + std::string rightName = Utf8Stream::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase)); result = leftName.compare(rightName); if (result != 0) @@ -82,30 +101,34 @@ namespace // 3. item with constant effect comes before items with non-constant effects int leftChargePercent = -1; int rightChargePercent = -1; - leftName = left.mBase.getClass().getEnchantment(left.mBase); - rightName = right.mBase.getClass().getEnchantment(right.mBase); + const ESM::RefId& leftNameEnch = left.mBase.getClass().getEnchantment(left.mBase); + const ESM::RefId& rightNameEnch = right.mBase.getClass().getEnchantment(right.mBase); - if (!leftName.empty()) + if (!leftNameEnch.empty()) { - const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(leftName); + const ESM::Enchantment* ench + = MWBase::Environment::get().getESMStore()->get().search(leftNameEnch); if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) leftChargePercent = 101; else - leftChargePercent = static_cast(left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); + leftChargePercent + = static_cast(left.mBase.getCellRef().getNormalizedEnchantmentCharge(*ench) * 100); } } - if (!rightName.empty()) + if (!rightNameEnch.empty()) { - const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(rightName); + const ESM::Enchantment* ench + = MWBase::Environment::get().getESMStore()->get().search(rightNameEnch); if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) rightChargePercent = 101; else - rightChargePercent = static_cast(right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); + rightChargePercent + = static_cast(right.mBase.getCellRef().getNormalizedEnchantmentCharge(*ench) * 100); } } @@ -116,13 +139,15 @@ namespace // compare items by condition if (left.mBase.getClass().hasItemHealth(left.mBase) && right.mBase.getClass().hasItemHealth(right.mBase)) { - result = left.mBase.getClass().getItemHealth(left.mBase) - right.mBase.getClass().getItemHealth(right.mBase); + result = left.mBase.getClass().getItemHealth(left.mBase) + - right.mBase.getClass().getItemHealth(right.mBase); if (result != 0) return result > 0; } // compare items by remaining usage time - result = left.mBase.getClass().getRemainingUsageTime(left.mBase) - right.mBase.getClass().getRemainingUsageTime(right.mBase); + result = left.mBase.getClass().getRemainingUsageTime(left.mBase) + - right.mBase.getClass().getRemainingUsageTime(right.mBase); if (result != 0) return result > 0; @@ -136,12 +161,7 @@ namespace if (result != 0) return result > 0; - // compare items by Id - leftName = left.mBase.getCellRef().getRefId(); - rightName = right.mBase.getCellRef().getRefId(); - - result = leftName.compare(rightName); - return result < 0; + return left.mBase.getCellRef().getRefId() < right.mBase.getCellRef().getRefId(); } }; } @@ -149,14 +169,12 @@ namespace namespace MWGui { - SortFilterItemModel::SortFilterItemModel(ItemModel *sourceModel) + SortFilterItemModel::SortFilterItemModel(std::unique_ptr sourceModel) : mCategory(Category_All) , mFilter(0) , mSortByType(true) - , mNameFilter("") - , mEffectFilter("") { - mSourceModel = sourceModel; + mSourceModel = std::move(sourceModel); } bool SortFilterItemModel::allowedToUseItems() const @@ -164,7 +182,7 @@ namespace MWGui return mSourceModel->allowedToUseItems(); } - void SortFilterItemModel::addDragItem (const MWWorld::Ptr& dragItem, size_t count) + void SortFilterItemModel::addDragItem(const MWWorld::Ptr& dragItem, size_t count) { mDragItems.emplace_back(dragItem, count); } @@ -174,28 +192,34 @@ namespace MWGui mDragItems.clear(); } - bool SortFilterItemModel::filterAccepts (const ItemStack& item) + bool SortFilterItemModel::filterAccepts(const ItemStack& item) { MWWorld::Ptr base = item.mBase; int category = 0; - if (base.getTypeName() == typeid(ESM::Armor).name() - || base.getTypeName() == typeid(ESM::Clothing).name()) - category = Category_Apparel; - else if (base.getTypeName() == typeid(ESM::Weapon).name()) - category = Category_Weapon; - else if (base.getTypeName() == typeid(ESM::Ingredient).name() - || base.getTypeName() == typeid(ESM::Potion).name()) - category = Category_Magic; - else if (base.getTypeName() == typeid(ESM::Miscellaneous).name() - || base.getTypeName() == typeid(ESM::Ingredient).name() - || base.getTypeName() == typeid(ESM::Repair).name() - || base.getTypeName() == typeid(ESM::Lockpick).name() - || base.getTypeName() == typeid(ESM::Light).name() - || base.getTypeName() == typeid(ESM::Apparatus).name() - || base.getTypeName() == typeid(ESM::Book).name() - || base.getTypeName() == typeid(ESM::Probe).name()) - category = Category_Misc; + switch (base.getType()) + { + case ESM::Armor::sRecordId: + case ESM::Clothing::sRecordId: + category = Category_Apparel; + break; + case ESM::Weapon::sRecordId: + category = Category_Weapon; + break; + case ESM::Ingredient::sRecordId: + case ESM::Potion::sRecordId: + category = Category_Magic; + break; + case ESM::Miscellaneous::sRecordId: + case ESM::Repair::sRecordId: + case ESM::Lockpick::sRecordId: + case ESM::Light::sRecordId: + case ESM::Apparatus::sRecordId: + case ESM::Book::sRecordId: + case ESM::Probe::sRecordId: + category = Category_Misc; + break; + } if (item.mFlags & ItemStack::Flag_Enchanted) category |= Category_Magic; @@ -205,7 +229,7 @@ namespace MWGui if (mFilter & Filter_OnlyIngredients) { - if (base.getTypeName() != typeid(ESM::Ingredient).name()) + if (base.getType() != ESM::Ingredient::sRecordId) return false; if (!mNameFilter.empty() && !mEffectFilter.empty()) @@ -213,20 +237,20 @@ namespace MWGui if (!mNameFilter.empty()) { - const auto itemName = Misc::StringUtils::lowerCaseUtf8(base.getClass().getName(base)); + const auto itemName = Utf8Stream::lowerCaseUtf8(base.getClass().getName(base)); return itemName.find(mNameFilter) != std::string::npos; } if (!mEffectFilter.empty()) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const auto alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); const auto effects = MWMechanics::Alchemy::effectsDescription(base, alchemySkill); for (const auto& effect : effects) { - const auto ciEffect = Misc::StringUtils::lowerCaseUtf8(effect); + const auto ciEffect = Utf8Stream::lowerCaseUtf8(effect); if (ciEffect.find(mEffectFilter) != std::string::npos) return true; @@ -238,33 +262,32 @@ namespace MWGui if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) return false; - if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name() - || base.getCellRef().getSoul() == "" || !MWBase::Environment::get().getWorld()->getStore().get().search(base.getCellRef().getSoul()))) + if ((mFilter & Filter_OnlyChargedSoulstones) + && (base.getType() != ESM::Miscellaneous::sRecordId || base.getCellRef().getSoul().empty() + || !MWBase::Environment::get().getESMStore()->get().search(base.getCellRef().getSoul()))) return false; - if ((mFilter & Filter_OnlyRepairTools) && (base.getTypeName() != typeid(ESM::Repair).name())) + if ((mFilter & Filter_OnlyRepairTools) && (base.getType() != ESM::Repair::sRecordId)) return false; - if ((mFilter & Filter_OnlyEnchantable) && (item.mFlags & ItemStack::Flag_Enchanted - || (base.getTypeName() != typeid(ESM::Armor).name() - && base.getTypeName() != typeid(ESM::Clothing).name() - && base.getTypeName() != typeid(ESM::Weapon).name() - && base.getTypeName() != typeid(ESM::Book).name()))) + if ((mFilter & Filter_OnlyEnchantable) + && (item.mFlags & ItemStack::Flag_Enchanted + || (base.getType() != ESM::Armor::sRecordId && base.getType() != ESM::Clothing::sRecordId + && base.getType() != ESM::Weapon::sRecordId && base.getType() != ESM::Book::sRecordId))) return false; - if ((mFilter & Filter_OnlyEnchantable) && base.getTypeName() == typeid(ESM::Book).name() - && !base.get()->mBase->mData.mIsScroll) + if ((mFilter & Filter_OnlyEnchantable) && base.getType() == ESM::Book::sRecordId + && !base.get()->mBase->mData.mIsScroll) return false; if ((mFilter & Filter_OnlyUsableItems) && base.getClass().getScript(base).empty()) { - std::shared_ptr actionOnUse = base.getClass().use(base); + std::unique_ptr actionOnUse = base.getClass().use(base); if (!actionOnUse || actionOnUse->isNullAction()) return false; } - if ((mFilter & Filter_OnlyRepairable) && ( - !base.getClass().hasItemHealth(base) - || (base.getClass().getItemHealth(base) == base.getClass().getItemMaxHealth(base)) - || (base.getTypeName() != typeid(ESM::Weapon).name() - && base.getTypeName() != typeid(ESM::Armor).name()))) + if ((mFilter & Filter_OnlyRepairable) + && (!base.getClass().hasItemHealth(base) + || (base.getClass().getItemHealth(base) == base.getClass().getItemMaxHealth(base)) + || (base.getType() != ESM::Weapon::sRecordId && base.getType() != ESM::Armor::sRecordId))) return false; if (mFilter & Filter_OnlyRechargable) @@ -272,27 +295,29 @@ namespace MWGui if (!(item.mFlags & ItemStack::Flag_Enchanted)) return false; - std::string enchId = base.getClass().getEnchantment(base); - const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(enchId); + const ESM::RefId& enchId = base.getClass().getEnchantment(base); + const ESM::Enchantment* ench + = MWBase::Environment::get().getESMStore()->get().search(enchId); if (!ench) { - Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchId << "' on item " << base.getCellRef().getRefId(); + Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchId << "' on item " + << base.getCellRef().getRefId(); return false; } - if (base.getCellRef().getEnchantmentCharge() >= ench->mData.mCharge - || base.getCellRef().getEnchantmentCharge() == -1) + if (base.getCellRef().getEnchantmentCharge() == -1 + || base.getCellRef().getEnchantmentCharge() >= MWMechanics::getEnchantmentCharge(*ench)) return false; } - std::string compare = Misc::StringUtils::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase)); - if(compare.find(mNameFilter) == std::string::npos) + std::string compare = Utf8Stream::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase)); + if (compare.find(mNameFilter) == std::string::npos) return false; return true; } - ItemStack SortFilterItemModel::getItem (ModelIndex index) + ItemStack SortFilterItemModel::getItem(ModelIndex index) { if (index < 0) throw std::runtime_error("Invalid index supplied"); @@ -306,24 +331,24 @@ namespace MWGui return mItems.size(); } - void SortFilterItemModel::setCategory (int category) + void SortFilterItemModel::setCategory(int category) { mCategory = category; } - void SortFilterItemModel::setFilter (int filter) + void SortFilterItemModel::setFilter(int filter) { mFilter = filter; } - void SortFilterItemModel::setNameFilter (const std::string& filter) + void SortFilterItemModel::setNameFilter(const std::string& filter) { - mNameFilter = Misc::StringUtils::lowerCaseUtf8(filter); + mNameFilter = Utf8Stream::lowerCaseUtf8(filter); } - void SortFilterItemModel::setEffectFilter (const std::string& filter) + void SortFilterItemModel::setEffectFilter(const std::string& filter) { - mEffectFilter = Misc::StringUtils::lowerCaseUtf8(filter); + mEffectFilter = Utf8Stream::lowerCaseUtf8(filter); } void SortFilterItemModel::update() @@ -333,11 +358,12 @@ namespace MWGui size_t count = mSourceModel->getItemCount(); mItems.clear(); - for (size_t i=0; igetItem(i); - for (std::vector >::iterator it = mDragItems.begin(); it != mDragItems.end(); ++it) + for (std::vector>::iterator it = mDragItems.begin(); it != mDragItems.end(); + ++it) { if (item.mBase == it->first) { @@ -361,12 +387,12 @@ namespace MWGui mSourceModel->onClose(); } - bool SortFilterItemModel::onDropItem(const MWWorld::Ptr &item, int count) + bool SortFilterItemModel::onDropItem(const MWWorld::Ptr& item, int count) { return mSourceModel->onDropItem(item, count); } - bool SortFilterItemModel::onTakeItem(const MWWorld::Ptr &item, int 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 64a01f71b..66a22b3af 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -9,52 +9,51 @@ namespace MWGui class SortFilterItemModel : public ProxyItemModel { public: - SortFilterItemModel (ItemModel* sourceModel); + SortFilterItemModel(std::unique_ptr sourceModel); void update() override; - bool filterAccepts (const ItemStack& item); + bool filterAccepts(const ItemStack& item); bool allowedToUseItems() const override; - ItemStack getItem (ModelIndex index) override; + ItemStack getItem(ModelIndex index) override; size_t getItemCount() override; /// Dragged items are not displayed. - void addDragItem (const MWWorld::Ptr& dragItem, size_t count); + void addDragItem(const MWWorld::Ptr& dragItem, size_t count); void clearDragItems(); - void setCategory (int category); - void setFilter (int filter); - void setNameFilter (const std::string& filter); - void setEffectFilter (const std::string& filter); + void setCategory(int category); + void setFilter(int filter); + void setNameFilter(const std::string& filter); + void setEffectFilter(const std::string& filter); /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } void onClose() override; - bool onDropItem(const MWWorld::Ptr &item, int count) override; - bool onTakeItem(const MWWorld::Ptr &item, int count) override; + bool onDropItem(const MWWorld::Ptr& item, int count) override; + bool onTakeItem(const MWWorld::Ptr& item, int count) override; - static constexpr int Category_Weapon = (1<<1); - static constexpr int Category_Apparel = (1<<2); - static constexpr int Category_Misc = (1<<3); - static constexpr int Category_Magic = (1<<4); + static constexpr int Category_Weapon = (1 << 1); + static constexpr int Category_Apparel = (1 << 2); + static constexpr int Category_Misc = (1 << 3); + static constexpr int Category_Magic = (1 << 4); static constexpr int Category_All = 255; - static constexpr int Filter_OnlyIngredients = (1<<0); - static constexpr int Filter_OnlyEnchanted = (1<<1); - static constexpr int Filter_OnlyEnchantable = (1<<2); - static constexpr int Filter_OnlyChargedSoulstones = (1<<3); - static constexpr int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action - static constexpr int Filter_OnlyRepairable = (1<<5); - static constexpr int Filter_OnlyRechargable = (1<<6); - static constexpr int Filter_OnlyRepairTools = (1<<7); - + static constexpr int Filter_OnlyIngredients = (1 << 0); + static constexpr int Filter_OnlyEnchanted = (1 << 1); + static constexpr int Filter_OnlyEnchantable = (1 << 2); + static constexpr int Filter_OnlyChargedSoulstones = (1 << 3); + static constexpr int Filter_OnlyUsableItems = (1 << 4); // Only items with a Use action + static constexpr int Filter_OnlyRepairable = (1 << 5); + static constexpr int Filter_OnlyRechargable = (1 << 6); + static constexpr int Filter_OnlyRepairTools = (1 << 7); private: std::vector mItems; - std::vector > mDragItems; + std::vector> mDragItems; int mCategory; int mFilter; diff --git a/apps/openmw/mwgui/soulgemdialog.cpp b/apps/openmw/mwgui/soulgemdialog.cpp index 345c8b722..8aaf18215 100644 --- a/apps/openmw/mwgui/soulgemdialog.cpp +++ b/apps/openmw/mwgui/soulgemdialog.cpp @@ -8,7 +8,7 @@ namespace MWGui { - void SoulgemDialog::show(const MWWorld::Ptr &soulgem) + void SoulgemDialog::show(const MWWorld::Ptr& soulgem) { mSoulgem = soulgem; std::vector buttons; diff --git a/apps/openmw/mwgui/soulgemdialog.hpp b/apps/openmw/mwgui/soulgemdialog.hpp index 9aea1f339..775378f76 100644 --- a/apps/openmw/mwgui/soulgemdialog.hpp +++ b/apps/openmw/mwgui/soulgemdialog.hpp @@ -11,10 +11,12 @@ namespace MWGui class SoulgemDialog { public: - SoulgemDialog (MessageBoxManager* manager) - : mManager(manager) {} + SoulgemDialog(MessageBoxManager* manager) + : mManager(manager) + { + } - void show (const MWWorld::Ptr& soulgem); + void show(const MWWorld::Ptr& soulgem); void onButtonPressed(int button); diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 81fedbcc3..f2eb79a13 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -1,9 +1,10 @@ #include "spellbuyingwindow.hpp" -#include #include +#include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -16,23 +17,28 @@ /* End of tes3mp addition */ +======= +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spells.hpp" namespace MWGui { - SpellBuyingWindow::SpellBuyingWindow() : - WindowBase("openmw_spell_buying_window.layout") + SpellBuyingWindow::SpellBuyingWindow() + : WindowBase("openmw_spell_buying_window.layout") , mCurrentY(0) { getWidget(mCancelButton, "CancelButton"); @@ -42,21 +48,19 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onCancelButtonClicked); } - bool SpellBuyingWindow::sortSpells (const ESM::Spell* left, const ESM::Spell* right) + bool SpellBuyingWindow::sortSpells(const ESM::Spell* left, const ESM::Spell* right) { - std::string leftName = Misc::StringUtils::lowerCase(left->mName); - std::string rightName = Misc::StringUtils::lowerCase(right->mName); - - return leftName.compare(rightName) < 0; + return Misc::StringUtils::ciLess(left->mName, right->mName); } void SpellBuyingWindow::addSpell(const ESM::Spell& spell) { - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - int price = std::max(1, static_cast(spell.mData.mCost*store.get().find("fSpellValueMult")->mValue.getFloat())); - price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); + int price = std::max(1, + static_cast( + spell.mData.mCost * store.get().find("fSpellValueMult")->mValue.getFloat())); + price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); @@ -65,39 +69,34 @@ namespace MWGui int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; - MyGUI::Button* toAdd = - mSpellsView->createWidget( - price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip - 0, - mCurrentY, - 200, - lineHeight, - MyGUI::Align::Default - ); + MyGUI::Button* toAdd = mSpellsView->createWidget(price <= playerGold + ? "SandTextButton" + : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip + 0, mCurrentY, 200, lineHeight, MyGUI::Align::Default); mCurrentY += lineHeight; toAdd->setUserData(price); - toAdd->setCaptionWithReplacing(spell.mName+" - "+MyGUI::utility::toString(price)+"#{sgp}"); + toAdd->setCaptionWithReplacing(spell.mName + " - " + MyGUI::utility::toString(price) + "#{sgp}"); toAdd->setSize(mSpellsView->getWidth(), lineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &SpellBuyingWindow::onMouseWheel); toAdd->setUserString("ToolTipType", "Spell"); - toAdd->setUserString("Spell", spell.mId); + toAdd->setUserString("Spell", spell.mId.serialize()); toAdd->setUserString("SpellCost", std::to_string(spell.mData.mCost)); toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onSpellButtonClick); - mSpellsWidgetMap.insert(std::make_pair (toAdd, spell.mId)); + mSpellsWidgetMap.insert(std::make_pair(toAdd, spell.mId)); } void SpellBuyingWindow::clearSpells() { - mSpellsView->setViewOffset(MyGUI::IntPoint(0,0)); + mSpellsView->setViewOffset(MyGUI::IntPoint(0, 0)); mCurrentY = 0; while (mSpellsView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mSpellsView->getChildAt(0)); mSpellsWidgetMap.clear(); } - void SpellBuyingWindow::setPtr(const MWWorld::Ptr &actor) + void SpellBuyingWindow::setPtr(const MWWorld::Ptr& actor) { setPtr(actor, 0); } @@ -108,30 +107,27 @@ namespace MWGui mPtr = actor; clearSpells(); - MWMechanics::Spells& merchantSpells = actor.getClass().getCreatureStats (actor).getSpells(); + MWMechanics::Spells& merchantSpells = actor.getClass().getCreatureStats(actor).getSpells(); std::vector spellsToSort; - for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter) + for (const ESM::Spell* spell : merchantSpells) { - const ESM::Spell* spell = iter->first; - - if (spell->mData.mType!=ESM::Spell::ST_Spell) + if (spell->mData.mType != ESM::Spell::ST_Spell) continue; // don't try to sell diseases, curses or powers if (actor.getClass().isNpc()) { - const ESM::Race* race = - MWBase::Environment::get().getWorld()->getStore().get().find( - actor.get()->mBase->mRace); + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find( + actor.get()->mBase->mRace); if (race->mPowers.exists(spell->mId)) continue; } - if (playerHasSpell(iter->first->mId)) + if (playerHasSpell(spell->mId)) continue; - spellsToSort.push_back(iter->first); + spellsToSort.push_back(spell); } std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells); @@ -145,14 +141,16 @@ namespace MWGui updateLabels(); - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mSpellsView->setVisibleVScroll(false); - mSpellsView->setCanvasSize (MyGUI::IntSize(mSpellsView->getWidth(), std::max(mSpellsView->getHeight(), mCurrentY))); + mSpellsView->setCanvasSize( + MyGUI::IntSize(mSpellsView->getWidth(), std::max(mSpellsView->getHeight(), mCurrentY))); mSpellsView->setVisibleVScroll(true); mSpellsView->setViewOffset(MyGUI::IntPoint(0, startOffset)); } - bool SpellBuyingWindow::playerHasSpell(const std::string &id) + bool SpellBuyingWindow::playerHasSpell(const ESM::RefId& id) { MWWorld::Ptr player = MWMechanics::getPlayer(); return player.getClass().getCreatureStats(player).getSpells().hasSpell(id); @@ -168,6 +166,7 @@ namespace MWGui MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); +<<<<<<< HEAD spells.add (mSpellsWidgetMap.find(_sender)->second); /* @@ -181,6 +180,10 @@ namespace MWGui */ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); +======= + spells.add(mSpellsWidgetMap.find(_sender)->second); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 // add gold to NPC trading gold pool MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); @@ -204,12 +207,12 @@ namespace MWGui setPtr(mPtr, mSpellsView->getViewOffset().top); - MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Item Gold Up")); } void SpellBuyingWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellBuying); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_SpellBuying); } void SpellBuyingWindow::updateLabels() @@ -218,25 +221,23 @@ namespace MWGui int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); - mPlayerGold->setCoord(8, - mPlayerGold->getTop(), - mPlayerGold->getTextSize().width, - mPlayerGold->getHeight()); + mPlayerGold->setCoord(8, mPlayerGold->getTop(), mPlayerGold->getTextSize().width, mPlayerGold->getHeight()); } void SpellBuyingWindow::onReferenceUnavailable() { - // remove both Spells and Dialogue (since you always trade with the NPC/creature that you have previously talked to) + // remove both Spells and Dialogue (since you always trade with the NPC/creature that you have previously talked + // to) MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellBuying); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } void SpellBuyingWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (mSpellsView->getViewOffset().top + _rel*0.3 > 0) + if (mSpellsView->getViewOffset().top + _rel * 0.3 > 0) mSpellsView->setViewOffset(MyGUI::IntPoint(0, 0)); else - mSpellsView->setViewOffset(MyGUI::IntPoint(0, static_cast(mSpellsView->getViewOffset().top + _rel*0.3f))); + mSpellsView->setViewOffset( + MyGUI::IntPoint(0, static_cast(mSpellsView->getViewOffset().top + _rel * 0.3f))); } } - diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index f46c43796..66919e5d3 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -1,9 +1,9 @@ #ifndef MWGUI_SpellBuyingWINDOW_H #define MWGUI_SpellBuyingWINDOW_H -#include "windowbase.hpp" #include "referenceinterface.hpp" - +#include "windowbase.hpp" +#include namespace ESM { struct Spell; @@ -11,48 +11,48 @@ namespace ESM namespace MyGUI { - class Gui; - class Widget; + class Gui; + class Widget; } namespace MWGui { class SpellBuyingWindow : public ReferenceInterface, public WindowBase { - public: - SpellBuyingWindow(); + public: + SpellBuyingWindow(); - void setPtr(const MWWorld::Ptr& actor) override; - void setPtr(const MWWorld::Ptr& actor, int startOffset); + void setPtr(const MWWorld::Ptr& actor) override; + void setPtr(const MWWorld::Ptr& actor, int startOffset); - void onFrame(float dt) override { checkReferenceAvailable(); } - void clear() override { resetReference(); } + void onFrame(float dt) override { checkReferenceAvailable(); } + void clear() override { resetReference(); } - void onResChange(int, int) override { center(); } + void onResChange(int, int) override { center(); } - protected: - MyGUI::Button* mCancelButton; - MyGUI::TextBox* mPlayerGold; + protected: + MyGUI::Button* mCancelButton; + MyGUI::TextBox* mPlayerGold; - MyGUI::ScrollView* mSpellsView; + MyGUI::ScrollView* mSpellsView; - std::map mSpellsWidgetMap; + std::map mSpellsWidgetMap; - void onCancelButtonClicked(MyGUI::Widget* _sender); - void onSpellButtonClick(MyGUI::Widget* _sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void addSpell(const ESM::Spell& spell); - void clearSpells(); - int mCurrentY; + void onCancelButtonClicked(MyGUI::Widget* _sender); + void onSpellButtonClick(MyGUI::Widget* _sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void addSpell(const ESM::Spell& spell); + void clearSpells(); + int mCurrentY; - void updateLabels(); + void updateLabels(); - void onReferenceUnavailable() override; + void onReferenceUnavailable() override; - bool playerHasSpell (const std::string& id); + bool playerHasSpell(const ESM::RefId& id); - private: - static bool sortSpells (const ESM::Spell* left, const ESM::Spell* right); + private: + static bool sortSpells(const ESM::Spell* left, const ESM::Spell* right); }; } diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index b5b3c854a..2386b5fe1 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -1,11 +1,15 @@ #include "spellcreationdialog.hpp" -#include +#include #include +#include +#include -#include +#include +#include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -20,32 +24,38 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +======= +#include + +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/store.hpp" -#include "../mwmechanics/spells.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellutil.hpp" -#include "tooltips.hpp" #include "class.hpp" +#include "tooltips.hpp" #include "widgets.hpp" namespace { - bool sortMagicEffects (short id1, short id2) + bool sortMagicEffects(short id1, short id2) { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); - return gmst.find(ESM::MagicEffect::effectIdToString (id1))->mValue.getString() - < gmst.find(ESM::MagicEffect::effectIdToString (id2))->mValue.getString(); + return gmst.find(ESM::MagicEffect::indexToGmstString(id1))->mValue.getString() + < gmst.find(ESM::MagicEffect::indexToGmstString(id2))->mValue.getString(); } void init(ESM::ENAMstruct& effect) @@ -97,8 +107,10 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked); mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onDeleteButtonClicked); - mMagnitudeMinSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged); - mMagnitudeMaxSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged); + mMagnitudeMinSlider->eventScrollChangePosition + += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged); + mMagnitudeMaxSlider->eventScrollChangePosition + += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged); mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged); mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged); } @@ -116,26 +128,22 @@ namespace MWGui bool EditEffectDialog::exit() { - if(mEditing) + if (mEditing) eventEffectModified(mOldEffect); else eventEffectRemoved(mEffect); return true; } - void EditEffectDialog::newEffect (const ESM::MagicEffect *effect) + void EditEffectDialog::newEffect(const ESM::MagicEffect* effect) { bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; - bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; - - if (!allowSelf && !allowTouch && !allowTarget) - return; // TODO: Show an error message popup? setMagicEffect(effect); mEditing = false; - mDeleteButton->setVisible (false); + mDeleteButton->setVisible(false); mEffect.mRange = ESM::RT_Self; if (!allowSelf) @@ -152,14 +160,14 @@ namespace MWGui onRangeButtonClicked(mRangeButton); - mMagnitudeMinSlider->setScrollPosition (0); - mMagnitudeMaxSlider->setScrollPosition (0); - mAreaSlider->setScrollPosition (0); - mDurationSlider->setScrollPosition (0); + mMagnitudeMinSlider->setScrollPosition(0); + mMagnitudeMaxSlider->setScrollPosition(0); + mAreaSlider->setScrollPosition(0); + mDurationSlider->setScrollPosition(0); mDurationValue->setCaption("1"); mMagnitudeMinValue->setCaption("1"); - const std::string to = MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-"); + const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; mMagnitudeMaxValue->setCaption(to + " 1"); mAreaValue->setCaption("0"); @@ -167,44 +175,45 @@ namespace MWGui setVisible(true); } - void EditEffectDialog::editEffect (ESM::ENAMstruct effect) + void EditEffectDialog::editEffect(ESM::ENAMstruct effect) { - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); setMagicEffect(magicEffect); mOldEffect = effect; mEffect = effect; mEditing = true; - mDeleteButton->setVisible (true); + mDeleteButton->setVisible(true); - mMagnitudeMinSlider->setScrollPosition (effect.mMagnMin-1); - mMagnitudeMaxSlider->setScrollPosition (effect.mMagnMax-1); - mAreaSlider->setScrollPosition (effect.mArea); - mDurationSlider->setScrollPosition (effect.mDuration-1); + mMagnitudeMinSlider->setScrollPosition(effect.mMagnMin - 1); + mMagnitudeMaxSlider->setScrollPosition(effect.mMagnMax - 1); + mAreaSlider->setScrollPosition(effect.mArea); + mDurationSlider->setScrollPosition(effect.mDuration - 1); if (mEffect.mRange == ESM::RT_Self) - mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}"); + mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); else if (mEffect.mRange == ESM::RT_Target) - mRangeButton->setCaptionWithReplacing ("#{sRangeTarget}"); + mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); else if (mEffect.mRange == ESM::RT_Touch) - mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}"); + mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); - onMagnitudeMinChanged (mMagnitudeMinSlider, effect.mMagnMin-1); - onMagnitudeMaxChanged (mMagnitudeMinSlider, effect.mMagnMax-1); - onAreaChanged (mAreaSlider, effect.mArea); - onDurationChanged (mDurationSlider, effect.mDuration-1); + onMagnitudeMinChanged(mMagnitudeMinSlider, effect.mMagnMin - 1); + onMagnitudeMaxChanged(mMagnitudeMinSlider, effect.mMagnMax - 1); + onAreaChanged(mAreaSlider, effect.mArea); + onDurationChanged(mDurationSlider, effect.mDuration - 1); eventEffectModified(mEffect); updateBoxes(); } - void EditEffectDialog::setMagicEffect (const ESM::MagicEffect *effect) + void EditEffectDialog::setMagicEffect(const ESM::MagicEffect* effect) { - mEffectImage->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(effect->mIcon)); + mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath( + effect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); - mEffectName->setCaptionWithReplacing("#{"+ESM::MagicEffect::effectIdToString (effect->mIndex)+"}"); + mEffectName->setCaptionWithReplacing("#{" + ESM::MagicEffect::indexToGmstString(effect->mIndex) + "}"); mEffect.mEffectID = effect->mIndex; @@ -218,129 +227,131 @@ namespace MWGui static int startY = mMagnitudeBox->getPosition().top; int curY = startY; - mMagnitudeBox->setVisible (false); - mDurationBox->setVisible (false); - mAreaBox->setVisible (false); + mMagnitudeBox->setVisible(false); + mDurationBox->setVisible(false); + mAreaBox->setVisible(false); if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { mMagnitudeBox->setPosition(mMagnitudeBox->getPosition().left, curY); - mMagnitudeBox->setVisible (true); + mMagnitudeBox->setVisible(true); curY += mMagnitudeBox->getSize().height; } - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)&&mConstantEffect==false) + if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) && mConstantEffect == false) { mDurationBox->setPosition(mDurationBox->getPosition().left, curY); - mDurationBox->setVisible (true); + mDurationBox->setVisible(true); curY += mDurationBox->getSize().height; } if (mEffect.mRange != ESM::RT_Self) { mAreaBox->setPosition(mAreaBox->getPosition().left, curY); - mAreaBox->setVisible (true); - //curY += mAreaBox->getSize().height; + mAreaBox->setVisible(true); + // curY += mAreaBox->getSize().height; } } - void EditEffectDialog::onRangeButtonClicked (MyGUI::Widget* sender) + void EditEffectDialog::onRangeButtonClicked(MyGUI::Widget* sender) { - mEffect.mRange = (mEffect.mRange+1)%3; + mEffect.mRange = (mEffect.mRange + 1) % 3; // cycle through range types until we find something that's allowed - // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect dialog) + // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect + // dialog) bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; if (mEffect.mRange == ESM::RT_Self && !allowSelf) - mEffect.mRange = (mEffect.mRange+1)%3; + mEffect.mRange = (mEffect.mRange + 1) % 3; if (mEffect.mRange == ESM::RT_Touch && !allowTouch) - mEffect.mRange = (mEffect.mRange+1)%3; + mEffect.mRange = (mEffect.mRange + 1) % 3; if (mEffect.mRange == ESM::RT_Target && !allowTarget) - mEffect.mRange = (mEffect.mRange+1)%3; + mEffect.mRange = (mEffect.mRange + 1) % 3; - if(mEffect.mRange == ESM::RT_Self) + if (mEffect.mRange == ESM::RT_Self) { mAreaSlider->setScrollPosition(0); - onAreaChanged(mAreaSlider,0); + onAreaChanged(mAreaSlider, 0); } if (mEffect.mRange == ESM::RT_Self) - mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}"); + mRangeButton->setCaptionWithReplacing("#{sRangeSelf}"); else if (mEffect.mRange == ESM::RT_Target) - mRangeButton->setCaptionWithReplacing ("#{sRangeTarget}"); + mRangeButton->setCaptionWithReplacing("#{sRangeTarget}"); else if (mEffect.mRange == ESM::RT_Touch) - mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}"); + mRangeButton->setCaptionWithReplacing("#{sRangeTouch}"); updateBoxes(); eventEffectModified(mEffect); } - void EditEffectDialog::onDeleteButtonClicked (MyGUI::Widget* sender) + void EditEffectDialog::onDeleteButtonClicked(MyGUI::Widget* sender) { setVisible(false); eventEffectRemoved(mEffect); } - void EditEffectDialog::onOkButtonClicked (MyGUI::Widget* sender) + void EditEffectDialog::onOkButtonClicked(MyGUI::Widget* sender) { setVisible(false); } - void EditEffectDialog::onCancelButtonClicked (MyGUI::Widget* sender) + void EditEffectDialog::onCancelButtonClicked(MyGUI::Widget* sender) { setVisible(false); exit(); } - void EditEffectDialog::setSkill (int skill) + void EditEffectDialog::setSkill(ESM::RefId skill) { - mEffect.mSkill = skill; + mEffect.mSkill = skill.getIf()->getValue(); eventEffectModified(mEffect); } - void EditEffectDialog::setAttribute (int attribute) + void EditEffectDialog::setAttribute(int attribute) { mEffect.mAttribute = attribute; eventEffectModified(mEffect); } - void EditEffectDialog::onMagnitudeMinChanged (MyGUI::ScrollBar* sender, size_t pos) + void EditEffectDialog::onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos) { - mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos+1)); - mEffect.mMagnMin = pos+1; + mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos + 1)); + mEffect.mMagnMin = pos + 1; // trigger the check again (see below) - onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition ()); + onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition()); eventEffectModified(mEffect); } - void EditEffectDialog::onMagnitudeMaxChanged (MyGUI::ScrollBar* sender, size_t pos) + void EditEffectDialog::onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos) { // make sure the max value is actually larger or equal than the min value - size_t magnMin = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning - if (pos+1 < magnMin) + size_t magnMin + = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning + if (pos + 1 < magnMin) { - pos = mEffect.mMagnMin-1; - sender->setScrollPosition (pos); + pos = mEffect.mMagnMin - 1; + sender->setScrollPosition(pos); } - mEffect.mMagnMax = pos+1; - const std::string to = MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-"); + mEffect.mMagnMax = pos + 1; + const std::string to{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-") }; - mMagnitudeMaxValue->setCaption(to + " " + MyGUI::utility::toString(pos+1)); + mMagnitudeMaxValue->setCaption(to + " " + MyGUI::utility::toString(pos + 1)); eventEffectModified(mEffect); } - void EditEffectDialog::onDurationChanged (MyGUI::ScrollBar* sender, size_t pos) + void EditEffectDialog::onDurationChanged(MyGUI::ScrollBar* sender, size_t pos) { - mDurationValue->setCaption(MyGUI::utility::toString(pos+1)); - mEffect.mDuration = pos+1; + mDurationValue->setCaption(MyGUI::utility::toString(pos + 1)); + mEffect.mDuration = pos + 1; eventEffectModified(mEffect); } - void EditEffectDialog::onAreaChanged (MyGUI::ScrollBar* sender, size_t pos) + void EditEffectDialog::onAreaChanged(MyGUI::ScrollBar* sender, size_t pos) { mAreaValue->setCaption(MyGUI::utility::toString(pos)); mEffect.mArea = pos; @@ -369,36 +380,36 @@ namespace MWGui setWidgets(mAvailableEffectsList, mUsedEffectsView); } - void SpellCreationDialog::setPtr (const MWWorld::Ptr& actor) + void SpellCreationDialog::setPtr(const MWWorld::Ptr& actor) { mPtr = actor; - mNameEdit->setCaption(""); + mNameEdit->setCaption({}); startEditing(); } - void SpellCreationDialog::onCancelButtonClicked (MyGUI::Widget* sender) + void SpellCreationDialog::onCancelButtonClicked(MyGUI::Widget* sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellCreation); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_SpellCreation); } - void SpellCreationDialog::onBuyButtonClicked (MyGUI::Widget* sender) + void SpellCreationDialog::onBuyButtonClicked(MyGUI::Widget* sender) { if (mEffects.size() <= 0) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage30}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage30}"); return; } - if (mNameEdit->getCaption () == "") + if (mNameEdit->getCaption().empty()) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage10}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage10}"); return; } if (mMagickaCost->getCaption() == "0") { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu8}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sEnchantmentMenu8}"); return; } @@ -408,13 +419,13 @@ namespace MWGui int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); if (price > playerGold) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage18}"); return; } mSpell.mName = mNameEdit->getCaption(); - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); // add gold to NPC trading gold pool MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); @@ -436,8 +447,9 @@ namespace MWGui End of tes3mp change (major) */ - MWBase::Environment::get().getWindowManager()->playSound ("Mysticism Hit"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Mysticism Hit")); +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -447,21 +459,27 @@ namespace MWGui */ /* const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell); +======= + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->insert(mSpell); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add(spell->mId); +<<<<<<< HEAD */ mwmp::Main::get().getNetworking()->getWorldstate()->sendSpellRecord(&mSpell); /* End of tes3mp addition */ +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); } - void SpellCreationDialog::onAccept(MyGUI::EditBox *sender) + void SpellCreationDialog::onAccept(MyGUI::EditBox* sender) { onBuyButtonClicked(sender); @@ -475,13 +493,13 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); } - void SpellCreationDialog::onReferenceUnavailable () + void SpellCreationDialog::onReferenceUnavailable() { - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellCreation); } - void SpellCreationDialog::notifyEffectsChanged () + void SpellCreationDialog::notifyEffectsChanged() { if (mEffects.empty()) { @@ -493,12 +511,12 @@ namespace MWGui float y = 0; - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); for (const ESM::ENAMstruct& effect : mEffects) { - y += std::max(1.f, MWMechanics::calcEffectCost(effect)); + y += std::max( + 1.f, MWMechanics::calcEffectCost(effect, nullptr, MWMechanics::EffectCostMethod::PlayerSpell)); if (effect.mRange == ESM::RT_Target) y *= 1.5; @@ -513,8 +531,7 @@ namespace MWGui mMagickaCost->setCaption(MyGUI::utility::toString(int(y))); - float fSpellMakingValueMult = - store.get().find("fSpellMakingValueMult")->mValue.getFloat(); + float fSpellMakingValueMult = store.get().find("fSpellMakingValueMult")->mValue.getFloat(); int price = std::max(1, static_cast(y * fSpellMakingValueMult)); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); @@ -529,13 +546,10 @@ namespace MWGui // ------------------------------------------------------------------------------------------------ - EffectEditorBase::EffectEditorBase(Type type) : mAvailableEffectsList(nullptr) , mUsedEffectsView(nullptr) , mAddEffectDialog() - , mSelectAttributeDialog(nullptr) - , mSelectSkillDialog(nullptr) , mSelectedEffect(0) , mSelectedKnownEffectId(0) , mConstantEffect(false) @@ -545,14 +559,12 @@ namespace MWGui mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified); mAddEffectDialog.eventEffectRemoved += MyGUI::newDelegate(this, &EffectEditorBase::onEffectRemoved); - mAddEffectDialog.setVisible (false); + mAddEffectDialog.setVisible(false); } - EffectEditorBase::~EffectEditorBase() - { - } + EffectEditorBase::~EffectEditorBase() {} - void EffectEditorBase::startEditing () + void EffectEditorBase::startEditing() { // get the list of magic effects that are known to the player @@ -562,20 +574,20 @@ namespace MWGui std::vector knownEffects; - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; - // only normal spells count if (spell->mData.mType != ESM::Spell::ST_Spell) continue; for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) { - const ESM::MagicEffect * effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectInfo.mEffectID); + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(effectInfo.mEffectID); // skip effects that do not allow spellmaking/enchanting - int requiredFlags = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; + int requiredFlags + = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; if (!(effect->mData.mFlags & requiredFlags)) continue; @@ -586,74 +598,76 @@ namespace MWGui std::sort(knownEffects.begin(), knownEffects.end(), sortMagicEffects); - mAvailableEffectsList->clear (); + mAvailableEffectsList->clear(); - int i=0; + int i = 0; for (const short effectId : knownEffects) { - mAvailableEffectsList->addItem(MWBase::Environment::get().getWorld ()->getStore ().get().find( - ESM::MagicEffect::effectIdToString(effectId))->mValue.getString()); + mAvailableEffectsList->addItem(MWBase::Environment::get() + .getESMStore() + ->get() + .find(ESM::MagicEffect::indexToGmstString(effectId)) + ->mValue.getString()); mButtonMapping[i] = effectId; ++i; } - mAvailableEffectsList->adjustSize (); + mAvailableEffectsList->adjustSize(); mAvailableEffectsList->scrollToTop(); for (const short effectId : knownEffects) { - std::string name = MWBase::Environment::get().getWorld ()->getStore ().get().find( - ESM::MagicEffect::effectIdToString(effectId))->mValue.getString(); + const std::string& name = MWBase::Environment::get() + .getESMStore() + ->get() + .find(ESM::MagicEffect::indexToGmstString(effectId)) + ->mValue.getString(); MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name); - ToolTips::createMagicEffectToolTip (w, effectId); + ToolTips::createMagicEffectToolTip(w, effectId); } mEffects.clear(); - updateEffectsView (); + updateEffectsView(); } - void EffectEditorBase::setWidgets (Gui::MWList *availableEffectsList, MyGUI::ScrollView *usedEffectsView) + void EffectEditorBase::setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView) { mAvailableEffectsList = availableEffectsList; mUsedEffectsView = usedEffectsView; - mAvailableEffectsList->eventWidgetSelected += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked); + mAvailableEffectsList->eventWidgetSelected + += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked); } - void EffectEditorBase::onSelectAttribute () + void EffectEditorBase::onSelectAttribute() { - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); mAddEffectDialog.newEffect(effect); - mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId()); - MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); - mSelectAttributeDialog = nullptr; + mAddEffectDialog.setAttribute(mSelectAttributeDialog->getAttributeId()); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); } - void EffectEditorBase::onSelectSkill () + void EffectEditorBase::onSelectSkill() { - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); mAddEffectDialog.newEffect(effect); - mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId()); - MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); - mSelectSkillDialog = nullptr; + mAddEffectDialog.setSkill(mSelectSkillDialog->getSkillId()); + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); } - void EffectEditorBase::onAttributeOrSkillCancel () + void EffectEditorBase::onAttributeOrSkillCancel() { - if (mSelectSkillDialog) - MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); - if (mSelectAttributeDialog) - MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); - - mSelectSkillDialog = nullptr; - mSelectAttributeDialog = nullptr; + if (mSelectSkillDialog != nullptr) + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectSkillDialog)); + if (mSelectAttributeDialog != nullptr) + MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mSelectAttributeDialog)); } - void EffectEditorBase::onAvailableEffectClicked (MyGUI::Widget* sender) + void EffectEditorBase::onAvailableEffectClicked(MyGUI::Widget* sender) { if (mEffects.size() >= 8) { @@ -664,24 +678,31 @@ namespace MWGui int buttonId = *sender->getUserData(); mSelectedKnownEffectId = mButtonMapping[buttonId]; - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); + + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; + bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; + + if (!allowSelf && !allowTouch && !allowTarget) + return; // TODO: Show an error message popup? if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) { - delete mSelectSkillDialog; - mSelectSkillDialog = new SelectSkillDialog(); + mSelectSkillDialog = std::make_unique(); mSelectSkillDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); mSelectSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectSkill); - mSelectSkillDialog->setVisible (true); + mSelectSkillDialog->setVisible(true); } else if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) { - delete mSelectAttributeDialog; - mSelectAttributeDialog = new SelectAttributeDialog(); - mSelectAttributeDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); - mSelectAttributeDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute); - mSelectAttributeDialog->setVisible (true); + mSelectAttributeDialog = std::make_unique(); + mSelectAttributeDialog->eventCancel + += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel); + mSelectAttributeDialog->eventItemSelected + += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute); + mSelectAttributeDialog->setVisible(true); } else { @@ -689,7 +710,7 @@ namespace MWGui { if (effectInfo.mEffectID == mSelectedKnownEffectId) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sOnetypeEffectMessage}"); return; } } @@ -698,25 +719,25 @@ namespace MWGui } } - void EffectEditorBase::onEffectModified (ESM::ENAMstruct effect) + void EffectEditorBase::onEffectModified(ESM::ENAMstruct effect) { mEffects[mSelectedEffect] = effect; updateEffectsView(); } - void EffectEditorBase::onEffectRemoved (ESM::ENAMstruct effect) + void EffectEditorBase::onEffectRemoved(ESM::ENAMstruct effect) { mEffects.erase(mEffects.begin() + mSelectedEffect); updateEffectsView(); } - void EffectEditorBase::updateEffectsView () + void EffectEditorBase::updateEffectsView() { - MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator (); - MyGUI::Gui::getInstance ().destroyWidgets (oldWidgets); + MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator(); + MyGUI::Gui::getInstance().destroyWidgets(oldWidgets); - MyGUI::IntSize size(0,0); + MyGUI::IntSize size(0, 0); int i = 0; for (const ESM::ENAMstruct& effectInfo : mEffects) @@ -732,25 +753,28 @@ namespace MWGui params.mArea = effectInfo.mArea; params.mIsConstant = mConstantEffect; - MyGUI::Button* button = mUsedEffectsView->createWidget("", MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); + MyGUI::Button* button = mUsedEffectsView->createWidget( + {}, MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); button->setUserData(i); button->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onEditEffect); - button->setNeedMouseFocus (true); + button->setNeedMouseFocus(true); - Widgets::MWSpellEffectPtr effect = button->createWidget("MW_EffectImage", MyGUI::IntCoord(0,0,0,24), MyGUI::Align::Default); + Widgets::MWSpellEffectPtr effect = button->createWidget( + "MW_EffectImage", MyGUI::IntCoord(0, 0, 0, 24), MyGUI::Align::Default); - effect->setNeedMouseFocus (false); - effect->setSpellEffect (params); + effect->setNeedMouseFocus(false); + effect->setSpellEffect(params); - effect->setSize(effect->getRequestedWidth (), 24); - button->setSize(effect->getRequestedWidth (), 24); + effect->setSize(effect->getRequestedWidth(), 24); + button->setSize(effect->getRequestedWidth(), 24); - size.width = std::max(size.width, effect->getRequestedWidth ()); + size.width = std::max(size.width, effect->getRequestedWidth()); size.height += 24; ++i; } - // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mUsedEffectsView->setVisibleHScroll(false); mUsedEffectsView->setCanvasSize(size); mUsedEffectsView->setVisibleHScroll(true); @@ -758,22 +782,22 @@ namespace MWGui notifyEffectsChanged(); } - void EffectEditorBase::onEffectAdded (ESM::ENAMstruct effect) + void EffectEditorBase::onEffectAdded(ESM::ENAMstruct effect) { mEffects.push_back(effect); - mSelectedEffect=mEffects.size()-1; + mSelectedEffect = mEffects.size() - 1; updateEffectsView(); } - void EffectEditorBase::onEditEffect (MyGUI::Widget *sender) + void EffectEditorBase::onEditEffect(MyGUI::Widget* sender) { int id = *sender->getUserData(); mSelectedEffect = id; - mAddEffectDialog.editEffect (mEffects[id]); - mAddEffectDialog.setVisible (true); + mAddEffectDialog.editEffect(mEffects[id]); + mAddEffectDialog.setVisible(true); } void EffectEditorBase::setConstantEffect(bool constant) @@ -788,7 +812,7 @@ namespace MWGui { if (it->mRange != ESM::RT_Self) { - auto& store = MWBase::Environment::get().getWorld()->getStore(); + auto& store = *MWBase::Environment::get().getESMStore(); auto magicEffect = store.get().find(it->mEffectID); if ((magicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) == 0) { diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index 73352ac23..cde0fcacd 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -1,11 +1,13 @@ #ifndef MWGUI_SPELLCREATION_H #define MWGUI_SPELLCREATION_H -#include -#include +#include + +#include +#include -#include "windowbase.hpp" #include "referenceinterface.hpp" +#include "windowbase.hpp" namespace Gui { @@ -28,11 +30,11 @@ namespace MWGui void setConstantEffect(bool constant); - void setSkill(int skill); + void setSkill(ESM::RefId skill); void setAttribute(int attribute); - void newEffect (const ESM::MagicEffect* effect); - void editEffect (ESM::ENAMstruct effect); + void newEffect(const ESM::MagicEffect* effect); + void editEffect(ESM::ENAMstruct effect); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Effect; EventHandle_Effect eventEffectAdded; @@ -68,15 +70,15 @@ namespace MWGui bool mEditing; protected: - void onRangeButtonClicked (MyGUI::Widget* sender); - void onDeleteButtonClicked (MyGUI::Widget* sender); - void onOkButtonClicked (MyGUI::Widget* sender); - void onCancelButtonClicked (MyGUI::Widget* sender); + void onRangeButtonClicked(MyGUI::Widget* sender); + void onDeleteButtonClicked(MyGUI::Widget* sender); + void onOkButtonClicked(MyGUI::Widget* sender); + void onCancelButtonClicked(MyGUI::Widget* sender); - void onMagnitudeMinChanged (MyGUI::ScrollBar* sender, size_t pos); - void onMagnitudeMaxChanged (MyGUI::ScrollBar* sender, size_t pos); - void onDurationChanged (MyGUI::ScrollBar* sender, size_t pos); - void onAreaChanged (MyGUI::ScrollBar* sender, size_t pos); + void onMagnitudeMinChanged(MyGUI::ScrollBar* sender, size_t pos); + void onMagnitudeMaxChanged(MyGUI::ScrollBar* sender, size_t pos); + void onDurationChanged(MyGUI::ScrollBar* sender, size_t pos); + void onAreaChanged(MyGUI::ScrollBar* sender, size_t pos); void setMagicEffect(const ESM::MagicEffect* effect); void updateBoxes(); @@ -90,7 +92,6 @@ namespace MWGui bool mConstantEffect; }; - class EffectEditorBase { public: @@ -112,8 +113,8 @@ namespace MWGui MyGUI::ScrollView* mUsedEffectsView; EditEffectDialog mAddEffectDialog; - SelectAttributeDialog* mSelectAttributeDialog; - SelectSkillDialog* mSelectSkillDialog; + std::unique_ptr mSelectAttributeDialog; + std::unique_ptr mSelectSkillDialog; int mSelectedEffect; short mSelectedKnownEffectId; @@ -126,7 +127,7 @@ namespace MWGui void onEffectModified(ESM::ENAMstruct effect); void onEffectRemoved(ESM::ENAMstruct effect); - void onAvailableEffectClicked (MyGUI::Widget* sender); + void onAvailableEffectClicked(MyGUI::Widget* sender); void onAttributeOrSkillCancel(); void onSelectAttribute(); @@ -137,9 +138,9 @@ namespace MWGui void updateEffectsView(); void startEditing(); - void setWidgets (Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); + void setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView); - virtual void notifyEffectsChanged () {} + virtual void notifyEffectsChanged() {} private: Type mType; @@ -160,8 +161,8 @@ namespace MWGui protected: void onReferenceUnavailable() override; - void onCancelButtonClicked (MyGUI::Widget* sender); - void onBuyButtonClicked (MyGUI::Widget* sender); + void onCancelButtonClicked(MyGUI::Widget* sender); + void onBuyButtonClicked(MyGUI::Widget* sender); void onAccept(MyGUI::EditBox* sender); void notifyEffectsChanged() override; @@ -174,7 +175,6 @@ namespace MWGui MyGUI::TextBox* mPriceLabel; ESM::Spell mSpell; - }; } diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 405abfbae..3347e9c26 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -1,89 +1,70 @@ #include "spellicons.hpp" -#include #include +#include #include -#include -#include +#include +#include +#include +#include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "tooltips.hpp" - namespace MWGui { - - void EffectSourceVisitor::visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime, float totalTime) + void SpellIcons::updateWidgets(MyGUI::Widget* parent, bool adjustSize) { - MagicEffectInfo newEffectSource; - newEffectSource.mKey = key; - newEffectSource.mMagnitude = static_cast(magnitude); - newEffectSource.mPermanent = mIsPermanent; - newEffectSource.mRemainingTime = remainingTime; - newEffectSource.mSource = sourceName; - newEffectSource.mTotalTime = totalTime; - - mEffectSources[key.mId].push_back(newEffectSource); - } - - - void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) - { - // TODO: Tracking add/remove/expire would be better than force updating every frame - MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - - EffectSourceVisitor visitor; - - // permanent item enchantments & permanent spells - visitor.mIsPermanent = true; - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - store.visitEffectSources(visitor); - stats.getSpells().visitEffectSources(visitor); - - // now add lasting effects - visitor.mIsPermanent = false; - stats.getActiveSpells().visitEffectSources(visitor); - - std::map >& effects = visitor.mEffectSources; - - int w=2; - - for (auto& effectInfoPair : effects) + std::map> effects; + for (const auto& params : stats.getActiveSpells()) { - const int effectId = effectInfoPair.first; - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectId); + for (const auto& effect : params.getEffects()) + { + if (!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) + continue; + MagicEffectInfo newEffectSource; + newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg); + newEffectSource.mMagnitude = static_cast(effect.mMagnitude); + newEffectSource.mPermanent = effect.mDuration == -1.f; + newEffectSource.mRemainingTime = effect.mTimeLeft; + newEffectSource.mSource = params.getDisplayName(); + newEffectSource.mTotalTime = effect.mDuration; + effects[effect.mEffectId].push_back(newEffectSource); + } + } + + int w = 2; + const auto& store = MWBase::Environment::get().getESMStore(); + for (const auto& [effectId, effectInfos] : effects) + { + const ESM::MagicEffect* effect = store->get().find(effectId); float remainingDuration = 0; float totalDuration = 0; std::string sourcesDescription; - static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->mValue.getFloat(); + static const float fadeTime + = store->get().find("fMagicStartIconBlink")->mValue.getFloat(); - std::vector& effectInfos = effectInfoPair.second; bool addNewLine = false; for (const MagicEffectInfo& effectInfo : effectInfos) { if (addNewLine) - sourcesDescription += "\n"; + sourcesDescription += '\n'; // if at least one of the effect sources is permanent, the effect will never wear off if (effectInfo.mPermanent) @@ -100,45 +81,63 @@ namespace MWGui sourcesDescription += effectInfo.mSource; if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) - sourcesDescription += " (" + - MWBase::Environment::get().getWindowManager()->getGameSettingString( - ESM::Skill::sSkillNameIds[effectInfo.mKey.mArg], "") + ")"; + { + const ESM::Skill* skill + = store->get().find(ESM::Skill::indexToRefId(effectInfo.mKey.mArg)); + sourcesDescription += " (" + skill->mName + ')'; + } if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - sourcesDescription += " (" + - MWBase::Environment::get().getWindowManager()->getGameSettingString( - ESM::Attribute::sGmstAttributeIds[effectInfo.mKey.mArg], "") + ")"; - + { + const ESM::Attribute* attribute = store->get().find(effectInfo.mKey.mArg); + sourcesDescription += " (" + attribute->mName + ')'; + } ESM::MagicEffect::MagnitudeDisplayType displayType = effect->getMagnitudeDisplayType(); if (displayType == ESM::MagicEffect::MDT_TimesInt) { - std::string timesInt = MWBase::Environment::get().getWindowManager()->getGameSettingString("sXTimesINT", ""); + std::string_view timesInt + = MWBase::Environment::get().getWindowManager()->getGameSettingString("sXTimesINT", {}); std::stringstream formatter; - formatter << std::fixed << std::setprecision(1) << " " << (effectInfo.mMagnitude / 10.0f) << timesInt; + formatter << std::fixed << std::setprecision(1) << " " << (effectInfo.mMagnitude / 10.0f) + << timesInt; sourcesDescription += formatter.str(); } - else if ( displayType != ESM::MagicEffect::MDT_None ) + else if (displayType != ESM::MagicEffect::MDT_None) { sourcesDescription += ": " + MyGUI::utility::toString(effectInfo.mMagnitude); - if ( displayType == ESM::MagicEffect::MDT_Percentage ) - sourcesDescription += MWBase::Environment::get().getWindowManager()->getGameSettingString("spercent", ""); - else if ( displayType == ESM::MagicEffect::MDT_Feet ) - sourcesDescription += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfeet", ""); - else if ( displayType == ESM::MagicEffect::MDT_Level ) + if (displayType == ESM::MagicEffect::MDT_Percentage) + sourcesDescription + += MWBase::Environment::get().getWindowManager()->getGameSettingString("spercent", {}); + else if (displayType == ESM::MagicEffect::MDT_Feet) { - sourcesDescription += " " + ((effectInfo.mMagnitude > 1) ? - MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevels", "") : - MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevel", "") ); + sourcesDescription += ' '; + sourcesDescription + += MWBase::Environment::get().getWindowManager()->getGameSettingString("sfeet", {}); + } + else if (displayType == ESM::MagicEffect::MDT_Level) + { + sourcesDescription += ' '; + if (effectInfo.mMagnitude > 1) + sourcesDescription + += MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevels", {}); + else + sourcesDescription + += MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevel", {}); } else // ESM::MagicEffect::MDT_Points { - sourcesDescription += " " + ((effectInfo.mMagnitude > 1) ? - MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", "") : - MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") ); + sourcesDescription += ' '; + if (effectInfo.mMagnitude > 1) + sourcesDescription + += MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", {}); + else + sourcesDescription + += MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", {}); } } - if (effectInfo.mRemainingTime > -1 && Settings::Manager::getBool("show effect duration","Game")) - sourcesDescription += MWGui::ToolTips::getDurationString(effectInfo.mRemainingTime, " #{sDuration}"); + if (effectInfo.mRemainingTime > -1 && Settings::game().mShowEffectDuration) + sourcesDescription + += MWGui::ToolTips::getDurationString(effectInfo.mRemainingTime, " #{sDuration}"); addNewLine = true; } @@ -148,13 +147,14 @@ namespace MWGui MyGUI::ImageBox* image; if (mWidgetMap.find(effectId) == mWidgetMap.end()) { - image = parent->createWidget - ("ImageBox", MyGUI::IntCoord(w,2,16,16), MyGUI::Align::Default); + image = parent->createWidget( + "ImageBox", MyGUI::IntCoord(w, 2, 16, 16), MyGUI::Align::Default); mWidgetMap[effectId] = image; - image->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(effect->mIcon)); + image->setImageTexture(Misc::ResourceHelpers::correctIconPath( + effect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); - std::string name = ESM::MagicEffect::effectIdToString (effectId); + const std::string& name = ESM::MagicEffect::indexToGmstString(effectId); ToolTipInfo tooltipInfo; tooltipInfo.caption = "#{" + name + "}"; @@ -168,7 +168,7 @@ namespace MWGui else image = mWidgetMap[effectId]; - image->setPosition(w,2); + image->setPosition(w, 2); image->setVisible(true); w += 16; @@ -177,7 +177,7 @@ namespace MWGui // Fade out if (totalDuration >= fadeTime && fadeTime > 0.f) - image->setAlpha(std::min(remainingDuration/fadeTime, 1.f)); + image->setAlpha(std::min(remainingDuration / fadeTime, 1.f)); } else if (mWidgetMap.find(effectId) != mWidgetMap.end()) { @@ -194,7 +194,7 @@ namespace MWGui s = 0; int diff = parent->getWidth() - s; parent->setSize(s, parent->getHeight()); - parent->setPosition(parent->getLeft()+diff, parent->getTop()); + parent->setPosition(parent->getLeft() + diff, parent->getTop()); } // hide inactive effects diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index b6aa49e69..e53fa9828 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -28,7 +28,8 @@ namespace MWGui , mRemainingTime(0.f) , mTotalTime(0.f) , mPermanent(false) - {} + { + } std::string mSource; // display name for effect source (e.g. potion name) MWMechanics::EffectKey mKey; int mMagnitude; @@ -37,27 +38,12 @@ namespace MWGui bool mPermanent; // the effect is permanent }; - class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor - { - public: - bool mIsPermanent; - - std::map > mEffectSources; - - virtual ~EffectSourceVisitor() {} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override; - }; - class SpellIcons { public: void updateWidgets(MyGUI::Widget* parent, bool adjustSize); private: - std::map mWidgetMap; }; diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 78b9171e5..a995865ca 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -1,17 +1,21 @@ #include "spellmodel.hpp" #include +#include + +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellutil.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/class.hpp" namespace { @@ -20,11 +24,7 @@ namespace { if (left.mType != right.mType) return left.mType < right.mType; - - std::string leftName = Misc::StringUtils::lowerCase(left.mName); - std::string rightName = Misc::StringUtils::lowerCase(right.mName); - - return leftName.compare(rightName) < 0; + return Misc::StringUtils::ciLess(left.mName, right.mName); } } @@ -32,21 +32,20 @@ namespace namespace MWGui { - SpellModel::SpellModel(const MWWorld::Ptr &actor, const std::string& filter) - : mActor(actor), mFilter(filter) + SpellModel::SpellModel(const MWWorld::Ptr& actor, const std::string& filter) + : mActor(actor) + , mFilter(filter) { } - SpellModel::SpellModel(const MWWorld::Ptr &actor) + SpellModel::SpellModel(const MWWorld::Ptr& actor) : mActor(actor) { } - bool SpellModel::matchingEffectExists(std::string filter, const ESM::EffectList &effects) + bool SpellModel::matchingEffectExists(std::string filter, const ESM::EffectList& effects) { - auto wm = MWBase::Environment::get().getWindowManager(); - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); for (const auto& effect : effects.mList) { @@ -54,22 +53,12 @@ namespace MWGui if (effectId != -1) { - const ESM::MagicEffect *magicEffect = - store.get().search(effectId); - std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId); - std::string fullEffectName = wm->getGameSettingString(effectIDStr, ""); + const ESM::MagicEffect* magicEffect = store.get().find(effectId); + const ESM::Attribute* attribute = store.get().search(effect.mAttribute); + const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(effect.mSkill)); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effect.mSkill != -1) - { - fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effect.mSkill], ""); - } - - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effect.mAttribute != -1) - { - fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], ""); - } - - std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName); + std::string fullEffectName = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); + std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName); if (convert.find(filter) != std::string::npos) { return true; @@ -87,21 +76,18 @@ namespace MWGui MWMechanics::CreatureStats& stats = mActor.getClass().getCreatureStats(mActor); const MWMechanics::Spells& spells = stats.getSpells(); - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); - std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter); + std::string filter = Utf8Stream::lowerCaseUtf8(mFilter); - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) continue; - std::string name = Misc::StringUtils::lowerCaseUtf8(spell->mName); - - if (name.find(filter) == std::string::npos - && !matchingEffectExists(filter, spell->mEffects)) + std::string name = Utf8Stream::lowerCaseUtf8(spell->mName); + + if (name.find(filter) == std::string::npos && !matchingEffectExists(filter, spell->mEffects)) continue; Spell newSpell; @@ -109,7 +95,7 @@ namespace MWGui if (spell->mData.mType == ESM::Spell::ST_Spell) { newSpell.mType = Spell::Type_Spell; - std::string cost = std::to_string(spell->mData.mCost); + std::string cost = std::to_string(MWMechanics::calcSpellCost(*spell)); std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor))); newSpell.mCostColumn = cost + "/" + chance; } @@ -127,23 +113,24 @@ namespace MWGui for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { MWWorld::Ptr item = *it; - const std::string enchantId = item.getClass().getEnchantment(item); + const ESM::RefId& enchantId = item.getClass().getEnchantment(item); if (enchantId.empty()) continue; const ESM::Enchantment* enchant = esmStore.get().search(enchantId); if (!enchant) { - Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantId << "' on item " << item.getCellRef().getRefId(); + Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantId << "' on item " + << item.getCellRef().getRefId(); continue; } - if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce) + if (enchant->mData.mType != ESM::Enchantment::WhenUsed + && enchant->mData.mType != ESM::Enchantment::CastOnce) continue; - std::string name = Misc::StringUtils::lowerCaseUtf8(item.getClass().getName(item)); + std::string name = Utf8Stream::lowerCaseUtf8(item.getClass().getName(item)); - if (name.find(filter) == std::string::npos - && !matchingEffectExists(filter, enchant->mEffects)) + if (name.find(filter) == std::string::npos && !matchingEffectExists(filter, enchant->mEffects)) continue; Spell newSpell; @@ -163,15 +150,15 @@ namespace MWGui else { if (!item.getClass().getEquipmentSlots(item).first.empty() - && item.getClass().canBeEquipped(item, mActor).first == 0) + && item.getClass().canBeEquipped(item, mActor).first == 0) continue; - int castCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast(enchant->mData.mCost), mActor); + int castCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchant, mActor); std::string cost = std::to_string(castCost); int currentCharge = int(item.getCellRef().getEnchantmentCharge()); - if (currentCharge == -1) - currentCharge = enchant->mData.mCharge; + if (currentCharge == -1) + currentCharge = MWMechanics::getEnchantmentCharge(*enchant); std::string charge = std::to_string(currentCharge); newSpell.mCostColumn = cost + "/" + charge; @@ -191,9 +178,10 @@ namespace MWGui SpellModel::ModelIndex SpellModel::getSelectedIndex() const { ModelIndex selected = -1; - for (SpellModel::ModelIndex i = 0; i +#include namespace MWGui { @@ -19,7 +19,7 @@ namespace MWGui Type mType; std::string mName; std::string mCostColumn; // Cost/chance or Cost/charge - std::string mId; // Item ID or spell ID + ESM::RefId mId; // Item ID or spell ID MWWorld::Ptr mItem; // Only for Type_EnchantedItem int mCount; // Only for Type_EnchantedItem bool mSelected; // Is this the currently selected spell/item (only one can be selected at a time) @@ -45,7 +45,7 @@ namespace MWGui void update(); - Spell getItem (ModelIndex index) const; + Spell getItem(ModelIndex index) const; ///< throws for invalid index size_t getItemCount() const; @@ -59,7 +59,7 @@ namespace MWGui std::string mFilter; - bool matchingEffectExists(std::string filter, const ESM::EffectList &effects); + bool matchingEffectExists(std::string filter, const ESM::EffectList& effects); }; } diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index a8b7cb639..b60ec2444 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -1,12 +1,15 @@ #include "spellview.hpp" #include -#include -#include #include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" -#include #include +#include #include "tooltips.hpp" @@ -15,12 +18,12 @@ namespace MWGui const char* SpellView::sSpellModelIndex = "SpellModelIndex"; - SpellView::LineInfo::LineInfo(MyGUI::Widget* leftWidget, MyGUI::Widget* rightWidget, SpellModel::ModelIndex spellIndex) + SpellView::LineInfo::LineInfo( + MyGUI::Widget* leftWidget, MyGUI::Widget* rightWidget, SpellModel::ModelIndex spellIndex) : mLeftWidget(leftWidget) , mRightWidget(rightWidget) , mSpellIndex(spellIndex) { - } SpellView::SpellView() @@ -46,7 +49,7 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } - void SpellView::setModel(SpellModel *model) + void SpellView::setModel(SpellModel* model) { mModel.reset(model); update(); @@ -84,20 +87,20 @@ namespace MWGui int curType = -1; - const int spellHeight = 18; + const int spellHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; mLines.clear(); while (mScrollView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); - for (SpellModel::ModelIndex i = 0; igetItemCount()); ++i) + for (SpellModel::ModelIndex i = 0; i < int(mModel->getItemCount()); ++i) { const Spell& spell = mModel->getItem(i); if (curType != spell.mType) { if (spell.mType == Spell::Type_Power) - addGroup("#{sPowers}", ""); + addGroup("#{sPowers}", {}); else if (spell.mType == Spell::Type_Spell) addGroup("#{sSpells}", mShowCostColumn ? "#{sCostChance}" : ""); else @@ -108,8 +111,8 @@ namespace MWGui const std::string skin = spell.mActive ? "SandTextButton" : "SpellTextUnequipped"; const std::string captionSuffix = MWGui::ToolTips::getCountString(spell.mCount); - Gui::SharedStateButton* t = mScrollView->createWidget(skin, - MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + Gui::SharedStateButton* t = mScrollView->createWidget( + skin, MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setNeedKeyFocus(true); t->setCaption(spell.mName + captionSuffix); t->setTextAlign(MyGUI::Align::Left); @@ -117,8 +120,8 @@ namespace MWGui if (!spell.mCostColumn.empty() && mShowCostColumn) { - Gui::SharedStateButton* costChance = mScrollView->createWidget(skin, - MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + Gui::SharedStateButton* costChance = mScrollView->createWidget( + skin, MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); costChance->setCaption(spell.mCostColumn); costChance->setTextAlign(MyGUI::Align::Right); adjustSpellWidget(spell, i, costChance); @@ -159,7 +162,7 @@ namespace MWGui // match model against line // if don't match, then major change has happened, so do a full update - if (mModel->getItemCount() <= static_cast(spellIndex)) + if (mModel->getItemCount() <= static_cast(spellIndex)) { fullUpdateRequired = true; break; @@ -187,14 +190,12 @@ namespace MWGui // special case, look for spells added to model that are beyond last updatable item SpellModel::ModelIndex topSpellIndex = mModel->getItemCount() - 1; - if (fullUpdateRequired || - ((0 <= topSpellIndex) && (maxSpellIndexFound < topSpellIndex))) + if (fullUpdateRequired || ((0 <= topSpellIndex) && (maxSpellIndexFound < topSpellIndex))) { update(); } } - void SpellView::layoutWidgets() { int height = 0; @@ -222,35 +223,33 @@ namespace MWGui height += lineHeight; } - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mScrollView->setVisibleVScroll(false); mScrollView->setCanvasSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), height)); mScrollView->setVisibleVScroll(true); } - void SpellView::addGroup(const std::string &label, const std::string& label2) + void SpellView::addGroup(const std::string& label, const std::string& label2) { if (mScrollView->getChildCount() > 0) { - MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", - MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 18), - MyGUI::Align::Left | MyGUI::Align::Top); + MyGUI::ImageBox* separator = mScrollView->createWidget( + "MW_HLine", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 18), MyGUI::Align::Left | MyGUI::Align::Top); separator->setNeedMouseFocus(false); mLines.emplace_back(separator, (MyGUI::Widget*)nullptr, NoSpellIndex); } MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", - MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), - MyGUI::Align::Left | MyGUI::Align::Top); + MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget->setCaptionWithReplacing(label); groupWidget->setTextAlign(MyGUI::Align::Left); groupWidget->setNeedMouseFocus(false); - if (label2 != "") + if (!label2.empty()) { MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", - MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), - MyGUI::Align::Left | MyGUI::Align::Top); + MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget2->setCaptionWithReplacing(label2); groupWidget2->setTextAlign(MyGUI::Align::Right); groupWidget2->setNeedMouseFocus(false); @@ -261,8 +260,7 @@ namespace MWGui mLines.emplace_back(groupWidget, (MyGUI::Widget*)nullptr, NoSpellIndex); } - - void SpellView::setSize(const MyGUI::IntSize &_value) + void SpellView::setSize(const MyGUI::IntSize& _value) { bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setSize(_value); @@ -270,7 +268,7 @@ namespace MWGui layoutWidgets(); } - void SpellView::setCoord(const MyGUI::IntCoord &_value) + void SpellView::setCoord(const MyGUI::IntCoord& _value) { bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setCoord(_value); @@ -278,7 +276,7 @@ namespace MWGui layoutWidgets(); } - void SpellView::adjustSpellWidget(const Spell &spell, SpellModel::ModelIndex index, MyGUI::Widget *widget) + void SpellView::adjustSpellWidget(const Spell& spell, SpellModel::ModelIndex index, MyGUI::Widget* widget) { if (spell.mType == Spell::Type_EnchantedItem) { @@ -288,7 +286,7 @@ namespace MWGui else { widget->setUserString("ToolTipType", "Spell"); - widget->setUserString("Spell", spell.mId); + widget->setUserString("Spell", spell.mId.serialize()); } widget->setUserString(sSpellModelIndex, MyGUI::utility::toString(index)); @@ -309,10 +307,11 @@ namespace MWGui void SpellView::onMouseWheelMoved(MyGUI::Widget* _sender, int _rel) { - if (mScrollView->getViewOffset().top + _rel*0.3f > 0) + if (mScrollView->getViewOffset().top + _rel * 0.3f > 0) mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); else - mScrollView->setViewOffset(MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + _rel*0.3f))); + mScrollView->setViewOffset( + MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + _rel * 0.3f))); } void SpellView::resetScrollbars() diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp index 6b3effc45..8805aaf2a 100644 --- a/apps/openmw/mwgui/spellview.hpp +++ b/apps/openmw/mwgui/spellview.hpp @@ -26,7 +26,7 @@ namespace MWGui SpellView(); /// Register needed components with MyGUI's factory manager - static void registerComponents (); + static void registerComponents(); /// Should the cost/chance column be shown? void setShowCostColumn(bool show); @@ -34,7 +34,7 @@ namespace MWGui void setHighlightSelected(bool highlight); /// Takes ownership of \a model - void setModel (SpellModel* model); + void setModel(SpellModel* model); SpellModel* getModel(); @@ -75,9 +75,12 @@ namespace MWGui }; /// magic number indicating LineInfo does not correspond to an item in mModel - enum { NoSpellIndex = -1 }; + enum + { + NoSpellIndex = -1 + }; - std::vector< LineInfo > mLines; + std::vector mLines; bool mShowCostColumn; bool mHighlightSelected; diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index b13737fa1..242c1e54f 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -3,9 +3,12 @@ #include #include -#include -#include +#include +#include +#include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -18,22 +21,25 @@ */ #include "../mwbase/windowmanager.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" -#include "../mwmechanics/spellutil.hpp" -#include "../mwmechanics/spells.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spells.hpp" +#include "../mwmechanics/spellutil.hpp" -#include "spellicons.hpp" #include "confirmationdialog.hpp" +#include "spellicons.hpp" #include "spellview.hpp" namespace MWGui @@ -45,7 +51,7 @@ namespace MWGui , mSpellView(nullptr) , mUpdateTimer(0.0f) { - mSpellIcons = new SpellIcons(); + mSpellIcons = std::make_unique(); MyGUI::Widget* deleteButton; getWidget(deleteButton, "DeleteSpellButton"); @@ -65,14 +71,9 @@ namespace MWGui mFilterEdit->setSize(filterWidth, mFilterEdit->getSize().height); } - SpellWindow::~SpellWindow() - { - delete mSpellIcons; - } - void SpellWindow::onPinToggled() { - Settings::Manager::setBool("spells pin", "Windows", mPinned); + Settings::windows().mSpellsPin.set(mPinned); MWBase::Environment::get().getWindowManager()->setSpellVisibility(!mPinned); } @@ -95,7 +96,7 @@ namespace MWGui updateSpells(); } - void SpellWindow::onFrame(float dt) + void SpellWindow::onFrame(float dt) { NoDrop::onFrame(dt); mUpdateTimer += dt; @@ -135,8 +136,7 @@ namespace MWGui throw std::runtime_error("can't find selected item"); // equip, if it can be equipped and is not already equipped - if (!alreadyEquipped - && !item.getClass().getEquipmentSlots(item).first.empty()) + if (!alreadyEquipped && !item.getClass().getEquipmentSlots(item).first.empty()) { MWBase::Environment::get().getWindowManager()->useItem(item); // make sure that item was successfully equipped @@ -151,34 +151,34 @@ namespace MWGui updateSpells(); } - void SpellWindow::askDeleteSpell(const std::string &spellId) + void SpellWindow::askDeleteSpell(const ESM::RefId& spellId) { // delete spell, if allowed - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); MWWorld::Ptr player = MWMechanics::getPlayer(); - std::string raceId = player.get()->mBase->mRace; - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceId); + const ESM::RefId& raceId = player.get()->mBase->mRace; + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(raceId); // can't delete racial spells, birthsign spells or powers bool isInherent = race->mPowers.exists(spell->mId) || spell->mData.mType == ESM::Spell::ST_Power; - const std::string& signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); + const ESM::RefId& signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); if (!isInherent && !signId.empty()) { - const ESM::BirthSign* sign = MWBase::Environment::get().getWorld()->getStore().get().find(signId); + const ESM::BirthSign* sign = MWBase::Environment::get().getESMStore()->get().find(signId); isInherent = sign->mPowers.exists(spell->mId); } + const auto windowManager = MWBase::Environment::get().getWindowManager(); if (isInherent) { - MWBase::Environment::get().getWindowManager()->messageBox("#{sDeleteSpellError}"); + windowManager->messageBox("#{sDeleteSpellError}"); } else { // ask for confirmation mSpellToDelete = spellId; - ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - std::string question = MWBase::Environment::get().getWindowManager()->getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); + ConfirmationDialog* dialog = windowManager->getConfirmationDialog(); + std::string question{ windowManager->getGameSettingString("sQuestionDeleteSpell", "Delete %s?") }; question = Misc::StringUtils::format(question, spell->mName); dialog->askForConfirmation(question); dialog->eventOkClicked.clear(); @@ -203,12 +203,12 @@ namespace MWGui } } - void SpellWindow::onFilterChanged(MyGUI::EditBox *sender) + void SpellWindow::onFilterChanged(MyGUI::EditBox* sender) { mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), sender->getCaption())); } - void SpellWindow::onDeleteClicked(MyGUI::Widget *widget) + void SpellWindow::onDeleteClicked(MyGUI::Widget* widget) { SpellModel::ModelIndex selected = mSpellView->getModel()->getSelectedIndex(); if (selected < 0) @@ -219,12 +219,13 @@ namespace MWGui askDeleteSpell(spell.mId); } - void SpellWindow::onSpellSelected(const std::string& spellId) + void SpellWindow::onSpellSelected(const ESM::RefId& spellId) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); store.setSelectedEnchantItem(store.end()); - MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); + MWBase::Environment::get().getWindowManager()->setSelectedSpell( + spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); updateSpells(); @@ -271,11 +272,11 @@ namespace MWGui return; bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); - const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); + const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) return; - mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), "")); + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); SpellModel::ModelIndex selected = mSpellView->getModel()->getSelectedIndex(); if (selected < 0) diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 786a7d877..69519a47e 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -1,20 +1,20 @@ #ifndef MWGUI_SPELLWINDOW_H #define MWGUI_SPELLWINDOW_H -#include "windowpinnablebase.hpp" +#include +#include "spellicons.hpp" #include "spellmodel.hpp" +#include "windowpinnablebase.hpp" namespace MWGui { - class SpellIcons; class SpellView; class SpellWindow : public WindowPinnableBase, public NoDrop { public: SpellWindow(DragAndDrop* drag); - virtual ~SpellWindow(); void updateSpells(); @@ -26,22 +26,22 @@ namespace MWGui protected: MyGUI::Widget* mEffectBox; - std::string mSpellToDelete; + ESM::RefId mSpellToDelete; void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped); - void onSpellSelected(const std::string& spellId); + void onSpellSelected(const ESM::RefId& spellId); void onModelIndexSelected(SpellModel::ModelIndex index); - void onFilterChanged(MyGUI::EditBox *sender); - void onDeleteClicked(MyGUI::Widget *widget); + void onFilterChanged(MyGUI::EditBox* sender); + void onDeleteClicked(MyGUI::Widget* widget); void onDeleteSpellAccept(); - void askDeleteSpell(const std::string& spellId); + void askDeleteSpell(const ESM::RefId& spellId); void onPinToggled() override; void onTitleDoubleClicked() override; void onOpen() override; SpellView* mSpellView; - SpellIcons* mSpellIcons; + std::unique_ptr mSpellIcons; MyGUI::EditBox* mFilterEdit; private: diff --git a/apps/openmw/mwgui/statswatcher.cpp b/apps/openmw/mwgui/statswatcher.cpp index ccb77de8f..b18073f7a 100644 --- a/apps/openmw/mwgui/statswatcher.cpp +++ b/apps/openmw/mwgui/statswatcher.cpp @@ -1,22 +1,27 @@ #include "statswatcher.hpp" +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/spellutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" + +#include namespace MWGui { // mWatchedTimeToStartDrowning = -1 for correct drowning state check, // if stats.getTimeToStartDrowning() == 0 already on game start StatsWatcher::StatsWatcher() - : mWatchedLevel(-1), mWatchedTimeToStartDrowning(-1), mWatchedStatsEmpty(true) + : mWatchedLevel(-1) + , mWatchedTimeToStartDrowning(-1) + , mWatchedStatsEmpty(true) { } @@ -30,49 +35,48 @@ namespace MWGui if (mWatched.isEmpty()) return; - MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); - const MWMechanics::NpcStats &stats = mWatched.getClass().getNpcStats(mWatched); - for (int i = 0;i < ESM::Attribute::Length;++i) + const auto& store = MWBase::Environment::get().getESMStore(); + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); + const MWMechanics::NpcStats& stats = mWatched.getClass().getNpcStats(mWatched); + for (const ESM::Attribute& attribute : store->get()) { - if (stats.getAttribute(i) != mWatchedAttributes[i] || mWatchedStatsEmpty) + const auto& value = stats.getAttribute(attribute.mId); + if (value != mWatchedAttributes[attribute.mId] || mWatchedStatsEmpty) { - std::stringstream attrname; - attrname << "AttribVal"<<(i+1); - - mWatchedAttributes[i] = stats.getAttribute(i); - setValue(attrname.str(), stats.getAttribute(i)); + mWatchedAttributes[attribute.mId] = value; + setValue(attribute.mId, value); } } if (stats.getHealth() != mWatchedHealth || mWatchedStatsEmpty) { - static const std::string hbar("HBar"); mWatchedHealth = stats.getHealth(); - setValue(hbar, stats.getHealth()); + setValue("HBar", stats.getHealth()); } if (stats.getMagicka() != mWatchedMagicka || mWatchedStatsEmpty) { - static const std::string mbar("MBar"); mWatchedMagicka = stats.getMagicka(); - setValue(mbar, stats.getMagicka()); + setValue("MBar", stats.getMagicka()); } if (stats.getFatigue() != mWatchedFatigue || mWatchedStatsEmpty) { - static const std::string fbar("FBar"); mWatchedFatigue = stats.getFatigue(); - setValue(fbar, stats.getFatigue()); + setValue("FBar", stats.getFatigue()); } float timeToDrown = stats.getTimeToStartDrowning(); if (timeToDrown != mWatchedTimeToStartDrowning) { - static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get() - .find("fHoldBreathTime")->mValue.getFloat(); + static const float fHoldBreathTime = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fHoldBreathTime") + ->mValue.getFloat(); mWatchedTimeToStartDrowning = timeToDrown; - if(timeToDrown >= fHoldBreathTime || timeToDrown == -1.0) // -1.0 is a special value during initialization + if (timeToDrown >= fHoldBreathTime || timeToDrown == -1.0) // -1.0 is a special value during initialization winMgr->setDrowningBarVisibility(false); else { @@ -81,13 +85,13 @@ namespace MWGui } } - //Loop over ESM::Skill::SkillEnum - for (int i = 0; i < ESM::Skill::Length; ++i) + for (const ESM::Skill& skill : store->get()) { - if(stats.getSkill(i) != mWatchedSkills[i] || mWatchedStatsEmpty) + const auto& value = stats.getSkill(skill.mId); + if (value != mWatchedSkills[skill.mId] || mWatchedStatsEmpty) { - mWatchedSkills[i] = stats.getSkill(i); - setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i)); + mWatchedSkills[skill.mId] = value; + setValue(skill.mId, value); } } @@ -99,7 +103,7 @@ namespace MWGui if (mWatched.getClass().isNpc()) { - const ESM::NPC *watchedRecord = mWatched.get()->mBase; + const ESM::NPC* watchedRecord = mWatched.get()->mBase; if (watchedRecord->mName != mWatchedName || mWatchedStatsEmpty) { @@ -110,25 +114,24 @@ namespace MWGui if (watchedRecord->mRace != mWatchedRace || mWatchedStatsEmpty) { mWatchedRace = watchedRecord->mRace; - const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore() - .get().find(watchedRecord->mRace); + const ESM::Race* race = store->get().find(watchedRecord->mRace); setValue("race", race->mName); } if (watchedRecord->mClass != mWatchedClass || mWatchedStatsEmpty) { mWatchedClass = watchedRecord->mClass; - const ESM::Class *cls = MWBase::Environment::get().getWorld()->getStore() - .get().find(watchedRecord->mClass); + const ESM::Class* cls = store->get().find(watchedRecord->mClass); setValue("class", cls->mName); - MWBase::WindowManager::SkillList majorSkills (5); - MWBase::WindowManager::SkillList minorSkills (5); + size_t size = cls->mData.mSkills.size(); + std::vector majorSkills(size); + std::vector minorSkills(size); - for (int i=0; i<5; ++i) + for (size_t i = 0; i < size; ++i) { - minorSkills[i] = cls->mData.mSkills[i][0]; - majorSkills[i] = cls->mData.mSkills[i][1]; + minorSkills[i] = ESM::Skill::indexToRefId(cls->mData.mSkills[i][0]); + majorSkills[i] = ESM::Skill::indexToRefId(cls->mData.mSkills[i][1]); } configureSkills(majorSkills, minorSkills); @@ -148,39 +151,37 @@ namespace MWGui mListeners.erase(listener); } - void StatsWatcher::setValue(const std::string& id, const MWMechanics::AttributeValue& value) + void StatsWatcher::setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) { for (StatsListener* listener : mListeners) listener->setValue(id, value); } - void StatsWatcher::setValue(ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) - { - /// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we - /// allow custom skills. - for (StatsListener* listener : mListeners) - listener->setValue(parSkill, value); - } - - void StatsWatcher::setValue(const std::string& id, const MWMechanics::DynamicStat& value) + void StatsWatcher::setValue(ESM::RefId id, const MWMechanics::SkillValue& value) { for (StatsListener* listener : mListeners) listener->setValue(id, value); } - void StatsWatcher::setValue(const std::string& id, const std::string& value) + void StatsWatcher::setValue(std::string_view id, const MWMechanics::DynamicStat& value) { for (StatsListener* listener : mListeners) listener->setValue(id, value); } - void StatsWatcher::setValue(const std::string& id, int value) + void StatsWatcher::setValue(std::string_view id, const std::string& value) { for (StatsListener* listener : mListeners) listener->setValue(id, value); } - void StatsWatcher::configureSkills(const std::vector& major, const std::vector& minor) + void StatsWatcher::setValue(std::string_view id, int value) + { + for (StatsListener* listener : mListeners) + listener->setValue(id, value); + } + + void StatsWatcher::configureSkills(const std::vector& major, const std::vector& minor) { for (StatsListener* listener : mListeners) listener->configureSkills(major, minor); diff --git a/apps/openmw/mwgui/statswatcher.hpp b/apps/openmw/mwgui/statswatcher.hpp index 353779d87..d4a9e5424 100644 --- a/apps/openmw/mwgui/statswatcher.hpp +++ b/apps/openmw/mwgui/statswatcher.hpp @@ -1,10 +1,11 @@ #ifndef MWGUI_STATSWATCHER_H #define MWGUI_STATSWATCHER_H +#include #include #include -#include +#include #include "../mwmechanics/stat.hpp" @@ -15,29 +16,31 @@ namespace MWGui class StatsListener { public: + virtual ~StatsListener() = default; + /// Set value for the given ID. - virtual void setValue(const std::string& id, const MWMechanics::AttributeValue& value) {} - virtual void setValue(const std::string& id, const MWMechanics::DynamicStat& value) {} - virtual void setValue(const std::string& id, const std::string& value) {} - virtual void setValue(const std::string& id, int value) {} - virtual void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) {} - virtual void configureSkills(const std::vector& major, const std::vector& minor) {} + virtual void setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) {} + virtual void setValue(std::string_view id, const MWMechanics::DynamicStat& value) {} + virtual void setValue(std::string_view, const std::string& value) {} + virtual void setValue(std::string_view, int value) {} + virtual void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) {} + virtual void configureSkills(const std::vector& major, const std::vector& minor) {} }; class StatsWatcher { MWWorld::Ptr mWatched; - MWMechanics::AttributeValue mWatchedAttributes[ESM::Attribute::Length]; - MWMechanics::SkillValue mWatchedSkills[ESM::Skill::Length]; + std::map mWatchedAttributes; + std::map mWatchedSkills; MWMechanics::DynamicStat mWatchedHealth; MWMechanics::DynamicStat mWatchedMagicka; MWMechanics::DynamicStat mWatchedFatigue; std::string mWatchedName; - std::string mWatchedRace; - std::string mWatchedClass; + ESM::RefId mWatchedRace; + ESM::RefId mWatchedClass; int mWatchedLevel; @@ -47,12 +50,12 @@ namespace MWGui std::set mListeners; - void setValue(const std::string& id, const MWMechanics::AttributeValue& value); - void setValue(const std::string& id, const MWMechanics::DynamicStat& value); - void setValue(const std::string& id, const std::string& value); - void setValue(const std::string& id, int value); - void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value); - void configureSkills(const std::vector& major, const std::vector& minor); + void setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value); + void setValue(std::string_view id, const MWMechanics::DynamicStat& value); + void setValue(std::string_view id, const std::string& value); + void setValue(std::string_view id, int value); + void setValue(ESM::RefId id, const MWMechanics::SkillValue& value); + void configureSkills(const std::vector& major, const std::vector& minor); public: StatsWatcher(); diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 8e6f95129..76c1b065d 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -1,76 +1,79 @@ #include "statswindow.hpp" -#include -#include -#include +#include +#include #include #include #include -#include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include + +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" #include "tooltips.hpp" +#include "ustring.hpp" namespace MWGui { - StatsWindow::StatsWindow (DragAndDrop* drag) - : WindowPinnableBase("openmw_stats_window.layout") - , NoDrop(drag, mMainWidget) - , mSkillView(nullptr) - , mMajorSkills() - , mMinorSkills() - , mMiscSkills() - , mSkillValues() - , mSkillWidgetMap() - , mFactionWidgetMap() - , mFactions() - , mBirthSignId() - , mReputation(0) - , mBounty(0) - , mSkillWidgets() - , mChanged(true) - , mMinFullWidth(mMainWidget->getSize().width) + StatsWindow::StatsWindow(DragAndDrop* drag) + : WindowPinnableBase("openmw_stats_window.layout") + , NoDrop(drag, mMainWidget) + , mSkillView(nullptr) + , mReputation(0) + , mBounty(0) + , mChanged(true) + , mMinFullWidth(mMainWidget->getSize().width) { - const char *names[][2] = + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + MyGUI::Widget* attributeView = getWidget("AttributeView"); + MyGUI::IntCoord coord{ 0, 0, 204, 18 }; + const MyGUI::Align alignment = MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch; + for (const ESM::Attribute& attribute : store.get()) { - { "Attrib1", "sAttributeStrength" }, - { "Attrib2", "sAttributeIntelligence" }, - { "Attrib3", "sAttributeWillpower" }, - { "Attrib4", "sAttributeAgility" }, - { "Attrib5", "sAttributeSpeed" }, - { "Attrib6", "sAttributeEndurance" }, - { "Attrib7", "sAttributePersonality" }, - { "Attrib8", "sAttributeLuck" }, - { 0, 0 } - }; - - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - for (int i=0; names[i][0]; ++i) - { - setText (names[i][0], store.get().find (names[i][1])->mValue.getString()); + auto* box = attributeView->createWidget({}, coord, alignment); + box->setUserString("ToolTipType", "Layout"); + box->setUserString("ToolTipLayout", "AttributeToolTip"); + box->setUserString("Caption_AttributeName", attribute.mName); + box->setUserString("Caption_AttributeDescription", attribute.mDescription); + box->setUserString("ImageTexture_AttributeImage", attribute.mIcon); + coord.top += coord.height; + auto* name = box->createWidget("SandText", { 0, 0, 160, 18 }, alignment); + name->setNeedMouseFocus(false); + name->setCaption(attribute.mName); + auto* value = box->createWidget( + "SandTextRight", { 160, 0, 44, 18 }, MyGUI::Align::Right | MyGUI::Align::Top); + value->setNeedMouseFocus(false); + mAttributeWidgets.emplace(attribute.mId, value); } getWidget(mSkillView, "SkillView"); getWidget(mLeftPane, "LeftPane"); getWidget(mRightPane, "RightPane"); - for (int i = 0; i < ESM::Skill::Length; ++i) + for (const ESM::Skill& skill : store.get()) { - mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); - mSkillWidgetMap.insert(std::make_pair(i, std::make_pair((MyGUI::TextBox*)nullptr, (MyGUI::TextBox*)nullptr))); + mSkillValues.emplace(skill.mId, MWMechanics::SkillValue()); + mSkillWidgetMap.emplace(skill.mId, std::make_pair(nullptr, nullptr)); } MyGUI::Window* t = mMainWidget->castType(); @@ -81,10 +84,11 @@ namespace MWGui void StatsWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (mSkillView->getViewOffset().top + _rel*0.3 > 0) + if (mSkillView->getViewOffset().top + _rel * 0.3 > 0) mSkillView->setViewOffset(MyGUI::IntPoint(0, 0)); else - mSkillView->setViewOffset(MyGUI::IntPoint(0, static_cast(mSkillView->getViewOffset().top + _rel*0.3))); + mSkillView->setViewOffset( + MyGUI::IntPoint(0, static_cast(mSkillView->getViewOffset().top + _rel * 0.3))); } void StatsWindow::onWindowResize(MyGUI::Window* window) @@ -92,7 +96,8 @@ namespace MWGui int windowWidth = window->getSize().width; int windowHeight = window->getSize().height; - //initial values defined in openmw_stats_window.layout, if custom options are not present in .layout, a default is loaded + // initial values defined in openmw_stats_window.layout, if custom options are not present in .layout, a default + // is loaded float leftPaneRatio = 0.44f; if (mLeftPane->isUserString("LeftPaneRatio")) leftPaneRatio = MyGUI::utility::parseFloat(mLeftPane->getUserString("LeftPaneRatio")); @@ -105,28 +110,30 @@ namespace MWGui int minLeftWidth = static_cast(mMinFullWidth * leftPaneRatio); int minLeftOffsetWidth = minLeftWidth + leftOffsetWidth; - //if there's no space for right pane + // if there's no space for right pane mRightPane->setVisible(windowWidth >= minLeftOffsetWidth); if (!mRightPane->getVisible()) { mLeftPane->setCoord(MyGUI::IntCoord(0, 0, windowWidth - leftOffsetWidth, windowHeight)); } - //if there's some space for right pane + // if there's some space for right pane else if (windowWidth < mMinFullWidth) { mLeftPane->setCoord(MyGUI::IntCoord(0, 0, minLeftWidth, windowHeight)); mRightPane->setCoord(MyGUI::IntCoord(minLeftWidth, 0, windowWidth - minLeftWidth, windowHeight)); } - //if there's enough space for both panes + // if there's enough space for both panes else { - mLeftPane->setCoord(MyGUI::IntCoord(0, 0, static_cast(leftPaneRatio*windowWidth), windowHeight)); - mRightPane->setCoord(MyGUI::IntCoord(static_cast(leftPaneRatio*windowWidth), 0, static_cast(rightPaneRatio*windowWidth), windowHeight)); + mLeftPane->setCoord(MyGUI::IntCoord(0, 0, static_cast(leftPaneRatio * windowWidth), windowHeight)); + mRightPane->setCoord(MyGUI::IntCoord(static_cast(leftPaneRatio * windowWidth), 0, + static_cast(rightPaneRatio * windowWidth), windowHeight)); } - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mSkillView->setVisibleVScroll(false); - mSkillView->setCanvasSize (mSkillView->getWidth(), mSkillView->getCanvasSize().height); + mSkillView->setCanvasSize(mSkillView->getWidth(), mSkillView->getCanvasSize().height); mSkillView->setVisibleVScroll(true); } @@ -148,48 +155,36 @@ namespace MWGui mMainWidget->castType()->setCaption(playerName); } - void StatsWindow::setValue (const std::string& id, const MWMechanics::AttributeValue& value) + void StatsWindow::setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) { - static const char *ids[] = + auto it = mAttributeWidgets.find(id); + if (it != mAttributeWidgets.end()) { - "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5", - "AttribVal6", "AttribVal7", "AttribVal8", - 0 - }; - - for (int i=0; ids[i]; ++i) - if (ids[i]==id) - { - setText (id, std::to_string(static_cast(value.getModified()))); - - MyGUI::TextBox* box; - getWidget(box, id); - - if (value.getModified()>value.getBase()) - box->_setWidgetState("increased"); - else if (value.getModified()_setWidgetState("decreased"); - else - box->_setWidgetState("normal"); - - break; - } + MyGUI::TextBox* box = it->second; + box->setCaption(std::to_string(static_cast(value.getModified()))); + if (value.getModified() > value.getBase()) + box->_setWidgetState("increased"); + else if (value.getModified() < value.getBase()) + box->_setWidgetState("decreased"); + else + box->_setWidgetState("normal"); + } } - void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) + void StatsWindow::setValue(std::string_view id, const MWMechanics::DynamicStat& value) { int current = static_cast(value.getCurrent()); - int modified = static_cast(value.getModified()); + int modified = static_cast(value.getModified(false)); // Fatigue can be negative if (id != "FBar") current = std::max(0, current); - setBar (id, id + "T", current, modified); + setBar(std::string(id), std::string(id) + "T", current, modified); // health, magicka, fatigue tooltip MyGUI::Widget* w; - std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); + std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); if (id == "HBar") { getWidget(w, "Health"); @@ -207,19 +202,19 @@ namespace MWGui } } - void StatsWindow::setValue (const std::string& id, const std::string& value) + void StatsWindow::setValue(std::string_view id, const std::string& value) { - if (id=="name") - setPlayerName (value); - else if (id=="race") - setText ("RaceText", value); - else if (id=="class") - setText ("ClassText", value); + if (id == "name") + setPlayerName(value); + else if (id == "race") + setText("RaceText", value); + else if (id == "class") + setText("ClassText", value); } - void StatsWindow::setValue (const std::string& id, int value) + void StatsWindow::setValue(std::string_view id, int value) { - if (id=="level") + if (id == "level") { std::ostringstream text; text << value; @@ -227,14 +222,13 @@ namespace MWGui } } - void setSkillProgress(MyGUI::Widget* w, float progress, int skillId) + void setSkillProgress(MyGUI::Widget* w, float progress, ESM::RefId skillId) { MWWorld::Ptr player = MWMechanics::getPlayer(); - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); - float progressRequirement = player.getClass().getNpcStats(player).getSkillProgressRequirement(skillId, - *esmStore.get().find(player.get()->mBase->mClass)); + float progressRequirement = player.getClass().getNpcStats(player).getSkillProgressRequirement( + skillId, *esmStore.get().find(player.get()->mBase->mClass)); // This is how vanilla MW displays the progress bar (I think). Note it's slightly inaccurate, // due to the int casting in the skill levelup logic. Also the progress label could in rare cases @@ -242,14 +236,14 @@ namespace MWGui // Leaving the original display logic for now, for consistency with ess-imported savegames. int progressPercent = int(float(progress) / float(progressRequirement) * 100.f + 0.5f); - w->setUserString("Caption_SkillProgressText", MyGUI::utility::toString(progressPercent)+"/100"); + w->setUserString("Caption_SkillProgressText", MyGUI::utility::toString(progressPercent) + "/100"); w->setUserString("RangePosition_SkillProgress", MyGUI::utility::toString(progressPercent)); } - void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) + void StatsWindow::setValue(ESM::RefId id, const MWMechanics::SkillValue& value) { - mSkillValues[parSkill] = value; - std::pair widgets = mSkillWidgetMap[(int)parSkill]; + mSkillValues[id] = value; + std::pair widgets = mSkillWidgetMap[id]; MyGUI::TextBox* valueWidget = widgets.second; MyGUI::TextBox* nameWidget = widgets.first; if (valueWidget && nameWidget) @@ -270,8 +264,9 @@ namespace MWGui int widthAfter = valueWidget->getTextSize().width; if (widthBefore != widthAfter) { - valueWidget->setCoord(valueWidget->getLeft() - (widthAfter-widthBefore), valueWidget->getTop(), valueWidget->getWidth() + (widthAfter-widthBefore), valueWidget->getHeight()); - nameWidget->setSize(nameWidget->getWidth() - (widthAfter-widthBefore), nameWidget->getHeight()); + valueWidget->setCoord(valueWidget->getLeft() - (widthAfter - widthBefore), valueWidget->getTop(), + valueWidget->getWidth() + (widthAfter - widthBefore), valueWidget->getHeight()); + nameWidget->setSize(nameWidget->getWidth() - (widthAfter - widthBefore), nameWidget->getHeight()); } if (value.getBase() < 100) @@ -286,8 +281,8 @@ namespace MWGui valueWidget->setUserString("Visible_SkillProgressVBox", "true"); valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false"); - setSkillProgress(nameWidget, value.getProgress(), parSkill); - setSkillProgress(valueWidget, value.getProgress(), parSkill); + setSkillProgress(nameWidget, value.getProgress(), id); + setSkillProgress(valueWidget, value.getProgress(), id); } else { @@ -304,71 +299,79 @@ namespace MWGui } } - void StatsWindow::configureSkills (const std::vector& major, const std::vector& minor) + void StatsWindow::configureSkills(const std::vector& major, const std::vector& minor) { mMajorSkills = major; mMinorSkills = minor; // Update misc skills with the remaining skills not in major or minor - std::set skillSet; + std::set skillSet; std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); mMiscSkills.clear(); - for (const int skill : ESM::Skill::sSkillIds) + const auto& store = MWBase::Environment::get().getWorld()->getStore().get(); + for (const auto& skill : store) { - if (skillSet.find(skill) == skillSet.end()) - mMiscSkills.push_back(skill); + if (!skillSet.contains(skill.mId)) + mMiscSkills.push_back(skill.mId); } updateSkillArea(); } - void StatsWindow::onFrame (float dt) + void StatsWindow::onFrame(float dt) { NoDrop::onFrame(dt); MWWorld::Ptr player = MWMechanics::getPlayer(); - const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player); + const MWMechanics::NpcStats& PCstats = player.getClass().getNpcStats(player); + const auto& store = MWBase::Environment::get().getESMStore(); + + std::stringstream detail; + bool first = true; + for (const auto& attribute : store->get()) + { + float mult = PCstats.getLevelupAttributeMultiplier(attribute.mId); + mult = std::min(mult, 100 - PCstats.getAttribute(attribute.mId).getBase()); + if (mult > 1) + { + if (!first) + detail << '\n'; + detail << attribute.mName << " x" << MyGUI::utility::toString(mult); + first = false; + } + } + std::string detailText = detail.str(); // level progress MyGUI::Widget* levelWidget; - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { - int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); - getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); + int max = store->get().find("iLevelUpTotal")->mValue.getInteger(); + getWidget(levelWidget, i == 0 ? "Level_str" : "LevelText"); - levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); + levelWidget->setUserString( + "RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); - levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" - + MyGUI::utility::toString(max)); + levelWidget->setUserString("Caption_LevelProgressText", + MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" + MyGUI::utility::toString(max)); + levelWidget->setUserString("Caption_LevelDetailText", detailText); } - std::stringstream detail; - for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) - { - float mult = PCstats.getLevelupAttributeMultiplier(attribute); - mult = std::min(mult, 100 - PCstats.getAttribute(attribute).getBase()); - if (mult > 1) - detail << (detail.str().empty() ? "" : "\n") << "#{" - << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[attribute]) - << "} x" << MyGUI::utility::toString(mult); - } - levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); setFactions(PCstats.getFactionRanks()); - setExpelled(PCstats.getExpelled ()); + setExpelled(PCstats.getExpelled()); - const std::string &signId = - MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); + const auto& signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); setBirthSign(signId); - setReputation (PCstats.getReputation ()); - setBounty (PCstats.getBounty ()); + setReputation(PCstats.getReputation()); + setBounty(PCstats.getBounty()); if (mChanged) updateSkillArea(); } - void StatsWindow::setFactions (const FactionList& factions) + void StatsWindow::setFactions(const FactionList& factions) { if (mFactions != factions) { @@ -377,7 +380,7 @@ namespace MWGui } } - void StatsWindow::setExpelled (const std::set& expelled) + void StatsWindow::setExpelled(const std::set& expelled) { if (mExpelled != expelled) { @@ -386,7 +389,7 @@ namespace MWGui } } - void StatsWindow::setBirthSign (const std::string& signId) + void StatsWindow::setBirthSign(const ESM::RefId& signId) { if (signId != mBirthSignId) { @@ -395,7 +398,7 @@ namespace MWGui } } - void StatsWindow::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + void StatsWindow::addSeparator(MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { MyGUI::ImageBox* separator = mSkillView->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), @@ -407,12 +410,12 @@ namespace MWGui coord2.top += separator->getHeight(); } - void StatsWindow::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + void StatsWindow::addGroup(std::string_view label, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { MyGUI::TextBox* groupWidget = mSkillView->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - groupWidget->setCaption(label); + groupWidget->setCaption(toUString(label)); groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); mSkillWidgets.push_back(groupWidget); @@ -421,22 +424,26 @@ namespace MWGui coord2.top += lineHeight; } - std::pair StatsWindow::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + std::pair StatsWindow::addValueItem(std::string_view text, + const std::string& value, const std::string& state, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { MyGUI::TextBox *skillNameWidget, *skillValueWidget; - skillNameWidget = mSkillView->createWidget("SandText", coord1, MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - skillNameWidget->setCaption(text); + skillNameWidget = mSkillView->createWidget( + "SandText", coord1, MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); + skillNameWidget->setCaption(toUString(text)); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); - skillValueWidget = mSkillView->createWidget("SandTextRight", coord2, MyGUI::Align::Right | MyGUI::Align::Top); + skillValueWidget = mSkillView->createWidget( + "SandTextRight", coord2, MyGUI::Align::Right | MyGUI::Align::Top); skillValueWidget->setCaption(value); skillValueWidget->_setWidgetState(state); skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); // resize dynamically according to text size int textWidthPlusMargin = skillValueWidget->getTextSize().width + 12; - skillValueWidget->setCoord(coord2.left + coord2.width - textWidthPlusMargin, coord2.top, textWidthPlusMargin, coord2.height); + skillValueWidget->setCoord( + coord2.left + coord2.width - textWidthPlusMargin, coord2.top, textWidthPlusMargin, coord2.height); skillNameWidget->setSize(skillNameWidget->getSize() + MyGUI::IntSize(coord2.width - textWidthPlusMargin, 0)); mSkillWidgets.push_back(skillNameWidget); @@ -449,7 +456,7 @@ namespace MWGui return std::make_pair(skillNameWidget, skillValueWidget); } - MyGUI::Widget* StatsWindow::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + MyGUI::Widget* StatsWindow::addItem(const std::string& text, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { MyGUI::TextBox* skillNameWidget; @@ -470,7 +477,8 @@ namespace MWGui return skillNameWidget; } - void StatsWindow::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) + void StatsWindow::addSkills(const std::vector& skills, const std::string& titleId, + const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) { // Add a line separator if there are items above if (!mSkillWidgets.empty()) @@ -478,40 +486,37 @@ namespace MWGui addSeparator(coord1, coord2); } - addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); + addGroup( + MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); - for (const int skillId : skills) + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + for (const ESM::RefId& skillId : skills) { - if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes + const ESM::Skill* skill = esmStore.get().search(skillId); + if (!skill) // Skip unknown skills continue; - const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const ESM::Attribute* attr = esmStore.get().find(skill->mData.mAttribute); - const ESM::Skill* skill = esmStore.get().find(skillId); + std::pair widgets + = addValueItem(skill->mName, {}, "normal", coord1, coord2); + mSkillWidgetMap[skill->mId] = widgets; - std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; - - const ESM::Attribute* attr = - esmStore.get().find(skill->mData.mAttribute); - - std::pair widgets = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), - "", "normal", coord1, coord2); - mSkillWidgetMap[skillId] = widgets; - - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "SkillToolTip"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillName", "#{"+skillNameId+"}"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillDescription", skill->mDescription); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("ToolTipType", "Layout"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("ToolTipLayout", "SkillToolTip"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString( + "Caption_SkillName", MyGUI::TextIterator::toTagsString(skill->mName)); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString( + "Caption_SkillDescription", skill->mDescription); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("Caption_SkillAttribute", + "#{sGoverningAttribute}: " + MyGUI::TextIterator::toTagsString(attr->mName)); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("ImageTexture_SkillImage", skill->mIcon); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("Range_SkillProgress", "100"); } - setValue(static_cast(skillId), mSkillValues.find(skillId)->second); + setValue(skill->mId, mSkillValues.find(skill->mId)->second); } } @@ -538,10 +543,9 @@ namespace MWGui if (!mMiscSkills.empty()) addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::ESMStore &store = world->getStore(); - const ESM::NPC *player = - world->getPlayerPtr().get()->mBase; + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::ESMStore& store = world->getStore(); + const ESM::NPC* player = world->getPlayerPtr().get()->mBase; // race tooltip const ESM::Race* playerRace = store.get().find(player->mRace); @@ -555,8 +559,7 @@ namespace MWGui // class tooltip MyGUI::Widget* classWidget; - const ESM::Class *playerClass = - store.get().find(player->mClass); + const ESM::Class* playerClass = store.get().find(player->mClass); getWidget(classWidget, "ClassText"); ToolTips::createClassToolTip(classWidget, *playerClass); @@ -566,15 +569,14 @@ namespace MWGui if (!mFactions.empty()) { MWWorld::Ptr playerPtr = MWMechanics::getPlayer(); - const MWMechanics::NpcStats &PCstats = playerPtr.getClass().getNpcStats(playerPtr); - const std::set &expelled = PCstats.getExpelled(); + const MWMechanics::NpcStats& PCstats = playerPtr.getClass().getNpcStats(playerPtr); + const std::set& expelled = PCstats.getExpelled(); - bool firstFaction=true; + bool firstFaction = true; for (auto& factionPair : mFactions) { - const std::string& factionId = factionPair.first; - const ESM::Faction *faction = - store.get().find(factionId); + const ESM::RefId& factionId = factionPair.first; + const ESM::Faction* faction = store.get().find(factionId); if (faction->mData.mIsHidden == 1) continue; @@ -584,7 +586,8 @@ namespace MWGui if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); - addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFaction", "Faction"), coord1, coord2); + addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFaction", "Faction"), + coord1, coord2); firstFaction = false; } @@ -599,35 +602,36 @@ namespace MWGui text += "\n#{fontcolourhtml=normal}#{sExpelled}"; else { - int rank = factionPair.second; - rank = std::max(0, std::min(9, rank)); + const int rank = std::clamp(factionPair.second, 0, 9); text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; if (rank < 9) { // player doesn't have max rank yet - text += std::string("\n\n#{fontcolourhtml=header}#{sNextRank} ") + faction->mRanks[rank+1]; + text += std::string("\n\n#{fontcolourhtml=header}#{sNextRank} ") + faction->mRanks[rank + 1]; - ESM::RankData rankData = faction->mData.mRankData[rank+1]; + const ESM::RankData& rankData = faction->mData.mRankData[rank + 1]; const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute[0]); const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute[1]); - text += "\n#{fontcolourhtml=normal}#{" + attr1->mName + "}: " + MyGUI::utility::toString(rankData.mAttribute1) - + ", #{" + attr2->mName + "}: " + MyGUI::utility::toString(rankData.mAttribute2); + text += "\n#{fontcolourhtml=normal}" + MyGUI::TextIterator::toTagsString(attr1->mName) + ": " + + MyGUI::utility::toString(rankData.mAttribute1) + ", " + + MyGUI::TextIterator::toTagsString(attr2->mName) + ": " + + MyGUI::utility::toString(rankData.mAttribute2); text += "\n\n#{fontcolourhtml=header}#{sFavoriteSkills}"; text += "\n#{fontcolourhtml=normal}"; bool firstSkill = true; - for (int i=0; i<7; ++i) + for (int id : faction->mData.mSkills) { - if (faction->mData.mSkills[i] != -1) + const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(id)); + if (skill) { if (!firstSkill) text += ", "; firstSkill = false; - - text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkills[i]]+"}"; + text += MyGUI::TextIterator::toTagsString(skill->mName); } } @@ -652,9 +656,9 @@ namespace MWGui if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); - addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBirthSign", "Sign"), coord1, coord2); - const ESM::BirthSign *sign = - store.get().find(mBirthSignId); + addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBirthSign", "Sign"), coord1, + coord2); + const ESM::BirthSign* sign = store.get().find(mBirthSignId); MyGUI::Widget* w = addItem(sign->mName, coord1, coord2); ToolTips::createBirthsignToolTip(w, mBirthSignId); @@ -665,34 +669,35 @@ namespace MWGui addSeparator(coord1, coord2); addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString("sReputation", "Reputation"), - MyGUI::utility::toString(static_cast(mReputation)), "normal", coord1, coord2); + MyGUI::utility::toString(static_cast(mReputation)), "normal", coord1, coord2); - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sSkillsMenuReputationHelp}"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("ToolTipType", "Layout"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("ToolTipLayout", "TextToolTip"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("Caption_Text", "#{sSkillsMenuReputationHelp}"); } addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBounty", "Bounty"), - MyGUI::utility::toString(static_cast(mBounty)), "normal", coord1, coord2); + MyGUI::utility::toString(static_cast(mBounty)), "normal", coord1, coord2); - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("ToolTipType", "Layout"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("ToolTipLayout", "TextToolTip"); + mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("Caption_Text", "#{sCrimeHelp}"); } - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mSkillView->setVisibleVScroll(false); - mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); + mSkillView->setCanvasSize(mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); mSkillView->setVisibleVScroll(true); } void StatsWindow::onPinToggled() { - Settings::Manager::setBool("stats pin", "Windows", mPinned); + Settings::windows().mStatsPin.set(mPinned); MWBase::Environment::get().getWindowManager()->setHMSVisibility(!mPinned); } diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index bf78cde34..92926c118 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -3,73 +3,86 @@ #include "statswatcher.hpp" #include "windowpinnablebase.hpp" +#include +#include namespace MWGui { class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener { - public: - typedef std::map FactionList; + public: + typedef std::map FactionList; - typedef std::vector SkillList; + StatsWindow(DragAndDrop* drag); - StatsWindow(DragAndDrop* drag); + /// automatically updates all the data in the stats window, but only if it has changed. + void onFrame(float dt) override; - /// automatically updates all the data in the stats window, but only if it has changed. - void onFrame(float dt) override; + void setBar(const std::string& name, const std::string& tname, int val, int max); + void setPlayerName(const std::string& playerName); - void setBar(const std::string& name, const std::string& tname, int val, int max); - void setPlayerName(const std::string& playerName); + /// Set value for the given ID. + void setValue(ESM::Attribute::AttributeID id, const MWMechanics::AttributeValue& value) override; + void setValue(std::string_view id, const MWMechanics::DynamicStat& value) override; + void setValue(std::string_view id, const std::string& value) override; + void setValue(std::string_view id, int value) override; + void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) override; + void configureSkills(const std::vector& major, const std::vector& minor) override; - /// Set value for the given ID. - void setValue (const std::string& id, const MWMechanics::AttributeValue& value) override; - void setValue (const std::string& id, const MWMechanics::DynamicStat& value) override; - void setValue (const std::string& id, const std::string& value) override; - void setValue (const std::string& id, int value) override; - void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override; - void configureSkills(const SkillList& major, const SkillList& minor) override; + void setReputation(int reputation) + { + if (reputation != mReputation) + mChanged = true; + this->mReputation = reputation; + } + void setBounty(int bounty) + { + if (bounty != mBounty) + mChanged = true; + this->mBounty = bounty; + } + void updateSkillArea(); - void setReputation (int reputation) { if (reputation != mReputation) mChanged = true; this->mReputation = reputation; } - void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; } - void updateSkillArea(); + void onOpen() override { onWindowResize(mMainWidget->castType()); } - void onOpen() override { onWindowResize(mMainWidget->castType()); } + private: + void addSkills(const std::vector& skills, const std::string& titleId, + const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + void addSeparator(MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + void addGroup(std::string_view label, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + std::pair addValueItem(std::string_view text, const std::string& value, + const std::string& state, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); + MyGUI::Widget* addItem(const std::string& text, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); - private: - void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - std::pair addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - MyGUI::Widget* addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); + void setFactions(const FactionList& factions); + void setExpelled(const std::set& expelled); + void setBirthSign(const ESM::RefId& signId); - void setFactions (const FactionList& factions); - void setExpelled (const std::set& expelled); - void setBirthSign (const std::string &signId); + void onWindowResize(MyGUI::Window* window); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void onWindowResize(MyGUI::Window* window); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); + MyGUI::Widget* mLeftPane; + MyGUI::Widget* mRightPane; - MyGUI::Widget* mLeftPane; - MyGUI::Widget* mRightPane; + MyGUI::ScrollView* mSkillView; - MyGUI::ScrollView* mSkillView; + std::vector mMajorSkills, mMinorSkills, mMiscSkills; + std::map mSkillValues; + std::map mAttributeWidgets; + std::map> mSkillWidgetMap; + std::map mFactionWidgetMap; + FactionList mFactions; ///< Stores a list of factions and the current rank + ESM::RefId mBirthSignId; + int mReputation, mBounty; + std::vector mSkillWidgets; //< Skills and other information + std::set mExpelled; - SkillList mMajorSkills, mMinorSkills, mMiscSkills; - std::map mSkillValues; - std::map > mSkillWidgetMap; - std::map mFactionWidgetMap; - FactionList mFactions; ///< Stores a list of factions and the current rank - std::string mBirthSignId; - int mReputation, mBounty; - std::vector mSkillWidgets; //< Skills and other information - std::set mExpelled; + bool mChanged; + const int mMinFullWidth; - bool mChanged; - const int mMinFullWidth; - - protected: - void onPinToggled() override; - void onTitleDoubleClicked() override; + protected: + void onPinToggled() override; + void onTitleDoubleClicked() override; }; } #endif diff --git a/apps/openmw/mwgui/textinput.cpp b/apps/openmw/mwgui/textinput.cpp index 54f2d3be9..6ff4e5304 100644 --- a/apps/openmw/mwgui/textinput.cpp +++ b/apps/openmw/mwgui/textinput.cpp @@ -1,16 +1,18 @@ #include "textinput.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" -#include #include +#include + +#include "ustring.hpp" namespace MWGui { TextInputDialog::TextInputDialog() - : WindowModal("openmw_text_input.layout") + : WindowModal("openmw_text_input.layout") { // Centre dialog center(); @@ -32,12 +34,14 @@ namespace MWGui getWidget(okButton, "OKButton"); if (shown) - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else - okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", "")); + okButton->setCaption( + toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } - void TextInputDialog::setTextLabel(const std::string &label) + void TextInputDialog::setTextLabel(std::string_view label) { setText("LabelT", label); } @@ -53,10 +57,10 @@ namespace MWGui void TextInputDialog::onOkClicked(MyGUI::Widget* _sender) { - if (mTextEdit->getCaption() == "") + if (mTextEdit->getCaption().empty()) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage37}"); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget (mTextEdit); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage37}"); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); } else eventDone(this); @@ -75,10 +79,9 @@ namespace MWGui return mTextEdit->getCaption(); } - void TextInputDialog::setTextInput(const std::string &text) + void TextInputDialog::setTextInput(const std::string& text) { mTextEdit->setCaption(text); } - } diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp index 4d365eb44..0e616305c 100644 --- a/apps/openmw/mwgui/textinput.hpp +++ b/apps/openmw/mwgui/textinput.hpp @@ -11,10 +11,10 @@ namespace MWGui TextInputDialog(); std::string getTextInput() const; - void setTextInput(const std::string &text); + void setTextInput(const std::string& text); void setNextButtonShow(bool shown); - void setTextLabel(const std::string &label); + void setTextLabel(std::string_view label); void onOpen() override; bool exit() override { return false; } diff --git a/apps/openmw/mwgui/timeadvancer.cpp b/apps/openmw/mwgui/timeadvancer.cpp index c38094ae4..2cdab127b 100644 --- a/apps/openmw/mwgui/timeadvancer.cpp +++ b/apps/openmw/mwgui/timeadvancer.cpp @@ -3,12 +3,12 @@ namespace MWGui { TimeAdvancer::TimeAdvancer(float delay) - : mRunning(false), - mCurHour(0), - mHours(1), - mInterruptAt(-1), - mDelay(delay), - mRemainingTime(delay) + : mRunning(false) + , mCurHour(0) + , mHours(1) + , mInterruptAt(-1) + , mDelay(delay) + , mRemainingTime(delay) { } @@ -54,7 +54,6 @@ namespace MWGui eventFinished(); return; } - } } diff --git a/apps/openmw/mwgui/timeadvancer.hpp b/apps/openmw/mwgui/timeadvancer.hpp index b8456f376..56a190a3e 100644 --- a/apps/openmw/mwgui/timeadvancer.hpp +++ b/apps/openmw/mwgui/timeadvancer.hpp @@ -1,39 +1,39 @@ #ifndef MWGUI_TIMEADVANCER_H #define MWGUI_TIMEADVANCER_H -#include +#include namespace MWGui { class TimeAdvancer { - public: - TimeAdvancer(float delay); + public: + TimeAdvancer(float delay); - void run(int hours, int interruptAt=-1); - void stop(); - void onFrame(float dt); + void run(int hours, int interruptAt = -1); + void stop(); + void onFrame(float dt); - int getHours() const; - bool isRunning() const; + int getHours() const; + bool isRunning() const; - // signals - typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; - typedef MyGUI::delegates::CMultiDelegate2 EventHandle_IntInt; + // signals + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; + typedef MyGUI::delegates::CMultiDelegate2 EventHandle_IntInt; - EventHandle_IntInt eventProgressChanged; - EventHandle_Void eventInterrupted; - EventHandle_Void eventFinished; + EventHandle_IntInt eventProgressChanged; + EventHandle_Void eventInterrupted; + EventHandle_Void eventFinished; - private: - bool mRunning; + private: + bool mRunning; - int mCurHour; - int mHours; - int mInterruptAt; + int mCurHour; + int mHours; + int mInterruptAt; - float mDelay; - float mRemainingTime; + float mDelay; + float mRemainingTime; }; } diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index a821d0106..17018db6d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -3,34 +3,35 @@ #include #include -#include -#include #include +#include +#include +#include -#include +#include +#include +#include +#include #include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/spellutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/spellutil.hpp" -#include "../mwmechanics/actorutil.hpp" -#include "mapwindow.hpp" #include "inventorywindow.hpp" +#include "mapwindow.hpp" #include "itemmodel.hpp" namespace MWGui { - std::string ToolTips::sSchoolNames[] = {"#{sSchoolAlteration}", "#{sSchoolConjuration}", "#{sSchoolDestruction}", "#{sSchoolIllusion}", "#{sSchoolMysticism}", "#{sSchoolRestoration}"}; - - ToolTips::ToolTips() : - Layout("openmw_tooltips.layout") + ToolTips::ToolTips() + : Layout("openmw_tooltips.layout") , mFocusToolTipX(0.0) , mFocusToolTipY(0.0) , mHorizontalScrollIndex(0) @@ -40,7 +41,6 @@ namespace MWGui , mLastMouseY(0) , mEnabled(true) , mFullHelp(false) - , mShowOwned(0) , mFrameDuration(0.f) { getWidget(mDynamicToolTipBox, "DynamicToolTipBox"); @@ -55,12 +55,10 @@ namespace MWGui mDelay = Settings::Manager::getFloat("tooltip delay", "GUI"); mRemainingDelay = mDelay; - for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) + for (unsigned int i = 0; i < mMainWidget->getChildCount(); ++i) { mMainWidget->getChildAt(i)->setVisible(false); } - - mShowOwned = Settings::Manager::getInt("show owned", "Game"); } void ToolTips::setEnabled(bool enabled) @@ -82,19 +80,19 @@ namespace MWGui } // start by hiding everything - for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) + for (unsigned int i = 0; i < mMainWidget->getChildCount(); ++i) { mMainWidget->getChildAt(i)->setVisible(false); } - const MyGUI::IntSize &viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); if (!mEnabled) { return; } - MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); bool guiMode = winMgr->isGuiMode(); if (guiMode) @@ -103,12 +101,11 @@ namespace MWGui return; const MyGUI::IntPoint& mousePos = MyGUI::InputManager::getInstance().getMousePosition(); - if (winMgr->getWorldMouseOver() && - (winMgr->isConsoleMode() || - (winMgr->getMode() == GM_Container) || - (winMgr->getMode() == GM_Inventory))) + if (winMgr->getWorldMouseOver() + && (winMgr->isConsoleMode() || (winMgr->getMode() == GM_Container) + || (winMgr->getMode() == GM_Inventory))) { - if (mFocusObject.isEmpty ()) + if (mFocusObject.isEmpty()) return; const MWWorld::Class& objectclass = mFocusObject.getClass(); @@ -121,8 +118,8 @@ namespace MWGui ToolTipInfo info; info.caption = mFocusObject.getClass().getName(mFocusObject); if (info.caption.empty()) - info.caption=mFocusObject.getCellRef().getRefId(); - info.icon=""; + info.caption = mFocusObject.getCellRef().getRefId().toDebugString(); + info.icon.clear(); tooltipSize = createToolTip(info, checkOwned()); } else @@ -148,7 +145,6 @@ namespace MWGui mLastMouseX = mousePos.left; mLastMouseY = mousePos.top; - if (mRemainingDelay > 0) return; @@ -160,23 +156,20 @@ namespace MWGui // try to go 1 level up until there is a widget that has tooltip // this is necessary because some skin elements are actually separate widgets - int i=0; while (!focus->isUserString("ToolTipType")) { focus = focus->getParent(); if (!focus) return; - ++i; } - std::string type = focus->getUserString("ToolTipType"); + std::string_view type = focus->getUserString("ToolTipType"); - if (type == "") + if (type.empty()) { return; } - // special handling for markers on the local map: the tooltip should only be visible // if the marker is not hidden due to the fog of war. if (type == "MapMarker") @@ -194,14 +187,15 @@ namespace MWGui else if (type == "ItemPtr") { mFocusObject = *focus->getUserData(); - if (!mFocusObject) + if (mFocusObject.isEmpty()) return; tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned()); } else if (type == "ItemModelIndex") { - std::pair pair = *focus->getUserData >(); + std::pair pair + = *focus->getUserData>(); mFocusObject = pair.second->getItem(pair.first).mBase; bool isAllowedToUse = pair.second->allowedToUseItems(); tooltipSize = getToolTipViaPtr(pair.second->getItem(pair.first).mCount, false, !isAllowedToUse); @@ -213,19 +207,22 @@ namespace MWGui else if (type == "AvatarItemSelection") { MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord(); - MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top); - MWWorld::Ptr item = winMgr->getInventoryWindow ()->getAvatarSelectedItem (relMousePos.left, relMousePos.top); + MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance().getMousePosition() + - MyGUI::IntPoint(avatarPos.left, avatarPos.top); + MWWorld::Ptr item + = winMgr->getInventoryWindow()->getAvatarSelectedItem(relMousePos.left, relMousePos.top); mFocusObject = item; - if (!mFocusObject.isEmpty ()) + if (!mFocusObject.isEmpty()) tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); } else if (type == "Spell") { ToolTipInfo info; - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find(focus->getUserString("Spell")); + const auto& store = MWBase::Environment::get().getESMStore(); + const ESM::Spell* spell + = store->get().find(ESM::RefId::deserialize(focus->getUserString("Spell"))); info.caption = spell->mName; Widgets::SpellEffectList effects; for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList) @@ -243,15 +240,18 @@ namespace MWGui params.mNoTarget = false; effects.push_back(params); } - if (MWMechanics::spellIncreasesSkill(spell)) // display school of spells that contribute to skill progress + if (MWMechanics::spellIncreasesSkill( + spell)) // display school of spells that contribute to skill progress { MWWorld::Ptr player = MWMechanics::getPlayer(); - int school = MWMechanics::getSpellSchool(spell, player); - info.text = "#{sSchool}: " + sSchoolNames[school]; + const auto& school + = store->get().find(MWMechanics::getSpellSchool(spell, player))->mSchool; + info.text = "#{sSchool}: " + MyGUI::TextIterator::toTagsString(school->mName).asUTF8(); } - std::string cost = focus->getUserString("SpellCost"); - if (cost != "" && cost != "0") - info.text += MWGui::ToolTips::getValueString(spell->mData.mCost, "#{sCastCost}"); + const std::string& cost = focus->getUserString("SpellCost"); + if (!cost.empty() && cost != "0") + info.text + += MWGui::ToolTips::getValueString(MWMechanics::calcSpellCost(*spell), "#{sCastCost}"); info.effects = effects; tooltipSize = createToolTip(info); } @@ -263,20 +263,21 @@ namespace MWGui tooltip->setVisible(true); - std::map userStrings = focus->getUserStrings(); + const auto& userStrings = focus->getUserStrings(); for (auto& userStringPair : userStrings) { size_t underscorePos = userStringPair.first.find('_'); if (underscorePos == std::string::npos) continue; std::string key = userStringPair.first.substr(0, underscorePos); - std::string widgetName = userStringPair.first.substr(underscorePos+1, userStringPair.first.size()-(underscorePos+1)); + std::string_view first = userStringPair.first; + std::string_view widgetName = first.substr(underscorePos + 1); type = "Property"; size_t caretPos = key.find('^'); if (caretPos != std::string::npos) { - type = key.substr(0, caretPos); + type = first.substr(0, caretPos); key.erase(key.begin(), key.begin() + caretPos + 1); } @@ -293,7 +294,7 @@ namespace MWGui tooltip->setCoord(0, 0, tooltipSize.width, tooltipSize.height); } else - throw std::runtime_error ("unknown tooltip type"); + throw std::runtime_error("unknown tooltip type"); MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); @@ -308,10 +309,9 @@ namespace MWGui { MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true, checkOwned()); - setCoord(viewSize.width/2 - tooltipSize.width/2, - std::max(0, int(mFocusToolTipY*viewSize.height - tooltipSize.height)), - tooltipSize.width, - tooltipSize.height); + setCoord(viewSize.width / 2 - tooltipSize.width / 2, + std::max(0, int(mFocusToolTipY * viewSize.height - tooltipSize.height)), tooltipSize.width, + tooltipSize.height); mDynamicToolTipBox->setVisible(true); } @@ -321,7 +321,9 @@ namespace MWGui void ToolTips::position(MyGUI::IntPoint& position, MyGUI::IntSize size, MyGUI::IntSize viewportSize) { position += MyGUI::IntPoint(0, 32) - - MyGUI::IntPoint(static_cast(MyGUI::InputManager::getInstance().getMousePosition().left / float(viewportSize.width) * size.width), 0); + - MyGUI::IntPoint(static_cast(MyGUI::InputManager::getInstance().getMousePosition().left + / float(viewportSize.width) * size.width), + 0); if ((position.left + size.width) > viewportSize.width) { @@ -342,7 +344,7 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(mDynamicToolTipBox->getChildAt(0)); } - for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) + for (unsigned int i = 0; i < mMainWidget->getChildCount(); ++i) { mMainWidget->getChildAt(i)->setVisible(false); } @@ -355,7 +357,7 @@ namespace MWGui update(mFrameDuration); } - MyGUI::IntSize ToolTips::getToolTipViaPtr (int count, bool image, bool isOwned) + MyGUI::IntSize ToolTips::getToolTipViaPtr(int count, bool image, bool isOwned) { // this the maximum width of the tooltip before it starts word-wrapping setCoord(0, 0, 300, 300); @@ -373,16 +375,16 @@ namespace MWGui ToolTipInfo info = object.getToolTipInfo(mFocusObject, count); if (!image) - info.icon = ""; + info.icon.clear(); tooltipSize = createToolTip(info, isOwned); } return tooltipSize; } - + bool ToolTips::checkOwned() { - if(mFocusObject.isEmpty()) + if (mFocusObject.isEmpty()) return false; MWWorld::Ptr ptr = MWMechanics::getPlayer(); @@ -395,15 +397,19 @@ namespace MWGui MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info, bool isOwned) { mDynamicToolTipBox->setVisible(true); - - if((mShowOwned == 1 || mShowOwned == 3) && isOwned) - mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp_Owned" : "HUD_Box_Owned"); - else - mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp" : "HUD_Box"); - std::string caption = info.caption; - std::string image = info.icon; - int imageSize = (image != "") ? info.imageSize : 0; + const int showOwned = Settings::game().mShowOwned; + if ((showOwned == 1 || showOwned == 3) && isOwned) + mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() + ? "HUD_Box_NoTransp_Owned" + : "HUD_Box_Owned"); + else + mDynamicToolTipBox->changeWidgetSkin( + MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp" : "HUD_Box"); + + const std::string& caption = info.caption; + const std::string& image = info.icon; + int imageSize = (!image.empty()) ? info.imageSize : 0; std::string text = info.text; // remove the first newline (easier this way) @@ -411,8 +417,8 @@ namespace MWGui text.erase(0, 1); const ESM::Enchantment* enchant = nullptr; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - if (info.enchant != "") + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + if (!info.enchant.empty()) { enchant = store.get().search(info.enchant); if (enchant) @@ -433,22 +439,26 @@ namespace MWGui const MyGUI::IntPoint padding(8, 8); - const int imageCaptionHPadding = (caption != "" ? 8 : 0); - const int imageCaptionVPadding = (caption != "" ? 4 : 0); + const int imageCaptionHPadding = !caption.empty() ? 8 : 0; + const int imageCaptionVPadding = !caption.empty() ? 4 : 0; const int maximumWidth = MyGUI::RenderManager::getInstance().getViewSize().width - imageCaptionHPadding * 2; - std::string realImage = MWBase::Environment::get().getWindowManager()->correctIconPath(image); + const std::string realImage + = Misc::ResourceHelpers::correctIconPath(image, MWBase::Environment::get().getResourceSystem()->getVFS()); - Gui::EditBox* captionWidget = mDynamicToolTipBox->createWidget("NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); + Gui::EditBox* captionWidget = mDynamicToolTipBox->createWidget( + "NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); captionWidget->setEditStatic(true); captionWidget->setNeedKeyFocus(false); captionWidget->setCaptionWithReplacing(caption); MyGUI::IntSize captionSize = captionWidget->getTextSize(); - int captionHeight = std::max(caption != "" ? captionSize.height : 0, imageSize); + int captionHeight = std::max(!caption.empty() ? captionSize.height : 0, imageSize); - Gui::EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", MyGUI::IntCoord(0, captionHeight+imageCaptionVPadding, 300, 300-captionHeight-imageCaptionVPadding), MyGUI::Align::Stretch, "ToolTipText"); + Gui::EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", + MyGUI::IntCoord(0, captionHeight + imageCaptionVPadding, 300, 300 - captionHeight - imageCaptionVPadding), + MyGUI::Align::Stretch, "ToolTipText"); textWidget->setEditStatic(true); textWidget->setEditMultiLine(true); textWidget->setEditWordWrap(info.wordWrap); @@ -458,73 +468,80 @@ namespace MWGui MyGUI::IntSize textSize = textWidget->getTextSize(); captionSize += MyGUI::IntSize(imageSize, 0); // adjust for image - MyGUI::IntSize totalSize = MyGUI::IntSize( std::min(std::max(textSize.width,captionSize.width + ((image != "") ? imageCaptionHPadding : 0)),maximumWidth), - ((text != "") ? textSize.height + imageCaptionVPadding : 0) + captionHeight ); + MyGUI::IntSize totalSize = MyGUI::IntSize( + std::min(std::max(textSize.width, captionSize.width + ((!image.empty()) ? imageCaptionHPadding : 0)), + maximumWidth), + (!text.empty() ? textSize.height + imageCaptionVPadding : 0) + captionHeight); for (const std::string& note : info.notes) { MyGUI::ImageBox* icon = mDynamicToolTipBox->createWidget("MarkerButton", - MyGUI::IntCoord(padding.left, totalSize.height+padding.top, 8, 8), MyGUI::Align::Default); + MyGUI::IntCoord(padding.left, totalSize.height + padding.top, 8, 8), MyGUI::Align::Default); icon->setColour(MyGUI::Colour(1.0f, 0.3f, 0.3f)); Gui::EditBox* edit = mDynamicToolTipBox->createWidget("SandText", - MyGUI::IntCoord(padding.left+8+4, totalSize.height+padding.top, 300-padding.left-8-4, 300-totalSize.height), - MyGUI::Align::Default); + MyGUI::IntCoord(padding.left + 8 + 4, totalSize.height + padding.top, 300 - padding.left - 8 - 4, + 300 - totalSize.height), + MyGUI::Align::Default); edit->setEditMultiLine(true); edit->setEditWordWrap(true); edit->setCaption(note); edit->setSize(edit->getWidth(), edit->getTextSize().height); - icon->setPosition(icon->getLeft(),(edit->getTop()+edit->getBottom())/2-icon->getHeight()/2); + icon->setPosition(icon->getLeft(), (edit->getTop() + edit->getBottom()) / 2 - icon->getHeight() / 2); totalSize.height += std::max(edit->getHeight(), icon->getHeight()); - totalSize.width = std::max(totalSize.width, edit->getWidth()+8+4); + totalSize.width = std::max(totalSize.width, edit->getWidth() + 8 + 4); } if (!info.effects.empty()) { - MyGUI::Widget* effectArea = mDynamicToolTipBox->createWidget("", - MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), + MyGUI::Widget* effectArea = mDynamicToolTipBox->createWidget({}, + MyGUI::IntCoord(padding.left, totalSize.height, 300 - padding.left, 300 - totalSize.height), MyGUI::Align::Stretch); MyGUI::IntCoord coord(0, 6, totalSize.width, 24); - Widgets::MWEffectListPtr effectsWidget = effectArea->createWidget - ("MW_StatName", coord, MyGUI::Align::Default); + Widgets::MWEffectListPtr effectsWidget + = effectArea->createWidget("MW_StatName", coord, MyGUI::Align::Default); effectsWidget->setEffectList(info.effects); std::vector effectItems; int flag = info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0; flag |= info.isIngredient ? Widgets::MWEffectList::EF_NoMagnitude : 0; - effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, flag); - totalSize.height += coord.top-6; + flag |= info.isIngredient ? Widgets::MWEffectList::EF_Constant : 0; + effectsWidget->createEffectWidgets( + effectItems, effectArea, coord, info.isPotion || info.isIngredient, flag); + totalSize.height += coord.top - 6; totalSize.width = std::max(totalSize.width, coord.width); } if (enchant) { - MyGUI::Widget* enchantArea = mDynamicToolTipBox->createWidget("", - MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), + MyGUI::Widget* enchantArea = mDynamicToolTipBox->createWidget({}, + MyGUI::IntCoord(padding.left, totalSize.height, 300 - padding.left, 300 - totalSize.height), MyGUI::Align::Stretch); MyGUI::IntCoord coord(0, 6, totalSize.width, 24); - Widgets::MWEffectListPtr enchantWidget = enchantArea->createWidget - ("MW_StatName", coord, MyGUI::Align::Default); + Widgets::MWEffectListPtr enchantWidget + = enchantArea->createWidget("MW_StatName", coord, MyGUI::Align::Default); enchantWidget->setEffectList(Widgets::MWEffectList::effectListFromESM(&enchant->mEffects)); std::vector enchantEffectItems; - int flag = (enchant->mData.mType == ESM::Enchantment::ConstantEffect) ? Widgets::MWEffectList::EF_Constant : 0; - enchantWidget->createEffectWidgets(enchantEffectItems, enchantArea, coord, true, flag); - totalSize.height += coord.top-6; + int flag + = (enchant->mData.mType == ESM::Enchantment::ConstantEffect) ? Widgets::MWEffectList::EF_Constant : 0; + enchantWidget->createEffectWidgets(enchantEffectItems, enchantArea, coord, false, flag); + totalSize.height += coord.top - 6; totalSize.width = std::max(totalSize.width, coord.width); if (enchant->mData.mType == ESM::Enchantment::WhenStrikes || enchant->mData.mType == ESM::Enchantment::WhenUsed) { - int maxCharge = enchant->mData.mCharge; + const int maxCharge = MWMechanics::getEnchantmentCharge(*enchant); int charge = (info.remainingEnchantCharge == -1) ? maxCharge : info.remainingEnchantCharge; const int chargeWidth = 204; - MyGUI::TextBox* chargeText = enchantArea->createWidget("SandText", MyGUI::IntCoord(0, 0, 10, 18), MyGUI::Align::Default, "ToolTipEnchantChargeText"); + MyGUI::TextBox* chargeText = enchantArea->createWidget( + "SandText", MyGUI::IntCoord(0, 0, 10, 18), MyGUI::Align::Default, "ToolTipEnchantChargeText"); chargeText->setCaptionWithReplacing("#{sCharges}"); const int chargeTextWidth = chargeText->getTextSize().width + 5; @@ -533,60 +550,69 @@ namespace MWGui totalSize.width = std::max(totalSize.width, chargeAndTextWidth); - chargeText->setCoord((totalSize.width - chargeAndTextWidth)/2, coord.top+6, chargeTextWidth, 18); + chargeText->setCoord((totalSize.width - chargeAndTextWidth) / 2, coord.top + 6, chargeTextWidth, 18); MyGUI::IntCoord chargeCoord; if (totalSize.width < chargeWidth) { totalSize.width = chargeWidth; - chargeCoord = MyGUI::IntCoord(0, coord.top+6, chargeWidth, 18); + chargeCoord = MyGUI::IntCoord(0, coord.top + 6, chargeWidth, 18); } else { - chargeCoord = MyGUI::IntCoord((totalSize.width - chargeAndTextWidth)/2 + chargeTextWidth, coord.top+6, chargeWidth, 18); + chargeCoord = MyGUI::IntCoord( + (totalSize.width - chargeAndTextWidth) / 2 + chargeTextWidth, coord.top + 6, chargeWidth, 18); } - Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget - ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default); + Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget( + "MW_ChargeBar", chargeCoord, MyGUI::Align::Default); chargeWidget->setValue(charge, maxCharge); totalSize.height += 24; } } - captionWidget->setCoord( (totalSize.width - captionSize.width)/2 + imageSize, - (captionHeight-captionSize.height)/2, - captionSize.width-imageSize, - captionSize.height); + captionWidget->setCoord((totalSize.width - captionSize.width) / 2 + imageSize, + (captionHeight - captionSize.height) / 2, captionSize.width - imageSize, captionSize.height); - //if its too long we do hscroll with the caption + // if its too long we do hscroll with the caption if (captionSize.width > maximumWidth) { mHorizontalScrollIndex = mHorizontalScrollIndex + 2; - if (mHorizontalScrollIndex > captionSize.width){ + if (mHorizontalScrollIndex > captionSize.width) + { mHorizontalScrollIndex = -totalSize.width; } int horizontal_scroll = mHorizontalScrollIndex; - if (horizontal_scroll < 40){ + if (horizontal_scroll < 40) + { horizontal_scroll = 40; - }else{ + } + else + { horizontal_scroll = 80 - mHorizontalScrollIndex; } - captionWidget->setPosition (MyGUI::IntPoint(horizontal_scroll, captionWidget->getPosition().top + padding.top)); - } else { - captionWidget->setPosition (captionWidget->getPosition() + padding); + captionWidget->setPosition( + MyGUI::IntPoint(horizontal_scroll, captionWidget->getPosition().top + padding.top)); + } + else + { + captionWidget->setPosition(captionWidget->getPosition() + padding); } - textWidget->setPosition (textWidget->getPosition() + MyGUI::IntPoint(0, padding.top)); // only apply vertical padding, the horizontal works automatically due to Align::HCenter + textWidget->setPosition(textWidget->getPosition() + + MyGUI::IntPoint(0, + padding.top)); // only apply vertical padding, the horizontal works automatically due to Align::HCenter - if (image != "") + if (!image.empty()) { MyGUI::ImageBox* imageWidget = mDynamicToolTipBox->createWidget("ImageBox", - MyGUI::IntCoord((totalSize.width - captionSize.width - imageCaptionHPadding)/2, 0, imageSize, imageSize), + MyGUI::IntCoord( + (totalSize.width - captionSize.width - imageCaptionHPadding) / 2, 0, imageSize, imageSize), MyGUI::Align::Left | MyGUI::Align::Top); imageWidget->setImageTexture(realImage); - imageWidget->setPosition (imageWidget->getPosition() + padding); + imageWidget->setPosition(imageWidget->getPosition() + padding); } - totalSize += MyGUI::IntSize(padding.left*2, padding.top*2); + totalSize += MyGUI::IntSize(padding.left * 2, padding.top * 2); return totalSize; } @@ -610,7 +636,7 @@ namespace MWGui std::string ToolTips::getWeightString(const float weight, const std::string& prefix) { if (weight == 0) - return ""; + return {}; else return "\n" + prefix + ": " + toString(weight); } @@ -618,23 +644,23 @@ namespace MWGui std::string ToolTips::getPercentString(const float value, const std::string& prefix) { if (value == 0) - return ""; + return {}; else - return "\n" + prefix + ": " + toString(value*100) +"%"; + return "\n" + prefix + ": " + toString(value * 100) + "%"; } std::string ToolTips::getValueString(const int value, const std::string& prefix) { if (value == 0) - return ""; + return {}; else return "\n" + prefix + ": " + toString(value); } std::string ToolTips::getMiscString(const std::string& text, const std::string& prefix) { - if (text == "") - return ""; + if (text.empty()) + return {}; else return "\n" + prefix + ": " + text; } @@ -642,41 +668,41 @@ namespace MWGui std::string ToolTips::getCountString(const int value) { if (value == 1) - return ""; + return {}; else return " (" + MyGUI::utility::toString(value) + ")"; } std::string ToolTips::getSoulString(const MWWorld::CellRef& cellref) { - std::string soul = cellref.getSoul(); + const ESM::RefId& soul = cellref.getSoul(); if (soul.empty()) - return std::string(); - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Creature *creature = store.get().search(soul); + return {}; + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::Creature* creature = store.get().search(soul); if (!creature) - return std::string(); + return {}; if (creature->mName.empty()) - return " (" + creature->mId + ")"; + return " (" + creature->mId.toDebugString() + ")"; return " (" + creature->mName + ")"; } std::string ToolTips::getCellRefString(const MWWorld::CellRef& cellref) { std::string ret; - ret += getMiscString(cellref.getOwner(), "Owner"); - const std::string factionId = cellref.getFaction(); + ret += getMiscString(cellref.getOwner().getRefIdString(), "Owner"); + const ESM::RefId& factionId = cellref.getFaction(); if (!factionId.empty()) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Faction *fact = store.get().search(factionId); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::Faction* fact = store.get().search(factionId); if (fact != nullptr) { - ret += getMiscString(fact->mName.empty() ? factionId : fact->mName, "Owner Faction"); + ret += getMiscString(fact->mName.empty() ? factionId.getRefIdString() : fact->mName, "Owner Faction"); if (cellref.getFactionRank() >= 0) { int rank = cellref.getFactionRank(); - const std::string rankName = fact->mRanks[rank]; + const std::string& rankName = fact->mRanks[rank]; if (rankName.empty()) ret += getValueString(cellref.getFactionRank(), "Rank"); else @@ -685,15 +711,16 @@ namespace MWGui } } - std::vector > itemOwners = - MWBase::Environment::get().getMechanicsManager()->getStolenItemOwners(cellref.getRefId()); + std::vector> itemOwners + = MWBase::Environment::get().getMechanicsManager()->getStolenItemOwners(cellref.getRefId()); - for (std::pair& owner : itemOwners) + for (std::pair& owner : itemOwners) { if (owner.second == std::numeric_limits::max()) - ret += std::string("\nStolen from ") + owner.first; // for legacy (ESS) savegames + ret += std::string("\nStolen from ") + owner.first.toDebugString(); // for legacy (ESS) savegames else - ret += std::string("\nStolen ") + MyGUI::utility::toString(owner.second) + " from " + owner.first; + ret += std::string("\nStolen ") + MyGUI::utility::toString(owner.second) + " from " + + owner.first.toDebugString(); } ret += getMiscString(cellref.getGlobalVariable(), "Global"); @@ -702,12 +729,14 @@ namespace MWGui std::string ToolTips::getDurationString(float duration, const std::string& prefix) { + auto l10n = MWBase::Environment::get().getL10nManager()->getContext("Interface"); + std::string ret; ret = prefix + ": "; if (duration < 1.f) { - ret += "0 s"; + ret += l10n->formatMessage("DurationSecond", { "seconds" }, { 0 }); return ret; } @@ -720,36 +749,37 @@ namespace MWGui int units = 0; int years = fullDuration / secondsPerYear; int months = fullDuration % secondsPerYear / secondsPerMonth; - int days = fullDuration % secondsPerYear % secondsPerMonth / secondsPerDay; // Because a year is not exactly 12 "months" + int days = fullDuration % secondsPerYear % secondsPerMonth + / secondsPerDay; // Because a year is not exactly 12 "months" int hours = fullDuration % secondsPerDay / secondsPerHour; int minutes = fullDuration % secondsPerHour / secondsPerMinute; int seconds = fullDuration % secondsPerMinute; if (years) { units++; - ret += toString(years) + " y "; + ret += l10n->formatMessage("DurationYear", { "years" }, { years }); } if (months) { units++; - ret += toString(months) + " mo "; + ret += l10n->formatMessage("DurationMonth", { "months" }, { months }); } if (units < 2 && days) { units++; - ret += toString(days) + " d "; + ret += l10n->formatMessage("DurationDay", { "days" }, { days }); } if (units < 2 && hours) { units++; - ret += toString(hours) + " h "; + ret += l10n->formatMessage("DurationHour", { "hours" }, { hours }); } if (units >= 2) return ret; if (minutes) - ret += toString(minutes) + " min "; + ret += l10n->formatMessage("DurationMinute", { "minutes" }, { minutes }); if (seconds) - ret += toString(seconds) + " s "; + ret += l10n->formatMessage("DurationSecond", { "seconds" }, { seconds }); return ret; } @@ -771,45 +801,37 @@ namespace MWGui mFocusToolTipY = min_y; } - void ToolTips::createSkillToolTip(MyGUI::Widget* widget, int skillId) + void ToolTips::createSkillToolTip(MyGUI::Widget* widget, ESM::RefId skillId) { - if (skillId == -1) + if (skillId.empty()) return; - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); - - const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const ESM::Skill* skill = store.get().find(skillId); - assert(skill); - - const ESM::Attribute* attr = - store.get().find(skill->mData.mAttribute); - assert(attr); - std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; + const ESM::Attribute* attr = store.get().find(skill->mData.mAttribute); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "SkillNoProgressToolTip"); - widget->setUserString("Caption_SkillNoProgressName", "#{"+skillNameId+"}"); + widget->setUserString("Caption_SkillNoProgressName", MyGUI::TextIterator::toTagsString(skill->mName)); widget->setUserString("Caption_SkillNoProgressDescription", skill->mDescription); - widget->setUserString("Caption_SkillNoProgressAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}"); - widget->setUserString("ImageTexture_SkillNoProgressImage", icon); + widget->setUserString("Caption_SkillNoProgressAttribute", + "#{sGoverningAttribute}: " + MyGUI::TextIterator::toTagsString(attr->mName)); + widget->setUserString("ImageTexture_SkillNoProgressImage", skill->mIcon); } - void ToolTips::createAttributeToolTip(MyGUI::Widget* widget, int attributeId) + void ToolTips::createAttributeToolTip(MyGUI::Widget* widget, ESM::Attribute::AttributeID attributeId) { - if (attributeId == -1) + const ESM::Attribute* attribute + = MWBase::Environment::get().getESMStore()->get().search(attributeId); + if (!attribute) return; - std::string icon = ESM::Attribute::sAttributeIcons[attributeId]; - std::string name = ESM::Attribute::sGmstAttributeIds[attributeId]; - std::string desc = ESM::Attribute::sGmstAttributeDescIds[attributeId]; - widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "AttributeToolTip"); - widget->setUserString("Caption_AttributeName", "#{"+name+"}"); - widget->setUserString("Caption_AttributeDescription", "#{"+desc+"}"); - widget->setUserString("ImageTexture_AttributeImage", icon); + widget->setUserString("Caption_AttributeName", MyGUI::TextIterator::toTagsString(attribute->mName)); + widget->setUserString( + "Caption_AttributeDescription", MyGUI::TextIterator::toTagsString(attribute->mDescription)); + widget->setUserString("ImageTexture_AttributeImage", attribute->mIcon); } void ToolTips::createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId) @@ -817,20 +839,19 @@ namespace MWGui widget->setUserString("Caption_Caption", name); std::string specText; // get all skills of this specialisation - const MWWorld::Store &skills = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& skills = MWBase::Environment::get().getESMStore()->get(); bool isFirst = true; - for (auto& skillPair : skills) + for (const auto& skill : skills) { - if (skillPair.second.mData.mSpecialization == specId) + if (skill.mData.mSpecialization == specId) { if (isFirst) isFirst = false; else specText += "\n"; - specText += std::string("#{") + ESM::Skill::sSkillNameIds[skillPair.first] + "}"; + specText += MyGUI::TextIterator::toTagsString(skill.mName); } } widget->setUserString("Caption_ColumnText", specText); @@ -838,26 +859,24 @@ namespace MWGui widget->setUserString("ToolTipType", "Layout"); } - void ToolTips::createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId) + void ToolTips::createBirthsignToolTip(MyGUI::Widget* widget, const ESM::RefId& birthsignId) { - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const ESM::BirthSign *sign = store.get().find(birthsignId); + const ESM::BirthSign* sign = store.get().find(birthsignId); + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "BirthSignToolTip"); - widget->setUserString("ImageTexture_BirthSignImage", MWBase::Environment::get().getWindowManager()->correctTexturePath(sign->mTexture)); - std::string text; + widget->setUserString( + "ImageTexture_BirthSignImage", Misc::ResourceHelpers::correctTexturePath(sign->mTexture, vfs)); + std::string text = sign->mName + "\n#{fontcolourhtml=normal}" + sign->mDescription; - text += sign->mName; - text += "\n#{fontcolourhtml=normal}" + sign->mDescription; + std::vector abilities, powers, spells; - std::vector abilities, powers, spells; - - for (const std::string& spellId : sign->mPowers.mList) + for (const ESM::RefId& spellId : sign->mPowers.mList) { - const ESM::Spell *spell = store.get().search(spellId); + const ESM::Spell* spell = store.get().search(spellId); if (!spell) continue; // Skip spells which cannot be found ESM::Spell::SpellType type = static_cast(spell->mData.mType); @@ -865,35 +884,28 @@ namespace MWGui continue; // We only want spell, ability and powers. if (type == ESM::Spell::ST_Ability) - abilities.push_back(spellId); + abilities.push_back(spell); else if (type == ESM::Spell::ST_Power) - powers.push_back(spellId); + powers.push_back(spell); else if (type == ESM::Spell::ST_Spell) - spells.push_back(spellId); + spells.push_back(spell); } - struct { - const std::vector &spells; - std::string label; - } - categories[3] = { - {abilities, "sBirthsignmenu1"}, - {powers, "sPowers"}, - {spells, "sBirthsignmenu2"} - }; - - for (int category = 0; category < 3; ++category) + using Category = std::pair&, std::string_view>; + for (const auto& [category, label] : std::initializer_list{ + { abilities, "sBirthsignmenu1" }, { powers, "sPowers" }, { spells, "sBirthsignmenu2" } }) { bool addHeader = true; - for (const std::string& spellId : categories[category].spells) + for (const ESM::Spell* spell : category) { if (addHeader) { - text += std::string("\n\n#{fontcolourhtml=header}") + std::string("#{") + categories[category].label + "}"; + text += "\n\n#{fontcolourhtml=header}#{"; + text += label; + text += '}'; addHeader = false; } - const ESM::Spell *spell = store.get().find(spellId); text += "\n#{fontcolourhtml=normal}" + spell->mName; } } @@ -911,17 +923,13 @@ namespace MWGui void ToolTips::createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass) { - if (playerClass.mName == "") + if (playerClass.mName.empty()) return; int spec = playerClass.mData.mSpecialization; - std::string specStr; - if (spec == 0) - specStr = "#{sSpecializationCombat}"; - else if (spec == 1) - specStr = "#{sSpecializationMagic}"; - else if (spec == 2) - specStr = "#{sSpecializationStealth}"; + std::string specStr = "#{"; + specStr += ESM::Class::sGmstSpecializationIds[spec]; + specStr += '}'; widget->setUserString("Caption_ClassName", playerClass.mName); widget->setUserString("Caption_ClassDescription", playerClass.mDescription); @@ -932,20 +940,24 @@ namespace MWGui void ToolTips::createMagicEffectToolTip(MyGUI::Widget* widget, short id) { - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(id); - const std::string &name = ESM::MagicEffect::effectIdToString (id); + const auto& store = MWBase::Environment::get().getESMStore(); + const ESM::MagicEffect* effect = store->get().find(id); + const std::string& name = ESM::MagicEffect::indexToGmstString(id); std::string icon = effect->mIcon; int slashPos = icon.rfind('\\'); - icon.insert(slashPos+1, "b_"); - icon = MWBase::Environment::get().getWindowManager()->correctIconPath(icon); + icon.insert(slashPos + 1, "b_"); + icon = Misc::ResourceHelpers::correctIconPath(icon, MWBase::Environment::get().getResourceSystem()->getVFS()); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "MagicEffectToolTip"); widget->setUserString("Caption_MagicEffectName", "#{" + name + "}"); widget->setUserString("Caption_MagicEffectDescription", effect->mDescription); - widget->setUserString("Caption_MagicEffectSchool", "#{sSchool}: " + sSchoolNames[effect->mData.mSchool]); + widget->setUserString("Caption_MagicEffectSchool", + "#{sSchool}: " + + MyGUI::TextIterator::toTagsString( + store->get().find(effect->mData.mSchool)->mSchool->mName) + .asUTF8()); widget->setUserString("ImageTexture_MagicEffectImage", icon); } diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index d7bb87bdb..aa03c8a31 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -1,8 +1,8 @@ #ifndef MWGUI_TOOLTIPS_H #define MWGUI_TOOLTIPS_H -#include "layout.hpp" #include "../mwworld/ptr.hpp" +#include "layout.hpp" #include "widgets.hpp" @@ -24,7 +24,8 @@ namespace MWGui , isPotion(false) , isIngredient(false) , wordWrap(true) - {} + { + } std::string caption; std::string text; @@ -32,7 +33,7 @@ namespace MWGui int imageSize; // enchantment (for cloth, armor, weapons) - std::string enchant; + ESM::RefId enchant; int remainingEnchantCharge; // effects (for potions, ingredients) @@ -87,28 +88,28 @@ namespace MWGui static std::string getCellRefString(const MWWorld::CellRef& cellref); ///< Returns a string containing debug tooltip information about the given cellref. - static std::string getDurationString (float duration, const std::string& prefix); + static std::string getDurationString(float duration, const std::string& prefix); ///< Returns duration as two largest time units, rounded down. Note: not localized; no line break. // these do not create an actual tooltip, but they fill in the data that is required so the tooltip // system knows what to show in case this widget is hovered - static void createSkillToolTip(MyGUI::Widget* widget, int skillId); - static void createAttributeToolTip(MyGUI::Widget* widget, int attributeId); + static void createSkillToolTip(MyGUI::Widget* widget, ESM::RefId skillId); + static void createAttributeToolTip(MyGUI::Widget* widget, ESM::Attribute::AttributeID attributeId); static void createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId); - static void createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId); + static void createBirthsignToolTip(MyGUI::Widget* widget, const ESM::RefId& birthsignId); static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace); static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass); static void createMagicEffectToolTip(MyGUI::Widget* widget, short id); - + bool checkOwned(); /// Returns True if taking mFocusObject would be crime - + private: MyGUI::Widget* mDynamicToolTipBox; MWWorld::Ptr mFocusObject; - MyGUI::IntSize getToolTipViaPtr (int count, bool image = true, bool isOwned = false); + MyGUI::IntSize getToolTipViaPtr(int count, bool image = true, bool isOwned = false); ///< @return requested tooltip size MyGUI::IntSize createToolTip(const ToolTipInfo& info, bool isOwned = false); @@ -121,11 +122,8 @@ namespace MWGui /// Adjust position for a tooltip so that it doesn't leave the screen and does not obscure the mouse cursor void position(MyGUI::IntPoint& position, MyGUI::IntSize size, MyGUI::IntSize viewportSize); - static std::string sSchoolNames[6]; - int mHorizontalScrollIndex; - float mDelay; float mRemainingDelay; // remaining time until tooltip will show @@ -135,8 +133,6 @@ namespace MWGui bool mEnabled; bool mFullHelp; - - int mShowOwned; float mFrameDuration; }; diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index f5144ba81..f14d7fe72 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -1,7 +1,7 @@ #include "tradeitemmodel.hpp" -#include -#include +#include +#include #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -10,10 +10,10 @@ namespace MWGui { - TradeItemModel::TradeItemModel(ItemModel *sourceModel, const MWWorld::Ptr& merchant) + TradeItemModel::TradeItemModel(std::unique_ptr sourceModel, const MWWorld::Ptr& merchant) : mMerchant(merchant) { - mSourceModel = sourceModel; + mSourceModel = std::move(sourceModel); } bool TradeItemModel::allowedToUseItems() const @@ -21,7 +21,7 @@ namespace MWGui return true; } - ItemStack TradeItemModel::getItem (ModelIndex index) + ItemStack TradeItemModel::getItem(ModelIndex index) { if (index < 0) throw std::runtime_error("Invalid index supplied"); @@ -35,7 +35,7 @@ namespace MWGui return mItems.size(); } - void TradeItemModel::borrowImpl(const ItemStack &item, std::vector &out) + void TradeItemModel::borrowImpl(const ItemStack& item, std::vector& out) { bool found = false; for (ItemStack& itemStack : out) @@ -51,7 +51,7 @@ namespace MWGui out.push_back(item); } - void TradeItemModel::unborrowImpl(const ItemStack &item, size_t count, std::vector &out) + void TradeItemModel::unborrowImpl(const ItemStack& item, size_t count, std::vector& out) { std::vector::iterator it = out.begin(); bool found = false; @@ -72,33 +72,33 @@ namespace MWGui throw std::runtime_error("Can't find borrowed item to return"); } - void TradeItemModel::borrowItemFromUs (ModelIndex itemIndex, size_t count) + void TradeItemModel::borrowItemFromUs(ModelIndex itemIndex, size_t count) { ItemStack item = getItem(itemIndex); item.mCount = count; borrowImpl(item, mBorrowedFromUs); } - void TradeItemModel::borrowItemToUs (ModelIndex itemIndex, ItemModel* source, size_t count) + void TradeItemModel::borrowItemToUs(ModelIndex itemIndex, ItemModel* source, size_t count) { ItemStack item = source->getItem(itemIndex); item.mCount = count; borrowImpl(item, mBorrowedToUs); } - void TradeItemModel::returnItemBorrowedToUs (ModelIndex itemIndex, size_t count) + void TradeItemModel::returnItemBorrowedToUs(ModelIndex itemIndex, size_t count) { ItemStack item = getItem(itemIndex); unborrowImpl(item, count, mBorrowedToUs); } - void TradeItemModel::returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count) + void TradeItemModel::returnItemBorrowedFromUs(ModelIndex itemIndex, ItemModel* source, size_t count) { ItemStack item = source->getItem(itemIndex); unborrowImpl(item, count, mBorrowedFromUs); } - void TradeItemModel::adjustEncumbrance(float &encumbrance) + void TradeItemModel::adjustEncumbrance(float& encumbrance) { for (ItemStack& itemStack : mBorrowedToUs) { @@ -130,8 +130,8 @@ namespace MWGui { // get index in the source model ItemModel* sourceModel = itemStack.mCreator; - size_t i=0; - for (; igetItemCount(); ++i) + size_t i = 0; + for (; i < sourceModel->getItemCount(); ++i) { if (itemStack.mBase == sourceModel->getItem(i).mBase) break; @@ -140,9 +140,8 @@ namespace MWGui throw std::runtime_error("The borrowed item disappeared"); const ItemStack& item = sourceModel->getItem(i); - static const bool prevent = Settings::Manager::getBool("prevent merchant equipping", "Game"); // copy the borrowed items to our model - copyItem(item, itemStack.mCount, !prevent); + copyItem(item, itemStack.mCount, !Settings::game().mPreventMerchantEquipping); // then remove them from the source model sourceModel->removeItem(item, itemStack.mCount); } @@ -160,19 +159,19 @@ namespace MWGui mItems.clear(); // add regular items - for (size_t i=0; igetItemCount(); ++i) + for (size_t i = 0; i < mSourceModel->getItemCount(); ++i) { ItemStack item = mSourceModel->getItem(i); - if(!mMerchant.isEmpty()) + if (!mMerchant.isEmpty()) { MWWorld::Ptr base = item.mBase; - if(Misc::StringUtils::ciEqual(base.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) + if (base.getCellRef().getRefId() == MWWorld::ContainerStore::sGoldId) continue; if (!base.getClass().showsInInventory(base)) return; - if(!base.getClass().canSell(base, services)) + if (!base.getClass().canSell(base, services)) continue; // Bound items may not be bought @@ -180,7 +179,7 @@ namespace MWGui continue; // don't show equipped items - if(mMerchant.getClass().hasInventoryStore(mMerchant)) + if (mMerchant.getClass().hasInventoryStore(mMerchant)) { MWWorld::InventoryStore& store = mMerchant.getClass().getInventoryStore(mMerchant); if (store.isEquipped(base)) diff --git a/apps/openmw/mwgui/tradeitemmodel.hpp b/apps/openmw/mwgui/tradeitemmodel.hpp index 53b616aed..d395744d2 100644 --- a/apps/openmw/mwgui/tradeitemmodel.hpp +++ b/apps/openmw/mwgui/tradeitemmodel.hpp @@ -13,32 +13,32 @@ namespace MWGui class TradeItemModel : public ProxyItemModel { public: - TradeItemModel (ItemModel* sourceModel, const MWWorld::Ptr& merchant); + TradeItemModel(std::unique_ptr sourceModel, const MWWorld::Ptr& merchant); bool allowedToUseItems() const override; - ItemStack getItem (ModelIndex index) override; + ItemStack getItem(ModelIndex index) override; size_t getItemCount() override; void update() override; - void borrowItemFromUs (ModelIndex itemIndex, size_t count); + void borrowItemFromUs(ModelIndex itemIndex, size_t count); - void borrowItemToUs (ModelIndex itemIndex, ItemModel* source, size_t count); + void borrowItemToUs(ModelIndex itemIndex, ItemModel* source, size_t count); ///< @note itemIndex points to an item in \a source - void returnItemBorrowedToUs (ModelIndex itemIndex, size_t count); + void returnItemBorrowedToUs(ModelIndex itemIndex, size_t count); - void returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count); + void returnItemBorrowedFromUs(ModelIndex itemIndex, ItemModel* source, size_t count); /// Permanently transfers items that were borrowed to us from another model to this model - void transferItems (); + void transferItems(); /// Aborts trade void abort(); /// Adjusts the given encumbrance by adding weight for items that have been lent to us, /// and removing weight for items we've lent to someone else. - void adjustEncumbrance (float& encumbrance); + void adjustEncumbrance(float& encumbrance); const std::vector getItemsBorrowedToUs() const; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 422b17186..b57b8db6a 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -1,12 +1,14 @@ #include "tradewindow.hpp" #include -#include #include #include +#include +#include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -23,7 +25,13 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -32,18 +40,18 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "containeritemmodel.hpp" +#include "countdialog.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" #include "sortfilteritemmodel.hpp" -#include "containeritemmodel.hpp" -#include "tradeitemmodel.hpp" -#include "countdialog.hpp" #include "tooltips.hpp" +#include "tradeitemmodel.hpp" namespace { - int getEffectiveValue (MWWorld::Ptr item, int count) + int getEffectiveValue(MWWorld::Ptr item, int count) { float price = static_cast(item.getClass().getValue(item)); if (item.getClass().hasItemHealth(item)) @@ -105,7 +113,8 @@ namespace MWGui mTotalBalance->eventValueChanged += MyGUI::newDelegate(this, &TradeWindow::onBalanceValueChanged); mTotalBalance->eventEditSelectAccept += MyGUI::newDelegate(this, &TradeWindow::onAccept); - mTotalBalance->setMinValue(std::numeric_limits::min()+1); // disallow INT_MIN since abs(INT_MIN) is undefined + mTotalBalance->setMinValue( + std::numeric_limits::min() + 1); // disallow INT_MIN since abs(INT_MIN) is undefined setCoord(400, 0, 400, 300); } @@ -125,9 +134,12 @@ namespace MWGui std::vector worldItems; MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems); - mTradeModel = new TradeItemModel(new ContainerItemModel(itemSources, worldItems), mPtr); - mSortModel = new SortFilterItemModel(mTradeModel); - mItemView->setModel (mSortModel); + auto tradeModel + = std::make_unique(std::make_unique(itemSources, worldItems), mPtr); + mTradeModel = tradeModel.get(); + auto sortModel = std::make_unique(std::move(tradeModel)); + mSortModel = sortModel.get(); + mItemView->setModel(std::move(sortModel)); mItemView->resetScrollBars(); updateLabels(); @@ -135,7 +147,7 @@ namespace MWGui setTitle(actor.getClass().getName(actor)); onFilterChanged(mFilterAll); - mFilterEdit->setCaption(""); + mFilterEdit->setCaption({}); } void TradeWindow::onFrame(float dt) @@ -185,7 +197,7 @@ namespace MWGui return true; } - void TradeWindow::onItemSelected (int index) + void TradeWindow::onItemSelected(int index) { const ItemStack& item = mSortModel->getItem(index); @@ -199,7 +211,8 @@ namespace MWGui { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = "#{sQuanityMenuMessage02}"; - std::string name = object.getClass().getName(object) + MWGui::ToolTips::getSoulString(object.getCellRef()); + std::string name{ object.getClass().getName(object) }; + name += MWGui::ToolTips::getSoulString(object.getCellRef()); dialog->openCountDialog(name, message, count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &TradeWindow::sellItem); @@ -208,17 +221,18 @@ namespace MWGui else { mItemToSell = mSortModel->mapToSource(index); - sellItem (nullptr, count); + sellItem(nullptr, count); } } void TradeWindow::sellItem(MyGUI::Widget* sender, int count) { const ItemStack& item = mTradeModel->getItem(mItemToSell); - std::string sound = item.mBase.getClass().getUpSoundId(item.mBase); + const ESM::RefId& sound = item.mBase.getClass().getUpSoundId(item.mBase); MWBase::Environment::get().getWindowManager()->playSound(sound); - TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + TradeItemModel* playerTradeModel + = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); if (item.mType == ItemStack::Type_Barter) { @@ -239,17 +253,19 @@ namespace MWGui mItemView->update(); } - void TradeWindow::borrowItem (int index, size_t count) + void TradeWindow::borrowItem(int index, size_t count) { - TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + TradeItemModel* playerTradeModel + = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); mTradeModel->borrowItemToUs(index, playerTradeModel, count); mItemView->update(); sellToNpc(playerTradeModel->getItem(index).mBase, count, false); } - void TradeWindow::returnItem (int index, size_t count) + void TradeWindow::returnItem(int index, size_t count) { - TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + TradeItemModel* playerTradeModel + = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); const ItemStack& item = playerTradeModel->getItem(index); mTradeModel->returnItemBorrowedFromUs(index, playerTradeModel, count); mItemView->update(); @@ -262,20 +278,24 @@ namespace MWGui if (amount > 0) { - store.add(MWWorld::ContainerStore::sGoldId, amount, actor); + store.add(MWWorld::ContainerStore::sGoldId, amount); } else { - store.remove(MWWorld::ContainerStore::sGoldId, - amount, actor); + store.remove(MWWorld::ContainerStore::sGoldId, -amount); } } void TradeWindow::onOfferButtonClicked(MyGUI::Widget* _sender) { - TradeItemModel* playerItemModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + TradeItemModel* playerItemModel + = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + + if (mTotalBalance->getValue() == 0) + mCurrentBalance = 0; // were there any items traded at all? const std::vector& playerBought = playerItemModel->getItemsBorrowedToUs(); @@ -283,8 +303,7 @@ namespace MWGui if (playerBought.empty() && merchantBought.empty()) { // user notification - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sBarterDialog11}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog11}"); return; } @@ -295,8 +314,7 @@ namespace MWGui if (mCurrentBalance < 0 && playerGold < std::abs(mCurrentBalance)) { // user notification - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sBarterDialog1}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog1}"); return; } @@ -304,21 +322,22 @@ namespace MWGui if (mCurrentBalance > 0 && getMerchantGold() < mCurrentBalance) { // user notification - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sBarterDialog2}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog2}"); return; } // check if the player is attempting to sell back an item stolen from this actor for (const ItemStack& itemStack : merchantBought) { - if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(itemStack.mBase.getCellRef().getRefId(), mPtr)) + if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom( + itemStack.mBase.getCellRef().getRefId(), mPtr)) { std::string msg = gmst.find("sNotifyMessage49")->mValue.getString(); msg = Misc::StringUtils::format(msg, itemStack.mBase.getClass().getName(itemStack.mBase)); MWBase::Environment::get().getWindowManager()->messageBox(msg); - MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, itemStack.mBase, mPtr, itemStack.mCount); + MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner( + player, itemStack.mBase, mPtr, itemStack.mCount); onCancelButtonClicked(mCancelButton); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); @@ -329,18 +348,18 @@ namespace MWGui bool offerAccepted = mTrading.haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); // apply disposition change if merchant is NPC - if ( mPtr.getClass().isNpc() ) { - int dispositionDelta = offerAccepted - ? gmst.find("iBarterSuccessDisposition")->mValue.getInteger() - : gmst.find("iBarterFailDisposition")->mValue.getInteger(); + if (mPtr.getClass().isNpc()) + { + int dispositionDelta = offerAccepted ? gmst.find("iBarterSuccessDisposition")->mValue.getInteger() + : gmst.find("iBarterFailDisposition")->mValue.getInteger(); MWBase::Environment::get().getDialogueManager()->applyBarterDispositionChange(dispositionDelta); } // display message on haggle failure - if ( !offerAccepted ) { - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sNotifyMessage9}"); + if (!offerAccepted) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage9}"); return; } @@ -352,6 +371,7 @@ namespace MWGui if (mCurrentBalance != 0) { addOrRemoveGold(mCurrentBalance, player); +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -371,15 +391,19 @@ namespace MWGui /* End of tes3mp change (major) */ +======= + mPtr.getClass().getCreatureStats(mPtr).setGoldPool( + mPtr.getClass().getCreatureStats(mPtr).getGoldPool() - mCurrentBalance); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } eventTradeDone(); - MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Item Gold Up")); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); } - void TradeWindow::onAccept(MyGUI::EditBox *sender) + void TradeWindow::onAccept(MyGUI::EditBox* sender) { onOfferButtonClicked(sender); @@ -399,9 +423,10 @@ namespace MWGui updateLabels(); } - void TradeWindow::addRepeatController(MyGUI::Widget *widget) + void TradeWindow::addRepeatController(MyGUI::Widget* widget) { - MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MyGUI::ControllerRepeatClick::getClassTypeName()); + MyGUI::ControllerItem* item + = MyGUI::ControllerManager::getInstance().createItem(MyGUI::ControllerRepeatClick::getClassTypeName()); MyGUI::ControllerRepeatClick* controller = static_cast(item); controller->eventRepeatClick += newDelegate(this, &TradeWindow::onRepeatClick); MyGUI::ControllerManager::getInstance().addItem(widget, controller); @@ -427,17 +452,22 @@ namespace MWGui onDecreaseButtonTriggered(); } - void TradeWindow::onBalanceButtonReleased(MyGUI::Widget *_sender, int _left, int _top, MyGUI::MouseButton _id) + void TradeWindow::onBalanceButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { MyGUI::ControllerManager::getInstance().removeItem(_sender); } void TradeWindow::onBalanceValueChanged(int value) { + int previousBalance = mCurrentBalance; + // Entering a "-" sign inverts the buying/selling state mCurrentBalance = (mCurrentBalance >= 0 ? 1 : -1) * value; updateLabels(); + if (mCurrentBalance == 0) + mCurrentBalance = previousBalance; + if (value != std::abs(value)) mTotalBalance->setValue(std::abs(value)); } @@ -445,17 +475,26 @@ namespace MWGui void TradeWindow::onIncreaseButtonTriggered() { // prevent overflows, and prevent entering INT_MIN since abs(INT_MIN) is undefined - if (mCurrentBalance == std::numeric_limits::max() || mCurrentBalance == std::numeric_limits::min()+1) + if (mCurrentBalance == std::numeric_limits::max() + || mCurrentBalance == std::numeric_limits::min() + 1) return; - if (mCurrentBalance < 0) mCurrentBalance -= 1; - else mCurrentBalance += 1; + if (mTotalBalance->getValue() == 0) + mCurrentBalance = 0; + if (mCurrentBalance < 0) + mCurrentBalance -= 1; + else + mCurrentBalance += 1; updateLabels(); } void TradeWindow::onDecreaseButtonTriggered() { - if (mCurrentBalance < 0) mCurrentBalance += 1; - else mCurrentBalance -= 1; + if (mTotalBalance->getValue() == 0) + mCurrentBalance = 0; + if (mCurrentBalance < 0) + mCurrentBalance += 1; + else + mCurrentBalance -= 1; updateLabels(); } @@ -463,9 +502,18 @@ namespace MWGui { MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + MyGUI::utility::toString(playerGold)); + TradeItemModel* playerTradeModel + = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + const std::vector& playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); + const std::vector& merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); + + if (playerBorrowed.empty() && merchantBorrowed.empty()) + { + mCurrentBalance = 0; + } + if (mCurrentBalance < 0) { mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalCost}"); @@ -482,7 +530,8 @@ namespace MWGui void TradeWindow::updateOffer() { - TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + TradeItemModel* playerTradeModel + = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); int merchantOffer = 0; @@ -494,8 +543,10 @@ namespace MWGui for (const ItemStack& itemStack : playerBorrowed) { const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); - const int cap = static_cast(std::max(1.f, 0.75f * basePrice)); // Minimum buying price -- 75% of the base - const int buyingPrice = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, true); + const int cap + = static_cast(std::max(1.f, 0.75f * basePrice)); // Minimum buying price -- 75% of the base + const int buyingPrice + = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, true); merchantOffer -= std::max(cap, buyingPrice); } @@ -503,8 +554,10 @@ namespace MWGui for (const ItemStack& itemStack : merchantBorrowed) { const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); - const int cap = static_cast(std::max(1.f, 0.75f * basePrice)); // Maximum selling price -- 75% of the base - const int sellingPrice = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, false); + const int cap + = static_cast(std::max(1.f, 0.75f * basePrice)); // Maximum selling price -- 75% of the base + const int sellingPrice + = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, false); merchantOffer += mPtr.getClass().isNpc() ? std::min(cap, sellingPrice) : sellingPrice; } @@ -526,7 +579,8 @@ namespace MWGui void TradeWindow::onReferenceUnavailable() { - // remove both Trade and Dialogue (since you always trade with the NPC/creature that you have previously talked to) + // remove both Trade and Dialogue (since you always trade with the NPC/creature that you have previously talked + // to) MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } @@ -552,4 +606,10 @@ namespace MWGui return; resetReference(); } + + void TradeWindow::onDeleteCustomData(const MWWorld::Ptr& ptr) + { + if (mTradeModel && mTradeModel->usesContainer(ptr)) + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); + } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index f82d7b0f7..62a51be0d 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -24,94 +24,98 @@ namespace MWGui class TradeWindow : public WindowBase, public ReferenceInterface { - public: - TradeWindow(); + public: + TradeWindow(); - void setPtr(const MWWorld::Ptr& actor) override; + void setPtr(const MWWorld::Ptr& actor) override; - void onClose() override; - void onFrame(float dt) override; - void clear() override { resetReference(); } + void onClose() override; + void onFrame(float dt) override; + void clear() override { resetReference(); } - void borrowItem (int index, size_t count); - void returnItem (int index, size_t count); + void borrowItem(int index, size_t count); + void returnItem(int index, size_t count); - int getMerchantServices(); + int getMerchantServices(); - bool exit() override; + bool exit() override; - void resetReference() override; + void resetReference() override; - typedef MyGUI::delegates::CMultiDelegate0 EventHandle_TradeDone; - EventHandle_TradeDone eventTradeDone; + void onDeleteCustomData(const MWWorld::Ptr& ptr) override; - private: - ItemView* mItemView; - SortFilterItemModel* mSortModel; - TradeItemModel* mTradeModel; - MWMechanics::Trading mTrading; + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_TradeDone; + EventHandle_TradeDone eventTradeDone; - static const float sBalanceChangeInitialPause; // in seconds - static const float sBalanceChangeInterval; // in seconds + private: + ItemView* mItemView; + SortFilterItemModel* mSortModel; + TradeItemModel* mTradeModel; + MWMechanics::Trading mTrading; - MyGUI::Button* mFilterAll; - MyGUI::Button* mFilterWeapon; - MyGUI::Button* mFilterApparel; - MyGUI::Button* mFilterMagic; - MyGUI::Button* mFilterMisc; + static const float sBalanceChangeInitialPause; // in seconds + static const float sBalanceChangeInterval; // in seconds - MyGUI::EditBox* mFilterEdit; + MyGUI::Button* mFilterAll; + MyGUI::Button* mFilterWeapon; + MyGUI::Button* mFilterApparel; + MyGUI::Button* mFilterMagic; + MyGUI::Button* mFilterMisc; - MyGUI::Button* mIncreaseButton; - MyGUI::Button* mDecreaseButton; - MyGUI::TextBox* mTotalBalanceLabel; - Gui::NumericEditBox* mTotalBalance; + MyGUI::EditBox* mFilterEdit; - MyGUI::Widget* mBottomPane; + MyGUI::Button* mIncreaseButton; + MyGUI::Button* mDecreaseButton; + MyGUI::TextBox* mTotalBalanceLabel; + Gui::NumericEditBox* mTotalBalance; - MyGUI::Button* mMaxSaleButton; - MyGUI::Button* mCancelButton; - MyGUI::Button* mOfferButton; - MyGUI::TextBox* mPlayerGold; - MyGUI::TextBox* mMerchantGold; + MyGUI::Widget* mBottomPane; - int mItemToSell; + MyGUI::Button* mMaxSaleButton; + MyGUI::Button* mCancelButton; + MyGUI::Button* mOfferButton; + MyGUI::TextBox* mPlayerGold; + MyGUI::TextBox* mMerchantGold; - int mCurrentBalance; - int mCurrentMerchantOffer; + int mItemToSell; - void sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance - void buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem); ///< only used for adjusting the gold balance + int mCurrentBalance; + int mCurrentMerchantOffer; - void updateOffer(); + void sellToNpc( + const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance + void buyFromNpc( + const MWWorld::Ptr& item, int count, bool soldItem); ///< only used for adjusting the gold balance - void onItemSelected (int index); - void sellItem (MyGUI::Widget* sender, int count); + void updateOffer(); - void onFilterChanged(MyGUI::Widget* _sender); - void onNameFilterChanged(MyGUI::EditBox* _sender); - void onOfferButtonClicked(MyGUI::Widget* _sender); - void onAccept(MyGUI::EditBox* sender); - void onCancelButtonClicked(MyGUI::Widget* _sender); - void onMaxSaleButtonClicked(MyGUI::Widget* _sender); - void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - void onBalanceButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - void onBalanceValueChanged(int value); - void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller); + void onItemSelected(int index); + void sellItem(MyGUI::Widget* sender, int count); - void addRepeatController(MyGUI::Widget* widget); + void onFilterChanged(MyGUI::Widget* _sender); + void onNameFilterChanged(MyGUI::EditBox* _sender); + void onOfferButtonClicked(MyGUI::Widget* _sender); + void onAccept(MyGUI::EditBox* sender); + void onCancelButtonClicked(MyGUI::Widget* _sender); + void onMaxSaleButtonClicked(MyGUI::Widget* _sender); + void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); + void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); + void onBalanceButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); + void onBalanceValueChanged(int value); + void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller); - void onIncreaseButtonTriggered(); - void onDecreaseButtonTriggered(); + void addRepeatController(MyGUI::Widget* widget); - void addOrRemoveGold(int gold, const MWWorld::Ptr& actor); + void onIncreaseButtonTriggered(); + void onDecreaseButtonTriggered(); - void updateLabels(); + void addOrRemoveGold(int gold, const MWWorld::Ptr& actor); - void onReferenceUnavailable() override; + void updateLabels(); - int getMerchantGold(); + void onReferenceUnavailable() override; + + int getMerchantGold(); }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index e055614d3..9cc1d0fc9 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -1,7 +1,10 @@ #include "trainingwindow.hpp" +#include #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -15,46 +18,31 @@ */ #include "../mwbase/windowmanager.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" -#include +#include +#include #include "tooltips.hpp" -namespace -{ -// Sorts a container descending by skill value. If skill value is equal, sorts ascending by skill ID. -// pair -bool sortSkills (const std::pair& left, const std::pair& right) -{ - if (left == right) - return false; - - if (left.second > right.second) - return true; - else if (left.second < right.second) - return false; - - return left.first < right.first; -} -} - namespace MWGui { TrainingWindow::TrainingWindow() : WindowBase("openmw_trainingwindow.layout") , mTimeAdvancer(0.05f) - , mTrainingSkillBasedOnBaseSkill(Settings::Manager::getBool("trainers training skills based on base skill", "Game")) { getWidget(mTrainingOptions, "TrainingOptions"); getWidget(mCancelButton, "CancelButton"); @@ -79,7 +67,7 @@ namespace MWGui center(); } - void TrainingWindow::setPtr (const MWWorld::Ptr& actor) + void TrainingWindow::setPtr(const MWWorld::Ptr& actor) { mPtr = actor; @@ -88,98 +76,109 @@ namespace MWGui mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); + const auto& store = MWBase::Environment::get().getESMStore(); + const MWWorld::Store& gmst = store->get(); + const MWWorld::Store& skillStore = store->get(); + // NPC can train you in his best 3 skills - std::vector< std::pair > skills; + std::vector> skills; MWMechanics::NpcStats const& actorStats(actor.getClass().getNpcStats(actor)); - for (int i=0; imId) < std::tie(left.second, right.first->mId); + }); - MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator (); - MyGUI::Gui::getInstance ().destroyWidgets (widgets); + MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator(); + MyGUI::Gui::getInstance().destroyWidgets(widgets); - MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) { - int price = static_cast(pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->mValue.getInteger()); + const ESM::Skill* skill = skills[i].first; + int price = static_cast( + pcStats.getSkill(skill->mId).getBase() * gmst.find("iTrainingMod")->mValue.getInteger()); price = std::max(1, price); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); - MyGUI::Button* button = mTrainingOptions->createWidget(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip - MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default); + MyGUI::Button* button = mTrainingOptions->createWidget(price <= playerGold + ? "SandTextButton" + : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip + MyGUI::IntCoord(5, 5 + i * lineHeight, mTrainingOptions->getWidth() - 10, lineHeight), + MyGUI::Align::Default); button->setUserData(skills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); - button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[skills[i].first] + "} - " + MyGUI::utility::toString(price)); + button->setCaptionWithReplacing( + MyGUI::TextIterator::toTagsString(skill->mName) + " - " + MyGUI::utility::toString(price)); - button->setSize(button->getTextSize ().width+12, button->getSize().height); + button->setSize(button->getTextSize().width + 12, button->getSize().height); - ToolTips::createSkillToolTip (button, skills[i].first); + ToolTips::createSkillToolTip(button, skill->mId); } center(); } - void TrainingWindow::onReferenceUnavailable () + void TrainingWindow::onReferenceUnavailable() { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Training); } - void TrainingWindow::onCancelButtonClicked (MyGUI::Widget *sender) + void TrainingWindow::onCancelButtonClicked(MyGUI::Widget* sender) { MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Training); } - void TrainingWindow::onTrainingSelected (MyGUI::Widget *sender) + void TrainingWindow::onTrainingSelected(MyGUI::Widget* sender) { - int skillId = *sender->getUserData(); + const ESM::Skill* skill = *sender->getUserData(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - int price = pcStats.getSkill (skillId).getBase() * store.get().find("iTrainingMod")->mValue.getInteger(); - price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); + int price = pcStats.getSkill(skill->mId).getBase() + * store.get().find("iTrainingMod")->mValue.getInteger(); + price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) return; - if (getSkillForTraining(mPtr.getClass().getNpcStats(mPtr), skillId) <= pcStats.getSkill(skillId).getBase()) + if (getSkillForTraining(mPtr.getClass().getNpcStats(mPtr), skill->mId) + <= pcStats.getSkill(skill->mId).getBase()) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sServiceTrainingWords}"); return; } // You can not train a skill above its governing attribute - const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillId); - if (pcStats.getSkill(skillId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase()) + if (pcStats.getSkill(skill->mId).getBase() + >= pcStats.getAttribute(ESM::Attribute::AttributeID(skill->mData.mAttribute)).getBase()) { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage17}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage17}"); return; } // increase skill - MWWorld::LiveCellRef *playerRef = player.get(); + MWWorld::LiveCellRef* playerRef = player.get(); - const ESM::Class *class_ = - store.get().find(playerRef->mBase->mClass); - pcStats.increaseSkill (skillId, *class_, true); + const ESM::Class* class_ = store.get().find(playerRef->mBase->mClass); + pcStats.increaseSkill(skill->mId, *class_, true); // remove gold - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); // add gold to NPC trading gold pool MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats(mPtr); @@ -201,6 +200,7 @@ namespace MWGui End of tes3mp change (major) */ +<<<<<<< HEAD // advance time MWBase::Environment::get().getMechanicsManager()->rest(2, false); @@ -214,13 +214,15 @@ namespace MWGui End of tes3mp change (major) */ +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 setVisible(false); mProgressBar.setVisible(true); mProgressBar.setProgress(0, 2); mTimeAdvancer.run(2); - MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.25); - MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.25, false, 0.25); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2, false, 0.2); } void TrainingWindow::onTrainingProgressChanged(int cur, int total) @@ -232,16 +234,20 @@ namespace MWGui { mProgressBar.setVisible(false); + // advance time + MWBase::Environment::get().getMechanicsManager()->rest(2, false); + MWBase::Environment::get().getWorld()->advanceTime(2); + // go back to game mode - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Training); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } - float TrainingWindow::getSkillForTraining(const MWMechanics::NpcStats& stats, int skillId) const + float TrainingWindow::getSkillForTraining(const MWMechanics::NpcStats& stats, ESM::RefId id) const { - if (mTrainingSkillBasedOnBaseSkill) - return stats.getSkill(skillId).getBase(); - return stats.getSkill(skillId).getModified(); + if (Settings::game().mTrainersTrainingSkillsBasedOnBaseSkill) + return stats.getSkill(id).getBase(); + return stats.getSkill(id).getModified(); } void TrainingWindow::onFrame(float dt) diff --git a/apps/openmw/mwgui/trainingwindow.hpp b/apps/openmw/mwgui/trainingwindow.hpp index 57fdd323a..70df7a1cd 100644 --- a/apps/openmw/mwgui/trainingwindow.hpp +++ b/apps/openmw/mwgui/trainingwindow.hpp @@ -1,10 +1,10 @@ #ifndef MWGUI_TRAININGWINDOW_H #define MWGUI_TRAININGWINDOW_H -#include "windowbase.hpp" #include "referenceinterface.hpp" #include "timeadvancer.hpp" #include "waitdialog.hpp" +#include "windowbase.hpp" namespace MWMechanics { @@ -34,7 +34,7 @@ namespace MWGui protected: void onReferenceUnavailable() override; - void onCancelButtonClicked (MyGUI::Widget* sender); + void onCancelButtonClicked(MyGUI::Widget* sender); void onTrainingSelected(MyGUI::Widget* sender); void onTrainingProgressChanged(int cur, int total); @@ -42,7 +42,7 @@ namespace MWGui // Retrieve the base skill value if the setting 'training skills based on base skill' is set; // otherwise returns the modified skill - float getSkillForTraining(const MWMechanics::NpcStats& stats, int skillId) const; + float getSkillForTraining(const MWMechanics::NpcStats& stats, ESM::RefId id) const; MyGUI::Widget* mTrainingOptions; MyGUI::Button* mCancelButton; @@ -50,7 +50,6 @@ namespace MWGui WaitDialogProgressBar mProgressBar; TimeAdvancer mTimeAdvancer; - bool mTrainingSkillBasedOnBaseSkill; //corresponds to the setting 'training skills based on base skill' }; } diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 24d62d877..57fd57dc3 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -1,10 +1,12 @@ #include "travelwindow.hpp" #include -#include #include +#include -#include +#include +#include +#include /* Start of tes3mp addition @@ -19,23 +21,25 @@ */ #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/actorutil.hpp" - +#include "../mwworld/actionteleport.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/actionteleport.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/cellstore.hpp" +#include "../mwworld/store.hpp" +#include "../mwworld/worldmodel.hpp" + +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" namespace MWGui { - TravelWindow::TravelWindow() : - WindowBase("openmw_travel_window.layout") + TravelWindow::TravelWindow() + : WindowBase("openmw_travel_window.layout") , mCurrentY(0) { getWidget(mCancelButton, "CancelButton"); @@ -46,24 +50,19 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TravelWindow::onCancelButtonClicked); - mDestinations->setCoord(450/2-mDestinations->getTextSize().width/2, - mDestinations->getTop(), - mDestinations->getTextSize().width, - mDestinations->getHeight()); - mSelect->setCoord(8, - mSelect->getTop(), - mSelect->getTextSize().width, - mSelect->getHeight()); + mDestinations->setCoord(450 / 2 - mDestinations->getTextSize().width / 2, mDestinations->getTop(), + mDestinations->getTextSize().width, mDestinations->getHeight()); + mSelect->setCoord(8, mSelect->getTop(), mSelect->getTextSize().width, mSelect->getHeight()); } - void TravelWindow::addDestination(const std::string& name, ESM::Position pos, bool interior) + void TravelWindow::addDestination(const ESM::RefId& name, const ESM::Position& pos, bool interior) { int price; - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (!mPtr.getCell()->isExterior()) @@ -73,7 +72,8 @@ namespace MWGui else { ESM::Position PlayerPos = player.getRefData().getPosition(); - float d = sqrt(pow(pos.pos[0] - PlayerPos.pos[0], 2) + pow(pos.pos[1] - PlayerPos.pos[1], 2) + pow(pos.pos[2] - PlayerPos.pos[2], 2)); + float d = sqrt(pow(pos.pos[0] - PlayerPos.pos[0], 2) + pow(pos.pos[1] - PlayerPos.pos[1], 2) + + pow(pos.pos[2] - PlayerPos.pos[2], 2)); float fTravelMult = gmst.find("fTravelMult")->mValue.getFloat(); if (fTravelMult != 0) price = static_cast(d / fTravelMult); @@ -86,33 +86,36 @@ namespace MWGui // Add price for the travelling followers std::set followers; - MWWorld::ActionTeleport::getFollowers(player, followers); + MWWorld::ActionTeleport::getFollowers(player, followers, !interior); // Apply followers cost, unlike vanilla the first follower doesn't travel for free price *= 1 + static_cast(followers.size()); int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; - MyGUI::Button* toAdd = mDestinationsView->createWidget("SandTextButton", 0, mCurrentY, 200, lineHeight, MyGUI::Align::Default); + MyGUI::Button* toAdd = mDestinationsView->createWidget( + "SandTextButton", 0, mCurrentY, 200, lineHeight, MyGUI::Align::Default); toAdd->setEnabled(price <= playerGold); mCurrentY += lineHeight; - if(interior) - toAdd->setUserString("interior","y"); + if (interior) + toAdd->setUserString("interior", "y"); else - toAdd->setUserString("interior","n"); + toAdd->setUserString("interior", "n"); + const std::string& nameString = name.getRefIdString(); toAdd->setUserString("price", std::to_string(price)); - toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + MyGUI::utility::toString(price)+"#{sgp}"); - toAdd->setSize(mDestinationsView->getWidth(),lineHeight); + toAdd->setCaptionWithReplacing( + "#{sCell=" + nameString + "} - " + MyGUI::utility::toString(price) + "#{sgp}"); + toAdd->setSize(mDestinationsView->getWidth(), lineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); - toAdd->setUserString("Destination", name); + toAdd->setUserString("Destination", nameString); toAdd->setUserData(pos); toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &TravelWindow::onTravelButtonClick); } void TravelWindow::clearDestinations() { - mDestinationsView->setViewOffset(MyGUI::IntPoint(0,0)); + mDestinationsView->setViewOffset(MyGUI::IntPoint(0, 0)); mCurrentY = 0; while (mDestinationsView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mDestinationsView->getChildAt(0)); @@ -127,42 +130,41 @@ namespace MWGui std::vector transport; if (mPtr.getClass().isNpc()) transport = mPtr.get()->mBase->getTransport(); - else if (mPtr.getTypeName() == typeid(ESM::Creature).name()) + else if (mPtr.getType() == ESM::Creature::sRecordId) transport = mPtr.get()->mBase->getTransport(); - for(unsigned int i = 0;ipositionToIndex(transport[i].mPos.pos[0], - transport[i].mPos.pos[1],x,y); - if (cellname == "") + const ESM::ExteriorCellLocation cellIndex + = ESM::positionToExteriorCellLocation(dest.mPos.pos[0], dest.mPos.pos[1]); + if (cellname.empty()) { - MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(x,y); - cellname = MWBase::Environment::get().getWorld()->getCellName(cell); + MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getExterior(cellIndex); + cellname = MWBase::Environment::get().getWorld()->getCellName(&cell); interior = false; } - addDestination(cellname,transport[i].mPos,interior); + addDestination(ESM::RefId::stringRefId(cellname), dest.mPos, interior); } updateLabels(); - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the + // scrollbar is hidden mDestinationsView->setVisibleVScroll(false); - mDestinationsView->setCanvasSize (MyGUI::IntSize(mDestinationsView->getWidth(), std::max(mDestinationsView->getHeight(), mCurrentY))); + mDestinationsView->setCanvasSize( + MyGUI::IntSize(mDestinationsView->getWidth(), std::max(mDestinationsView->getHeight(), mCurrentY))); mDestinationsView->setVisibleVScroll(true); } void TravelWindow::onTravelButtonClick(MyGUI::Widget* _sender) { - std::istringstream iss(_sender->getUserString("price")); - int price; - iss >> price; + const int price = Misc::StringUtils::toNumeric(_sender->getUserString("price"), 0); MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - if (playerGoldisExterior()) // Interior cell -> mages guild transport - MWBase::Environment::get().getWindowManager()->playSound("mysticism cast"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("mysticism cast")); - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); // add gold to NPC trading gold pool MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); @@ -197,11 +199,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->fadeScreenOut(1); ESM::Position pos = *_sender->getUserData(); - std::string cellname = _sender->getUserString("Destination"); + std::string_view cellname = _sender->getUserString("Destination"); bool interior = _sender->getUserString("interior") == "y"; if (mPtr.getCell()->isExterior()) { ESM::Position playerPos = player.getRefData().getPosition(); +<<<<<<< HEAD float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length(); int hours = static_cast(d /MWBase::Environment::get().getWorld()->getStore().get().find("fTravelTimeMult")->mValue.getFloat()); MWBase::Environment::get().getMechanicsManager()->rest(hours, true); @@ -215,15 +218,29 @@ namespace MWGui /* End of tes3mp change (major) */ +======= + float d + = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length(); + int hours = static_cast(d + / MWBase::Environment::get() + .getESMStore() + ->get() + .find("fTravelTimeMult") + ->mValue.getFloat()); + MWBase::Environment::get().getMechanicsManager()->rest(hours, true); + MWBase::Environment::get().getWorld()->advanceTime(hours); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); MWBase::Environment::get().getWindowManager()->fadeScreenOut(1); + const ESM::ExteriorCellLocation posCell = ESM::positionToExteriorCellLocation(pos.pos[0], pos.pos[1]); + ESM::RefId cellId = ESM::Cell::generateIdForCell(!interior, cellname, posCell.mX, posCell.mY); // Teleports any followers, too. - MWWorld::ActionTeleport action(interior ? cellname : "", pos, true); + MWWorld::ActionTeleport action(cellId, pos, true); action.execute(player); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0); @@ -237,14 +254,11 @@ namespace MWGui void TravelWindow::updateLabels() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); - mPlayerGold->setCoord(8, - mPlayerGold->getTop(), - mPlayerGold->getTextSize().width, - mPlayerGold->getHeight()); + mPlayerGold->setCoord(8, mPlayerGold->getTop(), mPlayerGold->getTextSize().width, mPlayerGold->getHeight()); } void TravelWindow::onReferenceUnavailable() @@ -255,10 +269,10 @@ namespace MWGui void TravelWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) { - if (mDestinationsView->getViewOffset().top + _rel*0.3f > 0) + if (mDestinationsView->getViewOffset().top + _rel * 0.3f > 0) mDestinationsView->setViewOffset(MyGUI::IntPoint(0, 0)); else - mDestinationsView->setViewOffset(MyGUI::IntPoint(0, static_cast(mDestinationsView->getViewOffset().top + _rel*0.3f))); + mDestinationsView->setViewOffset( + MyGUI::IntPoint(0, static_cast(mDestinationsView->getViewOffset().top + _rel * 0.3f))); } } - diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 00b7db730..ff492950f 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -1,43 +1,42 @@ #ifndef MWGUI_TravelWINDOW_H #define MWGUI_TravelWINDOW_H - -#include "windowbase.hpp" #include "referenceinterface.hpp" +#include "windowbase.hpp" namespace MyGUI { - class Gui; - class Widget; + class Gui; + class Widget; } namespace MWGui { class TravelWindow : public ReferenceInterface, public WindowBase { - public: - TravelWindow(); + public: + TravelWindow(); - void setPtr (const MWWorld::Ptr& actor) override; + void setPtr(const MWWorld::Ptr& actor) override; - protected: - MyGUI::Button* mCancelButton; - MyGUI::TextBox* mPlayerGold; - MyGUI::TextBox* mDestinations; - MyGUI::TextBox* mSelect; + protected: + MyGUI::Button* mCancelButton; + MyGUI::TextBox* mPlayerGold; + MyGUI::TextBox* mDestinations; + MyGUI::TextBox* mSelect; - MyGUI::ScrollView* mDestinationsView; + MyGUI::ScrollView* mDestinationsView; - void onCancelButtonClicked(MyGUI::Widget* _sender); - void onTravelButtonClick(MyGUI::Widget* _sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void addDestination(const std::string& name, ESM::Position pos, bool interior); - void clearDestinations(); - int mCurrentY; + void onCancelButtonClicked(MyGUI::Widget* _sender); + void onTravelButtonClick(MyGUI::Widget* _sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void addDestination(const ESM::RefId& name, const ESM::Position& pos, bool interior); + void clearDestinations(); + int mCurrentY; - void updateLabels(); + void updateLabels(); - void onReferenceUnavailable() override; + void onReferenceUnavailable() override; }; } diff --git a/apps/openmw/mwgui/ustring.hpp b/apps/openmw/mwgui/ustring.hpp new file mode 100644 index 000000000..5a6c30312 --- /dev/null +++ b/apps/openmw/mwgui/ustring.hpp @@ -0,0 +1,15 @@ +#ifndef MWGUI_USTRING_H +#define MWGUI_USTRING_H + +#include + +namespace MWGui +{ + // FIXME: Remove once we get a version of MyGUI that supports string_view + inline MyGUI::UString toUString(std::string_view string) + { + return { string.data(), string.size() }; + } +} + +#endif \ No newline at end of file diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index 2aea0018d..a82d8ce67 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -7,113 +7,112 @@ #include #include -#include #include +#include #include "../mwsound/movieaudiofactory.hpp" namespace MWGui { -VideoWidget::VideoWidget() - : mVFS(nullptr) -{ - mPlayer.reset(new Video::VideoPlayer()); - setNeedKeyFocus(true); -} - -VideoWidget::~VideoWidget() = default; - -void VideoWidget::setVFS(const VFS::Manager *vfs) -{ - mVFS = vfs; -} - -void VideoWidget::playVideo(const std::string &video) -{ - mPlayer->setAudioFactory(new MWSound::MovieAudioFactory()); - - Files::IStreamPtr videoStream; - try + VideoWidget::VideoWidget() + : mVFS(nullptr) { - videoStream = mVFS->get(video); - } - catch (std::exception& e) - { - Log(Debug::Error) << "Failed to open video: " << e.what(); - return; + mPlayer = std::make_unique(); + setNeedKeyFocus(true); } - mPlayer->playVideo(videoStream, video); + VideoWidget::~VideoWidget() = default; - osg::ref_ptr texture = mPlayer->getVideoTexture(); - if (!texture) - return; - - mTexture.reset(new osgMyGUI::OSGTexture(texture)); - - setRenderItemTexture(mTexture.get()); - getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); -} - -int VideoWidget::getVideoWidth() -{ - return mPlayer->getVideoWidth(); -} - -int VideoWidget::getVideoHeight() -{ - return mPlayer->getVideoHeight(); -} - -bool VideoWidget::update() -{ - return mPlayer->update(); -} - -void VideoWidget::stop() -{ - mPlayer->close(); -} - -void VideoWidget::pause() -{ - mPlayer->pause(); -} - -void VideoWidget::resume() -{ - mPlayer->play(); -} - -bool VideoWidget::isPaused() const -{ - return mPlayer->isPaused(); -} - -bool VideoWidget::hasAudioStream() -{ - return mPlayer->hasAudioStream(); -} - -void VideoWidget::autoResize(bool stretch) -{ - MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); - if (getParent()) - screenSize = getParent()->getSize(); - - if (getVideoHeight() > 0 && !stretch) + void VideoWidget::setVFS(const VFS::Manager* vfs) { - double imageaspect = static_cast(getVideoWidth())/getVideoHeight(); - - int leftPadding = std::max(0, static_cast(screenSize.width - screenSize.height * imageaspect) / 2); - int topPadding = std::max(0, static_cast(screenSize.height - screenSize.width / imageaspect) / 2); - - setCoord(leftPadding, topPadding, - screenSize.width - leftPadding*2, screenSize.height - topPadding*2); + mVFS = vfs; + } + + void VideoWidget::playVideo(const std::string& video) + { + mPlayer->setAudioFactory(new MWSound::MovieAudioFactory()); + + Files::IStreamPtr videoStream; + try + { + videoStream = mVFS->get(video); + } + catch (std::exception& e) + { + Log(Debug::Error) << "Failed to open video: " << e.what(); + return; + } + + mPlayer->playVideo(std::move(videoStream), video); + + osg::ref_ptr texture = mPlayer->getVideoTexture(); + if (!texture) + return; + + mTexture = std::make_unique(texture); + + setRenderItemTexture(mTexture.get()); + getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + } + + int VideoWidget::getVideoWidth() + { + return mPlayer->getVideoWidth(); + } + + int VideoWidget::getVideoHeight() + { + return mPlayer->getVideoHeight(); + } + + bool VideoWidget::update() + { + return mPlayer->update(); + } + + void VideoWidget::stop() + { + mPlayer->close(); + } + + void VideoWidget::pause() + { + mPlayer->pause(); + } + + void VideoWidget::resume() + { + mPlayer->play(); + } + + bool VideoWidget::isPaused() const + { + return mPlayer->isPaused(); + } + + bool VideoWidget::hasAudioStream() + { + return mPlayer->hasAudioStream(); + } + + void VideoWidget::autoResize(bool stretch) + { + MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); + if (getParent()) + screenSize = getParent()->getSize(); + + if (getVideoHeight() > 0 && !stretch) + { + double imageaspect = static_cast(getVideoWidth()) / getVideoHeight(); + + int leftPadding = std::max(0, static_cast(screenSize.width - screenSize.height * imageaspect) / 2); + int topPadding = std::max(0, static_cast(screenSize.height - screenSize.width / imageaspect) / 2); + + setCoord(leftPadding, topPadding, screenSize.width - leftPadding * 2, screenSize.height - topPadding * 2); + } + else + setCoord(0, 0, screenSize.width, screenSize.height); } - else - setCoord(0,0,screenSize.width,screenSize.height); -} } diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index 814b9ca73..f6f70068e 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -27,13 +27,13 @@ namespace MWGui MYGUI_RTTI_DERIVED(VideoWidget) VideoWidget(); - + ~VideoWidget(); /// Set the VFS (virtual file system) to find the videos on. void setVFS(const VFS::Manager* vfs); - void playVideo (const std::string& video); + void playVideo(const std::string& video); int getVideoWidth(); int getVideoHeight(); @@ -55,7 +55,7 @@ namespace MWGui /// based on the dimensions of the playing video. /// @param stretch Stretch the video to fill the whole screen? If false, /// black bars may be added to fix the aspect ratio. - void autoResize (bool stretch); + void autoResize(bool stretch); private: const VFS::Manager* mVFS; diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 58253090e..773e7ec44 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -1,14 +1,17 @@ #include "waitdialog.hpp" -#include #include +#include #include #include -#include +#include +#include #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -22,17 +25,21 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/actorutil.hpp" namespace MWGui { @@ -49,10 +56,10 @@ namespace MWGui center(); } - void WaitDialogProgressBar::setProgress (int cur, int total) + void WaitDialogProgressBar::setProgress(int cur, int total) { - mProgressBar->setProgressRange (total); - mProgressBar->setProgressPosition (cur); + mProgressBar->setProgressRange(total); + mProgressBar->setProgressPosition(cur); mProgressText->setCaption(MyGUI::utility::toString(cur) + "/" + MyGUI::utility::toString(total)); } @@ -90,17 +97,17 @@ namespace MWGui mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &WaitDialog::onWaitingFinished); } - void WaitDialog::setPtr(const MWWorld::Ptr &ptr) + void WaitDialog::setPtr(const MWWorld::Ptr& ptr) { - setCanRest(!ptr.isEmpty() || MWBase::Environment::get().getWorld ()->canRest () == MWBase::World::Rest_Allowed); + setCanRest(!ptr.isEmpty() || MWBase::Environment::get().getWorld()->canRest() == MWBase::World::Rest_Allowed); - if (ptr.isEmpty() && MWBase::Environment::get().getWorld ()->canRest() == MWBase::World::Rest_PlayerIsInAir) + if (ptr.isEmpty() && MWBase::Environment::get().getWorld()->canRest() == MWBase::World::Rest_PlayerIsInAir) { // Resting in air is not allowed unless you're using a bed - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}"); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage1}"); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Rest); } - + if (mUntilHealedButton->getVisible()) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mUntilHealedButton); else @@ -109,12 +116,22 @@ namespace MWGui bool WaitDialog::exit() { - return (!mTimeAdvancer.isRunning()); //Only exit if not currently waiting + bool canExit = !mTimeAdvancer.isRunning(); // Only exit if not currently waiting + if (canExit) + { + clear(); + stopWaiting(); + } + return canExit; } void WaitDialog::clear() { mSleeping = false; + mHours = 1; + mManualHours = 1; + mFadeTimeRemaining = 0; + mInterruptAt = -1; mTimeAdvancer.stop(); } @@ -131,23 +148,23 @@ namespace MWGui mProgressBar.setVisible(false); } - if (!MWBase::Environment::get().getWindowManager ()->getRestEnabled ()) + if (!MWBase::Environment::get().getWindowManager()->getRestEnabled()) { - MWBase::Environment::get().getWindowManager()->popGuiMode (); + MWBase::Environment::get().getWindowManager()->popGuiMode(); } - MWBase::World::RestPermitted canRest = MWBase::Environment::get().getWorld ()->canRest (); + MWBase::World::RestPermitted canRest = MWBase::Environment::get().getWorld()->canRest(); if (canRest == MWBase::World::Rest_EnemiesAreNearby) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); - MWBase::Environment::get().getWindowManager()->popGuiMode (); + MWBase::Environment::get().getWindowManager()->popGuiMode(); } else if (canRest == MWBase::World::Rest_PlayerIsUnderwater) { // resting underwater not allowed - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}"); - MWBase::Environment::get().getWindowManager()->popGuiMode (); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage1}"); + MWBase::Environment::get().getWindowManager()->popGuiMode(); } /* Start of tes3mp addition @@ -171,19 +188,23 @@ namespace MWGui */ onHourSliderChangedPosition(mHourSlider, 0); - mHourSlider->setScrollPosition (0); + mHourSlider->setScrollPosition(0); - std::string month = MWBase::Environment::get().getWorld ()->getMonthName(); + std::string_view month = MWBase::Environment::get().getWorld()->getMonthName(); int hour = static_cast(MWBase::Environment::get().getWorld()->getTimeStamp().getHour()); bool pm = hour >= 12; - if (hour >= 13) hour -= 12; - if (hour == 0) hour = 12; + if (hour >= 13) + hour -= 12; + if (hour == 0) + hour = 12; ESM::EpochTimeStamp currentDate = MWBase::Environment::get().getWorld()->getEpochTimeStamp(); - int daysPassed = MWBase::Environment::get().getWorld()->getTimeStamp().getDay(); - std::string formattedHour = pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"; - std::string dateTimeText = Misc::StringUtils::format("%i %s (#{sDay} %i) %i %s", currentDate.mDay, month, daysPassed, hour, formattedHour); - mDateTimeText->setCaptionWithReplacing (dateTimeText); + std::string daysPassed = Misc::StringUtils::format( + "(#{Calendar:day} %i)", MWBase::Environment::get().getWorld()->getTimeStamp().getDay()); + std::string_view formattedHour(pm ? "#{Calendar:pm}" : "#{Calendar:am}"); + std::string dateTimeText + = Misc::StringUtils::format("%i %s %s %i %s", currentDate.mDay, month, daysPassed, hour, formattedHour); + mDateTimeText->setCaptionWithReplacing(dateTimeText); } void WaitDialog::onUntilHealedButtonClicked(MyGUI::Widget* sender) @@ -200,6 +221,7 @@ namespace MWGui void WaitDialog::startWaiting(int hoursToWait) { +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -207,6 +229,9 @@ namespace MWGui */ /* if(Settings::Manager::getBool("autosave","Saves")) //autosaves when enabled +======= + if (Settings::Manager::getBool("autosave", "Saves")) // autosaves when enabled +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 MWBase::Environment::get().getStateManager()->quickSave("Autosave"); */ /* @@ -225,18 +250,20 @@ namespace MWGui MWWorld::Ptr player = world->getPlayerPtr(); if (mSleeping && player.getCell()->isExterior()) { - std::string regionstr = player.getCell()->getCell()->mRegion; + const ESM::RefId& regionstr = player.getCell()->getCell()->getRegion(); if (!regionstr.empty()) { - const ESM::Region *region = world->getStore().get().find (regionstr); + const ESM::Region* region = world->getStore().get().find(regionstr); if (!region->mSleepList.empty()) { // figure out if player will be woken while sleeping - int x = Misc::Rng::rollDice(hoursToWait); - float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->mValue.getFloat(); + int x = Misc::Rng::rollDice(hoursToWait, world->getPrng()); + float fSleepRandMod + = world->getStore().get().find("fSleepRandMod")->mValue.getFloat(); if (x < fSleepRandMod * hoursToWait) { - float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->mValue.getFloat(); + float fSleepRestMod + = world->getStore().get().find("fSleepRestMod")->mValue.getFloat(); int interruptAtHoursRemaining = int(fSleepRestMod * hoursToWait); if (interruptAtHoursRemaining != 0) { @@ -248,7 +275,7 @@ namespace MWGui } } - mProgressBar.setProgress (0, hoursToWait); + mProgressBar.setProgress(0, hoursToWait); } void WaitDialog::onCancelButtonClicked(MyGUI::Widget* sender) @@ -258,17 +285,18 @@ namespace MWGui void WaitDialog::onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position) { - mHourText->setCaptionWithReplacing (MyGUI::utility::toString(position+1) + " #{sRestMenu2}"); - mManualHours = position+1; + mHourText->setCaptionWithReplacing(MyGUI::utility::toString(position + 1) + " #{sRestMenu2}"); + mManualHours = position + 1; MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mWaitButton); } - void WaitDialog::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) + void WaitDialog::onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character) { if (key == MyGUI::KeyCode::ArrowUp) - mHourSlider->setScrollPosition(std::min(mHourSlider->getScrollPosition()+1, mHourSlider->getScrollRange()-1)); + mHourSlider->setScrollPosition( + std::min(mHourSlider->getScrollPosition() + 1, mHourSlider->getScrollRange() - 1)); else if (key == MyGUI::KeyCode::ArrowDown) - mHourSlider->setScrollPosition(std::max(static_cast(mHourSlider->getScrollPosition())-1, 0)); + mHourSlider->setScrollPosition(std::max(static_cast(mHourSlider->getScrollPosition()) - 1, 0)); else return; onHourSliderChangedPosition(mHourSlider, mHourSlider->getScrollPosition()); @@ -306,31 +334,30 @@ namespace MWGui stopWaiting(); MWWorld::Ptr player = MWMechanics::getPlayer(); - const MWMechanics::NpcStats &pcstats = player.getClass().getNpcStats(player); + const MWMechanics::NpcStats& pcstats = player.getClass().getNpcStats(player); // trigger levelup if possible - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->mValue.getInteger()) + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + if (mSleeping && pcstats.getLevelProgress() >= gmst.find("iLevelUpTotal")->mValue.getInteger()) { - MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Levelup); } } - void WaitDialog::setCanRest (bool canRest) + void WaitDialog::setCanRest(bool canRest) { MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); bool full = (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) - && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); + && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); MWMechanics::NpcStats& npcstats = player.getClass().getNpcStats(player); bool werewolf = npcstats.isWerewolf(); mUntilHealedButton->setVisible(canRest && !full); - mWaitButton->setCaptionWithReplacing (canRest ? "#{sRest}" : "#{sWait}"); - mRestText->setCaptionWithReplacing (canRest ? "#{sRestMenu3}" - : (werewolf ? "#{sWerewolfRestMessage}" - : "#{sRestIllegal}")); + mWaitButton->setCaptionWithReplacing(canRest ? "#{sRest}" : "#{sWait}"); + mRestText->setCaptionWithReplacing( + canRest ? "#{sRestMenu3}" : (werewolf ? "#{sWerewolfRestMessage}" : "#{sRestIllegal}")); mSleeping = canRest; @@ -357,16 +384,15 @@ namespace MWGui } } - void WaitDialog::stopWaiting () + void WaitDialog::stopWaiting() { MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2f); - mProgressBar.setVisible (false); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Rest); + mProgressBar.setVisible(false); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Rest); mTimeAdvancer.stop(); } - - void WaitDialog::wakeUp () + void WaitDialog::wakeUp() { mSleeping = false; if (mInterruptAt != -1) diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index bf84e7e81..488989fc8 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -2,8 +2,8 @@ #define MWGUI_WAIT_DIALOG_H #include "timeadvancer.hpp" - #include "windowbase.hpp" +#include namespace MWGui { @@ -27,7 +27,7 @@ namespace MWGui public: WaitDialog(); - void setPtr(const MWWorld::Ptr &ptr) override; + void setPtr(const MWWorld::Ptr& ptr) override; void onOpen() override; @@ -59,7 +59,7 @@ namespace MWGui float mFadeTimeRemaining; int mInterruptAt; - std::string mInterruptCreatureList; + ESM::RefId mInterruptCreatureList; WaitDialogProgressBar mProgressBar; diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index fb8521f06..de4f95443 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -1,538 +1,523 @@ #include "widgets.hpp" -#include #include -#include -#include +#include #include +#include +#include + +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/magiceffects.hpp" #include "../mwworld/esmstore.hpp" -#include "controllers.hpp" +#include "ustring.hpp" -namespace MWGui +namespace MWGui::Widgets { - namespace Widgets + /* MWSkill */ + + MWSkill::MWSkill() + : mSkillNameWidget(nullptr) + , mSkillValueWidget(nullptr) { - /* MWSkill */ + } - MWSkill::MWSkill() - : mSkillId(ESM::Skill::Length) - , mSkillNameWidget(nullptr) - , mSkillValueWidget(nullptr) - { - } + void MWSkill::setSkillId(ESM::RefId skill) + { + mSkillId = skill; + updateWidgets(); + } - void MWSkill::setSkillId(ESM::Skill::SkillEnum skill) - { - mSkillId = skill; - updateWidgets(); - } + void MWSkill::setSkillValue(const SkillValue& value) + { + mValue = value; + updateWidgets(); + } - void MWSkill::setSkillNumber(int skill) + void MWSkill::updateWidgets() + { + if (mSkillNameWidget) { - if (skill < 0) - setSkillId(ESM::Skill::Length); - else if (skill < ESM::Skill::Length) - setSkillId(static_cast(skill)); + const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().search(mSkillId); + if (skill == nullptr) + mSkillNameWidget->setCaption({}); else - throw std::runtime_error("Skill number out of range"); + mSkillNameWidget->setCaption(skill->mName); + } + if (mSkillValueWidget) + { + SkillValue::Type modified = mValue.getModified(), base = mValue.getBase(); + mSkillValueWidget->setCaption(MyGUI::utility::toString(modified)); + if (modified > base) + mSkillValueWidget->_setWidgetState("increased"); + else if (modified < base) + mSkillValueWidget->_setWidgetState("decreased"); + else + mSkillValueWidget->_setWidgetState("normal"); + } + } + + void MWSkill::onClicked(MyGUI::Widget* _sender) + { + eventClicked(this); + } + + MWSkill::~MWSkill() {} + + void MWSkill::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mSkillNameWidget, "StatName"); + assignWidget(mSkillValueWidget, "StatValue"); + + MyGUI::Button* button; + assignWidget(button, "StatNameButton"); + if (button) + { + mSkillNameWidget = button; + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); } - void MWSkill::setSkillValue(const SkillValue& value) + button = nullptr; + assignWidget(button, "StatValueButton"); + if (button) { - mValue = value; - updateWidgets(); + mSkillValueWidget = button; + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); } + } - void MWSkill::updateWidgets() + /* MWAttribute */ + + MWAttribute::MWAttribute() + : mId(ESM::Attribute::Length) + , mAttributeNameWidget(nullptr) + , mAttributeValueWidget(nullptr) + { + } + + void MWAttribute::setAttributeId(ESM::Attribute::AttributeID attributeId) + { + mId = attributeId; + updateWidgets(); + } + + void MWAttribute::setAttributeValue(const AttributeValue& value) + { + mValue = value; + updateWidgets(); + } + + void MWAttribute::onClicked(MyGUI::Widget* _sender) + { + eventClicked(this); + } + + void MWAttribute::updateWidgets() + { + if (mAttributeNameWidget) { - if (mSkillNameWidget) + const ESM::Attribute* attribute + = MWBase::Environment::get().getESMStore()->get().search(mId); + if (!attribute) { - if (mSkillId == ESM::Skill::Length) - { - mSkillNameWidget->setCaption(""); - } - else - { - const std::string &name = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Skill::sSkillNameIds[mSkillId], ""); - mSkillNameWidget->setCaption(name); - } + mAttributeNameWidget->setCaption({}); } - if (mSkillValueWidget) + else { - SkillValue::Type modified = mValue.getModified(), base = mValue.getBase(); - mSkillValueWidget->setCaption(MyGUI::utility::toString(modified)); - if (modified > base) - mSkillValueWidget->_setWidgetState("increased"); - else if (modified < base) - mSkillValueWidget->_setWidgetState("decreased"); - else - mSkillValueWidget->_setWidgetState("normal"); + MyGUI::UString name = toUString(attribute->mName); + mAttributeNameWidget->setCaption(name); } } - - void MWSkill::onClicked(MyGUI::Widget* _sender) + if (mAttributeValueWidget) { - eventClicked(this); + int modified = mValue.getModified(), base = mValue.getBase(); + mAttributeValueWidget->setCaption(MyGUI::utility::toString(modified)); + if (modified > base) + mAttributeValueWidget->_setWidgetState("increased"); + else if (modified < base) + mAttributeValueWidget->_setWidgetState("decreased"); + else + mAttributeValueWidget->_setWidgetState("normal"); + } + } + + void MWAttribute::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mAttributeNameWidget, "StatName"); + assignWidget(mAttributeValueWidget, "StatValue"); + + MyGUI::Button* button; + assignWidget(button, "StatNameButton"); + if (button) + { + mAttributeNameWidget = button; + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); } - MWSkill::~MWSkill() + button = nullptr; + assignWidget(button, "StatValueButton"); + if (button) { + mAttributeValueWidget = button; + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); + } + } + + /* MWSpell */ + + MWSpell::MWSpell() + : mSpellNameWidget(nullptr) + { + } + + void MWSpell::setSpellId(const ESM::RefId& spellId) + { + mId = spellId; + updateWidgets(); + } + + void MWSpell::createEffectWidgets( + std::vector& effects, MyGUI::Widget* creator, MyGUI::IntCoord& coord, int flags) + { + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + + const ESM::Spell* spell = store.get().search(mId); + MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); + + for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) + { + MWSpellEffectPtr effect + = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); + SpellEffectParams params; + params.mEffectID = effectInfo.mEffectID; + params.mSkill = effectInfo.mSkill; + params.mAttribute = effectInfo.mAttribute; + params.mDuration = effectInfo.mDuration; + params.mMagnMin = effectInfo.mMagnMin; + params.mMagnMax = effectInfo.mMagnMax; + params.mRange = effectInfo.mRange; + params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0; + params.mNoTarget = (flags & MWEffectList::EF_NoTarget); + params.mNoMagnitude = (flags & MWEffectList::EF_NoMagnitude); + effect->setSpellEffect(params); + effects.push_back(effect); + coord.top += effect->getHeight(); + coord.width = std::max(coord.width, effect->getRequestedWidth()); + } + } + + void MWSpell::updateWidgets() + { + if (mSpellNameWidget && MWBase::Environment::get().getWindowManager()) + { + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + + const ESM::Spell* spell = store.get().search(mId); + if (spell) + mSpellNameWidget->setCaption(spell->mName); + else + mSpellNameWidget->setCaption({}); + } + } + + void MWSpell::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mSpellNameWidget, "StatName"); + } + + MWSpell::~MWSpell() {} + + /* MWEffectList */ + + MWEffectList::MWEffectList() + : mEffectList(0) + { + } + + void MWEffectList::setEffectList(const SpellEffectList& list) + { + mEffectList = list; + updateWidgets(); + } + + void MWEffectList::createEffectWidgets( + std::vector& effects, MyGUI::Widget* creator, MyGUI::IntCoord& coord, bool center, int flags) + { + // We don't know the width of all the elements beforehand, so we do it in + // 2 steps: first, create all widgets and check their width.... + MWSpellEffectPtr effect = nullptr; + int maxwidth = coord.width; + + for (auto& effectInfo : mEffectList) + { + effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); + effectInfo.mIsConstant = (flags & EF_Constant) || effectInfo.mIsConstant; + effectInfo.mNoTarget = (flags & EF_NoTarget) || effectInfo.mNoTarget; + effectInfo.mNoMagnitude = (flags & EF_NoMagnitude) || effectInfo.mNoMagnitude; + effect->setSpellEffect(effectInfo); + effects.push_back(effect); + if (effect->getRequestedWidth() > maxwidth) + maxwidth = effect->getRequestedWidth(); + + coord.top += effect->getHeight(); } - void MWSkill::initialiseOverride() + // ... then adjust the size for all widgets + for (MyGUI::Widget* effectWidget : effects) { - Base::initialiseOverride(); - - assignWidget(mSkillNameWidget, "StatName"); - assignWidget(mSkillValueWidget, "StatValue"); - - MyGUI::Button* button; - assignWidget(button, "StatNameButton"); - if (button) + effect = effectWidget->castType(); + bool needcenter = center && (maxwidth > effect->getRequestedWidth()); + int diff = maxwidth - effect->getRequestedWidth(); + if (needcenter) { - mSkillNameWidget = button; - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); + effect->setCoord( + diff / 2, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height); } - - button = nullptr; - assignWidget(button, "StatValueButton"); - if (button) + else { - mSkillValueWidget = button; - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); + effect->setCoord(0, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height); } } - /* MWAttribute */ + // inform the parent about width + coord.width = maxwidth; + } - MWAttribute::MWAttribute() - : mId(-1) - , mAttributeNameWidget(nullptr) - , mAttributeValueWidget(nullptr) + void MWEffectList::updateWidgets() {} + + void MWEffectList::initialiseOverride() + { + Base::initialiseOverride(); + } + + MWEffectList::~MWEffectList() {} + + SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) + { + SpellEffectList result; + for (const ESM::ENAMstruct& effectInfo : effects->mList) { + SpellEffectParams params; + params.mEffectID = effectInfo.mEffectID; + params.mSkill = effectInfo.mSkill; + params.mAttribute = effectInfo.mAttribute; + params.mDuration = effectInfo.mDuration; + params.mMagnMin = effectInfo.mMagnMin; + params.mMagnMax = effectInfo.mMagnMax; + params.mRange = effectInfo.mRange; + params.mArea = effectInfo.mArea; + result.push_back(params); } + return result; + } - void MWAttribute::setAttributeId(int attributeId) + /* MWSpellEffect */ + + MWSpellEffect::MWSpellEffect() + : mImageWidget(nullptr) + , mTextWidget(nullptr) + , mRequestedWidth(0) + { + } + + void MWSpellEffect::setSpellEffect(const SpellEffectParams& params) + { + mEffectParams = params; + updateWidgets(); + } + + void MWSpellEffect::updateWidgets() + { + if (!mEffectParams.mKnown) { - mId = attributeId; - updateWidgets(); - } - - void MWAttribute::setAttributeValue(const AttributeValue& value) - { - mValue = value; - updateWidgets(); - } - - void MWAttribute::onClicked(MyGUI::Widget* _sender) - { - eventClicked(this); - } - - void MWAttribute::updateWidgets() - { - if (mAttributeNameWidget) - { - if (mId < 0 || mId >= 8) - { - mAttributeNameWidget->setCaption(""); - } - else - { - static const char *attributes[8] = { - "sAttributeStrength", - "sAttributeIntelligence", - "sAttributeWillpower", - "sAttributeAgility", - "sAttributeSpeed", - "sAttributeEndurance", - "sAttributePersonality", - "sAttributeLuck" - }; - const std::string &name = MWBase::Environment::get().getWindowManager()->getGameSettingString(attributes[mId], ""); - mAttributeNameWidget->setCaption(name); - } - } - if (mAttributeValueWidget) - { - int modified = mValue.getModified(), base = mValue.getBase(); - mAttributeValueWidget->setCaption(MyGUI::utility::toString(modified)); - if (modified > base) - mAttributeValueWidget->_setWidgetState("increased"); - else if (modified < base) - mAttributeValueWidget->_setWidgetState("decreased"); - else - mAttributeValueWidget->_setWidgetState("normal"); - } - } - - MWAttribute::~MWAttribute() - { - } - - void MWAttribute::initialiseOverride() - { - Base::initialiseOverride(); - - assignWidget(mAttributeNameWidget, "StatName"); - assignWidget(mAttributeValueWidget, "StatValue"); - - MyGUI::Button* button; - assignWidget(button, "StatNameButton"); - if (button) - { - mAttributeNameWidget = button; - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); - } - - button = nullptr; - assignWidget(button, "StatValueButton"); - if (button) - { - mAttributeValueWidget = button; - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); - } - } - - /* MWSpell */ - - MWSpell::MWSpell() - : mSpellNameWidget(nullptr) - { - } - - void MWSpell::setSpellId(const std::string &spellId) - { - mId = spellId; - updateWidgets(); - } - - void MWSpell::createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, int flags) - { - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); - - const ESM::Spell *spell = store.get().search(mId); - MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); - - for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) - { - MWSpellEffectPtr effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); - SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = effectInfo.mSkill; - params.mAttribute = effectInfo.mAttribute; - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; - params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0; - params.mNoTarget = (flags & MWEffectList::EF_NoTarget); - params.mNoMagnitude = (flags & MWEffectList::EF_NoMagnitude); - effect->setSpellEffect(params); - effects.push_back(effect); - coord.top += effect->getHeight(); - coord.width = std::max(coord.width, effect->getRequestedWidth()); - } - } - - void MWSpell::updateWidgets() - { - if (mSpellNameWidget && MWBase::Environment::get().getWindowManager()) - { - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); - - const ESM::Spell *spell = store.get().search(mId); - if (spell) - mSpellNameWidget->setCaption(spell->mName); - else - mSpellNameWidget->setCaption(""); - } - } - - void MWSpell::initialiseOverride() - { - Base::initialiseOverride(); - - assignWidget(mSpellNameWidget, "StatName"); - } - - MWSpell::~MWSpell() - { - } - - /* MWEffectList */ - - MWEffectList::MWEffectList() - : mEffectList(0) - { - } - - void MWEffectList::setEffectList(const SpellEffectList& list) - { - mEffectList = list; - updateWidgets(); - } - - void MWEffectList::createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, bool center, int flags) - { - // We don't know the width of all the elements beforehand, so we do it in - // 2 steps: first, create all widgets and check their width.... - MWSpellEffectPtr effect = nullptr; - int maxwidth = coord.width; - - for (auto& effectInfo : mEffectList) - { - effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); - effectInfo.mIsConstant = (flags & EF_Constant) || effectInfo.mIsConstant; - effectInfo.mNoTarget = (flags & EF_NoTarget) || effectInfo.mNoTarget; - effectInfo.mNoMagnitude = (flags & EF_NoMagnitude) || effectInfo.mNoMagnitude; - effect->setSpellEffect(effectInfo); - effects.push_back(effect); - if (effect->getRequestedWidth() > maxwidth) - maxwidth = effect->getRequestedWidth(); - - coord.top += effect->getHeight(); - } - - // ... then adjust the size for all widgets - for (MyGUI::Widget* effectWidget : effects) - { - effect = effectWidget->castType(); - bool needcenter = center && (maxwidth > effect->getRequestedWidth()); - int diff = maxwidth - effect->getRequestedWidth(); - if (needcenter) - { - effect->setCoord(diff/2, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height); - } - else - { - effect->setCoord(0, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height); - } - } - - // inform the parent about width - coord.width = maxwidth; - } - - void MWEffectList::updateWidgets() - { - } - - void MWEffectList::initialiseOverride() - { - Base::initialiseOverride(); - } - - MWEffectList::~MWEffectList() - { - } - - SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) - { - SpellEffectList result; - for (const ESM::ENAMstruct& effectInfo : effects->mList) - { - SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = effectInfo.mSkill; - params.mAttribute = effectInfo.mAttribute; - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; - params.mArea = effectInfo.mArea; - result.push_back(params); - } - return result; - } - - /* MWSpellEffect */ - - MWSpellEffect::MWSpellEffect() - : mImageWidget(nullptr) - , mTextWidget(nullptr) - , mRequestedWidth(0) - { - } - - void MWSpellEffect::setSpellEffect(const SpellEffectParams& params) - { - mEffectParams = params; - updateWidgets(); - } - - void MWSpellEffect::updateWidgets() - { - if (!mEffectParams.mKnown) - { - mTextWidget->setCaption ("?"); - mTextWidget->setCoord(sIconOffset / 2, mTextWidget->getCoord().top, mTextWidget->getCoord().width, mTextWidget->getCoord().height); // Compensates for the missing image when effect is not known - mRequestedWidth = mTextWidget->getTextSize().width + sIconOffset; - mImageWidget->setImageTexture (""); - return; - } - - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); - - const ESM::MagicEffect *magicEffect = - store.get().search(mEffectParams.mEffectID); - - assert(magicEffect); - - std::string pt = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", ""); - std::string pts = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", ""); - std::string pct = MWBase::Environment::get().getWindowManager()->getGameSettingString("spercent", ""); - std::string ft = MWBase::Environment::get().getWindowManager()->getGameSettingString("sfeet", ""); - std::string lvl = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevel", ""); - std::string lvls = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevels", ""); - std::string to = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "") + " "; - std::string sec = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("ssecond", ""); - std::string secs = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sseconds", ""); - - std::string effectIDStr = ESM::MagicEffect::effectIdToString(mEffectParams.mEffectID); - std::string spellLine = MWBase::Environment::get().getWindowManager()->getGameSettingString(effectIDStr, ""); - - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && mEffectParams.mSkill != -1) - { - spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Skill::sSkillNameIds[mEffectParams.mSkill], ""); - } - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && mEffectParams.mAttribute != -1) - { - spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Attribute::sGmstAttributeIds[mEffectParams.mAttribute], ""); - } - - if (mEffectParams.mMagnMin || mEffectParams.mMagnMax) { - ESM::MagicEffect::MagnitudeDisplayType displayType = magicEffect->getMagnitudeDisplayType(); - if ( displayType == ESM::MagicEffect::MDT_TimesInt ) { - std::string timesInt = MWBase::Environment::get().getWindowManager()->getGameSettingString("sXTimesINT", ""); - std::stringstream formatter; - - formatter << std::fixed << std::setprecision(1) << " " << (mEffectParams.mMagnMin / 10.0f); - if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) - formatter << to << (mEffectParams.mMagnMax / 10.0f); - formatter << timesInt; - - spellLine += formatter.str(); - } - else if ( displayType != ESM::MagicEffect::MDT_None && !mEffectParams.mNoMagnitude) { - spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin); - if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) - spellLine += to + MyGUI::utility::toString(mEffectParams.mMagnMax); - - if ( displayType == ESM::MagicEffect::MDT_Percentage ) - spellLine += pct; - else if ( displayType == ESM::MagicEffect::MDT_Feet ) - spellLine += " " + ft; - else if ( displayType == ESM::MagicEffect::MDT_Level ) - spellLine += " " + ((mEffectParams.mMagnMin == mEffectParams.mMagnMax && std::abs(mEffectParams.mMagnMin) == 1) ? lvl : lvls ); - else // ESM::MagicEffect::MDT_Points - spellLine += " " + ((mEffectParams.mMagnMin == mEffectParams.mMagnMax && std::abs(mEffectParams.mMagnMin) == 1) ? pt : pts ); - } - } - - // constant effects have no duration and no target - if (!mEffectParams.mIsConstant) - { - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) - mEffectParams.mDuration = std::max(1, mEffectParams.mDuration); - - if (mEffectParams.mDuration > 0 && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - { - spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfor", "") + " " + MyGUI::utility::toString(mEffectParams.mDuration) + ((mEffectParams.mDuration == 1) ? sec : secs); - } - - if (mEffectParams.mArea > 0) - { - spellLine += " #{sin} " + MyGUI::utility::toString(mEffectParams.mArea) + " #{sfootarea}"; - } - - // potions have no target - if (!mEffectParams.mNoTarget) - { - std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sonword", ""); - if (mEffectParams.mRange == ESM::RT_Self) - spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeSelf", ""); - else if (mEffectParams.mRange == ESM::RT_Touch) - spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeTouch", ""); - else if (mEffectParams.mRange == ESM::RT_Target) - spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeTarget", ""); - } - } - - mTextWidget->setCaptionWithReplacing(spellLine); + mTextWidget->setCaption("?"); + mTextWidget->setCoord(sIconOffset / 2, mTextWidget->getCoord().top, mTextWidget->getCoord().width, + mTextWidget->getCoord().height); // Compensates for the missing image when effect is not known mRequestedWidth = mTextWidget->getTextSize().width + sIconOffset; - - mImageWidget->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(magicEffect->mIcon)); + mImageWidget->setImageTexture({}); + return; } - MWSpellEffect::~MWSpellEffect() + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + + const ESM::MagicEffect* magicEffect = store.get().search(mEffectParams.mEffectID); + const ESM::Attribute* attribute = store.get().search(mEffectParams.mAttribute); + const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(mEffectParams.mSkill)); + + assert(magicEffect); + + auto windowManager = MWBase::Environment::get().getWindowManager(); + + std::string_view pt = windowManager->getGameSettingString("spoint", {}); + std::string_view pts = windowManager->getGameSettingString("spoints", {}); + std::string_view pct = windowManager->getGameSettingString("spercent", {}); + std::string_view ft = windowManager->getGameSettingString("sfeet", {}); + std::string_view lvl = windowManager->getGameSettingString("sLevel", {}); + std::string_view lvls = windowManager->getGameSettingString("sLevels", {}); + std::string to = " " + std::string{ windowManager->getGameSettingString("sTo", {}) } + " "; + std::string sec = " " + std::string{ windowManager->getGameSettingString("ssecond", {}) }; + std::string secs = " " + std::string{ windowManager->getGameSettingString("sseconds", {}) }; + + std::string spellLine = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); + + if (mEffectParams.mMagnMin || mEffectParams.mMagnMax) { + ESM::MagicEffect::MagnitudeDisplayType displayType = magicEffect->getMagnitudeDisplayType(); + if (displayType == ESM::MagicEffect::MDT_TimesInt) + { + std::string_view timesInt = windowManager->getGameSettingString("sXTimesINT", {}); + std::stringstream formatter; + + formatter << std::fixed << std::setprecision(1) << " " << (mEffectParams.mMagnMin / 10.0f); + if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) + formatter << to << (mEffectParams.mMagnMax / 10.0f); + formatter << timesInt; + + spellLine += formatter.str(); + } + else if (displayType != ESM::MagicEffect::MDT_None && !mEffectParams.mNoMagnitude) + { + spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin); + if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) + spellLine += to + MyGUI::utility::toString(mEffectParams.mMagnMax); + + if (displayType == ESM::MagicEffect::MDT_Percentage) + spellLine += pct; + else if (displayType == ESM::MagicEffect::MDT_Feet) + { + spellLine += ' '; + spellLine += ft; + } + else if (displayType == ESM::MagicEffect::MDT_Level) + { + spellLine += ' '; + if (mEffectParams.mMagnMin == mEffectParams.mMagnMax && std::abs(mEffectParams.mMagnMin) == 1) + spellLine += lvl; + else + spellLine += lvls; + } + else // ESM::MagicEffect::MDT_Points + { + spellLine += ' '; + if (mEffectParams.mMagnMin == mEffectParams.mMagnMax && std::abs(mEffectParams.mMagnMin) == 1) + spellLine += pt; + else + spellLine += pts; + } + } } - void MWSpellEffect::initialiseOverride() + // constant effects have no duration and no target + if (!mEffectParams.mIsConstant) { - Base::initialiseOverride(); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) + mEffectParams.mDuration = std::max(1, mEffectParams.mDuration); - assignWidget(mTextWidget, "Text"); - assignWidget(mImageWidget, "Image"); + if (mEffectParams.mDuration > 0 && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + { + spellLine += ' '; + spellLine += windowManager->getGameSettingString("sfor", {}); + spellLine += ' ' + MyGUI::utility::toString(mEffectParams.mDuration) + + ((mEffectParams.mDuration == 1) ? sec : secs); + } + + if (mEffectParams.mArea > 0) + { + spellLine += " #{sin} " + MyGUI::utility::toString(mEffectParams.mArea) + " #{sfootarea}"; + } + + // potions have no target + if (!mEffectParams.mNoTarget) + { + spellLine += ' '; + spellLine += windowManager->getGameSettingString("sonword", {}); + spellLine += ' '; + if (mEffectParams.mRange == ESM::RT_Self) + spellLine += windowManager->getGameSettingString("sRangeSelf", {}); + else if (mEffectParams.mRange == ESM::RT_Touch) + spellLine += windowManager->getGameSettingString("sRangeTouch", {}); + else if (mEffectParams.mRange == ESM::RT_Target) + spellLine += windowManager->getGameSettingString("sRangeTarget", {}); + } } - /* MWDynamicStat */ + mTextWidget->setCaptionWithReplacing(spellLine); + mRequestedWidth = mTextWidget->getTextSize().width + sIconOffset; - MWDynamicStat::MWDynamicStat() + mImageWidget->setImageTexture(Misc::ResourceHelpers::correctIconPath( + magicEffect->mIcon, MWBase::Environment::get().getResourceSystem()->getVFS())); + } + + MWSpellEffect::~MWSpellEffect() {} + + void MWSpellEffect::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mTextWidget, "Text"); + assignWidget(mImageWidget, "Image"); + } + + /* MWDynamicStat */ + + MWDynamicStat::MWDynamicStat() : mValue(0) , mMax(1) , mTextWidget(nullptr) , mBarWidget(nullptr) , mBarTextWidget(nullptr) + { + } + + void MWDynamicStat::setValue(int cur, int max) + { + mValue = cur; + mMax = max; + + if (mBarWidget) { + mBarWidget->setProgressRange(std::max(0, mMax)); + mBarWidget->setProgressPosition(std::max(0, mValue)); } - void MWDynamicStat::setValue(int cur, int max) + if (mBarTextWidget) { - mValue = cur; - mMax = max; - - if (mBarWidget) - { - mBarWidget->setProgressRange(std::max(0, mMax)); - mBarWidget->setProgressPosition(std::max(0, mValue)); - } - - if (mBarTextWidget) - { - std::stringstream out; - out << mValue << "/" << mMax; - mBarTextWidget->setCaption(out.str().c_str()); - } - } - void MWDynamicStat::setTitle(const std::string& text) - { - if (mTextWidget) - mTextWidget->setCaption(text); - } - - MWDynamicStat::~MWDynamicStat() - { - } - - void MWDynamicStat::initialiseOverride() - { - Base::initialiseOverride(); - - assignWidget(mTextWidget, "Text"); - assignWidget(mBarWidget, "Bar"); - assignWidget(mBarTextWidget, "BarText"); + std::stringstream out; + out << mValue << "/" << mMax; + mBarTextWidget->setCaption(out.str().c_str()); } } + void MWDynamicStat::setTitle(std::string_view text) + { + if (mTextWidget) + mTextWidget->setCaption(toUString(text)); + } + + MWDynamicStat::~MWDynamicStat() {} + + void MWDynamicStat::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mTextWidget, "Text"); + assignWidget(mBarWidget, "Bar"); + assignWidget(mBarTextWidget, "BarText"); + } } diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 3c5528715..8e45b37f2 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -3,12 +3,14 @@ #include "../mwmechanics/stat.hpp" -#include -#include +#include +#include +#include -#include -#include -#include +#include +#include +#include +#include namespace MyGUI { @@ -31,8 +33,6 @@ namespace MWGui { class MWEffectList; - void fixTexturePath(std::string &path); - struct SpellEffectParams { SpellEffectParams() @@ -71,20 +71,21 @@ namespace MWGui bool operator==(const SpellEffectParams& other) const { - if (mEffectID != other.mEffectID) + if (mEffectID != other.mEffectID) return false; bool involvesAttribute = (mEffectID == 74 // restore attribute - || mEffectID == 85 // absorb attribute - || mEffectID == 17 // drain attribute - || mEffectID == 79 // fortify attribute - || mEffectID == 22); // damage attribute + || mEffectID == 85 // absorb attribute + || mEffectID == 17 // drain attribute + || mEffectID == 79 // fortify attribute + || mEffectID == 22); // damage attribute bool involvesSkill = (mEffectID == 78 // restore skill - || mEffectID == 89 // absorb skill - || mEffectID == 21 // drain skill - || mEffectID == 83 // fortify skill - || mEffectID == 26); // damage skill - return ((other.mSkill == mSkill) || !involvesSkill) && ((other.mAttribute == mAttribute) && !involvesAttribute) && (other.mArea == mArea); + || mEffectID == 89 // absorb skill + || mEffectID == 21 // drain skill + || mEffectID == 83 // fortify skill + || mEffectID == 26); // damage skill + return ((other.mSkill == mSkill) || !involvesSkill) + && ((other.mAttribute == mAttribute) && !involvesAttribute) && (other.mArea == mArea); } }; @@ -92,17 +93,16 @@ namespace MWGui class MWSkill final : public MyGUI::Widget { - MYGUI_RTTI_DERIVED( MWSkill ) + MYGUI_RTTI_DERIVED(MWSkill) public: MWSkill(); typedef MWMechanics::Stat SkillValue; - void setSkillId(ESM::Skill::SkillEnum skillId); - void setSkillNumber(int skillId); + void setSkillId(ESM::RefId skillId); void setSkillValue(const SkillValue& value); - ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } + ESM::RefId getSkillId() const { return mSkillId; } const SkillValue& getSkillValue() const { return mValue; } // Events @@ -121,10 +121,9 @@ namespace MWGui void onClicked(MyGUI::Widget* _sender); private: - void updateWidgets(); - ESM::Skill::SkillEnum mSkillId; + ESM::RefId mSkillId; SkillValue mValue; MyGUI::TextBox* mSkillNameWidget; MyGUI::TextBox* mSkillValueWidget; @@ -133,16 +132,16 @@ namespace MWGui class MWAttribute final : public MyGUI::Widget { - MYGUI_RTTI_DERIVED( MWAttribute ) + MYGUI_RTTI_DERIVED(MWAttribute) public: MWAttribute(); typedef MWMechanics::AttributeValue AttributeValue; - void setAttributeId(int attributeId); + void setAttributeId(ESM::Attribute::AttributeID attributeId); void setAttributeValue(const AttributeValue& value); - int getAttributeId() const { return mId; } + ESM::Attribute::AttributeID getAttributeId() const { return mId; } const AttributeValue& getAttributeValue() const { return mValue; } // Events @@ -154,17 +153,16 @@ namespace MWGui EventHandle_AttributeVoid eventClicked; protected: - virtual ~MWAttribute(); + ~MWAttribute() override = default; void initialiseOverride() override; void onClicked(MyGUI::Widget* _sender); private: - void updateWidgets(); - int mId; + ESM::Attribute::AttributeID mId; AttributeValue mValue; MyGUI::TextBox* mAttributeNameWidget; MyGUI::TextBox* mAttributeValueWidget; @@ -177,24 +175,24 @@ namespace MWGui class MWSpellEffect; class MWSpell final : public MyGUI::Widget { - MYGUI_RTTI_DERIVED( MWSpell ) + MYGUI_RTTI_DERIVED(MWSpell) public: MWSpell(); - typedef MWMechanics::Stat SpellValue; - - void setSpellId(const std::string &id); + void setSpellId(const ESM::RefId& id); /** * @param vector to store the created effect widgets * @param parent widget * @param coordinates to use, will be expanded if more space is needed - * @param spell category, if this is 0, this means the spell effects are permanent and won't display e.g. duration + * @param spell category, if this is 0, this means the spell effects are permanent and won't display e.g. + * duration * @param various flags, see MWEffectList::EffectFlags */ - void createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, int flags); + void createEffectWidgets( + std::vector& effects, MyGUI::Widget* creator, MyGUI::IntCoord& coord, int flags); - const std::string &getSpellId() const { return mId; } + const ESM::RefId& getSpellId() const { return mId; } protected: virtual ~MWSpell(); @@ -204,19 +202,17 @@ namespace MWGui private: void updateWidgets(); - std::string mId; + ESM::RefId mId; MyGUI::TextBox* mSpellNameWidget; }; typedef MWSpell* MWSpellPtr; class MWEffectList final : public MyGUI::Widget { - MYGUI_RTTI_DERIVED( MWEffectList ) + MYGUI_RTTI_DERIVED(MWEffectList) public: MWEffectList(); - typedef MWMechanics::Stat EnchantmentValue; - enum EffectFlags { EF_NoTarget = 0x01, // potions have no target (target is always the player) @@ -236,7 +232,8 @@ namespace MWGui * @param center the effect widgets horizontally * @param various flags, see MWEffectList::EffectFlags */ - void createEffectWidgets(std::vector &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, bool center, int flags); + void createEffectWidgets(std::vector& effects, MyGUI::Widget* creator, + MyGUI::IntCoord& coord, bool center, int flags); protected: virtual ~MWEffectList(); @@ -252,7 +249,7 @@ namespace MWGui class MWSpellEffect final : public MyGUI::Widget { - MYGUI_RTTI_DERIVED( MWSpellEffect ) + MYGUI_RTTI_DERIVED(MWSpellEffect) public: MWSpellEffect(); @@ -269,7 +266,7 @@ namespace MWGui private: static constexpr int sIconOffset = 24; - + void updateWidgets(); SpellEffectParams mEffectParams; @@ -281,12 +278,12 @@ namespace MWGui class MWDynamicStat final : public MyGUI::Widget { - MYGUI_RTTI_DERIVED( MWDynamicStat ) + MYGUI_RTTI_DERIVED(MWDynamicStat) public: MWDynamicStat(); void setValue(int value, int max); - void setTitle(const std::string& text); + void setTitle(std::string_view text); int getValue() const { return mValue; } int getMax() const { return mMax; } @@ -297,7 +294,6 @@ namespace MWGui void initialiseOverride() override; private: - int mValue, mMax; MyGUI::TextBox* mTextWidget; MyGUI::ProgressBar* mBarWidget; diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 84e557fcd..d072efb67 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -4,8 +4,8 @@ #include #include -#include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include @@ -14,8 +14,8 @@ using namespace MWGui; -WindowBase::WindowBase(const std::string& parLayout) - : Layout(parLayout) +WindowBase::WindowBase(std::string_view parLayout) + : Layout(parLayout) { mMainWidget->setVisible(false); @@ -41,7 +41,7 @@ void WindowBase::onTitleDoubleClicked() MWBase::Environment::get().getWindowManager()->toggleMaximized(this); } -void WindowBase::onDoubleClick(MyGUI::Widget *_sender) +void WindowBase::onDoubleClick(MyGUI::Widget* _sender) { onTitleDoubleClicked(); } @@ -71,8 +71,8 @@ void WindowBase::center() layerSize = mMainWidget->getLayer()->getSize(); MyGUI::IntCoord coord = mMainWidget->getCoord(); - coord.left = (layerSize.width - coord.width)/2; - coord.top = (layerSize.height - coord.height)/2; + coord.left = (layerSize.width - coord.width) / 2; + coord.top = (layerSize.height - coord.height) / 2; mMainWidget->setCoord(coord); } @@ -83,10 +83,10 @@ WindowModal::WindowModal(const std::string& parLayout) void WindowModal::onOpen() { - MWBase::Environment::get().getWindowManager()->addCurrentModal(this); //Set so we can escape it if needed + MWBase::Environment::get().getWindowManager()->addCurrentModal(this); // Set so we can escape it if needed MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - MyGUI::InputManager::getInstance ().addWidgetModal (mMainWidget); + MyGUI::InputManager::getInstance().addWidgetModal(mMainWidget); MyGUI::InputManager::getInstance().setKeyFocusWidget(focus); } @@ -94,11 +94,13 @@ void WindowModal::onClose() { MWBase::Environment::get().getWindowManager()->removeCurrentModal(this); - MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget); + MyGUI::InputManager::getInstance().removeWidgetModal(mMainWidget); } -NoDrop::NoDrop(DragAndDrop *drag, MyGUI::Widget *widget) - : mWidget(widget), mDrag(drag), mTransparent(false) +NoDrop::NoDrop(DragAndDrop* drag, MyGUI::Widget* widget) + : mWidget(widget) + , mDrag(drag) + , mTransparent(false) { } @@ -124,12 +126,12 @@ void NoDrop::onFrame(float dt) if (mTransparent) { mWidget->setNeedMouseFocus(false); // Allow click-through - setAlpha(std::max(0.13f, mWidget->getAlpha() - dt*5)); + setAlpha(std::max(0.13f, mWidget->getAlpha() - dt * 5)); } else { mWidget->setNeedMouseFocus(true); - setAlpha(std::min(1.0f, mWidget->getAlpha() + dt*5)); + setAlpha(std::min(1.0f, mWidget->getAlpha() + dt * 5)); } } @@ -139,15 +141,15 @@ void NoDrop::setAlpha(float alpha) mWidget->setAlpha(alpha); } -BookWindowBase::BookWindowBase(const std::string& parLayout) - : WindowBase(parLayout) +BookWindowBase::BookWindowBase(std::string_view parLayout) + : WindowBase(parLayout) { } -float BookWindowBase::adjustButton (char const * name) +float BookWindowBase::adjustButton(std::string_view name) { Gui::ImageButton* button; - WindowBase::getWidget (button, name); + WindowBase::getWidget(button, name); MyGUI::IntSize requested = button->getRequestedSize(); float scale = float(requested.height) / button->getSize().height; MyGUI::IntSize newSize = requested; @@ -160,7 +162,7 @@ float BookWindowBase::adjustButton (char const * name) MyGUI::IntSize diff = (button->getSize() - requested); diff.width /= scale; diff.height /= scale; - button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0)); + button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width, 0)); } return scale; diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 90ef2118d..4bf622a09 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -12,10 +12,10 @@ namespace MWGui { class DragAndDrop; - class WindowBase: public Layout + class WindowBase : public Layout { public: - WindowBase(const std::string& parLayout); + WindowBase(std::string_view parLayout); virtual MyGUI::Widget* getDefaultKeyFocus() { return nullptr; } @@ -31,9 +31,9 @@ namespace MWGui /// Notify that window has been made visible virtual void onOpen() {} /// Notify that window has been hidden - virtual void onClose () {} + virtual void onClose() {} /// Gracefully exits the window - virtual bool exit() {return true;} + virtual bool exit() { return true; } /// Sets the visibility of the window void setVisible(bool visible) override; /// Returns the visibility state of the window @@ -47,6 +47,8 @@ namespace MWGui /// Called when GUI viewport changes size virtual void onResChange(int width, int height) {} + virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) {} + protected: virtual void onTitleDoubleClicked(); @@ -63,7 +65,7 @@ namespace MWGui WindowModal(const std::string& parLayout); void onOpen() override; void onClose() override; - bool exit() override {return true;} + bool exit() override { return true; } }; /// A window that cannot be the target of a drag&drop action. @@ -86,10 +88,10 @@ namespace MWGui class BookWindowBase : public WindowBase { public: - BookWindowBase(const std::string& parLayout); + BookWindowBase(std::string_view parLayout); protected: - float adjustButton (char const * name); + float adjustButton(std::string_view name); }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3a63010c9..70c00ba2e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -3,25 +3,23 @@ #include #include #include +#include #include #include -#include -#include +#include #include +#include #include #include -#include -#include -#include -#include +#include // For BT_NO_PROFILE #include -#include #include +#include /* Start of tes3mp addition @@ -37,183 +35,204 @@ #include -#include -#include - -#include -#include +#include +#include #include -#include #include +#include +#include #include #include +#include #include #include -#include #include #include #include +#include -#include #include +#include + +#include + +#include + #include "../mwbase/inputmanager.hpp" -#include "../mwbase/statemanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/world.hpp" #include "../mwrender/vismask.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/globals.hpp" +#include "../mwworld/player.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" -#include "../mwrender/localmap.hpp" +#include "../mwrender/postprocessor.hpp" -#include "console.hpp" -#include "journalwindow.hpp" -#include "journalviewmodel.hpp" -#include "charactercreation.hpp" -#include "dialogue.hpp" -#include "statswindow.hpp" -#include "messagebox.hpp" -#include "tooltips.hpp" -#include "scrollwindow.hpp" -#include "bookwindow.hpp" -#include "hud.hpp" -#include "mainmenu.hpp" -#include "countdialog.hpp" -#include "tradewindow.hpp" -#include "spellbuyingwindow.hpp" -#include "travelwindow.hpp" -#include "settingswindow.hpp" -#include "confirmationdialog.hpp" #include "alchemywindow.hpp" -#include "spellwindow.hpp" -#include "quickkeysmenu.hpp" -#include "loadingscreen.hpp" -#include "levelupdialog.hpp" -#include "waitdialog.hpp" -#include "enchantingdialog.hpp" -#include "trainingwindow.hpp" -#include "recharge.hpp" -#include "exposedwindow.hpp" -#include "cursor.hpp" -#include "merchantrepair.hpp" -#include "repair.hpp" -#include "soulgemdialog.hpp" -#include "companionwindow.hpp" -#include "inventorywindow.hpp" -#include "bookpage.hpp" -#include "itemview.hpp" -#include "videowidget.hpp" #include "backgroundimage.hpp" -#include "itemwidget.hpp" -#include "screenfader.hpp" -#include "debugwindow.hpp" -#include "spellview.hpp" -#include "draganddrop.hpp" +#include "bookpage.hpp" +#include "bookwindow.hpp" +#include "companionwindow.hpp" +#include "confirmationdialog.hpp" +#include "console.hpp" #include "container.hpp" #include "controllers.hpp" -#include "jailscreen.hpp" +#include "countdialog.hpp" +#include "cursor.hpp" +#include "debugwindow.hpp" +#include "dialogue.hpp" +#include "enchantingdialog.hpp" +#include "exposedwindow.hpp" +#include "hud.hpp" +#include "inventorywindow.hpp" #include "itemchargeview.hpp" +#include "itemview.hpp" +#include "itemwidget.hpp" +#include "jailscreen.hpp" +#include "journalviewmodel.hpp" +#include "journalwindow.hpp" #include "keyboardnavigation.hpp" +#include "levelupdialog.hpp" +#include "loadingscreen.hpp" +#include "mainmenu.hpp" +#include "merchantrepair.hpp" +#include "postprocessorhud.hpp" +#include "quickkeysmenu.hpp" +#include "recharge.hpp" +#include "repair.hpp" #include "resourceskin.hpp" +#include "screenfader.hpp" +#include "scrollwindow.hpp" +#include "settingswindow.hpp" +#include "spellbuyingwindow.hpp" +#include "spellview.hpp" +#include "spellwindow.hpp" +#include "statswindow.hpp" +#include "tradewindow.hpp" +#include "trainingwindow.hpp" +#include "travelwindow.hpp" +#include "ustring.hpp" +#include "videowidget.hpp" +#include "waitdialog.hpp" namespace MWGui { - WindowManager::WindowManager( - SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, - ToUTF8::FromType encoding, bool exportFonts, const std::string& versionDescription, const std::string& userDataPath) - : mOldUpdateMask(0) - , mOldCullMask(0) - , mStore(nullptr) - , mResourceSystem(resourceSystem) - , mWorkQueue(workQueue) - , mViewer(viewer) - , mConsoleOnlyScripts(consoleOnlyScripts) - , mCurrentModals() - , mHud(nullptr) - , mMap(nullptr) - , mLocalMapRender(nullptr) - , mToolTips(nullptr) - , mStatsWindow(nullptr) - , mMessageBoxManager(nullptr) - , mConsole(nullptr) - , mDialogueWindow(nullptr) - , mDragAndDrop(nullptr) - , mInventoryWindow(nullptr) - , mScrollWindow(nullptr) - , mBookWindow(nullptr) - , mCountDialog(nullptr) - , mTradeWindow(nullptr) - , mSettingsWindow(nullptr) - , mConfirmationDialog(nullptr) - , mSpellWindow(nullptr) - , mQuickKeysMenu(nullptr) - , mLoadingScreen(nullptr) - , mWaitDialog(nullptr) - , mSoulgemDialog(nullptr) - , mVideoBackground(nullptr) - , mVideoWidget(nullptr) - , mWerewolfFader(nullptr) - , mBlindnessFader(nullptr) - , mHitFader(nullptr) - , mScreenFader(nullptr) - , mDebugWindow(nullptr) - , mJailScreen(nullptr) - , mTranslationDataStorage (translationDataStorage) - , mCharGen(nullptr) - , mInputBlocker(nullptr) - , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) - , mSubtitlesEnabled(Settings::Manager::getBool ("subtitles", "GUI")) - , mHitFaderEnabled(Settings::Manager::getBool ("hit fader", "GUI")) - , mWerewolfOverlayEnabled(Settings::Manager::getBool ("werewolf overlay", "GUI")) - , mHudEnabled(true) - , mCursorVisible(true) - , mCursorActive(true) - , mPlayerBounty(-1) - , mGui(nullptr) - , mGuiModes() - , mCursorManager(nullptr) - , mGarbageDialogs() - , mShown(GW_ALL) - , mForceHidden(GW_None) - , mAllowed(GW_ALL) - , mRestAllowed(true) - , mShowOwned(0) - , mEncoding(encoding) - , mVersionDescription(versionDescription) - , mWindowVisible(true) + namespace { - mScalingFactor = std::clamp(Settings::Manager::getFloat("scaling factor", "GUI"), 0.5f, 8.f); - mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), mScalingFactor); - mGuiPlatform->initialise(resourcePath, (boost::filesystem::path(logpath) / "MyGUI.log").generic_string()); + Settings::SettingValue* findHiddenSetting(GuiWindow window) + { + switch (window) + { + case GW_Inventory: + return &Settings::windows().mInventoryHidden; + case GW_Map: + return &Settings::windows().mMapHidden; + case GW_Magic: + return &Settings::windows().mSpellsHidden; + case GW_Stats: + return &Settings::windows().mStatsHidden; + default: + return nullptr; + } + } + } - mGui = new MyGUI::Gui; - mGui->initialise(""); + WindowManager::WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, + Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::filesystem::path& logpath, + bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, + const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr) + : mOldUpdateMask(0) + , mOldCullMask(0) + , mStore(nullptr) + , mResourceSystem(resourceSystem) + , mWorkQueue(workQueue) + , mViewer(viewer) + , mConsoleOnlyScripts(consoleOnlyScripts) + , mCurrentModals() + , mHud(nullptr) + , mMap(nullptr) + , mStatsWindow(nullptr) + , mConsole(nullptr) + , mDialogueWindow(nullptr) + , mInventoryWindow(nullptr) + , mScrollWindow(nullptr) + , mBookWindow(nullptr) + , mCountDialog(nullptr) + , mTradeWindow(nullptr) + , mSettingsWindow(nullptr) + , mConfirmationDialog(nullptr) + , mSpellWindow(nullptr) + , mQuickKeysMenu(nullptr) + , mLoadingScreen(nullptr) + , mWaitDialog(nullptr) + , mVideoBackground(nullptr) + , mVideoWidget(nullptr) + , mWerewolfFader(nullptr) + , mBlindnessFader(nullptr) + , mHitFader(nullptr) + , mScreenFader(nullptr) + , mDebugWindow(nullptr) + , mPostProcessorHud(nullptr) + , mJailScreen(nullptr) + , mContainerWindow(nullptr) + , mTranslationDataStorage(translationDataStorage) + , mInputBlocker(nullptr) + , mCrosshairEnabled(Settings::Manager::getBool("crosshair", "HUD")) + , mSubtitlesEnabled(Settings::Manager::getBool("subtitles", "GUI")) + , mHitFaderEnabled(Settings::Manager::getBool("hit fader", "GUI")) + , mWerewolfOverlayEnabled(Settings::Manager::getBool("werewolf overlay", "GUI")) + , mHudEnabled(true) + , mCursorVisible(true) + , mCursorActive(true) + , mPlayerBounty(-1) + , mGuiModes() + , mGarbageDialogs() + , mShown(GW_ALL) + , mForceHidden(GW_None) + , mAllowed(GW_ALL) + , mRestAllowed(true) + , mEncoding(encoding) + , mVersionDescription(versionDescription) + , mWindowVisible(true) + , mCfgMgr(cfgMgr) + { + int w, h; + SDL_GetWindowSize(window, &w, &h); + int dw, dh; + SDL_GL_GetDrawableSize(window, &dw, &dh); + + mScalingFactor = std::clamp(Settings::Manager::getFloat("scaling factor", "GUI"), 0.5f, 8.f) * (dw / w); + mGuiPlatform = std::make_unique(viewer, guiRoot, resourceSystem->getImageManager(), + resourceSystem->getVFS(), mScalingFactor, "mygui", logpath / "MyGUI.log"); + + mGui = std::make_unique(); + mGui->initialise({}); createTextures(); MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Load fonts - mFontLoader.reset(new Gui::FontLoader(encoding, resourceSystem->getVFS(), userDataPath, mScalingFactor)); - mFontLoader->loadBitmapFonts(exportFonts); + mFontLoader = std::make_unique(encoding, resourceSystem->getVFS(), mScalingFactor); - //Register own widgets with MyGUI + // Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -225,34 +244,40 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Layer"); MyGUI::FactoryManager::getInstance().registerFactory("Layer"); - BookPage::registerMyGUIComponents (); + BookPage::registerMyGUIComponents(); + PostProcessorHud::registerMyGUIComponents(); ItemView::registerComponents(); ItemChargeView::registerComponents(); ItemWidget::registerComponents(); SpellView::registerComponents(); Gui::registerAllWidgets(); + LuaUi::registerAllWidgets(); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); - MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); - MyGUI::FactoryManager::getInstance().registerFactory("Resource", "AutoSizedResourceSkin"); + MyGUI::FactoryManager::getInstance().registerFactory( + "Resource", "ResourceImageSetPointer"); + MyGUI::FactoryManager::getInstance().registerFactory( + "Resource", "AutoSizedResourceSkin"); MyGUI::ResourceManager::getInstance().load("core.xml"); - WindowManager::loadUserFonts(); bool keyboardNav = Settings::Manager::getBool("keyboard navigation", "GUI"); - mKeyboardNavigation.reset(new KeyboardNavigation()); + mKeyboardNavigation = std::make_unique(); mKeyboardNavigation->setEnabled(keyboardNav); Gui::ImageButton::setDefaultNeedKeyFocus(keyboardNav); - mLoadingScreen = new LoadingScreen(mResourceSystem, mViewer); - mWindows.push_back(mLoadingScreen); + auto loadingScreen = std::make_unique(mResourceSystem, mViewer); + mLoadingScreen = loadingScreen.get(); + mWindows.push_back(std::move(loadingScreen)); - //set up the hardware cursor manager - mCursorManager = new SDLUtil::SDLCursorManager(); + // set up the hardware cursor manager + mCursorManager = std::make_unique(); - MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange); + MyGUI::PointerManager::getInstance().eventChangeMousePointer + += MyGUI::newDelegate(this, &WindowManager::onCursorChange); - MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); + MyGUI::InputManager::getInstance().eventChangeKeyFocus + += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); // Create all cursors in advance createCursors(); @@ -262,14 +287,14 @@ namespace MWGui // hide mygui's pointer MyGUI::PointerManager::getInstance().setVisible(false); - mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Default, "InputBlocker"); + mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal( + "ImageBox", 0, 0, 1, 1, MyGUI::Align::Default, "Video"); mVideoBackground->setImageTexture("black"); mVideoBackground->setVisible(false); mVideoBackground->setNeedMouseFocus(true); mVideoBackground->setNeedKeyFocus(true); - mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); + mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0, 0, 1, 1, MyGUI::Align::Default); mVideoWidget->setNeedMouseFocus(true); mVideoWidget->setNeedKeyFocus(true); mVideoWidget->setVFS(resourceSystem->getVFS()); @@ -278,21 +303,19 @@ namespace MWGui MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); - MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged); - MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); + MyGUI::ClipboardManager::getInstance().eventClipboardChanged + += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged); + MyGUI::ClipboardManager::getInstance().eventClipboardRequested + += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); - mShowOwned = Settings::Manager::getInt("show owned", "Game"); + mVideoWrapper = std::make_unique(window, viewer); + mVideoWrapper->setGammaContrast( + Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); - mVideoWrapper = new SDLUtil::VideoWrapper(window, viewer); - mVideoWrapper->setGammaContrast(Settings::Manager::getFloat("gamma", "Video"), - Settings::Manager::getFloat("contrast", "Video")); + if (useShaders) + mGuiPlatform->getRenderManagerPtr()->enableShaders(mResourceSystem->getSceneManager()->getShaderManager()); - mStatsWatcher.reset(new StatsWatcher()); - } - - void WindowManager::loadUserFonts() - { - mFontLoader->loadTrueTypeFonts(); + mStatsWatcher = std::make_unique(); } void WindowManager::initUI() @@ -303,68 +326,78 @@ namespace MWGui mTextColours.loadColours(); - mDragAndDrop = new DragAndDrop(); + mDragAndDrop = std::make_unique(); - Recharge* recharge = new Recharge(); - mGuiModeStates[GM_Recharge] = GuiModeState(recharge); - mWindows.push_back(recharge); + auto recharge = std::make_unique(); + mGuiModeStates[GM_Recharge] = GuiModeState(recharge.get()); + mWindows.push_back(std::move(recharge)); - MainMenu* menu = new MainMenu(w, h, mResourceSystem->getVFS(), mVersionDescription); - mGuiModeStates[GM_MainMenu] = GuiModeState(menu); - mWindows.push_back(menu); + auto menu = std::make_unique(w, h, mResourceSystem->getVFS(), mVersionDescription); + mGuiModeStates[GM_MainMenu] = GuiModeState(menu.get()); + mWindows.push_back(std::move(menu)); - mLocalMapRender = new MWRender::LocalMap(mViewer->getSceneData()->asGroup()); - mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender, mWorkQueue); - mWindows.push_back(mMap); + mLocalMapRender = std::make_unique(mViewer->getSceneData()->asGroup()); + auto map = std::make_unique(mCustomMarkers, mDragAndDrop.get(), mLocalMapRender.get(), mWorkQueue); + mMap = map.get(); + mWindows.push_back(std::move(map)); mMap->renderGlobalMap(); - trackWindow(mMap, "map"); + trackWindow(mMap, makeMapWindowSettingValues()); - mStatsWindow = new StatsWindow(mDragAndDrop); - mWindows.push_back(mStatsWindow); - trackWindow(mStatsWindow, "stats"); + auto statsWindow = std::make_unique(mDragAndDrop.get()); + mStatsWindow = statsWindow.get(); + mWindows.push_back(std::move(statsWindow)); + trackWindow(mStatsWindow, makeStatsWindowSettingValues()); - mInventoryWindow = new InventoryWindow(mDragAndDrop, mViewer->getSceneData()->asGroup(), mResourceSystem); - mWindows.push_back(mInventoryWindow); + auto inventoryWindow = std::make_unique( + mDragAndDrop.get(), mViewer->getSceneData()->asGroup(), mResourceSystem); + mInventoryWindow = inventoryWindow.get(); + mWindows.push_back(std::move(inventoryWindow)); - mSpellWindow = new SpellWindow(mDragAndDrop); - mWindows.push_back(mSpellWindow); - trackWindow(mSpellWindow, "spells"); + auto spellWindow = std::make_unique(mDragAndDrop.get()); + mSpellWindow = spellWindow.get(); + mWindows.push_back(std::move(spellWindow)); + trackWindow(mSpellWindow, makeSpellsWindowSettingValues()); - mGuiModeStates[GM_Inventory] = GuiModeState({mMap, mInventoryWindow, mSpellWindow, mStatsWindow}); - mGuiModeStates[GM_None] = GuiModeState({mMap, mInventoryWindow, mSpellWindow, mStatsWindow}); + mGuiModeStates[GM_Inventory] = GuiModeState({ mMap, mInventoryWindow, mSpellWindow, mStatsWindow }); + mGuiModeStates[GM_None] = GuiModeState({ mMap, mInventoryWindow, mSpellWindow, mStatsWindow }); - mTradeWindow = new TradeWindow(); - mWindows.push_back(mTradeWindow); - trackWindow(mTradeWindow, "barter"); - mGuiModeStates[GM_Barter] = GuiModeState({mInventoryWindow, mTradeWindow}); + auto tradeWindow = std::make_unique(); + mTradeWindow = tradeWindow.get(); + mWindows.push_back(std::move(tradeWindow)); + trackWindow(mTradeWindow, makeBarterWindowSettingValues()); + mGuiModeStates[GM_Barter] = GuiModeState({ mInventoryWindow, mTradeWindow }); - mConsole = new Console(w,h, mConsoleOnlyScripts); - mWindows.push_back(mConsole); - trackWindow(mConsole, "console"); + auto console = std::make_unique(w, h, mConsoleOnlyScripts, mCfgMgr); + mConsole = console.get(); + mWindows.push_back(std::move(console)); + trackWindow(mConsole, makeConsoleWindowSettingValues()); bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); - JournalWindow* journal = JournalWindow::create(JournalViewModel::create (), questList, mEncoding); - mWindows.push_back(journal); - mGuiModeStates[GM_Journal] = GuiModeState(journal); - mGuiModeStates[GM_Journal].mCloseSound = "book close"; - mGuiModeStates[GM_Journal].mOpenSound = "book open"; + auto journal = JournalWindow::create(JournalViewModel::create(), questList, mEncoding); + mGuiModeStates[GM_Journal] = GuiModeState(journal.get()); + mGuiModeStates[GM_Journal].mCloseSound = ESM::RefId::stringRefId("book close"); + mGuiModeStates[GM_Journal].mOpenSound = ESM::RefId::stringRefId("book open"); + mWindows.push_back(std::move(journal)); - mMessageBoxManager = new MessageBoxManager(mStore->get().find("fMessageTimePerChar")->mValue.getFloat()); + mMessageBoxManager = std::make_unique( + mStore->get().find("fMessageTimePerChar")->mValue.getFloat()); - SpellBuyingWindow* spellBuyingWindow = new SpellBuyingWindow(); - mWindows.push_back(spellBuyingWindow); - mGuiModeStates[GM_SpellBuying] = GuiModeState(spellBuyingWindow); + auto spellBuyingWindow = std::make_unique(); + mGuiModeStates[GM_SpellBuying] = GuiModeState(spellBuyingWindow.get()); + mWindows.push_back(std::move(spellBuyingWindow)); - TravelWindow* travelWindow = new TravelWindow(); - mWindows.push_back(travelWindow); - mGuiModeStates[GM_Travel] = GuiModeState(travelWindow); + auto travelWindow = std::make_unique(); + mGuiModeStates[GM_Travel] = GuiModeState(travelWindow.get()); + mWindows.push_back(std::move(travelWindow)); - mDialogueWindow = new DialogueWindow(); - mWindows.push_back(mDialogueWindow); - trackWindow(mDialogueWindow, "dialogue"); + auto dialogueWindow = std::make_unique(); + mDialogueWindow = dialogueWindow.get(); + mWindows.push_back(std::move(dialogueWindow)); + trackWindow(mDialogueWindow, makeDialogueWindowSettingValues()); mGuiModeStates[GM_Dialogue] = GuiModeState(mDialogueWindow); mTradeWindow->eventTradeDone += MyGUI::newDelegate(mDialogueWindow, &DialogueWindow::onTradeComplete); +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -378,114 +411,142 @@ namespace MWGui /* End of tes3mp change (major) */ +======= + auto containerWindow = std::make_unique(mDragAndDrop.get()); + mContainerWindow = containerWindow.get(); + mWindows.push_back(std::move(containerWindow)); + trackWindow(mContainerWindow, makeContainerWindowSettingValues()); + mGuiModeStates[GM_Container] = GuiModeState({ mContainerWindow, mInventoryWindow }); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender); - mWindows.push_back(mHud); + auto hud = std::make_unique(mCustomMarkers, mDragAndDrop.get(), mLocalMapRender.get()); + mHud = hud.get(); + mWindows.push_back(std::move(hud)); - mToolTips = new ToolTips(); + mToolTips = std::make_unique(); - mScrollWindow = new ScrollWindow(); - mWindows.push_back(mScrollWindow); + auto scrollWindow = std::make_unique(); + mScrollWindow = scrollWindow.get(); + mWindows.push_back(std::move(scrollWindow)); mGuiModeStates[GM_Scroll] = GuiModeState(mScrollWindow); - mGuiModeStates[GM_Scroll].mOpenSound = "scroll"; - mGuiModeStates[GM_Scroll].mCloseSound = "scroll"; + mGuiModeStates[GM_Scroll].mOpenSound = ESM::RefId::stringRefId("scroll"); + mGuiModeStates[GM_Scroll].mCloseSound = ESM::RefId::stringRefId("scroll"); - mBookWindow = new BookWindow(); - mWindows.push_back(mBookWindow); + auto bookWindow = std::make_unique(); + mBookWindow = bookWindow.get(); + mWindows.push_back(std::move(bookWindow)); mGuiModeStates[GM_Book] = GuiModeState(mBookWindow); - mGuiModeStates[GM_Book].mOpenSound = "book open"; - mGuiModeStates[GM_Book].mCloseSound = "book close"; + mGuiModeStates[GM_Book].mOpenSound = ESM::RefId::stringRefId("book open"); + mGuiModeStates[GM_Book].mCloseSound = ESM::RefId::stringRefId("book close"); - mCountDialog = new CountDialog(); - mWindows.push_back(mCountDialog); + auto countDialog = std::make_unique(); + mCountDialog = countDialog.get(); + mWindows.push_back(std::move(countDialog)); - mSettingsWindow = new SettingsWindow(); - mWindows.push_back(mSettingsWindow); + auto settingsWindow = std::make_unique(); + mSettingsWindow = settingsWindow.get(); + mWindows.push_back(std::move(settingsWindow)); + trackWindow(mSettingsWindow, makeSettingsWindowSettingValues()); mGuiModeStates[GM_Settings] = GuiModeState(mSettingsWindow); - mConfirmationDialog = new ConfirmationDialog(); - mWindows.push_back(mConfirmationDialog); + auto confirmationDialog = std::make_unique(); + mConfirmationDialog = confirmationDialog.get(); + mWindows.push_back(std::move(confirmationDialog)); - AlchemyWindow* alchemyWindow = new AlchemyWindow(); - mWindows.push_back(alchemyWindow); - trackWindow(alchemyWindow, "alchemy"); - mGuiModeStates[GM_Alchemy] = GuiModeState(alchemyWindow); + auto alchemyWindow = std::make_unique(); + trackWindow(alchemyWindow.get(), makeAlchemyWindowSettingValues()); + mGuiModeStates[GM_Alchemy] = GuiModeState(alchemyWindow.get()); + mWindows.push_back(std::move(alchemyWindow)); - mQuickKeysMenu = new QuickKeysMenu(); - mWindows.push_back(mQuickKeysMenu); + auto quickKeysMenu = std::make_unique(); + mQuickKeysMenu = quickKeysMenu.get(); + mWindows.push_back(std::move(quickKeysMenu)); mGuiModeStates[GM_QuickKeysMenu] = GuiModeState(mQuickKeysMenu); - LevelupDialog* levelupDialog = new LevelupDialog(); - mWindows.push_back(levelupDialog); - mGuiModeStates[GM_Levelup] = GuiModeState(levelupDialog); + auto levelupDialog = std::make_unique(); + mGuiModeStates[GM_Levelup] = GuiModeState(levelupDialog.get()); + mWindows.push_back(std::move(levelupDialog)); - mWaitDialog = new WaitDialog(); - mWindows.push_back(mWaitDialog); - mGuiModeStates[GM_Rest] = GuiModeState({mWaitDialog->getProgressBar(), mWaitDialog}); + auto waitDialog = std::make_unique(); + mWaitDialog = waitDialog.get(); + mWindows.push_back(std::move(waitDialog)); + mGuiModeStates[GM_Rest] = GuiModeState({ mWaitDialog->getProgressBar(), mWaitDialog }); - SpellCreationDialog* spellCreationDialog = new SpellCreationDialog(); - mWindows.push_back(spellCreationDialog); - mGuiModeStates[GM_SpellCreation] = GuiModeState(spellCreationDialog); + auto spellCreationDialog = std::make_unique(); + mGuiModeStates[GM_SpellCreation] = GuiModeState(spellCreationDialog.get()); + mWindows.push_back(std::move(spellCreationDialog)); - EnchantingDialog* enchantingDialog = new EnchantingDialog(); - mWindows.push_back(enchantingDialog); - mGuiModeStates[GM_Enchanting] = GuiModeState(enchantingDialog); + auto enchantingDialog = std::make_unique(); + mGuiModeStates[GM_Enchanting] = GuiModeState(enchantingDialog.get()); + mWindows.push_back(std::move(enchantingDialog)); - TrainingWindow* trainingWindow = new TrainingWindow(); - mWindows.push_back(trainingWindow); - mGuiModeStates[GM_Training] = GuiModeState({trainingWindow->getProgressBar(), trainingWindow}); + auto trainingWindow = std::make_unique(); + mGuiModeStates[GM_Training] = GuiModeState({ trainingWindow->getProgressBar(), trainingWindow.get() }); + mWindows.push_back(std::move(trainingWindow)); - MerchantRepair* merchantRepair = new MerchantRepair(); - mWindows.push_back(merchantRepair); - mGuiModeStates[GM_MerchantRepair] = GuiModeState(merchantRepair); + auto merchantRepair = std::make_unique(); + mGuiModeStates[GM_MerchantRepair] = GuiModeState(merchantRepair.get()); + mWindows.push_back(std::move(merchantRepair)); - Repair* repair = new Repair(); - mWindows.push_back(repair); - mGuiModeStates[GM_Repair] = GuiModeState(repair); + auto repair = std::make_unique(); + mGuiModeStates[GM_Repair] = GuiModeState(repair.get()); + mWindows.push_back(std::move(repair)); - mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); + mSoulgemDialog = std::make_unique(mMessageBoxManager.get()); - CompanionWindow* companionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); - mWindows.push_back(companionWindow); - trackWindow(companionWindow, "companion"); - mGuiModeStates[GM_Companion] = GuiModeState({mInventoryWindow, companionWindow}); + auto companionWindow = std::make_unique(mDragAndDrop.get(), mMessageBoxManager.get()); + trackWindow(companionWindow.get(), makeCompanionWindowSettingValues()); + mGuiModeStates[GM_Companion] = GuiModeState({ mInventoryWindow, companionWindow.get() }); + mWindows.push_back(std::move(companionWindow)); - mJailScreen = new JailScreen(); - mWindows.push_back(mJailScreen); + auto jailScreen = std::make_unique(); + mJailScreen = jailScreen.get(); + mWindows.push_back(std::move(jailScreen)); mGuiModeStates[GM_Jail] = GuiModeState(mJailScreen); std::string werewolfFaderTex = "textures\\werewolfoverlay.dds"; if (mResourceSystem->getVFS()->exists(werewolfFaderTex)) { - mWerewolfFader = new ScreenFader(werewolfFaderTex); - mWindows.push_back(mWerewolfFader); + auto werewolfFader = std::make_unique(werewolfFaderTex); + mWerewolfFader = werewolfFader.get(); + mWindows.push_back(std::move(werewolfFader)); } - mBlindnessFader = new ScreenFader("black"); - mWindows.push_back(mBlindnessFader); + auto blindnessFader = std::make_unique("black"); + mBlindnessFader = blindnessFader.get(); + mWindows.push_back(std::move(blindnessFader)); // fall back to player_hit_01.dds if bm_player_hit_01.dds is not available std::string hitFaderTexture = "textures\\bm_player_hit_01.dds"; const std::string hitFaderLayout = "openmw_screen_fader_hit.layout"; - MyGUI::FloatCoord hitFaderCoord (0,0,1,1); - if(!mResourceSystem->getVFS()->exists(hitFaderTexture)) + MyGUI::FloatCoord hitFaderCoord(0, 0, 1, 1); + if (!mResourceSystem->getVFS()->exists(hitFaderTexture)) { hitFaderTexture = "textures\\player_hit_01.dds"; hitFaderCoord = MyGUI::FloatCoord(0.2, 0.25, 0.6, 0.5); } - mHitFader = new ScreenFader(hitFaderTexture, hitFaderLayout, hitFaderCoord); - mWindows.push_back(mHitFader); + auto hitFader = std::make_unique(hitFaderTexture, hitFaderLayout, hitFaderCoord); + mHitFader = hitFader.get(); + mWindows.push_back(std::move(hitFader)); - mScreenFader = new ScreenFader("black"); - mWindows.push_back(mScreenFader); + auto screenFader = std::make_unique("black"); + mScreenFader = screenFader.get(); + mWindows.push_back(std::move(screenFader)); - mDebugWindow = new DebugWindow(); - mWindows.push_back(mDebugWindow); + auto debugWindow = std::make_unique(); + mDebugWindow = debugWindow.get(); + mWindows.push_back(std::move(debugWindow)); - mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker"); + auto postProcessorHud = std::make_unique(); + mPostProcessorHud = postProcessorHud.get(); + mWindows.push_back(std::move(postProcessorHud)); + trackWindow(mPostProcessorHud, makePostprocessorWindowSettingValues()); + + mInputBlocker = MyGUI::Gui::getInstance().createWidget( + {}, 0, 0, w, h, MyGUI::Align::Stretch, "InputBlocker"); mHud->setVisible(true); - mCharGen = new CharacterCreation(mViewer->getSceneData()->asGroup(), mResourceSystem); + mCharGen = std::make_unique(mViewer->getSceneData()->asGroup(), mResourceSystem); updatePinnedWindows(); @@ -494,7 +555,7 @@ namespace MWGui mStatsWatcher->addListener(mHud); mStatsWatcher->addListener(mStatsWindow); - mStatsWatcher->addListener(mCharGen); + mStatsWatcher->addListener(mCharGen.get()); } int WindowManager::getFontHeight() const @@ -508,10 +569,9 @@ namespace MWGui { disallowAll(); - mStatsWatcher->removeListener(mCharGen); - delete mCharGen; - mCharGen = new CharacterCreation(mViewer->getSceneData()->asGroup(), mResourceSystem); - mStatsWatcher->addListener(mCharGen); + mStatsWatcher->removeListener(mCharGen.get()); + mCharGen = std::make_unique(mViewer->getSceneData()->asGroup(), mResourceSystem); + mStatsWatcher->addListener(mCharGen.get()); } else allow(GW_ALL); @@ -523,6 +583,8 @@ namespace MWGui { try { + LuaUi::clearUserInterface(); + mStatsWatcher.reset(); MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); @@ -531,17 +593,10 @@ namespace MWGui MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); - for (WindowBase* window : mWindows) - delete window; mWindows.clear(); - - delete mMessageBoxManager; - delete mLocalMapRender; - delete mCharGen; - delete mDragAndDrop; - delete mSoulgemDialog; - delete mCursorManager; - delete mToolTips; + mMessageBoxManager.reset(); + mToolTips.reset(); + mCharGen.reset(); mKeyboardNavigation.reset(); @@ -550,19 +605,16 @@ namespace MWGui mFontLoader.reset(); mGui->shutdown(); - delete mGui; mGuiPlatform->shutdown(); - delete mGuiPlatform; - delete mVideoWrapper; } - catch(const MyGUI::Exception& e) + catch (const MyGUI::Exception& e) { Log(Debug::Error) << "Error in the destructor: " << e.what(); } } - void WindowManager::setStore(const MWWorld::ESMStore &store) + void WindowManager::setStore(const MWWorld::ESMStore& store) { mStore = &store; } @@ -570,30 +622,23 @@ namespace MWGui void WindowManager::cleanupGarbage() { // Delete any dialogs which are no longer in use - if (!mGarbageDialogs.empty()) - { - for (Layout* widget : mGarbageDialogs) - { - delete widget; - } - mGarbageDialogs.clear(); - } + mGarbageDialogs.clear(); } void WindowManager::enableScene(bool enable) { - unsigned int disablemask = MWRender::Mask_GUI|MWRender::Mask_PreCompile; - if (!enable && mViewer->getCamera()->getCullMask() != disablemask) + unsigned int disablemask = MWRender::Mask_GUI | MWRender::Mask_PreCompile; + if (!enable && getCullMask() != disablemask) { mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); - mOldCullMask = mViewer->getCamera()->getCullMask(); + mOldCullMask = getCullMask(); mViewer->getUpdateVisitor()->setTraversalMask(disablemask); - mViewer->getCamera()->setCullMask(disablemask); + setCullMask(disablemask); } - else if (enable && mViewer->getCamera()->getCullMask() == disablemask) + else if (enable && getCullMask() == disablemask) { mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask); - mViewer->getCamera()->setCullMask(mOldCullMask); + setCullMask(mOldCullMask); } } @@ -606,7 +651,8 @@ namespace MWGui { bool loading = (getMode() == GM_Loading || getMode() == GM_LoadingWallpaper); - bool mainmenucover = containsMode(GM_MainMenu) && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; + bool mainmenucover = containsMode(GM_MainMenu) + && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; enableScene(!loading && !mainmenucover); @@ -620,7 +666,7 @@ namespace MWGui MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode); - mInputBlocker->setVisible (gameMode); + mInputBlocker->setVisible(gameMode); if (loading) setCursorVisible(mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); @@ -628,11 +674,12 @@ namespace MWGui setCursorVisible(!gameMode); if (gameMode) - setKeyFocusWidget (nullptr); + setKeyFocusWidget(nullptr); // Icons of forced hidden windows are displayed setMinimapVisibility((mAllowed & GW_Map) && (!mMap->pinned() || (mForceHidden & GW_Map))); - setWeaponVisibility((mAllowed & GW_Inventory) && (!mInventoryWindow->pinned() || (mForceHidden & GW_Inventory))); + setWeaponVisibility( + (mAllowed & GW_Inventory) && (!mInventoryWindow->pinned() || (mForceHidden & GW_Inventory))); setSpellVisibility((mAllowed & GW_Magic) && (!mSpellWindow->pinned() || (mForceHidden & GW_Magic))); setHMSVisibility((mAllowed & GW_Stats) && (!mStatsWindow->pinned() || (mForceHidden & GW_Stats))); @@ -642,9 +689,12 @@ namespace MWGui if (mGuiModes.empty()) { mMap->setVisible(mMap->pinned() && !isConsoleMode() && !(mForceHidden & GW_Map) && (mAllowed & GW_Map)); - mStatsWindow->setVisible(mStatsWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Stats) && (mAllowed & GW_Stats)); - mInventoryWindow->setVisible(mInventoryWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Inventory) && (mAllowed & GW_Inventory)); - mSpellWindow->setVisible(mSpellWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Magic) && (mAllowed & GW_Magic)); + mStatsWindow->setVisible( + mStatsWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Stats) && (mAllowed & GW_Stats)); + mInventoryWindow->setVisible(mInventoryWindow->pinned() && !isConsoleMode() + && !(mForceHidden & GW_Inventory) && (mAllowed & GW_Inventory)); + mSpellWindow->setVisible( + mSpellWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Magic) && (mAllowed & GW_Magic)); return; } else if (getMode() != GM_Inventory) @@ -652,7 +702,9 @@ namespace MWGui mMap->setVisible(false); mStatsWindow->setVisible(false); mSpellWindow->setVisible(false); - mInventoryWindow->setVisible(getMode() == GM_Container || getMode() == GM_Barter || getMode() == GM_Companion); + mHud->setDrowningBarVisible(false); + mInventoryWindow->setVisible( + getMode() == GM_Container || getMode() == GM_Barter || getMode() == GM_Companion); } GuiMode mode = mGuiModes.back(); @@ -674,6 +726,7 @@ namespace MWGui switch (mode) { +<<<<<<< HEAD // FIXME: refactor chargen windows to use modes properly (or not use them at all) case GM_Name: case GM_Race: @@ -696,20 +749,35 @@ namespace MWGui End of tes3mp addition */ break; +======= + // FIXME: refactor chargen windows to use modes properly (or not use them at all) + case GM_Name: + case GM_Race: + case GM_Class: + case GM_ClassPick: + case GM_ClassCreate: + case GM_Birth: + case GM_ClassGenerate: + case GM_Review: + mCharGen->spawnDialog(mode); + break; + default: + break; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } - void WindowManager::setDrowningTimeLeft (float time, float maxTime) + void WindowManager::setDrowningTimeLeft(float time, float maxTime) { mHud->setDrowningTimeLeft(time, maxTime); } - void WindowManager::removeDialog(Layout*dialog) + void WindowManager::removeDialog(std::unique_ptr&& dialog) { if (!dialog) return; dialog->setVisible(false); - mGarbageDialogs.push_back(dialog); + mGarbageDialogs.push_back(std::move(dialog)); } void WindowManager::exitCurrentGuiMode() @@ -721,13 +789,13 @@ namespace MWGui } GuiModeState& state = mGuiModeStates[mGuiModes.back()]; - for (WindowBase* window : state.mWindows) + for (const auto& window : state.mWindows) { if (!window->exit()) { // unable to exit window, but give access to main menu if (!MyGUI::InputManager::getInstance().isModalAny() && getMode() != GM_MainMenu) - pushGuiMode (GM_MainMenu); + pushGuiMode(GM_MainMenu); return; } } @@ -735,6 +803,7 @@ namespace MWGui popGuiMode(); } +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -744,6 +813,10 @@ namespace MWGui Use the hasServerOrigin argument when creating an interactive message box */ void WindowManager::interactiveMessageBox(const std::string &message, const std::vector &buttons, bool block, bool hasServerOrigin) +======= + void WindowManager::interactiveMessageBox( + std::string_view message, const std::vector& buttons, bool block) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { mMessageBoxManager->createInteractiveMessageBox(message, buttons, hasServerOrigin); /* @@ -753,11 +826,14 @@ namespace MWGui if (block) { - Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); + Misc::FrameRateLimiter frameRateLimiter + = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); while (mMessageBoxManager->readPressedButton(false) == -1 - && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) + && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - const double dt = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()).count(); + const double dt + = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()) + .count(); mKeyboardNavigation->onFrame(); mMessageBoxManager->onFrame(dt); @@ -781,16 +857,25 @@ namespace MWGui } } - void WindowManager::messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode) + void WindowManager::messageBox(std::string_view message, enum MWGui::ShowInDialogueMode showInDialogueMode) { - if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) { - mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message)); - } else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) { + if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) + { + MyGUI::UString text = MyGUI::LanguageManager::getInstance().replaceTags(toUString(message)); + mDialogueWindow->addMessageBox(text.asUTF8()); + } + else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) + { mMessageBoxManager->createMessageBox(message); } } - void WindowManager::staticMessageBox(const std::string& message) + void WindowManager::scheduleMessageBox(std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode) + { + mScheduledMessageBoxes.lock()->emplace_back(std::move(message), showInDialogueMode); + } + + void WindowManager::staticMessageBox(std::string_view message) { mMessageBoxManager->createMessageBox(message, true); } @@ -800,16 +885,16 @@ namespace MWGui mMessageBoxManager->removeStaticMessageBox(); } - int WindowManager::readPressedButton () + int WindowManager::readPressedButton() { return mMessageBoxManager->readPressedButton(); } - std::string WindowManager::getGameSettingString(const std::string &id, const std::string &default_) + std::string_view WindowManager::getGameSettingString(std::string_view id, std::string_view default_) { - const ESM::GameSetting *setting = mStore->get().search(id); + const ESM::GameSetting* setting = mStore->get().search(id); - if (setting && setting->mValue.getType()==ESM::VT_String) + if (setting && setting->mValue.getType() == ESM::VT_String) return setting->mValue.getString(); return default_; @@ -823,11 +908,11 @@ namespace MWGui MWWorld::ConstPtr player = MWMechanics::getPlayer(); osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3(); - osg::Quat playerOrientation (-player.getRefData().getPosition().rot[2], osg::Vec3(0,0,1)); + osg::Quat playerOrientation(-player.getRefData().getPosition().rot[2], osg::Vec3(0, 0, 1)); osg::Vec3f playerdirection; - int x,y; - float u,v; + int x, y; + float u, v; mLocalMapRender->updatePlayer(playerPosition, playerOrientation, u, v, x, y, playerdirection); if (!player.getCell()->isExterior()) @@ -842,10 +927,12 @@ namespace MWGui mHud->setPlayerPos(x, y, u, v); } - void WindowManager::update (float frameDuration) + void WindowManager::update(float frameDuration) { - bool gameRunning = MWBase::Environment::get().getStateManager()->getState()!= - MWBase::StateManager::State_NoGame; + handleScheduledMessageBoxes(); + + bool gameRunning + = MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame; if (gameRunning) updateMap(); @@ -887,9 +974,11 @@ namespace MWGui // 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()) + if (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox() + && mCurrentModals.back() != mMessageBoxManager->getInteractiveMessageBox()) { - std::vector::iterator found = std::find(mCurrentModals.begin(), mCurrentModals.end(), mMessageBoxManager->getInteractiveMessageBox()); + std::vector::iterator found = std::find( + mCurrentModals.begin(), mCurrentModals.end(), mMessageBoxManager->getInteractiveMessageBox()); if (found != mCurrentModals.end()) { WindowModal* msgbox = *found; @@ -913,13 +1002,19 @@ namespace MWGui if (mLocalMapRender) mLocalMapRender->cleanupCameras(); + mDebugWindow->onFrame(frameDuration); + + if (isConsoleMode()) + mConsole->onFrame(frameDuration); + if (!gameRunning) return; // We should display message about crime only once per frame, even if there are several crimes. // Otherwise we will get message spam when stealing several items via Take All button. const MWWorld::Ptr player = MWMechanics::getPlayer(); - int currentBounty = player.getClass().getNpcStats(player).getBounty(); + const MWWorld::Class& playerCls = player.getClass(); + int currentBounty = playerCls.getNpcStats(player).getBounty(); if (currentBounty != mPlayerBounty) { if (mPlayerBounty >= 0 && currentBounty > mPlayerBounty) @@ -928,11 +1023,31 @@ namespace MWGui mPlayerBounty = currentBounty; } + MWBase::LuaManager::ActorControls* playerControls + = MWBase::Environment::get().getLuaManager()->getActorControls(player); + bool triedToMove = playerControls + && (playerControls->mMovement != 0 || playerControls->mSideMovement != 0 || playerControls->mJump); + if (triedToMove && playerCls.getEncumbrance(player) > playerCls.getCapacity(player)) + { + const auto& msgboxs = mMessageBoxManager->getActiveMessageBoxes(); + auto it + = std::find_if(msgboxs.begin(), msgboxs.end(), [](const std::unique_ptr& msgbox) { + return (msgbox->getMessage() == "#{sNotifyMessage59}"); + }); + + // if an overencumbered messagebox is already present, reset its expiry timer, + // otherwise create a new one. + if (it != msgboxs.end()) + (*it)->mCurrentTime = 0; + else + messageBox("#{sNotifyMessage59}"); + } + mDragAndDrop->onFrame(); mHud->onFrame(frameDuration); - mDebugWindow->onFrame(frameDuration); + mPostProcessorHud->onFrame(frameDuration); if (mCharGen) mCharGen->onFrame(frameDuration); @@ -948,24 +1063,25 @@ namespace MWGui { mMap->requestMapRender(cell); - std::string name = MWBase::Environment::get().getWorld()->getCellName (cell); + std::string name{ MWBase::Environment::get().getWorld()->getCellName(cell) }; - mMap->setCellName( name ); - mHud->setCellName( name ); + mMap->setCellName(name); + mHud->setCellName(name); + auto cellCommon = cell->getCell(); - if (cell->getCell()->isExterior()) + if (cellCommon->isExterior()) { - if (!cell->getCell()->mName.empty()) - mMap->addVisitedLocation (name, cell->getCell()->getGridX (), cell->getCell()->getGridY ()); + if (!cellCommon->getNameId().empty()) + mMap->addVisitedLocation(name, cellCommon->getGridX(), cellCommon->getGridY()); - mMap->cellExplored (cell->getCell()->getGridX(), cell->getCell()->getGridY()); + mMap->cellExplored(cellCommon->getGridX(), cellCommon->getGridY()); - setActiveMap(cell->getCell()->getGridX(), cell->getCell()->getGridY(), false); + setActiveMap(cellCommon->getGridX(), cellCommon->getGridY(), false); } else { - mMap->setCellPrefix (cell->getCell()->mName ); - mHud->setCellPrefix (cell->getCell()->mName ); + mMap->setCellPrefix(std::string(cellCommon->getNameId())); + mHud->setCellPrefix(std::string(cellCommon->getNameId())); osg::Vec3f worldPos; if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos)) @@ -994,8 +1110,8 @@ namespace MWGui void WindowManager::setActiveMap(int x, int y, bool interior) { - mMap->setActiveCell(x,y, interior); - mHud->setActiveCell(x,y, interior); + mMap->setActiveCell(x, y, interior); + mHud->setActiveCell(x, y, interior); } void WindowManager::setDrowningBarVisibility(bool visible) @@ -1005,12 +1121,12 @@ namespace MWGui void WindowManager::setHMSVisibility(bool visible) { - mHud->setHmsVisible (visible); + mHud->setHmsVisible(visible); } void WindowManager::setMinimapVisibility(bool visible) { - mHud->setMinimapVisible (visible); + mHud->setMinimapVisible(visible); } bool WindowManager::toggleFogOfWar() @@ -1023,7 +1139,8 @@ namespace MWGui { mToolTips->setFocusObject(focus); - if(mHud && (mShowOwned == 2 || mShowOwned == 3)) + const int showOwned = Settings::game().mShowOwned; + if (mHud && (showOwned == 2 || showOwned == 3)) { bool owned = mToolTips->checkOwned(); mHud->setCrosshairOwned(owned); @@ -1047,13 +1164,13 @@ namespace MWGui void WindowManager::setWeaponVisibility(bool visible) { - mHud->setWeapVisible (visible); + mHud->setWeapVisible(visible); } void WindowManager::setSpellVisibility(bool visible) { - mHud->setSpellVisible (visible); - mHud->setEffectVisible (visible); + mHud->setSpellVisible(visible); + mHud->setEffectVisible(visible); } void WindowManager::setSneakVisibility(bool visible) @@ -1093,26 +1210,25 @@ namespace MWGui void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) { - std::string tag(_tag); - - std::string MyGuiPrefix = "setting="; - size_t MyGuiPrefixLength = MyGuiPrefix.length(); + std::string_view tag = _tag.asUTF8(); - std::string tokenToFind = "sCell="; - size_t tokenLength = tokenToFind.length(); - - if(tag.compare(0, MyGuiPrefixLength, MyGuiPrefix) == 0) + std::string_view MyGuiPrefix = "setting="; + + std::string_view tokenToFind = "sCell="; + + if (tag.starts_with(MyGuiPrefix)) { - tag = tag.substr(MyGuiPrefixLength, tag.length()); + tag = tag.substr(MyGuiPrefix.length()); size_t comma_pos = tag.find(','); - std::string settingSection = tag.substr(0, comma_pos); - std::string settingTag = tag.substr(comma_pos+1, tag.length()); - - _result = Settings::Manager::getString(settingTag, settingSection); + std::string_view settingSection = tag.substr(0, comma_pos); + std::string_view settingTag = tag.substr(comma_pos + 1, tag.length()); + + _result = Settings::Manager::getString(settingTag, settingSection); } - else if (tag.compare(0, tokenLength, tokenToFind) == 0) + else if (tag.starts_with(tokenToFind)) { - _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); + std::string_view cellName = mTranslationDataStorage.translateCellName(tag.substr(tokenToFind.length())); + _result.assign(cellName.data(), cellName.size()); _result = MyGUI::TextIterator::toTagsString(_result); } else if (Gui::replaceTag(tag, _result)) @@ -1121,17 +1237,32 @@ namespace MWGui } else { - if (!mStore) + std::vector split; + Misc::StringUtils::split(std::string{ tag }, split, ":"); + + l10n::Manager& l10nManager = *MWBase::Environment::get().getL10nManager(); + + // If a key has a "Context:KeyName" format, use YAML to translate data + if (split.size() == 2) { - Log(Debug::Error) << "Error: WindowManager::onRetrieveTag: no Store set up yet, can not replace '" << tag << "'"; + _result = l10nManager.getContext(split[0])->formatMessage(split[1], {}, {}); return; } - const ESM::GameSetting *setting = mStore->get().find(tag); - if (setting && setting->mValue.getType()==ESM::VT_String) + // If not, treat is as GMST name from legacy localization + if (!mStore) + { + Log(Debug::Error) << "Error: WindowManager::onRetrieveTag: no Store set up yet, can not replace '" + << tag << "'"; + _result.assign(tag.data(), tag.size()); + return; + } + const ESM::GameSetting* setting = mStore->get().search(tag); + + if (setting && setting->mValue.getType() == ESM::VT_String) _result = setting->mValue.getString(); else - _result = tag; + _result.assign(tag.data(), tag.size()); } } @@ -1143,42 +1274,47 @@ namespace MWGui for (const auto& setting : changed) { if (setting.first == "HUD" && setting.second == "crosshair") - mCrosshairEnabled = Settings::Manager::getBool ("crosshair", "HUD"); + mCrosshairEnabled = Settings::Manager::getBool("crosshair", "HUD"); else if (setting.first == "GUI" && setting.second == "subtitles") - mSubtitlesEnabled = Settings::Manager::getBool ("subtitles", "GUI"); + mSubtitlesEnabled = Settings::Manager::getBool("subtitles", "GUI"); else if (setting.first == "GUI" && setting.second == "menu transparency") setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); - else if (setting.first == "Video" && ( - setting.second == "resolution x" - || setting.second == "resolution y" - || setting.second == "fullscreen" - || setting.second == "window border")) + else if (setting.first == "Video" + && (setting.second == "resolution x" || setting.second == "resolution y" + || setting.second == "window mode" || setting.second == "window border")) changeRes = true; - else if (setting.first == "Video" && setting.second == "vsync") - mVideoWrapper->setSyncToVBlank(Settings::Manager::getBool("vsync", "Video")); + else if (setting.first == "Video" && setting.second == "vsync mode") + mVideoWrapper->setSyncToVBlank(Settings::Manager::getInt("vsync mode", "Video")); else if (setting.first == "Video" && (setting.second == "gamma" || setting.second == "contrast")) - mVideoWrapper->setGammaContrast(Settings::Manager::getFloat("gamma", "Video"), - Settings::Manager::getFloat("contrast", "Video")); + mVideoWrapper->setGammaContrast( + Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); } if (changeRes) { mVideoWrapper->setVideoMode(Settings::Manager::getInt("resolution x", "Video"), - Settings::Manager::getInt("resolution y", "Video"), - Settings::Manager::getBool("fullscreen", "Video"), - Settings::Manager::getBool("window border", "Video")); + Settings::Manager::getInt("resolution y", "Video"), + static_cast(Settings::Manager::getInt("window mode", "Video")), + Settings::Manager::getBool("window border", "Video")); } } void WindowManager::windowResized(int x, int y) { - // Note: this is a side effect of resolution change or window resize. - // There is no need to track these changes. Settings::Manager::setInt("resolution x", "Video", x); Settings::Manager::setInt("resolution y", "Video", y); - Settings::Manager::resetPendingChange("resolution x", "Video"); - Settings::Manager::resetPendingChange("resolution y", "Video"); + + // We only want to process changes to window-size related settings. + Settings::CategorySettingVector filter = { { "Video", "resolution x" }, { "Video", "resolution y" } }; + + // If the HUD has not been initialised, the World singleton will not be available. + if (mHud) + { + MWBase::Environment::get().getWorld()->processChangedSettings(Settings::Manager::getPendingChanges(filter)); + } + + Settings::Manager::resetPendingChanges(filter); mGuiPlatform->getRenderManagerPtr()->setViewSize(x, y); @@ -1192,26 +1328,16 @@ namespace MWGui if (!mHud) return; // UI not initialized yet - for (std::map::iterator it = mTrackedWindows.begin(); it != mTrackedWindows.end(); ++it) + for (const auto& [window, settings] : mTrackedWindows) { - std::string settingName = it->second; - if (Settings::Manager::getBool(settingName + " maximized", "Windows")) - settingName += " maximized"; - - MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(settingName + " x", "Windows") * x), - static_cast(Settings::Manager::getFloat(settingName + " y", "Windows") * y)); - MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(settingName + " w", "Windows") * x), - static_cast(Settings::Manager::getFloat(settingName + " h", "Windows") * y)); - it->first->setPosition(pos); - it->first->setSize(size); + const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; + window->setPosition(MyGUI::IntPoint(static_cast(rect.mX * x), static_cast(rect.mY * y))); + window->setSize(MyGUI::IntSize(static_cast(rect.mW * x), static_cast(rect.mH * y))); } - for (WindowBase* window : mWindows) + for (const auto& window : mWindows) window->onResChange(x, y); - // We should reload TrueType fonts to fit new resolution - loadUserFonts(); - // TODO: check if any windows are now off-screen and move them back if so } @@ -1230,7 +1356,7 @@ namespace MWGui MWBase::Environment::get().getStateManager()->requestQuit(); } - void WindowManager::onCursorChange(const std::string &name) + void WindowManager::onCursorChange(const std::string& name) { mCursorManager->cursorChanged(name); } @@ -1242,7 +1368,17 @@ namespace MWGui void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg) { - if (mode==GM_Inventory && mAllowed==GW_None) + pushGuiMode(mode, arg, false); + } + + void WindowManager::forceLootMode(const MWWorld::Ptr& ptr) + { + pushGuiMode(MWGui::GM_Container, ptr, true); + } + + void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force) + { + if (mode == GM_Inventory && mAllowed == GW_None) return; if (mGuiModes.empty() || mGuiModes.back() != mode) @@ -1263,6 +1399,8 @@ namespace MWGui mGuiModeStates[mode].update(true); playSound(mGuiModeStates[mode].mOpenSound); } + if (force) + mContainerWindow->treatNextOpenAsLoot(); for (WindowBase* window : mGuiModeStates[mode].mWindows) window->setPtr(arg); @@ -1271,6 +1409,21 @@ namespace MWGui updateVisible(); } + void WindowManager::setCullMask(uint32_t mask) + { + mViewer->getCamera()->setCullMask(mask); + + // We could check whether stereo is enabled here, but these methods are + // trivial and have no effect in mono or multiview so just call them regardless. + mViewer->getCamera()->setCullMaskLeft(mask); + mViewer->getCamera()->setCullMaskRight(mask); + } + + uint32_t WindowManager::getCullMask() + { + return mViewer->getCamera()->getCullMask(); + } + void WindowManager::popGuiMode(bool noSound) { if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop) @@ -1328,7 +1481,7 @@ namespace MWGui mJailScreen->goToJail(days); } - void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent) + void WindowManager::setSelectedSpell(const ESM::RefId& spellId, int successChancePercent) { mSelectedSpell = spellId; mSelectedEnchantItem = MWWorld::Ptr(); @@ -1342,16 +1495,15 @@ namespace MWGui void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item) { mSelectedEnchantItem = item; - mSelectedSpell = ""; - const ESM::Enchantment* ench = mStore->get() - .find(item.getClass().getEnchantment(item)); + mSelectedSpell = ESM::RefId(); + const ESM::Enchantment* ench = mStore->get().find(item.getClass().getEnchantment(item)); - int chargePercent = static_cast(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); + int chargePercent = static_cast(item.getCellRef().getNormalizedEnchantmentCharge(*ench) * 100); mHud->setSelectedEnchantItem(item, chargePercent); mSpellWindow->setTitle(item.getClass().getName(item)); } - const MWWorld::Ptr &WindowManager::getSelectedEnchantItem() const + const MWWorld::Ptr& WindowManager::getSelectedEnchantItem() const { return mSelectedEnchantItem; } @@ -1368,22 +1520,22 @@ namespace MWGui mInventoryWindow->setTitle(item.getClass().getName(item)); } - const MWWorld::Ptr &WindowManager::getSelectedWeapon() const + const MWWorld::Ptr& WindowManager::getSelectedWeapon() const { return mSelectedWeapon; } void WindowManager::unsetSelectedSpell() { - mSelectedSpell = ""; + mSelectedSpell = ESM::RefId(); mSelectedEnchantItem = MWWorld::Ptr(); mHud->unsetSelectedSpell(); MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); - if (player->getDrawState() == MWMechanics::DrawState_Spell) - player->setDrawState(MWMechanics::DrawState_Nothing); + if (player->getDrawState() == MWMechanics::DrawState::Spell) + player->setDrawState(MWMechanics::DrawState::Nothing); - mSpellWindow->setTitle("#{sNone}"); + mSpellWindow->setTitle("#{Interface:None}"); } void WindowManager::unsetSelectedWeapon() @@ -1393,14 +1545,14 @@ namespace MWGui mInventoryWindow->setTitle("#{sSkillHandtohand}"); } - void WindowManager::getMousePosition(int &x, int &y) + void WindowManager::getMousePosition(int& x, int& y) { const MyGUI::IntPoint& pos = MyGUI::InputManager::getInstance().getMousePosition(); x = pos.left; y = pos.top; } - void WindowManager::getMousePosition(float &x, float &y) + void WindowManager::getMousePosition(float& x, float& y) { const MyGUI::IntPoint& pos = MyGUI::InputManager::getInstance().getMousePosition(); x = static_cast(pos.left); @@ -1415,16 +1567,17 @@ namespace MWGui return mHud->getWorldMouseOver(); } - float WindowManager::getScalingFactor() + float WindowManager::getScalingFactor() const { return mScalingFactor; } - void WindowManager::executeInConsole (const std::string& path) + void WindowManager::executeInConsole(const std::filesystem::path& path) { - mConsole->executeFile (path); + mConsole->executeFile(path); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -1465,24 +1618,48 @@ namespace MWGui */ void WindowManager::useItem(const MWWorld::Ptr &item, bool bypassBeastRestrictions) +======= + MWGui::InventoryWindow* WindowManager::getInventoryWindow() + { + return mInventoryWindow; + } + MWGui::CountDialog* WindowManager::getCountDialog() + { + return mCountDialog; + } + MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() + { + return mConfirmationDialog; + } + MWGui::TradeWindow* WindowManager::getTradeWindow() + { + return mTradeWindow; + } + MWGui::PostProcessorHud* WindowManager::getPostProcessorHud() + { + return mPostProcessorHud; + } + + void WindowManager::useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { if (mInventoryWindow) mInventoryWindow->useItem(item, bypassBeastRestrictions); } - bool WindowManager::isAllowed (GuiWindow wnd) const + bool WindowManager::isAllowed(GuiWindow wnd) const { return (mAllowed & wnd) != 0; } - void WindowManager::allow (GuiWindow wnd) + void WindowManager::allow(GuiWindow wnd) { mAllowed = (GuiWindow)(mAllowed | wnd); if (wnd & GW_Inventory) { - mBookWindow->setInventoryAllowed (true); - mScrollWindow->setInventoryAllowed (true); + mBookWindow->setInventoryAllowed(true); + mScrollWindow->setInventoryAllowed(true); } updateVisible(); @@ -1493,42 +1670,19 @@ namespace MWGui mAllowed = GW_None; mRestAllowed = false; - mBookWindow->setInventoryAllowed (false); - mScrollWindow->setInventoryAllowed (false); + mBookWindow->setInventoryAllowed(false); + mScrollWindow->setInventoryAllowed(false); updateVisible(); } - void WindowManager::toggleVisible (GuiWindow wnd) + void WindowManager::toggleVisible(GuiWindow wnd) { if (getMode() != GM_Inventory) return; - std::string settingName; - switch (wnd) - { - case GW_Inventory: - settingName = "inventory"; - break; - case GW_Map: - settingName = "map"; - break; - case GW_Magic: - settingName = "spells"; - break; - case GW_Stats: - settingName = "stats"; - break; - default: - break; - } - - if (!settingName.empty()) - { - settingName += " hidden"; - bool hidden = Settings::Manager::getBool(settingName, "Windows"); - Settings::Manager::setBool(settingName, "Windows", !hidden); - } + if (Settings::SettingValue* const hidden = findHiddenSetting(wnd)) + hidden->set(!hidden->get()); mShown = (GuiWindow)(mShown ^ wnd); updateVisible(); @@ -1548,10 +1702,8 @@ namespace MWGui bool WindowManager::isGuiMode() const { - return - !mGuiModes.empty() || - isConsoleMode() || - (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); + return !mGuiModes.empty() || isConsoleMode() || (mPostProcessorHud && mPostProcessorHud->isVisible()) + || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); } bool WindowManager::isConsoleMode() const @@ -1559,6 +1711,11 @@ namespace MWGui return mConsole && mConsole->isVisible(); } + bool WindowManager::isPostProcessorHudVisible() const + { + return mPostProcessorHud->isVisible(); + } + MWGui::GuiMode WindowManager::getMode() const { if (mGuiModes.empty()) @@ -1568,44 +1725,45 @@ namespace MWGui void WindowManager::disallowMouse() { - mInputBlocker->setVisible (true); + mInputBlocker->setVisible(true); } void WindowManager::allowMouse() { - mInputBlocker->setVisible (!isGuiMode ()); + mInputBlocker->setVisible(!isGuiMode()); } - void WindowManager::notifyInputActionBound () + void WindowManager::notifyInputActionBound() { - mSettingsWindow->updateControlsBox (); + mSettingsWindow->updateControlsBox(); allowMouse(); } bool WindowManager::containsMode(GuiMode mode) const { - if(mGuiModes.empty()) + if (mGuiModes.empty()) return false; return std::find(mGuiModes.begin(), mGuiModes.end(), mode) != mGuiModes.end(); } - void WindowManager::showCrosshair (bool show) + void WindowManager::showCrosshair(bool show) { if (mHud) - mHud->setCrosshairVisible (show && mCrosshairEnabled); + mHud->setCrosshairVisible(show && mCrosshairEnabled); } - void WindowManager::updateActivatedQuickKey () + void WindowManager::updateActivatedQuickKey() { mQuickKeysMenu->updateActivatedQuickKey(); } - void WindowManager::activateQuickKey (int index) + void WindowManager::activateQuickKey(int index) { mQuickKeysMenu->activateQuickKey(index); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -1641,6 +1799,9 @@ namespace MWGui */ bool WindowManager::getSubtitlesEnabled () +======= + bool WindowManager::getSubtitlesEnabled() +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { return mSubtitlesEnabled; } @@ -1649,18 +1810,20 @@ namespace MWGui { mHudEnabled = !mHudEnabled; updateVisible(); + mMessageBoxManager->setVisible(mHudEnabled); return mHudEnabled; } bool WindowManager::getRestEnabled() { - //Enable rest dialogue if character creation finished - if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) - mRestAllowed=true; + // Enable rest dialogue if character creation finished + if (mRestAllowed == false + && MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1) + mRestAllowed = true; return mRestAllowed; } - bool WindowManager::getPlayerSleeping () + bool WindowManager::getPlayerSleeping() { return mWaitDialog->getSleeping(); } @@ -1672,7 +1835,7 @@ namespace MWGui void WindowManager::addVisitedLocation(const std::string& name, int x, int y) { - mMap->addVisitedLocation (name, x, y); + mMap->addVisitedLocation(name, x, y); } const Translation::Storage& WindowManager::getTranslationDataStorage() const @@ -1680,7 +1843,7 @@ namespace MWGui return mTranslationDataStorage; } - void WindowManager::changePointer(const std::string &name) + void WindowManager::changePointer(const std::string& name) { MyGUI::PointerManager::getInstance().setPointer(name); onCursorChange(name); @@ -1705,13 +1868,13 @@ namespace MWGui } // Remove this wrapper once onKeyFocusChanged call is rendered unnecessary - void WindowManager::setKeyFocusWidget(MyGUI::Widget *widget) + void WindowManager::setKeyFocusWidget(MyGUI::Widget* widget) { MyGUI::InputManager::getInstance().setKeyFocusWidget(widget); onKeyFocusChanged(widget); } - void WindowManager::onKeyFocusChanged(MyGUI::Widget *widget) + void WindowManager::onKeyFocusChanged(MyGUI::Widget* widget) { if (widget && widget->castType(false)) SDL_StartTextInput(); @@ -1719,7 +1882,7 @@ namespace MWGui SDL_StopTextInput(); } - void WindowManager::setEnemy(const MWWorld::Ptr &enemy) + void WindowManager::setEnemy(const MWWorld::Ptr& enemy) { mHud->setEnemy(enemy); } @@ -1743,68 +1906,64 @@ namespace MWGui return mCursorVisible && mCursorActive; } - void WindowManager::trackWindow(Layout *layout, const std::string &name) + void WindowManager::trackWindow(Layout* layout, const WindowSettingValues& settings) { - std::string settingName = name; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - bool isMaximized = Settings::Manager::getBool(name + " maximized", "Windows"); - if (isMaximized) - settingName += " maximized"; - MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(settingName + " x", "Windows") * viewSize.width), - static_cast(Settings::Manager::getFloat(settingName + " y", "Windows") * viewSize.height)); - MyGUI::IntSize size (static_cast(Settings::Manager::getFloat(settingName + " w", "Windows") * viewSize.width), - static_cast(Settings::Manager::getFloat(settingName + " h", "Windows") * viewSize.height)); - layout->mMainWidget->setPosition(pos); - layout->mMainWidget->setSize(size); + const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; + + layout->mMainWidget->setPosition( + MyGUI::IntPoint(static_cast(rect.mX * viewSize.width), static_cast(rect.mY * viewSize.height))); + layout->mMainWidget->setSize( + MyGUI::IntSize(static_cast(rect.mW * viewSize.width), static_cast(rect.mH * viewSize.height))); MyGUI::Window* window = layout->mMainWidget->castType(); window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); - mTrackedWindows[window] = name; + mTrackedWindows.emplace(window, settings); } - void WindowManager::toggleMaximized(Layout *layout) + void WindowManager::toggleMaximized(Layout* layout) { MyGUI::Window* window = layout->mMainWidget->castType(); - std::string setting = mTrackedWindows[window]; - if (setting.empty()) + const auto it = mTrackedWindows.find(window); + if (it == mTrackedWindows.end()) return; - bool maximized = !Settings::Manager::getBool(setting + " maximized", "Windows"); - if (maximized) - setting += " maximized"; + const WindowSettingValues& settings = it->second; + const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mRegular : settings.mMaximized; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - float x = Settings::Manager::getFloat(setting + " x", "Windows") * float(viewSize.width); - float y = Settings::Manager::getFloat(setting + " y", "Windows") * float(viewSize.height); - float w = Settings::Manager::getFloat(setting + " w", "Windows") * float(viewSize.width); - float h = Settings::Manager::getFloat(setting + " h", "Windows") * float(viewSize.height); + const float x = rect.mX * viewSize.width; + const float y = rect.mY * viewSize.height; + const float w = rect.mW * viewSize.width; + const float h = rect.mH * viewSize.height; window->setCoord(x, y, w, h); - Settings::Manager::setBool(mTrackedWindows[window] + " maximized", "Windows", maximized); + + settings.mIsMaximized.set(!settings.mIsMaximized.get()); } - void WindowManager::onWindowChangeCoord(MyGUI::Window *_sender) + void WindowManager::onWindowChangeCoord(MyGUI::Window* window) { - std::string setting = mTrackedWindows[_sender]; + const auto it = mTrackedWindows.find(window); + if (it == mTrackedWindows.end()) + return; + + const WindowSettingValues& settings = it->second; + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - float x = _sender->getPosition().left / float(viewSize.width); - float y = _sender->getPosition().top / float(viewSize.height); - float w = _sender->getSize().width / float(viewSize.width); - float h = _sender->getSize().height / float(viewSize.height); - Settings::Manager::setFloat(setting + " x", "Windows", x); - Settings::Manager::setFloat(setting + " y", "Windows", y); - Settings::Manager::setFloat(setting + " w", "Windows", w); - Settings::Manager::setFloat(setting + " h", "Windows", h); - bool maximized = Settings::Manager::getBool(setting + " maximized", "Windows"); - if (maximized) - Settings::Manager::setBool(setting + " maximized", "Windows", false); + settings.mRegular.mX.set(window->getPosition().left / static_cast(viewSize.width)); + settings.mRegular.mY.set(window->getPosition().top / static_cast(viewSize.height)); + settings.mRegular.mW.set(window->getSize().width / static_cast(viewSize.width)); + settings.mRegular.mH.set(window->getSize().height / static_cast(viewSize.height)); + + settings.mIsMaximized.set(false); } void WindowManager::clear() { mPlayerBounty = -1; - for (WindowBase* window : mWindows) + for (const auto& window : mWindows) window->clear(); if (mLocalMapRender) @@ -1814,7 +1973,7 @@ namespace MWGui mToolTips->clear(); - mSelectedSpell.clear(); + mSelectedSpell = ESM::RefId(); mCustomMarkers.clear(); mForceHidden = GW_None; @@ -1826,7 +1985,7 @@ namespace MWGui updateVisible(); } - void WindowManager::write(ESM::ESMWriter &writer, Loading::Listener& progress) + void WindowManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) { mMap->write(writer, progress); @@ -1835,11 +1994,12 @@ namespace MWGui if (!mSelectedSpell.empty()) { writer.startRecord(ESM::REC_ASPL); - writer.writeHNString("ID__", mSelectedSpell); + writer.writeHNRefId("ID__", mSelectedSpell); writer.endRecord(ESM::REC_ASPL); } - for (CustomMarkerCollection::ContainerType::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + for (CustomMarkerCollection::ContainerType::const_iterator it = mCustomMarkers.begin(); + it != mCustomMarkers.end(); ++it) { writer.startRecord(ESM::REC_MARK); it->second.save(writer); @@ -1847,7 +2007,7 @@ namespace MWGui } } - void WindowManager::readRecord(ESM::ESMReader &reader, uint32_t type) + void WindowManager::readRecord(ESM::ESMReader& reader, uint32_t type) { if (type == ESM::REC_GMAP) mMap->readRecord(reader, type); @@ -1856,7 +2016,7 @@ namespace MWGui else if (type == ESM::REC_ASPL) { reader.getSubNameIs("ID__"); - std::string spell = reader.getHString(); + ESM::RefId spell = reader.getRefId(); if (mStore->get().search(spell)) mSelectedSpell = spell; } @@ -1871,22 +2031,21 @@ namespace MWGui int WindowManager::countSavedGameRecords() const { return 1 // Global map - + 1 // QuickKeysMenu - + mCustomMarkers.size() - + (!mSelectedSpell.empty() ? 1 : 0); + + 1 // QuickKeysMenu + + mCustomMarkers.size() + (!mSelectedSpell.empty() ? 1 : 0); } bool WindowManager::isSavingAllowed() const { return !MyGUI::InputManager::getInstance().isModalAny() - && !isConsoleMode() - // TODO: remove this, once we have properly serialized the state of open windows - && (!isGuiMode() || (mGuiModes.size() == 1 && (getMode() == GM_MainMenu || getMode() == GM_Rest))); + && !isConsoleMode() + // TODO: remove this, once we have properly serialized the state of open windows + && (!isGuiMode() || (mGuiModes.size() == 1 && (getMode() == GM_MainMenu || getMode() == GM_Rest))); } - void WindowManager::playVideo(const std::string &name, bool allowSkipping) + void WindowManager::playVideo(std::string_view name, bool allowSkipping, bool overrideSounds) { - mVideoWidget->playVideo("video\\" + name); + mVideoWidget->playVideo("video\\" + std::string{ name }); mVideoWidget->eventKeyButtonPressed.clear(); mVideoBackground->eventKeyButtonPressed.clear(); @@ -1909,15 +2068,17 @@ namespace MWGui bool cursorWasVisible = mCursorVisible; setCursorVisible(false); - if (mVideoWidget->hasAudioStream()) - MWBase::Environment::get().getSoundManager()->pauseSounds(MWSound::VideoPlayback, - ~MWSound::Type::Movie & MWSound::Type::Mask - ); + if (overrideSounds && mVideoWidget->hasAudioStream()) + MWBase::Environment::get().getSoundManager()->pauseSounds( + MWSound::VideoPlayback, ~MWSound::Type::Movie & MWSound::Type::Mask); - Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); + Misc::FrameRateLimiter frameRateLimiter + = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - const double dt = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()).count(); + const double dt + = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()) + .count(); MWBase::Environment::get().getInputManager()->update(dt, true, false); @@ -1975,7 +2136,7 @@ namespace MWGui } } - void WindowManager::addCurrentModal(WindowModal *input) + void WindowManager::addCurrentModal(WindowModal* input) { if (mCurrentModals.empty()) mKeyboardNavigation->saveFocus(getMode()); @@ -1989,9 +2150,9 @@ namespace MWGui void WindowManager::removeCurrentModal(WindowModal* input) { - if(!mCurrentModals.empty()) + if (!mCurrentModals.empty()) { - if(input == mCurrentModals.back()) + if (input == mCurrentModals.back()) { mCurrentModals.pop_back(); mKeyboardNavigation->saveFocus(-1); @@ -2014,7 +2175,7 @@ namespace MWGui mKeyboardNavigation->setModalWindow(mCurrentModals.back()->mMainWidget); } - void WindowManager::onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) + void WindowManager::onVideoKeyPressed(MyGUI::Widget* _sender, MyGUI::KeyCode _key, MyGUI::Char _char) { if (_key == MyGUI::KeyCode::Escape) mVideoWidget->stop(); @@ -2022,20 +2183,20 @@ namespace MWGui void WindowManager::updatePinnedWindows() { - mInventoryWindow->setPinned(Settings::Manager::getBool("inventory pin", "Windows")); - if (Settings::Manager::getBool("inventory hidden", "Windows")) + mInventoryWindow->setPinned(Settings::windows().mInventoryPin); + if (Settings::windows().mInventoryHidden) mShown = (GuiWindow)(mShown ^ GW_Inventory); - mMap->setPinned(Settings::Manager::getBool("map pin", "Windows")); - if (Settings::Manager::getBool("map hidden", "Windows")) + mMap->setPinned(Settings::windows().mMapPin); + if (Settings::windows().mMapHidden) mShown = (GuiWindow)(mShown ^ GW_Map); - mSpellWindow->setPinned(Settings::Manager::getBool("spells pin", "Windows")); - if (Settings::Manager::getBool("spells hidden", "Windows")) + mSpellWindow->setPinned(Settings::windows().mSpellsPin); + if (Settings::windows().mSpellsHidden) mShown = (GuiWindow)(mShown ^ GW_Magic); - mStatsWindow->setPinned(Settings::Manager::getBool("stats pin", "Windows")); - if (Settings::Manager::getBool("stats hidden", "Windows")) + mStatsWindow->setPinned(Settings::windows().mStatsPin); + if (Settings::windows().mStatsHidden) mShown = (GuiWindow)(mShown ^ GW_Stats); } @@ -2043,20 +2204,20 @@ namespace MWGui { switch (window) { - case GW_Inventory: - mInventoryWindow->setPinned(true); - break; - case GW_Map: - mMap->setPinned(true); - break; - case GW_Magic: - mSpellWindow->setPinned(true); - break; - case GW_Stats: - mStatsWindow->setPinned(true); - break; - default: - break; + case GW_Inventory: + mInventoryWindow->setPinned(true); + break; + case GW_Map: + mMap->setPinned(true); + break; + case GW_Magic: + mSpellWindow->setPinned(true); + break; + case GW_Stats: + mStatsWindow->setPinned(true); + break; + default: + break; } updateVisible(); @@ -2110,17 +2271,17 @@ namespace MWGui mWerewolfFader->notifyAlphaChanged(set ? 1.0f : 0.0f); } - void WindowManager::onClipboardChanged(const std::string &_type, const std::string &_data) + void WindowManager::onClipboardChanged(const std::string& _type, const std::string& _data) { if (_type == "Text") SDL_SetClipboardText(MyGUI::TextIterator::getOnlyText(MyGUI::UString(_data)).asUTF8().c_str()); } - void WindowManager::onClipboardRequested(const std::string &_type, std::string &_data) + void WindowManager::onClipboardRequested(const std::string& _type, std::string& _data) { if (_type != "Text") return; - char* text=nullptr; + char* text = nullptr; text = SDL_GetClipboardText(); if (text) _data = MyGUI::TextIterator::toTagsString(text); @@ -2145,9 +2306,28 @@ namespace MWGui void WindowManager::toggleDebugWindow() { -#ifndef BT_NO_PROFILE mDebugWindow->setVisible(!mDebugWindow->isVisible()); -#endif + } + + void WindowManager::togglePostProcessorHud() + { + if (!MWBase::Environment::get().getWorld()->getPostProcessor()->isEnabled()) + { + messageBox("#{OMWEngine:PostProcessingIsNotEnabled}"); + return; + } + + bool visible = mPostProcessorHud->isVisible(); + + if (!visible && !mGuiModes.empty()) + mKeyboardNavigation->saveFocus(mGuiModes.back()); + + mPostProcessorHud->setVisible(!visible); + + if (visible && !mGuiModes.empty()) + mKeyboardNavigation->restoreFocus(mGuiModes.back()); + + updateVisible(); } void WindowManager::cycleSpell(bool next) @@ -2162,12 +2342,13 @@ namespace MWGui mInventoryWindow->cycle(next); } - void WindowManager::playSound(const std::string& soundId, float volume, float pitch) + void WindowManager::playSound(const ESM::RefId& soundId, float volume, float pitch) { if (soundId.empty()) return; - MWBase::Environment::get().getSoundManager()->playSound(soundId, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); + MWBase::Environment::get().getSoundManager()->playSound( + soundId, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnvNoScaling); } void WindowManager::updateSpellWindow() @@ -2176,11 +2357,12 @@ namespace MWGui mSpellWindow->updateSpells(); } - void WindowManager::setConsoleSelectedObject(const MWWorld::Ptr &object) + void WindowManager::setConsoleSelectedObject(const MWWorld::Ptr& object) { mConsole->setSelectedObject(object); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -2211,34 +2393,25 @@ namespace MWGui */ std::string WindowManager::correctIconPath(const std::string& path) +======= + MWWorld::Ptr WindowManager::getConsoleSelectedObject() const +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS()); + return mConsole->getSelectedObject(); } - std::string WindowManager::correctBookartPath(const std::string& path, int width, int height, bool* exists) + void WindowManager::printToConsole(const std::string& msg, std::string_view color) { - std::string corrected = Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS()); - if (exists) - *exists = mResourceSystem->getVFS()->exists(corrected); - return corrected; + mConsole->print(msg, color); } - std::string WindowManager::correctTexturePath(const std::string& path) + void WindowManager::setConsoleMode(const std::string& mode) { - return Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); - } - - bool WindowManager::textureExists(const std::string &path) - { - std::string corrected = Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); - return mResourceSystem->getVFS()->exists(corrected); + mConsole->setConsoleMode(mode); } void WindowManager::createCursors() { - // FIXME: currently we do not scale cursor since it is not a MyGUI widget. - // In theory, we can do it manually (rescale the cursor image via osg::Imag::scaleImage() and scale the hotspot position). - // Unfortunately, this apploach can lead to driver crashes on some setups (e.g. on laptops with nvidia-prime on Linux). MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator(); while (enumerator.next()) { @@ -2246,18 +2419,20 @@ namespace MWGui ResourceImageSetPointerFix* imgSetPointer = resource->castType(false); if (!imgSetPointer) continue; - std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture; + std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0, 0).texture; osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(tex_name); - if(image.valid()) + if (image.valid()) { - //everything looks good, send it to the cursor manager + // everything looks good, send it to the cursor manager Uint8 hotspot_x = imgSetPointer->getHotSpot().left; Uint8 hotspot_y = imgSetPointer->getHotSpot().top; int rotation = imgSetPointer->getRotation(); + MyGUI::IntSize pointerSize = imgSetPointer->getSize(); - mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, hotspot_x, hotspot_y); + mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, hotspot_x, hotspot_y, + pointerSize.width, pointerSize.height); } } } @@ -2268,8 +2443,8 @@ namespace MWGui MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture("white"); tex->createManual(8, 8, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8); unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); - for (int x=0; x<8; ++x) - for (int y=0; y<8; ++y) + for (int x = 0; x < 8; ++x) + for (int y = 0; y < 8; ++y) { *(data++) = 255; *(data++) = 255; @@ -2282,8 +2457,8 @@ namespace MWGui MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture("black"); tex->createManual(8, 8, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8); unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); - for (int x=0; x<8; ++x) - for (int y=0; y<8; ++y) + for (int x = 0; x < 8; ++x) + for (int y = 0; y < 8; ++y) { *(data++) = 0; *(data++) = 0; @@ -2303,13 +2478,13 @@ namespace MWGui { MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().getTexture("transparent"); unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); - for (int x=0; x<8; ++x) - for (int y=0; y<8; ++y) + for (int x = 0; x < 8; ++x) + for (int y = 0; y < 8; ++y) { *(data++) = 255; *(data++) = 255; *(data++) = 255; - *(data++) = static_cast(value*255); + *(data++) = static_cast(value * 255); } tex->unlock(); } @@ -2319,12 +2494,12 @@ namespace MWGui mLocalMapRender->addCell(cell); } - void WindowManager::removeCell(MWWorld::CellStore *cell) + void WindowManager::removeCell(MWWorld::CellStore* cell) { mLocalMapRender->removeCell(cell); } - void WindowManager::writeFog(MWWorld::CellStore *cell) + void WindowManager::writeFog(MWWorld::CellStore* cell) { mLocalMapRender->saveFogOfWar(cell); } @@ -2347,16 +2522,16 @@ namespace MWGui { switch (key.getValue()) { - case MyGUI::KeyCode::ArrowDown: - case MyGUI::KeyCode::ArrowUp: - case MyGUI::KeyCode::ArrowLeft: - case MyGUI::KeyCode::ArrowRight: - case MyGUI::KeyCode::Return: - case MyGUI::KeyCode::NumpadEnter: - case MyGUI::KeyCode::Space: - return true; - default: - return false; + case MyGUI::KeyCode::ArrowDown: + case MyGUI::KeyCode::ArrowUp: + case MyGUI::KeyCode::ArrowLeft: + case MyGUI::KeyCode::ArrowRight: + case MyGUI::KeyCode::Return: + case MyGUI::KeyCode::NumpadEnter: + case MyGUI::KeyCode::Space: + return true; + default: + return false; } } return false; @@ -2372,8 +2547,8 @@ namespace MWGui void WindowManager::GuiModeState::update(bool visible) { - for (unsigned int i=0; isetVisible(visible); + for (const auto& window : mWindows) + window->setVisible(visible); } void WindowManager::watchActor(const MWWorld::Ptr& ptr) @@ -2385,4 +2560,28 @@ namespace MWGui { return mStatsWatcher->getWatchedActor(); } + + const std::string& WindowManager::getVersionDescription() const + { + return mVersionDescription; + } + + void WindowManager::handleScheduledMessageBoxes() + { + const auto scheduledMessageBoxes = mScheduledMessageBoxes.lock(); + for (const ScheduledMessageBox& v : *scheduledMessageBoxes) + messageBox(v.mMessage, v.mShowInDialogueMode); + scheduledMessageBoxes->clear(); + } + + void WindowManager::onDeleteCustomData(const MWWorld::Ptr& ptr) + { + for (const auto& window : mWindows) + window->onDeleteCustomData(ptr); + } + + void WindowManager::asyncPrepareSaveMap() + { + mMap->asyncPrepareSaveMap(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index b786a4123..7002d2f12 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -7,26 +7,41 @@ and retrieving information from the Gui. **/ +#include #include +#include #include #include "../mwbase/windowmanager.hpp" +#include "../mwrender/localmap.hpp" +#include +#include #include +#include +#include #include #include +#include "charactercreation.hpp" +#include "draganddrop.hpp" #include "mapwindow.hpp" +#include "messagebox.hpp" +#include "settings.hpp" +#include "soulgemdialog.hpp" #include "statswatcher.hpp" #include "textcolours.hpp" +#include "tooltips.hpp" +#include "windowbase.hpp" +#include #include #include +#include namespace MyGUI { - class Gui; class Widget; class Window; class UString; @@ -67,29 +82,14 @@ namespace SceneUtil class WorkQueue; } -namespace SDLUtil -{ - class SDLCursorManager; - class VideoWrapper; -} - -namespace osgMyGUI -{ - class Platform; -} - namespace Gui { class FontLoader; } -namespace MWRender -{ - class LocalMap; -} - namespace MWGui { +<<<<<<< HEAD class WindowBase; class HUD; class MapWindow; @@ -570,96 +570,507 @@ namespace MWGui MyGUI::Gui *mGui; // Gui struct GuiModeState +======= + class HUD; + class MapWindow; + class MainMenu; + class StatsWindow; + class InventoryWindow; + struct JournalWindow; + class TextInputDialog; + class InfoBoxDialog; + class SettingsWindow; + class AlchemyWindow; + class QuickKeysMenu; + class LoadingScreen; + class LevelupDialog; + class WaitDialog; + class SpellCreationDialog; + class EnchantingDialog; + class TrainingWindow; + class SpellIcons; + class MerchantRepair; + class Recharge; + class CompanionWindow; + class VideoWidget; + class WindowModal; + class ScreenFader; + class DebugWindow; + class PostProcessorHud; + class JailScreen; + class KeyboardNavigation; + + class WindowManager : public MWBase::WindowManager +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - GuiModeState(WindowBase* window) + public: + typedef std::pair Faction; + typedef std::vector FactionList; + + WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, + Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, + const std::filesystem::path& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, + ToUTF8::FromType encoding, const std::string& versionDescription, bool useShaders, + Files::ConfigurationManager& cfgMgr); + virtual ~WindowManager(); + + /// Set the ESMStore to use for retrieving of GUI-related strings. + void setStore(const MWWorld::ESMStore& store); + + void initUI(); + + Loading::Listener* getLoadingScreen() override; + + /// @note This method will block until the video finishes playing + /// (and will continually update the window while doing so) + void playVideo(std::string_view name, bool allowSkipping, bool overrideSounds = true) override; + + /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. + void setKeyFocusWidget(MyGUI::Widget* widget) override; + + void setNewGame(bool newgame) override; + + void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg) override; + void pushGuiMode(GuiMode mode) override; + void popGuiMode(bool noSound = false) override; + void removeGuiMode(GuiMode mode, bool noSound = false) override; ///< can be anywhere in the stack + + void goToJail(int days) override; + + GuiMode getMode() const override; + bool containsMode(GuiMode mode) const override; + + bool isGuiMode() const override; + + bool isConsoleMode() const override; + + bool isPostProcessorHudVisible() const override; + + void toggleVisible(GuiWindow wnd) override; + + void forceHide(MWGui::GuiWindow wnd) override; + void unsetForceHide(MWGui::GuiWindow wnd) override; + + /// Disallow all inventory mode windows + void disallowAll() override; + + /// Allow one or more windows + void allow(GuiWindow wnd) override; + + bool isAllowed(GuiWindow wnd) const override; + + /// \todo investigate, if we really need to expose every single lousy UI element to the outside world + MWGui::InventoryWindow* getInventoryWindow() override; + MWGui::CountDialog* getCountDialog() override; + MWGui::ConfirmationDialog* getConfirmationDialog() override; + MWGui::TradeWindow* getTradeWindow() override; + MWGui::PostProcessorHud* getPostProcessorHud() override; + + /// Make the player use an item, while updating GUI state accordingly + void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions = false) override; + + void updateSpellWindow() override; + + void setConsoleSelectedObject(const MWWorld::Ptr& object) override; + MWWorld::Ptr getConsoleSelectedObject() const override; + void printToConsole(const std::string& msg, std::string_view color) override; + void setConsoleMode(const std::string& mode) override; + + /// Set time left for the player to start drowning (update the drowning bar) + /// @param time time left to start drowning + /// @param maxTime how long we can be underwater (in total) until drowning starts + void setDrowningTimeLeft(float time, float maxTime) override; + + void changeCell(const MWWorld::CellStore* cell) override; ///< change the active cell + + void setFocusObject(const MWWorld::Ptr& focus) override; + void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) override; + + void getMousePosition(int& x, int& y) override; + void getMousePosition(float& x, float& y) override; + void setDragDrop(bool dragDrop) override; + bool getWorldMouseOver() override; + + float getScalingFactor() const override; + + bool toggleFogOfWar() override; + bool toggleFullHelp() override; ///< show extra info in item tooltips (owner, script) + bool getFullHelp() const override; + + void setActiveMap(int x, int y, bool interior) override; + ///< set the indices of the map texture that should be used + + /// sets the visibility of the drowning bar + void setDrowningBarVisibility(bool visible) override; + + // sets the visibility of the hud health/magicka/stamina bars + void setHMSVisibility(bool visible) override; + // sets the visibility of the hud minimap + void setMinimapVisibility(bool visible) override; + void setWeaponVisibility(bool visible) override; + void setSpellVisibility(bool visible) override; + void setSneakVisibility(bool visible) override; + + /// activate selected quick key + void activateQuickKey(int index) override; + /// update activated quick key state (if action executing was delayed for some reason) + void updateActivatedQuickKey() override; + + const ESM::RefId& getSelectedSpell() override { return mSelectedSpell; } + void setSelectedSpell(const ESM::RefId& spellId, int successChancePercent) override; + void setSelectedEnchantItem(const MWWorld::Ptr& item) override; + const MWWorld::Ptr& getSelectedEnchantItem() const override; + void setSelectedWeapon(const MWWorld::Ptr& item) override; + const MWWorld::Ptr& getSelectedWeapon() const override; + int getFontHeight() const override; + void unsetSelectedSpell() override; + void unsetSelectedWeapon() override; + + void updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) override; + + void showCrosshair(bool show) override; + bool getSubtitlesEnabled() override; + + /// Turn visibility of HUD on or off + bool toggleHud() override; + + void disallowMouse() override; + void allowMouse() override; + void notifyInputActionBound() override; + + void addVisitedLocation(const std::string& name, int x, int y) override; + + /// Hides dialog and schedules dialog to be deleted. + void removeDialog(std::unique_ptr&& dialog) override; + + /// Gracefully attempts to exit the topmost GUI mode + void exitCurrentGuiMode() override; + + void messageBox(std::string_view message, + enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override; + void scheduleMessageBox(std::string message, + enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override; + void staticMessageBox(std::string_view message) override; + void removeStaticMessageBox() override; + void interactiveMessageBox( + std::string_view message, const std::vector& buttons = {}, bool block = false) override; + + int readPressedButton() override; ///< returns the index of the pressed button or -1 if no button was pressed + ///< (->MessageBoxmanager->InteractiveMessageBox) + + void update(float duration); + + /** + * Fetches a GMST string from the store, if there is no setting with the given + * ID or it is not a string the default string is returned. + * + * @param id Identifier for the GMST setting, e.g. "aName" + * @param default Default value if the GMST setting cannot be used. + */ + std::string_view getGameSettingString(std::string_view id, std::string_view default_) override; + + void processChangedSettings(const Settings::CategorySettingVector& changed) override; + + void windowVisibilityChange(bool visible) override; + void windowResized(int x, int y) override; + void windowClosed() override; + bool isWindowVisible() override; + + void watchActor(const MWWorld::Ptr& ptr) override; + MWWorld::Ptr getWatchedActor() const override; + + void executeInConsole(const std::filesystem::path& path) override; + + void enableRest() override { mRestAllowed = true; } + bool getRestEnabled() override; + + bool getJournalAllowed() override { return (mAllowed & GW_Magic) != 0; } + + bool getPlayerSleeping() override; + void wakeUpPlayer() override; + + void updatePlayer() override; + + void showSoulgemDialog(MWWorld::Ptr item) override; + + void changePointer(const std::string& name) override; + + void setEnemy(const MWWorld::Ptr& enemy) override; + + int getMessagesCount() const override; + + const Translation::Storage& getTranslationDataStorage() const override; + + void onSoulgemDialogButtonPressed(int button); + + bool getCursorVisible() override; + + /// Call when mouse cursor or buttons are used. + void setCursorActive(bool active) override; + + /// Clear all savegame-specific data + void clear() override; + + void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; + void readRecord(ESM::ESMReader& reader, uint32_t type) override; + int countSavedGameRecords() const override; + + /// Does the current stack of GUI-windows permit saving? + bool isSavingAllowed() const override; + + /// Send exit command to active Modal window **/ + void exitCurrentModal() override; + + /// Sets the current Modal + /** Used to send exit command to active Modal when Esc is pressed **/ + void addCurrentModal(WindowModal* input) override; + + /// Removes the top Modal + /** Used when one Modal adds another Modal + \param input Pointer to the current modal, to ensure proper modal is removed **/ + void removeCurrentModal(WindowModal* input) override; + + void pinWindow(MWGui::GuiWindow window) override; + void toggleMaximized(Layout* layout) override; + + /// Fade the screen in, over \a time seconds + void fadeScreenIn(const float time, bool clearQueue, float delay) override; + /// Fade the screen out to black, over \a time seconds + void fadeScreenOut(const float time, bool clearQueue, float delay) override; + /// Fade the screen to a specified percentage of black, over \a time seconds + void fadeScreenTo(const int percent, const float time, bool clearQueue, float delay) override; + /// Darken the screen to a specified percentage + void setBlindness(const int percent) override; + + void activateHitOverlay(bool interrupt) override; + void setWerewolfOverlay(bool set) override; + + void toggleConsole() override; + void toggleDebugWindow() override; + void togglePostProcessorHud() override; + + /// Cycle to next or previous spell + void cycleSpell(bool next) override; + /// Cycle to next or previous weapon + void cycleWeapon(bool next) override; + + void playSound(const ESM::RefId& soundId, float volume = 1.f, float pitch = 1.f) override; + + void addCell(MWWorld::CellStore* cell) override; + void removeCell(MWWorld::CellStore* cell) override; + void writeFog(MWWorld::CellStore* cell) override; + + const MWGui::TextColours& getTextColours() override; + + bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat = false) override; + bool injectKeyRelease(MyGUI::KeyCode key) override; + + const std::string& getVersionDescription() const override; + + void onDeleteCustomData(const MWWorld::Ptr& ptr) override; + void forceLootMode(const MWWorld::Ptr& ptr) override; + + void asyncPrepareSaveMap() override; + + private: + unsigned int mOldUpdateMask; + unsigned int mOldCullMask; + + const MWWorld::ESMStore* mStore; + Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mWorkQueue; + + std::unique_ptr mGuiPlatform; + osgViewer::Viewer* mViewer; + + std::unique_ptr mFontLoader; + std::unique_ptr mStatsWatcher; + + bool mConsoleOnlyScripts; + + std::map mTrackedWindows; + void trackWindow(Layout* layout, const WindowSettingValues& settings); + void onWindowChangeCoord(MyGUI::Window* _sender); + + ESM::RefId mSelectedSpell; + MWWorld::Ptr mSelectedEnchantItem; + MWWorld::Ptr mSelectedWeapon; + + std::vector mCurrentModals; + + // Markers placed manually by the player. Must be shared between both map views (the HUD map and the map + // window). + CustomMarkerCollection mCustomMarkers; + + HUD* mHud; + MapWindow* mMap; + std::unique_ptr mLocalMapRender; + std::unique_ptr mToolTips; + StatsWindow* mStatsWindow; + std::unique_ptr mMessageBoxManager; + Console* mConsole; + DialogueWindow* mDialogueWindow; + std::unique_ptr mDragAndDrop; + InventoryWindow* mInventoryWindow; + ScrollWindow* mScrollWindow; + BookWindow* mBookWindow; + CountDialog* mCountDialog; + TradeWindow* mTradeWindow; + SettingsWindow* mSettingsWindow; + ConfirmationDialog* mConfirmationDialog; + SpellWindow* mSpellWindow; + QuickKeysMenu* mQuickKeysMenu; + LoadingScreen* mLoadingScreen; + WaitDialog* mWaitDialog; + std::unique_ptr mSoulgemDialog; + MyGUI::ImageBox* mVideoBackground; + VideoWidget* mVideoWidget; + ScreenFader* mWerewolfFader; + ScreenFader* mBlindnessFader; + ScreenFader* mHitFader; + ScreenFader* mScreenFader; + DebugWindow* mDebugWindow; + PostProcessorHud* mPostProcessorHud; + JailScreen* mJailScreen; + ContainerWindow* mContainerWindow; + + std::vector> mWindows; + + Translation::Storage& mTranslationDataStorage; + + std::unique_ptr mCharGen; + + MyGUI::Widget* mInputBlocker; + + bool mCrosshairEnabled; + bool mSubtitlesEnabled; + bool mHitFaderEnabled; + bool mWerewolfOverlayEnabled; + bool mHudEnabled; + bool mCursorVisible; + bool mCursorActive; + + int mPlayerBounty; + + void setCursorVisible(bool visible) override; + + std::unique_ptr mGui; // Gui + + struct GuiModeState { - mWindows.push_back(window); - } - GuiModeState(const std::vector& windows) - : mWindows(windows) {} - GuiModeState() {} + GuiModeState(WindowBase* window) { mWindows.push_back(window); } + GuiModeState(const std::vector& windows) + : mWindows(windows) + { + } + GuiModeState() {} - void update(bool visible); + void update(bool visible); - std::vector mWindows; + std::vector mWindows; - std::string mCloseSound; - std::string mOpenSound; + ESM::RefId mCloseSound; + ESM::RefId mOpenSound; + }; + // Defines the windows that should be shown in a particular GUI mode. + std::map mGuiModeStates; + // The currently active stack of GUI modes (top mode is the one we are in). + std::vector mGuiModes; + + std::unique_ptr mCursorManager; + + std::vector> mGarbageDialogs; + void cleanupGarbage(); + + GuiWindow mShown; // Currently shown windows in inventory mode + GuiWindow mForceHidden; // Hidden windows (overrides mShown) + + /* Currently ALLOWED windows in inventory mode. This is used at + the start of the game, when windows are enabled one by one + through script commands. You can manipulate this through using + allow() and disableAll(). + */ + GuiWindow mAllowed; + // is the rest window allowed? + bool mRestAllowed; + + void updateVisible(); // Update visibility of all windows based on mode, shown and allowed settings + + void updateMap(); + + ToUTF8::FromType mEncoding; + + std::string mVersionDescription; + + bool mWindowVisible; + + MWGui::TextColours mTextColours; + + std::unique_ptr mKeyboardNavigation; + + std::unique_ptr mVideoWrapper; + + float mScalingFactor; + + struct ScheduledMessageBox + { + std::string mMessage; + MWGui::ShowInDialogueMode mShowInDialogueMode; + + ScheduledMessageBox(std::string&& message, MWGui::ShowInDialogueMode showInDialogueMode) + : mMessage(std::move(message)) + , mShowInDialogueMode(showInDialogueMode) + { + } + }; + + Misc::ScopeGuarded> mScheduledMessageBoxes; + + /** + * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be + * replaced upon setting a user visible text/property. Supported syntax: + * #{GMSTName}: retrieves String value of the GMST called GMSTName + * #{setting=CATEGORY_NAME,SETTING_NAME}: retrieves String value of SETTING_NAME under category CATEGORY_NAME + * from settings.cfg + * #{sCell=CellID}: retrieves translated name of the given CellID (used only by some Morrowind localisations, in + * others cell ID is == cell name) + * #{fontcolour=FontColourName}: retrieves the value of the fallback setting "FontColor_color_" + * from openmw.cfg, in the format "r g b a", float values in range 0-1. Useful for "Colour" and "TextColour" + * properties in skins. + * #{fontcolourhtml=FontColourName}: retrieves the value of the fallback setting + * "FontColor_color_" from openmw.cfg, in the format "#xxxxxx" where x are hexadecimal numbers. + * Useful in an EditBox's caption to change the color of following text. + */ + void onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result); + + void onCursorChange(const std::string& name); + void onKeyFocusChanged(MyGUI::Widget* widget); + + // Key pressed while playing a video + void onVideoKeyPressed(MyGUI::Widget* _sender, MyGUI::KeyCode _key, MyGUI::Char _char); + + void sizeVideo(int screenWidth, int screenHeight); + + void onClipboardChanged(const std::string& _type, const std::string& _data); + void onClipboardRequested(const std::string& _type, std::string& _data); + + void createTextures(); + void createCursors(); + void setMenuTransparency(float value); + + void updatePinnedWindows(); + + void enableScene(bool enable); + + void handleScheduledMessageBoxes(); + + void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force); + + void setCullMask(uint32_t mask) override; + uint32_t getCullMask() override; + + Files::ConfigurationManager& mCfgMgr; }; - // Defines the windows that should be shown in a particular GUI mode. - std::map mGuiModeStates; - // The currently active stack of GUI modes (top mode is the one we are in). - std::vector mGuiModes; - - SDLUtil::SDLCursorManager* mCursorManager; - - std::vector mGarbageDialogs; - void cleanupGarbage(); - - GuiWindow mShown; // Currently shown windows in inventory mode - GuiWindow mForceHidden; // Hidden windows (overrides mShown) - - /* Currently ALLOWED windows in inventory mode. This is used at - the start of the game, when windows are enabled one by one - through script commands. You can manipulate this through using - allow() and disableAll(). - */ - GuiWindow mAllowed; - // is the rest window allowed? - bool mRestAllowed; - - void updateVisible(); // Update visibility of all windows based on mode, shown and allowed settings - - void updateMap(); - - int mShowOwned; - - ToUTF8::FromType mEncoding; - - std::string mVersionDescription; - - bool mWindowVisible; - - MWGui::TextColours mTextColours; - - std::unique_ptr mKeyboardNavigation; - - SDLUtil::VideoWrapper* mVideoWrapper; - - float mScalingFactor; - - /** - * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. - * Supported syntax: - * #{GMSTName}: retrieves String value of the GMST called GMSTName - * #{setting=CATEGORY_NAME,SETTING_NAME}: retrieves String value of SETTING_NAME under category CATEGORY_NAME from settings.cfg - * #{sCell=CellID}: retrieves translated name of the given CellID (used only by some Morrowind localisations, in others cell ID is == cell name) - * #{fontcolour=FontColourName}: retrieves the value of the fallback setting "FontColor_color_" from openmw.cfg, - * in the format "r g b a", float values in range 0-1. Useful for "Colour" and "TextColour" properties in skins. - * #{fontcolourhtml=FontColourName}: retrieves the value of the fallback setting "FontColor_color_" from openmw.cfg, - * in the format "#xxxxxx" where x are hexadecimal numbers. Useful in an EditBox's caption to change the color of following text. - */ - void onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result); - - void onCursorChange(const std::string& name); - void onKeyFocusChanged(MyGUI::Widget* widget); - - // Key pressed while playing a video - void onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); - - void sizeVideo(int screenWidth, int screenHeight); - - void onClipboardChanged(const std::string& _type, const std::string& _data); - void onClipboardRequested(const std::string& _type, std::string& _data); - - void createTextures(); - void createCursors(); - void setMenuTransparency(float value); - - void updatePinnedWindows(); - - void enableScene(bool enable); - }; } #endif diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 6106d6b56..1b3bd1d2d 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -5,10 +5,11 @@ namespace MWGui { WindowPinnableBase::WindowPinnableBase(const std::string& parLayout) - : WindowBase(parLayout), mPinned(false) + : WindowBase(parLayout) + , mPinned(false) { Window* window = mMainWidget->castType(); - mPinButton = window->getSkinWidget ("Button"); + mPinButton = window->getSkinWidget("Button"); mPinButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonPressed); } @@ -21,9 +22,9 @@ namespace MWGui mPinned = !mPinned; if (mPinned) - mPinButton->changeWidgetSkin ("PinDown"); + mPinButton->changeWidgetSkin("PinDown"); else - mPinButton->changeWidgetSkin ("PinUp"); + mPinButton->changeWidgetSkin("PinUp"); onPinToggled(); } diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index c91f0a148..ebb3e2cf0 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -5,12 +5,12 @@ namespace MWGui { - class WindowPinnableBase: public WindowBase + class WindowPinnableBase : public WindowBase { public: WindowPinnableBase(const std::string& parLayout); bool pinned() { return mPinned; } - void setPinned (bool pinned); + void setPinned(bool pinned); void setPinButtonVisible(bool visible); private: diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index e6557a440..0170f71d8 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -6,6 +6,7 @@ #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -20,314 +21,162 @@ #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/globals.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" + +#include "../mwgui/messagebox.hpp" #include "actions.hpp" #include "bindingsmanager.hpp" namespace MWInput { - const float ZOOM_SCALE = 10.f; /// Used for scrolling camera in and out ActionManager::ActionManager(BindingsManager* bindingsManager, - osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, - osg::ref_ptr viewer, - osg::ref_ptr screenCaptureHandler) + osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, + osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler) : mBindingsManager(bindingsManager) , mViewer(viewer) , mScreenCaptureHandler(screenCaptureHandler) , mScreenCaptureOperation(screenCaptureOperation) - , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) - , mSneaking(false) - , mAttemptJump(false) - , mOverencumberedMessageDelay(0.f) - , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) { } - void ActionManager::update(float dt, bool triedToMove) + void ActionManager::update(float dt) { - // Disable movement in Gui mode - if (MWBase::Environment::get().getWindowManager()->isGuiMode() - || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) - { - mAttemptJump = false; - return; - } - - // Configure player movement according to keyboard input. Actual movement will - // be done in the physics system. - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - { - bool alwaysRunAllowed = false; - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - - if (mBindingsManager->actionIsActive(A_MoveLeft) != mBindingsManager->actionIsActive(A_MoveRight)) - { - alwaysRunAllowed = true; - triedToMove = true; - player.setLeftRight(mBindingsManager->actionIsActive(A_MoveRight) ? 1 : -1); - } - - if (mBindingsManager->actionIsActive(A_MoveForward) != mBindingsManager->actionIsActive(A_MoveBackward)) - { - alwaysRunAllowed = true; - triedToMove = true; - player.setAutoMove (false); - player.setForwardBackward(mBindingsManager->actionIsActive(A_MoveForward) ? 1 : -1); - } - - if (player.getAutoMove()) - { - alwaysRunAllowed = true; - triedToMove = true; - player.setForwardBackward (1); - } - - if (mAttemptJump && MWBase::Environment::get().getInputManager()->getControlSwitch("playerjumping")) - { - player.setUpDown(1); - triedToMove = true; - mOverencumberedMessageDelay = 0.f; - } - - // if player tried to start moving, but can't (due to being overencumbered), display a notification. - if (triedToMove) - { - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - mOverencumberedMessageDelay -= dt; - if (playerPtr.getClass().getEncumbrance(playerPtr) > playerPtr.getClass().getCapacity(playerPtr)) - { - player.setAutoMove (false); - if (mOverencumberedMessageDelay <= 0) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); - mOverencumberedMessageDelay = 1.0; - } - } - } - - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) - { - const float switchLimit = 0.25; - MWBase::World* world = MWBase::Environment::get().getWorld(); - if (mBindingsManager->actionIsActive(A_TogglePOV)) - { - if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0) - world->togglePreviewMode(true); - mPreviewPOVDelay += dt; - } - else - { - //disable preview mode - if (mPreviewPOVDelay > 0) - world->togglePreviewMode(false); - if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit) - world->togglePOV(); - mPreviewPOVDelay = 0.f; - } - } - - if (triedToMove) - MWBase::Environment::get().getInputManager()->resetIdleTime(); - - static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); - if (!isToggleSneak) - { - if(!MWBase::Environment::get().getInputManager()->joystickLastUsed()) - player.setSneak(mBindingsManager->actionIsActive(A_Sneak)); - } - - float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight); - float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward); - bool isRunning = osg::Vec2f(xAxis * 2 - 1, yAxis * 2 - 1).length2() > 0.25f; - if ((mAlwaysRunActive && alwaysRunAllowed) || isRunning) - player.setRunState(!mBindingsManager->actionIsActive(A_Run)); - else - player.setRunState(mBindingsManager->actionIsActive(A_Run)); - } - - if (mBindingsManager->actionIsActive(A_MoveForward) || - mBindingsManager->actionIsActive(A_MoveBackward) || - mBindingsManager->actionIsActive(A_MoveLeft) || - mBindingsManager->actionIsActive(A_MoveRight) || - mBindingsManager->actionIsActive(A_Jump) || - mBindingsManager->actionIsActive(A_Sneak) || - mBindingsManager->actionIsActive(A_TogglePOV) || - mBindingsManager->actionIsActive(A_ZoomIn) || - mBindingsManager->actionIsActive(A_ZoomOut)) + if (mBindingsManager->actionIsActive(A_MoveForward) || mBindingsManager->actionIsActive(A_MoveBackward) + || mBindingsManager->actionIsActive(A_MoveLeft) || mBindingsManager->actionIsActive(A_MoveRight) + || mBindingsManager->actionIsActive(A_Jump) || mBindingsManager->actionIsActive(A_Sneak) + || mBindingsManager->actionIsActive(A_TogglePOV) || mBindingsManager->actionIsActive(A_ZoomIn) + || mBindingsManager->actionIsActive(A_ZoomOut)) { resetIdleTime(); } else - { - updateIdleTime(dt); - } - - mAttemptJump = false; - } - - bool ActionManager::isPreviewModeEnabled() - { - return MWBase::Environment::get().getWorld()->isPreviewModeEnabled(); + mTimeIdle += dt; } void ActionManager::resetIdleTime() { - if (mTimeIdle < 0) - MWBase::Environment::get().getWorld()->toggleVanityMode(false); mTimeIdle = 0.f; } - void ActionManager::updateIdleTime(float dt) - { - static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get() - .find("fVanityDelay")->mValue.getFloat(); - if (mTimeIdle >= 0.f) - mTimeIdle += dt; - if (mTimeIdle > vanityDelay) - { - MWBase::Environment::get().getWorld()->toggleVanityMode(true); - mTimeIdle = -1.f; - } - } - void ActionManager::executeAction(int action) { - auto* inputManager = MWBase::Environment::get().getInputManager(); - auto* windowManager = MWBase::Environment::get().getWindowManager(); + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::Action, action }); + const auto inputManager = MWBase::Environment::get().getInputManager(); + const auto windowManager = MWBase::Environment::get().getWindowManager(); // trigger action activated switch (action) { - case A_GameMenu: - toggleMainMenu (); - break; - case A_Screenshot: - screenshot(); - break; - case A_Inventory: - toggleInventory (); - break; - case A_Console: - toggleConsole (); - break; - case A_Activate: - inputManager->resetIdleTime(); - activate(); - break; - case A_MoveLeft: - case A_MoveRight: - case A_MoveForward: - case A_MoveBackward: - handleGuiArrowKey(action); - break; - case A_Journal: - toggleJournal(); - break; - case A_AutoMove: - toggleAutoMove(); - break; - case A_AlwaysRun: - toggleWalking(); - break; - case A_ToggleWeapon: - toggleWeapon(); - break; - case A_Rest: - rest(); - break; - case A_ToggleSpell: - toggleSpell(); - break; - case A_QuickKey1: - quickKey(1); - break; - case A_QuickKey2: - quickKey(2); - break; - case A_QuickKey3: - quickKey(3); - break; - case A_QuickKey4: - quickKey(4); - break; - case A_QuickKey5: - quickKey(5); - break; - case A_QuickKey6: - quickKey(6); - break; - case A_QuickKey7: - quickKey(7); - break; - case A_QuickKey8: - quickKey(8); - break; - case A_QuickKey9: - quickKey(9); - break; - case A_QuickKey10: - quickKey(10); - break; - case A_QuickKeysMenu: - showQuickKeysMenu(); - break; - case A_ToggleHUD: - windowManager->toggleHud(); - break; - case A_ToggleDebug: - windowManager->toggleDebugWindow(); - break; - case A_ZoomIn: - if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode()) - MWBase::Environment::get().getWorld()->adjustCameraDistance(-ZOOM_SCALE); - break; - case A_ZoomOut: - if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode()) - MWBase::Environment::get().getWorld()->adjustCameraDistance(ZOOM_SCALE); - break; - case A_QuickSave: - quickSave(); - break; - case A_QuickLoad: - quickLoad(); - break; - case A_CycleSpellLeft: - if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Magic)) - MWBase::Environment::get().getWindowManager()->cycleSpell(false); - break; - case A_CycleSpellRight: - if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Magic)) - MWBase::Environment::get().getWindowManager()->cycleSpell(true); - break; - case A_CycleWeaponLeft: - if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory)) - MWBase::Environment::get().getWindowManager()->cycleWeapon(false); - break; - case A_CycleWeaponRight: - if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory)) - MWBase::Environment::get().getWindowManager()->cycleWeapon(true); - break; - case A_Sneak: - static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); - if (isToggleSneak) - { - toggleSneaking(); - } - break; + case A_GameMenu: + toggleMainMenu(); + break; + case A_Screenshot: + screenshot(); + break; + case A_Inventory: + toggleInventory(); + break; + case A_Console: + toggleConsole(); + break; + case A_Activate: + inputManager->resetIdleTime(); + activate(); + break; + case A_MoveLeft: + case A_MoveRight: + case A_MoveForward: + case A_MoveBackward: + handleGuiArrowKey(action); + break; + case A_Journal: + toggleJournal(); + break; + case A_Rest: + rest(); + break; + case A_QuickKey1: + quickKey(1); + break; + case A_QuickKey2: + quickKey(2); + break; + case A_QuickKey3: + quickKey(3); + break; + case A_QuickKey4: + quickKey(4); + break; + case A_QuickKey5: + quickKey(5); + break; + case A_QuickKey6: + quickKey(6); + break; + case A_QuickKey7: + quickKey(7); + break; + case A_QuickKey8: + quickKey(8); + break; + case A_QuickKey9: + quickKey(9); + break; + case A_QuickKey10: + quickKey(10); + break; + case A_QuickKeysMenu: + showQuickKeysMenu(); + break; + case A_ToggleHUD: + windowManager->toggleHud(); + break; + case A_ToggleDebug: + windowManager->toggleDebugWindow(); + break; + case A_TogglePostProcessorHUD: + windowManager->togglePostProcessorHud(); + break; + case A_QuickSave: + quickSave(); + break; + case A_QuickLoad: + quickLoad(); + break; + case A_CycleSpellLeft: + if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Magic)) + MWBase::Environment::get().getWindowManager()->cycleSpell(false); + break; + case A_CycleSpellRight: + if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Magic)) + MWBase::Environment::get().getWindowManager()->cycleSpell(true); + break; + case A_CycleWeaponLeft: + if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory)) + MWBase::Environment::get().getWindowManager()->cycleWeapon(false); + break; + case A_CycleWeaponRight: + if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory)) + MWBase::Environment::get().getWindowManager()->cycleWeapon(true); + break; } } @@ -346,7 +195,7 @@ namespace MWInput void ActionManager::screenshot() { const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); - bool regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; + bool regularScreenshot = settingStr.empty() || settingStr == "regular"; if (regularScreenshot) { @@ -355,11 +204,11 @@ namespace MWInput } else { - osg::ref_ptr screenshot (new osg::Image); + osg::ref_ptr screenshot(new osg::Image); if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) { - (*mScreenCaptureOperation) (*(screenshot.get()), 0); + (*mScreenCaptureOperation)(*(screenshot.get()), 0); // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason } } @@ -392,44 +241,22 @@ namespace MWInput return; } - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) //No open GUIs, open up the MainMenu + if (MWBase::Environment::get().getWindowManager()->isPostProcessorHudVisible()) { - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getWindowManager()->togglePostProcessorHud(); + return; } - else //Close current GUI + + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) // No open GUIs, open up the MainMenu + { + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); + } + else // Close current GUI { MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } } - void ActionManager::toggleSpell() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - - // Not allowed before the magic window is accessible - if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playermagic") || !MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - return; - - if (!checkAllowedToUseItems()) - return; - - // Not allowed if no spell selected - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - MWWorld::InventoryStore& inventory = player.getPlayer().getClass().getInventoryStore(player.getPlayer()); - if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty() && - inventory.getSelectedEnchantItem() == inventory.end()) - return; - - if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player.getPlayer())) - return; - - MWMechanics::DrawState_ state = player.getDrawState(); - if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing) - player.setDrawState(MWMechanics::DrawState_Spell); - else - player.setDrawState(MWMechanics::DrawState_Nothing); - } - void ActionManager::quickLoad() { if (!MyGUI::InputManager::getInstance().isModalAny()) @@ -442,37 +269,16 @@ namespace MWInput MWBase::Environment::get().getStateManager()->quickSave(); } - void ActionManager::toggleWeapon() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - - // Not allowed before the inventory window is accessible - if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playerfighting") || !MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - return; - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - // We want to interrupt animation only if attack is preparing, but still is not triggered - // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice - if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(player.getPlayer())) - player.setAttackingOrSpell(false); - else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player.getPlayer())) - return; - - MWMechanics::DrawState_ state = player.getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - player.setDrawState(MWMechanics::DrawState_Weapon); - else - player.setDrawState(MWMechanics::DrawState_Nothing); - } - void ActionManager::rest() { if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; - if (!MWBase::Environment::get().getWindowManager()->getRestEnabled() || MWBase::Environment::get().getWindowManager()->isGuiMode()) + if (!MWBase::Environment::get().getWindowManager()->getRestEnabled() + || MWBase::Environment::get().getWindowManager()->isGuiMode()) return; +<<<<<<< HEAD /* Start of tes3mp addition @@ -490,6 +296,9 @@ namespace MWInput */ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Rest); //Open rest GUI +======= + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Rest); // Open rest GUI +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } void ActionManager::toggleInventory() @@ -515,12 +324,12 @@ namespace MWInput */ // Toggle between game mode and inventory mode - if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Inventory); else { MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); - if(mode == MWGui::GM_Inventory || mode == MWGui::GM_Container) + if (mode == MWGui::GM_Inventory || mode == MWGui::GM_Container) MWBase::Environment::get().getWindowManager()->popGuiMode(); } @@ -550,54 +359,52 @@ namespace MWInput { if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; - if (MyGUI::InputManager::getInstance ().isModalAny()) + if (MyGUI::InputManager::getInstance().isModalAny()) return; - if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Journal - && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_MainMenu - && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings - && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); + if (windowManager->getMode() != MWGui::GM_Journal && windowManager->getMode() != MWGui::GM_MainMenu + && windowManager->getMode() != MWGui::GM_Settings && windowManager->getJournalAllowed()) { - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); + windowManager->pushGuiMode(MWGui::GM_Journal); } - else if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Journal)) + else if (windowManager->containsMode(MWGui::GM_Journal)) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Journal); + windowManager->removeGuiMode(MWGui::GM_Journal); } } - void ActionManager::quickKey (int index) + void ActionManager::quickKey(int index) { - if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols") || !MWBase::Environment::get().getInputManager()->getControlSwitch("playerfighting") || !MWBase::Environment::get().getInputManager()->getControlSwitch("playermagic")) + if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols") + || !MWBase::Environment::get().getInputManager()->getControlSwitch("playerfighting") + || !MWBase::Environment::get().getInputManager()->getControlSwitch("playermagic")) return; if (!checkAllowedToUseItems()) return; - if (MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")!=-1) + if (MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) != -1) return; if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - MWBase::Environment::get().getWindowManager()->activateQuickKey (index); + MWBase::Environment::get().getWindowManager()->activateQuickKey(index); } void ActionManager::showQuickKeysMenu() { - if (!MWBase::Environment::get().getWindowManager()->isGuiMode () - && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_QuickKeysMenu) { - if (!checkAllowedToUseItems()) - return; + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + return; + } - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); - } - else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) - { - while (MyGUI::InputManager::getInstance().isModalAny()) - { //Handle any open Modal windows - MWBase::Environment::get().getWindowManager()->exitCurrentModal(); - } - MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); //And handle the actual main window - } + if (MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) != -1) + return; + + if (!checkAllowedToUseItems()) + return; + + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_QuickKeysMenu); } void ActionManager::activate() @@ -615,32 +422,10 @@ namespace MWInput } } - void ActionManager::toggleAutoMove() + bool ActionManager::isSneaking() const { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - { - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - player.setAutoMove (!player.getAutoMove()); - } - } - - void ActionManager::toggleWalking() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode() || SDL_IsTextInputActive()) return; - mAlwaysRunActive = !mAlwaysRunActive; - - Settings::Manager::setBool("always run", "Input", mAlwaysRunActive); - } - - void ActionManager::toggleSneaking() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) return; - mSneaking = !mSneaking; - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - player.setSneak(mSneaking); + const MWBase::Environment& env = MWBase::Environment::get(); + return env.getMechanicsManager()->isSneaking(env.getWorld()->getPlayer().getPlayer()); } void ActionManager::handleGuiArrowKey(int action) @@ -660,19 +445,19 @@ namespace MWInput MyGUI::KeyCode key; switch (action) { - case A_MoveLeft: - key = MyGUI::KeyCode::ArrowLeft; - break; - case A_MoveRight: - key = MyGUI::KeyCode::ArrowRight; - break; - case A_MoveForward: - key = MyGUI::KeyCode::ArrowUp; - break; - case A_MoveBackward: - default: - key = MyGUI::KeyCode::ArrowDown; - break; + case A_MoveLeft: + key = MyGUI::KeyCode::ArrowLeft; + break; + case A_MoveRight: + key = MyGUI::KeyCode::ArrowRight; + break; + case A_MoveForward: + key = MyGUI::KeyCode::ArrowUp; + break; + case A_MoveBackward: + default: + key = MyGUI::KeyCode::ArrowDown; + break; } MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false); diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index eceac2e94..0979e9ad7 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -17,61 +17,42 @@ namespace MWInput class ActionManager { public: - ActionManager(BindingsManager* bindingsManager, osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, - osg::ref_ptr viewer, - osg::ref_ptr screenCaptureHandler); + osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler); - void update(float dt, bool triedToMove); + void update(float dt); void executeAction(int action); bool checkAllowedToUseItems() const; void toggleMainMenu(); - void toggleSpell(); - void toggleWeapon(); void toggleInventory(); void toggleConsole(); void screenshot(); void toggleJournal(); void activate(); - void toggleWalking(); - void toggleSneaking(); - void toggleAutoMove(); void rest(); void quickLoad(); void quickSave(); - void quickKey (int index); + void quickKey(int index); void showQuickKeysMenu(); void resetIdleTime(); + float getIdleTime() const { return mTimeIdle; } - bool isAlwaysRunActive() const { return mAlwaysRunActive; }; - bool isSneaking() const { return mSneaking; }; - - void setAttemptJump(bool enabled) { mAttemptJump = enabled; } - - bool isPreviewModeEnabled(); + bool isSneaking() const; private: void handleGuiArrowKey(int action); - void updateIdleTime(float dt); - BindingsManager* mBindingsManager; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; osgViewer::ScreenCaptureHandler::CaptureOperation* mScreenCaptureOperation; - bool mAlwaysRunActive; - bool mSneaking; - bool mAttemptJump; - - float mOverencumberedMessageDelay; - float mPreviewPOVDelay; float mTimeIdle; }; } diff --git a/apps/openmw/mwinput/actions.hpp b/apps/openmw/mwinput/actions.hpp index a1c160712..538d12f2c 100644 --- a/apps/openmw/mwinput/actions.hpp +++ b/apps/openmw/mwinput/actions.hpp @@ -5,75 +5,70 @@ namespace MWInput { enum Actions { - // please add new actions at the bottom, in order to preserve the channel IDs in the key configuration files + // Action IDs are used in the configuration file input_v3.xml - A_GameMenu, + A_GameMenu = 0, - A_Unused, + A_Screenshot = 2, // Take a screenshot - A_Screenshot, // Take a screenshot + A_Inventory = 3, // Toggle inventory screen + A_Console = 4, // Toggle console screen - A_Inventory, // Toggle inventory screen + A_MoveLeft = 5, // Move player left / right + A_MoveRight = 6, + A_MoveForward = 7, // Forward / Backward + A_MoveBackward = 8, - A_Console, // Toggle console screen + A_Activate = 9, - A_MoveLeft, // Move player left / right - A_MoveRight, - A_MoveForward, // Forward / Backward - A_MoveBackward, + A_Use = 10, // Use weapon, spell, etc. + A_Jump = 11, + A_AutoMove = 12, // Toggle Auto-move forward + A_Rest = 13, // Rest + A_Journal = 14, // Journal + A_Run = 17, // Run when held + A_CycleSpellLeft = 18, // cycling through spells + A_CycleSpellRight = 19, + A_CycleWeaponLeft = 20, // Cycling through weapons + A_CycleWeaponRight = 21, + A_AlwaysRun = 23, // Toggle Walking/Running + A_Sneak = 24, - A_Activate, + A_QuickSave = 25, + A_QuickLoad = 26, + A_QuickMenu = 27, + A_ToggleWeapon = 28, + A_ToggleSpell = 29, - A_Use, //Use weapon, spell, etc. - A_Jump, - A_AutoMove, //Toggle Auto-move forward - A_Rest, //Rest - A_Journal, //Journal - A_Weapon, //Draw/Sheath weapon - A_Spell, //Ready/Unready Casting - A_Run, //Run when held - A_CycleSpellLeft, //cycling through spells - A_CycleSpellRight, - A_CycleWeaponLeft, //Cycling through weapons - A_CycleWeaponRight, - A_ToggleSneak, //Toggles Sneak - A_AlwaysRun, //Toggle Walking/Running - A_Sneak, + A_TogglePOV = 30, - A_QuickSave, - A_QuickLoad, - A_QuickMenu, - A_ToggleWeapon, - A_ToggleSpell, + A_QuickKey1 = 31, + A_QuickKey2 = 32, + A_QuickKey3 = 33, + A_QuickKey4 = 34, + A_QuickKey5 = 35, + A_QuickKey6 = 36, + A_QuickKey7 = 37, + A_QuickKey8 = 38, + A_QuickKey9 = 39, + A_QuickKey10 = 40, - A_TogglePOV, + A_QuickKeysMenu = 41, - A_QuickKey1, - A_QuickKey2, - A_QuickKey3, - A_QuickKey4, - A_QuickKey5, - A_QuickKey6, - A_QuickKey7, - A_QuickKey8, - A_QuickKey9, - A_QuickKey10, + A_ToggleHUD = 42, + A_ToggleDebug = 43, - A_QuickKeysMenu, + A_LookUpDown = 44, // Joystick look + A_LookLeftRight = 45, + A_MoveForwardBackward = 46, + A_MoveLeftRight = 47, - A_ToggleHUD, + A_ZoomIn = 48, + A_ZoomOut = 49, - A_ToggleDebug, + A_TogglePostProcessorHUD = 50, - A_LookUpDown, //Joystick look - A_LookLeftRight, - A_MoveForwardBackward, - A_MoveLeftRight, - - A_ZoomIn, - A_ZoomOut, - - A_Last // Marker for the last item + A_Last // Marker for the last item }; } #endif diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 51dcd46da..744ffef6b 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -5,6 +5,7 @@ #include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -15,6 +16,10 @@ /* End of tes3mp addition */ +======= +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" @@ -24,11 +29,11 @@ #include "../mwworld/player.hpp" #include "actions.hpp" -#include "sdlmappings.hpp" namespace MWInput { - static const int sFakeDeviceId = 1; //As we only support one controller at a time, use a fake deviceID so we don't lose bindings when switching controllers + static const int sFakeDeviceId = 1; // As we only support one controller at a time, use a fake deviceID so we don't + // lose bindings when switching controllers void clearAllKeyBindings(ICS::InputControlSystem* inputBinder, ICS::Control* control) { @@ -37,7 +42,8 @@ namespace MWInput inputBinder->removeKeyBinding(inputBinder->getKeyBinding(control, ICS::Control::INCREASE)); if (inputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) inputBinder->removeMouseButtonBinding(inputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE)); - if (inputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) != ICS::InputControlSystem::MouseWheelClick::UNASSIGNED) + if (inputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) + != ICS::InputControlSystem::MouseWheelClick::UNASSIGNED) inputBinder->removeMouseWheelBinding(inputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE)); } @@ -45,23 +51,24 @@ namespace MWInput { // right now we don't really need multiple bindings for the same action, so remove all others first if (inputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) != SDL_SCANCODE_UNKNOWN) - inputBinder->removeJoystickAxisBinding(sFakeDeviceId, inputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE)); - if (inputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) - inputBinder->removeJoystickButtonBinding(sFakeDeviceId, inputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE)); + inputBinder->removeJoystickAxisBinding( + sFakeDeviceId, inputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE)); + if (inputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) + != ICS_MAX_DEVICE_BUTTONS) + inputBinder->removeJoystickButtonBinding( + sFakeDeviceId, inputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE)); } class InputControlSystem : public ICS::InputControlSystem { public: - InputControlSystem(const std::string& bindingsFile) - : ICS::InputControlSystem(bindingsFile, true, nullptr, nullptr, A_Last) + InputControlSystem(const std::filesystem::path& bindingsFile) + : ICS::InputControlSystem(Files::pathToUnicodeString(bindingsFile), true, nullptr, nullptr, A_Last) { } }; - class BindingsListener : - public ICS::ChannelListener, - public ICS::DetectingBindingListener + class BindingsListener : public ICS::ChannelListener, public ICS::DetectingBindingListener { public: BindingsListener(ICS::InputControlSystem* inputBinder, BindingsManager* bindingsManager) @@ -79,13 +86,13 @@ namespace MWInput mBindingsManager->actionValueChanged(action, currentValue, previousValue); } - void keyBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control - , SDL_Scancode key, ICS::Control::ControlChangingDirection direction) override + void keyBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control, SDL_Scancode key, + ICS::Control::ControlChangingDirection direction) override { - //Disallow binding escape key - if (key==SDL_SCANCODE_ESCAPE) + // Disallow binding escape key + if (key == SDL_SCANCODE_ESCAPE) { - //Stop binding if esc pressed + // Stop binding if esc pressed mInputBinder->cancelDetectingBindingState(); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); return; @@ -95,11 +102,11 @@ namespace MWInput if (key == SDL_SCANCODE_F3 || key == SDL_SCANCODE_F4 || key == SDL_SCANCODE_F10) return; - #ifndef __APPLE__ +#ifndef __APPLE__ // Disallow binding Windows/Meta keys if (key == SDL_SCANCODE_LGUI || key == SDL_SCANCODE_RGUI) return; - #endif +#endif if (!mDetectingKeyboard) return; @@ -110,15 +117,15 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } - void mouseAxisBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control - , ICS::InputControlSystem::NamedAxis axis, ICS::Control::ControlChangingDirection direction) override + void mouseAxisBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control, + ICS::InputControlSystem::NamedAxis axis, ICS::Control::ControlChangingDirection direction) override { // we don't want mouse movement bindings return; } - void mouseButtonBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control - , unsigned int button, ICS::Control::ControlChangingDirection direction) override + void mouseButtonBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control, unsigned int button, + ICS::Control::ControlChangingDirection direction) override { if (!mDetectingKeyboard) return; @@ -128,8 +135,8 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } - void mouseWheelBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control - , ICS::InputControlSystem::MouseWheelClick click, ICS::Control::ControlChangingDirection direction) override + void mouseWheelBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control, + ICS::InputControlSystem::MouseWheelClick click, ICS::Control::ControlChangingDirection direction) override { if (!mDetectingKeyboard) return; @@ -139,30 +146,30 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } - void joystickAxisBindingDetected(ICS::InputControlSystem* ICS, int deviceID, ICS::Control* control - , int axis, ICS::Control::ControlChangingDirection direction) override + void joystickAxisBindingDetected(ICS::InputControlSystem* ICS, int deviceID, ICS::Control* control, int axis, + ICS::Control::ControlChangingDirection direction) override { - //only allow binding to the trigers + // only allow binding to the trigers if (axis != SDL_CONTROLLER_AXIS_TRIGGERLEFT && axis != SDL_CONTROLLER_AXIS_TRIGGERRIGHT) return; if (mDetectingKeyboard) return; clearAllControllerBindings(mInputBinder, control); - control->setValue(0.5f); //axis bindings must start at 0.5 + control->setValue(0.5f); // axis bindings must start at 0.5 control->setInitialValue(0.5f); ICS::DetectingBindingListener::joystickAxisBindingDetected(ICS, deviceID, control, axis, direction); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } - void joystickButtonBindingDetected(ICS::InputControlSystem* ICS, int deviceID, ICS::Control* control - , unsigned int button, ICS::Control::ControlChangingDirection direction) override + void joystickButtonBindingDetected(ICS::InputControlSystem* ICS, int deviceID, ICS::Control* control, + unsigned int button, ICS::Control::ControlChangingDirection direction) override { if (mDetectingKeyboard) return; - clearAllControllerBindings(mInputBinder,control); + clearAllControllerBindings(mInputBinder, control); control->setInitialValue(0.0f); - ICS::DetectingBindingListener::joystickButtonBindingDetected (ICS, deviceID, control, button, direction); + ICS::DetectingBindingListener::joystickButtonBindingDetected(ICS, deviceID, control, button, direction); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } @@ -177,11 +184,11 @@ namespace MWInput bool mDetectingKeyboard; }; - BindingsManager::BindingsManager(const std::string& userFile, bool userFileExists) + BindingsManager::BindingsManager(const std::filesystem::path& userFile, bool userFileExists) : mUserFile(userFile) , mDragDrop(false) { - std::string file = userFileExists ? userFile : ""; + const auto file = userFileExists ? userFile : std::filesystem::path(); mInputBinder = std::make_unique(file); mListener = std::make_unique(mInputBinder.get(), this); mInputBinder->setDetectingBindingListener(mListener.get()); @@ -202,7 +209,7 @@ namespace MWInput BindingsManager::~BindingsManager() { - mInputBinder->save(mUserFile); + mInputBinder->save(Files::pathToUnicodeString(mUserFile)); } void BindingsManager::update(float dt) @@ -213,10 +220,12 @@ namespace MWInput bool BindingsManager::isLeftOrRightButton(int action, bool joystick) const { - int mouseBinding = mInputBinder->getMouseButtonBinding(mInputBinder->getControl(action), ICS::Control::INCREASE); + int mouseBinding + = mInputBinder->getMouseButtonBinding(mInputBinder->getControl(action), ICS::Control::INCREASE); if (mouseBinding != ICS_MAX_DEVICE_BUTTONS) return true; - int buttonBinding = mInputBinder->getJoystickButtonBinding(mInputBinder->getControl(action), sFakeDeviceId, ICS::Control::INCREASE); + int buttonBinding = mInputBinder->getJoystickButtonBinding( + mInputBinder->getControl(action), sFakeDeviceId, ICS::Control::INCREASE); if (joystick && (buttonBinding == 0 || buttonBinding == 1)) return true; return false; @@ -224,13 +233,11 @@ namespace MWInput void BindingsManager::setPlayerControlsEnabled(bool enabled) { - int playerChannels[] = {A_AutoMove, A_AlwaysRun, A_ToggleWeapon, - A_ToggleSpell, A_Rest, A_QuickKey1, A_QuickKey2, - A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, - A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, - A_Use, A_Journal}; + int playerChannels[] = { A_AutoMove, A_AlwaysRun, A_ToggleWeapon, A_ToggleSpell, A_Rest, A_QuickKey1, + A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, + A_QuickKey10, A_Use, A_Journal }; - for(int pc : playerChannels) + for (int pc : playerChannels) { mInputBinder->getChannel(pc)->setEnabled(enabled); } @@ -241,23 +248,23 @@ namespace MWInput mInputBinder->setJoystickDeadZone(deadZone); } - float BindingsManager::getActionValue (int id) const + float BindingsManager::getActionValue(int id) const { return mInputBinder->getChannel(id)->getValue(); } - bool BindingsManager::actionIsActive (int id) const + bool BindingsManager::actionIsActive(int id) const { return getActionValue(id) == 1.0; } - void BindingsManager::loadKeyDefaults (bool force) + void BindingsManager::loadKeyDefaults(bool force) { // using hardcoded key defaults is inevitable, if we want the configuration files to stay valid // across different versions of OpenMW (in the case where another input action is added) std::map defaultKeyBindings; - //Gets the Keyvalue from the Scancode; gives the button in the same place reguardless of keyboard format + // Gets the Keyvalue from the Scancode; gives the button in the same place reguardless of keyboard format defaultKeyBindings[A_Activate] = SDL_SCANCODE_SPACE; defaultKeyBindings[A_MoveBackward] = SDL_SCANCODE_S; defaultKeyBindings[A_MoveForward] = SDL_SCANCODE_W; @@ -296,6 +303,7 @@ namespace MWInput defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; defaultKeyBindings[A_QuickSave] = SDL_SCANCODE_F5; defaultKeyBindings[A_QuickLoad] = SDL_SCANCODE_F9; + defaultKeyBindings[A_TogglePostProcessorHUD] = SDL_SCANCODE_F2; std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; @@ -320,38 +328,41 @@ namespace MWInput control = mInputBinder->getChannel(i)->getAttachedControls().front().control; } - if (!controlExists || force || - (mInputBinder->getKeyBinding(control, ICS::Control::INCREASE) == SDL_SCANCODE_UNKNOWN - && mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS - && mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED)) + if (!controlExists || force + || (mInputBinder->getKeyBinding(control, ICS::Control::INCREASE) == SDL_SCANCODE_UNKNOWN + && mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS + && mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) + == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED)) { clearAllKeyBindings(mInputBinder.get(), control); if (defaultKeyBindings.find(i) != defaultKeyBindings.end() - && (force || !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() - && (force || !mInputBinder->isMouseButtonBound(defaultMouseButtonBindings[i]))) + && (force || !mInputBinder->isMouseButtonBound(defaultMouseButtonBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addMouseButtonBinding(control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); } else if (defaultMouseWheelBindings.find(i) != defaultMouseWheelBindings.end() - && (force || !mInputBinder->isMouseWheelBound(defaultMouseWheelBindings[i]))) + && (force || !mInputBinder->isMouseWheelBound(defaultMouseWheelBindings[i]))) { control->setInitialValue(0.f); mInputBinder->addMouseWheelBinding(control, defaultMouseWheelBindings[i], ICS::Control::INCREASE); } - if (i == A_LookLeftRight && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_4) && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_6)) + if (i == A_LookLeftRight && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_4) + && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_6)) { mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_6, ICS::Control::INCREASE); mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_4, ICS::Control::DECREASE); } - if (i == A_LookUpDown && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_8) && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_2)) + if (i == A_LookUpDown && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_8) + && !mInputBinder->isKeyBound(SDL_SCANCODE_KP_2)) { mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_2, ICS::Control::INCREASE); mInputBinder->addKeyBinding(control, SDL_SCANCODE_KP_8, ICS::Control::DECREASE); @@ -369,7 +380,8 @@ namespace MWInput defaultButtonBindings[A_Activate] = SDL_CONTROLLER_BUTTON_A; defaultButtonBindings[A_ToggleWeapon] = SDL_CONTROLLER_BUTTON_X; defaultButtonBindings[A_ToggleSpell] = SDL_CONTROLLER_BUTTON_Y; - //defaultButtonBindings[A_QuickButtonsMenu] = SDL_GetButtonFromScancode(SDL_SCANCODE_F1); // Need to implement, should be ToggleSpell(5) AND Wait(9) + // defaultButtonBindings[A_QuickButtonsMenu] = SDL_GetButtonFromScancode(SDL_SCANCODE_F1); // Need to implement, + // should be ToggleSpell(5) AND Wait(9) defaultButtonBindings[A_Sneak] = SDL_CONTROLLER_BUTTON_LEFTSTICK; defaultButtonBindings[A_Journal] = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; defaultButtonBindings[A_Rest] = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; @@ -399,7 +411,8 @@ namespace MWInput float initial; if (defaultAxisBindings.find(i) == defaultAxisBindings.end()) initial = 0.0f; - else initial = 0.5f; + else + initial = 0.5f; control = new ICS::Control(std::to_string(i), false, true, initial, ICS::ICS_MAX, ICS::ICS_MAX); mInputBinder->addControl(control); control->attachChannel(mInputBinder->getChannel(i), ICS::Channel::DIRECT); @@ -409,39 +422,45 @@ namespace MWInput control = mInputBinder->getChannel(i)->getAttachedControls().front().control; } - if (!controlExists || force || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS::InputControlSystem::UNASSIGNED && - mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS)) + if (!controlExists || force + || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) + == ICS::InputControlSystem::UNASSIGNED + && mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) + == ICS_MAX_DEVICE_BUTTONS)) { clearAllControllerBindings(mInputBinder.get(), control); if (defaultButtonBindings.find(i) != defaultButtonBindings.end() - && (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i]))) + && (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i]))) { control->setInitialValue(0.0f); - mInputBinder->addJoystickButtonBinding(control, sFakeDeviceId, defaultButtonBindings[i], ICS::Control::INCREASE); + mInputBinder->addJoystickButtonBinding( + control, sFakeDeviceId, defaultButtonBindings[i], ICS::Control::INCREASE); } - else if (defaultAxisBindings.find(i) != defaultAxisBindings.end() && (force || !mInputBinder->isJoystickAxisBound(sFakeDeviceId, defaultAxisBindings[i]))) + else if (defaultAxisBindings.find(i) != defaultAxisBindings.end() + && (force || !mInputBinder->isJoystickAxisBound(sFakeDeviceId, defaultAxisBindings[i]))) { control->setValue(0.5f); control->setInitialValue(0.5f); - mInputBinder->addJoystickAxisBinding(control, sFakeDeviceId, defaultAxisBindings[i], ICS::Control::INCREASE); + mInputBinder->addJoystickAxisBinding( + control, sFakeDeviceId, defaultAxisBindings[i], ICS::Control::INCREASE); } } } } - std::string BindingsManager::getActionDescription(int action) + std::string_view BindingsManager::getActionDescription(int action) { switch (action) { case A_Screenshot: - return "Screenshot"; + return "#{OMWEngine:Screenshot}"; case A_ZoomIn: - return "Zoom In"; + return "#{OMWEngine:CameraZoomIn}"; case A_ZoomOut: - return "Zoom Out"; + return "#{OMWEngine:CameraZoomOut}"; case A_ToggleHUD: - return "Toggle HUD"; + return "#{OMWEngine:ToggleHUD}"; case A_Use: return "#{sUse}"; case A_Activate: @@ -467,7 +486,7 @@ namespace MWInput case A_CycleWeaponRight: return "#{sNextWeapon}"; case A_Console: - return "#{sConsoleTitle}"; + return "#{OMWEngine:ConsoleWindow}"; case A_Run: return "#{sRun}"; case A_Sneak: @@ -512,15 +531,17 @@ namespace MWInput return "#{sQuickSaveCmd}"; case A_QuickLoad: return "#{sQuickLoadCmd}"; + case A_TogglePostProcessorHUD: + return "#{OMWEngine:TogglePostProcessorHUD}"; default: - return std::string(); // not configurable + return {}; // not configurable } } std::string BindingsManager::getActionKeyBindingName(int action) { if (mInputBinder->getChannel(action)->getControlsCount() == 0) - return "#{sNone}"; + return "#{Interface:None}"; ICS::Control* c = mInputBinder->getChannel(action)->getAttachedControls().front().control; @@ -543,51 +564,49 @@ namespace MWInput case ICS::InputControlSystem::MouseWheelClick::LEFT: return "Mouse Wheel Left"; default: - return "#{sNone}"; + return "#{Interface:None}"; } else - return "#{sNone}"; + return "#{Interface:None}"; } std::string BindingsManager::getActionControllerBindingName(int action) { if (mInputBinder->getChannel(action)->getControlsCount() == 0) - return "#{sNone}"; + return "#{Interface:None}"; ICS::Control* c = mInputBinder->getChannel(action)->getAttachedControls().front().control; - if (mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS::InputControlSystem::UNASSIGNED) - return sdlControllerAxisToString(mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); - else if (mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) - return sdlControllerButtonToString(mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); + if (mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE) + != ICS::InputControlSystem::UNASSIGNED) + return SDLUtil::sdlControllerAxisToString( + mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); + else if (mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE) + != ICS_MAX_DEVICE_BUTTONS) + return SDLUtil::sdlControllerButtonToString( + mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); else - return "#{sNone}"; + return "#{Interface:None}"; } - std::vector BindingsManager::getActionKeySorting() + const std::initializer_list& BindingsManager::getActionKeySorting() { - static const std::vector actions - { - A_MoveForward, A_MoveBackward, A_MoveLeft, A_MoveRight, A_TogglePOV, A_ZoomIn, A_ZoomOut, - A_Run, A_AlwaysRun, A_Sneak, A_Activate, A_Use, A_ToggleWeapon, A_ToggleSpell, - A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight, A_AutoMove, - A_Jump, A_Inventory, A_Journal, A_Rest, A_Console, A_QuickSave, A_QuickLoad, - A_ToggleHUD, A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, - A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10 - }; + static const std::initializer_list actions{ A_MoveForward, A_MoveBackward, A_MoveLeft, A_MoveRight, + A_TogglePOV, A_ZoomIn, A_ZoomOut, A_Run, A_AlwaysRun, A_Sneak, A_Activate, A_Use, A_ToggleWeapon, + A_ToggleSpell, A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight, A_AutoMove, + A_Jump, A_Inventory, A_Journal, A_Rest, A_Console, A_QuickSave, A_QuickLoad, A_ToggleHUD, A_Screenshot, + A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, + A_QuickKey8, A_QuickKey9, A_QuickKey10, A_TogglePostProcessorHUD }; return actions; } - std::vector BindingsManager::getActionControllerSorting() + const std::initializer_list& BindingsManager::getActionControllerSorting() { - static const std::vector actions - { - A_TogglePOV, A_ZoomIn, A_ZoomOut, A_Sneak, A_Activate, A_Use, A_ToggleWeapon, A_ToggleSpell, - A_AutoMove, A_Jump, A_Inventory, A_Journal, A_Rest, A_QuickSave, A_QuickLoad, A_ToggleHUD, - A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, - A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, - A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight - }; + static const std::initializer_list actions{ A_TogglePOV, A_ZoomIn, A_ZoomOut, A_Sneak, A_Activate, A_Use, + A_ToggleWeapon, A_ToggleSpell, A_AutoMove, A_Jump, A_Inventory, A_Journal, A_Rest, A_QuickSave, A_QuickLoad, + A_ToggleHUD, A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, + A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, A_CycleSpellLeft, A_CycleSpellRight, + A_CycleWeaponLeft, A_CycleWeaponRight }; return actions; } @@ -604,27 +623,27 @@ namespace MWInput return mInputBinder->detectingBindingState(); } - void BindingsManager::mousePressed(const SDL_MouseButtonEvent &arg, int deviceID) + void BindingsManager::mousePressed(const SDL_MouseButtonEvent& arg, int deviceID) { mInputBinder->mousePressed(arg, deviceID); } - void BindingsManager::mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID) + void BindingsManager::mouseReleased(const SDL_MouseButtonEvent& arg, int deviceID) { mInputBinder->mouseReleased(arg, deviceID); } - void BindingsManager::mouseMoved(const SDLUtil::MouseMotionEvent &arg) + void BindingsManager::mouseMoved(const SDLUtil::MouseMotionEvent& arg) { mInputBinder->mouseMoved(arg); } - void BindingsManager::mouseWheelMoved(const SDL_MouseWheelEvent &arg) + void BindingsManager::mouseWheelMoved(const SDL_MouseWheelEvent& arg) { mInputBinder->mouseWheelMoved(arg); } - void BindingsManager::keyPressed(const SDL_KeyboardEvent &arg) + void BindingsManager::keyPressed(const SDL_KeyboardEvent& arg) { /* Start of tes3mp addition @@ -639,32 +658,32 @@ namespace MWInput mInputBinder->keyPressed(arg); } - void BindingsManager::keyReleased(const SDL_KeyboardEvent &arg) + void BindingsManager::keyReleased(const SDL_KeyboardEvent& arg) { mInputBinder->keyReleased(arg); } - void BindingsManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) + void BindingsManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent& arg) { mInputBinder->controllerAdded(deviceID, arg); } - void BindingsManager::controllerRemoved(const SDL_ControllerDeviceEvent &arg) + void BindingsManager::controllerRemoved(const SDL_ControllerDeviceEvent& arg) { mInputBinder->controllerRemoved(arg); } - void BindingsManager::controllerButtonPressed(int deviceID, const SDL_ControllerButtonEvent &arg) + void BindingsManager::controllerButtonPressed(int deviceID, const SDL_ControllerButtonEvent& arg) { mInputBinder->buttonPressed(deviceID, arg); } - void BindingsManager::controllerButtonReleased(int deviceID, const SDL_ControllerButtonEvent &arg) + void BindingsManager::controllerButtonReleased(int deviceID, const SDL_ControllerButtonEvent& arg) { mInputBinder->buttonReleased(deviceID, arg); } - void BindingsManager::controllerAxisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) + void BindingsManager::controllerAxisMoved(int deviceID, const SDL_ControllerAxisEvent& arg) { mInputBinder->axisMoved(deviceID, arg); } @@ -674,34 +693,36 @@ namespace MWInput return mInputBinder->getKeyBinding(mInputBinder->getControl(actionId), ICS::Control::INCREASE); } + SDL_GameController* BindingsManager::getControllerOrNull() const + { + const auto& controllers = mInputBinder->getJoystickInstanceMap(); + if (controllers.empty()) + return nullptr; + else + return controllers.begin()->second; + } + void BindingsManager::actionValueChanged(int action, float currentValue, float previousValue) { - MWBase::Environment::get().getInputManager()->resetIdleTime(); + auto manager = MWBase::Environment::get().getInputManager(); + manager->resetIdleTime(); if (mDragDrop && action != A_GameMenu && action != A_Inventory) return; - if ((previousValue == 1 || previousValue == 0) && (currentValue==1 || currentValue==0)) + if (manager->joystickLastUsed() && manager->getControlSwitch("playercontrols")) { - //Is a normal button press, so don't change it at all - } - //Otherwise only trigger button presses as they go through specific points - else if (previousValue >= 0.8 && currentValue < 0.8) - { - currentValue = 0.0; - previousValue = 1.0; - } - else if (previousValue <= 0.6 && currentValue > 0.6) - { - currentValue = 1.0; - previousValue = 0.0; - } - else - { - //If it's not switching between those values, ignore the channel change. - return; + if (action == A_Use && actionIsActive(A_ToggleWeapon)) + action = A_CycleWeaponRight; + else if (action == A_Use && actionIsActive(A_ToggleSpell)) + action = A_CycleSpellRight; + else if (action == A_Jump && actionIsActive(A_ToggleWeapon)) + action = A_CycleWeaponLeft; + else if (action == A_Jump && actionIsActive(A_ToggleSpell)) + action = A_CycleSpellLeft; } +<<<<<<< HEAD if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) { bool joystickUsed = MWBase::Environment::get().getInputManager()->joystickLastUsed(); @@ -746,5 +767,9 @@ namespace MWInput if (currentValue == 1) MWBase::Environment::get().getInputManager()->executeAction(action); +======= + if (previousValue <= 0.6 && currentValue > 0.6) + manager->executeAction(action); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index 74416d3c7..a11baf74d 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -1,6 +1,7 @@ #ifndef MWINPUT_MWBINDINGSMANAGER_H #define MWINPUT_MWBINDINGSMANAGER_H +#include #include #include #include @@ -15,17 +16,17 @@ namespace MWInput class BindingsManager { public: - BindingsManager(const std::string& userFile, bool userFileExists); + BindingsManager(const std::filesystem::path& userFile, bool userFileExists); virtual ~BindingsManager(); - std::string getActionDescription (int action); - std::string getActionKeyBindingName (int action); - std::string getActionControllerBindingName (int action); - std::vector getActionKeySorting(); - std::vector getActionControllerSorting(); + std::string_view getActionDescription(int action); + std::string getActionKeyBindingName(int action); + std::string getActionControllerBindingName(int action); + const std::initializer_list& getActionKeySorting(); + const std::initializer_list& getActionControllerSorting(); - void enableDetectingBindingMode (int action, bool keyboard); + void enableDetectingBindingMode(int action, bool keyboard); bool isDetectingBindingState() const; void loadKeyDefaults(bool force = false); @@ -42,21 +43,23 @@ namespace MWInput bool isLeftOrRightButton(int action, bool joystick) const; bool actionIsActive(int id) const; - float getActionValue(int id) const; + float getActionValue(int id) const; // returns value in range [0, 1] - void mousePressed(const SDL_MouseButtonEvent &evt, int deviceID); - void mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID); - void mouseMoved(const SDLUtil::MouseMotionEvent &arg); - void mouseWheelMoved(const SDL_MouseWheelEvent &arg); + SDL_GameController* getControllerOrNull() const; - void keyPressed(const SDL_KeyboardEvent &arg); - void keyReleased(const SDL_KeyboardEvent &arg); + void mousePressed(const SDL_MouseButtonEvent& evt, int deviceID); + void mouseReleased(const SDL_MouseButtonEvent& arg, int deviceID); + void mouseMoved(const SDLUtil::MouseMotionEvent& arg); + void mouseWheelMoved(const SDL_MouseWheelEvent& arg); - void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg); - void controllerRemoved(const SDL_ControllerDeviceEvent &arg); - void controllerButtonPressed(int deviceID, const SDL_ControllerButtonEvent &arg); - void controllerButtonReleased(int deviceID, const SDL_ControllerButtonEvent &arg); - void controllerAxisMoved(int deviceID, const SDL_ControllerAxisEvent &arg); + void keyPressed(const SDL_KeyboardEvent& arg); + void keyReleased(const SDL_KeyboardEvent& arg); + + void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent& arg); + void controllerRemoved(const SDL_ControllerDeviceEvent& arg); + void controllerButtonPressed(int deviceID, const SDL_ControllerButtonEvent& arg); + void controllerButtonReleased(int deviceID, const SDL_ControllerButtonEvent& arg); + void controllerAxisMoved(int deviceID, const SDL_ControllerAxisEvent& arg); SDL_Scancode getKeyBinding(int actionId); @@ -68,7 +71,7 @@ namespace MWInput std::unique_ptr mInputBinder; std::unique_ptr mListener; - std::string mUserFile; + std::filesystem::path mUserFile; bool mDragDrop; }; diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index abb214710..c42c7aa64 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -2,52 +2,48 @@ #include #include -#include + +#include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" -#include "actions.hpp" #include "actionmanager.hpp" +#include "actions.hpp" #include "bindingsmanager.hpp" #include "mousemanager.hpp" -#include "sdlmappings.hpp" namespace MWInput { - ControllerManager::ControllerManager(BindingsManager* bindingsManager, - ActionManager* actionManager, - MouseManager* mouseManager, - const std::string& userControllerBindingsFile, - const std::string& controllerBindingsFile) + ControllerManager::ControllerManager(BindingsManager* bindingsManager, MouseManager* mouseManager, + const std::filesystem::path& userControllerBindingsFile, const std::filesystem::path& controllerBindingsFile) : mBindingsManager(bindingsManager) - , mActionManager(actionManager) , mMouseManager(mouseManager) - , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) + , mJoystickEnabled(Settings::Manager::getBool("enable controller", "Input")) + , mGyroAvailable(false) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) - , mSneakToggleShortcutTimer(0.f) - , mGamepadZoom(0) , mGamepadGuiCursorEnabled(true) , mGuiCursorEnabled(true) , mJoystickLastUsed(false) - , mSneakGamepadShortcut(false) - , mGamepadPreviewMode(false) { if (!controllerBindingsFile.empty()) { - SDL_GameControllerAddMappingsFromFile(controllerBindingsFile.c_str()); + SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(controllerBindingsFile).c_str()); } if (!userControllerBindingsFile.empty()) { - SDL_GameControllerAddMappingsFromFile(userControllerBindingsFile.c_str()); + SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(userControllerBindingsFile).c_str()); } // Open all presently connected sticks @@ -59,7 +55,7 @@ namespace MWInput SDL_ControllerDeviceEvent evt; evt.which = i; static const int fakeDeviceID = 1; - controllerAdded(fakeDeviceID, evt); + ControllerManager::controllerAdded(fakeDeviceID, evt); Log(Debug::Info) << "Detected game controller: " << SDL_GameControllerNameForIndex(i); } else @@ -69,7 +65,7 @@ namespace MWInput } float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input"); - deadZoneRadius = std::min(std::max(deadZoneRadius, 0.0f), 0.5f); + deadZoneRadius = std::clamp(deadZoneRadius, 0.f, 0.5f); mBindingsManager->setJoystickDeadZone(deadZoneRadius); } @@ -82,10 +78,8 @@ namespace MWInput } } - bool ControllerManager::update(float dt) + void ControllerManager::update(float dt) { - mGamepadPreviewMode = mActionManager->isPreviewModeEnabled(); - if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) { float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f; @@ -110,94 +104,28 @@ namespace MWInput } } - // Disable movement in Gui mode - if (MWBase::Environment::get().getWindowManager()->isGuiMode() - || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) - { - mGamepadZoom = 0; - return false; - } - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - bool triedToMove = false; - - // Configure player movement according to controller input. Actual movement will - // be done in the physics system. - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) + if (!MWBase::Environment::get().getWindowManager()->isGuiMode() + && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Running + && MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) { float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight); float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward); - if (xAxis != 0.5) - { - triedToMove = true; - player.setLeftRight((xAxis - 0.5f) * 2); - } - - if (yAxis != 0.5) - { - triedToMove = true; - player.setAutoMove (false); - player.setForwardBackward((0.5f - yAxis) * 2); - } - - if (triedToMove) + if (xAxis != 0.5 || yAxis != 0.5) { mJoystickLastUsed = true; MWBase::Environment::get().getInputManager()->resetIdleTime(); } - - static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); - if (!isToggleSneak) - { - if (mJoystickLastUsed) - { - if (mBindingsManager->actionIsActive(A_Sneak)) - { - if (mSneakToggleShortcutTimer) // New Sneak Button Press - { - if (mSneakToggleShortcutTimer <= 0.3f) - { - mSneakGamepadShortcut = true; - mActionManager->toggleSneaking(); - } - else - mSneakGamepadShortcut = false; - } - - if (!mActionManager->isSneaking()) - mActionManager->toggleSneaking(); - mSneakToggleShortcutTimer = 0.f; - } - else - { - if (!mSneakGamepadShortcut && mActionManager->isSneaking()) - mActionManager->toggleSneaking(); - if (mSneakToggleShortcutTimer <= 0.3f) - mSneakToggleShortcutTimer += dt; - } - } - else - player.setSneak(mBindingsManager->actionIsActive(A_Sneak)); - } } - - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) - { - if (!mBindingsManager->actionIsActive(A_TogglePOV)) - mGamepadZoom = 0; - - if (mGamepadZoom) - MWBase::Environment::get().getWorld()->adjustCameraDistance(-mGamepadZoom); - } - - return triedToMove; } - void ControllerManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg) + void ControllerManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent& arg) { if (!mJoystickEnabled || mBindingsManager->isDetectingBindingState()) return; + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::ControllerPressed, arg.button }); + mJoystickLastUsed = true; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { @@ -212,9 +140,11 @@ namespace MWInput bool mousePressSuccess = mMouseManager->injectMouseButtonPress(SDL_BUTTON_LEFT); if (MyGUI::InputManager::getInstance().getMouseFocusWidget()) { - MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); + MyGUI::Button* b + = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); if (b && b->getEnabled()) - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); + MWBase::Environment::get().getWindowManager()->playSound( + ESM::RefId::stringRefId("Menu Click")); } mBindingsManager->setPlayerControlsEnabled(!mousePressSuccess); @@ -224,15 +154,15 @@ namespace MWInput else mBindingsManager->setPlayerControlsEnabled(true); - //esc, to leave initial movie screen - auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); + // esc, to leave initial movie screen + auto kc = SDLUtil::sdlKeyToMyGUI(SDLK_ESCAPE); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0)); if (!MWBase::Environment::get().getInputManager()->controlsDisabled()) mBindingsManager->controllerButtonPressed(deviceID, arg); } - void ControllerManager::buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg) + void ControllerManager::buttonReleased(int deviceID, const SDL_ControllerButtonEvent& arg) { if (mBindingsManager->isDetectingBindingState()) { @@ -240,6 +170,12 @@ namespace MWInput return; } + if (mJoystickEnabled) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::ControllerReleased, arg.button }); + } + if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) return; @@ -252,7 +188,8 @@ namespace MWInput if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click. { bool mousePressSuccess = mMouseManager->injectMouseButtonRelease(SDL_BUTTON_LEFT); - if (mBindingsManager->isDetectingBindingState()) // If the player just triggered binding, don't let button release bind. + if (mBindingsManager->isDetectingBindingState()) // If the player just triggered binding, don't let + // button release bind. return; mBindingsManager->setPlayerControlsEnabled(!mousePressSuccess); @@ -262,14 +199,14 @@ namespace MWInput else mBindingsManager->setPlayerControlsEnabled(true); - //esc, to leave initial movie screen - auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); + // esc, to leave initial movie screen + auto kc = SDLUtil::sdlKeyToMyGUI(SDLK_ESCAPE); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->controllerButtonReleased(deviceID, arg); } - void ControllerManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) + void ControllerManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent& arg) { if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) return; @@ -279,36 +216,27 @@ namespace MWInput { gamepadToGuiControl(arg); } - else + else if (mBindingsManager->actionIsActive(A_TogglePOV) + && (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT || arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT)) { - if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming - { - if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) - { - mGamepadZoom = arg.value * 0.85f / 1000.f / 12.f; - return; // Do not propagate event. - } - else if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) - { - mGamepadZoom = -arg.value * 0.85f / 1000.f / 12.f; - return; // Do not propagate event. - } - } + // Preview Mode Gamepad Zooming; do not propagate to mBindingsManager + return; } mBindingsManager->controllerAxisMoved(deviceID, arg); } - void ControllerManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) + void ControllerManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent& arg) { mBindingsManager->controllerAdded(deviceID, arg); + enableGyroSensor(); } - void ControllerManager::controllerRemoved(const SDL_ControllerDeviceEvent &arg) + void ControllerManager::controllerRemoved(const SDL_ControllerDeviceEvent& arg) { mBindingsManager->controllerRemoved(arg); } - bool ControllerManager::gamepadToGuiControl(const SDL_ControllerButtonEvent &arg) + bool ControllerManager::gamepadToGuiControl(const SDL_ControllerButtonEvent& arg) { // Presumption of GUI mode will be removed in the future. // MyGUI KeyCodes *may* change. @@ -367,7 +295,7 @@ namespace MWInput return true; } - bool ControllerManager::gamepadToGuiControl(const SDL_ControllerAxisEvent &arg) + bool ControllerManager::gamepadToGuiControl(const SDL_ControllerAxisEvent& arg) { switch (arg.axis) { @@ -393,4 +321,69 @@ namespace MWInput return true; } + + float ControllerManager::getAxisValue(SDL_GameControllerAxis axis) const + { + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + constexpr int AXIS_MAX_ABSOLUTE_VALUE = 32768; + if (cntrl) + return SDL_GameControllerGetAxis(cntrl, axis) / static_cast(AXIS_MAX_ABSOLUTE_VALUE); + else + return 0; + } + + bool ControllerManager::isButtonPressed(SDL_GameControllerButton button) const + { + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (cntrl) + return SDL_GameControllerGetButton(cntrl, button) > 0; + else + return false; + } + + void ControllerManager::enableGyroSensor() + { + mGyroAvailable = false; +#if SDL_VERSION_ATLEAST(2, 0, 14) + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (!cntrl) + return; + if (!SDL_GameControllerHasSensor(cntrl, SDL_SENSOR_GYRO)) + return; + if (SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE) < 0) + return; + mGyroAvailable = true; +#endif + } + + bool ControllerManager::isGyroAvailable() const + { + return mGyroAvailable; + } + + std::array ControllerManager::getGyroValues() const + { + float gyro[3] = { 0.f }; +#if SDL_VERSION_ATLEAST(2, 0, 14) + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (cntrl && mGyroAvailable) + SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); +#endif + return std::array({ gyro[0], gyro[1], gyro[2] }); + } + + void ControllerManager::touchpadMoved(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::TouchMoved, arg }); + } + + void ControllerManager::touchpadPressed(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::TouchPressed, arg }); + } + + void ControllerManager::touchpadReleased(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::TouchReleased, arg }); + } } diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index d8c62d57c..de959fc8c 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -3,62 +3,68 @@ #include -#include #include +#include +#include namespace MWInput { - class ActionManager; class BindingsManager; class MouseManager; class ControllerManager : public SDLUtil::ControllerListener { public: - ControllerManager(BindingsManager* bindingsManager, - ActionManager* actionManager, - MouseManager* mouseManager, - const std::string& userControllerBindingsFile, - const std::string& controllerBindingsFile); + ControllerManager(BindingsManager* bindingsManager, MouseManager* mouseManager, + const std::filesystem::path& userControllerBindingsFile, + const std::filesystem::path& controllerBindingsFile); virtual ~ControllerManager() = default; - bool update(float dt); + void update(float dt); - void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg) override; - void buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg) override; - void axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) override; - void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) override; - void controllerRemoved(const SDL_ControllerDeviceEvent &arg) override; + void buttonPressed(int deviceID, const SDL_ControllerButtonEvent& arg) override; + void buttonReleased(int deviceID, const SDL_ControllerButtonEvent& arg) override; + void axisMoved(int deviceID, const SDL_ControllerAxisEvent& arg) override; + void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent& arg) override; + void controllerRemoved(const SDL_ControllerDeviceEvent& arg) override; + + void touchpadMoved(int deviceId, const SDLUtil::TouchEvent& arg) override; + void touchpadPressed(int deviceId, const SDLUtil::TouchEvent& arg) override; + void touchpadReleased(int deviceId, const SDLUtil::TouchEvent& arg) override; void processChangedSettings(const Settings::CategorySettingVector& changed); void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; } - bool joystickLastUsed() { return mJoystickLastUsed; } + bool joystickLastUsed() const { return mJoystickLastUsed; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } void setGamepadGuiCursorEnabled(bool enabled) { mGamepadGuiCursorEnabled = enabled; } - bool gamepadGuiCursorEnabled() { return mGamepadGuiCursorEnabled; } + bool gamepadGuiCursorEnabled() const { return mGamepadGuiCursorEnabled; } + + float getAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1] + bool isButtonPressed(SDL_GameControllerButton button) const; + + bool isGyroAvailable() const; + std::array getGyroValues() const; private: // Return true if GUI consumes input. - bool gamepadToGuiControl(const SDL_ControllerButtonEvent &arg); - bool gamepadToGuiControl(const SDL_ControllerAxisEvent &arg); + bool gamepadToGuiControl(const SDL_ControllerButtonEvent& arg); + bool gamepadToGuiControl(const SDL_ControllerAxisEvent& arg); + + void enableGyroSensor(); BindingsManager* mBindingsManager; - ActionManager* mActionManager; MouseManager* mMouseManager; bool mJoystickEnabled; + bool mGyroAvailable; float mGamepadCursorSpeed; - float mSneakToggleShortcutTimer; - float mGamepadZoom; bool mGamepadGuiCursorEnabled; bool mGuiCursorEnabled; bool mJoystickLastUsed; - bool mSneakGamepadShortcut; - bool mGamepadPreviewMode; }; } #endif diff --git a/apps/openmw/mwinput/controlswitch.cpp b/apps/openmw/mwinput/controlswitch.cpp index 33c4b75dc..bf9842ae3 100644 --- a/apps/openmw/mwinput/controlswitch.cpp +++ b/apps/openmw/mwinput/controlswitch.cpp @@ -1,8 +1,8 @@ #include "controlswitch.hpp" -#include -#include -#include +#include +#include +#include #include @@ -20,46 +20,34 @@ namespace MWInput void ControlSwitch::clear() { - mSwitches["playercontrols"] = true; - mSwitches["playerfighting"] = true; - mSwitches["playerjumping"] = true; - mSwitches["playerlooking"] = true; - mSwitches["playermagic"] = true; - mSwitches["playerviewswitch"] = true; - mSwitches["vanitymode"] = true; + mSwitches["playercontrols"] = true; + mSwitches["playerfighting"] = true; + mSwitches["playerjumping"] = true; + mSwitches["playerlooking"] = true; + mSwitches["playermagic"] = true; + mSwitches["playerviewswitch"] = true; + mSwitches["vanitymode"] = true; } - bool ControlSwitch::get(const std::string& key) + bool ControlSwitch::get(std::string_view key) { - return mSwitches[key]; + auto it = mSwitches.find(key); + if (it == mSwitches.end()) + throw std::runtime_error("Incorrect ControlSwitch: " + std::string(key)); + return it->second; } - void ControlSwitch::set(const std::string& key, bool value) + void ControlSwitch::set(std::string_view key, bool value) { - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - - /// \note 7 switches at all, if-else is relevant - if (key == "playercontrols" && !value) + if (key == "playerlooking" && !value) { - player.setLeftRight(0); - player.setForwardBackward(0); - player.setAutoMove(false); - player.setUpDown(0); + auto world = MWBase::Environment::get().getWorld(); + world->rotateObject(world->getPlayerPtr(), osg::Vec3f()); } - else if (key == "playerjumping" && !value) - { - /// \fixme maybe crouching at this time - player.setUpDown(0); - } - else if (key == "vanitymode") - { - MWBase::Environment::get().getWorld()->allowVanityMode(value); - } - else if (key == "playerlooking" && !value) - { - MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), 0.f, 0.f, 0.f); - } - mSwitches[key] = value; + auto it = mSwitches.find(key); + if (it == mSwitches.end()) + throw std::runtime_error("Incorrect ControlSwitch: " + std::string(key)); + it->second = value; } void ControlSwitch::write(ESM::ESMWriter& writer, Loading::Listener& /*progress*/) @@ -73,9 +61,9 @@ namespace MWInput controls.mWeaponDrawingDisabled = !mSwitches["playerfighting"]; controls.mSpellDrawingDisabled = !mSwitches["playermagic"]; - writer.startRecord (ESM::REC_INPU); + writer.startRecord(ESM::REC_INPU); controls.save(writer); - writer.endRecord (ESM::REC_INPU); + writer.endRecord(ESM::REC_INPU); } void ControlSwitch::readRecord(ESM::ESMReader& reader, uint32_t type) diff --git a/apps/openmw/mwinput/controlswitch.hpp b/apps/openmw/mwinput/controlswitch.hpp index 38d01066b..b86744a06 100644 --- a/apps/openmw/mwinput/controlswitch.hpp +++ b/apps/openmw/mwinput/controlswitch.hpp @@ -1,8 +1,10 @@ #ifndef MWINPUT_CONTROLSWITCH_H #define MWINPUT_CONTROLSWITCH_H +#include #include #include +#include namespace ESM { @@ -23,8 +25,8 @@ namespace MWInput public: ControlSwitch(); - bool get(const std::string& key); - void set(const std::string& key, bool value); + bool get(std::string_view key); + void set(std::string_view key, bool value); void clear(); void write(ESM::ESMWriter& writer, Loading::Listener& progress); @@ -32,7 +34,7 @@ namespace MWInput int countSavedGameRecords() const; private: - std::map mSwitches; + std::map> mSwitches; }; } #endif diff --git a/apps/openmw/mwinput/gyromanager.cpp b/apps/openmw/mwinput/gyromanager.cpp new file mode 100644 index 000000000..1ffa36872 --- /dev/null +++ b/apps/openmw/mwinput/gyromanager.cpp @@ -0,0 +1,102 @@ +#include "gyromanager.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" + +namespace MWInput +{ + GyroManager::GyroscopeAxis GyroManager::gyroscopeAxisFromString(std::string_view s) + { + if (s == "x") + return GyroscopeAxis::X; + else if (s == "y") + return GyroscopeAxis::Y; + else if (s == "z") + return GyroscopeAxis::Z; + else if (s == "-x") + return GyroscopeAxis::Minus_X; + else if (s == "-y") + return GyroscopeAxis::Minus_Y; + else if (s == "-z") + return GyroscopeAxis::Minus_Z; + + return GyroscopeAxis::Unknown; + } + + GyroManager::GyroManager() + : mEnabled(Settings::Manager::getBool("enable gyroscope", "Input")) + , mGuiCursorEnabled(true) + , mSensitivityH(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) + , mSensitivityV(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) + , mInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) + , mAxisH(gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input"))) + , mAxisV(gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input"))) + { + } + + void GyroManager::update(float dt, std::array values) const + { + if (!mGuiCursorEnabled) + { + float gyroH = getAxisValue(mAxisH, values); + float gyroV = getAxisValue(mAxisV, values); + + if (gyroH == 0.f && gyroV == 0.f) + return; + + float rot[3]; + rot[0] = -gyroV * dt * mSensitivityV; + rot[1] = 0.0f; + rot[2] = -gyroH * dt * mSensitivityH; + + // Only actually turn player when we're not in vanity mode + bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); + if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) + { + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + player.yaw(-rot[2]); + player.pitch(-rot[0]); + } + else if (!playerLooking) + MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); + + MWBase::Environment::get().getInputManager()->resetIdleTime(); + } + } + + void GyroManager::processChangedSettings(const Settings::CategorySettingVector& changed) + { + for (const auto& setting : changed) + { + if (setting.first != "Input") + continue; + + if (setting.second == "enable gyroscope") + mEnabled = Settings::Manager::getBool("enable gyroscope", "Input"); + else if (setting.second == "gyro horizontal sensitivity") + mSensitivityH = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); + else if (setting.second == "gyro vertical sensitivity") + mSensitivityV = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); + else if (setting.second == "gyro input threshold") + mInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); + else if (setting.second == "gyro horizontal axis") + mAxisH = gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input")); + else if (setting.second == "gyro vertical axis") + mAxisV = gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input")); + } + } + + float GyroManager::getAxisValue(GyroscopeAxis axis, std::array values) const + { + if (axis == GyroscopeAxis::Unknown) + return 0; + float value = values[std::abs(axis) - 1]; + if (axis < 0) + value *= -1; + if (std::abs(value) <= mInputThreshold) + value = 0; + return value; + } +} diff --git a/apps/openmw/mwinput/gyromanager.hpp b/apps/openmw/mwinput/gyromanager.hpp new file mode 100644 index 000000000..c9877945a --- /dev/null +++ b/apps/openmw/mwinput/gyromanager.hpp @@ -0,0 +1,47 @@ +#ifndef MWINPUT_GYROMANAGER +#define MWINPUT_GYROMANAGER + +#include + +namespace MWInput +{ + class GyroManager + { + public: + GyroManager(); + + bool isEnabled() const { return mEnabled; } + + void update(float dt, std::array values) const; + + void processChangedSettings(const Settings::CategorySettingVector& changed); + + void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } + + private: + enum GyroscopeAxis + { + Unknown = 0, + X = 1, + Y = 2, + Z = 3, + Minus_X = -1, + Minus_Y = -2, + Minus_Z = -3 + }; + + static GyroscopeAxis gyroscopeAxisFromString(std::string_view s); + + bool mEnabled; + bool mGuiCursorEnabled; + float mSensitivityH; + float mSensitivityV; + float mInputThreshold; + GyroscopeAxis mAxisH; + GyroscopeAxis mAxisV; + + float getAxisValue(GyroscopeAxis axis, std::array values) const; + }; +} + +#endif // !MWINPUT_GYROMANAGER diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 13139e4ef..7359f1083 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -2,13 +2,18 @@ #include +#include +#include #include +<<<<<<< HEAD #include #include +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 -#include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" @@ -17,42 +22,37 @@ #include "bindingsmanager.hpp" #include "controllermanager.hpp" #include "controlswitch.hpp" +#include "gyromanager.hpp" #include "keyboardmanager.hpp" #include "mousemanager.hpp" -#include "sdlmappings.hpp" #include "sensormanager.hpp" namespace MWInput { - InputManager::InputManager( - SDL_Window* window, - osg::ref_ptr viewer, - osg::ref_ptr screenCaptureHandler, - osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, - const std::string& userFile, bool userFileExists, const std::string& userControllerBindingsFile, - const std::string& controllerBindingsFile, bool grab) + InputManager::InputManager(SDL_Window* window, osg::ref_ptr viewer, + osg::ref_ptr screenCaptureHandler, + osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, + const std::filesystem::path& userFile, bool userFileExists, + const std::filesystem::path& userControllerBindingsFile, const std::filesystem::path& controllerBindingsFile, + bool grab) : mControlsDisabled(false) + , mInputWrapper(std::make_unique(window, viewer, grab)) + , mBindingsManager(std::make_unique(userFile, userFileExists)) + , mControlSwitch(std::make_unique()) + , mActionManager(std::make_unique( + mBindingsManager.get(), screenCaptureOperation, viewer, screenCaptureHandler)) + , mKeyboardManager(std::make_unique(mBindingsManager.get())) + , mMouseManager(std::make_unique(mBindingsManager.get(), mInputWrapper.get(), window)) + , mControllerManager(std::make_unique( + mBindingsManager.get(), mMouseManager.get(), userControllerBindingsFile, controllerBindingsFile)) + , mSensorManager(std::make_unique()) + , mGyroManager(std::make_unique()) { - mInputWrapper = new SDLUtil::InputWrapper(window, viewer, grab); mInputWrapper->setWindowEventCallback(MWBase::Environment::get().getWindowManager()); - - mBindingsManager = new BindingsManager(userFile, userFileExists); - - mControlSwitch = new ControlSwitch(); - - mActionManager = new ActionManager(mBindingsManager, screenCaptureOperation, viewer, screenCaptureHandler); - - mKeyboardManager = new KeyboardManager(mBindingsManager); - mInputWrapper->setKeyboardEventCallback(mKeyboardManager); - - mMouseManager = new MouseManager(mBindingsManager, mInputWrapper, window); - mInputWrapper->setMouseEventCallback(mMouseManager); - - mControllerManager = new ControllerManager(mBindingsManager, mActionManager, mMouseManager, userControllerBindingsFile, controllerBindingsFile); - mInputWrapper->setControllerEventCallback(mControllerManager); - - mSensorManager = new SensorManager(); - mInputWrapper->setSensorEventCallback(mSensorManager); + mInputWrapper->setKeyboardEventCallback(mKeyboardManager.get()); + mInputWrapper->setMouseEventCallback(mMouseManager.get()); + mInputWrapper->setControllerEventCallback(mControllerManager.get()); + mInputWrapper->setSensorEventCallback(mSensorManager.get()); } void InputManager::clear() @@ -61,25 +61,7 @@ namespace MWInput mControlSwitch->clear(); } - InputManager::~InputManager() - { - delete mActionManager; - delete mControllerManager; - delete mKeyboardManager; - delete mMouseManager; - delete mSensorManager; - - delete mControlSwitch; - - delete mBindingsManager; - - delete mInputWrapper; - } - - void InputManager::setAttemptJump(bool jumping) - { - mActionManager->setAttemptJump(jumping); - } + InputManager::~InputManager() {} void InputManager::update(float dt, bool disableControls, bool disableEvents) { @@ -98,12 +80,21 @@ namespace MWInput mMouseManager->updateCursorMode(); - bool controllerMove = mControllerManager->update(dt); + mControllerManager->update(dt); mMouseManager->update(dt); mSensorManager->update(dt); - mActionManager->update(dt, controllerMove); + mActionManager->update(dt); - MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(dt); + if (mGyroManager->isEnabled()) + { + bool controllerAvailable = mControllerManager->isGyroAvailable(); + bool sensorAvailable = mSensorManager->isGyroAvailable(); + if (controllerAvailable || sensorAvailable) + { + mGyroManager->update( + dt, controllerAvailable ? mControllerManager->getGyroValues() : mSensorManager->getGyroValues()); + } + } } void InputManager::setDragDrop(bool dragDrop) @@ -120,12 +111,13 @@ namespace MWInput { mControllerManager->setGuiCursorEnabled(guiMode); mMouseManager->setGuiCursorEnabled(guiMode); - mSensorManager->setGuiCursorEnabled(guiMode); + mGyroManager->setGuiCursorEnabled(guiMode); mMouseManager->setMouseLookEnabled(!guiMode); if (guiMode) MWBase::Environment::get().getWindowManager()->showCrosshair(false); - bool isCursorVisible = guiMode && (!mControllerManager->joystickLastUsed() || mControllerManager->gamepadGuiCursorEnabled()); + bool isCursorVisible + = guiMode && (!mControllerManager->joystickLastUsed() || mControllerManager->gamepadGuiCursorEnabled()); MWBase::Environment::get().getWindowManager()->setCursorVisible(isCursorVisible); // if not in gui mode, the camera decides whether to show crosshair or not. } @@ -134,14 +126,15 @@ namespace MWInput { mMouseManager->processChangedSettings(changed); mSensorManager->processChangedSettings(changed); + mGyroManager->processChangedSettings(changed); } - bool InputManager::getControlSwitch(const std::string& sw) + bool InputManager::getControlSwitch(std::string_view sw) { return mControlSwitch->get(sw); } - void InputManager::toggleControlSwitch(const std::string& sw, bool value) + void InputManager::toggleControlSwitch(std::string_view sw, bool value) { mControlSwitch->set(sw, value); } @@ -151,27 +144,62 @@ namespace MWInput mActionManager->resetIdleTime(); } - std::string InputManager::getActionDescription(int action) + bool InputManager::isIdle() const + { + return mActionManager->getIdleTime() > 0.5; + } + + std::string_view InputManager::getActionDescription(int action) const { return mBindingsManager->getActionDescription(action); } - std::string InputManager::getActionKeyBindingName(int action) + std::string InputManager::getActionKeyBindingName(int action) const { return mBindingsManager->getActionKeyBindingName(action); } - std::string InputManager::getActionControllerBindingName(int action) + std::string InputManager::getActionControllerBindingName(int action) const { return mBindingsManager->getActionControllerBindingName(action); } - std::vector InputManager::getActionKeySorting() + bool InputManager::actionIsActive(int action) const + { + return mBindingsManager->actionIsActive(action); + } + + float InputManager::getActionValue(int action) const + { + return mBindingsManager->getActionValue(action); + } + + bool InputManager::isControllerButtonPressed(SDL_GameControllerButton button) const + { + return mControllerManager->isButtonPressed(button); + } + + float InputManager::getControllerAxisValue(SDL_GameControllerAxis axis) const + { + return mControllerManager->getAxisValue(axis); + } + + int InputManager::getMouseMoveX() const + { + return mMouseManager->getMouseMoveX(); + } + + int InputManager::getMouseMoveY() const + { + return mMouseManager->getMouseMoveY(); + } + + const std::initializer_list& InputManager::getActionKeySorting() { return mBindingsManager->getActionKeySorting(); } - std::vector InputManager::getActionControllerSorting() + const std::initializer_list& InputManager::getActionControllerSorting() { return mBindingsManager->getActionControllerSorting(); } diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index f930836d1..c5de57996 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -1,16 +1,17 @@ #ifndef MWINPUT_MWINPUTMANAGERIMP_H #define MWINPUT_MWINPUTMANAGERIMP_H +#include + #include #include -#include #include +#include +#include #include "../mwbase/inputmanager.hpp" -#include "../mwgui/mode.hpp" - #include "actions.hpp" namespace MWWorld @@ -39,28 +40,27 @@ namespace MWInput class KeyboardManager; class MouseManager; class SensorManager; + class GyroManager; /** - * @brief Class that provides a high-level API for game input - */ - class InputManager : public MWBase::InputManager + * @brief Class that provides a high-level API for game input + */ + class InputManager final : public MWBase::InputManager { public: - InputManager( - SDL_Window* window, - osg::ref_ptr viewer, + InputManager(SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, - osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, - const std::string& userFile, bool userFileExists, - const std::string& userControllerBindingsFile, - const std::string& controllerBindingsFile, bool grab); + osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, + const std::filesystem::path& userFile, bool userFileExists, + const std::filesystem::path& userControllerBindingsFile, + const std::filesystem::path& controllerBindingsFile, bool grab); - virtual ~InputManager(); + ~InputManager() final; /// Clear all savegame-specific data void clear() override; - void update(float dt, bool disableControls=false, bool disableEvents=false) override; + void update(float dt, bool disableControls, bool disableEvents = false) override; void changeInputMode(bool guiMode) override; @@ -68,18 +68,25 @@ namespace MWInput void setDragDrop(bool dragDrop) override; void setGamepadGuiCursorEnabled(bool enabled) override; - void setAttemptJump(bool jumping) override; - void toggleControlSwitch (const std::string& sw, bool value) override; - bool getControlSwitch (const std::string& sw) override; + void toggleControlSwitch(std::string_view sw, bool value) override; + bool getControlSwitch(std::string_view sw) override; + + std::string_view getActionDescription(int action) const override; + std::string getActionKeyBindingName(int action) const override; + std::string getActionControllerBindingName(int action) const override; + bool actionIsActive(int action) const override; + + float getActionValue(int action) const override; + bool isControllerButtonPressed(SDL_GameControllerButton button) const override; + float getControllerAxisValue(SDL_GameControllerAxis axis) const override; + int getMouseMoveX() const override; + int getMouseMoveY() const override; - std::string getActionDescription (int action) override; - std::string getActionKeyBindingName (int action) override; - std::string getActionControllerBindingName (int action) override; int getNumActions() override { return A_Last; } - std::vector getActionKeySorting() override; - std::vector getActionControllerSorting() override; - void enableDetectingBindingMode (int action, bool keyboard) override; + const std::initializer_list& getActionKeySorting() override; + const std::initializer_list& getActionControllerSorting() override; + void enableDetectingBindingMode(int action, bool keyboard) override; void resetToDefaultKeyBindings() override; void resetToDefaultControllerBindings() override; @@ -91,6 +98,7 @@ namespace MWInput void readRecord(ESM::ESMReader& reader, uint32_t type) override; void resetIdleTime() override; + bool isIdle() const override; void executeAction(int action) override; @@ -107,18 +115,17 @@ namespace MWInput void loadKeyDefaults(bool force = false); void loadControllerDefaults(bool force = false); - SDLUtil::InputWrapper* mInputWrapper; - bool mControlsDisabled; - ControlSwitch* mControlSwitch; - - ActionManager* mActionManager; - BindingsManager* mBindingsManager; - ControllerManager* mControllerManager; - KeyboardManager* mKeyboardManager; - MouseManager* mMouseManager; - SensorManager* mSensorManager; + std::unique_ptr mInputWrapper; + std::unique_ptr mBindingsManager; + std::unique_ptr mControlSwitch; + std::unique_ptr mActionManager; + std::unique_ptr mKeyboardManager; + std::unique_ptr mMouseManager; + std::unique_ptr mControllerManager; + std::unique_ptr mSensorManager; + std::unique_ptr mGyroManager; }; } #endif diff --git a/apps/openmw/mwinput/keyboardmanager.cpp b/apps/openmw/mwinput/keyboardmanager.cpp index 854085846..6b1daf450 100644 --- a/apps/openmw/mwinput/keyboardmanager.cpp +++ b/apps/openmw/mwinput/keyboardmanager.cpp @@ -4,15 +4,15 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/player.hpp" - #include "actions.hpp" #include "bindingsmanager.hpp" -#include "sdlmappings.hpp" namespace MWInput { @@ -21,7 +21,7 @@ namespace MWInput { } - void KeyboardManager::textInput(const SDL_TextInputEvent &arg) + void KeyboardManager::textInput(const SDL_TextInputEvent& arg) { MyGUI::UString ustring(&arg.text[0]); MyGUI::UString::utf32string utf32string = ustring.asUTF32(); @@ -29,21 +29,21 @@ namespace MWInput MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it); } - void KeyboardManager::keyPressed(const SDL_KeyboardEvent &arg) + void KeyboardManager::keyPressed(const SDL_KeyboardEvent& arg) { // HACK: to make default keybinding for the console work without printing an extra "^" upon closing // This assumes that SDL_TextInput events always come *after* the key event // (which is somewhat reasonable, and hopefully true for all SDL platforms) - auto kc = sdlKeyToMyGUI(arg.keysym.sym); + auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym); if (mBindingsManager->getKeyBinding(A_Console) == arg.keysym.scancode - && MWBase::Environment::get().getWindowManager()->isConsoleMode()) + && MWBase::Environment::get().getWindowManager()->isConsoleMode()) SDL_StopTextInput(); - bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable - (!(SDLK_SCANCODE_MASK & arg.keysym.sym) && - (std::isprint(arg.keysym.sym) || - // Don't trust isprint for symbols outside the extended ASCII range - (kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff))); + bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable + (!(SDLK_SCANCODE_MASK & arg.keysym.sym) && + // Don't trust isprint for symbols outside the extended ASCII range + ((kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff) + || (arg.keysym.sym >= 0 && arg.keysym.sym <= 255 && std::isprint(arg.keysym.sym)))); if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState()) { if (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat)) @@ -58,16 +58,24 @@ namespace MWInput if (!input->controlsDisabled() && !consumed) mBindingsManager->keyPressed(arg); + if (!consumed) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::KeyPressed, arg.keysym }); + } + input->setJoystickLastUsed(false); } - void KeyboardManager::keyReleased(const SDL_KeyboardEvent &arg) + void KeyboardManager::keyReleased(const SDL_KeyboardEvent& arg) { MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); - auto kc = sdlKeyToMyGUI(arg.keysym.sym); + auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym); if (!mBindingsManager->isDetectingBindingState()) mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->keyReleased(arg); + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::KeyReleased, arg.keysym }); } } diff --git a/apps/openmw/mwinput/keyboardmanager.hpp b/apps/openmw/mwinput/keyboardmanager.hpp index ca58461a2..f7b1bbee2 100644 --- a/apps/openmw/mwinput/keyboardmanager.hpp +++ b/apps/openmw/mwinput/keyboardmanager.hpp @@ -14,9 +14,9 @@ namespace MWInput virtual ~KeyboardManager() = default; - void textInput(const SDL_TextInputEvent &arg) override; - void keyPressed(const SDL_KeyboardEvent &arg) override; - void keyReleased(const SDL_KeyboardEvent &arg) override; + void textInput(const SDL_TextInputEvent& arg) override; + void keyPressed(const SDL_KeyboardEvent& arg) override; + void keyReleased(const SDL_KeyboardEvent& arg) override; private: BindingsManager* mBindingsManager; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index cf151dfac..8caed79e2 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -3,10 +3,9 @@ #include #include #include -#include -#include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" @@ -17,11 +16,11 @@ #include "actions.hpp" #include "bindingsmanager.hpp" -#include "sdlmappings.hpp" namespace MWInput { - MouseManager::MouseManager(BindingsManager* bindingsManager, SDLUtil::InputWrapper* inputWrapper, SDL_Window* window) + MouseManager::MouseManager( + BindingsManager* bindingsManager, SDLUtil::InputWrapper* inputWrapper, SDL_Window* window) : mInvertX(Settings::Manager::getBool("invert x axis", "Input")) , mInvertY(Settings::Manager::getBool("invert y axis", "Input")) , mGrabCursor(Settings::Manager::getBool("grab cursor", "Input")) @@ -34,8 +33,10 @@ namespace MWInput , mMouseWheel(0) , mMouseLookEnabled(false) , mGuiCursorEnabled(true) + , mMouseMoveX(0) + , mMouseMoveY(0) { - int w,h; + int w, h; SDL_GetWindowSize(window, &w, &h); float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); @@ -61,7 +62,7 @@ namespace MWInput } } - void MouseManager::mouseMoved(const SDLUtil::MouseMotionEvent &arg) + void MouseManager::mouseMoved(const SDLUtil::MouseMotionEvent& arg) { mBindingsManager->mouseMoved(arg); @@ -81,9 +82,12 @@ namespace MWInput mMouseWheel = static_cast(arg.z); - MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), mMouseWheel); - // FIXME: inject twice to force updating focused widget states (tooltips) resulting from changing the viewport by scroll wheel - MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), mMouseWheel); + MyGUI::InputManager::getInstance().injectMouseMove( + static_cast(mGuiCursorX), static_cast(mGuiCursorY), mMouseWheel); + // FIXME: inject twice to force updating focused widget states (tooltips) resulting from changing the + // viewport by scroll wheel + MyGUI::InputManager::getInstance().injectMouseMove( + static_cast(mGuiCursorX), static_cast(mGuiCursorY), mMouseWheel); MWBase::Environment::get().getWindowManager()->setCursorActive(true); } @@ -112,7 +116,7 @@ namespace MWInput } } - void MouseManager::mouseReleased(const SDL_MouseButtonEvent &arg, Uint8 id) + void MouseManager::mouseReleased(const SDL_MouseButtonEvent& arg, Uint8 id) { MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); @@ -123,7 +127,9 @@ namespace MWInput else { bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; + guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), + static_cast(mGuiCursorY), SDLUtil::sdlMouseButtonToMyGui(id)) + && guiMode; if (mBindingsManager->isDetectingBindingState()) return; // don't allow same mouseup to bind as initiated bind @@ -133,7 +139,7 @@ namespace MWInput } } - void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent &arg) + void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent& arg) { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) @@ -142,7 +148,7 @@ namespace MWInput input->setJoystickLastUsed(false); } - void MouseManager::mousePressed(const SDL_MouseButtonEvent &arg, Uint8 id) + void MouseManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 id) { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); input->setJoystickLastUsed(false); @@ -151,13 +157,16 @@ namespace MWInput if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events { guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; - if (MyGUI::InputManager::getInstance().getMouseFocusWidget () != nullptr) + guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), + static_cast(mGuiCursorY), SDLUtil::sdlMouseButtonToMyGui(id)) + && guiMode; + if (MyGUI::InputManager::getInstance().getMouseFocusWidget() != nullptr) { - MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); + MyGUI::Button* b + = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); if (b && b->getEnabled() && id == SDL_BUTTON_LEFT) { - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click")); } } MWBase::Environment::get().getWindowManager()->setCursorActive(true); @@ -167,14 +176,15 @@ namespace MWInput // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading - if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings && !input->controlsDisabled()) + if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings + && !input->controlsDisabled()) mBindingsManager->mousePressed(arg, id); } void MouseManager::updateCursorMode() { bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) - && !MWBase::Environment::get().getWindowManager()->isConsoleMode(); + && !MWBase::Environment::get().getWindowManager()->isConsoleMode(); bool wasRelative = mInputWrapper->getMouseRelative(); bool isRelative = !MWBase::Environment::get().getWindowManager()->isGuiMode(); @@ -183,11 +193,11 @@ namespace MWInput // stop using raw mouse motions and switch to system cursor movements mInputWrapper->setMouseRelative(isRelative); - //we let the mouse escape in the main menu + // we let the mouse escape in the main menu mInputWrapper->setGrabPointer(grab && (mGrabCursor || isRelative)); - //we switched to non-relative mode, move our cursor to where the in-game - //cursor is + // we switched to non-relative mode, move our cursor to where the in-game + // cursor is if (!isRelative && wasRelative != isRelative) { warpMouse(); @@ -196,6 +206,8 @@ namespace MWInput void MouseManager::update(float dt) { + SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY); + if (!mMouseLookEnabled) return; @@ -225,12 +237,14 @@ namespace MWInput bool MouseManager::injectMouseButtonPress(Uint8 button) { - return MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(button)); + return MyGUI::InputManager::getInstance().injectMousePress( + static_cast(mGuiCursorX), static_cast(mGuiCursorY), SDLUtil::sdlMouseButtonToMyGui(button)); } bool MouseManager::injectMouseButtonRelease(Uint8 button) { - return MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(button)); + return MyGUI::InputManager::getInstance().injectMouseRelease( + static_cast(mGuiCursorX), static_cast(mGuiCursorY), SDLUtil::sdlMouseButtonToMyGui(button)); } void MouseManager::injectMouseMove(float xMove, float yMove, float mouseWheelMove) @@ -240,15 +254,16 @@ namespace MWInput mMouseWheel += mouseWheelMove; const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - mGuiCursorX = std::max(0.f, std::min(mGuiCursorX, float(viewSize.width - 1))); - mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(viewSize.height - 1))); + mGuiCursorX = std::clamp(mGuiCursorX, 0.f, viewSize.width - 1); + mGuiCursorY = std::clamp(mGuiCursorY, 0.f, viewSize.height - 1); - MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), static_cast(mMouseWheel)); + MyGUI::InputManager::getInstance().injectMouseMove( + static_cast(mGuiCursorX), static_cast(mGuiCursorY), static_cast(mMouseWheel)); } void MouseManager::warpMouse() { float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - mInputWrapper->warpMouse(static_cast(mGuiCursorX*uiScale), static_cast(mGuiCursorY*uiScale)); + mInputWrapper->warpMouse(static_cast(mGuiCursorX * uiScale), static_cast(mGuiCursorY * uiScale)); } } diff --git a/apps/openmw/mwinput/mousemanager.hpp b/apps/openmw/mwinput/mousemanager.hpp index 000e7cd0b..4e8996a99 100644 --- a/apps/openmw/mwinput/mousemanager.hpp +++ b/apps/openmw/mwinput/mousemanager.hpp @@ -1,8 +1,8 @@ #ifndef MWINPUT_MWMOUSEMANAGER_H #define MWINPUT_MWMOUSEMANAGER_H -#include #include +#include namespace SDLUtil { @@ -23,10 +23,10 @@ namespace MWInput void updateCursorMode(); void update(float dt); - void mouseMoved(const SDLUtil::MouseMotionEvent &arg) override; - void mousePressed(const SDL_MouseButtonEvent &arg, Uint8 id) override; - void mouseReleased(const SDL_MouseButtonEvent &arg, Uint8 id) override; - void mouseWheelMoved(const SDL_MouseWheelEvent &arg) override; + void mouseMoved(const SDLUtil::MouseMotionEvent& arg) override; + void mousePressed(const SDL_MouseButtonEvent& arg, Uint8 id) override; + void mouseReleased(const SDL_MouseButtonEvent& arg, Uint8 id) override; + void mouseWheelMoved(const SDL_MouseWheelEvent& arg) override; void processChangedSettings(const Settings::CategorySettingVector& changed); @@ -38,6 +38,9 @@ namespace MWInput void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } + int getMouseMoveX() const { return mMouseMoveX; } + int getMouseMoveY() const { return mMouseMoveY; } + private: bool mInvertX; bool mInvertY; @@ -53,6 +56,9 @@ namespace MWInput int mMouseWheel; bool mMouseLookEnabled; bool mGuiCursorEnabled; + + int mMouseMoveX; + int mMouseMoveY; }; } #endif diff --git a/apps/openmw/mwinput/sdlmappings.cpp b/apps/openmw/mwinput/sdlmappings.cpp deleted file mode 100644 index 0c3f5c5d8..000000000 --- a/apps/openmw/mwinput/sdlmappings.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "sdlmappings.hpp" - -#include - -#include - -#include -#include - -namespace MWInput -{ - std::string sdlControllerButtonToString(int button) - { - switch(button) - { - case SDL_CONTROLLER_BUTTON_A: - return "A Button"; - case SDL_CONTROLLER_BUTTON_B: - return "B Button"; - case SDL_CONTROLLER_BUTTON_BACK: - return "Back Button"; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - return "DPad Down"; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: - return "DPad Left"; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - return "DPad Right"; - case SDL_CONTROLLER_BUTTON_DPAD_UP: - return "DPad Up"; - case SDL_CONTROLLER_BUTTON_GUIDE: - return "Guide Button"; - case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - return "Left Shoulder"; - case SDL_CONTROLLER_BUTTON_LEFTSTICK: - return "Left Stick Button"; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - return "Right Shoulder"; - case SDL_CONTROLLER_BUTTON_RIGHTSTICK: - return "Right Stick Button"; - case SDL_CONTROLLER_BUTTON_START: - return "Start Button"; - case SDL_CONTROLLER_BUTTON_X: - return "X Button"; - case SDL_CONTROLLER_BUTTON_Y: - return "Y Button"; - default: - return "Button " + std::to_string(button); - } - } - - std::string sdlControllerAxisToString(int axis) - { - switch(axis) - { - case SDL_CONTROLLER_AXIS_LEFTX: - return "Left Stick X"; - case SDL_CONTROLLER_AXIS_LEFTY: - return "Left Stick Y"; - case SDL_CONTROLLER_AXIS_RIGHTX: - return "Right Stick X"; - case SDL_CONTROLLER_AXIS_RIGHTY: - return "Right Stick Y"; - case SDL_CONTROLLER_AXIS_TRIGGERLEFT: - return "Left Trigger"; - case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - return "Right Trigger"; - default: - return "Axis " + std::to_string(axis); - } - } - - MyGUI::MouseButton sdlButtonToMyGUI(Uint8 button) - { - //The right button is the second button, according to MyGUI - if(button == SDL_BUTTON_RIGHT) - button = SDL_BUTTON_MIDDLE; - else if(button == SDL_BUTTON_MIDDLE) - button = SDL_BUTTON_RIGHT; - - //MyGUI's buttons are 0 indexed - return MyGUI::MouseButton::Enum(button - 1); - } - - void initKeyMap(std::map& keyMap) - { - keyMap[SDLK_UNKNOWN] = MyGUI::KeyCode::None; - keyMap[SDLK_ESCAPE] = MyGUI::KeyCode::Escape; - keyMap[SDLK_1] = MyGUI::KeyCode::One; - keyMap[SDLK_2] = MyGUI::KeyCode::Two; - keyMap[SDLK_3] = MyGUI::KeyCode::Three; - keyMap[SDLK_4] = MyGUI::KeyCode::Four; - keyMap[SDLK_5] = MyGUI::KeyCode::Five; - keyMap[SDLK_6] = MyGUI::KeyCode::Six; - keyMap[SDLK_7] = MyGUI::KeyCode::Seven; - keyMap[SDLK_8] = MyGUI::KeyCode::Eight; - keyMap[SDLK_9] = MyGUI::KeyCode::Nine; - keyMap[SDLK_0] = MyGUI::KeyCode::Zero; - keyMap[SDLK_MINUS] = MyGUI::KeyCode::Minus; - keyMap[SDLK_EQUALS] = MyGUI::KeyCode::Equals; - keyMap[SDLK_BACKSPACE] = MyGUI::KeyCode::Backspace; - keyMap[SDLK_TAB] = MyGUI::KeyCode::Tab; - keyMap[SDLK_q] = MyGUI::KeyCode::Q; - keyMap[SDLK_w] = MyGUI::KeyCode::W; - keyMap[SDLK_e] = MyGUI::KeyCode::E; - keyMap[SDLK_r] = MyGUI::KeyCode::R; - keyMap[SDLK_t] = MyGUI::KeyCode::T; - keyMap[SDLK_y] = MyGUI::KeyCode::Y; - keyMap[SDLK_u] = MyGUI::KeyCode::U; - keyMap[SDLK_i] = MyGUI::KeyCode::I; - keyMap[SDLK_o] = MyGUI::KeyCode::O; - keyMap[SDLK_p] = MyGUI::KeyCode::P; - keyMap[SDLK_RETURN] = MyGUI::KeyCode::Return; - keyMap[SDLK_a] = MyGUI::KeyCode::A; - keyMap[SDLK_s] = MyGUI::KeyCode::S; - keyMap[SDLK_d] = MyGUI::KeyCode::D; - keyMap[SDLK_f] = MyGUI::KeyCode::F; - keyMap[SDLK_g] = MyGUI::KeyCode::G; - keyMap[SDLK_h] = MyGUI::KeyCode::H; - keyMap[SDLK_j] = MyGUI::KeyCode::J; - keyMap[SDLK_k] = MyGUI::KeyCode::K; - keyMap[SDLK_l] = MyGUI::KeyCode::L; - keyMap[SDLK_SEMICOLON] = MyGUI::KeyCode::Semicolon; - keyMap[SDLK_QUOTE] = MyGUI::KeyCode::Apostrophe; - keyMap[SDLK_BACKQUOTE] = MyGUI::KeyCode::Grave; - keyMap[SDLK_LSHIFT] = MyGUI::KeyCode::LeftShift; - keyMap[SDLK_BACKSLASH] = MyGUI::KeyCode::Backslash; - keyMap[SDLK_z] = MyGUI::KeyCode::Z; - keyMap[SDLK_x] = MyGUI::KeyCode::X; - keyMap[SDLK_c] = MyGUI::KeyCode::C; - keyMap[SDLK_v] = MyGUI::KeyCode::V; - keyMap[SDLK_b] = MyGUI::KeyCode::B; - keyMap[SDLK_n] = MyGUI::KeyCode::N; - keyMap[SDLK_m] = MyGUI::KeyCode::M; - keyMap[SDLK_COMMA] = MyGUI::KeyCode::Comma; - keyMap[SDLK_PERIOD] = MyGUI::KeyCode::Period; - keyMap[SDLK_SLASH] = MyGUI::KeyCode::Slash; - keyMap[SDLK_RSHIFT] = MyGUI::KeyCode::RightShift; - keyMap[SDLK_KP_MULTIPLY] = MyGUI::KeyCode::Multiply; - keyMap[SDLK_LALT] = MyGUI::KeyCode::LeftAlt; - keyMap[SDLK_SPACE] = MyGUI::KeyCode::Space; - keyMap[SDLK_CAPSLOCK] = MyGUI::KeyCode::Capital; - keyMap[SDLK_F1] = MyGUI::KeyCode::F1; - keyMap[SDLK_F2] = MyGUI::KeyCode::F2; - keyMap[SDLK_F3] = MyGUI::KeyCode::F3; - keyMap[SDLK_F4] = MyGUI::KeyCode::F4; - keyMap[SDLK_F5] = MyGUI::KeyCode::F5; - keyMap[SDLK_F6] = MyGUI::KeyCode::F6; - keyMap[SDLK_F7] = MyGUI::KeyCode::F7; - keyMap[SDLK_F8] = MyGUI::KeyCode::F8; - keyMap[SDLK_F9] = MyGUI::KeyCode::F9; - keyMap[SDLK_F10] = MyGUI::KeyCode::F10; - keyMap[SDLK_NUMLOCKCLEAR] = MyGUI::KeyCode::NumLock; - keyMap[SDLK_SCROLLLOCK] = MyGUI::KeyCode::ScrollLock; - keyMap[SDLK_KP_7] = MyGUI::KeyCode::Numpad7; - keyMap[SDLK_KP_8] = MyGUI::KeyCode::Numpad8; - keyMap[SDLK_KP_9] = MyGUI::KeyCode::Numpad9; - keyMap[SDLK_KP_MINUS] = MyGUI::KeyCode::Subtract; - keyMap[SDLK_KP_4] = MyGUI::KeyCode::Numpad4; - keyMap[SDLK_KP_5] = MyGUI::KeyCode::Numpad5; - keyMap[SDLK_KP_6] = MyGUI::KeyCode::Numpad6; - keyMap[SDLK_KP_PLUS] = MyGUI::KeyCode::Add; - keyMap[SDLK_KP_1] = MyGUI::KeyCode::Numpad1; - keyMap[SDLK_KP_2] = MyGUI::KeyCode::Numpad2; - keyMap[SDLK_KP_3] = MyGUI::KeyCode::Numpad3; - keyMap[SDLK_KP_0] = MyGUI::KeyCode::Numpad0; - keyMap[SDLK_KP_PERIOD] = MyGUI::KeyCode::Decimal; - keyMap[SDLK_F11] = MyGUI::KeyCode::F11; - keyMap[SDLK_F12] = MyGUI::KeyCode::F12; - keyMap[SDLK_F13] = MyGUI::KeyCode::F13; - keyMap[SDLK_F14] = MyGUI::KeyCode::F14; - keyMap[SDLK_F15] = MyGUI::KeyCode::F15; - keyMap[SDLK_KP_EQUALS] = MyGUI::KeyCode::NumpadEquals; - keyMap[SDLK_COLON] = MyGUI::KeyCode::Colon; - keyMap[SDLK_KP_ENTER] = MyGUI::KeyCode::NumpadEnter; - keyMap[SDLK_KP_DIVIDE] = MyGUI::KeyCode::Divide; - keyMap[SDLK_SYSREQ] = MyGUI::KeyCode::SysRq; - keyMap[SDLK_RALT] = MyGUI::KeyCode::RightAlt; - keyMap[SDLK_HOME] = MyGUI::KeyCode::Home; - keyMap[SDLK_UP] = MyGUI::KeyCode::ArrowUp; - keyMap[SDLK_PAGEUP] = MyGUI::KeyCode::PageUp; - keyMap[SDLK_LEFT] = MyGUI::KeyCode::ArrowLeft; - keyMap[SDLK_RIGHT] = MyGUI::KeyCode::ArrowRight; - keyMap[SDLK_END] = MyGUI::KeyCode::End; - keyMap[SDLK_DOWN] = MyGUI::KeyCode::ArrowDown; - keyMap[SDLK_PAGEDOWN] = MyGUI::KeyCode::PageDown; - keyMap[SDLK_INSERT] = MyGUI::KeyCode::Insert; - keyMap[SDLK_DELETE] = MyGUI::KeyCode::Delete; - keyMap[SDLK_APPLICATION] = MyGUI::KeyCode::AppMenu; - -//The function of the Ctrl and Meta keys are switched on macOS compared to other platforms. -//For instance] = Cmd+C versus Ctrl+C to copy from the system clipboard -#if defined(__APPLE__) - keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftControl; - keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightControl; - keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftWindows; - keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightWindows; -#else - keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftWindows; - keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightWindows; - keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftControl; - keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightControl; -#endif - } - - MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code) - { - static std::map keyMap; - if (keyMap.empty()) - initKeyMap(keyMap); - - MyGUI::KeyCode kc = MyGUI::KeyCode::None; - auto foundKey = keyMap.find(code); - if (foundKey != keyMap.end()) - kc = foundKey->second; - - return kc; - } -} diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index 3e8e70aef..33b493f4d 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -11,18 +11,10 @@ namespace MWInput { SensorManager::SensorManager() - : mInvertX(Settings::Manager::getBool("invert x axis", "Input")) - , mInvertY(Settings::Manager::getBool("invert y axis", "Input")) - , mGyroXSpeed(0.f) - , mGyroYSpeed(0.f) + : mRotation() + , mGyroValues() , mGyroUpdateTimer(0.f) - , mGyroHSensitivity(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) - , mGyroVSensitivity(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) - , mGyroHAxis(GyroscopeAxis::Minus_X) - , mGyroVAxis(GyroscopeAxis::Y) - , mGyroInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) , mGyroscope(nullptr) - , mGuiCursorEnabled(true) { init(); } @@ -42,24 +34,6 @@ namespace MWInput } } - SensorManager::GyroscopeAxis SensorManager::mapGyroscopeAxis(const std::string& axis) - { - if (axis == "x") - return GyroscopeAxis::X; - else if (axis == "y") - return GyroscopeAxis::Y; - else if (axis == "z") - return GyroscopeAxis::Z; - else if (axis == "-x") - return GyroscopeAxis::Minus_X; - else if (axis == "-y") - return GyroscopeAxis::Minus_Y; - else if (axis == "-z") - return GyroscopeAxis::Minus_Z; - - return GyroscopeAxis::Unknown; - } - void SensorManager::correctGyroscopeAxes() { if (!Settings::Manager::getBool("enable gyroscope", "Input")) @@ -68,40 +42,37 @@ namespace MWInput // Treat setting from config as axes for landscape mode. // If the device does not support orientation change, do nothing. // Note: in is unclear how to correct axes for devices with non-standart Z axis direction. - mGyroHAxis = mapGyroscopeAxis(Settings::Manager::getString("gyro horizontal axis", "Input")); - mGyroVAxis = mapGyroscopeAxis(Settings::Manager::getString("gyro vertical axis", "Input")); - SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); + mRotation = osg::Matrixf::identity(); + + float angle = 0; + + SDL_DisplayOrientation currentOrientation + = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: - return; + break; case SDL_ORIENTATION_LANDSCAPE: break; case SDL_ORIENTATION_LANDSCAPE_FLIPPED: { - mGyroHAxis = GyroscopeAxis(-mGyroHAxis); - mGyroVAxis = GyroscopeAxis(-mGyroVAxis); - + angle = osg::PIf; break; } case SDL_ORIENTATION_PORTRAIT: { - GyroscopeAxis oldVAxis = mGyroVAxis; - mGyroVAxis = mGyroHAxis; - mGyroHAxis = GyroscopeAxis(-oldVAxis); - + angle = -0.5 * osg::PIf; break; } case SDL_ORIENTATION_PORTRAIT_FLIPPED: { - GyroscopeAxis oldVAxis = mGyroVAxis; - mGyroVAxis = GyroscopeAxis(-mGyroHAxis); - mGyroHAxis = oldVAxis; - + angle = 0.5 * osg::PIf; break; } } + + mRotation.makeRotate(angle, osg::Vec3f(0, 0, 1)); } void SensorManager::updateSensors() @@ -114,19 +85,20 @@ namespace MWInput if (SDL_SensorGetDeviceType(i) == SDL_SENSOR_GYRO) { // It is unclear how to handle several enabled gyroscopes, so use the first one. - // Note: Android registers some gyroscope as two separate sensors, for non-wake-up mode and for wake-up mode. + // Note: Android registers some gyroscope as two separate sensors, for non-wake-up mode and for + // wake-up mode. if (mGyroscope != nullptr) { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; - mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } // FIXME: SDL2 does not provide a way to configure a sensor update frequency so far. - SDL_Sensor *sensor = SDL_SensorOpen(i); + SDL_Sensor* sensor = SDL_SensorOpen(i); if (sensor == nullptr) - Log(Debug::Error) << "Couldn't open sensor " << SDL_SensorGetDeviceName(i) << ": " << SDL_GetError(); + Log(Debug::Error) + << "Couldn't open sensor " << SDL_SensorGetDeviceName(i) << ": " << SDL_GetError(); else { mGyroscope = sensor; @@ -141,7 +113,6 @@ namespace MWInput { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; - mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } } @@ -151,46 +122,8 @@ namespace MWInput { for (const auto& setting : changed) { - if (setting.first == "Input" && setting.second == "invert x axis") - mInvertX = Settings::Manager::getBool("invert x axis", "Input"); - - if (setting.first == "Input" && setting.second == "invert y axis") - mInvertY = Settings::Manager::getBool("invert y axis", "Input"); - - if (setting.first == "Input" && setting.second == "gyro horizontal sensitivity") - mGyroHSensitivity = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); - - if (setting.first == "Input" && setting.second == "gyro vertical sensitivity") - mGyroVSensitivity = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); - if (setting.first == "Input" && setting.second == "enable gyroscope") init(); - - if (setting.first == "Input" && setting.second == "gyro horizontal axis") - correctGyroscopeAxes(); - - if (setting.first == "Input" && setting.second == "gyro vertical axis") - correctGyroscopeAxes(); - - if (setting.first == "Input" && setting.second == "gyro input threshold") - mGyroInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); - } - } - - float SensorManager::getGyroAxisSpeed(GyroscopeAxis axis, const SDL_SensorEvent &arg) const - { - switch (axis) - { - case GyroscopeAxis::X: - case GyroscopeAxis::Y: - case GyroscopeAxis::Z: - return std::abs(arg.data[0]) >= mGyroInputThreshold ? arg.data[axis-1] : 0.f; - case GyroscopeAxis::Minus_X: - case GyroscopeAxis::Minus_Y: - case GyroscopeAxis::Minus_Z: - return std::abs(arg.data[0]) >= mGyroInputThreshold ? -arg.data[std::abs(axis)-1] : 0.f; - default: - return 0.f; } } @@ -199,12 +132,12 @@ namespace MWInput correctGyroscopeAxes(); } - void SensorManager::sensorUpdated(const SDL_SensorEvent &arg) + void SensorManager::sensorUpdated(const SDL_SensorEvent& arg) { if (!Settings::Manager::getBool("enable gyroscope", "Input")) return; - SDL_Sensor *sensor = SDL_SensorFromInstanceID(arg.which); + SDL_Sensor* sensor = SDL_SensorFromInstanceID(arg.which); if (!sensor) { Log(Debug::Info) << "Couldn't get sensor for sensor event"; @@ -217,54 +150,36 @@ namespace MWInput break; case SDL_SENSOR_GYRO: { - mGyroXSpeed = getGyroAxisSpeed(mGyroHAxis, arg); - mGyroYSpeed = getGyroAxisSpeed(mGyroVAxis, arg); + osg::Vec3f gyro(arg.data[0], arg.data[1], arg.data[2]); + mGyroValues = mRotation * gyro; mGyroUpdateTimer = 0.f; - break; - } - default: - break; + } + default: + break; } } void SensorManager::update(float dt) { - if (mGyroXSpeed == 0.f && mGyroYSpeed == 0.f) - return; - + mGyroUpdateTimer += dt; if (mGyroUpdateTimer > 0.5f) { // More than half of second passed since the last gyroscope update. // A device more likely was disconnected or switched to the sleep mode. // Reset current rotation speed and wait for update. - mGyroXSpeed = 0.f; - mGyroYSpeed = 0.f; + mGyroValues = osg::Vec3f(); mGyroUpdateTimer = 0.f; - return; - } - - mGyroUpdateTimer += dt; - - if (!mGuiCursorEnabled) - { - float rot[3]; - rot[0] = -mGyroYSpeed * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1); - rot[1] = 0.0f; - rot[2] = -mGyroXSpeed * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1); - - // Only actually turn player when we're not in vanity mode - bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); - if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) - { - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - player.yaw(-rot[2]); - player.pitch(-rot[0]); - } - else if (!playerLooking) - MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); - - MWBase::Environment::get().getInputManager()->resetIdleTime(); } } + + bool SensorManager::isGyroAvailable() const + { + return mGyroscope != nullptr; + } + + std::array SensorManager::getGyroValues() const + { + return { mGyroValues.x(), mGyroValues.y(), mGyroValues.z() }; + } } diff --git a/apps/openmw/mwinput/sensormanager.hpp b/apps/openmw/mwinput/sensormanager.hpp index 75472d43b..96d46d628 100644 --- a/apps/openmw/mwinput/sensormanager.hpp +++ b/apps/openmw/mwinput/sensormanager.hpp @@ -3,8 +3,11 @@ #include -#include +#include +#include + #include +#include namespace SDLUtil { @@ -29,45 +32,22 @@ namespace MWInput void update(float dt); - void sensorUpdated(const SDL_SensorEvent &arg) override; + void sensorUpdated(const SDL_SensorEvent& arg) override; void displayOrientationChanged() override; void processChangedSettings(const Settings::CategorySettingVector& changed); - void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } + bool isGyroAvailable() const; + std::array getGyroValues() const; private: - enum GyroscopeAxis - { - Unknown = 0, - X = 1, - Y = 2, - Z = 3, - Minus_X = -1, - Minus_Y = -2, - Minus_Z = -3 - }; - void updateSensors(); void correctGyroscopeAxes(); - GyroscopeAxis mapGyroscopeAxis(const std::string& axis); - float getGyroAxisSpeed(GyroscopeAxis axis, const SDL_SensorEvent &arg) const; - bool mInvertX; - bool mInvertY; - - float mGyroXSpeed; - float mGyroYSpeed; + osg::Matrixf mRotation; + osg::Vec3f mGyroValues; float mGyroUpdateTimer; - float mGyroHSensitivity; - float mGyroVSensitivity; - GyroscopeAxis mGyroHAxis; - GyroscopeAxis mGyroVAxis; - float mGyroInputThreshold; - SDL_Sensor* mGyroscope; - - bool mGuiCursorEnabled; }; } #endif diff --git a/apps/openmw/mwlua/README.md b/apps/openmw/mwlua/README.md new file mode 100644 index 000000000..ed911eb01 --- /dev/null +++ b/apps/openmw/mwlua/README.md @@ -0,0 +1,102 @@ +# MWLua + +This folder contains the C++ implementation of the Lua scripting system. + +For user-facing documentation, see +[OpenMW Lua scripting](https://openmw.readthedocs.io/en/latest/reference/lua-scripting/index.html). +The documentation is generated from +[/docs/source/reference/lua-scripting](/docs/source/reference/lua-scripting). +You can find instructions for generating the documentation at the +root of the [docs folder](/docs/README.md). + +The Lua API reference is generated from the specifications in +[/files/lua_api](/files/lua_api/). They are written in the +Lua Development Tool [Documentation Language](https://wiki.eclipse.org/LDT/User_Area/Documentation_Language), +and also enable autocompletion for ([LDT](https://www.eclipse.org/ldt/)) users. +Please update them to reflect any changes you make. + +## MWLua::LuaManager + +The Lua manager is the central interface through which information flows +from the engine to the scripts and back. + +Lua is executed in a separate [thread](/apps/openmw/mwlua/worker.hpp) by +[default](https://openmw.readthedocs.io/en/latest/reference/modding/settings/lua.html#lua-num-threads). +This thread executes `update()` in parallel with rendering logic (specifically with OSG Cull traversal). +Because of this, Lua must not synchronously mutate anything that can directly or indirectly affect the scene graph. +Instead such changes are queued to `mActionQueue`. They are then processed by +`synchronizedUpdate()`, which is executed by the main thread. +The Lua thread is paused while other updates of the game state take place, +which means that state that doesn't affect the scene graph +can be mutated immediately. There is no easy way to characterize +which things affect the graph, you'll need to inspect the code. + +## Bindings + +The bulk of the code in this folder consists of bindings that expose C++ data to Lua. + +As explained in the [scripting overview](https://openmw.readthedocs.io/en/latest/reference/lua-scripting/overview.html), +there are Global and Local scripts, and they have different capabilities. +A Local script has read-only access to objects other the one it is attached to. +The bindings use the types `MWLua::GObject`, `MWLua::LObject`, `MWLua::SelfObject` to enforce this behaviour. + +* `MWLua::GObject` is used in global scripts +* `MWLua::LObject` is used in local scripts (readonly), +* `MWLua::SelfObject` is the object the local script is attached to. +* `MWLua::Object` is the common base of all 3. + +Functions that don't change objects are usually available in both local and global scripts so they accept `MWLua::Object`. +Some (for example `setEquipment` in [actor.cpp](https://gitlab.com/OpenMW/openmw/-/blob/master/apps/openmw/mwlua/types/actor.cpp)) +should work only on self and because of this have argument of type `SelfObject`. +There are also cases where a function is available in both local and global scripts, but has different effects in different cases. +For example see the binding `actor["inventory"]` in 'MWLua::addActorBindings` in [actor.cpp](https://gitlab.com/OpenMW/openmw/-/blob/master/apps/openmw/mwlua/types/actor.cpp): + +```cpp +actor["inventory"] = sol::overload([](const LObject& o) { return Inventory{ o }; }, + [](const GObject& o) { return Inventory{ o }; }); +``` + +The difference is that `Inventory` is readonly and `Inventory` is mutable. +The read-only bindings are defined for both, but some functions are exclusive for `Inventory`. + +### Mutations that affect the scene graph + +Because of the threading issues mentioned under `MWLua::LuaManager`, +bindings that mutate things that affect the scene graph +must be implemented by queuing an action with `LuaManager::addAction`. + +Here is an example that illustrates action queuing, +along with the differences between `GObject` and `LObject`: + +```cpp +// We can always read the value because OSG Cull doesn't modify `RefData`. +auto isEnabled = [](const Object& o) { return o.ptr().getRefData().isEnabled(); }; + +// Changing the value must be queued because `World::enable`/`World::disable` aside of +// changing `RefData` also adds/removes the object to the scene graph. +auto setEnabled = [context](const Object& object, bool enable) { + // It is important that the lambda state stores `object` and not the result of + // `object.ptr()` because when delayed will be executed the old Ptr can potentially + // be already invalidated. + context.mLuaManager->addAction([object, enable] { + if (enable) + MWBase::Environment::get().getWorld()->enable(object.ptr()); + else + MWBase::Environment::get().getWorld()->disable(object.ptr()); + }); +}; + +// Local scripts can only view the value (because in multiplayer local scripts +// will be client-side and we want to avoid synchronization issues). +LObjectMetatable["enabled"] = sol::readonly_property(isEnabled); + +// Global scripts can both read and modify the value. +GObjectMetatable["enabled"] = sol::property(isEnabled, setEnabled); +``` + +Please note that queueing means changes scripts make won't be visible to other scripts before the +next frame. If you want to avoid that, you can implement a cache in the bindings. +The first write will create the cache and queue the value to be synchronized from the +cache to the engine in the next synchronization. Later writes will update the cache. +Reads will read the cache if it exists. See [LocalScripts::SelfObject::mStatsCache](/apps/openmw/mwlua/localscripts.hpp) +for an example. diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp new file mode 100644 index 000000000..bd5c43d24 --- /dev/null +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -0,0 +1,129 @@ +#include "luabindings.hpp" + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwrender/camera.hpp" +#include "../mwrender/renderingmanager.hpp" + +namespace MWLua +{ + + using CameraMode = MWRender::Camera::Mode; + + sol::table initCameraPackage(sol::state_view& lua) + { + MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera(); + MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager(); + + sol::table api(lua, sol::create); + api["MODE"] = LuaUtil::makeStrictReadOnly( + lua.create_table_with("Static", CameraMode::Static, "FirstPerson", CameraMode::FirstPerson, "ThirdPerson", + CameraMode::ThirdPerson, "Vanity", CameraMode::Vanity, "Preview", CameraMode::Preview)); + + api["getMode"] = [camera]() -> int { return static_cast(camera->getMode()); }; + api["getQueuedMode"] = [camera]() -> sol::optional { + std::optional mode = camera->getQueuedMode(); + if (mode) + return static_cast(*mode); + else + return sol::nullopt; + }; + api["setMode"] = [camera](int mode, sol::optional force) { + camera->setMode(static_cast(mode), force ? *force : false); + }; + + api["allowCharacterDeferredRotation"] = [camera](bool v) { camera->allowCharacterDeferredRotation(v); }; + api["showCrosshair"] = [camera](bool v) { camera->showCrosshair(v); }; + + api["getTrackedPosition"] = [camera]() -> osg::Vec3f { return camera->getTrackedPosition(); }; + api["getPosition"] = [camera]() -> osg::Vec3f { return camera->getPosition(); }; + + // All angles are negated in order to make camera rotation consistent with objects rotation. + // TODO: Fix the inconsistency of rotation direction in camera.cpp. + api["getPitch"] = [camera]() { return -camera->getPitch(); }; + api["getYaw"] = [camera]() { return -camera->getYaw(); }; + api["getRoll"] = [camera]() { return -camera->getRoll(); }; + + api["setStaticPosition"] = [camera](const osg::Vec3f& pos) { camera->setStaticPosition(pos); }; + api["setPitch"] = [camera](float v) { + camera->setPitch(-v, true); + if (camera->getMode() == CameraMode::ThirdPerson) + camera->calculateDeferredRotation(); + }; + api["setYaw"] = [camera](float v) { + camera->setYaw(-v, true); + if (camera->getMode() == CameraMode::ThirdPerson) + camera->calculateDeferredRotation(); + }; + api["setRoll"] = [camera](float v) { camera->setRoll(-v); }; + api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); }; + api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); }; + api["setExtraRoll"] = [camera](float v) { camera->setExtraRoll(-v); }; + api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); }; + api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); }; + api["getExtraRoll"] = [camera]() { return -camera->getExtraRoll(); }; + + api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); }; + api["setPreferredThirdPersonDistance"] = [camera](float v) { camera->setPreferredCameraDistance(v); }; + + api["getFirstPersonOffset"] = [camera]() { return camera->getFirstPersonOffset(); }; + api["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); }; + + api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); }; + api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); }; + api["getFocalTransitionSpeed"] = [camera]() { return camera->getFocalPointTransitionSpeed(); }; + api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); }; + api["instantTransition"] = [camera]() { camera->instantTransition(); }; + + api["getCollisionType"] = [camera]() { return camera->getCollisionType(); }; + api["setCollisionType"] = [camera](int collisionType) { camera->setCollisionType(collisionType); }; + + api["getBaseFieldOfView"] = [] { return osg::DegreesToRadians(Settings::camera().mFieldOfView); }; + api["getFieldOfView"] + = [renderingManager]() { return osg::DegreesToRadians(renderingManager->getFieldOfView()); }; + api["setFieldOfView"] + = [renderingManager](float v) { renderingManager->setFieldOfView(osg::RadiansToDegrees(v)); }; + + api["getBaseViewDistance"] = [] { return Settings::camera().mViewingDistance.get(); }; + api["getViewDistance"] = [renderingManager]() { return renderingManager->getViewDistance(); }; + api["setViewDistance"] = [renderingManager](float d) { renderingManager->setViewDistance(d, true); }; + + api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; }; + + api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f { + double width = Settings::Manager::getInt("resolution x", "Video"); + double height = Settings::Manager::getInt("resolution y", "Video"); + double aspect = (height == 0.0) ? 1.0 : width / height; + double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2); + osg::Matrixf invertedViewMatrix; + invertedViewMatrix.invert(camera->getViewMatrix()); + float x = (pos.x() * 2 - 1) * aspect * fovTan; + float y = (1 - pos.y() * 2) * fovTan; + return invertedViewMatrix.preMult(osg::Vec3f(x, y, -1)) - camera->getPosition(); + }; + + api["worldToViewportVector"] = [camera](osg::Vec3f pos) { + double width = Settings::Manager::getInt("resolution x", "Video"); + double height = Settings::Manager::getInt("resolution y", "Video"); + + osg::Matrix windowMatrix + = osg::Matrix::translate(1.0, 1.0, 1.0) * osg::Matrix::scale(0.5 * width, 0.5 * height, 0.5); + osg::Vec3f vpCoords = pos * (camera->getViewMatrix() * camera->getProjectionMatrix() * windowMatrix); + + // Move 0,0 to top left to match viewportToWorldVector + vpCoords.y() = height - vpCoords.y(); + + // Set the z component to be distance from camera, in world space units + vpCoords.z() = (pos - camera->getPosition()).length(); + + return vpCoords; + }; + + return LuaUtil::makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/camerabindings.hpp b/apps/openmw/mwlua/camerabindings.hpp new file mode 100644 index 000000000..be468495e --- /dev/null +++ b/apps/openmw/mwlua/camerabindings.hpp @@ -0,0 +1,11 @@ +#ifndef MWLUA_CAMERABINDINGS_H +#define MWLUA_CAMERABINDINGS_H + +#include + +namespace MWLua +{ + sol::table initCameraPackage(sol::state_view& lua); +} + +#endif // MWLUA_CAMERABINDINGS_H diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp new file mode 100644 index 000000000..e961168be --- /dev/null +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -0,0 +1,238 @@ +#include "cellbindings.hpp" + +#include +#include + +#include "../mwworld/cellstore.hpp" + +#include "types/types.hpp" +#include "worldview.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + + template + static void initCellBindings(const std::string& prefix, const Context& context) + { + sol::usertype cellT = context.mLua->sol().new_usertype(prefix + "Cell"); + + cellT[sol::meta_function::equal_to] = [](const CellT& a, const CellT& b) { return a.mStore == b.mStore; }; + cellT[sol::meta_function::to_string] = [](const CellT& c) { + auto cell = c.mStore->getCell(); + std::stringstream res; + if (cell->isExterior()) + res << "exterior(" << cell->getGridX() << ", " << cell->getGridY() << ", " + << cell->getWorldSpace().toDebugString() << ")"; + else + res << "interior(" << cell->getNameId() << ")"; + return res.str(); + }; + + cellT["name"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getNameId(); }); + cellT["region"] = sol::readonly_property( + [](const CellT& c) -> std::string { return c.mStore->getCell()->getRegion().serializeText(); }); + cellT["worldSpaceId"] = sol::readonly_property( + [](const CellT& c) -> std::string { return c.mStore->getCell()->getWorldSpace().serializeText(); }); + cellT["gridX"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridX(); }); + cellT["gridY"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridY(); }); + cellT["hasWater"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->hasWater(); }); + cellT["hasSky"] = sol::readonly_property([](const CellT& c) { + return c.mStore->getCell()->isExterior() || (c.mStore->getCell()->isQuasiExterior()) != 0; + }); + cellT["isExterior"] = sol::readonly_property([](const CellT& c) { return c.mStore->isExterior(); }); + + // deprecated, use cell:hasTag("QuasiExterior") instead + cellT["isQuasiExterior"] + = sol::readonly_property([](const CellT& c) { return (c.mStore->getCell()->isQuasiExterior()) != 0; }); + + cellT["hasTag"] = [](const CellT& c, std::string_view tag) -> bool { + if (tag == "NoSleep") + return (c.mStore->getCell()->noSleep()) != 0; + else if (tag == "QuasiExterior") + return (c.mStore->getCell()->isQuasiExterior()) != 0; + return false; + }; + + cellT["isInSameSpace"] = [](const CellT& c, const ObjectT& obj) { + const MWWorld::Ptr& ptr = obj.ptr(); + if (!ptr.isInCell()) + return false; + MWWorld::CellStore* cell = ptr.getCell(); + return cell == c.mStore || (cell->getCell()->getWorldSpace() == c.mStore->getCell()->getWorldSpace()); + }; + + if constexpr (std::is_same_v) + { // only for global scripts + cellT["getAll"] = [ids = getPackageToTypeTable(context.mLua->sol())]( + const CellT& cell, sol::optional type) { + if (cell.mStore->getState() != MWWorld::CellStore::State_Loaded) + cell.mStore->load(); + ObjectIdList res = std::make_shared>(); + auto visitor = [&](const MWWorld::Ptr& ptr) { + if (ptr.getRefData().isDeleted()) + return true; + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + if (getLiveCellRefType(ptr.mRef) == ptr.getType()) + res->push_back(getId(ptr)); + return true; + }; + + bool ok = false; + sol::optional typeId = sol::nullopt; + if (type.has_value()) + typeId = ids[*type]; + else + { + ok = true; + cell.mStore->forEach(std::move(visitor)); + } + if (typeId.has_value()) + { + ok = true; + switch (*typeId) + { + case ESM::REC_INTERNAL_PLAYER: + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getCell() == cell.mStore) + res->push_back(getId(player)); + } + break; + + case ESM::REC_CREA: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_NPC_: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_ACTI: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_DOOR: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_CONT: + cell.mStore->template forEachType(visitor); + break; + + case ESM::REC_ALCH: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_ARMO: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_BOOK: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_CLOT: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_INGR: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_LIGH: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_MISC: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_WEAP: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_APPA: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_LOCK: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_PROB: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_REPA: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_STAT: + cell.mStore->template forEachType(visitor); + break; + + case ESM::REC_ACTI4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_AMMO4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_ARMO4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_BOOK4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_CLOT4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_CONT4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_DOOR4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_FURN4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_INGR4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_LIGH4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_MISC4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_ALCH4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_STAT4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_TREE4: + cell.mStore->template forEachType(visitor); + break; + case ESM::REC_WEAP4: + cell.mStore->template forEachType(visitor); + break; + + default: + ok = false; + } + } + if (!ok) + throw std::runtime_error( + std::string("Incorrect type argument in cell:getAll: " + LuaUtil::toString(*type))); + return GObjectList{ res }; + }; + } + } + + void initCellBindingsForLocalScripts(const Context& context) + { + initCellBindings("L", context); + } + + void initCellBindingsForGlobalScripts(const Context& context) + { + initCellBindings("G", context); + } + +} diff --git a/apps/openmw/mwlua/cellbindings.hpp b/apps/openmw/mwlua/cellbindings.hpp new file mode 100644 index 000000000..0d8e90e98 --- /dev/null +++ b/apps/openmw/mwlua/cellbindings.hpp @@ -0,0 +1,12 @@ +#ifndef MWLUA_CELLBINDINGS_H +#define MWLUA_CELLBINDINGS_H + +#include "context.hpp" + +namespace MWLua +{ + void initCellBindingsForLocalScripts(const Context&); + void initCellBindingsForGlobalScripts(const Context&); +} + +#endif // MWLUA_CELLBINDINGS_H diff --git a/apps/openmw/mwlua/context.hpp b/apps/openmw/mwlua/context.hpp new file mode 100644 index 000000000..e743bfa50 --- /dev/null +++ b/apps/openmw/mwlua/context.hpp @@ -0,0 +1,28 @@ +#ifndef MWLUA_CONTEXT_H +#define MWLUA_CONTEXT_H + +namespace LuaUtil +{ + class LuaState; + class UserdataSerializer; +} + +namespace MWLua +{ + class LuaEvents; + class LuaManager; + class WorldView; + + struct Context + { + bool mIsGlobal; + LuaManager* mLuaManager; + LuaUtil::LuaState* mLua; + LuaUtil::UserdataSerializer* mSerializer; + WorldView* mWorldView; + LuaEvents* mLuaEvents; + }; + +} + +#endif // MWLUA_CONTEXT_H diff --git a/apps/openmw/mwlua/debugbindings.cpp b/apps/openmw/mwlua/debugbindings.cpp new file mode 100644 index 000000000..7f64188ff --- /dev/null +++ b/apps/openmw/mwlua/debugbindings.cpp @@ -0,0 +1,85 @@ +#include "debugbindings.hpp" +#include "context.hpp" +#include "luamanagerimp.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwrender/postprocessor.hpp" +#include "../mwrender/renderingmanager.hpp" + +#include +#include +#include + +#include + +namespace MWLua +{ + sol::table initDebugPackage(const Context& context) + { + sol::table api = context.mLua->newTable(); + + api["RENDER_MODE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "CollisionDebug", MWRender::Render_CollisionDebug }, + { "Wireframe", MWRender::Render_Wireframe }, + { "Pathgrid", MWRender::Render_Pathgrid }, + { "Water", MWRender::Render_Water }, + { "Scene", MWRender::Render_Scene }, + { "NavMesh", MWRender::Render_NavMesh }, + { "ActorsPaths", MWRender::Render_ActorsPaths }, + { "RecastMesh", MWRender::Render_RecastMesh }, + })); + + api["toggleRenderMode"] = [context](MWRender::RenderMode value) { + context.mLuaManager->addAction([value] { MWBase::Environment::get().getWorld()->toggleRenderMode(value); }); + }; + + api["toggleGodMode"] = []() { MWBase::Environment::get().getWorld()->toggleGodMode(); }; + api["isGodMode"] = []() { return MWBase::Environment::get().getWorld()->getGodModeState(); }; + + api["toggleCollision"] = []() { MWBase::Environment::get().getWorld()->toggleCollisionMode(); }; + api["isCollisionEnabled"] = []() { + auto world = MWBase::Environment::get().getWorld(); + return world->isActorCollisionEnabled(world->getPlayerPtr()); + }; + + api["NAV_MESH_RENDER_MODE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "AreaType", MWRender::NavMeshMode::AreaType }, + { "UpdateFrequency", MWRender::NavMeshMode::UpdateFrequency }, + })); + + api["setNavMeshRenderMode"] = [context](MWRender::NavMeshMode value) { + context.mLuaManager->addAction( + [value] { MWBase::Environment::get().getWorld()->getRenderingManager()->setNavMeshMode(value); }); + }; + + api["triggerShaderReload"] = [context]() { + context.mLuaManager->addAction([] { + auto world = MWBase::Environment::get().getWorld(); + + world->getRenderingManager() + ->getResourceSystem() + ->getSceneManager() + ->getShaderManager() + .triggerShaderReload(); + world->getPostProcessor()->triggerShaderReload(); + }); + }; + + api["setShaderHotReloadEnabled"] = [context](bool value) { + context.mLuaManager->addAction([value] { + auto world = MWBase::Environment::get().getWorld(); + world->getRenderingManager() + ->getResourceSystem() + ->getSceneManager() + ->getShaderManager() + .setHotReloadEnabled(value); + world->getPostProcessor()->mEnableLiveReload = value; + }); + }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/debugbindings.hpp b/apps/openmw/mwlua/debugbindings.hpp new file mode 100644 index 000000000..c508b5449 --- /dev/null +++ b/apps/openmw/mwlua/debugbindings.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_MWLUA_DEBUGBINDINGS_H +#define OPENMW_MWLUA_DEBUGBINDINGS_H + +#include + +namespace MWLua +{ + struct Context; + + sol::table initDebugPackage(const Context& context); +} + +#endif // OPENMW_MWLUA_DEBUGBINDINGS_H diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp new file mode 100644 index 000000000..13d3f033e --- /dev/null +++ b/apps/openmw/mwlua/engineevents.cpp @@ -0,0 +1,107 @@ +#include "engineevents.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/worldmodel.hpp" + +#include "globalscripts.hpp" +#include "localscripts.hpp" +#include "object.hpp" + +namespace MWLua +{ + + class EngineEvents::Visitor + { + public: + explicit Visitor(GlobalScripts& globalScripts) + : mGlobalScripts(globalScripts) + { + } + + void operator()(const OnNewGame&) const { mGlobalScripts.newGameStarted(); } + + void operator()(const OnActive& event) const + { + MWWorld::Ptr ptr = getPtr(event.mObject); + if (ptr.isEmpty()) + return; + if (ptr.getCellRef().getRefId() == "player") + mGlobalScripts.playerAdded(GObject(ptr)); + else + { + mGlobalScripts.objectActive(GObject(ptr)); + const MWWorld::Class& objClass = ptr.getClass(); + if (objClass.isActor()) + mGlobalScripts.actorActive(GObject(ptr)); + if (objClass.isItem(ptr)) + mGlobalScripts.itemActive(GObject(ptr)); + } + if (auto* scripts = getLocalScripts(ptr)) + scripts->setActive(true); + } + + void operator()(const OnInactive& event) const + { + if (auto* scripts = getLocalScripts(event.mObject)) + scripts->setActive(false); + } + + void operator()(const OnActivate& event) const + { + MWWorld::Ptr obj = getPtr(event.mObject); + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty() || obj.isEmpty()) + return; + mGlobalScripts.onActivate(GObject(obj), GObject(actor)); + if (auto* scripts = getLocalScripts(obj)) + scripts->onActivated(LObject(actor)); + } + + void operator()(const OnConsume& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + MWWorld::Ptr consumable = getPtr(event.mConsumable); + if (actor.isEmpty() || consumable.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onConsume(LObject(consumable)); + } + + void operator()(const OnNewExterior& event) const { mGlobalScripts.onNewExterior(GCell{ &event.mCell }); } + + private: + MWWorld::Ptr getPtr(const ESM::RefNum& id) const + { + MWWorld::Ptr res = mWorldModel->getPtr(id); + if (res.isEmpty() && Settings::lua().mLuaDebug) + Log(Debug::Verbose) << "Can not find object" << id.toString() << " when calling engine hanglers"; + return res; + } + + LocalScripts* getLocalScripts(const MWWorld::Ptr& ptr) const + { + if (ptr.isEmpty()) + return nullptr; + else + return ptr.getRefData().getLuaScripts(); + } + + LocalScripts* getLocalScripts(const ESM::RefNum& id) const { return getLocalScripts(getPtr(id)); } + + GlobalScripts& mGlobalScripts; + MWWorld::WorldModel* mWorldModel = MWBase::Environment::get().getWorldModel(); + }; + + void EngineEvents::callEngineHandlers() + { + Visitor vis(mGlobalScripts); + for (const Event& event : mQueue) + std::visit(vis, event); + mQueue.clear(); + } + +} diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp new file mode 100644 index 000000000..78892ae19 --- /dev/null +++ b/apps/openmw/mwlua/engineevents.hpp @@ -0,0 +1,62 @@ +#ifndef MWLUA_ENGINEEVENTS_H +#define MWLUA_ENGINEEVENTS_H + +#include + +#include // defines RefNum that is used as a unique id + +#include "../mwworld/cellstore.hpp" + +namespace MWLua +{ + class GlobalScripts; + + class EngineEvents + { + public: + explicit EngineEvents(GlobalScripts& globalScripts) + : mGlobalScripts(globalScripts) + { + } + + struct OnNewGame + { + }; + struct OnActive + { + ESM::RefNum mObject; + }; + struct OnInactive + { + ESM::RefNum mObject; + }; + struct OnActivate + { + ESM::RefNum mActor; + ESM::RefNum mObject; + }; + struct OnConsume + { + ESM::RefNum mActor; + ESM::RefNum mConsumable; + }; + struct OnNewExterior + { + MWWorld::CellStore& mCell; + }; + using Event = std::variant; + + void clear() { mQueue.clear(); } + void addToQueue(Event e) { mQueue.push_back(std::move(e)); } + void callEngineHandlers(); + + private: + class Visitor; + + GlobalScripts& mGlobalScripts; + std::vector mQueue; + }; + +} + +#endif // MWLUA_ENGINEEVENTS_H diff --git a/apps/openmw/mwlua/globalscripts.hpp b/apps/openmw/mwlua/globalscripts.hpp new file mode 100644 index 000000000..019000058 --- /dev/null +++ b/apps/openmw/mwlua/globalscripts.hpp @@ -0,0 +1,49 @@ +#ifndef MWLUA_GLOBALSCRIPTS_H +#define MWLUA_GLOBALSCRIPTS_H + +#include +#include +#include + +#include +#include + +#include "object.hpp" + +namespace MWLua +{ + + class GlobalScripts : public LuaUtil::ScriptsContainer + { + public: + GlobalScripts(LuaUtil::LuaState* lua) + : LuaUtil::ScriptsContainer(lua, "Global") + { + registerEngineHandlers({ &mObjectActiveHandlers, &mActorActiveHandlers, &mItemActiveHandlers, + &mNewGameHandlers, &mPlayerAddedHandlers, &mOnActivateHandlers, &mOnNewExteriorHandlers }); + } + + void newGameStarted() { callEngineHandlers(mNewGameHandlers); } + void objectActive(const GObject& obj) { callEngineHandlers(mObjectActiveHandlers, obj); } + void actorActive(const GObject& obj) { callEngineHandlers(mActorActiveHandlers, obj); } + void itemActive(const GObject& obj) { callEngineHandlers(mItemActiveHandlers, obj); } + void playerAdded(const GObject& obj) { callEngineHandlers(mPlayerAddedHandlers, obj); } + void onActivate(const GObject& obj, const GObject& actor) + { + callEngineHandlers(mOnActivateHandlers, obj, actor); + } + void onNewExterior(const GCell& cell) { callEngineHandlers(mOnNewExteriorHandlers, cell); } + + private: + EngineHandlerList mObjectActiveHandlers{ "onObjectActive" }; + EngineHandlerList mActorActiveHandlers{ "onActorActive" }; + EngineHandlerList mItemActiveHandlers{ "onItemActive" }; + EngineHandlerList mNewGameHandlers{ "onNewGame" }; + EngineHandlerList mPlayerAddedHandlers{ "onPlayerAdded" }; + EngineHandlerList mOnActivateHandlers{ "onActivate" }; + EngineHandlerList mOnNewExteriorHandlers{ "onNewExterior" }; + }; + +} + +#endif // MWLUA_GLOBALSCRIPTS_H diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp new file mode 100644 index 000000000..9384eccdb --- /dev/null +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -0,0 +1,295 @@ +#include "inputbindings.hpp" + +#include +#include +#include + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" +#include "../mwinput/actions.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + + sol::table initInputPackage(const Context& context) + { + sol::usertype keyEvent = context.mLua->sol().new_usertype("KeyEvent"); + keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { + if (e.sym > 0 && e.sym <= 255) + return std::string(1, static_cast(e.sym)); + else + return std::string(); + }); + keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; }); + keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; }); + keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; }); + keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); + keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); + + auto touchpadEvent = context.mLua->sol().new_usertype("TouchpadEvent"); + touchpadEvent["device"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; }); + touchpadEvent["finger"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; }); + touchpadEvent["position"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> osg::Vec2f { + return { e.mX, e.mY }; + }); + touchpadEvent["pressure"] + = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); + + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); + sol::table api(context.mLua->sol(), sol::create); + + api["isIdle"] = [input]() { return input->isIdle(); }; + api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); }; + api["isKeyPressed"] = [](SDL_Scancode code) -> bool { + int maxCode; + const auto* state = SDL_GetKeyboardState(&maxCode); + if (code >= 0 && code < maxCode) + return state[code] != 0; + else + return false; + }; + api["isShiftPressed"] = []() -> bool { return SDL_GetModState() & KMOD_SHIFT; }; + api["isCtrlPressed"] = []() -> bool { return SDL_GetModState() & KMOD_CTRL; }; + api["isAltPressed"] = []() -> bool { return SDL_GetModState() & KMOD_ALT; }; + api["isSuperPressed"] = []() -> bool { return SDL_GetModState() & KMOD_GUI; }; + api["isControllerButtonPressed"] = [input](int button) { + return input->isControllerButtonPressed(static_cast(button)); + }; + api["isMouseButtonPressed"] + = [](int button) -> bool { return SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(button); }; + api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; + api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; + api["getAxisValue"] = [input](int axis) { + if (axis < SDL_CONTROLLER_AXIS_MAX) + return input->getControllerAxisValue(static_cast(axis)); + else + return input->getActionValue(axis - SDL_CONTROLLER_AXIS_MAX) * 2 - 1; + }; + + api["getControlSwitch"] = [input](std::string_view key) { return input->getControlSwitch(key); }; + api["setControlSwitch"] = [input](std::string_view key, bool v) { input->toggleControlSwitch(key, v); }; + + api["getKeyName"] = [](SDL_Scancode code) { return SDL_GetKeyName(SDL_GetKeyFromScancode(code)); }; + + api["ACTION"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "GameMenu", MWInput::A_GameMenu }, + { "Screenshot", MWInput::A_Screenshot }, + { "Inventory", MWInput::A_Inventory }, + { "Console", MWInput::A_Console }, + + { "MoveLeft", MWInput::A_MoveLeft }, + { "MoveRight", MWInput::A_MoveRight }, + { "MoveForward", MWInput::A_MoveForward }, + { "MoveBackward", MWInput::A_MoveBackward }, + + { "Activate", MWInput::A_Activate }, + { "Use", MWInput::A_Use }, + { "Jump", MWInput::A_Jump }, + { "AutoMove", MWInput::A_AutoMove }, + { "Rest", MWInput::A_Rest }, + { "Journal", MWInput::A_Journal }, + { "Run", MWInput::A_Run }, + { "CycleSpellLeft", MWInput::A_CycleSpellLeft }, + { "CycleSpellRight", MWInput::A_CycleSpellRight }, + { "CycleWeaponLeft", MWInput::A_CycleWeaponLeft }, + { "CycleWeaponRight", MWInput::A_CycleWeaponRight }, + { "AlwaysRun", MWInput::A_AlwaysRun }, + { "Sneak", MWInput::A_Sneak }, + + { "QuickSave", MWInput::A_QuickSave }, + { "QuickLoad", MWInput::A_QuickLoad }, + { "QuickMenu", MWInput::A_QuickMenu }, + { "ToggleWeapon", MWInput::A_ToggleWeapon }, + { "ToggleSpell", MWInput::A_ToggleSpell }, + { "TogglePOV", MWInput::A_TogglePOV }, + + { "QuickKey1", MWInput::A_QuickKey1 }, + { "QuickKey2", MWInput::A_QuickKey2 }, + { "QuickKey3", MWInput::A_QuickKey3 }, + { "QuickKey4", MWInput::A_QuickKey4 }, + { "QuickKey5", MWInput::A_QuickKey5 }, + { "QuickKey6", MWInput::A_QuickKey6 }, + { "QuickKey7", MWInput::A_QuickKey7 }, + { "QuickKey8", MWInput::A_QuickKey8 }, + { "QuickKey9", MWInput::A_QuickKey9 }, + { "QuickKey10", MWInput::A_QuickKey10 }, + { "QuickKeysMenu", MWInput::A_QuickKeysMenu }, + + { "ToggleHUD", MWInput::A_ToggleHUD }, + { "ToggleDebug", MWInput::A_ToggleDebug }, + { "TogglePostProcessorHUD", MWInput::A_TogglePostProcessorHUD }, + + { "ZoomIn", MWInput::A_ZoomIn }, + { "ZoomOut", MWInput::A_ZoomOut }, + })); + + api["CONTROL_SWITCH"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Controls", "playercontrols" }, + { "Fighting", "playerfighting" }, + { "Jumping", "playerjumping" }, + { "Looking", "playerlooking" }, + { "Magic", "playermagic" }, + { "ViewMode", "playerviewswitch" }, + { "VanityMode", "vanitymode" }, + })); + + api["CONTROLLER_BUTTON"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "A", SDL_CONTROLLER_BUTTON_A }, + { "B", SDL_CONTROLLER_BUTTON_B }, + { "X", SDL_CONTROLLER_BUTTON_X }, + { "Y", SDL_CONTROLLER_BUTTON_Y }, + { "Back", SDL_CONTROLLER_BUTTON_BACK }, + { "Guide", SDL_CONTROLLER_BUTTON_GUIDE }, + { "Start", SDL_CONTROLLER_BUTTON_START }, + { "LeftStick", SDL_CONTROLLER_BUTTON_LEFTSTICK }, + { "RightStick", SDL_CONTROLLER_BUTTON_RIGHTSTICK }, + { "LeftShoulder", SDL_CONTROLLER_BUTTON_LEFTSHOULDER }, + { "RightShoulder", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER }, + { "DPadUp", SDL_CONTROLLER_BUTTON_DPAD_UP }, + { "DPadDown", SDL_CONTROLLER_BUTTON_DPAD_DOWN }, + { "DPadLeft", SDL_CONTROLLER_BUTTON_DPAD_LEFT }, + { "DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT }, + })); + + api["CONTROLLER_AXIS"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "LeftX", SDL_CONTROLLER_AXIS_LEFTX }, + { "LeftY", SDL_CONTROLLER_AXIS_LEFTY }, + { "RightX", SDL_CONTROLLER_AXIS_RIGHTX }, + { "RightY", SDL_CONTROLLER_AXIS_RIGHTY }, + { "TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT }, + { "TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT }, + + { "LookUpDown", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_LookUpDown) }, + { "LookLeftRight", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_LookLeftRight) }, + { "MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_MoveForwardBackward) }, + { "MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_MoveLeftRight) }, + })); + + api["KEY"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "_0", SDL_SCANCODE_0 }, + { "_1", SDL_SCANCODE_1 }, + { "_2", SDL_SCANCODE_2 }, + { "_3", SDL_SCANCODE_3 }, + { "_4", SDL_SCANCODE_4 }, + { "_5", SDL_SCANCODE_5 }, + { "_6", SDL_SCANCODE_6 }, + { "_7", SDL_SCANCODE_7 }, + { "_8", SDL_SCANCODE_8 }, + { "_9", SDL_SCANCODE_9 }, + + { "NP_0", SDL_SCANCODE_KP_0 }, + { "NP_1", SDL_SCANCODE_KP_1 }, + { "NP_2", SDL_SCANCODE_KP_2 }, + { "NP_3", SDL_SCANCODE_KP_3 }, + { "NP_4", SDL_SCANCODE_KP_4 }, + { "NP_5", SDL_SCANCODE_KP_5 }, + { "NP_6", SDL_SCANCODE_KP_6 }, + { "NP_7", SDL_SCANCODE_KP_7 }, + { "NP_8", SDL_SCANCODE_KP_8 }, + { "NP_9", SDL_SCANCODE_KP_9 }, + { "NP_Divide", SDL_SCANCODE_KP_DIVIDE }, + { "NP_Enter", SDL_SCANCODE_KP_ENTER }, + { "NP_Minus", SDL_SCANCODE_KP_MINUS }, + { "NP_Multiply", SDL_SCANCODE_KP_MULTIPLY }, + { "NP_Delete", SDL_SCANCODE_KP_PERIOD }, + { "NP_Plus", SDL_SCANCODE_KP_PLUS }, + + { "F1", SDL_SCANCODE_F1 }, + { "F2", SDL_SCANCODE_F2 }, + { "F3", SDL_SCANCODE_F3 }, + { "F4", SDL_SCANCODE_F4 }, + { "F5", SDL_SCANCODE_F5 }, + { "F6", SDL_SCANCODE_F6 }, + { "F7", SDL_SCANCODE_F7 }, + { "F8", SDL_SCANCODE_F8 }, + { "F9", SDL_SCANCODE_F9 }, + { "F10", SDL_SCANCODE_F10 }, + { "F11", SDL_SCANCODE_F11 }, + { "F12", SDL_SCANCODE_F12 }, + + { "A", SDL_SCANCODE_A }, + { "B", SDL_SCANCODE_B }, + { "C", SDL_SCANCODE_C }, + { "D", SDL_SCANCODE_D }, + { "E", SDL_SCANCODE_E }, + { "F", SDL_SCANCODE_F }, + { "G", SDL_SCANCODE_G }, + { "H", SDL_SCANCODE_H }, + { "I", SDL_SCANCODE_I }, + { "J", SDL_SCANCODE_J }, + { "K", SDL_SCANCODE_K }, + { "L", SDL_SCANCODE_L }, + { "M", SDL_SCANCODE_M }, + { "N", SDL_SCANCODE_N }, + { "O", SDL_SCANCODE_O }, + { "P", SDL_SCANCODE_P }, + { "Q", SDL_SCANCODE_Q }, + { "R", SDL_SCANCODE_R }, + { "S", SDL_SCANCODE_S }, + { "T", SDL_SCANCODE_T }, + { "U", SDL_SCANCODE_U }, + { "V", SDL_SCANCODE_V }, + { "W", SDL_SCANCODE_W }, + { "X", SDL_SCANCODE_X }, + { "Y", SDL_SCANCODE_Y }, + { "Z", SDL_SCANCODE_Z }, + + { "LeftArrow", SDL_SCANCODE_LEFT }, + { "RightArrow", SDL_SCANCODE_RIGHT }, + { "UpArrow", SDL_SCANCODE_UP }, + { "DownArrow", SDL_SCANCODE_DOWN }, + + { "LeftAlt", SDL_SCANCODE_LALT }, + { "LeftCtrl", SDL_SCANCODE_LCTRL }, + { "LeftBracket", SDL_SCANCODE_LEFTBRACKET }, + { "LeftSuper", SDL_SCANCODE_LGUI }, + { "LeftShift", SDL_SCANCODE_LSHIFT }, + { "RightAlt", SDL_SCANCODE_RALT }, + { "RightCtrl", SDL_SCANCODE_RCTRL }, + { "RightSuper", SDL_SCANCODE_RGUI }, + { "RightBracket", SDL_SCANCODE_RIGHTBRACKET }, + { "RightShift", SDL_SCANCODE_RSHIFT }, + + { "Apostrophe", SDL_SCANCODE_APOSTROPHE }, + { "BackSlash", SDL_SCANCODE_BACKSLASH }, + { "Backspace", SDL_SCANCODE_BACKSPACE }, + { "CapsLock", SDL_SCANCODE_CAPSLOCK }, + { "Comma", SDL_SCANCODE_COMMA }, + { "Delete", SDL_SCANCODE_DELETE }, + { "End", SDL_SCANCODE_END }, + { "Enter", SDL_SCANCODE_RETURN }, + { "Equals", SDL_SCANCODE_EQUALS }, + { "Escape", SDL_SCANCODE_ESCAPE }, + { "Home", SDL_SCANCODE_HOME }, + { "Insert", SDL_SCANCODE_INSERT }, + { "Minus", SDL_SCANCODE_MINUS }, + { "NumLock", SDL_SCANCODE_NUMLOCKCLEAR }, + { "PageDown", SDL_SCANCODE_PAGEDOWN }, + { "PageUp", SDL_SCANCODE_PAGEUP }, + { "Period", SDL_SCANCODE_PERIOD }, + { "Pause", SDL_SCANCODE_PAUSE }, + { "PrintScreen", SDL_SCANCODE_PRINTSCREEN }, + { "ScrollLock", SDL_SCANCODE_SCROLLLOCK }, + { "Semicolon", SDL_SCANCODE_SEMICOLON }, + { "Slash", SDL_SCANCODE_SLASH }, + { "Space", SDL_SCANCODE_SPACE }, + { "Tab", SDL_SCANCODE_TAB }, + })); + + return LuaUtil::makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/inputbindings.hpp b/apps/openmw/mwlua/inputbindings.hpp new file mode 100644 index 000000000..195f69f5f --- /dev/null +++ b/apps/openmw/mwlua/inputbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_INPUTBINDINGS_H +#define MWLUA_INPUTBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initInputPackage(const Context&); +} + +#endif // MWLUA_INPUTBINDINGS_H diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp new file mode 100644 index 000000000..4972a6274 --- /dev/null +++ b/apps/openmw/mwlua/localscripts.cpp @@ -0,0 +1,193 @@ +#include "localscripts.hpp" + +#include +#include + +#include "../mwmechanics/aicombat.hpp" +#include "../mwmechanics/aiescort.hpp" +#include "../mwmechanics/aifollow.hpp" +#include "../mwmechanics/aipackage.hpp" +#include "../mwmechanics/aipursue.hpp" +#include "../mwmechanics/aisequence.hpp" +#include "../mwmechanics/aitravel.hpp" +#include "../mwmechanics/aiwander.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/ptr.hpp" + +#include "context.hpp" +#include "worldview.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + + void LocalScripts::initializeSelfPackage(const Context& context) + { + using ActorControls = MWBase::LuaManager::ActorControls; + sol::usertype controls = context.mLua->sol().new_usertype("ActorControls"); + +#define CONTROL(TYPE, FIELD) \ + sol::property([](const ActorControls& c) { return c.FIELD; }, \ + [](ActorControls& c, const TYPE& v) { \ + c.FIELD = v; \ + c.mChanged = true; \ + }) + controls["movement"] = CONTROL(float, mMovement); + controls["sideMovement"] = CONTROL(float, mSideMovement); + controls["pitchChange"] = CONTROL(float, mPitchChange); + controls["yawChange"] = CONTROL(float, mYawChange); + controls["run"] = CONTROL(bool, mRun); + controls["sneak"] = CONTROL(bool, mSneak); + controls["jump"] = CONTROL(bool, mJump); + controls["use"] = CONTROL(int, mUse); +#undef CONTROL + + sol::usertype selfAPI = context.mLua->sol().new_usertype( + "SelfObject", sol::base_classes, sol::bases()); + selfAPI[sol::meta_function::to_string] + = [](SelfObject& self) { return "openmw.self[" + self.toString() + "]"; }; + selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); + selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); + selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; + selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; + + using AiPackage = MWMechanics::AiPackage; + sol::usertype aiPackage = context.mLua->sol().new_usertype("AiPackage"); + aiPackage["type"] = sol::readonly_property([](const AiPackage& p) -> std::string_view { + switch (p.getTypeId()) + { + case MWMechanics::AiPackageTypeId::Wander: + return "Wander"; + case MWMechanics::AiPackageTypeId::Travel: + return "Travel"; + case MWMechanics::AiPackageTypeId::Escort: + return "Escort"; + case MWMechanics::AiPackageTypeId::Follow: + return "Follow"; + case MWMechanics::AiPackageTypeId::Activate: + return "Activate"; + case MWMechanics::AiPackageTypeId::Combat: + return "Combat"; + case MWMechanics::AiPackageTypeId::Pursue: + return "Pursue"; + case MWMechanics::AiPackageTypeId::AvoidDoor: + return "AvoidDoor"; + case MWMechanics::AiPackageTypeId::Face: + return "Face"; + case MWMechanics::AiPackageTypeId::Breathe: + return "Breathe"; + case MWMechanics::AiPackageTypeId::Cast: + return "Cast"; + default: + return "Unknown"; + } + }); + aiPackage["target"] = sol::readonly_property([](const AiPackage& p) -> sol::optional { + MWWorld::Ptr target = p.getTarget(); + if (target.isEmpty()) + return sol::nullopt; + else + return LObject(getId(target)); + }); + aiPackage["sideWithTarget"] = sol::readonly_property([](const AiPackage& p) { return p.sideWithTarget(); }); + aiPackage["destPosition"] = sol::readonly_property([](const AiPackage& p) { return p.getDestination(); }); + + selfAPI["_getActiveAiPackage"] = [](SelfObject& self) -> sol::optional> { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + if (ai.isEmpty()) + return sol::nullopt; + else + return *ai.begin(); + }; + selfAPI["_iterateAndFilterAiSequence"] = [](SelfObject& self, sol::function callback) { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + + ai.erasePackagesIf([&](auto& entry) { + bool keep = LuaUtil::call(callback, entry).template get(); + return !keep; + }); + }; + selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target, bool cancelOther) { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiCombat(target.ptr()), ptr, cancelOther); + }; + selfAPI["_startAiPursue"] = [](SelfObject& self, const LObject& target, bool cancelOther) { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiPursue(target.ptr()), ptr, cancelOther); + }; + selfAPI["_startAiFollow"] = [](SelfObject& self, const LObject& target, bool cancelOther) { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiFollow(target.ptr()), ptr, cancelOther); + }; + selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell, float duration, + const osg::Vec3f& dest, bool cancelOther) { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + // TODO: change AiEscort implementation to accept ptr instead of a non-unique refId. + const ESM::RefId& refId = target.ptr().getCellRef().getRefId(); + int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); + auto* esmCell = cell.mStore->getCell(); + if (esmCell->isExterior()) + ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr, + cancelOther); + else + ai.stack(MWMechanics::AiEscort( + refId, esmCell->getNameId(), gameHoursDuration, dest.x(), dest.y(), dest.z(), false), + ptr, cancelOther); + }; + selfAPI["_startAiWander"] = [](SelfObject& self, int distance, float duration, bool cancelOther) { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); + ai.stack(MWMechanics::AiWander(distance, gameHoursDuration, 0, {}, false), ptr, cancelOther); + }; + selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target, bool cancelOther) { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), false), ptr, cancelOther); + }; + } + + LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj) + : LuaUtil::ScriptsContainer(lua, "L" + obj.id().toString()) + , mData(obj) + { + this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); + registerEngineHandlers( + { &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers }); + } + + void LocalScripts::setActive(bool active) + { + mData.mIsActive = active; + if (active) + callEngineHandlers(mOnActiveHandlers); + else + callEngineHandlers(mOnInactiveHandlers); + } + + void LocalScripts::applyStatsCache() + { + const auto& ptr = mData.ptr(); + for (auto& [stat, value] : mData.mStatsCache) + stat(ptr, value); + mData.mStatsCache.clear(); + } +} diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp new file mode 100644 index 000000000..ea5432169 --- /dev/null +++ b/apps/openmw/mwlua/localscripts.hpp @@ -0,0 +1,88 @@ +#ifndef MWLUA_LOCALSCRIPTS_H +#define MWLUA_LOCALSCRIPTS_H + +#include +#include +#include +#include + +#include +#include + +#include "../mwbase/luamanager.hpp" + +#include "object.hpp" + +namespace MWLua +{ + struct Context; + + struct SelfObject : public LObject + { + class CachedStat + { + public: + using Index = std::variant; + using Setter = void (*)(const Index&, std::string_view, const MWWorld::Ptr&, const sol::object&); + + CachedStat(Setter setter, Index index, std::string_view prop) + : mSetter(setter) + , mIndex(std::move(index)) + , mProp(std::move(prop)) + { + } + + void operator()(const MWWorld::Ptr& ptr, const sol::object& object) const + { + mSetter(mIndex, mProp, ptr, object); + } + + bool operator<(const CachedStat& other) const + { + return std::tie(mSetter, mIndex, mProp) < std::tie(other.mSetter, other.mIndex, other.mProp); + } + + private: + Setter mSetter; // Function that updates a stat's property + Index mIndex; // Optional index to disambiguate the stat + std::string_view mProp; // Name of the stat's property + }; + + SelfObject(const LObject& obj) + : LObject(obj) + , mIsActive(false) + { + } + MWBase::LuaManager::ActorControls mControls; + std::map mStatsCache; + bool mIsActive; + }; + + class LocalScripts : public LuaUtil::ScriptsContainer + { + public: + static void initializeSelfPackage(const Context&); + LocalScripts(LuaUtil::LuaState* lua, const LObject& obj); + + MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; } + const MWWorld::Ptr& getPtrOrEmpty() const { return mData.ptrOrEmpty(); } + + void setActive(bool active); + void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); } + void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); } + + void applyStatsCache(); + + protected: + SelfObject mData; + + private: + EngineHandlerList mOnActiveHandlers{ "onActive" }; + EngineHandlerList mOnInactiveHandlers{ "onInactive" }; + EngineHandlerList mOnConsumeHandlers{ "onConsume" }; + EngineHandlerList mOnActivatedHandlers{ "onActivated" }; + }; + +} + +#endif // MWLUA_LOCALSCRIPTS_H diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp new file mode 100644 index 000000000..aa14c022e --- /dev/null +++ b/apps/openmw/mwlua/luabindings.cpp @@ -0,0 +1,326 @@ +#include "luabindings.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" +#include "../mwworld/action.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/scene.hpp" +#include "../mwworld/store.hpp" + +#include "luaevents.hpp" +#include "luamanagerimp.hpp" +#include "mwscriptbindings.hpp" +#include "worldview.hpp" + +#include "camerabindings.hpp" +#include "cellbindings.hpp" +#include "debugbindings.hpp" +#include "inputbindings.hpp" +#include "magicbindings.hpp" +#include "nearbybindings.hpp" +#include "objectbindings.hpp" +#include "postprocessingbindings.hpp" +#include "types/types.hpp" +#include "uibindings.hpp" + +namespace MWLua +{ + struct CellsStore + { + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + + static void addTimeBindings(sol::table& api, const Context& context, bool global) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + + api["getSimulationTime"] = [world = context.mWorldView]() { return world->getSimulationTime(); }; + api["getSimulationTimeScale"] = [world]() { return world->getSimulationTimeScale(); }; + api["getGameTime"] = [world = context.mWorldView]() { return world->getGameTime(); }; + api["getGameTimeScale"] = [world = context.mWorldView]() { return world->getGameTimeScale(); }; + api["isWorldPaused"] = [world = context.mWorldView]() { return world->isPaused(); }; + api["getRealTime"] = []() { + return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); + }; + + if (!global) + return; + + api["setGameTimeScale"] = [world = context.mWorldView](double scale) { world->setGameTimeScale(scale); }; + + api["setSimulationTimeScale"] = [context, world](float scale) { + context.mLuaManager->addAction([scale, world] { world->setSimulationTimeScale(scale); }); + }; + + // TODO: Ability to pause/resume world from Lua (needed for UI dehardcoding) + // api["pause"] = []() {}; + // api["resume"] = []() {}; + } + + static sol::table initContentFilesBindings(sol::state_view& lua) + { + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + sol::table list(lua, sol::create); + for (size_t i = 0; i < contentList.size(); ++i) + list[i + 1] = Misc::StringUtils::lowerCase(contentList[i]); + sol::table res(lua, sol::create); + res["list"] = LuaUtil::makeReadOnly(list); + res["indexOf"] = [&contentList](std::string_view contentFile) -> sol::optional { + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return i + 1; + return sol::nullopt; + }; + res["has"] = [&contentList](std::string_view contentFile) -> bool { + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return true; + return false; + }; + return LuaUtil::makeReadOnly(res); + } + + static sol::table initCorePackage(const Context& context) + { + auto* lua = context.mLua; + sol::table api(lua->sol(), sol::create); + api["API_REVISION"] = 41; + api["quit"] = [lua]() { + Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); + MWBase::Environment::get().getStateManager()->requestQuit(); + }; + api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { + context.mLuaEvents->addGlobalEvent( + { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); + }; + api["contentFiles"] = initContentFilesBindings(lua->sol()); + api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return ESM::RefId(ESM::FormIdRefId(ESM::FormId{ index, int(i) })).serializeText(); + throw std::runtime_error("Content file not found: " + std::string(contentFile)); + }; + addTimeBindings(api, context, false); + api["magic"] = initCoreMagicBindings(context); + api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); + const MWWorld::Store* gmstStore + = &MWBase::Environment::get().getESMStore()->get(); + api["getGMST"] = [lua = context.mLua, gmstStore](const std::string& setting) -> sol::object { + const ESM::GameSetting* gmst = gmstStore->search(setting); + if (gmst == nullptr) + return sol::nil; + const ESM::Variant& value = gmst->mValue; + switch (value.getType()) + { + case ESM::VT_Float: + return sol::make_object(lua->sol(), value.getFloat()); + case ESM::VT_Short: + case ESM::VT_Long: + case ESM::VT_Int: + return sol::make_object(lua->sol(), value.getInteger()); + case ESM::VT_String: + return sol::make_object(lua->sol(), value.getString()); + case ESM::VT_Unknown: + case ESM::VT_None: + break; + } + return sol::nil; + }; + + sol::table skill(context.mLua->sol(), sol::create); + api["SKILL"] = LuaUtil::makeStrictReadOnly(skill); + for (int id = 0; id < ESM::Skill::Length; ++id) + skill[ESM::Skill::sSkillNames[id]] = Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id]); + + sol::table attribute(context.mLua->sol(), sol::create); + api["ATTRIBUTE"] = LuaUtil::makeStrictReadOnly(attribute); + for (int id = 0; id < ESM::Attribute::Length; ++id) + attribute[ESM::Attribute::sAttributeNames[id]] + = Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[id]); + + return LuaUtil::makeReadOnly(api); + } + + static void addCellGetters(sol::table& api, const Context& context) + { + api["getCellByName"] = [](std::string_view name) { + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false) }; + }; + api["getExteriorCell"] = [](int x, int y, sol::object cellOrName) { + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; + return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior( + ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) }; + }; + + const MWWorld::Store* cells3Store = &MWBase::Environment::get().getESMStore()->get(); + const MWWorld::Store* cells4Store = &MWBase::Environment::get().getESMStore()->get(); + sol::usertype cells = context.mLua->sol().new_usertype("Cells"); + cells[sol::meta_function::length] + = [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); }; + cells[sol::meta_function::index] = [cells3Store, cells4Store](const CellsStore&, size_t index) -> GCell { + index--; // Translate from Lua's 1-based indexing. + if (index < cells3Store->getSize()) + { + const ESM::Cell* cellRecord = cells3Store->at(index); + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( + cellRecord->mId, /*forceLoad=*/false) }; + } + else + { + const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize()); + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( + cellRecord->mId, /*forceLoad=*/false) }; + } + }; + cells[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get(); + cells[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get(); + api["cells"] = CellsStore{}; + } + + static sol::table initWorldPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + WorldView* worldView = context.mWorldView; + addTimeBindings(api, context, true); + addCellGetters(api, context); + api["mwscript"] = initMWScriptBindings(context); + api["activeActors"] = GObjectList{ worldView->getActorsInScene() }; + api["players"] = GObjectList{ worldView->getPlayers() }; + api["createObject"] = [](std::string_view recordId, sol::optional count) -> GObject { + // Doesn't matter which cell to use because the new object will be in disabled state. + MWWorld::CellStore* cell = MWBase::Environment::get().getWorldScene()->getCurrentCell(); + + MWWorld::ManualRef mref(*MWBase::Environment::get().getESMStore(), ESM::RefId::deserializeText(recordId)); + const MWWorld::Ptr& ptr = mref.getPtr(); + ptr.getRefData().disable(); + MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *cell, count.value_or(1)); + return GObject(newPtr); + }; + api["getObjectByFormId"] = [](std::string_view formIdStr) -> GObject { + ESM::RefId refId = ESM::RefId::deserializeText(formIdStr); + if (!refId.is()) + throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId"); + return GObject(refId.getIf()->getValue()); + }; + + // Creates a new record in the world database. + api["createRecord"] = sol::overload( + [](const ESM::Activator& activator) -> const ESM::Activator* { + return MWBase::Environment::get().getESMStore()->insert(activator); + }, + [](const ESM::Armor& armor) -> const ESM::Armor* { + return MWBase::Environment::get().getESMStore()->insert(armor); + }, + [](const ESM::Clothing& clothing) -> const ESM::Clothing* { + return MWBase::Environment::get().getESMStore()->insert(clothing); + }, + [](const ESM::Book& book) -> const ESM::Book* { + return MWBase::Environment::get().getESMStore()->insert(book); + }, + [](const ESM::Miscellaneous& misc) -> const ESM::Miscellaneous* { + return MWBase::Environment::get().getESMStore()->insert(misc); + }, + [](const ESM::Potion& potion) -> const ESM::Potion* { + return MWBase::Environment::get().getESMStore()->insert(potion); + }, + [](const ESM::Weapon& weapon) -> const ESM::Weapon* { + return MWBase::Environment::get().getESMStore()->insert(weapon); + }); + + api["_runStandardActivationAction"] = [context](const GObject& object, const GObject& actor) { + context.mLuaManager->addAction( + [object, actor] { + const MWWorld::Ptr& objPtr = object.ptr(); + const MWWorld::Ptr& actorPtr = actor.ptr(); + objPtr.getClass().activate(objPtr, actorPtr)->execute(actorPtr); + }, + "_runStandardActivationAction"); + }; + + return LuaUtil::makeReadOnly(api); + } + + std::map initCommonPackages(const Context& context) + { + sol::state_view lua = context.mLua->sol(); + WorldView* w = context.mWorldView; + return { + { "openmw.async", + LuaUtil::getAsyncPackageInitializer( + lua, [w] { return w->getSimulationTime(); }, [w] { return w->getGameTime(); }) }, + { "openmw.core", initCorePackage(context) }, + { "openmw.types", initTypesPackage(context) }, + { "openmw.util", LuaUtil::initUtilPackage(lua) }, + }; + } + + std::map initGlobalPackages(const Context& context) + { + initObjectBindingsForGlobalScripts(context); + initCellBindingsForGlobalScripts(context); + return { + { "openmw.world", initWorldPackage(context) }, + }; + } + + std::map initLocalPackages(const Context& context) + { + initObjectBindingsForLocalScripts(context); + initCellBindingsForLocalScripts(context); + LocalScripts::initializeSelfPackage(context); + return { + { "openmw.nearby", initNearbyPackage(context) }, + }; + } + + std::map initPlayerPackages(const Context& context) + { + return { + { "openmw.camera", initCameraPackage(context.mLua->sol()) }, + { "openmw.debug", initDebugPackage(context) }, + { "openmw.input", initInputPackage(context) }, + { "openmw.postprocessing", initPostprocessingPackage(context) }, + { "openmw.ui", initUserInterfacePackage(context) }, + }; + } + +} diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp new file mode 100644 index 000000000..e5d481d1e --- /dev/null +++ b/apps/openmw/mwlua/luabindings.hpp @@ -0,0 +1,25 @@ +#ifndef MWLUA_LUABINDINGS_H +#define MWLUA_LUABINDINGS_H + +#include +#include +#include + +#include "context.hpp" + +namespace MWLua +{ + // Initialize Lua packages that are available for all scripts. + std::map initCommonPackages(const Context&); + + // Initialize Lua packages that are available only for global scripts. + std::map initGlobalPackages(const Context&); + + // Initialize Lua packages that are available only for local scripts (including player scripts). + std::map initLocalPackages(const Context&); + + // Initialize Lua packages that are available only for local scripts on the player. + std::map initPlayerPackages(const Context&); +} + +#endif // MWLUA_LUABINDINGS_H diff --git a/apps/openmw/mwlua/luaevents.cpp b/apps/openmw/mwlua/luaevents.cpp new file mode 100644 index 000000000..b036fea3b --- /dev/null +++ b/apps/openmw/mwlua/luaevents.cpp @@ -0,0 +1,107 @@ +#include "luaevents.hpp" + +#include + +#include +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/worldmodel.hpp" + +#include "globalscripts.hpp" +#include "localscripts.hpp" + +namespace MWLua +{ + + void LuaEvents::clear() + { + mGlobalEventBatch.clear(); + mLocalEventBatch.clear(); + mNewGlobalEventBatch.clear(); + mNewLocalEventBatch.clear(); + } + + void LuaEvents::finalizeEventBatch() + { + mNewGlobalEventBatch.swap(mGlobalEventBatch); + mNewLocalEventBatch.swap(mLocalEventBatch); + mNewGlobalEventBatch.clear(); + mNewLocalEventBatch.clear(); + } + + void LuaEvents::callEventHandlers() + { + for (const Global& e : mGlobalEventBatch) + mGlobalScripts.receiveEvent(e.mEventName, e.mEventData); + mGlobalEventBatch.clear(); + for (const Local& e : mLocalEventBatch) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorldModel()->getPtr(e.mDest); + LocalScripts* scripts = ptr.isEmpty() ? nullptr : ptr.getRefData().getLuaScripts(); + if (scripts) + scripts->receiveEvent(e.mEventName, e.mEventData); + else + Log(Debug::Debug) << "Ignored event " << e.mEventName << " to L" << e.mDest.toString() + << ". Object not found or has no attached scripts"; + } + mLocalEventBatch.clear(); + } + + template + static void saveEvent(ESM::ESMWriter& esm, const ESM::RefNum& dest, const Event& event) + { + esm.writeHNString("LUAE", event.mEventName); + esm.writeFormId(dest, true); + if (!event.mEventData.empty()) + saveLuaBinaryData(esm, event.mEventData); + } + + void LuaEvents::load(lua_State* lua, ESM::ESMReader& esm, const std::map& contentFileMapping, + const LuaUtil::UserdataSerializer* serializer) + { + clear(); + while (esm.isNextSub("LUAE")) + { + std::string name = esm.getHString(); + ESM::RefNum dest = esm.getFormId(true); + std::string data = loadLuaBinaryData(esm); + try + { + data = LuaUtil::serialize(LuaUtil::deserialize(lua, data, serializer), serializer); + } + catch (std::exception& e) + { + Log(Debug::Error) << "loadEvent: invalid event data: " << e.what(); + } + if (dest.isSet()) + { + auto it = contentFileMapping.find(dest.mContentFile); + if (it != contentFileMapping.end()) + dest.mContentFile = it->second; + mLocalEventBatch.push_back({ dest, std::move(name), std::move(data) }); + } + else + mGlobalEventBatch.push_back({ std::move(name), std::move(data) }); + } + } + + void LuaEvents::save(ESM::ESMWriter& esm) const + { + // Used as a marker of a global event. + constexpr ESM::RefNum globalId; + + for (const Global& e : mGlobalEventBatch) + saveEvent(esm, globalId, e); + for (const Global& e : mNewGlobalEventBatch) + saveEvent(esm, globalId, e); + for (const Local& e : mLocalEventBatch) + saveEvent(esm, e.mDest, e); + for (const Local& e : mNewLocalEventBatch) + saveEvent(esm, e.mDest, e); + } + +} diff --git a/apps/openmw/mwlua/luaevents.hpp b/apps/openmw/mwlua/luaevents.hpp new file mode 100644 index 000000000..5eeae4653 --- /dev/null +++ b/apps/openmw/mwlua/luaevents.hpp @@ -0,0 +1,68 @@ +#ifndef MWLUA_LUAEVENTS_H +#define MWLUA_LUAEVENTS_H + +#include +#include + +#include // defines RefNum that is used as a unique id + +struct lua_State; + +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + +namespace LuaUtil +{ + class UserdataSerializer; +} + +namespace MWLua +{ + + class GlobalScripts; + + class LuaEvents + { + public: + explicit LuaEvents(GlobalScripts& globalScripts) + : mGlobalScripts(globalScripts) + { + } + + struct Global + { + std::string mEventName; + std::string mEventData; + }; + struct Local + { + ESM::RefNum mDest; + std::string mEventName; + std::string mEventData; + }; + + void addGlobalEvent(Global event) { mNewGlobalEventBatch.push_back(std::move(event)); } + void addLocalEvent(Local event) { mNewLocalEventBatch.push_back(std::move(event)); } + + void clear(); + void finalizeEventBatch(); + void callEventHandlers(); + + void load(lua_State* lua, ESM::ESMReader& esm, const std::map& contentFileMapping, + const LuaUtil::UserdataSerializer* serializer); + void save(ESM::ESMWriter& esm) const; + + private: + GlobalScripts& mGlobalScripts; + std::vector mNewGlobalEventBatch; + std::vector mNewLocalEventBatch; + std::vector mGlobalEventBatch; + std::vector mLocalEventBatch; + }; + +} + +#endif // MWLUA_LUAEVENTS_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp new file mode 100644 index 000000000..285a378ea --- /dev/null +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -0,0 +1,624 @@ +#include "luamanagerimp.hpp" + +#include + +#include + +#include "sol/state_view.hpp" + +#include + +#include +#include +#include + +#include + +#include + +#include +#include + +#include "../mwbase/windowmanager.hpp" + +#include "../mwrender/postprocessor.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/scene.hpp" + +#include "luabindings.hpp" +#include "playerscripts.hpp" +#include "types/types.hpp" +#include "userdataserializer.hpp" + +namespace MWLua +{ + + static LuaUtil::LuaStateSettings createLuaStateSettings() + { + if (!Settings::lua().mLuaProfiler) + LuaUtil::LuaState::disableProfiler(); + return { .mInstructionLimit = Settings::lua().mInstructionLimitPerCall, + .mMemoryLimit = Settings::lua().mMemoryLimit, + .mSmallAllocMaxSize = Settings::lua().mSmallAllocMaxSize, + .mLogMemoryUsage = Settings::lua().mLogMemoryUsage }; + } + + LuaManager::LuaManager(const VFS::Manager* vfs, const std::filesystem::path& libsDir) + : mLua(vfs, &mConfiguration, createLuaStateSettings()) + { + Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); + mLua.addInternalLibSearchPath(libsDir); + + mGlobalSerializer = createUserdataSerializer(false); + mLocalSerializer = createUserdataSerializer(true); + mGlobalLoader = createUserdataSerializer(false, &mContentFileMapping); + mLocalLoader = createUserdataSerializer(true, &mContentFileMapping); + + mGlobalScripts.setSerializer(mGlobalSerializer.get()); + } + + void LuaManager::initConfiguration() + { + mConfiguration.init(MWBase::Environment::get().getESMStore()->getLuaScriptsCfg()); + Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):"; + for (size_t i = 0; i < mConfiguration.size(); ++i) + Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]); + mGlobalScripts.setAutoStartConf(mConfiguration.getGlobalConf()); + } + + void LuaManager::init() + { + Context context; + context.mIsGlobal = true; + context.mLuaManager = this; + context.mLua = &mLua; + context.mWorldView = &mWorldView; + context.mLuaEvents = &mLuaEvents; + context.mSerializer = mGlobalSerializer.get(); + + Context localContext = context; + localContext.mIsGlobal = false; + localContext.mSerializer = mLocalSerializer.get(); + + for (const auto& [name, package] : initCommonPackages(context)) + mLua.addCommonPackage(name, package); + for (const auto& [name, package] : initGlobalPackages(context)) + mGlobalScripts.addPackage(name, package); + + mLocalPackages = initLocalPackages(localContext); + mPlayerPackages = initPlayerPackages(localContext); + mPlayerPackages.insert(mLocalPackages.begin(), mLocalPackages.end()); + + LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); + mGlobalScripts.addPackage( + "openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage)); + mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage); + mPlayerPackages["openmw.storage"] + = LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage); + + initConfiguration(); + mInitialized = true; + } + + void LuaManager::loadPermanentStorage(const std::filesystem::path& userConfigPath) + { + const auto globalPath = userConfigPath / "global_storage.bin"; + const auto playerPath = userConfigPath / "player_storage.bin"; + if (std::filesystem::exists(globalPath)) + mGlobalStorage.load(globalPath); + if (std::filesystem::exists(playerPath)) + mPlayerStorage.load(playerPath); + } + + void LuaManager::savePermanentStorage(const std::filesystem::path& userConfigPath) + { + mGlobalStorage.save(userConfigPath / "global_storage.bin"); + mPlayerStorage.save(userConfigPath / "player_storage.bin"); + } + + void LuaManager::update() + { + if (Settings::lua().mGcStepsPerFrame > 0) + lua_gc(mLua.sol(), LUA_GCSTEP, Settings::lua().mGcStepsPerFrame); + + if (mPlayer.isEmpty()) + return; // The game is not started yet. + + float frameDuration = MWBase::Environment::get().getFrameDuration(); + + MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (!(getId(mPlayer) == getId(newPlayerPtr))) + throw std::logic_error("Player RefNum was changed unexpectedly"); + if (!mPlayer.isInCell() || !newPlayerPtr.isInCell() || mPlayer.getCell() != newPlayerPtr.getCell()) + { + mPlayer = newPlayerPtr; // player was moved to another cell, update ptr in registry + MWBase::Environment::get().getWorldModel()->registerPtr(mPlayer); + } + + mWorldView.update(); + + std::erase_if(mActiveLocalScripts, [](const LocalScripts* l) { + return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().getRefData().isDeleted(); + }); + + mGlobalScripts.statsNextFrame(); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->statsNextFrame(); + + mLuaEvents.finalizeEventBatch(); + + if (!mWorldView.isPaused()) + { // Update time and process timers + double simulationTime = mWorldView.getSimulationTime() + frameDuration; + mWorldView.setSimulationTime(simulationTime); + double gameTime = mWorldView.getGameTime(); + + mGlobalScripts.processTimers(simulationTime, gameTime); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->processTimers(simulationTime, gameTime); + } + + // Run event handlers for events that were sent before `finalizeEventBatch`. + mLuaEvents.callEventHandlers(); + + // Run queued callbacks + for (CallbackWithData& c : mQueuedCallbacks) + c.mCallback.tryCall(c.mArg); + mQueuedCallbacks.clear(); + + // Run engine handlers + mEngineEvents.callEngineHandlers(); + if (!mWorldView.isPaused()) + { + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->update(frameDuration); + mGlobalScripts.update(frameDuration); + } + } + + void LuaManager::synchronizedUpdate() + { + if (mPlayer.isEmpty()) + return; // The game is not started yet. + + // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. + mProcessingInputEvents = true; + PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); + if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) + { + for (const auto& event : mInputEvents) + playerScripts->processInputEvent(event); + } + mInputEvents.clear(); + if (playerScripts) + playerScripts->onFrame(mWorldView.isPaused() ? 0.0 : MWBase::Environment::get().getFrameDuration()); + mProcessingInputEvents = false; + + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); + for (const std::string& message : mUIMessages) + windowManager->messageBox(message); + mUIMessages.clear(); + for (auto& [msg, color] : mInGameConsoleMessages) + windowManager->printToConsole(msg, "#" + color.toHex()); + mInGameConsoleMessages.clear(); + + for (DelayedAction& action : mActionQueue) + action.apply(); + mActionQueue.clear(); + + if (mTeleportPlayerAction) + mTeleportPlayerAction->apply(); + mTeleportPlayerAction.reset(); + } + + void LuaManager::clear() + { + LuaUi::clearUserInterface(); + mUiResourceManager.clear(); + MWBase::Environment::get().getWindowManager()->setConsoleMode(""); + MWBase::Environment::get().getWorld()->getPostProcessor()->disableDynamicShaders(); + mActiveLocalScripts.clear(); + mLuaEvents.clear(); + mEngineEvents.clear(); + mInputEvents.clear(); + mWorldView.clear(); + mGlobalScripts.removeAllScripts(); + mGlobalScriptsStarted = false; + if (!mPlayer.isEmpty()) + { + mPlayer.getCellRef().unsetRefNum(); + mPlayer.getRefData().setLuaScripts(nullptr); + mPlayer = MWWorld::Ptr(); + } + mGlobalStorage.clearTemporaryAndRemoveCallbacks(); + mPlayerStorage.clearTemporaryAndRemoveCallbacks(); + for (int i = 0; i < 5; ++i) + lua_gc(mLua.sol(), LUA_GCCOLLECT, 0); + } + + void LuaManager::setupPlayer(const MWWorld::Ptr& ptr) + { + if (!mInitialized) + return; + if (!mPlayer.isEmpty()) + throw std::logic_error("Player is initialized twice"); + mWorldView.objectAddedToScene(ptr); + mWorldView.setPlayer(ptr); + mPlayer = ptr; + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts) + { + localScripts = createLocalScripts(ptr); + localScripts->addAutoStartedScripts(); + } + mActiveLocalScripts.insert(localScripts); + mEngineEvents.addToQueue(EngineEvents::OnActive{ getId(ptr) }); + } + + void LuaManager::newGameStarted() + { + mInputEvents.clear(); + mGlobalScripts.addAutoStartedScripts(); + mGlobalScriptsStarted = true; + mEngineEvents.addToQueue(EngineEvents::OnNewGame{}); + } + + void LuaManager::gameLoaded() + { + if (!mGlobalScriptsStarted) + mGlobalScripts.addAutoStartedScripts(); + mGlobalScriptsStarted = true; + } + + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) + { + mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. + mEngineEvents.addToQueue(EngineEvents::OnActive{ getId(ptr) }); + + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts) + { + LuaUtil::ScriptIdsWithInitializationData autoStartConf + = mConfiguration.getLocalConf(getLiveCellRefType(ptr.mRef), ptr.getCellRef().getRefId(), getId(ptr)); + if (!autoStartConf.empty()) + { + localScripts = createLocalScripts(ptr, std::move(autoStartConf)); + localScripts->addAutoStartedScripts(); // TODO: put to a queue and apply on next `update()` + } + } + if (localScripts) + mActiveLocalScripts.insert(localScripts); + } + + void LuaManager::objectRemovedFromScene(const MWWorld::Ptr& ptr) + { + mWorldView.objectRemovedFromScene(ptr); + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (localScripts) + { + mActiveLocalScripts.erase(localScripts); + if (!MWBase::Environment::get().getWorldModel()->getPtr(getId(ptr)).isEmpty()) + mEngineEvents.addToQueue(EngineEvents::OnInactive{ getId(ptr) }); + } + } + + MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const + { + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts) + return nullptr; + return localScripts->getActorControls(); + } + + void LuaManager::addCustomLocalScript(const MWWorld::Ptr& ptr, int scriptId, std::string_view initData) + { + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts) + { + localScripts = createLocalScripts(ptr); + localScripts->addAutoStartedScripts(); + if (ptr.isInCell() && MWBase::Environment::get().getWorldScene()->isCellActive(*ptr.getCell())) + mActiveLocalScripts.insert(localScripts); + } + localScripts->addCustomScript(scriptId, initData); + } + + LocalScripts* LuaManager::createLocalScripts( + const MWWorld::Ptr& ptr, std::optional autoStartConf) + { + assert(mInitialized); + std::shared_ptr scripts; + const uint32_t type = getLiveCellRefType(ptr.mRef); + if (type == ESM::REC_STAT) + throw std::runtime_error("Lua scripts on static objects are not allowed"); + else if (type == ESM::REC_INTERNAL_PLAYER) + { + scripts = std::make_shared(&mLua, LObject(getId(ptr))); + scripts->setAutoStartConf(mConfiguration.getPlayerConf()); + for (const auto& [name, package] : mPlayerPackages) + scripts->addPackage(name, package); + } + else + { + scripts = std::make_shared(&mLua, LObject(getId(ptr))); + if (!autoStartConf.has_value()) + autoStartConf = mConfiguration.getLocalConf(type, ptr.getCellRef().getRefId(), getId(ptr)); + scripts->setAutoStartConf(std::move(*autoStartConf)); + for (const auto& [name, package] : mLocalPackages) + scripts->addPackage(name, package); + } + scripts->setSerializer(mLocalSerializer.get()); + + MWWorld::RefData& refData = ptr.getRefData(); + refData.setLuaScripts(std::move(scripts)); + return refData.getLuaScripts(); + } + + void LuaManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) + { + writer.startRecord(ESM::REC_LUAM); + + mWorldView.save(writer); + ESM::LuaScripts globalScripts; + mGlobalScripts.save(globalScripts); + globalScripts.save(writer); + mLuaEvents.save(writer); + + writer.endRecord(ESM::REC_LUAM); + } + + void LuaManager::readRecord(ESM::ESMReader& reader, uint32_t type) + { + if (type != ESM::REC_LUAM) + throw std::runtime_error("ESM::REC_LUAM is expected"); + + mWorldView.load(reader); + ESM::LuaScripts globalScripts; + globalScripts.load(reader); + mLuaEvents.load(mLua.sol(), reader, mContentFileMapping, mGlobalLoader.get()); + + mGlobalScripts.setSavedDataDeserializer(mGlobalLoader.get()); + mGlobalScripts.load(globalScripts); + mGlobalScriptsStarted = true; + } + + void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) + { + if (ptr.getRefData().getLuaScripts()) + ptr.getRefData().getLuaScripts()->save(data); + else + data.mScripts.clear(); + } + + void LuaManager::loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) + { + if (data.mScripts.empty()) + { + if (ptr.getRefData().getLuaScripts()) + ptr.getRefData().setLuaScripts(nullptr); + return; + } + + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + LocalScripts* scripts = createLocalScripts(ptr); + + scripts->setSerializer(mLocalSerializer.get()); + scripts->setSavedDataDeserializer(mLocalLoader.get()); + scripts->load(data); + + // LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered. + MWBase::Environment::get().getWorldModel()->deregisterPtr(ptr); + } + + void LuaManager::reloadAllScripts() + { + Log(Debug::Info) << "Reload Lua"; + + LuaUi::clearUserInterface(); + MWBase::Environment::get().getWindowManager()->setConsoleMode(""); + MWBase::Environment::get().getL10nManager()->dropCache(); + mUiResourceManager.clear(); + mLua.dropScriptCache(); + initConfiguration(); + + { // Reload global scripts + mGlobalScripts.setSavedDataDeserializer(mGlobalSerializer.get()); + ESM::LuaScripts data; + mGlobalScripts.save(data); + mGlobalScripts.load(data); + } + + for (const auto& [id, ptr] : MWBase::Environment::get().getWorldModel()->getPtrRegistryView()) + { // Reload local scripts + LocalScripts* scripts = ptr.getRefData().getLuaScripts(); + if (scripts == nullptr) + continue; + scripts->setSavedDataDeserializer(mLocalSerializer.get()); + ESM::LuaScripts data; + scripts->save(data); + scripts->load(data); + } + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->setActive(true); + } + + void LuaManager::handleConsoleCommand( + const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) + { + PlayerScripts* playerScripts = nullptr; + if (!mPlayer.isEmpty()) + playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); + if (!playerScripts) + { + MWBase::Environment::get().getWindowManager()->printToConsole( + "You must enter a game session to run Lua commands\n", MWBase::WindowManager::sConsoleColor_Error); + return; + } + sol::object selected = sol::nil; + if (!selectedPtr.isEmpty()) + selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr))); + if (!playerScripts->consoleCommand(consoleMode, command, selected)) + MWBase::Environment::get().getWindowManager()->printToConsole( + "No Lua handlers for console\n", MWBase::WindowManager::sConsoleColor_Error); + } + + LuaManager::DelayedAction::DelayedAction(LuaUtil::LuaState* state, std::function fn, std::string_view name) + : mFn(std::move(fn)) + , mName(name) + { + if (Settings::lua().mLuaDebug) + mCallerTraceback = state->debugTraceback(); + } + + void LuaManager::DelayedAction::apply() const + { + try + { + mFn(); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Error in DelayedAction " << mName << ": " << e.what(); + + if (mCallerTraceback.empty()) + Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; + else + Log(Debug::Error) << "Caller " << mCallerTraceback; + } + } + + void LuaManager::addAction(std::function action, std::string_view name) + { + mActionQueue.emplace_back(&mLua, std::move(action), name); + } + + void LuaManager::addTeleportPlayerAction(std::function action) + { + mTeleportPlayerAction = DelayedAction(&mLua, std::move(action), "TeleportPlayer"); + } + + void LuaManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + stats.setAttribute(frameNumber, "Lua UsedMemory", mLua.getTotalMemoryUsage()); + } + + std::string LuaManager::formatResourceUsageStats() const + { + if (!LuaUtil::LuaState::isProfilerEnabled()) + return "Lua profiler is disabled"; + + std::stringstream out; + + constexpr int nameW = 50; + constexpr int valueW = 12; + + auto outMemSize = [&](int64_t bytes) { + constexpr int64_t limit = 10000; + out << std::right << std::setw(valueW - 3); + if (bytes < limit) + out << bytes << " B "; + else if (bytes < limit * 1024) + out << (bytes / 1024) << " KB"; + else if (bytes < limit * 1024 * 1024) + out << (bytes / (1024 * 1024)) << " MB"; + else + out << (bytes / (1024 * 1024 * 1024)) << " GB"; + }; + + const uint64_t smallAllocSize = Settings::lua().mSmallAllocMaxSize; + out << "Total memory usage:"; + outMemSize(mLua.getTotalMemoryUsage()); + out << "\n"; + out << "LuaUtil::ScriptsContainer count: " << LuaUtil::ScriptsContainer::getInstanceCount() << "\n"; + out << "\n"; + out << "small alloc max size = " << smallAllocSize << " (section [Lua] in settings.cfg)\n"; + out << "Smaller values give more information for the profiler, but increase performance overhead.\n"; + out << " Memory allocations <= " << smallAllocSize << " bytes:"; + outMemSize(mLua.getSmallAllocMemoryUsage()); + out << " (not tracked)\n"; + out << " Memory allocations > " << smallAllocSize << " bytes:"; + outMemSize(mLua.getTotalMemoryUsage() - mLua.getSmallAllocMemoryUsage()); + out << " (see the table below)\n\n"; + + using Stats = LuaUtil::ScriptsContainer::ScriptStats; + + std::vector activeStats; + mGlobalScripts.collectStats(activeStats); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->collectStats(activeStats); + + std::vector selectedStats; + MWWorld::Ptr selectedPtr = MWBase::Environment::get().getWindowManager()->getConsoleSelectedObject(); + LocalScripts* selectedScripts = nullptr; + if (!selectedPtr.isEmpty()) + { + selectedScripts = selectedPtr.getRefData().getLuaScripts(); + if (selectedScripts) + selectedScripts->collectStats(selectedStats); + out << "Profiled object (selected in the in-game console): " << selectedPtr.toString() << "\n"; + } + else + out << "No selected object. Use the in-game console to select an object for detailed profile.\n"; + out << "\n"; + + out << "Legend\n"; + out << " ops: Averaged number of Lua instruction per frame;\n"; + out << " memory: Aggregated size of Lua allocations > " << smallAllocSize << " bytes;\n"; + out << " [all]: Sum over all instances of each script;\n"; + out << " [active]: Sum over all active (i.e. currently in scene) instances of each script;\n"; + out << " [inactive]: Sum over all inactive instances of each script;\n"; + out << " [for selected object]: Only for the object that is selected in the console;\n"; + out << "\n"; + + out << std::left; + out << " " << std::setw(nameW + 2) << "*** Resource usage per script"; + out << std::right; + out << std::setw(valueW) << "ops"; + out << std::setw(valueW) << "memory"; + out << std::setw(valueW) << "memory"; + out << std::setw(valueW) << "ops"; + out << std::setw(valueW) << "memory"; + out << "\n"; + out << std::left << " " << std::setw(nameW + 2) << "[name]" << std::right; + out << std::setw(valueW) << "[all]"; + out << std::setw(valueW) << "[active]"; + out << std::setw(valueW) << "[inactive]"; + out << std::setw(valueW * 2) << "[for selected object]"; + out << "\n"; + + for (size_t i = 0; i < mConfiguration.size(); ++i) + { + bool isGlobal = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sGlobal; + + out << std::left; + out << " " << std::setw(nameW) << mConfiguration[i].mScriptPath; + if (mConfiguration[i].mScriptPath.size() > nameW) + out << "\n " << std::setw(nameW) << ""; // if path is too long, break line + out << std::right; + out << std::setw(valueW) << static_cast(activeStats[i].mAvgInstructionCount); + outMemSize(activeStats[i].mMemoryUsage); + outMemSize(mLua.getMemoryUsageByScriptIndex(i) - activeStats[i].mMemoryUsage); + + if (isGlobal) + out << std::setw(valueW * 2) << "NA (global script)"; + else if (selectedPtr.isEmpty()) + out << std::setw(valueW * 2) << "NA (not selected) "; + else if (!selectedScripts || !selectedScripts->hasScript(i)) + { + out << std::setw(valueW) << "-"; + outMemSize(selectedStats[i].mMemoryUsage); + } + else + { + out << std::setw(valueW) << static_cast(selectedStats[i].mAvgInstructionCount); + outMemSize(selectedStats[i].mMemoryUsage); + } + out << "\n"; + } + + return out.str(); + } +} diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp new file mode 100644 index 000000000..2b23182f4 --- /dev/null +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -0,0 +1,196 @@ +#ifndef MWLUA_LUAMANAGERIMP_H +#define MWLUA_LUAMANAGERIMP_H + +#include +#include +#include + +#include +#include +#include +#include + +#include "../mwbase/luamanager.hpp" + +#include "engineevents.hpp" +#include "globalscripts.hpp" +#include "localscripts.hpp" +#include "luaevents.hpp" +#include "object.hpp" +#include "worldview.hpp" + +namespace MWLua +{ + // \brief LuaManager is the central interface through which the engine invokes lua scripts. + // + // This class implements the interface defined in MWBase::LuaManager. + // In addition to the interface, this class exposes lower level interaction between the engine + // and the lua world. + class LuaManager : public MWBase::LuaManager + { + public: + LuaManager(const VFS::Manager* vfs, const std::filesystem::path& libsDir); + LuaManager(const LuaManager&) = delete; + LuaManager(LuaManager&&) = delete; + + // Called by engine.cpp when the environment is fully initialized. + void init(); + + void loadPermanentStorage(const std::filesystem::path& userConfigPath); + void savePermanentStorage(const std::filesystem::path& userConfigPath); + + // \brief Executes lua handlers. Defaults to running in parallel with OSG Cull. + // + // The OSG Cull is expensive enough that we have "free" time to + // execute Lua by running it in parallel. The Cull also does + // not modify the game state, meaning we can safely read state from Lua + // despite the concurrency. Only modifying the parts of the game state + // that affect the scene graph is forbidden. Such modifications must + // be queued for execution in synchronizedUpdate(). + // The parallelism can be turned off in the settings. + void update(); + + // \brief Executes latency-critical and scene graph related Lua logic. + // + // Called by engine.cpp from the main thread between InputManager and MechanicsManager updates. + // Can use the scene graph and applies the actions queued during update() + void synchronizedUpdate(); + + // Available everywhere through the MWBase::LuaManager interface. + // LuaManager queues these events and propagates to scripts on the next `update` call. + void newGameStarted() override; + void gameLoaded() override; + void objectAddedToScene(const MWWorld::Ptr& ptr) override; + void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; + void inputEvent(const InputEvent& event) override { mInputEvents.push_back(event); } + void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) override + { + mEngineEvents.addToQueue(EngineEvents::OnConsume{ getId(actor), getId(consumable) }); + } + void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override + { + mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); + } + void exteriorCreated(MWWorld::CellStore& cell) override + { + mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); + } + + MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; + + void clear() override; // should be called before loading game or starting a new game to reset internal state. + void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear". + + // Used only in Lua bindings + void addCustomLocalScript(const MWWorld::Ptr&, int scriptId, std::string_view initData); + void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } + void addInGameConsoleMessage(const std::string& msg, const Misc::Color& color) + { + mInGameConsoleMessages.push_back({ msg, color }); + } + + // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with + // OSG Cull), so we need to queue it and apply from the main thread. + void addAction(std::function action, std::string_view name = ""); + void addTeleportPlayerAction(std::function action); + + // Saving + void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; + void saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) override; + + // Loading from a save + void readRecord(ESM::ESMReader& reader, uint32_t type) override; + void loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) override; + void setContentFileMapping(const std::map& mapping) override { mContentFileMapping = mapping; } + + // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. + void reloadAllScripts() override; + + void handleConsoleCommand( + const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) override; + + // Used to call Lua callbacks from C++ + void queueCallback(LuaUtil::Callback callback, sol::main_object arg) + { + mQueuedCallbacks.push_back({ std::move(callback), std::move(arg) }); + } + + // Wraps Lua callback into an std::function. + // NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or + // any other Lua-related function is running. + template + std::function wrapLuaCallback(const LuaUtil::Callback& c) + { + return + [this, c](Arg arg) { this->queueCallback(c, sol::main_object(this->mLua.sol(), sol::in_place, arg)); }; + } + + LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } + + bool isProcessingInputEvents() const { return mProcessingInputEvents; } + + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + std::string formatResourceUsageStats() const override; + + private: + void initConfiguration(); + LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, + std::optional autoStartConf = std::nullopt); + + bool mInitialized = false; + bool mGlobalScriptsStarted = false; + bool mProcessingInputEvents = false; + LuaUtil::ScriptsConfiguration mConfiguration; + LuaUtil::LuaState mLua; + LuaUi::ResourceManager mUiResourceManager; + std::map mLocalPackages; + std::map mPlayerPackages; + + GlobalScripts mGlobalScripts{ &mLua }; + std::set mActiveLocalScripts; + WorldView mWorldView; + + MWWorld::Ptr mPlayer; + + LuaEvents mLuaEvents{ mGlobalScripts }; + EngineEvents mEngineEvents{ mGlobalScripts }; + std::vector mInputEvents; + + std::unique_ptr mGlobalSerializer; + std::unique_ptr mLocalSerializer; + + std::map mContentFileMapping; + std::unique_ptr mGlobalLoader; + std::unique_ptr mLocalLoader; + + struct CallbackWithData + { + LuaUtil::Callback mCallback; + sol::main_object mArg; + }; + std::vector mQueuedCallbacks; + + // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). + class DelayedAction + { + public: + DelayedAction(LuaUtil::LuaState* state, std::function fn, std::string_view name); + void apply() const; + + private: + std::string mCallerTraceback; + std::function mFn; + std::string mName; + }; + std::vector mActionQueue; + std::optional mTeleportPlayerAction; + std::vector mUIMessages; + std::vector> mInGameConsoleMessages; + + LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() }; + LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() }; + }; + +} + +#endif // MWLUA_LUAMANAGERIMP_H diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp new file mode 100644 index 000000000..4e01f4c79 --- /dev/null +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -0,0 +1,699 @@ +#include "magicbindings.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwmechanics/activespells.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/magiceffects.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellutil.hpp" +#include "../mwworld/action.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "localscripts.hpp" +#include "luamanagerimp.hpp" +#include "object.hpp" +#include "objectvariant.hpp" +#include "worldview.hpp" + +namespace MWLua +{ + template + struct ActorStore + { + using Collection = typename Store::Collection; + using Iterator = typename Collection::const_iterator; + + ActorStore(const sol::object& actor) + : mActor(actor) + , mIterator() + , mIndex(0) + { + if (!isActor()) + throw std::runtime_error("Actor expected"); + reset(); + } + + bool isActor() const { return !mActor.ptr().isEmpty() && mActor.ptr().getClass().isActor(); } + + bool isLObject() const { return mActor.isLObject(); } + + void reset() + { + mIndex = 0; + auto* store = getStore(); + if (store) + mIterator = store->begin(); + } + + bool isEnd() const + { + auto* store = getStore(); + if (store) + return mIterator == store->end(); + return true; + } + + void advance() + { + auto* store = getStore(); + if (store) + { + mIterator++; + mIndex++; + } + } + + Store* getStore() const; + + ObjectVariant mActor; + Iterator mIterator; + int mIndex; + }; + + template <> + MWMechanics::Spells* ActorStore::getStore() const + { + if (!isActor()) + return nullptr; + const MWWorld::Ptr& ptr = mActor.ptr(); + return &ptr.getClass().getCreatureStats(ptr).getSpells(); + } + + template <> + MWMechanics::MagicEffects* ActorStore::getStore() const + { + if (!isActor()) + return nullptr; + const MWWorld::Ptr& ptr = mActor.ptr(); + return &ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + } + + template <> + MWMechanics::ActiveSpells* ActorStore::getStore() const + { + if (!isActor()) + return nullptr; + const MWWorld::Ptr& ptr = mActor.ptr(); + return &ptr.getClass().getCreatureStats(ptr).getActiveSpells(); + } + + struct ActiveEffect + { + MWMechanics::EffectKey key; + MWMechanics::EffectParam param; + }; + // class returned via 'types.Actor.spells(obj)' in Lua + using ActorSpells = ActorStore; + // class returned via 'types.Actor.activeEffects(obj)' in Lua + using ActorActiveEffects = ActorStore; + // class returned via 'types.Actor.activeSpells(obj)' in Lua + using ActorActiveSpells = ActorStore; +} + +namespace sol +{ + template + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + static ESM::RefId toSpellId(const sol::object& spellOrId) + { + if (spellOrId.is()) + return spellOrId.as()->mId; + else + return ESM::RefId::deserializeText(LuaUtil::cast(spellOrId)); + } + + sol::table initCoreMagicBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table magicApi(lua, sol::create); + + // Constants + magicApi["RANGE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Self", ESM::RT_Self }, + { "Touch", ESM::RT_Touch }, + { "Target", ESM::RT_Target }, + })); + magicApi["SCHOOL"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Alteration", 0 }, + { "Conjuration", 1 }, + { "Destruction", 2 }, + { "Illusion", 3 }, + { "Mysticism", 4 }, + { "Restoration", 5 }, + })); + magicApi["SPELL_TYPE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Spell", ESM::Spell::ST_Spell }, + { "Ability", ESM::Spell::ST_Ability }, + { "Blight", ESM::Spell::ST_Blight }, + { "Disease", ESM::Spell::ST_Disease }, + { "Curse", ESM::Spell::ST_Curse }, + { "Power", ESM::Spell::ST_Power }, + })); + magicApi["ENCHANTMENT_TYPE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "CastOnce", ESM::Enchantment::Type::CastOnce }, + { "CastOnStrike", ESM::Enchantment::Type::WhenStrikes }, + { "CastOnUse", ESM::Enchantment::Type::WhenUsed }, + { "ConstantEffect", ESM::Enchantment::Type::ConstantEffect }, + })); + + sol::table effect(context.mLua->sol(), sol::create); + magicApi["EFFECT_TYPE"] = LuaUtil::makeStrictReadOnly(effect); + for (const auto& name : ESM::MagicEffect::sIndexNames) + { + effect[name] = Misc::StringUtils::lowerCase(name); + } + + // Spell store + using SpellStore = MWWorld::Store; + const SpellStore* spellStore = &MWBase::Environment::get().getWorld()->getStore().get(); + sol::usertype spellStoreT = lua.new_usertype("ESM3_SpellStore"); + spellStoreT[sol::meta_function::to_string] + = [](const SpellStore& store) { return "ESM3_SpellStore{" + std::to_string(store.getSize()) + " spells}"; }; + spellStoreT[sol::meta_function::length] = [](const SpellStore& store) { return store.getSize(); }; + spellStoreT[sol::meta_function::index] = sol::overload( + [](const SpellStore& store, size_t index) -> const ESM::Spell* { return store.at(index - 1); }, + [](const SpellStore& store, std::string_view spellId) -> const ESM::Spell* { + return store.find(ESM::RefId::deserializeText(spellId)); + }); + spellStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + spellStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + + magicApi["spells"] = spellStore; + + // Enchantment store + using EnchantmentStore = MWWorld::Store; + const EnchantmentStore* enchantmentStore + = &MWBase::Environment::get().getWorld()->getStore().get(); + sol::usertype enchantmentStoreT = lua.new_usertype("ESM3_EnchantmentStore"); + enchantmentStoreT[sol::meta_function::to_string] = [](const EnchantmentStore& store) { + return "ESM3_EnchantmentStore{" + std::to_string(store.getSize()) + " enchantments}"; + }; + enchantmentStoreT[sol::meta_function::length] = [](const EnchantmentStore& store) { return store.getSize(); }; + enchantmentStoreT[sol::meta_function::index] = sol::overload( + [](const EnchantmentStore& store, size_t index) -> const ESM::Enchantment* { return store.at(index - 1); }, + [](const EnchantmentStore& store, std::string_view enchantmentId) -> const ESM::Enchantment* { + return store.find(ESM::RefId::deserializeText(enchantmentId)); + }); + enchantmentStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + enchantmentStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + + magicApi["enchantments"] = enchantmentStore; + + // MagicEffect store + using MagicEffectStore = MWWorld::Store; + const MagicEffectStore* magicEffectStore + = &MWBase::Environment::get().getWorld()->getStore().get(); + auto magicEffectStoreT = lua.new_usertype("ESM3_MagicEffectStore"); + magicEffectStoreT[sol::meta_function::to_string] = [](const MagicEffectStore& store) { + return "ESM3_MagicEffectStore{" + std::to_string(store.getSize()) + " effects}"; + }; + magicEffectStoreT[sol::meta_function::index] + = [](const MagicEffectStore& store, int id) -> const ESM::MagicEffect* { return store.find(id); }; + auto magicEffectsIter = [magicEffectStore](sol::this_state lua, const sol::object& /*store*/, + sol::optional id) -> std::tuple { + MagicEffectStore::iterator iter; + if (id.has_value()) + { + iter = magicEffectStore->findIter(*id); + if (iter != magicEffectStore->end()) + iter++; + } + else + iter = magicEffectStore->begin(); + if (iter != magicEffectStore->end()) + return std::make_tuple(sol::make_object(lua, iter->first), sol::make_object(lua, &iter->second)); + else + return std::make_tuple(sol::nil, sol::nil); + }; + magicEffectStoreT[sol::meta_function::pairs] + = [iter = sol::make_object(lua, magicEffectsIter)] { return iter; }; + + magicApi["effects"] = magicEffectStore; + + // Spell record + auto spellT = lua.new_usertype("ESM3_Spell"); + spellT[sol::meta_function::to_string] + = [](const ESM::Spell& rec) -> std::string { return "ESM3_Spell[" + rec.mId.toDebugString() + "]"; }; + spellT["id"] = sol::readonly_property([](const ESM::Spell& rec) { return rec.mId.serializeText(); }); + spellT["name"] = sol::readonly_property([](const ESM::Spell& rec) -> std::string_view { return rec.mName; }); + spellT["type"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mType; }); + spellT["cost"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mCost; }); + spellT["effects"] = sol::readonly_property([&lua](const ESM::Spell& rec) -> sol::table { + sol::table res(lua, sol::create); + for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) + res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) + return res; + }); + + // Enchantment record + auto enchantT = lua.new_usertype("ESM3_Enchantment"); + enchantT[sol::meta_function::to_string] = [](const ESM::Enchantment& rec) -> std::string { + return "ESM3_Enchantment[" + rec.mId.toDebugString() + "]"; + }; + enchantT["id"] = sol::readonly_property([](const ESM::Enchantment& rec) { return rec.mId.serializeText(); }); + enchantT["type"] = sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mType; }); + enchantT["autocalcFlag"] = sol::readonly_property( + [](const ESM::Enchantment& rec) -> bool { return !!(rec.mData.mFlags & ESM::Enchantment::Autocalc); }); + enchantT["cost"] = sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mCost; }); + enchantT["charge"] + = sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mCharge; }); + enchantT["effects"] = sol::readonly_property([&lua](const ESM::Enchantment& rec) -> sol::table { + sol::table res(lua, sol::create); + for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) + res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) + return res; + }); + + // Effect params + auto effectParamsT = lua.new_usertype("ESM3_EffectParams"); + effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) { + const ESM::MagicEffect* const rec = magicEffectStore->find(params.mEffectID); + return "ESM3_EffectParams[" + ESM::MagicEffect::indexToGmstString(rec->mIndex) + "]"; + }; + effectParamsT["effect"] + = sol::readonly_property([magicEffectStore](const ESM::ENAMstruct& params) -> const ESM::MagicEffect* { + return magicEffectStore->find(params.mEffectID); + }); + effectParamsT["affectedSkill"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { + if (params.mSkill >= 0 && params.mSkill < ESM::Skill::Length) + return Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[params.mSkill]); + else + return sol::nullopt; + }); + effectParamsT["affectedAttribute"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { + if (params.mAttribute >= 0 && params.mAttribute < ESM::Attribute::Length) + return Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[params.mAttribute]); + else + return sol::nullopt; + }); + effectParamsT["range"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mRange; }); + effectParamsT["area"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mArea; }); + effectParamsT["magnitudeMin"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMin; }); + effectParamsT["magnitudeMax"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMax; }); + effectParamsT["duration"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mDuration; }); + + // MagicEffect record + auto magicEffectT = context.mLua->sol().new_usertype("ESM3_MagicEffect"); + + magicEffectT[sol::meta_function::to_string] = [](const ESM::MagicEffect& rec) { + return "ESM3_MagicEffect[" + ESM::MagicEffect::indexToGmstString(rec.mIndex) + "]"; + }; + magicEffectT["id"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string { + auto name = ESM::MagicEffect::indexToName(rec.mIndex); + return Misc::StringUtils::lowerCase(name); + }); + magicEffectT["icon"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + magicEffectT["name"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view { + return MWBase::Environment::get() + .getWorld() + ->getStore() + .get() + .find(ESM::MagicEffect::indexToGmstString(rec.mIndex)) + ->mValue.getString(); + }); + magicEffectT["school"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> int { return ESM::MagicSchool::skillRefIdToIndex(rec.mData.mSchool); }); + magicEffectT["baseCost"] + = sol::readonly_property([](const ESM::MagicEffect& rec) -> float { return rec.mData.mBaseCost; }); + magicEffectT["color"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> Misc::Color { + return Misc::Color(rec.mData.mRed / 255.f, rec.mData.mGreen / 255.f, rec.mData.mBlue / 255.f, 1.f); + }); + magicEffectT["harmful"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::Harmful; }); + + // TODO: Should we expose it? What happens if a spell has several effects with different projectileSpeed? + // magicEffectT["projectileSpeed"] + // = sol::readonly_property([](const ESM::MagicEffect& rec) -> float { return rec.mData.mSpeed; }); + + auto activeEffectT = context.mLua->sol().new_usertype("ActiveEffect"); + + activeEffectT[sol::meta_function::to_string] = [](const ActiveEffect& effect) { + return "ActiveEffect[" + ESM::MagicEffect::indexToGmstString(effect.key.mId) + "]"; + }; + activeEffectT["id"] = sol::readonly_property([](const ActiveEffect& effect) -> std::string { + auto name = ESM::MagicEffect::indexToName(effect.key.mId); + return Misc::StringUtils::lowerCase(name); + }); + activeEffectT["name"] + = sol::readonly_property([](const ActiveEffect& effect) -> std::string { return effect.key.toString(); }); + + activeEffectT["affectedSkill"] + = sol::readonly_property([magicEffectStore](const ActiveEffect& effect) -> sol::optional { + auto* rec = magicEffectStore->find(effect.key.mId); + if ((rec->mData.mFlags & ESM::MagicEffect::TargetSkill) && effect.key.mArg >= 0 + && effect.key.mArg < ESM::Skill::Length) + return Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[effect.key.mArg]); + else + return sol::nullopt; + }); + activeEffectT["affectedAttribute"] + = sol::readonly_property([magicEffectStore](const ActiveEffect& effect) -> sol::optional { + auto* rec = magicEffectStore->find(effect.key.mId); + if ((rec->mData.mFlags & ESM::MagicEffect::TargetAttribute) && effect.key.mArg >= 0 + && effect.key.mArg < ESM::Attribute::Length) + return Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[effect.key.mArg]); + else + return sol::nullopt; + }); + + activeEffectT["magnitude"] + = sol::readonly_property([](const ActiveEffect& effect) { return effect.param.getMagnitude(); }); + activeEffectT["magnitudeBase"] + = sol::readonly_property([](const ActiveEffect& effect) { return effect.param.getBase(); }); + activeEffectT["magnitudeModifier"] + = sol::readonly_property([](const ActiveEffect& effect) { return effect.param.getModifier(); }); + + return LuaUtil::makeReadOnly(magicApi); + } + + void addActorMagicBindings(sol::table& actor, const Context& context) + { + const MWWorld::Store* spellStore + = &MWBase::Environment::get().getWorld()->getStore().get(); + + // types.Actor.spells(o) + actor["spells"] = [](const sol::object& actor) { return ActorSpells{ actor }; }; + auto spellsT = context.mLua->sol().new_usertype("ActorSpells"); + spellsT[sol::meta_function::to_string] + = [](const ActorSpells& spells) { return "ActorSpells[" + spells.mActor.object().toString() + "]"; }; + + actor["activeSpells"] = [](const sol::object& actor) { return ActorActiveSpells{ actor }; }; + auto activeSpellsT = context.mLua->sol().new_usertype("ActorActiveSpells"); + activeSpellsT[sol::meta_function::to_string] = [](const ActorActiveSpells& spells) { + return "ActorActiveSpells[" + spells.mActor.object().toString() + "]"; + }; + + actor["activeEffects"] = [](const sol::object& actor) { return ActorActiveEffects{ actor }; }; + auto activeEffectsT = context.mLua->sol().new_usertype("ActorActiveEffects"); + activeEffectsT[sol::meta_function::to_string] = [](const ActorActiveEffects& effects) { + return "ActorActiveEffects[" + effects.mActor.object().toString() + "]"; + }; + + actor["getSelectedSpell"] = [spellStore](const Object& o) -> sol::optional { + const MWWorld::Ptr& ptr = o.ptr(); + const MWWorld::Class& cls = ptr.getClass(); + if (!cls.isActor()) + throw std::runtime_error("Actor expected"); + ESM::RefId spellId; + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + spellId = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); + else + spellId = cls.getCreatureStats(ptr).getSpells().getSelectedSpell(); + if (spellId.empty()) + return sol::nullopt; + else + return spellStore->find(spellId); + }; + actor["setSelectedSpell"] = [context, spellStore](const SelfObject& o, const sol::object& spellOrId) { + const MWWorld::Ptr& ptr = o.ptr(); + const MWWorld::Class& cls = ptr.getClass(); + if (!cls.isActor()) + throw std::runtime_error("Actor expected"); + ESM::RefId spellId; + if (spellOrId != sol::nil) + { + spellId = toSpellId(spellOrId); + const ESM::Spell* spell = spellStore->find(spellId); + if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power) + throw std::runtime_error("Ability or disease can not be casted: " + spellId.toDebugString()); + } + context.mLuaManager->addAction([obj = Object(ptr), spellId]() { + const MWWorld::Ptr& ptr = obj.ptr(); + auto& stats = ptr.getClass().getCreatureStats(ptr); + if (!stats.getSpells().hasSpell(spellId)) + throw std::runtime_error("Actor doesn't know spell " + spellId.toDebugString()); + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + int chance = 0; + if (!spellId.empty()) + chance = MWMechanics::getSpellSuccessChance(spellId, ptr); + MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, chance); + } + else + ptr.getClass().getCreatureStats(ptr).getSpells().setSelectedSpell(spellId); + }); + }; + + // #(types.Actor.spells(o)) + spellsT[sol::meta_function::length] = [](const ActorSpells& spells) -> size_t { + if (auto* store = spells.getStore()) + return store->count(); + return 0; + }; + + // types.Actor.spells(o)[i] + spellsT[sol::meta_function::index] = sol::overload( + [](const ActorSpells& spells, size_t index) -> sol::optional { + if (auto* store = spells.getStore()) + if (index <= store->count()) + return store->at(index - 1); + return sol::nullopt; + }, + [spellStore](const ActorSpells& spells, std::string_view spellId) -> sol::optional { + if (auto* store = spells.getStore()) + { + const ESM::Spell* spell = spellStore->find(ESM::RefId::deserializeText(spellId)); + if (store->hasSpell(spell)) + return spell; + } + return sol::nullopt; + }); + + // pairs(types.Actor.spells(o)) + spellsT[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get(); + + // ipairs(types.Actor.spells(o)) + spellsT[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get(); + + // types.Actor.spells(o):add(id) + spellsT["add"] = [context](const ActorSpells& spells, const sol::object& spellOrId) { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + context.mLuaManager->addAction([obj = spells.mActor.object(), id = toSpellId(spellOrId)]() { + const MWWorld::Ptr& ptr = obj.ptr(); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).getSpells().add(id); + }); + }; + + // types.Actor.spells(o):remove(id) + spellsT["remove"] = [context](const ActorSpells& spells, const sol::object& spellOrId) { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + context.mLuaManager->addAction([obj = spells.mActor.object(), id = toSpellId(spellOrId)]() { + const MWWorld::Ptr& ptr = obj.ptr(); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).getSpells().remove(id); + }); + }; + + // types.Actor.spells(o):clear() + spellsT["clear"] = [context](const ActorSpells& spells) { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + context.mLuaManager->addAction([obj = spells.mActor.object()]() { + const MWWorld::Ptr& ptr = obj.ptr(); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).getSpells().clear(); + }); + }; + + // pairs(types.Actor.activeSpells(o)) + // Note that the indexes are fake, and only for consistency with other lua pairs interfaces. You can't use them + // for anything. + activeSpellsT["__pairs"] = [](sol::this_state ts, ActorActiveSpells& self) { + sol::state_view lua(ts); + self.reset(); + return sol::as_function([lua, &self]() mutable -> std::pair { + if (!self.isEnd()) + { + auto result = sol::make_object(lua, self.mIterator->getId()); + auto index = sol::make_object(lua, self.mIndex + 1); + self.advance(); + return { index, result }; + } + else + { + return { sol::lua_nil, sol::lua_nil }; + } + }); + }; + + // types.Actor.activeSpells(o):isSpellActive(id) + activeSpellsT["isSpellActive"] = [](const ActorActiveSpells& spells, const sol::object& spellOrId) -> bool { + auto id = toSpellId(spellOrId); + if (auto* store = spells.getStore()) + return store->isSpellActive(id); + return false; + }; + + // types.Actor.activeSpells(o):remove(id) + activeSpellsT["remove"] = [](const ActorActiveSpells& spells, const sol::object& spellOrId) { + if (spells.isLObject()) + throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); + + auto id = toSpellId(spellOrId); + if (auto* store = spells.getStore()) + { + store->removeEffects(spells.mActor.ptr(), id); + } + }; + + // pairs(types.Actor.activeEffects(o)) + // Note that the indexes are fake, and only for consistency with other lua pairs interfaces. You can't use them + // for anything. + activeEffectsT["__pairs"] = [](sol::this_state ts, ActorActiveEffects& self) { + sol::state_view lua(ts); + self.reset(); + return sol::as_function([lua, &self]() mutable -> std::pair { + if (!self.isEnd()) + { + ActiveEffect effect = ActiveEffect{ self.mIterator->first, self.mIterator->second }; + auto result = sol::make_object(lua, effect); + + auto key = sol::make_object(lua, self.mIterator->first.toString()); + self.advance(); + return { key, result }; + } + else + { + return { sol::lua_nil, sol::lua_nil }; + } + }); + }; + + auto getEffectKey + = [](std::string_view idStr, sol::optional argStr) -> MWMechanics::EffectKey { + auto id = ESM::MagicEffect::indexNameToIndex(idStr); + auto* rec = MWBase::Environment::get().getWorld()->getStore().get().find(id); + + MWMechanics::EffectKey key = MWMechanics::EffectKey(id); + + if (argStr.has_value() + && (rec->mData.mFlags & (ESM::MagicEffect::TargetAttribute | ESM::MagicEffect::TargetSkill))) + { + // MWLua exposes attributes and skills as strings, so we have to convert them back to IDs here + if (rec->mData.mFlags & ESM::MagicEffect::TargetAttribute) + key = MWMechanics::EffectKey(id, ESM::Attribute::stringToAttributeId(argStr.value())); + + if (rec->mData.mFlags & ESM::MagicEffect::TargetSkill) + key = MWMechanics::EffectKey(id, ESM::Skill::stringToSkillId(argStr.value())); + } + + return key; + }; + + // types.Actor.activeEffects(o):getEffect(id, ?arg) + activeEffectsT["getEffect"] = [getEffectKey](const ActorActiveEffects& effects, std::string_view idStr, + sol::optional argStr) -> sol::optional { + if (!effects.isActor()) + return sol::nullopt; + + MWMechanics::EffectKey key = getEffectKey(idStr, argStr); + + if (auto* store = effects.getStore()) + if (auto effect = store->get(key)) + return ActiveEffect{ key, effect.value() }; + return sol::nullopt; + }; + + // types.Actor.activeEffects(o):removeEffect(id, ?arg) + activeEffectsT["remove"] = [getEffectKey](const ActorActiveEffects& effects, std::string_view idStr, + sol::optional argStr) { + if (!effects.isActor()) + return; + + if (effects.isLObject()) + throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); + + MWMechanics::EffectKey key = getEffectKey(idStr, argStr); + + // Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a + // spell), we still need to use the active spells store to purge this effect from active spells. + auto ptr = effects.mActor.ptr(); + + // TODO: The current ActiveSpell API does not allow us to differentiate between skill/attribute parameters + // of effects. So this cannot remove e.g. "Fortify Luck" without also removing all other fortify attribute + // effects such as "Fortify Speed". + auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells(); + activeSpells.purgeEffect(ptr, key.mId, key.mArg); + + // Now remove any leftover effects that have been added by script/console. + effects.getStore()->remove(key); + }; + + // types.Actor.activeEffects(o):set(value, id, ?arg) + activeEffectsT["set"] = [getEffectKey](const ActorActiveEffects& effects, int value, std::string_view idStr, + sol::optional argStr) { + if (!effects.isActor()) + return; + + if (effects.isLObject()) + throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); + + MWMechanics::EffectKey key = getEffectKey(idStr, argStr); + int currentValue = effects.getStore()->getOrDefault(key).getMagnitude(); + effects.getStore()->modifyBase(key, value - currentValue); + }; + + // types.Actor.activeEffects(o):modify(value, id, ?arg) + activeEffectsT["modify"] = [getEffectKey](const ActorActiveEffects& effects, int value, std::string_view idStr, + sol::optional argStr) { + if (!effects.isActor()) + return; + + if (effects.isLObject()) + throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); + + MWMechanics::EffectKey key = getEffectKey(idStr, argStr); + effects.getStore()->modifyBase(key, value); + }; + } +} diff --git a/apps/openmw/mwlua/magicbindings.hpp b/apps/openmw/mwlua/magicbindings.hpp new file mode 100644 index 000000000..047bd2e3d --- /dev/null +++ b/apps/openmw/mwlua/magicbindings.hpp @@ -0,0 +1,14 @@ +#ifndef MWLUA_MAGICBINDINGS_H +#define MWLUA_MAGICBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initCoreMagicBindings(const Context& context); + void addActorMagicBindings(sol::table& actor, const Context& context); +} + +#endif // MWLUA_MAGICBINDINGS_H diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp new file mode 100644 index 000000000..2b91d0d04 --- /dev/null +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -0,0 +1,109 @@ +#include "mwscriptbindings.hpp" + +#include + +#include "../mwbase/world.hpp" +#include "../mwscript/scriptmanagerimp.hpp" + +#include "object.hpp" + +namespace MWLua +{ + struct MWScriptRef + { + ESM::RefId mId; + sol::optional mObj; + + MWScript::Locals& getLocals() + { + if (mObj) + return mObj->ptr().getRefData().getLocals(); + else + return MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocals(mId); + } + }; + struct MWScriptVariables + { + MWScriptRef mRef; + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + + sol::table initMWScriptBindings(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + api["getGlobalScript"] + = [](std::string_view recordId, sol::optional player) -> sol::optional { + if (player.has_value() && player->ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("Second argument must either be a player or be missing"); + auto scriptId = ESM::RefId::deserializeText(recordId); + if (MWBase::Environment::get().getScriptManager()->getGlobalScripts().getScriptIfPresent(scriptId)) + return MWScriptRef{ scriptId, sol::nullopt }; + else + return sol::nullopt; + }; + api["getLocalScript"] = [](const GObject& obj, sol::optional player) -> sol::optional { + if (player.has_value() && player->ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("Second argument must either be a player or be missing"); + auto scriptId = obj.ptr().getRefData().getLocals().getScriptId(); + if (scriptId.empty()) + return sol::nullopt; + return MWScriptRef{ scriptId, obj }; + }; + + // In multiplayer it will be possible to have several instances (per player) of a single script, + // so we will likely add functions returning Lua table of scripts. + // api["getGlobalScripts"] = [](std::string_view recordId) -> list of scripts + // api["getLocalScripts"] = [](const GObject& obj) -> list of scripts + + sol::usertype mwscript = context.mLua->sol().new_usertype("MWScript"); + sol::usertype mwscriptVars + = context.mLua->sol().new_usertype("MWScriptVariables"); + mwscript[sol::meta_function::to_string] + = [](const MWScriptRef& s) { return std::string("MWScript{") + s.mId.toDebugString() + "}"; }; + mwscript["recordId"] = sol::readonly_property([](const MWScriptRef& s) { return s.mId.serializeText(); }); + mwscript["variables"] = sol::readonly_property([](const MWScriptRef& s) { return MWScriptVariables{ s }; }); + mwscript["object"] = sol::readonly_property([](const MWScriptRef& s) -> sol::optional { + if (s.mObj) + return s.mObj; + const MWScript::GlobalScriptDesc* script + = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getScriptIfPresent(s.mId); + if (!script) + throw std::runtime_error("Invalid MWScriptRef"); + const MWWorld::Ptr* ptr = script->getPtrIfPresent(); + if (ptr && !ptr->isEmpty()) + return GObject(*ptr); + else + return sol::nullopt; + }); + mwscript["player"] = sol::readonly_property( + [](const MWScriptRef&) { return GObject(MWBase::Environment::get().getWorld()->getPlayerPtr()); }); + mwscriptVars[sol::meta_function::index] = [](MWScriptVariables& s, std::string_view var) { + return s.mRef.getLocals().getVarAsDouble(s.mRef.mId, Misc::StringUtils::lowerCase(var)); + }; + mwscriptVars[sol::meta_function::new_index] = [](MWScriptVariables& s, std::string_view var, double val) { + MWScript::Locals& locals = s.mRef.getLocals(); + if (!locals.setVar(s.mRef.mId, Misc::StringUtils::lowerCase(var), val)) + throw std::runtime_error( + "No variable \"" + std::string(var) + "\" in mwscript " + s.mRef.mId.toDebugString()); + }; + + return LuaUtil::makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/mwscriptbindings.hpp b/apps/openmw/mwlua/mwscriptbindings.hpp new file mode 100644 index 000000000..9598f051a --- /dev/null +++ b/apps/openmw/mwlua/mwscriptbindings.hpp @@ -0,0 +1,15 @@ +#ifndef MWLUA_MWSCRIPTBINDINGS_H +#define MWLUA_MWSCRIPTBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + + sol::table initMWScriptBindings(const Context&); + +} + +#endif // MWLUA_MWSCRIPTBINDINGS_H diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp new file mode 100644 index 000000000..6c7ee8880 --- /dev/null +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -0,0 +1,274 @@ +#include "nearbybindings.hpp" + +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwphysics/raycasting.hpp" + +#include "luamanagerimp.hpp" +#include "worldview.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + sol::table initNearbyPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + WorldView* worldView = context.mWorldView; + + sol::usertype rayResult + = context.mLua->sol().new_usertype("RayCastingResult"); + rayResult["hit"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) { return r.mHit; }); + rayResult["hitPos"] + = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional { + if (r.mHit) + return r.mHitPos; + else + return sol::nullopt; + }); + rayResult["hitNormal"] + = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional { + if (r.mHit) + return r.mHitNormal; + else + return sol::nullopt; + }); + rayResult["hitObject"] + = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional { + if (r.mHitObject.isEmpty()) + return sol::nullopt; + else + return LObject(getId(r.mHitObject)); + }); + + api["COLLISION_TYPE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "World", MWPhysics::CollisionType_World }, + { "Door", MWPhysics::CollisionType_Door }, + { "Actor", MWPhysics::CollisionType_Actor }, + { "HeightMap", MWPhysics::CollisionType_HeightMap }, + { "Projectile", MWPhysics::CollisionType_Projectile }, + { "Water", MWPhysics::CollisionType_Water }, + { "Default", MWPhysics::CollisionType_Default }, + { "AnyPhysical", MWPhysics::CollisionType_AnyPhysical }, + { "Camera", MWPhysics::CollisionType_CameraOnly }, + { "VisualOnly", MWPhysics::CollisionType_VisualOnly }, + })); + + api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { + MWWorld::Ptr ignore; + int collisionType = MWPhysics::CollisionType_Default; + float radius = 0; + if (options) + { + sol::optional ignoreObj = options->get>("ignore"); + if (ignoreObj) + ignore = ignoreObj->ptr(); + collisionType = options->get>("collisionType").value_or(collisionType); + radius = options->get>("radius").value_or(0); + } + const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + if (radius <= 0) + return rayCasting->castRay(from, to, ignore, std::vector(), collisionType); + else + { + if (!ignore.isEmpty()) + throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + return rayCasting->castSphere(from, to, radius, collisionType); + } + }; + // TODO: async raycasting + /*api["asyncCastRay"] = [luaManager = context.mLuaManager]( + const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional + options) + { + std::function callback = + luaManager->wrapLuaCallback(luaCallback); + MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + + // Handle options the same way as in `castRay`. + + // NOTE: `callback` is not thread safe. If MWPhysics works in separate thread, it must put results to a + queue + // and use this callback from the main thread at the beginning of the next frame processing. + rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); + };*/ + api["castRenderingRay"] = [manager = context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to) { + if (!manager->isProcessingInputEvents()) + { + throw std::logic_error( + "castRenderingRay can be used only in player scripts during processing of input events; " + "use asyncCastRenderingRay instead."); + } + MWPhysics::RayCastingResult res; + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + return res; + }; + api["asyncCastRenderingRay"] = [context]( + const sol::table& callback, const osg::Vec3f& from, const osg::Vec3f& to) { + context.mLuaManager->addAction([context, callback = LuaUtil::Callback::fromLua(callback), from, to] { + MWPhysics::RayCastingResult res; + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res)); + }); + }; + + api["getObjectByFormId"] = [](std::string_view formIdStr) -> LObject { + ESM::RefId refId = ESM::RefId::deserializeText(formIdStr); + if (!refId.is()) + throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId"); + return LObject(refId.getIf()->getValue()); + }; + + api["activators"] = LObjectList{ worldView->getActivatorsInScene() }; + api["actors"] = LObjectList{ worldView->getActorsInScene() }; + api["containers"] = LObjectList{ worldView->getContainersInScene() }; + api["doors"] = LObjectList{ worldView->getDoorsInScene() }; + api["items"] = LObjectList{ worldView->getItemsInScene() }; + api["players"] = LObjectList{ worldView->getPlayers() }; + + api["NAVIGATOR_FLAGS"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Walk", DetourNavigator::Flag_walk }, + { "Swim", DetourNavigator::Flag_swim }, + { "OpenDoor", DetourNavigator::Flag_openDoor }, + { "UsePathgrid", DetourNavigator::Flag_usePathgrid }, + })); + + api["COLLISION_SHAPE_TYPE"] = LuaUtil::makeStrictReadOnly( + context.mLua->tableFromPairs({ + { "Aabb", DetourNavigator::CollisionShapeType::Aabb }, + { "RotatingBox", DetourNavigator::CollisionShapeType::RotatingBox }, + { "Cylinder", DetourNavigator::CollisionShapeType::Cylinder }, + })); + + api["FIND_PATH_STATUS"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Success", DetourNavigator::Status::Success }, + { "PartialPath", DetourNavigator::Status::PartialPath }, + { "NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound }, + { "StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound }, + { "EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound }, + { "MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed }, + { "FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed }, + { "InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed }, + })); + + static const DetourNavigator::AgentBounds defaultAgentBounds{ + Settings::game().mActorCollisionShapeType, + Settings::game().mDefaultActorPathfindHalfExtents, + }; + static const float defaultStepSize + = 2 * std::max(defaultAgentBounds.mHalfExtents.x(), defaultAgentBounds.mHalfExtents.y()); + static constexpr DetourNavigator::Flags defaultIncludeFlags = DetourNavigator::Flag_walk + | DetourNavigator::Flag_swim | DetourNavigator::Flag_openDoor | DetourNavigator::Flag_usePathgrid; + + api["findPath"] + = [](const osg::Vec3f& source, const osg::Vec3f& destination, const sol::optional& options) { + DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; + float stepSize = defaultStepSize; + DetourNavigator::Flags includeFlags = defaultIncludeFlags; + DetourNavigator::AreaCosts areaCosts{}; + float destinationTolerance = 1; + + if (options.has_value()) + { + if (const auto& t = options->get>("agentBounds")) + { + if (const auto& v = t->get>("shapeType")) + agentBounds.mShapeType = *v; + if (const auto& v = t->get>("halfExtents")) + { + agentBounds.mHalfExtents = *v; + stepSize = 2 * std::max(v->x(), v->y()); + } + } + if (const auto& v = options->get>("stepSize")) + stepSize = *v; + if (const auto& v = options->get>("includeFlags")) + includeFlags = *v; + if (const auto& t = options->get>("areaCosts")) + { + if (const auto& v = t->get>("water")) + areaCosts.mWater = *v; + if (const auto& v = t->get>("door")) + areaCosts.mDoor = *v; + if (const auto& v = t->get>("pathgrid")) + areaCosts.mPathgrid = *v; + if (const auto& v = t->get>("ground")) + areaCosts.mGround = *v; + } + if (const auto& v = options->get>("destinationTolerance")) + destinationTolerance = *v; + } + + std::vector result; + + const DetourNavigator::Status status = DetourNavigator::findPath( + *MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, stepSize, source, + destination, includeFlags, areaCosts, destinationTolerance, std::back_inserter(result)); + + return std::make_tuple(status, std::move(result)); + }; + + api["findRandomPointAroundCircle"] = [](const osg::Vec3f& position, float maxRadius, + const sol::optional& options) { + DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; + DetourNavigator::Flags includeFlags = defaultIncludeFlags; + + if (options.has_value()) + { + if (const auto& t = options->get>("agentBounds")) + { + if (const auto& v = t->get>("shapeType")) + agentBounds.mShapeType = *v; + if (const auto& v = t->get>("halfExtents")) + agentBounds.mHalfExtents = *v; + } + if (const auto& v = options->get>("includeFlags")) + includeFlags = *v; + } + + constexpr auto getRandom + = [] { return Misc::Rng::rollProbability(MWBase::Environment::get().getWorld()->getPrng()); }; + + return DetourNavigator::findRandomPointAroundCircle(*MWBase::Environment::get().getWorld()->getNavigator(), + agentBounds, position, maxRadius, includeFlags, getRandom); + }; + + api["castNavigationRay"] + = [](const osg::Vec3f& from, const osg::Vec3f& to, const sol::optional& options) { + DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; + DetourNavigator::Flags includeFlags = defaultIncludeFlags; + + if (options.has_value()) + { + if (const auto& t = options->get>("agentBounds")) + { + if (const auto& v = t->get>("shapeType")) + agentBounds.mShapeType = *v; + if (const auto& v = t->get>("halfExtents")) + agentBounds.mHalfExtents = *v; + } + if (const auto& v = options->get>("includeFlags")) + includeFlags = *v; + } + + return DetourNavigator::raycast( + *MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, from, to, includeFlags); + }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/nearbybindings.hpp b/apps/openmw/mwlua/nearbybindings.hpp new file mode 100644 index 000000000..ee0022898 --- /dev/null +++ b/apps/openmw/mwlua/nearbybindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_NEARBYBINDINGS_H +#define MWLUA_NEARBYBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initNearbyPackage(const Context&); +} + +#endif // MWLUA_NEARBYBINDINGS_H diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp new file mode 100644 index 000000000..89bf42529 --- /dev/null +++ b/apps/openmw/mwlua/object.hpp @@ -0,0 +1,77 @@ +#ifndef MWLUA_OBJECT_H +#define MWLUA_OBJECT_H + +#include +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/worldmodel.hpp" + +namespace MWLua +{ + // ObjectId is a unique identifier of a game object. + // It can change only if the order of content files was change. + using ObjectId = ESM::RefNum; + inline const ObjectId& getId(const MWWorld::Ptr& ptr) + { + return ptr.getCellRef().getRefNum(); + } + + // Lua scripts can't use MWWorld::Ptr directly, because lifetime of a script can be longer than lifetime of Ptr. + // `GObject` and `LObject` are intended to be passed to Lua as a userdata. + // It automatically updates the underlying Ptr when needed. + class Object : public MWWorld::SafePtr + { + public: + using SafePtr::SafePtr; + const MWWorld::Ptr& ptr() const + { + const MWWorld::Ptr& res = ptrOrEmpty(); + if (res.isEmpty()) + throw std::runtime_error("Object is not available: " + id().toString()); + return res; + } + }; + + // Used only in local scripts + struct LCell + { + MWWorld::CellStore* mStore; + }; + class LObject : public Object + { + using Object::Object; + }; + + // Used only in global scripts + struct GCell + { + MWWorld::CellStore* mStore; + }; + class GObject : public Object + { + using Object::Object; + }; + + using ObjectIdList = std::shared_ptr>; + template + struct ObjectList + { + ObjectIdList mIds; + }; + using GObjectList = ObjectList; + using LObjectList = ObjectList; + + template + struct Inventory + { + Obj mObj; + }; +} + +#endif // MWLUA_OBJECT_H diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp new file mode 100644 index 000000000..1ebc0702b --- /dev/null +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -0,0 +1,625 @@ +#include "objectbindings.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/scene.hpp" + +#include "../mwrender/renderingmanager.hpp" +#include "../mwrender/vismask.hpp" + +#include "../mwmechanics/creaturestats.hpp" + +#include "luaevents.hpp" +#include "luamanagerimp.hpp" +#include "types/types.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + + namespace + { + MWWorld::CellStore* findCell(const sol::object& cellOrName, const osg::Vec3f& pos) + { + MWWorld::WorldModel* wm = MWBase::Environment::get().getWorldModel(); + MWWorld::CellStore* cell; + if (cellOrName.is()) + cell = cellOrName.as().mStore; + else + { + std::string_view name = LuaUtil::cast(cellOrName); + if (name.empty()) + cell = nullptr; // default exterior worldspace + else + cell = &wm->getCell(name); + } + if (cell != nullptr && !cell->isExterior()) + return cell; + const ESM::RefId worldspace + = cell == nullptr ? ESM::Cell::sDefaultWorldspaceId : cell->getCell()->getWorldSpace(); + return &wm->getExterior(ESM::positionToExteriorCellLocation(pos.x(), pos.y(), worldspace)); + } + + void teleportPlayer( + MWWorld::CellStore* destCell, const osg::Vec3f& pos, const osg::Vec3f& rot, bool placeOnGround) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + ESM::Position esmPos; + static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); + std::memcpy(esmPos.pos, &pos, sizeof(osg::Vec3f)); + std::memcpy(esmPos.rot, &rot, sizeof(osg::Vec3f)); + MWWorld::Ptr ptr = world->getPlayerPtr(); + auto& stats = ptr.getClass().getCreatureStats(ptr); + stats.land(true); + stats.setTeleported(true); + world->getPlayer().setTeleported(true); + world->changeToCell(destCell->getCell()->getId(), esmPos, false); + MWWorld::Ptr newPtr = world->getPlayerPtr(); + world->moveObject(newPtr, pos); + world->rotateObject(newPtr, rot); + if (placeOnGround) + world->adjustPosition(newPtr, true); + } + + void teleportNotPlayer(const MWWorld::Ptr& ptr, MWWorld::CellStore* destCell, const osg::Vec3f& pos, + const osg::Vec3f& rot, bool placeOnGround) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Class& cls = ptr.getClass(); + if (cls.isActor()) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + stats.land(false); + stats.setTeleported(true); + } + MWWorld::Ptr newPtr = world->moveObject(ptr, destCell, pos); + world->rotateObject(newPtr, rot, MWBase::RotationFlag_none); + if (placeOnGround) + world->adjustPosition(newPtr, true); + if (cls.isDoor()) + { // Change "original position and rotation" because without it teleported animated doors don't work + // properly. + newPtr.getCellRef().setPosition(newPtr.getRefData().getPosition()); + } + if (!newPtr.getRefData().isEnabled()) + world->enable(newPtr); + } + + template + using Cell = std::conditional_t, LCell, GCell>; + + template + void registerObjectList(const std::string& prefix, const Context& context) + { + using ListT = ObjectList; + sol::state_view& lua = context.mLua->sol(); + sol::usertype listT = lua.new_usertype(prefix + "ObjectList"); + listT[sol::meta_function::to_string] + = [](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; }; + listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); }; + listT[sol::meta_function::index] = [](const ListT& list, size_t index) { + if (index > 0 && index <= list.mIds->size()) + return ObjectT((*list.mIds)[index - 1]); + else + throw std::runtime_error("Index out of range"); + }; + listT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + } + + osg::Vec3f toEulerRotation(const sol::object& transform, bool isActor) + { + if (transform.is()) + { + const osg::Quat& q = transform.as().mQ; + return isActor ? Misc::toEulerAnglesXZ(q) : Misc::toEulerAnglesZYX(q); + } + else + { + const osg::Matrixf& m = LuaUtil::cast(transform).mM; + return isActor ? Misc::toEulerAnglesXZ(m) : Misc::toEulerAnglesZYX(m); + } + } + + osg::Quat toQuat(const ESM::Position& pos, bool isActor) + { + if (isActor) + return osg::Quat(pos.rot[0], osg::Vec3(-1, 0, 0)) * osg::Quat(pos.rot[2], osg::Vec3(0, 0, -1)); + else + return Misc::Convert::makeOsgQuat(pos.rot); + } + + template + void addBasicBindings(sol::usertype& objectT, const Context& context) + { + objectT["id"] = sol::readonly_property([](const ObjectT& o) -> std::string { return o.id().toString(); }); + objectT["contentFile"] = sol::readonly_property([](const ObjectT& o) -> sol::optional { + int contentFileIndex = o.id().mContentFile; + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + if (contentFileIndex < 0 || contentFileIndex >= static_cast(contentList.size())) + return sol::nullopt; + return Misc::StringUtils::lowerCase(contentList[contentFileIndex]); + }); + objectT["isValid"] = [](const ObjectT& o) { return !o.ptrOrEmpty().isEmpty(); }; + objectT["recordId"] = sol::readonly_property( + [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); }); + objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional> { + const MWWorld::Ptr& ptr = o.ptr(); + if (ptr.isInCell()) + return Cell{ ptr.getCell() }; + else + return sol::nullopt; + }); + objectT["position"] = sol::readonly_property( + [](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asVec3(); }); + objectT["scale"] + = sol::readonly_property([](const ObjectT& o) -> float { return o.ptr().getCellRef().getScale(); }); + objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> LuaUtil::TransformQ { + return { toQuat(o.ptr().getRefData().getPosition(), o.ptr().getClass().isActor()) }; + }); + objectT["startingPosition"] = sol::readonly_property( + [](const ObjectT& o) -> osg::Vec3f { return o.ptr().getCellRef().getPosition().asVec3(); }); + objectT["startingRotation"] = sol::readonly_property([](const ObjectT& o) -> LuaUtil::TransformQ { + return { toQuat(o.ptr().getCellRef().getPosition(), o.ptr().getClass().isActor()) }; + }); + objectT["getBoundingBox"] = [](const ObjectT& o) { + MWRender::RenderingManager* renderingManager + = MWBase::Environment::get().getWorld()->getRenderingManager(); + osg::BoundingBox bb = renderingManager->getCullSafeBoundingBox(o.ptr()); + return LuaUtil::Box{ bb.center(), bb._max - bb.center() }; + }; + + objectT["type"] = sol::readonly_property( + [types = getTypeToPackageTable(context.mLua->sol())]( + const ObjectT& o) mutable { return types[getLiveCellRefType(o.ptr().mRef)]; }); + + objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); + objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; + objectT[sol::meta_function::to_string] = &ObjectT::toString; + objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) { + context.mLuaEvents->addLocalEvent( + { dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); + }; + auto getOwnerRecordId = [](const ObjectT& o) -> sol::optional { + ESM::RefId owner = o.ptr().getCellRef().getOwner(); + if (owner.empty()) + return sol::nullopt; + else + return owner.serializeText(); + }; + auto setOwnerRecordId = [](const ObjectT& obj, sol::optional ownerId) { + if (std::is_same_v && !dynamic_cast(&obj)) + throw std::runtime_error("Local scripts can set an owner only on self"); + const MWWorld::Ptr& ptr = obj.ptr(); + + if (!ownerId) + { + ptr.getCellRef().setOwner(ESM::RefId()); + return; + } + ESM::RefId owner = ESM::RefId::deserializeText(*ownerId); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + if (!store.get().search(owner)) + throw std::runtime_error("Invalid owner record id"); + ptr.getCellRef().setOwner(owner); + }; + objectT["ownerRecordId"] = sol::property(getOwnerRecordId, setOwnerRecordId); + + auto getOwnerFactionId = [](const ObjectT& o) -> sol::optional { + ESM::RefId owner = o.ptr().getCellRef().getFaction(); + if (owner.empty()) + return sol::nullopt; + else + return owner.serializeText(); + }; + auto setOwnerFactionId = [](const ObjectT& object, sol::optional ownerId) { + ESM::RefId ownerFac; + if (std::is_same_v && !dynamic_cast(&object)) + throw std::runtime_error("Local scripts can set an owner faction only on self"); + if (!ownerId) + { + object.ptr().getCellRef().setFaction(ESM::RefId()); + return; + } + ownerFac = ESM::RefId::deserializeText(*ownerId); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + if (!store.get().search(ownerFac)) + throw std::runtime_error("Invalid owner faction id"); + object.ptr().getCellRef().setFaction(ownerFac); + }; + objectT["ownerFactionId"] = sol::property(getOwnerFactionId, setOwnerFactionId); + + auto getOwnerFactionRank = [](const ObjectT& o) -> int { return o.ptr().getCellRef().getFactionRank(); }; + auto setOwnerFactionRank = [](const ObjectT& object, int factionRank) { + if (std::is_same_v && !dynamic_cast(&object)) + throw std::runtime_error("Local scripts can set an owner faction rank only on self"); + object.ptr().getCellRef().setFactionRank(factionRank); + }; + objectT["ownerFactionRank"] = sol::property(getOwnerFactionRank, setOwnerFactionRank); + + objectT["activateBy"] = [](const ObjectT& object, const ObjectT& actor) { + const MWWorld::Ptr& objPtr = object.ptr(); + const MWWorld::Ptr& actorPtr = actor.ptr(); + uint32_t esmRecordType = actorPtr.getType(); + if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_) + throw std::runtime_error( + "The argument of `activateBy` must be an actor who activates the object. Got: " + + actor.toString()); + if (objPtr.getRefData().activate()) + MWBase::Environment::get().getLuaManager()->objectActivated(objPtr, actorPtr); + }; + + auto isEnabled = [](const ObjectT& o) { return o.ptr().getRefData().isEnabled(); }; + auto setEnabled = [context](const GObject& object, bool enable) { + if (enable && object.ptr().getRefData().isDeleted()) + throw std::runtime_error("Object is removed"); + context.mLuaManager->addAction([object, enable] { + if (object.ptr().getRefData().isDeleted()) + return; + if (object.ptr().isInCell()) + { + if (enable) + MWBase::Environment::get().getWorld()->enable(object.ptr()); + else + MWBase::Environment::get().getWorld()->disable(object.ptr()); + } + else + { + if (enable) + object.ptr().getRefData().enable(); + else + throw std::runtime_error("Objects in containers can't be disabled"); + } + }); + }; + if constexpr (std::is_same_v) + objectT["enabled"] = sol::property(isEnabled, setEnabled); + else + objectT["enabled"] = sol::readonly_property(isEnabled); + + if constexpr (std::is_same_v) + { // Only for global scripts + objectT["setScale"] = [context](const GObject& object, float scale) { + context.mLuaManager->addAction( + [object, scale] { MWBase::Environment::get().getWorld()->scaleObject(object.ptr(), scale); }); + }; + objectT["addScript"] = [context](const GObject& object, std::string_view path, sol::object initData) { + const LuaUtil::ScriptsConfiguration& cfg = context.mLua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + throw std::runtime_error("Unknown script: " + std::string(path)); + if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom)) + throw std::runtime_error( + "Script without CUSTOM tag can not be added dynamically: " + std::string(path)); + if (object.ptr().getType() == ESM::REC_STAT) + throw std::runtime_error("Attaching scripts to Static is not allowed: " + std::string(path)); + if (initData != sol::nil) + context.mLuaManager->addCustomLocalScript(object.ptr(), *scriptId, + LuaUtil::serialize(LuaUtil::cast(initData), context.mSerializer)); + else + context.mLuaManager->addCustomLocalScript( + object.ptr(), *scriptId, cfg[*scriptId].mInitializationData); + }; + objectT["hasScript"] = [lua = context.mLua](const GObject& object, std::string_view path) { + const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + return false; + MWWorld::Ptr ptr = object.ptr(); + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (localScripts) + return localScripts->hasScript(*scriptId); + else + return false; + }; + objectT["removeScript"] = [lua = context.mLua](const GObject& object, std::string_view path) { + const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + throw std::runtime_error("Unknown script: " + std::string(path)); + MWWorld::Ptr ptr = object.ptr(); + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts || !localScripts->hasScript(*scriptId)) + throw std::runtime_error("There is no script " + std::string(path) + " on " + ptr.toString()); + if (localScripts->getAutoStartConf().count(*scriptId) > 0) + throw std::runtime_error("Autostarted script can not be removed: " + std::string(path)); + localScripts->removeScript(*scriptId); + }; + + using DelayedRemovalFn = std::function; + auto removeFn = [](const MWWorld::Ptr ptr, int countToRemove) -> std::optional { + int currentCount = ptr.getRefData().getCount(); + if (countToRemove <= 0 || countToRemove > currentCount) + throw std::runtime_error("Can't remove " + std::to_string(countToRemove) + " of " + + std::to_string(currentCount) + " items"); + ptr.getRefData().setCount(currentCount - countToRemove); // Immediately change count + if (!ptr.getContainerStore() && currentCount > countToRemove) + return std::nullopt; + // Delayed action to trigger side effects + return [countToRemove](MWWorld::Ptr ptr) { + // Restore the original count + ptr.getRefData().setCount(ptr.getRefData().getCount() + countToRemove); + // And now remove properly + if (ptr.getContainerStore()) + ptr.getContainerStore()->remove(ptr, countToRemove); + else + { + MWBase::Environment::get().getWorld()->disable(ptr); + MWBase::Environment::get().getWorld()->deleteObject(ptr); + } + }; + }; + objectT["remove"] = [removeFn, context](const GObject& object, sol::optional count) { + std::optional delayed + = removeFn(object.ptr(), count.value_or(object.ptr().getRefData().getCount())); + if (delayed.has_value()) + context.mLuaManager->addAction([fn = *delayed, object] { fn(object.ptr()); }); + }; + objectT["split"] = [removeFn, context](const GObject& object, int count) -> GObject { + // Doesn't matter which cell to use because the new instance will be in disabled state. + MWWorld::CellStore* cell = MWBase::Environment::get().getWorldScene()->getCurrentCell(); + + const MWWorld::Ptr& ptr = object.ptr(); + MWWorld::Ptr splitted = ptr.getClass().copyToCell(ptr, *cell, count); + splitted.getRefData().disable(); + + std::optional delayedRemovalFn = removeFn(ptr, count); + if (delayedRemovalFn.has_value()) + context.mLuaManager->addAction([fn = *delayedRemovalFn, object] { fn(object.ptr()); }); + + return GObject(splitted); + }; + objectT["moveInto"] = [removeFn, context](const GObject& object, const Inventory& inventory) { + const MWWorld::Ptr& ptr = object.ptr(); + int count = ptr.getRefData().getCount(); + std::optional delayedRemovalFn = removeFn(ptr, count); + context.mLuaManager->addAction([item = object, count, cont = inventory.mObj, delayedRemovalFn] { + const MWWorld::Ptr& oldPtr = item.ptr(); + auto& refData = oldPtr.getRefData(); + refData.setCount(count); // temporarily undo removal to run ContainerStore::add + refData.enable(); + cont.ptr().getClass().getContainerStore(cont.ptr()).add(oldPtr, count, false); + refData.setCount(0); + if (delayedRemovalFn.has_value()) + (*delayedRemovalFn)(oldPtr); + }); + }; + objectT["teleport"] = [removeFn, context](const GObject& object, const sol::object& cellOrName, + const osg::Vec3f& pos, const sol::object& options) { + MWWorld::CellStore* cell = findCell(cellOrName, pos); + MWWorld::Ptr ptr = object.ptr(); + int count = ptr.getRefData().getCount(); + if (count == 0) + throw std::runtime_error("Object is either removed or already in the process of teleporting"); + osg::Vec3f rot = ptr.getRefData().getPosition().asRotationVec3(); + bool placeOnGround = false; + if (LuaUtil::isTransform(options)) + rot = toEulerRotation(options, ptr.getClass().isActor()); + else if (options != sol::nil) + { + sol::table t = LuaUtil::cast(options); + sol::object rotationArg = t["rotation"]; + if (rotationArg != sol::nil) + rot = toEulerRotation(rotationArg, ptr.getClass().isActor()); + placeOnGround = LuaUtil::getValueOrDefault(t["onGround"], placeOnGround); + } + if (ptr.getContainerStore()) + { + DelayedRemovalFn delayedRemovalFn = *removeFn(ptr, count); + context.mLuaManager->addAction( + [object, cell, pos, rot, count, delayedRemovalFn, placeOnGround] { + MWWorld::Ptr oldPtr = object.ptr(); + oldPtr.getRefData().setCount(count); + MWWorld::Ptr newPtr = oldPtr.getClass().moveToCell(oldPtr, *cell); + oldPtr.getRefData().setCount(0); + newPtr.getRefData().disable(); + teleportNotPlayer(newPtr, cell, pos, rot, placeOnGround); + delayedRemovalFn(oldPtr); + }, + "TeleportFromContainerAction"); + } + else if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + context.mLuaManager->addTeleportPlayerAction( + [cell, pos, rot, placeOnGround] { teleportPlayer(cell, pos, rot, placeOnGround); }); + else + { + ptr.getRefData().setCount(0); + context.mLuaManager->addAction( + [object, cell, pos, rot, count, placeOnGround] { + object.ptr().getRefData().setCount(count); + teleportNotPlayer(object.ptr(), cell, pos, rot, placeOnGround); + }, + "TeleportAction"); + } + }; + } + } + + template + void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) + { + using InventoryT = Inventory; + sol::usertype inventoryT = context.mLua->sol().new_usertype(prefix + "Inventory"); + + inventoryT[sol::meta_function::to_string] + = [](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; }; + + inventoryT["getAll"] = [ids = getPackageToTypeTable(context.mLua->sol())]( + const InventoryT& inventory, sol::optional type) { + int mask = -1; + sol::optional typeId = sol::nullopt; + if (type.has_value()) + typeId = ids[*type]; + else + mask = MWWorld::ContainerStore::Type_All; + + if (typeId.has_value()) + { + switch (*typeId) + { + case ESM::REC_ALCH: + mask = MWWorld::ContainerStore::Type_Potion; + break; + case ESM::REC_ARMO: + mask = MWWorld::ContainerStore::Type_Armor; + break; + case ESM::REC_BOOK: + mask = MWWorld::ContainerStore::Type_Book; + break; + case ESM::REC_CLOT: + mask = MWWorld::ContainerStore::Type_Clothing; + break; + case ESM::REC_INGR: + mask = MWWorld::ContainerStore::Type_Ingredient; + break; + case ESM::REC_LIGH: + mask = MWWorld::ContainerStore::Type_Light; + break; + case ESM::REC_MISC: + mask = MWWorld::ContainerStore::Type_Miscellaneous; + break; + case ESM::REC_WEAP: + mask = MWWorld::ContainerStore::Type_Weapon; + break; + case ESM::REC_APPA: + mask = MWWorld::ContainerStore::Type_Apparatus; + break; + case ESM::REC_LOCK: + mask = MWWorld::ContainerStore::Type_Lockpick; + break; + case ESM::REC_PROB: + mask = MWWorld::ContainerStore::Type_Probe; + break; + case ESM::REC_REPA: + mask = MWWorld::ContainerStore::Type_Repair; + break; + default:; + } + } + + if (mask == -1) + throw std::runtime_error( + std::string("Incorrect type argument in inventory:getAll: " + LuaUtil::toString(*type))); + + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + ObjectIdList list = std::make_shared>(); + auto it = store.begin(mask); + while (it.getType() != -1) + { + const MWWorld::Ptr& item = *(it++); + MWBase::Environment::get().getWorldModel()->registerPtr(item); + list->push_back(getId(item)); + } + return ObjectList{ list }; + }; + + inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) { + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + return store.count(ESM::RefId::stringRefId(recordId)); + }; + if constexpr (std::is_same_v) + { + inventoryT["resolve"] = [](const InventoryT& inventory) { + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + store.resolve(); + }; + } + inventoryT["isResolved"] = [](const InventoryT& inventory) -> bool { + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + return store.isResolved(); + }; + inventoryT["find"] = [](const InventoryT& inventory, std::string_view recordId) -> sol::optional { + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + auto itemId = ESM::RefId::stringRefId(recordId); + for (const MWWorld::Ptr& item : store) + { + if (item.getCellRef().getRefId() == itemId) + { + MWBase::Environment::get().getWorldModel()->registerPtr(item); + return ObjectT(getId(item)); + } + } + return sol::nullopt; + }; + inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) { + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + auto itemId = ESM::RefId::stringRefId(recordId); + ObjectIdList list = std::make_shared>(); + for (const MWWorld::Ptr& item : store) + { + if (item.getCellRef().getRefId() == itemId) + { + MWBase::Environment::get().getWorldModel()->registerPtr(item); + list->push_back(getId(item)); + } + } + return ObjectList{ list }; + }; + } + + template + void initObjectBindings(const std::string& prefix, const Context& context) + { + sol::usertype objectT + = context.mLua->sol().new_usertype(prefix + "Object", sol::base_classes, sol::bases()); + addBasicBindings(objectT, context); + addInventoryBindings(objectT, prefix, context); + + registerObjectList(prefix, context); + } + } // namespace + + void initObjectBindingsForLocalScripts(const Context& context) + { + initObjectBindings("L", context); + } + + void initObjectBindingsForGlobalScripts(const Context& context) + { + initObjectBindings("G", context); + } + +} diff --git a/apps/openmw/mwlua/objectbindings.hpp b/apps/openmw/mwlua/objectbindings.hpp new file mode 100644 index 000000000..5f69c54da --- /dev/null +++ b/apps/openmw/mwlua/objectbindings.hpp @@ -0,0 +1,12 @@ +#ifndef MWLUA_OBJECTBINDINGS_H +#define MWLUA_OBJECTBINDINGS_H + +#include "context.hpp" + +namespace MWLua +{ + void initObjectBindingsForLocalScripts(const Context&); + void initObjectBindingsForGlobalScripts(const Context&); +} + +#endif // MWLUA_OBJECTBINDINGS_H diff --git a/apps/openmw/mwlua/objectvariant.hpp b/apps/openmw/mwlua/objectvariant.hpp new file mode 100644 index 000000000..238ad7cfa --- /dev/null +++ b/apps/openmw/mwlua/objectvariant.hpp @@ -0,0 +1,59 @@ +#ifndef MWLUA_OBJECTVARIANT_H +#define MWLUA_OBJECTVARIANT_H + +#include + +#include "localscripts.hpp" +#include "object.hpp" + +namespace MWLua +{ + + class ObjectVariant + { + public: + explicit ObjectVariant(const sol::object& obj) + { + if (obj.is()) + mVariant.emplace(obj.as()); + else if (obj.is()) + mVariant.emplace(obj.as()); + else if (obj.is()) + mVariant.emplace(obj.as()); + else + throw std::runtime_error("Expected game object, got: " + LuaUtil::toString(obj)); + } + + bool isSelfObject() const { return std::holds_alternative(mVariant); } + bool isLObject() const { return std::holds_alternative(mVariant); } + bool isGObject() const { return std::holds_alternative(mVariant); } + + SelfObject* asSelfObject() const + { + if (!isSelfObject()) + throw std::runtime_error("Allowed only in local scripts for 'openmw.self'."); + return std::get(mVariant); + } + + const MWWorld::Ptr& ptr() const + { + return std::visit( + [](auto&& variant) -> const MWWorld::Ptr& { + using T = std::decay_t; + if constexpr (std::is_same_v) + return variant->ptr(); + else + return variant.ptr(); + }, + mVariant); + } + + Object object() const { return Object(ptr()); } + + private: + std::variant mVariant; + }; + +} // namespace MWLua + +#endif // MWLUA_OBJECTVARIANT_H diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp new file mode 100644 index 000000000..577e93a55 --- /dev/null +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -0,0 +1,82 @@ +#ifndef MWLUA_PLAYERSCRIPTS_H +#define MWLUA_PLAYERSCRIPTS_H + +#include + +#include + +#include "../mwbase/luamanager.hpp" + +#include "localscripts.hpp" + +namespace MWLua +{ + + class PlayerScripts : public LocalScripts + { + public: + PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) + : LocalScripts(lua, obj) + { + registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers, + &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers, + &mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved }); + } + + void processInputEvent(const MWBase::LuaManager::InputEvent& event) + { + using InputEvent = MWBase::LuaManager::InputEvent; + switch (event.mType) + { + case InputEvent::KeyPressed: + callEngineHandlers(mKeyPressHandlers, std::get(event.mValue)); + break; + case InputEvent::KeyReleased: + callEngineHandlers(mKeyReleaseHandlers, std::get(event.mValue)); + break; + case InputEvent::ControllerPressed: + callEngineHandlers(mControllerButtonPressHandlers, std::get(event.mValue)); + break; + case InputEvent::ControllerReleased: + callEngineHandlers(mControllerButtonReleaseHandlers, std::get(event.mValue)); + break; + case InputEvent::Action: + callEngineHandlers(mActionHandlers, std::get(event.mValue)); + break; + case InputEvent::TouchPressed: + callEngineHandlers(mTouchpadPressed, std::get(event.mValue)); + break; + case InputEvent::TouchReleased: + callEngineHandlers(mTouchpadReleased, std::get(event.mValue)); + break; + case InputEvent::TouchMoved: + callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); + break; + } + } + + void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); } + + bool consoleCommand( + const std::string& consoleMode, const std::string& command, const sol::object& selectedObject) + { + callEngineHandlers(mConsoleCommandHandlers, consoleMode, command, selectedObject); + return !mConsoleCommandHandlers.mList.empty(); + } + + private: + EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" }; + EngineHandlerList mKeyPressHandlers{ "onKeyPress" }; + EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" }; + EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" }; + EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" }; + EngineHandlerList mActionHandlers{ "onInputAction" }; + EngineHandlerList mOnFrameHandlers{ "onFrame" }; + EngineHandlerList mTouchpadPressed{ "onTouchPress" }; + EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; + EngineHandlerList mTouchpadMoved{ "onTouchMove" }; + }; + +} + +#endif // MWLUA_PLAYERSCRIPTS_H diff --git a/apps/openmw/mwlua/postprocessingbindings.cpp b/apps/openmw/mwlua/postprocessingbindings.cpp new file mode 100644 index 000000000..f63ba5e70 --- /dev/null +++ b/apps/openmw/mwlua/postprocessingbindings.cpp @@ -0,0 +1,166 @@ +#include "postprocessingbindings.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwrender/postprocessor.hpp" + +#include "luamanagerimp.hpp" + +namespace MWLua +{ + struct Shader; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + struct Shader + { + std::shared_ptr mShader; + + Shader(std::shared_ptr shader) + : mShader(std::move(shader)) + { + } + + std::string toString() const + { + if (!mShader) + return "Shader(nil)"; + + return Misc::StringUtils::format("Shader(%s, %s)", mShader->getName(), mShader->getFileName()); + } + + enum + { + Action_None, + Action_Enable, + Action_Disable + } mQueuedAction + = Action_None; + }; + + template + auto getSetter(const Context& context) + { + return [context](const Shader& shader, const std::string& name, const T& value) { + context.mLuaManager->addAction( + [=] { + MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(shader.mShader, name, value); + }, + "SetUniformShaderAction"); + }; + } + + template + auto getArraySetter(const Context& context) + { + return [context](const Shader& shader, const std::string& name, const sol::table& table) { + auto targetSize + = MWBase::Environment::get().getWorld()->getPostProcessor()->getUniformSize(shader.mShader, name); + + if (!targetSize.has_value()) + throw std::runtime_error(Misc::StringUtils::format("Failed setting uniform array '%s'", name)); + + if (*targetSize != table.size()) + throw std::runtime_error(Misc::StringUtils::format( + "Mismatching uniform array size, got %zu expected %zu", table.size(), *targetSize)); + + std::vector values; + values.reserve(*targetSize); + + for (size_t i = 0; i < *targetSize; ++i) + { + sol::object obj = table[i + 1]; + if (!obj.is()) + throw std::runtime_error("Invalid type for uniform array"); + values.push_back(obj.as()); + } + + context.mLuaManager->addAction( + [=] { + MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(shader.mShader, name, values); + }, + "SetUniformShaderAction"); + }; + } + + sol::table initPostprocessingPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + sol::usertype shader = context.mLua->sol().new_usertype("Shader"); + shader[sol::meta_function::to_string] = [](const Shader& shader) { return shader.toString(); }; + + shader["enable"] = [context](Shader& shader, sol::optional optPos) { + std::optional pos = std::nullopt; + if (optPos) + pos = optPos.value(); + + if (shader.mShader && shader.mShader->isValid()) + shader.mQueuedAction = Shader::Action_Enable; + + context.mLuaManager->addAction([=, &shader] { + shader.mQueuedAction = Shader::Action_None; + + if (MWBase::Environment::get().getWorld()->getPostProcessor()->enableTechnique(shader.mShader, pos) + == MWRender::PostProcessor::Status_Error) + throw std::runtime_error("Failed enabling shader '" + shader.mShader->getName() + "'"); + }); + }; + + shader["disable"] = [context](Shader& shader) { + shader.mQueuedAction = Shader::Action_Disable; + + context.mLuaManager->addAction([&] { + shader.mQueuedAction = Shader::Action_None; + + if (MWBase::Environment::get().getWorld()->getPostProcessor()->disableTechnique(shader.mShader) + == MWRender::PostProcessor::Status_Error) + throw std::runtime_error("Failed disabling shader '" + shader.mShader->getName() + "'"); + }); + }; + + shader["isEnabled"] = [](const Shader& shader) { + if (shader.mQueuedAction == Shader::Action_Enable) + return true; + else if (shader.mQueuedAction == Shader::Action_Disable) + return false; + return MWBase::Environment::get().getWorld()->getPostProcessor()->isTechniqueEnabled(shader.mShader); + }; + + shader["setBool"] = getSetter(context); + shader["setFloat"] = getSetter(context); + shader["setInt"] = getSetter(context); + shader["setVector2"] = getSetter(context); + shader["setVector3"] = getSetter(context); + shader["setVector4"] = getSetter(context); + + shader["setFloatArray"] = getArraySetter(context); + shader["setIntArray"] = getArraySetter(context); + shader["setVector2Array"] = getArraySetter(context); + shader["setVector3Array"] = getArraySetter(context); + shader["setVector4Array"] = getArraySetter(context); + + api["load"] = [](const std::string& name) { + Shader shader{ MWBase::Environment::get().getWorld()->getPostProcessor()->loadTechnique(name, false) }; + + if (!shader.mShader || !shader.mShader->isValid()) + throw std::runtime_error(Misc::StringUtils::format("Failed loading shader '%s'", name)); + + if (!shader.mShader->getDynamic()) + throw std::runtime_error(Misc::StringUtils::format("Shader '%s' is not marked as dynamic", name)); + + return shader; + }; + + return LuaUtil::makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/postprocessingbindings.hpp b/apps/openmw/mwlua/postprocessingbindings.hpp new file mode 100644 index 000000000..50cd84fa2 --- /dev/null +++ b/apps/openmw/mwlua/postprocessingbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_POSTPROCESSINGBINDINGS_H +#define MWLUA_POSTPROCESSINGBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initPostprocessingPackage(const Context&); +} + +#endif // MWLUA_POSTPROCESSINGBINDINGS_H diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp new file mode 100644 index 000000000..da4d2a461 --- /dev/null +++ b/apps/openmw/mwlua/stats.cpp @@ -0,0 +1,398 @@ +#include "stats.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include "context.hpp" +#include "localscripts.hpp" +#include "luamanagerimp.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "objectvariant.hpp" + +namespace +{ + using SelfObject = MWLua::SelfObject; + using ObjectVariant = MWLua::ObjectVariant; + using Index = const SelfObject::CachedStat::Index&; + + template + auto addIndexedAccessor(Index index) + { + return [index](const sol::object& o) { return T::create(ObjectVariant(o), index); }; + } + + template + void addProp(const MWLua::Context& context, sol::usertype& type, std::string_view prop, G getter) + { + type[prop] = sol::property([=](const T& stat) { return stat.get(context, prop, getter); }, + [=](const T& stat, const sol::object& value) { stat.cache(context, prop, value); }); + } + + template + sol::object getValue(const MWLua::Context& context, const ObjectVariant& obj, SelfObject::CachedStat::Setter setter, + Index index, std::string_view prop, G getter) + { + if (obj.isSelfObject()) + { + SelfObject* self = obj.asSelfObject(); + auto it = self->mStatsCache.find({ setter, index, prop }); + if (it != self->mStatsCache.end()) + return it->second; + } + return sol::make_object(context.mLua->sol(), getter(obj.ptr())); + } +} + +namespace MWLua +{ + static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj) + { + if (!obj.mStatsCache.empty()) + return; // was already added before + manager->addAction( + [obj = Object(obj)] { + LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + if (scripts) + scripts->applyStatsCache(); + }, + "StatUpdateAction"); + } + + class LevelStat + { + ObjectVariant mObject; + + LevelStat(ObjectVariant object) + : mObject(std::move(object)) + { + } + + public: + sol::object getCurrent(const Context& context) const + { + return getValue(context, mObject, &LevelStat::setValue, 0, "current", + [](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); }); + } + + void setCurrent(const Context& context, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, 0, "current" }] = value; + } + + sol::object getProgress(const Context& context) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return sol::nil; + return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress()); + } + + static std::optional create(ObjectVariant object, Index) + { + if (!object.ptr().getClass().isActor()) + return {}; + return LevelStat{ std::move(object) }; + } + + static void setValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + if (prop == "current") + stats.setLevel(LuaUtil::cast(value)); + } + }; + + class DynamicStat + { + ObjectVariant mObject; + int mIndex; + + DynamicStat(ObjectVariant object, int index) + : mObject(std::move(object)) + , mIndex(index) + { + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue( + context, mObject, &DynamicStat::setValue, mIndex, prop, [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).*getter)(); + }); + } + + static std::optional create(ObjectVariant object, Index i) + { + if (!object.ptr().getClass().isActor()) + return {}; + int index = std::get(i); + return DynamicStat{ std::move(object), index }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = value; + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + int index = std::get(i); + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getDynamic(index); + float floatValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(floatValue); + else if (prop == "current") + stat.setCurrent(floatValue, true, true); + else if (prop == "modifier") + stat.setModifier(floatValue); + stats.setDynamic(index, stat); + } + }; + + class AttributeStat + { + ObjectVariant mObject; + int mIndex; + + AttributeStat(ObjectVariant object, int index) + : mObject(std::move(object)) + , mIndex(index) + { + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + auto id = static_cast(mIndex); + return getValue( + context, mObject, &AttributeStat::setValue, mIndex, prop, [id, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getCreatureStats(ptr).getAttribute(id).*getter)(); + }); + } + + float getModified(const Context& context) const + { + auto base = LuaUtil::cast(get(context, "base", &MWMechanics::AttributeValue::getBase)); + auto damage = LuaUtil::cast(get(context, "damage", &MWMechanics::AttributeValue::getDamage)); + auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::AttributeValue::getModifier)); + return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified + } + + static std::optional create(ObjectVariant object, Index i) + { + if (!object.ptr().getClass().isActor()) + return {}; + int index = std::get(i); + return AttributeStat{ std::move(object), index }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mIndex, prop }] = value; + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto id = static_cast(std::get(i)); + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getAttribute(id); + float floatValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(floatValue); + else if (prop == "damage") + { + stat.restore(stat.getDamage()); + stat.damage(floatValue); + } + else if (prop == "modifier") + stat.setModifier(floatValue); + stats.setAttribute(id, stat); + } + }; + + class SkillStat + { + ObjectVariant mObject; + ESM::RefId mId; + + SkillStat(ObjectVariant object, ESM::RefId id) + : mObject(std::move(object)) + , mId(id) + { + } + + static float getProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat) + { + float progress = stat.getProgress(); + if (progress != 0.f) + progress /= getMaxProgress(ptr, id, stat); + return progress; + } + + static float getMaxProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat) + { + const auto& store = *MWBase::Environment::get().getESMStore(); + const auto cl = store.get().find(ptr.get()->mBase->mClass); + return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(id, *cl); + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue(context, mObject, &SkillStat::setValue, mId, prop, [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getNpcStats(ptr).getSkill(mId).*getter)(); + }); + } + + float getModified(const Context& context) const + { + auto base = LuaUtil::cast(get(context, "base", &MWMechanics::SkillValue::getBase)); + auto damage = LuaUtil::cast(get(context, "damage", &MWMechanics::SkillValue::getDamage)); + auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::SkillValue::getModifier)); + return std::max(0.f, base - damage + modifier); // Should match SkillValue::getModified + } + + sol::object getProgress(const Context& context) const + { + return getValue(context, mObject, &SkillStat::setValue, mId, "progress", [this](const MWWorld::Ptr& ptr) { + return getProgress(ptr, mId, ptr.getClass().getNpcStats(ptr).getSkill(mId)); + }); + } + + static std::optional create(ObjectVariant object, Index index) + { + if (!object.ptr().getClass().isNpc()) + return {}; + ESM::RefId id = std::get(index); + return SkillStat{ std::move(object), id }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = value; + } + + static void setValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + ESM::RefId id = std::get(index); + auto& stats = ptr.getClass().getNpcStats(ptr); + auto stat = stats.getSkill(id); + float floatValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(floatValue); + else if (prop == "damage") + { + stat.restore(stat.getDamage()); + stat.damage(floatValue); + } + else if (prop == "modifier") + stat.setModifier(floatValue); + else if (prop == "progress") + stat.setProgress(floatValue * getMaxProgress(ptr, id, stat)); + stats.setSkill(id, stat); + } + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addActorStatsBindings(sol::table& actor, const Context& context) + { + sol::table stats(context.mLua->sol(), sol::create); + actor["stats"] = LuaUtil::makeReadOnly(stats); + + auto levelStatT = context.mLua->sol().new_usertype("LevelStat"); + levelStatT["current"] = sol::property([context](const LevelStat& stat) { return stat.getCurrent(context); }, + [context](const LevelStat& stat, const sol::object& value) { stat.setCurrent(context, value); }); + levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }); + stats["level"] = addIndexedAccessor(0); + + auto dynamicStatT = context.mLua->sol().new_usertype("DynamicStat"); + addProp(context, dynamicStatT, "base", &MWMechanics::DynamicStat::getBase); + addProp(context, dynamicStatT, "current", &MWMechanics::DynamicStat::getCurrent); + addProp(context, dynamicStatT, "modifier", &MWMechanics::DynamicStat::getModifier); + sol::table dynamic(context.mLua->sol(), sol::create); + stats["dynamic"] = LuaUtil::makeReadOnly(dynamic); + dynamic["health"] = addIndexedAccessor(0); + dynamic["magicka"] = addIndexedAccessor(1); + dynamic["fatigue"] = addIndexedAccessor(2); + + auto attributeStatT = context.mLua->sol().new_usertype("AttributeStat"); + addProp(context, attributeStatT, "base", &MWMechanics::AttributeValue::getBase); + addProp(context, attributeStatT, "damage", &MWMechanics::AttributeValue::getDamage); + attributeStatT["modified"] + = sol::property([=](const AttributeStat& stat) { return stat.getModified(context); }); + addProp(context, attributeStatT, "modifier", &MWMechanics::AttributeValue::getModifier); + sol::table attributes(context.mLua->sol(), sol::create); + stats["attributes"] = LuaUtil::makeReadOnly(attributes); + for (int id = ESM::Attribute::Strength; id < ESM::Attribute::Length; ++id) + attributes[Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[id])] + = addIndexedAccessor(id); + } + + void addNpcStatsBindings(sol::table& npc, const Context& context) + { + sol::table npcStats(context.mLua->sol(), sol::create); + sol::table baseMeta(context.mLua->sol(), sol::create); + baseMeta[sol::meta_function::index] = LuaUtil::getMutableFromReadOnly(npc["baseType"]["stats"]); + npcStats[sol::metatable_key] = baseMeta; + npc["stats"] = LuaUtil::makeReadOnly(npcStats); + + auto skillStatT = context.mLua->sol().new_usertype("SkillStat"); + addProp(context, skillStatT, "base", &MWMechanics::SkillValue::getBase); + addProp(context, skillStatT, "damage", &MWMechanics::SkillValue::getDamage); + skillStatT["modified"] = sol::property([=](const SkillStat& stat) { return stat.getModified(context); }); + addProp(context, skillStatT, "modifier", &MWMechanics::SkillValue::getModifier); + skillStatT["progress"] = sol::property([context](const SkillStat& stat) { return stat.getProgress(context); }, + [context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); }); + sol::table skills(context.mLua->sol(), sol::create); + npcStats["skills"] = LuaUtil::makeReadOnly(skills); + for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get()) + skills[Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[skill.mIndex])] + = addIndexedAccessor(skill.mId); + } +} diff --git a/apps/openmw/mwlua/stats.hpp b/apps/openmw/mwlua/stats.hpp new file mode 100644 index 000000000..8c5824cc7 --- /dev/null +++ b/apps/openmw/mwlua/stats.hpp @@ -0,0 +1,14 @@ +#ifndef MWLUA_STATS_H +#define MWLUA_STATS_H + +#include + +namespace MWLua +{ + struct Context; + + void addActorStatsBindings(sol::table& actor, const Context& context); + void addNpcStatsBindings(sol::table& npc, const Context& context); +} + +#endif diff --git a/apps/openmw/mwlua/types/activator.cpp b/apps/openmw/mwlua/types/activator.cpp new file mode 100644 index 000000000..77ffd384c --- /dev/null +++ b/apps/openmw/mwlua/types/activator.cpp @@ -0,0 +1,54 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} +namespace +{ + // Populates a activator struct from a Lua table. + ESM::Activator tableToActivator(const sol::table& rec) + { + ESM::Activator activator; + activator.mName = rec["name"]; + activator.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + std::string_view scriptId = rec["mwscript"].get(); + activator.mScript = ESM::RefId::deserializeText(scriptId); + return activator; + } +} + +namespace MWLua +{ + void addActivatorBindings(sol::table activator, const Context& context) + { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + activator["createRecordDraft"] = tableToActivator; + addRecordFunctionBinding(activator, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Activator"); + record[sol::meta_function::to_string] + = [](const ESM::Activator& rec) { return "ESM3_Activator[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Activator& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Activator& rec) -> std::string { return rec.mScript.serializeText(); }); + } +} diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp new file mode 100644 index 000000000..42d25789f --- /dev/null +++ b/apps/openmw/mwlua/types/actor.cpp @@ -0,0 +1,362 @@ +#include "types.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../localscripts.hpp" +#include "../luamanagerimp.hpp" +#include "../magicbindings.hpp" +#include "../stats.hpp" + +namespace MWLua +{ + using EquipmentItem = std::variant; + using Equipment = std::map; + static constexpr int sAnySlot = -1; + + static std::pair findInInventory( + MWWorld::InventoryStore& store, const EquipmentItem& item, int slot = sAnySlot) + { + auto old_it = slot != sAnySlot ? store.getSlot(slot) : store.end(); + MWWorld::Ptr itemPtr; + + if (std::holds_alternative(item)) + { + itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(std::get(item)); + if (old_it != store.end() && *old_it == itemPtr) + return { old_it, true }; // already equipped + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 + || itemPtr.getContainerStore() != static_cast(&store)) + { + Log(Debug::Warning) << "Object" << std::get(item).toString() << " is not in inventory"; + return { store.end(), false }; + } + } + else + { + ESM::RefId recordId = ESM::RefId::deserializeText(std::get(item)); + if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) + return { old_it, true }; // already equipped + itemPtr = store.search(recordId); + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) + { + Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; + return { store.end(), false }; + } + } + + // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. + MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); + if (it == store.end()) // should never happen + throw std::logic_error("Item not found in container"); + + return { it, false }; + } + + static void setEquipment(const MWWorld::Ptr& actor, const Equipment& equipment) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + std::array usedSlots; + std::fill(usedSlots.begin(), usedSlots.end(), false); + + auto tryEquipToSlot = [&store, &usedSlots](int slot, const EquipmentItem& item) -> bool { + auto [it, alreadyEquipped] = findInInventory(store, item, slot); + if (alreadyEquipped) + return true; + if (it == store.end()) + return false; + MWWorld::Ptr itemPtr = *it; + + auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); + bool requestedSlotIsAllowed + = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end(); + if (!requestedSlotIsAllowed) + { + auto firstAllowed + = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); + if (firstAllowed == allowedSlots.end()) + { + Log(Debug::Warning) << "No suitable slot for " << itemPtr.toString(); + return false; + } + slot = *firstAllowed; + } + + store.equip(slot, it); + return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed + }; + + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + auto old_it = store.getSlot(slot); + auto new_it = equipment.find(slot); + if (new_it == equipment.end()) + { + if (old_it != store.end()) + store.unequipSlot(slot); + continue; + } + if (tryEquipToSlot(slot, new_it->second)) + usedSlots[slot] = true; + } + for (const auto& [slot, item] : equipment) + if (slot >= MWWorld::InventoryStore::Slots) + tryEquipToSlot(sAnySlot, item); + } + + static void setSelectedEnchantedItem(const MWWorld::Ptr& actor, const EquipmentItem& item) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + // We're not passing in a specific slot, so ignore the already equipped return value + auto [it, _] = findInInventory(store, item, sAnySlot); + if (it == store.end()) + return; + + MWWorld::Ptr itemPtr = *it; + + // Equip the item if applicable + auto slots = itemPtr.getClass().getEquipmentSlots(itemPtr); + if (!slots.first.empty()) + { + bool alreadyEquipped = false; + for (auto slot : slots.first) + { + if (store.getSlot(slot) == it) + alreadyEquipped = true; + } + + if (!alreadyEquipped) + { + MWBase::Environment::get().getWindowManager()->useItem(itemPtr); + // make sure that item was successfully equipped + if (!store.isEquipped(itemPtr)) + return; + } + } + + store.setSelectedEnchantItem(it); + // to reset WindowManager::mSelectedSpell immediately + MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(*it); + } + + void addActorBindings(sol::table actor, const Context& context) + { + actor["STANCE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Nothing", MWMechanics::DrawState::Nothing }, + { "Weapon", MWMechanics::DrawState::Weapon }, + { "Spell", MWMechanics::DrawState::Spell }, + })); + actor["EQUIPMENT_SLOT"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs( + { { "Helmet", MWWorld::InventoryStore::Slot_Helmet }, { "Cuirass", MWWorld::InventoryStore::Slot_Cuirass }, + { "Greaves", MWWorld::InventoryStore::Slot_Greaves }, + { "LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron }, + { "RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron }, + { "LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet }, + { "RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet }, + { "Boots", MWWorld::InventoryStore::Slot_Boots }, { "Shirt", MWWorld::InventoryStore::Slot_Shirt }, + { "Pants", MWWorld::InventoryStore::Slot_Pants }, { "Skirt", MWWorld::InventoryStore::Slot_Skirt }, + { "Robe", MWWorld::InventoryStore::Slot_Robe }, { "LeftRing", MWWorld::InventoryStore::Slot_LeftRing }, + { "RightRing", MWWorld::InventoryStore::Slot_RightRing }, + { "Amulet", MWWorld::InventoryStore::Slot_Amulet }, { "Belt", MWWorld::InventoryStore::Slot_Belt }, + { "CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight }, + { "CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft }, + { "Ammunition", MWWorld::InventoryStore::Slot_Ammunition } })); + + actor["getStance"] = [](const Object& o) { + const MWWorld::Class& cls = o.ptr().getClass(); + if (cls.isActor()) + return cls.getCreatureStats(o.ptr()).getDrawState(); + else + throw std::runtime_error("Actor expected"); + }; + actor["stance"] = actor["getStance"]; // for compatibility; should be removed later + actor["setStance"] = [](const SelfObject& self, int stance) { + const MWWorld::Class& cls = self.ptr().getClass(); + if (!cls.isActor()) + throw std::runtime_error("Actor expected"); + auto& stats = cls.getCreatureStats(self.ptr()); + if (stance != static_cast(MWMechanics::DrawState::Nothing) + && stance != static_cast(MWMechanics::DrawState::Weapon) + && stance != static_cast(MWMechanics::DrawState::Spell)) + { + throw std::runtime_error("Incorrect stance"); + } + MWMechanics::DrawState newDrawState = static_cast(stance); + if (stats.getDrawState() == newDrawState) + return; + if (newDrawState == MWMechanics::DrawState::Spell) + { + bool hasSelectedSpell; + if (self.ptr() == MWBase::Environment::get().getWorld()->getPlayerPtr()) + // For the player selecting spell in UI doesn't change selected spell in CreatureStats (was + // implemented this way to prevent changing spell during casting, probably should be refactored), so + // we have to handle the player separately. + hasSelectedSpell = !MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty(); + else + hasSelectedSpell = !stats.getSpells().getSelectedSpell().empty(); + if (!hasSelectedSpell) + { + if (!cls.hasInventoryStore(self.ptr())) + return; // No selected spell and no items; can't use magic stance. + MWWorld::InventoryStore& store = cls.getInventoryStore(self.ptr()); + if (store.getSelectedEnchantItem() == store.end()) + return; // No selected spell and no selected enchanted item; can't use magic stance. + } + } + MWBase::MechanicsManager* mechanics = MWBase::Environment::get().getMechanicsManager(); + // We want to interrupt animation only if attack is preparing, but still is not triggered. + // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle + // Weapon" key twice. + if (mechanics->isAttackPreparing(self.ptr())) + stats.setAttackingOrSpell(false); // interrupt attack + else if (mechanics->isAttackingOrSpell(self.ptr())) + return; // can't be interrupted; ignore setStance + stats.setDrawState(newDrawState); + }; + + actor["getSelectedEnchantedItem"] = [](sol::this_state lua, const Object& o) -> sol::object { + const MWWorld::Ptr& ptr = o.ptr(); + if (!ptr.getClass().hasInventoryStore(ptr)) + return sol::nil; + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + auto it = store.getSelectedEnchantItem(); + if (it == store.end()) + return sol::nil; + MWBase::Environment::get().getWorldModel()->registerPtr(*it); + if (dynamic_cast(&o)) + return sol::make_object(lua, GObject(*it)); + else + return sol::make_object(lua, LObject(*it)); + }; + actor["setSelectedEnchantedItem"] = [context](const SelfObject& obj, const sol::object& item) { + const MWWorld::Ptr& ptr = obj.ptr(); + if (!ptr.getClass().hasInventoryStore(ptr)) + return; + + EquipmentItem ei; + if (item.is()) + { + ei = LuaUtil::cast(item).id(); + } + else + { + ei = LuaUtil::cast(item); + } + context.mLuaManager->addAction([obj = Object(ptr), ei = ei] { setSelectedEnchantedItem(obj.ptr(), ei); }, + "setSelectedEnchantedItemAction"); + }; + + actor["canMove"] = [](const Object& o) { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getMaxSpeed(o.ptr()) > 0; + }; + actor["getRunSpeed"] = [](const Object& o) { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getRunSpeed(o.ptr()); + }; + actor["getWalkSpeed"] = [](const Object& o) { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getWalkSpeed(o.ptr()); + }; + actor["getCurrentSpeed"] = [](const Object& o) { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getCurrentSpeed(o.ptr()); + }; + + // for compatibility; should be removed later + actor["runSpeed"] = actor["getRunSpeed"]; + actor["walkSpeed"] = actor["getWalkSpeed"]; + actor["currentSpeed"] = actor["getCurrentSpeed"]; + + actor["isOnGround"] + = [](const LObject& o) { return MWBase::Environment::get().getWorld()->isOnGround(o.ptr()); }; + actor["isSwimming"] + = [](const LObject& o) { return MWBase::Environment::get().getWorld()->isSwimming(o.ptr()); }; + + actor["inventory"] = sol::overload([](const LObject& o) { return Inventory{ o }; }, + [](const GObject& o) { return Inventory{ o }; }); + auto getAllEquipment = [](sol::this_state lua, const Object& o) { + const MWWorld::Ptr& ptr = o.ptr(); + sol::table equipment(lua, sol::create); + if (!ptr.getClass().hasInventoryStore(ptr)) + return equipment; + + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + auto it = store.getSlot(slot); + if (it == store.end()) + continue; + MWBase::Environment::get().getWorldModel()->registerPtr(*it); + if (dynamic_cast(&o)) + equipment[slot] = sol::make_object(lua, GObject(*it)); + else + equipment[slot] = sol::make_object(lua, LObject(*it)); + } + return equipment; + }; + auto getEquipmentFromSlot = [](sol::this_state lua, const Object& o, int slot) -> sol::object { + const MWWorld::Ptr& ptr = o.ptr(); + if (!ptr.getClass().hasInventoryStore(ptr)) + return sol::nil; + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + auto it = store.getSlot(slot); + if (it == store.end()) + return sol::nil; + MWBase::Environment::get().getWorldModel()->registerPtr(*it); + if (dynamic_cast(&o)) + return sol::make_object(lua, GObject(*it)); + else + return sol::make_object(lua, LObject(*it)); + }; + actor["getEquipment"] = sol::overload(getAllEquipment, getEquipmentFromSlot); + actor["equipment"] = actor["getEquipment"]; // for compatibility; should be removed later + actor["hasEquipped"] = [](const Object& o, const Object& item) { + const MWWorld::Ptr& ptr = o.ptr(); + if (!ptr.getClass().hasInventoryStore(ptr)) + return false; + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + return store.isEquipped(item.ptr()); + }; + actor["setEquipment"] = [context](const SelfObject& obj, const sol::table& equipment) { + const MWWorld::Ptr& ptr = obj.ptr(); + if (!ptr.getClass().hasInventoryStore(ptr)) + { + if (!equipment.empty()) + throw std::runtime_error(obj.toString() + " has no equipment slots"); + return; + } + Equipment eqp; + for (auto& [key, value] : equipment) + { + int slot = LuaUtil::cast(key); + if (value.is()) + eqp[slot] = LuaUtil::cast(value).id(); + else + eqp[slot] = LuaUtil::cast(value); + } + context.mLuaManager->addAction( + [obj = Object(ptr), eqp = std::move(eqp)] { setEquipment(obj.ptr(), eqp); }, "SetEquipmentAction"); + }; + actor["getPathfindingAgentBounds"] = [](sol::this_state lua, const LObject& o) { + const DetourNavigator::AgentBounds agentBounds + = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(o.ptr()); + sol::table result(lua, sol::create); + result["shapeType"] = agentBounds.mShapeType; + result["halfExtents"] = agentBounds.mHalfExtents; + return result; + }; + + addActorStatsBindings(actor, context); + addActorMagicBindings(actor, context); + } + +} diff --git a/apps/openmw/mwlua/types/apparatus.cpp b/apps/openmw/mwlua/types/apparatus.cpp new file mode 100644 index 000000000..10bdbcdd2 --- /dev/null +++ b/apps/openmw/mwlua/types/apparatus.cpp @@ -0,0 +1,55 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addApparatusBindings(sol::table apparatus, const Context& context) + { + apparatus["TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "MortarPestle", ESM::Apparatus::MortarPestle }, + { "Alembic", ESM::Apparatus::Alembic }, + { "Calcinator", ESM::Apparatus::Calcinator }, + { "Retort", ESM::Apparatus::Retort }, + })); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(apparatus, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Apparatus"); + record[sol::meta_function::to_string] + = [](const ESM::Apparatus& rec) { return "ESM3_Apparatus[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Apparatus& rec) -> std::string { return rec.mScript.serializeText(); }); + record["icon"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["type"] = sol::readonly_property([](const ESM::Apparatus& rec) -> int { return rec.mData.mType; }); + record["value"] = sol::readonly_property([](const ESM::Apparatus& rec) -> int { return rec.mData.mValue; }); + record["weight"] = sol::readonly_property([](const ESM::Apparatus& rec) -> float { return rec.mData.mWeight; }); + record["quality"] + = sol::readonly_property([](const ESM::Apparatus& rec) -> float { return rec.mData.mQuality; }); + } +} diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp new file mode 100644 index 000000000..1006dcefc --- /dev/null +++ b/apps/openmw/mwlua/types/armor.cpp @@ -0,0 +1,95 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} +namespace +{ + // Populates an armor struct from a Lua table. + ESM::Armor tableToArmor(const sol::table& rec) + { + ESM::Armor armor; + armor.mName = rec["name"]; + armor.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + armor.mIcon = rec["icon"]; + std::string_view enchantId = rec["enchant"].get(); + armor.mEnchant = ESM::RefId::deserializeText(enchantId); + std::string_view scriptId = rec["mwscript"].get(); + armor.mScript = ESM::RefId::deserializeText(scriptId); + + armor.mData.mWeight = rec["weight"]; + armor.mData.mValue = rec["value"]; + int armorType = rec["type"].get(); + if (armorType >= 0 && armorType <= ESM::Armor::RBracer) + armor.mData.mType = armorType; + else + throw std::runtime_error("Invalid Armor Type provided: " + std::to_string(armorType)); + armor.mData.mHealth = rec["health"]; + armor.mData.mArmor = rec["baseArmor"]; + armor.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); + + return armor; + } +} + +namespace MWLua +{ + void addArmorBindings(sol::table armor, const Context& context) + { + armor["TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Helmet", ESM::Armor::Helmet }, + { "Cuirass", ESM::Armor::Cuirass }, + { "LPauldron", ESM::Armor::LPauldron }, + { "RPauldron", ESM::Armor::RPauldron }, + { "Greaves", ESM::Armor::Greaves }, + { "Boots", ESM::Armor::Boots }, + { "LGauntlet", ESM::Armor::LGauntlet }, + { "RGauntlet", ESM::Armor::RGauntlet }, + { "Shield", ESM::Armor::Shield }, + { "LBracer", ESM::Armor::LBracer }, + { "RBracer", ESM::Armor::RBracer }, + })); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(armor, context); + + armor["createRecordDraft"] = tableToArmor; + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Armor"); + record[sol::meta_function::to_string] + = [](const ESM::Armor& rec) -> std::string { return "ESM3_Armor[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["enchant"] + = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["mwscript"] + = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mScript.serializeText(); }); + record["weight"] = sol::readonly_property([](const ESM::Armor& rec) -> float { return rec.mData.mWeight; }); + record["value"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mValue; }); + record["type"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mType; }); + record["health"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mHealth; }); + record["baseArmor"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mArmor; }); + record["enchantCapacity"] + = sol::readonly_property([](const ESM::Armor& rec) -> float { return rec.mData.mEnchant * 0.1f; }); + } +} diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp new file mode 100644 index 000000000..f7120e529 --- /dev/null +++ b/apps/openmw/mwlua/types/book.cpp @@ -0,0 +1,109 @@ +#include "types.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace +{ + // Populates a book struct from a Lua table. + ESM::Book tableToBook(const sol::table& rec) + { + ESM::Book book; + book.mName = rec["name"]; + book.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + book.mIcon = rec["icon"]; + book.mText = rec["text"]; + std::string_view enchantId = rec["enchant"].get(); + book.mEnchant = ESM::RefId::deserializeText(enchantId); + + book.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); + std::string_view scriptId = rec["mwscript"].get(); + book.mScript = ESM::RefId::deserializeText(scriptId); + book.mData.mWeight = rec["weight"]; + book.mData.mValue = rec["value"]; + book.mData.mIsScroll = rec["isScroll"]; + + std::string_view skill = rec["skill"].get(); + + book.mData.mSkillId = -1; + if (skill.length() > 0) + { + for (std::size_t i = 0; i < std::size(ESM::Skill::sSkillNames); ++i) + { + if (Misc::StringUtils::ciEqual(ESM::Skill::sSkillNames[i], skill)) + book.mData.mSkillId = i; + } + if (book.mData.mSkillId == -1) + throw std::runtime_error("Incorrect skill: " + std::string(skill)); + } + return book; + } +} + +namespace MWLua +{ + void addBookBindings(sol::table book, const Context& context) + { + // types.book.SKILL is deprecated (core.SKILL should be used instead) + // TODO: Remove book.SKILL after branching 0.49 + sol::table skill(context.mLua->sol(), sol::create); + book["SKILL"] = LuaUtil::makeStrictReadOnly(skill); + book["createRecordDraft"] = tableToBook; + for (int id = 0; id < ESM::Skill::Length; ++id) + { + std::string skillName = Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id]); + skill[skillName] = skillName; + } + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(book, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Book"); + record[sol::meta_function::to_string] + = [](const ESM::Book& rec) { return "ESM3_Book[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] + = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mScript.serializeText(); }); + record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["text"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mText; }); + record["enchant"] + = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["isScroll"] = sol::readonly_property([](const ESM::Book& rec) -> bool { return rec.mData.mIsScroll; }); + record["value"] = sol::readonly_property([](const ESM::Book& rec) -> int { return rec.mData.mValue; }); + record["weight"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mWeight; }); + record["enchantCapacity"] + = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mEnchant * 0.1f; }); + record["skill"] = sol::readonly_property([](const ESM::Book& rec) -> sol::optional { + if (rec.mData.mSkillId >= 0) + return Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[rec.mData.mSkillId]); + else + return sol::nullopt; + }); + } +} diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp new file mode 100644 index 000000000..029b084e9 --- /dev/null +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -0,0 +1,88 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} +namespace +{ + // Populates a clothing struct from a Lua table. + ESM::Clothing tableToClothing(const sol::table& rec) + { + ESM::Clothing clothing; + clothing.mName = rec["name"]; + clothing.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + clothing.mIcon = rec["icon"]; + std::string_view scriptId = rec["mwscript"].get(); + clothing.mScript = ESM::RefId::deserializeText(scriptId); + clothing.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); + std::string_view enchantId = rec["enchant"].get(); + clothing.mEnchant = ESM::RefId::deserializeText(enchantId); + clothing.mData.mWeight = rec["weight"]; + clothing.mData.mValue = rec["value"]; + int clothingType = rec["type"].get(); + if (clothingType >= 0 && clothingType <= ESM::Clothing::Amulet) + clothing.mData.mType = clothingType; + else + throw std::runtime_error("Invalid Clothing Type provided: " + std::to_string(clothingType)); + return clothing; + } +} +namespace MWLua +{ + void addClothingBindings(sol::table clothing, const Context& context) + { + clothing["createRecordDraft"] = tableToClothing; + + clothing["TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Amulet", ESM::Clothing::Amulet }, + { "Belt", ESM::Clothing::Belt }, + { "LGlove", ESM::Clothing::LGlove }, + { "Pants", ESM::Clothing::Pants }, + { "RGlove", ESM::Clothing::RGlove }, + { "Ring", ESM::Clothing::Ring }, + { "Robe", ESM::Clothing::Robe }, + { "Shirt", ESM::Clothing::Shirt }, + { "Shoes", ESM::Clothing::Shoes }, + { "Skirt", ESM::Clothing::Skirt }, + })); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(clothing, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Clothing"); + record[sol::meta_function::to_string] + = [](const ESM::Clothing& rec) -> std::string { return "ESM3_Clothing[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["icon"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["enchant"] = sol::readonly_property( + [](const ESM::Clothing& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Clothing& rec) -> std::string { return rec.mScript.serializeText(); }); + record["weight"] = sol::readonly_property([](const ESM::Clothing& rec) -> float { return rec.mData.mWeight; }); + record["value"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mValue; }); + record["type"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mType; }); + record["enchantCapacity"] + = sol::readonly_property([](const ESM::Clothing& rec) -> float { return rec.mData.mEnchant * 0.1f; }); + } +} diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp new file mode 100644 index 000000000..21ec2f04d --- /dev/null +++ b/apps/openmw/mwlua/types/container.cpp @@ -0,0 +1,67 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + + static const MWWorld::Ptr& containerPtr(const Object& o) + { + return verifyType(ESM::REC_CONT, o.ptr()); + } + + void addContainerBindings(sol::table container, const Context& context) + { + container["content"] = sol::overload( + [](const LObject& o) { + containerPtr(o); + return Inventory{ o }; + }, + [](const GObject& o) { + containerPtr(o); + return Inventory{ o }; + }); + container["encumbrance"] = [](const Object& obj) -> float { + const MWWorld::Ptr& ptr = containerPtr(obj); + return ptr.getClass().getEncumbrance(ptr); + }; + container["capacity"] = [](const Object& obj) -> float { + const MWWorld::Ptr& ptr = containerPtr(obj); + return ptr.getClass().getCapacity(ptr); + }; + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(container, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Container"); + record[sol::meta_function::to_string] = [](const ESM::Container& rec) -> std::string { + return "ESM3_Container[" + rec.mId.toDebugString() + "]"; + }; + record["id"] + = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Container& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Container& rec) -> std::string { return rec.mScript.serializeText(); }); + record["weight"] = sol::readonly_property([](const ESM::Container& rec) -> float { return rec.mWeight; }); + } +} diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp new file mode 100644 index 000000000..b42f433ed --- /dev/null +++ b/apps/openmw/mwlua/types/creature.cpp @@ -0,0 +1,51 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addCreatureBindings(sol::table creature, const Context& context) + { + creature["TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Creatures", ESM::Creature::Creatures }, + { "Daedra", ESM::Creature::Daedra }, + { "Undead", ESM::Creature::Undead }, + { "Humanoid", ESM::Creature::Humanoid }, + })); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(creature, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Creature"); + record[sol::meta_function::to_string] + = [](const ESM::Creature& rec) { return "ESM3_Creature[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Creature& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Creature& rec) -> std::string { return rec.mScript.serializeText(); }); + record["baseCreature"] = sol::readonly_property( + [](const ESM::Creature& rec) -> std::string { return rec.mOriginal.serializeText(); }); + record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); + record["type"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mType; }); + } +} diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp new file mode 100644 index 000000000..217a6fa95 --- /dev/null +++ b/apps/openmw/mwlua/types/door.cpp @@ -0,0 +1,116 @@ +#include "types.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include "../luabindings.hpp" +#include "../worldview.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + + static const MWWorld::Ptr& doorPtr(const Object& o) + { + return verifyType(ESM::REC_DOOR, o.ptr()); + } + + static const MWWorld::Ptr& door4Ptr(const Object& o) + { + return verifyType(ESM::REC_DOOR4, o.ptr()); + } + + void addDoorBindings(sol::table door, const Context& context) + { + door["isTeleport"] = [](const Object& o) { return doorPtr(o).getCellRef().getTeleport(); }; + door["destPosition"] + = [](const Object& o) -> osg::Vec3f { return doorPtr(o).getCellRef().getDoorDest().asVec3(); }; + door["destRotation"] = [](const Object& o) -> LuaUtil::TransformQ { + return { Misc::Convert::makeOsgQuat(doorPtr(o).getCellRef().getDoorDest().rot) }; + }; + door["destCell"] = [](sol::this_state lua, const Object& o) -> sol::object { + const MWWorld::CellRef& cellRef = doorPtr(o).getCellRef(); + if (!cellRef.getTeleport()) + return sol::nil; + MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell()); + if (dynamic_cast(&o)) + return sol::make_object(lua, GCell{ &cell }); + else + return sol::make_object(lua, LCell{ &cell }); + }; + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(door, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Door"); + record[sol::meta_function::to_string] + = [](const ESM::Door& rec) -> std::string { return "ESM3_Door[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Door& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] + = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript.serializeText(); }); + record["openSound"] = sol::readonly_property( + [](const ESM::Door& rec) -> std::string { return rec.mOpenSound.serializeText(); }); + record["closeSound"] = sol::readonly_property( + [](const ESM::Door& rec) -> std::string { return rec.mCloseSound.serializeText(); }); + } + + void addESM4DoorBindings(sol::table door, const Context& context) + { + door["isTeleport"] = [](const Object& o) { return door4Ptr(o).getCellRef().getTeleport(); }; + door["destPosition"] + = [](const Object& o) -> osg::Vec3f { return door4Ptr(o).getCellRef().getDoorDest().asVec3(); }; + door["destRotation"] = [](const Object& o) -> LuaUtil::TransformQ { + return { Misc::Convert::makeOsgQuat(door4Ptr(o).getCellRef().getDoorDest().rot) }; + }; + door["destCell"] = [](sol::this_state lua, const Object& o) -> sol::object { + const MWWorld::CellRef& cellRef = door4Ptr(o).getCellRef(); + if (!cellRef.getTeleport()) + return sol::nil; + MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell()); + if (dynamic_cast(&o)) + return sol::make_object(lua, GCell{ &cell }); + else + return sol::make_object(lua, LCell{ &cell }); + }; + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(door, context, "ESM4Door"); + + sol::usertype record = context.mLua->sol().new_usertype("ESM4_Door"); + record[sol::meta_function::to_string] + = [](const ESM4::Door& rec) -> std::string { return "ESM4_Door[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM4::Door& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM4::Door& rec) -> std::string { return rec.mFullName; }); + record["model"] = sol::readonly_property([vfs](const ESM4::Door& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["isAutomatic"] = sol::readonly_property( + [](const ESM4::Door& rec) -> bool { return rec.mDoorFlags & ESM4::Door::Flag_AutomaticDoor; }); + } +} diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp new file mode 100644 index 000000000..cbf1b317f --- /dev/null +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -0,0 +1,64 @@ +#include "types.hpp" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addIngredientBindings(sol::table ingredient, const Context& context) + { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(ingredient, context); + + sol::usertype record = context.mLua->sol().new_usertype(("ESM3_Ingredient")); + record[sol::meta_function::to_string] + = [](const ESM::Ingredient& rec) { return "ESM3_Ingredient[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Ingredient& rec) -> std::string { return rec.mScript.serializeText(); }); + record["icon"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["weight"] + = sol::readonly_property([](const ESM::Ingredient& rec) -> float { return rec.mData.mWeight; }); + record["value"] = sol::readonly_property([](const ESM::Ingredient& rec) -> int { return rec.mData.mValue; }); + record["effects"] = sol::readonly_property([context](const ESM::Ingredient& rec) -> sol::table { + sol::table res(context.mLua->sol(), sol::create); + for (size_t i = 0; i < 4; ++i) + { + if (rec.mData.mEffectID[i] < 0) + continue; + ESM::ENAMstruct effect; + effect.mEffectID = rec.mData.mEffectID[i]; + effect.mSkill = rec.mData.mSkills[i]; + effect.mAttribute = rec.mData.mAttributes[i]; + effect.mRange = ESM::RT_Self; + effect.mArea = 0; + res[i + 1] = effect; + } + return res; + }); + } +} diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp new file mode 100644 index 000000000..cc3695786 --- /dev/null +++ b/apps/openmw/mwlua/types/item.cpp @@ -0,0 +1,15 @@ +#include "../luabindings.hpp" +#include "../worldview.hpp" + +#include "types.hpp" + +namespace MWLua +{ + void addItemBindings(sol::table item) + { + item["getEnchantmentCharge"] + = [](const Object& object) { return object.ptr().getCellRef().getEnchantmentCharge(); }; + item["setEnchantmentCharge"] + = [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); }; + } +} diff --git a/apps/openmw/mwlua/types/levelledlist.cpp b/apps/openmw/mwlua/types/levelledlist.cpp new file mode 100644 index 000000000..cb3bd7a6b --- /dev/null +++ b/apps/openmw/mwlua/types/levelledlist.cpp @@ -0,0 +1,59 @@ +#include "types.hpp" + +#include + +#include "../../mwbase/environment.hpp" +#include "../../mwbase/world.hpp" +#include "../../mwmechanics/levelledlist.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addLevelledCreatureBindings(sol::table list, const Context& context) + { + auto& state = context.mLua->sol(); + auto item = state.new_usertype("ESM3_LevelledListItem"); + item["id"] = sol::readonly_property( + [](const ESM::LevelledListBase::LevelItem& rec) -> std::string { return rec.mId.serializeText(); }); + item["level"] + = sol::readonly_property([](const ESM::LevelledListBase::LevelItem& rec) -> int { return rec.mLevel; }); + item[sol::meta_function::to_string] = [](const ESM::LevelledListBase::LevelItem& rec) -> std::string { + return "ESM3_LevelledListItem[" + rec.mId.toDebugString() + ", " + std::to_string(rec.mLevel) + "]"; + }; + + addRecordFunctionBinding(list, context); + + auto record = state.new_usertype("ESM3_CreatureLevelledList"); + record[sol::meta_function::to_string] = [](const ESM::CreatureLevList& rec) -> std::string { + return "ESM3_CreatureLevelledList[" + rec.mId.toDebugString() + "]"; + }; + record["id"] = sol::readonly_property( + [](const ESM::CreatureLevList& rec) -> std::string { return rec.mId.serializeText(); }); + record["chanceNone"] = sol::readonly_property( + [](const ESM::CreatureLevList& rec) -> float { return std::clamp(rec.mChanceNone / 100.f, 0.f, 1.f); }); + record["creatures"] = sol::readonly_property([&](const ESM::CreatureLevList& rec) -> sol::table { + sol::table res(state, sol::create); + for (size_t i = 0; i < rec.mList.size(); ++i) + res[i + 1] = rec.mList[i]; + return res; + }); + record["calculateFromAllLevels"] = sol::readonly_property( + [](const ESM::CreatureLevList& rec) -> bool { return rec.mFlags & ESM::CreatureLevList::AllLevels; }); + + record["getRandomId"] = [](const ESM::CreatureLevList& rec, int level) -> std::string { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + return MWMechanics::getLevelledItem(&rec, true, prng, level).serializeText(); + }; + } +} diff --git a/apps/openmw/mwlua/types/light.cpp b/apps/openmw/mwlua/types/light.cpp new file mode 100644 index 000000000..347bb6164 --- /dev/null +++ b/apps/openmw/mwlua/types/light.cpp @@ -0,0 +1,52 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addLightBindings(sol::table light, const Context& context) + { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(light, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Light"); + record[sol::meta_function::to_string] + = [](const ESM::Light& rec) -> std::string { return "ESM3_Light[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Light& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["icon"] = sol::readonly_property([vfs](const ESM::Light& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["sound"] + = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mSound.serializeText(); }); + record["mwscript"] + = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mScript.serializeText(); }); + record["weight"] = sol::readonly_property([](const ESM::Light& rec) -> float { return rec.mData.mWeight; }); + record["value"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mValue; }); + record["duration"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mTime; }); + record["radius"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mRadius; }); + record["color"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mColor; }); + record["isCarriable"] = sol::readonly_property( + [](const ESM::Light& rec) -> bool { return rec.mData.mFlags & ESM::Light::Carry; }); + } +} diff --git a/apps/openmw/mwlua/types/lockable.cpp b/apps/openmw/mwlua/types/lockable.cpp new file mode 100644 index 000000000..2a400f3cc --- /dev/null +++ b/apps/openmw/mwlua/types/lockable.cpp @@ -0,0 +1,87 @@ + +#include "types.hpp" +#include +#include +#include +#include + +#include + +#include "../luabindings.hpp" +#include "../worldview.hpp" + +namespace MWLua +{ + + void addLockableBindings(sol::table lockable) + { + lockable["getLockLevel"] + = [](const Object& object) { return std::abs(object.ptr().getCellRef().getLockLevel()); }; + lockable["isLocked"] = [](const Object& object) { return object.ptr().getCellRef().isLocked(); }; + lockable["getKeyRecord"] = [](const Object& object) -> sol::optional { + ESM::RefId key = object.ptr().getCellRef().getKey(); + if (key.empty()) + return sol::nullopt; + return MWBase::Environment::get().getESMStore()->get().find(key); + }; + lockable["lock"] = [](const GObject& object, sol::optional lockLevel) { + object.ptr().getCellRef().setLocked(true); + + int level = 1; + + if (lockLevel) + level = lockLevel.value(); + else if (object.ptr().getCellRef().getLockLevel() < 0) + level = -object.ptr().getCellRef().getLockLevel(); + else if (object.ptr().getCellRef().getLockLevel() > 0) + level = object.ptr().getCellRef().getLockLevel(); + + object.ptr().getCellRef().setLockLevel(level); + }; + lockable["unlock"] = [](const GObject& object) { + if (!object.ptr().getCellRef().isLocked()) + return; + object.ptr().getCellRef().setLocked(false); + + object.ptr().getCellRef().setLockLevel(-object.ptr().getCellRef().getLockLevel()); + }; + lockable["setTrapSpell"] = [](const GObject& object, const sol::object& spellOrId) { + if (spellOrId == sol::nil) + { + object.ptr().getCellRef().setTrap(ESM::RefId()); // remove the trap value + return; + } + if (spellOrId.is()) + object.ptr().getCellRef().setTrap(spellOrId.as()->mId); + else + { + ESM::RefId spellId = ESM::RefId::deserializeText(LuaUtil::cast(spellOrId)); + const auto& spellStore = MWBase::Environment::get().getESMStore()->get(); + const ESM::Spell* spell = spellStore.find(spellId); + object.ptr().getCellRef().setTrap(spell->mId); + } + }; + lockable["setKeyRecord"] = [](const GObject& object, const sol::object& itemOrRecordId) { + if (itemOrRecordId == sol::nil) + { + object.ptr().getCellRef().setKey(ESM::RefId()); // remove the trap value + return; + } + if (itemOrRecordId.is()) + object.ptr().getCellRef().setKey(itemOrRecordId.as()->mId); + else + { + ESM::RefId miscId = ESM::RefId::deserializeText(LuaUtil::cast(itemOrRecordId)); + const auto& keyStore = MWBase::Environment::get().getESMStore()->get(); + const ESM::Miscellaneous* key = keyStore.find(miscId); + object.ptr().getCellRef().setKey(key->mId); + } + }; + lockable["getTrapSpell"] = [](sol::this_state lua, const Object& o) -> sol::optional { + ESM::RefId trap = o.ptr().getCellRef().getTrap(); + if (trap.empty()) + return sol::nullopt; + return MWBase::Environment::get().getESMStore()->get().find(trap); + }; + } +} diff --git a/apps/openmw/mwlua/types/lockpick.cpp b/apps/openmw/mwlua/types/lockpick.cpp new file mode 100644 index 000000000..786471461 --- /dev/null +++ b/apps/openmw/mwlua/types/lockpick.cpp @@ -0,0 +1,49 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addLockpickBindings(sol::table lockpick, const Context& context) + { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(lockpick, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Lockpick"); + record[sol::meta_function::to_string] + = [](const ESM::Lockpick& rec) { return "ESM3_Lockpick[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Lockpick& rec) -> std::string { return rec.mScript.serializeText(); }); + record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["maxCondition"] + = sol::readonly_property([](const ESM::Lockpick& rec) -> int { return rec.mData.mUses; }); + record["value"] = sol::readonly_property([](const ESM::Lockpick& rec) -> int { return rec.mData.mValue; }); + record["weight"] = sol::readonly_property([](const ESM::Lockpick& rec) -> float { return rec.mData.mWeight; }); + record["quality"] + = sol::readonly_property([](const ESM::Lockpick& rec) -> float { return rec.mData.mQuality; }); + } +} diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp new file mode 100644 index 000000000..3a4fba9ce --- /dev/null +++ b/apps/openmw/mwlua/types/misc.cpp @@ -0,0 +1,88 @@ +#include "types.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace +{ + // Populates a misc struct from a Lua table. + ESM::Miscellaneous tableToMisc(const sol::table& rec) + { + ESM::Miscellaneous misc; + misc.mName = rec["name"]; + misc.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + misc.mIcon = rec["icon"]; + std::string_view scriptId = rec["mwscript"].get(); + misc.mScript = ESM::RefId::deserializeText(scriptId); + misc.mData.mWeight = rec["weight"]; + misc.mData.mValue = rec["value"]; + return misc; + } +} + +namespace MWLua +{ + void addMiscellaneousBindings(sol::table miscellaneous, const Context& context) + { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(miscellaneous, context); + miscellaneous["createRecordDraft"] = tableToMisc; + + miscellaneous["setSoul"] = [](const GObject& object, std::string_view soulId) { + ESM::RefId creature = ESM::RefId::deserializeText(soulId); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + + if (!store.get().search(creature)) + { + // TODO: Add Support for NPC Souls + throw std::runtime_error("Cannot use non-existent creature as a soul: " + std::string(soulId)); + } + + object.ptr().getCellRef().setSoul(creature); + }; + miscellaneous["getSoul"] = [](const Object& object) -> sol::optional { + ESM::RefId soul = object.ptr().getCellRef().getSoul(); + if (soul.empty()) + return sol::nullopt; + else + return soul.serializeText(); + }; + miscellaneous["soul"] = miscellaneous["getSoul"]; // for compatibility; should be removed later + sol::usertype record + = context.mLua->sol().new_usertype("ESM3_Miscellaneous"); + record[sol::meta_function::to_string] + = [](const ESM::Miscellaneous& rec) { return "ESM3_Miscellaneous[" + rec.mId.toDebugString() + "]"; }; + record["id"] = sol::readonly_property( + [](const ESM::Miscellaneous& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Miscellaneous& rec) -> std::string { return rec.mScript.serializeText(); }); + record["icon"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["isKey"] = sol::readonly_property( + [](const ESM::Miscellaneous& rec) -> bool { return rec.mData.mFlags & ESM::Miscellaneous::Key; }); + record["value"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> int { return rec.mData.mValue; }); + record["weight"] + = sol::readonly_property([](const ESM::Miscellaneous& rec) -> float { return rec.mData.mWeight; }); + } +} diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp new file mode 100644 index 000000000..eb8dd3244 --- /dev/null +++ b/apps/openmw/mwlua/types/npc.cpp @@ -0,0 +1,57 @@ +#include "types.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include "../stats.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addNpcBindings(sol::table npc, const Context& context) + { + addNpcStatsBindings(npc, context); + + addRecordFunctionBinding(npc, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_NPC"); + record[sol::meta_function::to_string] + = [](const ESM::NPC& rec) { return "ESM3_NPC[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mName; }); + record["race"] + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace.serializeText(); }); + record["class"] + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass.serializeText(); }); + record["mwscript"] + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mScript.serializeText(); }); + record["hair"] + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.serializeText(); }); + record["head"] + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); }); + record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); + + // This function is game-specific, in future we should replace it with something more universal. + npc["isWerewolf"] = [](const Object& o) { + const MWWorld::Class& cls = o.ptr().getClass(); + if (cls.isNpc()) + return cls.getNpcStats(o.ptr()).isWerewolf(); + else + throw std::runtime_error("NPC or Player expected"); + }; + } +} diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp new file mode 100644 index 000000000..4203e68d0 --- /dev/null +++ b/apps/openmw/mwlua/types/player.cpp @@ -0,0 +1,16 @@ +#include "types.hpp" + +#include +#include + +namespace MWLua +{ + + void addPlayerBindings(sol::table player, const Context& context) + { + player["getCrimeLevel"] = [](const Object& o) -> int { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getNpcStats(o.ptr()).getBounty(); + }; + } +} diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp new file mode 100644 index 000000000..87ef67bf6 --- /dev/null +++ b/apps/openmw/mwlua/types/potion.cpp @@ -0,0 +1,78 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace +{ + // Populates a potion struct from a Lua table. + ESM::Potion tableToPotion(const sol::table& rec) + { + ESM::Potion potion; + potion.mName = rec["name"]; + potion.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + potion.mIcon = rec["icon"]; + std::string_view scriptId = rec["mwscript"].get(); + potion.mScript = ESM::RefId::deserializeText(scriptId); + potion.mData.mWeight = rec["weight"]; + potion.mData.mValue = rec["value"]; + sol::table effectsTable = rec["effects"]; + size_t numEffects = effectsTable.size(); + potion.mEffects.mList.resize(numEffects); + for (size_t i = 0; i < numEffects; ++i) + potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + return potion; + } +} + +namespace MWLua +{ + void addPotionBindings(sol::table potion, const Context& context) + { + addRecordFunctionBinding(potion, context); + + // Creates a new potion struct but does not store it in MWWorld::ESMStore. + // Global scripts can use world.createRecord to add the potion to the world. + // Note: This potion instance must be owned by lua, so we return it + // by value. + potion["createRecordDraft"] = tableToPotion; + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Potion"); + record[sol::meta_function::to_string] + = [](const ESM::Potion& rec) { return "ESM3_Potion[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["mwscript"] + = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mScript.serializeText(); }); + record["weight"] = sol::readonly_property([](const ESM::Potion& rec) -> float { return rec.mData.mWeight; }); + record["value"] = sol::readonly_property([](const ESM::Potion& rec) -> int { return rec.mData.mValue; }); + record["effects"] = sol::readonly_property([context](const ESM::Potion& rec) -> sol::table { + sol::table res(context.mLua->sol(), sol::create); + for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) + res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) + return res; + }); + } +} diff --git a/apps/openmw/mwlua/types/probe.cpp b/apps/openmw/mwlua/types/probe.cpp new file mode 100644 index 000000000..668e58c98 --- /dev/null +++ b/apps/openmw/mwlua/types/probe.cpp @@ -0,0 +1,47 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addProbeBindings(sol::table probe, const Context& context) + { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(probe, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Probe"); + record[sol::meta_function::to_string] + = [](const ESM::Probe& rec) { return "ESM3_Probe[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] + = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mScript.serializeText(); }); + record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["maxCondition"] = sol::readonly_property([](const ESM::Probe& rec) -> int { return rec.mData.mUses; }); + record["value"] = sol::readonly_property([](const ESM::Probe& rec) -> int { return rec.mData.mValue; }); + record["weight"] = sol::readonly_property([](const ESM::Probe& rec) -> float { return rec.mData.mWeight; }); + record["quality"] = sol::readonly_property([](const ESM::Probe& rec) -> float { return rec.mData.mQuality; }); + } +} diff --git a/apps/openmw/mwlua/types/repair.cpp b/apps/openmw/mwlua/types/repair.cpp new file mode 100644 index 000000000..75d0a17c4 --- /dev/null +++ b/apps/openmw/mwlua/types/repair.cpp @@ -0,0 +1,47 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addRepairBindings(sol::table repair, const Context& context) + { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(repair, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Repair"); + record[sol::meta_function::to_string] + = [](const ESM::Repair& rec) { return "ESM3_Repair[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["mwscript"] + = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mScript.serializeText(); }); + record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["maxCondition"] = sol::readonly_property([](const ESM::Repair& rec) -> int { return rec.mData.mUses; }); + record["value"] = sol::readonly_property([](const ESM::Repair& rec) -> int { return rec.mData.mValue; }); + record["weight"] = sol::readonly_property([](const ESM::Repair& rec) -> float { return rec.mData.mWeight; }); + record["quality"] = sol::readonly_property([](const ESM::Repair& rec) -> float { return rec.mData.mQuality; }); + } +} diff --git a/apps/openmw/mwlua/types/static.cpp b/apps/openmw/mwlua/types/static.cpp new file mode 100644 index 000000000..76dac4fa0 --- /dev/null +++ b/apps/openmw/mwlua/types/static.cpp @@ -0,0 +1,37 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addStaticBindings(sol::table stat, const Context& context) + { + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(stat, context); + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Static"); + record[sol::meta_function::to_string] + = [](const ESM::Static& rec) -> std::string { return "ESM3_Static[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Static& rec) -> std::string { return rec.mId.serializeText(); }); + record["model"] = sol::readonly_property([vfs](const ESM::Static& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + } +} diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp new file mode 100644 index 000000000..3f9680e44 --- /dev/null +++ b/apps/openmw/mwlua/types/types.cpp @@ -0,0 +1,246 @@ +#include "types.hpp" + +#include +#include + +namespace MWLua +{ + namespace ObjectTypeName + { + // Names of object types in Lua. + // These names are part of OpenMW Lua API. + constexpr std::string_view Actor = "Actor"; // base type for NPC, Creature, Player + constexpr std::string_view Item = "Item"; // base type for all items + constexpr std::string_view Lockable = "Lockable"; // base type for doors and containers + + constexpr std::string_view Activator = "Activator"; + constexpr std::string_view Armor = "Armor"; + constexpr std::string_view Book = "Book"; + constexpr std::string_view Clothing = "Clothing"; + constexpr std::string_view Container = "Container"; + constexpr std::string_view Creature = "Creature"; + constexpr std::string_view Door = "Door"; + constexpr std::string_view Ingredient = "Ingredient"; + constexpr std::string_view LevelledCreature = "LevelledCreature"; + constexpr std::string_view Light = "Light"; + constexpr std::string_view MiscItem = "Miscellaneous"; + constexpr std::string_view NPC = "NPC"; + constexpr std::string_view Player = "Player"; + constexpr std::string_view Potion = "Potion"; + constexpr std::string_view Static = "Static"; + constexpr std::string_view Weapon = "Weapon"; + constexpr std::string_view Apparatus = "Apparatus"; + constexpr std::string_view Lockpick = "Lockpick"; + constexpr std::string_view Probe = "Probe"; + constexpr std::string_view Repair = "Repair"; + constexpr std::string_view Marker = "Marker"; + + constexpr std::string_view ESM4Activator = "ESM4Activator"; + constexpr std::string_view ESM4Ammunition = "ESM4Ammunition"; + constexpr std::string_view ESM4Armor = "ESM4Armor"; + constexpr std::string_view ESM4Book = "ESM4Book"; + constexpr std::string_view ESM4Clothing = "ESM4Clothing"; + constexpr std::string_view ESM4Container = "ESM4Container"; + constexpr std::string_view ESM4Door = "ESM4Door"; + constexpr std::string_view ESM4Furniture = "ESM4Furniture"; + constexpr std::string_view ESM4Ingredient = "ESM4Ingredient"; + constexpr std::string_view ESM4Light = "ESM4Light"; + constexpr std::string_view ESM4MiscItem = "ESM4Miscellaneous"; + constexpr std::string_view ESM4Potion = "ESM4Potion"; + constexpr std::string_view ESM4Static = "ESM4Static"; + constexpr std::string_view ESM4Tree = "ESM4Tree"; + constexpr std::string_view ESM4Weapon = "ESM4Weapon"; + } + + namespace + { + const static std::unordered_map luaObjectTypeInfo = { + { ESM::REC_INTERNAL_PLAYER, ObjectTypeName::Player }, + { ESM::REC_INTERNAL_MARKER, ObjectTypeName::Marker }, + { ESM::REC_ACTI, ObjectTypeName::Activator }, + { ESM::REC_ARMO, ObjectTypeName::Armor }, + { ESM::REC_BOOK, ObjectTypeName::Book }, + { ESM::REC_CLOT, ObjectTypeName::Clothing }, + { ESM::REC_CONT, ObjectTypeName::Container }, + { ESM::REC_CREA, ObjectTypeName::Creature }, + { ESM::REC_DOOR, ObjectTypeName::Door }, + { ESM::REC_INGR, ObjectTypeName::Ingredient }, + { ESM::REC_LEVC, ObjectTypeName::LevelledCreature }, + { ESM::REC_LIGH, ObjectTypeName::Light }, + { ESM::REC_MISC, ObjectTypeName::MiscItem }, + { ESM::REC_NPC_, ObjectTypeName::NPC }, + { ESM::REC_ALCH, ObjectTypeName::Potion }, + { ESM::REC_STAT, ObjectTypeName::Static }, + { ESM::REC_WEAP, ObjectTypeName::Weapon }, + { ESM::REC_APPA, ObjectTypeName::Apparatus }, + { ESM::REC_LOCK, ObjectTypeName::Lockpick }, + { ESM::REC_PROB, ObjectTypeName::Probe }, + { ESM::REC_REPA, ObjectTypeName::Repair }, + + { ESM::REC_ACTI4, ObjectTypeName::ESM4Activator }, + { ESM::REC_AMMO4, ObjectTypeName::ESM4Ammunition }, + { ESM::REC_ARMO4, ObjectTypeName::ESM4Armor }, + { ESM::REC_BOOK4, ObjectTypeName::ESM4Book }, + { ESM::REC_CLOT4, ObjectTypeName::ESM4Clothing }, + { ESM::REC_CONT4, ObjectTypeName::ESM4Container }, + { ESM::REC_DOOR4, ObjectTypeName::ESM4Door }, + { ESM::REC_FURN4, ObjectTypeName::ESM4Furniture }, + { ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient }, + { ESM::REC_LIGH4, ObjectTypeName::ESM4Light }, + { ESM::REC_MISC4, ObjectTypeName::ESM4MiscItem }, + { ESM::REC_ALCH4, ObjectTypeName::ESM4Potion }, + { ESM::REC_STAT4, ObjectTypeName::ESM4Static }, + { ESM::REC_TREE4, ObjectTypeName::ESM4Tree }, + { ESM::REC_WEAP4, ObjectTypeName::ESM4Weapon }, + }; + } + + unsigned int getLiveCellRefType(const MWWorld::LiveCellRefBase* ref) + { + if (ref == nullptr) + throw std::runtime_error("Can't get type name from an empty object."); + const ESM::RefId& id = ref->mRef.getRefId(); + if (id == "Player") + return ESM::REC_INTERNAL_PLAYER; + if (Misc::ResourceHelpers::isHiddenMarker(id)) + return ESM::REC_INTERNAL_MARKER; + return ref->getType(); + } + + std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback) + { + auto it = luaObjectTypeInfo.find(type); + if (it != luaObjectTypeInfo.end()) + return it->second; + else + return fallback; + } + + std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr) + { + return getLuaObjectTypeName( + static_cast(getLiveCellRefType(ptr.mRef)), /*fallback=*/ptr.getTypeDescription()); + } + + const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr) + { + if (ptr.getType() != recordType) + { + std::string msg = "Requires type '"; + msg.append(getLuaObjectTypeName(recordType)); + msg.append("', but applied to "); + msg.append(ptr.toString()); + throw std::runtime_error(msg); + } + return ptr; + } + + sol::table getTypeToPackageTable(lua_State* L) + { + constexpr std::string_view key = "typeToPackage"; + sol::state_view lua(L); + if (lua[key] == sol::nil) + lua[key] = sol::table(lua, sol::create); + return lua[key]; + } + + sol::table getPackageToTypeTable(lua_State* L) + { + constexpr std::string_view key = "packageToType"; + sol::state_view lua(L); + if (lua[key] == sol::nil) + lua[key] = sol::table(lua, sol::create); + return lua[key]; + } + + sol::table initTypesPackage(const Context& context) + { + auto* lua = context.mLua; + sol::table types(lua->sol(), sol::create); + auto addType = [&](std::string_view name, std::vector recTypes, + std::optional base = std::nullopt) -> sol::table { + sol::table t(lua->sol(), sol::create); + sol::table ro = LuaUtil::makeReadOnly(t); + sol::table meta = ro[sol::metatable_key]; + meta[sol::meta_function::to_string] = [name]() { return name; }; + if (base) + { + t["baseType"] = types[*base]; + sol::table baseMeta(lua->sol(), sol::create); + baseMeta[sol::meta_function::index] = LuaUtil::getMutableFromReadOnly(types[*base]); + t[sol::metatable_key] = baseMeta; + } + t["objectIsInstance"] = [types = recTypes](const Object& o) { + unsigned int type = getLiveCellRefType(o.ptr().mRef); + for (ESM::RecNameInts t : types) + if (t == type) + return true; + return false; + }; + types[name] = ro; + return t; + }; + + addActorBindings( + addType(ObjectTypeName::Actor, { ESM::REC_INTERNAL_PLAYER, ESM::REC_CREA, ESM::REC_NPC_ }), context); + addItemBindings(addType(ObjectTypeName::Item, + { ESM::REC_ARMO, ESM::REC_BOOK, ESM::REC_CLOT, ESM::REC_INGR, ESM::REC_LIGH, ESM::REC_MISC, ESM::REC_ALCH, + ESM::REC_WEAP, ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA })); + addLockableBindings( + addType(ObjectTypeName::Lockable, { ESM::REC_CONT, ESM::REC_DOOR, ESM::REC_CONT4, ESM::REC_DOOR4 })); + + addCreatureBindings(addType(ObjectTypeName::Creature, { ESM::REC_CREA }, ObjectTypeName::Actor), context); + addNpcBindings( + addType(ObjectTypeName::NPC, { ESM::REC_INTERNAL_PLAYER, ESM::REC_NPC_ }, ObjectTypeName::Actor), context); + addPlayerBindings(addType(ObjectTypeName::Player, { ESM::REC_INTERNAL_PLAYER }, ObjectTypeName::NPC), context); + + addLevelledCreatureBindings(addType(ObjectTypeName::LevelledCreature, { ESM::REC_LEVC }), context); + + addArmorBindings(addType(ObjectTypeName::Armor, { ESM::REC_ARMO }, ObjectTypeName::Item), context); + addClothingBindings(addType(ObjectTypeName::Clothing, { ESM::REC_CLOT }, ObjectTypeName::Item), context); + addIngredientBindings(addType(ObjectTypeName::Ingredient, { ESM::REC_INGR }, ObjectTypeName::Item), context); + addLightBindings(addType(ObjectTypeName::Light, { ESM::REC_LIGH }, ObjectTypeName::Item), context); + addMiscellaneousBindings(addType(ObjectTypeName::MiscItem, { ESM::REC_MISC }, ObjectTypeName::Item), context); + addPotionBindings(addType(ObjectTypeName::Potion, { ESM::REC_ALCH }, ObjectTypeName::Item), context); + addWeaponBindings(addType(ObjectTypeName::Weapon, { ESM::REC_WEAP }, ObjectTypeName::Item), context); + addBookBindings(addType(ObjectTypeName::Book, { ESM::REC_BOOK }, ObjectTypeName::Item), context); + addLockpickBindings(addType(ObjectTypeName::Lockpick, { ESM::REC_LOCK }, ObjectTypeName::Item), context); + addProbeBindings(addType(ObjectTypeName::Probe, { ESM::REC_PROB }, ObjectTypeName::Item), context); + addApparatusBindings(addType(ObjectTypeName::Apparatus, { ESM::REC_APPA }, ObjectTypeName::Item), context); + addRepairBindings(addType(ObjectTypeName::Repair, { ESM::REC_REPA }, ObjectTypeName::Item), context); + + addActivatorBindings(addType(ObjectTypeName::Activator, { ESM::REC_ACTI }), context); + addContainerBindings(addType(ObjectTypeName::Container, { ESM::REC_CONT }, ObjectTypeName::Lockable), context); + addDoorBindings(addType(ObjectTypeName::Door, { ESM::REC_DOOR }, ObjectTypeName::Lockable), context); + addStaticBindings(addType(ObjectTypeName::Static, { ESM::REC_STAT }), context); + + addType(ObjectTypeName::ESM4Activator, { ESM::REC_ACTI4 }); + addType(ObjectTypeName::ESM4Ammunition, { ESM::REC_AMMO4 }); + addType(ObjectTypeName::ESM4Armor, { ESM::REC_ARMO4 }); + addType(ObjectTypeName::ESM4Book, { ESM::REC_BOOK4 }); + addType(ObjectTypeName::ESM4Clothing, { ESM::REC_CLOT4 }); + addType(ObjectTypeName::ESM4Container, { ESM::REC_CONT4 }); + addESM4DoorBindings(addType(ObjectTypeName::ESM4Door, { ESM::REC_DOOR4 }, ObjectTypeName::Lockable), context); + addType(ObjectTypeName::ESM4Furniture, { ESM::REC_FURN4 }); + addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 }); + addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 }); + addType(ObjectTypeName::ESM4MiscItem, { ESM::REC_MISC4 }); + addType(ObjectTypeName::ESM4Potion, { ESM::REC_ALCH4 }); + addType(ObjectTypeName::ESM4Static, { ESM::REC_STAT4 }); + addType(ObjectTypeName::ESM4Tree, { ESM::REC_TREE4 }); + addType(ObjectTypeName::ESM4Weapon, { ESM::REC_WEAP4 }); + + sol::table typeToPackage = getTypeToPackageTable(context.mLua->sol()); + sol::table packageToType = getPackageToTypeTable(context.mLua->sol()); + for (const auto& [type, name] : luaObjectTypeInfo) + { + sol::object t = types[name]; + if (t == sol::nil) + continue; + typeToPackage[type] = t; + packageToType[t] = type; + } + + return LuaUtil::makeReadOnly(types); + } +} diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp new file mode 100644 index 000000000..9f179a78e --- /dev/null +++ b/apps/openmw/mwlua/types/types.hpp @@ -0,0 +1,101 @@ +#ifndef MWLUA_TYPES_H +#define MWLUA_TYPES_H + +#include + +#include +#include + +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwworld/store.hpp" + +#include "../context.hpp" +#include "../object.hpp" + +namespace sol +{ + // Ensure sol does not try to create the automatic Container or usertype bindings for Store. + // They include write operations and we want the store to be read-only. + template + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + // `getLiveCellRefType()` is not exactly what we usually mean by "type" because some refids have special meaning. + // This function handles these special refids (and by this adds some performance overhead). + // We use this "fixed" type in Lua because we don't want to expose the weirdness of Morrowind internals to our API. + // TODO: Implement https://gitlab.com/OpenMW/openmw/-/issues/6617 and make `MWWorld::PtrBase::getType` work the + // same as `getLiveCellRefType`. + unsigned int getLiveCellRefType(const MWWorld::LiveCellRefBase* ref); + + std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback = "Unknown"); + std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr); + const MWWorld::Ptr& verifyType(ESM::RecNameInts type, const MWWorld::Ptr& ptr); + + sol::table getTypeToPackageTable(lua_State* L); + sol::table getPackageToTypeTable(lua_State* L); + + sol::table initTypesPackage(const Context& context); + + // used in initTypesPackage + void addActivatorBindings(sol::table activator, const Context& context); + void addBookBindings(sol::table book, const Context& context); + void addContainerBindings(sol::table container, const Context& context); + void addDoorBindings(sol::table door, const Context& context); + void addItemBindings(sol::table item); + void addActorBindings(sol::table actor, const Context& context); + void addWeaponBindings(sol::table weapon, const Context& context); + void addNpcBindings(sol::table npc, const Context& context); + void addPlayerBindings(sol::table player, const Context& context); + void addCreatureBindings(sol::table creature, const Context& context); + void addLockpickBindings(sol::table lockpick, const Context& context); + void addProbeBindings(sol::table probe, const Context& context); + void addApparatusBindings(sol::table apparatus, const Context& context); + void addRepairBindings(sol::table repair, const Context& context); + void addMiscellaneousBindings(sol::table miscellaneous, const Context& context); + void addPotionBindings(sol::table potion, const Context& context); + void addIngredientBindings(sol::table Ingredient, const Context& context); + void addArmorBindings(sol::table armor, const Context& context); + void addLockableBindings(sol::table lockable); + void addClothingBindings(sol::table clothing, const Context& context); + void addStaticBindings(sol::table stat, const Context& context); + void addLightBindings(sol::table light, const Context& context); + void addLevelledCreatureBindings(sol::table list, const Context& context); + + void addESM4DoorBindings(sol::table door, const Context& context); + + template + void addRecordFunctionBinding( + sol::table table, const Context& context, const std::string& recordName = std::string(T::getRecordType())) + { + const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); + + table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, + [&store](std::string_view id) -> const T* { return store.find(ESM::RefId::deserializeText(id)); }); + + // Define a custom user type for the store. + // Provide the interface of a read-only array. + using StoreT = MWWorld::Store; + sol::state_view& lua = context.mLua->sol(); + sol::usertype storeT = lua.new_usertype(recordName + "WorldStore"); + storeT[sol::meta_function::to_string] = [recordName](const StoreT& store) { + return "{" + std::to_string(store.getSize()) + " " + recordName + " records}"; + }; + storeT[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; + storeT[sol::meta_function::index] = [](const StoreT& store, size_t index) -> const T* { + return store.at(index - 1); // Translate from Lua's 1-based indexing. + }; + storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + storeT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + + // Provide access to the store. + table["records"] = &store; + } +} + +#endif // MWLUA_TYPES_H diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp new file mode 100644 index 000000000..1f06a4bd7 --- /dev/null +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -0,0 +1,130 @@ +#include "types.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} +#include + +namespace +{ + // Populates a weapon struct from a Lua table. + ESM::Weapon tableToWeapon(const sol::table& rec) + { + ESM::Weapon weapon; + weapon.mName = rec["name"]; + weapon.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + weapon.mIcon = rec["icon"]; + std::string_view enchantId = rec["enchant"].get(); + weapon.mEnchant = ESM::RefId::deserializeText(enchantId); + std::string_view scriptId = rec["mwscript"].get(); + weapon.mScript = ESM::RefId::deserializeText(scriptId); + weapon.mData.mFlags = 0; + if (rec["isMagical"]) + weapon.mData.mFlags |= ESM::Weapon::Magical; + if (rec["isSilver"]) + weapon.mData.mFlags |= ESM::Weapon::Silver; + int weaponType = rec["type"].get(); + if (weaponType >= 0 && weaponType <= ESM::Weapon::MarksmanThrown) + weapon.mData.mType = weaponType; + else + throw std::runtime_error("Invalid Weapon Type provided: " + std::to_string(weaponType)); + + weapon.mData.mWeight = rec["weight"]; + weapon.mData.mValue = rec["value"]; + weapon.mData.mHealth = rec["health"]; + weapon.mData.mSpeed = rec["speed"]; + weapon.mData.mReach = rec["reach"]; + weapon.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); + weapon.mData.mChop[0] = rec["chopMinDamage"]; + weapon.mData.mChop[1] = rec["chopMaxDamage"]; + weapon.mData.mSlash[0] = rec["slashMinDamage"]; + weapon.mData.mSlash[1] = rec["slashMaxDamage"]; + weapon.mData.mThrust[0] = rec["thrustMinDamage"]; + weapon.mData.mThrust[1] = rec["thrustMaxDamage"]; + + return weapon; + } +} + +namespace MWLua +{ + void addWeaponBindings(sol::table weapon, const Context& context) + { + weapon["TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "ShortBladeOneHand", ESM::Weapon::ShortBladeOneHand }, + { "LongBladeOneHand", ESM::Weapon::LongBladeOneHand }, + { "LongBladeTwoHand", ESM::Weapon::LongBladeTwoHand }, + { "BluntOneHand", ESM::Weapon::BluntOneHand }, + { "BluntTwoClose", ESM::Weapon::BluntTwoClose }, + { "BluntTwoWide", ESM::Weapon::BluntTwoWide }, + { "SpearTwoWide", ESM::Weapon::SpearTwoWide }, + { "AxeOneHand", ESM::Weapon::AxeOneHand }, + { "AxeTwoHand", ESM::Weapon::AxeTwoHand }, + { "MarksmanBow", ESM::Weapon::MarksmanBow }, + { "MarksmanCrossbow", ESM::Weapon::MarksmanCrossbow }, + { "MarksmanThrown", ESM::Weapon::MarksmanThrown }, + { "Arrow", ESM::Weapon::Arrow }, + { "Bolt", ESM::Weapon::Bolt }, + })); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + addRecordFunctionBinding(weapon, context); + weapon["createRecordDraft"] = tableToWeapon; + + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Weapon"); + record[sol::meta_function::to_string] + = [](const ESM::Weapon& rec) -> std::string { return "ESM3_Weapon[" + rec.mId.toDebugString() + "]"; }; + record["id"] + = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mId.serializeText(); }); + record["name"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + }); + record["icon"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + record["enchant"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["mwscript"] + = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mScript.serializeText(); }); + record["isMagical"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Magical; }); + record["isSilver"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Silver; }); + record["weight"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mWeight; }); + record["value"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mValue; }); + record["type"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mType; }); + record["health"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mHealth; }); + record["speed"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mSpeed; }); + record["reach"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mReach; }); + record["enchantCapacity"] + = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mEnchant * 0.1f; }); + record["chopMinDamage"] + = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mChop[0]; }); + record["chopMaxDamage"] + = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mChop[1]; }); + record["slashMinDamage"] + = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mSlash[0]; }); + record["slashMaxDamage"] + = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mSlash[1]; }); + record["thrustMinDamage"] + = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mThrust[0]; }); + record["thrustMaxDamage"] + = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mThrust[1]; }); + } + +} diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp new file mode 100644 index 000000000..56bbe0d73 --- /dev/null +++ b/apps/openmw/mwlua/uibindings.cpp @@ -0,0 +1,193 @@ +#include "uibindings.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "context.hpp" +#include "luamanagerimp.hpp" + +#include "../mwbase/windowmanager.hpp" + +namespace MWLua +{ + namespace + { + template + void wrapAction(const std::shared_ptr& element, Fn&& fn) + { + try + { + fn(); + } + catch (...) + { + // prevent any actions on a potentially corrupted widget + element->mRoot = nullptr; + throw; + } + } + + // Lua arrays index from 1 + inline size_t fromLuaIndex(size_t i) + { + return i - 1; + } + inline size_t toLuaIndex(size_t i) + { + return i + 1; + } + } + + sol::table initUserInterfacePackage(const Context& context) + { + auto element = context.mLua->sol().new_usertype("Element"); + element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; }, + [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); + element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { + if (element->mDestroy || element->mUpdate) + return; + element->mUpdate = true; + luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); + }; + element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { + if (element->mDestroy) + return; + element->mDestroy = true; + luaManager->addAction([element] { wrapAction(element, [&] { element->destroy(); }); }, "Destroy UI"); + }; + + sol::table api = context.mLua->newTable(); + api["showMessage"] + = [luaManager = context.mLuaManager](std::string_view message) { luaManager->addUIMessage(message); }; + api["CONSOLE_COLOR"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Default", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Default.substr(1)) }, + { "Error", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Error.substr(1)) }, + { "Success", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Success.substr(1)) }, + { "Info", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Info.substr(1)) }, + })); + api["printToConsole"] + = [luaManager = context.mLuaManager](const std::string& message, const Misc::Color& color) { + luaManager->addInGameConsoleMessage(message + "\n", color); + }; + api["setConsoleMode"] = [luaManager = context.mLuaManager](std::string_view mode) { + luaManager->addAction( + [mode = std::string(mode)] { MWBase::Environment::get().getWindowManager()->setConsoleMode(mode); }); + }; + api["setConsoleSelectedObject"] = [luaManager = context.mLuaManager](const sol::object& obj) { + const auto wm = MWBase::Environment::get().getWindowManager(); + if (obj == sol::nil) + luaManager->addAction([wm] { wm->setConsoleSelectedObject(MWWorld::Ptr()); }); + else + { + if (!obj.is()) + throw std::runtime_error("Game object expected"); + luaManager->addAction([wm, obj = obj.as()] { wm->setConsoleSelectedObject(obj.ptr()); }); + } + }; + api["content"] = LuaUi::loadContentConstructor(context.mLua); + api["create"] = [luaManager = context.mLuaManager](const sol::table& layout) { + auto element = LuaUi::Element::make(layout); + luaManager->addAction([element] { wrapAction(element, [&] { element->create(); }); }, "Create UI"); + return element; + }; + api["updateAll"] = [context]() { + LuaUi::Element::forEach([](LuaUi::Element* e) { e->mUpdate = true; }); + context.mLuaManager->addAction( + []() { LuaUi::Element::forEach([](LuaUi::Element* e) { e->update(); }); }, "Update all UI elements"); + }; + api["_getMenuTransparency"] = []() { return Settings::Manager::getFloat("menu transparency", "GUI"); }; + + auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); + uiLayer["name"] = sol::property([](LuaUi::Layer& self) { return self.name(); }); + uiLayer["size"] = sol::property([](LuaUi::Layer& self) { return self.size(); }); + uiLayer[sol::meta_function::to_string] + = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; + + sol::table layers = context.mLua->newTable(); + layers[sol::meta_function::length] = []() { return LuaUi::Layer::count(); }; + layers[sol::meta_function::index] = [](size_t index) { + index = fromLuaIndex(index); + return LuaUi::Layer(index); + }; + layers["indexOf"] = [](std::string_view name) -> sol::optional { + size_t index = LuaUi::Layer::indexOf(name); + if (index == LuaUi::Layer::count()) + return sol::nullopt; + else + return toLuaIndex(index); + }; + layers["insertAfter"] = [context](std::string_view afterName, std::string_view name, const sol::object& opt) { + LuaUi::Layer::Options options; + options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); + size_t index = LuaUi::Layer::indexOf(afterName); + if (index == LuaUi::Layer::count()) + throw std::logic_error(std::string("Layer not found")); + index++; + context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); + }; + layers["insertBefore"] = [context](std::string_view beforename, std::string_view name, const sol::object& opt) { + LuaUi::Layer::Options options; + options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); + size_t index = LuaUi::Layer::indexOf(beforename); + if (index == LuaUi::Layer::count()) + throw std::logic_error(std::string("Layer not found")); + context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); + }; + { + auto pairs = [layers](const sol::object&) { + auto next = [](const sol::table& l, size_t i) -> sol::optional> { + if (i < LuaUi::Layer::count()) + return std::make_tuple(i + 1, LuaUi::Layer(i)); + else + return sol::nullopt; + }; + return std::make_tuple(next, layers, 0); + }; + layers[sol::meta_function::pairs] = pairs; + layers[sol::meta_function::ipairs] = pairs; + } + api["layers"] = LuaUtil::makeReadOnly(layers); + + sol::table typeTable = context.mLua->newTable(); + for (const auto& it : LuaUi::widgetTypeToName()) + typeTable.set(it.second, it.first); + api["TYPE"] = LuaUtil::makeStrictReadOnly(typeTable); + + api["ALIGNMENT"] = LuaUtil::makeStrictReadOnly( + context.mLua->tableFromPairs({ { "Start", LuaUi::Alignment::Start }, + { "Center", LuaUi::Alignment::Center }, { "End", LuaUi::Alignment::End } })); + + api["registerSettingsPage"] = &LuaUi::registerSettingsPage; + + api["texture"] = [luaManager = context.mLuaManager](const sol::table& options) { + LuaUi::TextureData data; + sol::object path = LuaUtil::getFieldOrNil(options, "path"); + if (path.is()) + data.mPath = path.as(); + if (data.mPath.empty()) + throw std::logic_error("Invalid texture path"); + sol::object offset = LuaUtil::getFieldOrNil(options, "offset"); + if (offset.is()) + data.mOffset = offset.as(); + sol::object size = LuaUtil::getFieldOrNil(options, "size"); + if (size.is()) + data.mSize = size.as(); + return luaManager->uiResourceManager()->registerTexture(data); + }; + + api["screenSize"] = []() { + return osg::Vec2f( + Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video")); + }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/uibindings.hpp b/apps/openmw/mwlua/uibindings.hpp new file mode 100644 index 000000000..930ba7f3d --- /dev/null +++ b/apps/openmw/mwlua/uibindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_UIBINDINGS_H +#define MWLUA_UIBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initUserInterfacePackage(const Context&); +} + +#endif // MWLUA_UIBINDINGS_H diff --git a/apps/openmw/mwlua/userdataserializer.cpp b/apps/openmw/mwlua/userdataserializer.cpp new file mode 100644 index 000000000..6565ee0bd --- /dev/null +++ b/apps/openmw/mwlua/userdataserializer.cpp @@ -0,0 +1,117 @@ +#include "userdataserializer.hpp" + +#include + +#include +#include + +#include "object.hpp" + +namespace MWLua +{ + + class Serializer final : public LuaUtil::UserdataSerializer + { + public: + explicit Serializer(bool localSerializer, std::map* contentFileMapping) + : mLocalSerializer(localSerializer) + , mContentFileMapping(contentFileMapping) + { + } + + private: + // Appends serialized sol::userdata to the end of BinaryData. + // Returns false if this type of userdata is not supported by this serializer. + bool serialize(LuaUtil::BinaryData& out, const sol::userdata& data) const override + { + if (data.is() || data.is()) + { + appendRefNum(out, data.as().id()); + return true; + } + if (data.is()) + { + appendObjectIdList(out, data.as().mIds); + return true; + } + if (data.is()) + { + appendObjectIdList(out, data.as().mIds); + return true; + } + return false; + } + + constexpr static std::string_view sObjListTypeName = "objlist"; + void appendObjectIdList(LuaUtil::BinaryData& out, const ObjectIdList& objList) const + { + static_assert(sizeof(ESM::RefNum) == 8); + if constexpr (Misc::IS_LITTLE_ENDIAN) + append(out, sObjListTypeName, objList->data(), objList->size() * sizeof(ESM::RefNum)); + else + { + std::vector buf; + buf.reserve(objList->size()); + for (const ESM::RefNum& v : *objList) + buf.push_back({ Misc::toLittleEndian(v.mIndex), Misc::toLittleEndian(v.mContentFile) }); + append(out, sObjListTypeName, buf.data(), buf.size() * sizeof(ESM::RefNum)); + } + } + + void adjustRefNum(ESM::RefNum& refNum) const + { + if (refNum.hasContentFile() && mContentFileMapping) + { + auto iter = mContentFileMapping->find(refNum.mContentFile); + if (iter != mContentFileMapping->end()) + refNum.mContentFile = iter->second; + } + } + + // Deserializes userdata of type "typeName" from binaryData. Should push the result on stack using + // sol::stack::push. Returns false if this type is not supported by this serializer. + bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override + { + if (typeName == sRefNumTypeName) + { + ObjectId id = loadRefNum(binaryData); + adjustRefNum(id); + if (mLocalSerializer) + sol::stack::push(lua, LObject(id)); + else + sol::stack::push(lua, GObject(id)); + return true; + } + if (typeName == sObjListTypeName) + { + if (binaryData.size() % sizeof(ESM::RefNum) != 0) + throw std::runtime_error("Invalid size of ObjectIdList in MWLua::Serializer"); + ObjectIdList objList = std::make_shared>(); + objList->resize(binaryData.size() / sizeof(ESM::RefNum)); + std::memcpy(objList->data(), binaryData.data(), binaryData.size()); + for (ESM::RefNum& id : *objList) + { + id.mIndex = Misc::fromLittleEndian(id.mIndex); + id.mContentFile = Misc::fromLittleEndian(id.mContentFile); + adjustRefNum(id); + } + if (mLocalSerializer) + sol::stack::push(lua, LObjectList{ std::move(objList) }); + else + sol::stack::push(lua, GObjectList{ std::move(objList) }); + return true; + } + return false; + } + + bool mLocalSerializer; + std::map* mContentFileMapping; + }; + + std::unique_ptr createUserdataSerializer( + bool local, std::map* contentFileMapping) + { + return std::make_unique(local, contentFileMapping); + } + +} diff --git a/apps/openmw/mwlua/userdataserializer.hpp b/apps/openmw/mwlua/userdataserializer.hpp new file mode 100644 index 000000000..f52bb2272 --- /dev/null +++ b/apps/openmw/mwlua/userdataserializer.hpp @@ -0,0 +1,22 @@ +#ifndef MWLUA_USERDATASERIALIZER_H +#define MWLUA_USERDATASERIALIZER_H + +#include "object.hpp" + +namespace LuaUtil +{ + class UserdataSerializer; +} + +namespace MWLua +{ + // UserdataSerializer is an extension for components/lua/serialization.hpp + // Needed to serialize references to objects. + // If local=true, then during deserialization creates LObject, otherwise creates GObject. + // contentFileMapping is used only for deserialization. Needed to fix references if the order + // of content files was changed. + std::unique_ptr createUserdataSerializer( + bool local, std::map* contentFileMapping = nullptr); +} + +#endif // MWLUA_USERDATASERIALIZER_H diff --git a/apps/openmw/mwlua/worker.cpp b/apps/openmw/mwlua/worker.cpp new file mode 100644 index 000000000..66fbf0d55 --- /dev/null +++ b/apps/openmw/mwlua/worker.cpp @@ -0,0 +1,92 @@ +#include "worker.hpp" + +#include "luamanagerimp.hpp" + +#include + +#include + +#include + +namespace MWLua +{ + Worker::Worker(LuaManager& manager, osgViewer::Viewer& viewer) + : mManager(manager) + , mViewer(viewer) + { + if (Settings::lua().mLuaNumThreads > 0) + mThread = std::thread([this] { run(); }); + } + + Worker::~Worker() + { + if (mThread && mThread->joinable()) + { + Log(Debug::Error) + << "Unexpected destruction of LuaWorker; likely there is an unhandled exception in the main thread."; + join(); + } + } + + void Worker::allowUpdate() + { + if (!mThread) + return; + { + std::lock_guard lk(mMutex); + mUpdateRequest = true; + } + mCV.notify_one(); + } + + void Worker::finishUpdate() + { + if (mThread) + { + std::unique_lock lk(mMutex); + mCV.wait(lk, [&] { return !mUpdateRequest; }); + } + else + update(); + } + + void Worker::join() + { + if (mThread) + { + { + std::lock_guard lk(mMutex); + mJoinRequest = true; + } + mCV.notify_one(); + mThread->join(); + } + } + + void Worker::update() + { + const osg::Timer_t frameStart = mViewer.getStartTick(); + const unsigned int frameNumber = mViewer.getFrameStamp()->getFrameNumber(); + OMW::ScopedProfile profile( + frameStart, frameNumber, *osg::Timer::instance(), *mViewer.getViewerStats()); + + mManager.update(); + } + + void Worker::run() noexcept + { + while (true) + { + std::unique_lock lk(mMutex); + mCV.wait(lk, [&] { return mUpdateRequest || mJoinRequest; }); + if (mJoinRequest) + break; + + update(); + + mUpdateRequest = false; + lk.unlock(); + mCV.notify_one(); + } + } +} diff --git a/apps/openmw/mwlua/worker.hpp b/apps/openmw/mwlua/worker.hpp new file mode 100644 index 000000000..fed625e1f --- /dev/null +++ b/apps/openmw/mwlua/worker.hpp @@ -0,0 +1,46 @@ +#ifndef OPENMW_MWLUA_WORKER_H +#define OPENMW_MWLUA_WORKER_H + +#include +#include +#include +#include + +namespace osgViewer +{ + class Viewer; +} + +namespace MWLua +{ + class LuaManager; + + class Worker + { + public: + explicit Worker(LuaManager& manager, osgViewer::Viewer& viewer); + + ~Worker(); + + void allowUpdate(); + + void finishUpdate(); + + void join(); + + private: + void update(); + + void run() noexcept; + + LuaManager& mManager; + osgViewer::Viewer& mViewer; + std::mutex mMutex; + std::condition_variable mCV; + bool mUpdateRequest = false; + bool mJoinRequest = false; + std::optional mThread; + }; +} + +#endif // OPENMW_MWLUA_LUAWORKER_H diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp new file mode 100644 index 000000000..6d8c01c56 --- /dev/null +++ b/apps/openmw/mwlua/worldview.cpp @@ -0,0 +1,122 @@ +#include "worldview.hpp" + +#include +#include +#include + +#include + +#include "../mwbase/windowmanager.hpp" + +#include "../mwclass/container.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/timestamp.hpp" +#include "../mwworld/worldmodel.hpp" + +namespace MWLua +{ + + void WorldView::update() + { + mActivatorsInScene.updateList(); + mActorsInScene.updateList(); + mContainersInScene.updateList(); + mDoorsInScene.updateList(); + mItemsInScene.updateList(); + mPaused = MWBase::Environment::get().getWindowManager()->isGuiMode(); + } + + void WorldView::clear() + { + mActivatorsInScene.clear(); + mActorsInScene.clear(); + mContainersInScene.clear(); + mDoorsInScene.clear(); + mItemsInScene.clear(); + } + + WorldView::ObjectGroup* WorldView::chooseGroup(const MWWorld::Ptr& ptr) + { + // It is important to check `isMarker` first. + // For example "prisonmarker" has class "Door" despite that it is only an invisible marker. + if (Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId())) + return nullptr; + const MWWorld::Class& cls = ptr.getClass(); + if (cls.isActivator()) + return &mActivatorsInScene; + if (cls.isActor()) + return &mActorsInScene; + if (ptr.mRef->getType() == ESM::REC_DOOR || ptr.mRef->getType() == ESM::REC_DOOR4) + return &mDoorsInScene; + if (typeid(cls) == typeid(MWClass::Container)) + return &mContainersInScene; + if (cls.isItem(ptr)) + return &mItemsInScene; + return nullptr; + } + + void WorldView::objectAddedToScene(const MWWorld::Ptr& ptr) + { + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + ObjectGroup* group = chooseGroup(ptr); + if (group) + addToGroup(*group, ptr); + } + + void WorldView::objectRemovedFromScene(const MWWorld::Ptr& ptr) + { + ObjectGroup* group = chooseGroup(ptr); + if (group) + removeFromGroup(*group, ptr); + } + + double WorldView::getGameTime() const + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::TimeStamp timeStamp = world->getTimeStamp(); + return (static_cast(timeStamp.getDay()) * 24 + timeStamp.getHour()) * 3600.0; + } + + void WorldView::load(ESM::ESMReader& esm) + { + esm.getHNT(mSimulationTime, "LUAW"); + MWBase::Environment::get().getWorldModel()->setLastGeneratedRefNum(esm.getFormId(true)); + } + + void WorldView::save(ESM::ESMWriter& esm) const + { + esm.writeHNT("LUAW", mSimulationTime); + esm.writeFormId(MWBase::Environment::get().getWorldModel()->getLastGeneratedRefNum(), true); + } + + void WorldView::ObjectGroup::updateList() + { + if (mChanged) + { + mList->clear(); + for (const ObjectId& id : mSet) + mList->push_back(id); + mChanged = false; + } + } + + void WorldView::ObjectGroup::clear() + { + mChanged = false; + mList->clear(); + mSet.clear(); + } + + void WorldView::addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr) + { + group.mSet.insert(getId(ptr)); + group.mChanged = true; + } + + void WorldView::removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr) + { + group.mSet.erase(getId(ptr)); + group.mChanged = true; + } +} diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp new file mode 100644 index 000000000..7efb7c0f0 --- /dev/null +++ b/apps/openmw/mwlua/worldview.hpp @@ -0,0 +1,92 @@ +#ifndef MWLUA_WORLDVIEW_H +#define MWLUA_WORLDVIEW_H + +#include "object.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/globals.hpp" + +#include + +namespace ESM +{ + class ESMWriter; + class ESMReader; +} + +namespace MWLua +{ + + // WorldView is a kind of an extension to mwworld. It was created on initial stage of + // OpenMW Lua development in order to minimize the risk of merge conflicts. + // TODO: Move get*InScene functions to mwworld/scene + // TODO: Move time-related stuff to mwworld; maybe create a new class TimeManager. + // TODO: Remove WorldView. + class WorldView + { + public: + void update(); // Should be called every frame. + void clear(); // Should be called every time before starting or loading a new game. + + // Whether the world is paused (i.e. game time is not changing and actors don't move). + bool isPaused() const { return mPaused; } + + // The number of seconds passed from the beginning of the game. + double getSimulationTime() const { return mSimulationTime; } + void setSimulationTime(double t) { mSimulationTime = t; } + + // The game time (in game seconds) passed from the beginning of the game. + // Note that game time generally goes faster than the simulation time. + double getGameTime() const; + double getGameTimeScale() const { return MWBase::Environment::get().getWorld()->getTimeScaleFactor(); } + void setGameTimeScale(double s) + { + MWBase::Environment::get().getWorld()->setGlobalFloat(MWWorld::Globals::sTimeScale, s); + } + + ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; } + ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } + ObjectIdList getContainersInScene() const { return mContainersInScene.mList; } + ObjectIdList getDoorsInScene() const { return mDoorsInScene.mList; } + ObjectIdList getItemsInScene() const { return mItemsInScene.mList; } + ObjectIdList getPlayers() const { return mPlayers; } + + void objectAddedToScene(const MWWorld::Ptr& ptr); + void objectRemovedFromScene(const MWWorld::Ptr& ptr); + + void setPlayer(const MWWorld::Ptr& player) { *mPlayers = { getId(player) }; } + + void load(ESM::ESMReader& esm); + void save(ESM::ESMWriter& esm) const; + + private: + struct ObjectGroup + { + void updateList(); + void clear(); + + bool mChanged = false; + ObjectIdList mList = std::make_shared>(); + std::set mSet; + }; + + ObjectGroup* chooseGroup(const MWWorld::Ptr& ptr); + void addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); + void removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); + + ObjectGroup mActivatorsInScene; + ObjectGroup mActorsInScene; + ObjectGroup mContainersInScene; + ObjectGroup mDoorsInScene; + ObjectGroup mItemsInScene; + ObjectIdList mPlayers = std::make_shared>(); + + double mSimulationTime = 0; + bool mPaused = false; + }; + +} + +#endif // MWLUA_WORLDVIEW_H diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index ddb6a3353..c04dcb7ad 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,9 +1,23 @@ #include "activespells.hpp" -#include -#include +#include -#include +#include + +#include + +#include + +#include +#include +#include + +#include + +#include "actorutil.hpp" +#include "creaturestats.hpp" +#include "spellcasting.hpp" +#include "spelleffects.hpp" /* Start of tes3mp addition @@ -22,24 +36,187 @@ */ #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwrender/animation.hpp" + +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" + +namespace +{ + bool merge(std::vector& present, const std::vector& queued) + { + // Can't merge if we already have an effect with the same effect index + auto problem = std::find_if(queued.begin(), queued.end(), [&](const auto& qEffect) { + return std::find_if(present.begin(), present.end(), [&](const auto& pEffect) { + return pEffect.mEffectIndex == qEffect.mEffectIndex; + }) != present.end(); + }); + if (problem != queued.end()) + return false; + present.insert(present.end(), queued.begin(), queued.end()); + return true; + } + + void addEffects( + std::vector& effects, const ESM::EffectList& list, bool ignoreResistances = false) + { + int currentEffectIndex = 0; + for (const auto& enam : list.mList) + { + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mMagnMin; + effect.mMaxMagnitude = enam.mMagnMax; + effect.mEffectIndex = currentEffectIndex++; + effect.mFlags = ESM::ActiveEffect::Flag_None; + if (ignoreResistances) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effects.emplace_back(effect); + } + } +} namespace MWMechanics { - void ActiveSpells::update(float duration) const + ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) + : mActiveSpells(spells) { - bool rebuild = false; + mActiveSpells.mIterating = true; + } - // Erase no longer active spells and effects - if (duration > 0) + ActiveSpells::IterationGuard::~IterationGuard() + { + mActiveSpells.mIterating = false; + } + + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster) + : mId(cast.mId) + , mDisplayName(cast.mSourceName) + , mCasterActorId(-1) + , mSlot(cast.mSlot) + , mType(cast.mType) + , mWorsenings(-1) + { + if (!caster.isEmpty() && caster.getClass().isActor()) + mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId(); + } + + ActiveSpells::ActiveSpellParams::ActiveSpellParams( + const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances) + : mId(spell->mId) + , mDisplayName(spell->mName) + , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) + , mSlot(0) + , mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability + : ESM::ActiveSpells::Type_Permanent) + , mWorsenings(-1) + { + assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power); + addEffects(mEffects, spell->mEffects, ignoreResistances); + } + + ActiveSpells::ActiveSpellParams::ActiveSpellParams( + const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor) + : mId(item.getCellRef().getRefId()) + , mDisplayName(item.getClass().getName(item)) + , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) + , mSlot(slotIndex) + , mType(ESM::ActiveSpells::Type_Enchantment) + , mWorsenings(-1) + { + assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect); + addEffects(mEffects, enchantment->mEffects); + } + + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params) + : mId(params.mId) + , mEffects(params.mEffects) + , mDisplayName(params.mDisplayName) + , mCasterActorId(params.mCasterActorId) + , mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0) + , mType(params.mType) + , mWorsenings(params.mWorsenings) + , mNextWorsening({ params.mNextWorsening }) + { + } + + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor) + : mId(params.mId) + , mDisplayName(params.mDisplayName) + , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) + , mSlot(params.mSlot) + , mType(params.mType) + , mWorsenings(-1) + { + } + + ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const + { + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = mId; + params.mEffects = mEffects; + params.mDisplayName = mDisplayName; + params.mCasterActorId = mCasterActorId; + if (mSlot) { - TContainer::iterator iter (mSpells.begin()); - while (iter!=mSpells.end()) + // Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing + // mSlot needs to be replaced with a RefNum once inventory items get persistent RefNum (#4508 #6148) + params.mItem = { static_cast(mSlot), 0 }; + } + params.mType = mType; + params.mWorsenings = mWorsenings; + params.mNextWorsening = mNextWorsening.toEsm(); + return params; + } + + void ActiveSpells::ActiveSpellParams::worsen() + { + ++mWorsenings; + if (!mWorsenings) + mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp(); + mNextWorsening += CorprusStats::sWorseningPeriod; + } + + bool ActiveSpells::ActiveSpellParams::shouldWorsen() const + { + return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening; + } + + void ActiveSpells::ActiveSpellParams::resetWorsenings() + { + mWorsenings = -1; + } + + void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) + { + if (mIterating) + return; + auto& creatureStats = ptr.getClass().getCreatureStats(ptr); + assert(&creatureStats.getActiveSpells() == this); + IterationGuard guard{ *this }; + // Erase no longer active spells and effects + for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();) + { + if (spellIt->mType != ESM::ActiveSpells::Type_Temporary + && spellIt->mType != ESM::ActiveSpells::Type_Consumable) { - if (!timeToExpire (iter)) + ++spellIt; + continue; + } + bool removedSpell = false; + for (auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();) + { + if (effectIt->mFlags & ESM::ActiveEffect::Flag_Remove && effectIt->mTimeLeft <= 0.f) { +<<<<<<< HEAD /* Start of tes3mp addition @@ -66,74 +243,208 @@ namespace MWMechanics mSpells.erase (iter++); rebuild = true; +======= + auto effect = *effectIt; + effectIt = spellIt->mEffects.erase(effectIt); + onMagicEffectRemoved(ptr, *spellIt, effect); + removedSpell = applyPurges(ptr, &spellIt, &effectIt); + if (removedSpell) + break; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } else { - bool interrupt = false; - std::vector& effects = iter->second.mEffects; - for (std::vector::iterator effectIt = effects.begin(); effectIt != effects.end();) - { - if (effectIt->mTimeLeft <= 0) - { - rebuild = true; + ++effectIt; + } + } + if (removedSpell) + continue; + if (spellIt->mEffects.empty()) + spellIt = mSpells.erase(spellIt); + else + ++spellIt; + } - // Note: it we expire a Corprus effect, we should remove the whole spell. - if (effectIt->mEffectId == ESM::MagicEffect::Corprus) - { - iter = mSpells.erase (iter); - interrupt = true; - break; - } + for (const auto& spell : mQueue) + addToSpells(ptr, spell); + mQueue.clear(); - effectIt = effects.erase(effectIt); - } - else - { - effectIt->mTimeLeft -= duration; - ++effectIt; - } - } + // Vanilla only does this on cell change I think + const auto& spells = creatureStats.getSpells(); + for (const ESM::Spell* spell : spells) + { + if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power + && !isSpellActive(spell->mId)) + mSpells.emplace_back(ActiveSpellParams{ spell, ptr }); + } - if (!interrupt) - ++iter; + if (ptr.getClass().hasInventoryStore(ptr) + && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())) + { + auto& store = ptr.getClass().getInventoryStore(ptr); + if (store.getInvListener() != nullptr) + { + bool playNonLooping = !store.isFirstEquip(); + const auto world = MWBase::Environment::get().getWorld(); + for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) + { + auto slot = store.getSlot(slotIndex); + if (slot == store.end()) + continue; + const ESM::RefId& enchantmentId = slot->getClass().getEnchantment(*slot); + if (enchantmentId.empty()) + continue; + const ESM::Enchantment* enchantment = world->getStore().get().find(enchantmentId); + if (enchantment->mData.mType != ESM::Enchantment::ConstantEffect) + continue; + if (std::find_if(mSpells.begin(), mSpells.end(), + [&](const ActiveSpellParams& params) { + return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment + && params.mId == slot->getCellRef().getRefId(); + }) + != mSpells.end()) + continue; + // world->breakInvisibility leads to a stack overflow as it calls this method so just break + // invisibility manually + purgeEffect(ptr, ESM::MagicEffect::Invisibility); + applyPurges(ptr); + const ActiveSpellParams& params + = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, slotIndex, ptr }); + for (const auto& effect : params.mEffects) + MWMechanics::playEffects( + ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); } } } - if (mSpellsChanged) + const MWWorld::Ptr player = MWMechanics::getPlayer(); + bool updatedHitOverlay = false; + bool updatedEnemy = false; + // Update effects + for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - mSpellsChanged = false; - rebuild = true; + const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId( + spellIt->mCasterActorId); // Maybe make this search outside active grid? + bool removedSpell = false; + std::optional reflected; + for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) + { + auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration); + if (result.mType == MagicApplicationResult::Type::REFLECTED) + { + if (!reflected) + { + if (Settings::game().mClassicReflectedAbsorbSpellsBehavior) + reflected = { *spellIt, caster }; + else + reflected = { *spellIt, ptr }; + } + auto& reflectedEffect = reflected->mEffects.emplace_back(*it); + reflectedEffect.mFlags + = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; + it = spellIt->mEffects.erase(it); + } + else if (result.mType == MagicApplicationResult::Type::REMOVED) + it = spellIt->mEffects.erase(it); + else + { + ++it; + if (!updatedEnemy && result.mShowHealth && caster == player && ptr != player) + { + MWBase::Environment::get().getWindowManager()->setEnemy(ptr); + updatedEnemy = true; + } + if (!updatedHitOverlay && result.mShowHit && ptr == player) + { + MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); + updatedHitOverlay = true; + } + } + removedSpell = applyPurges(ptr, &spellIt, &it); + if (removedSpell) + break; + } + if (reflected) + { + const ESM::Static* reflectStatic = MWBase::Environment::get().getESMStore()->get().find( + ESM::RefId::stringRefId("VFX_Reflect")); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if (animation && !reflectStatic->mModel.empty()) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel, vfs), + ESM::MagicEffect::Reflect, false); + } + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); + } + if (removedSpell) + continue; + + bool remove = false; + if (spellIt->mType == ESM::ActiveSpells::Type_Ability + || spellIt->mType == ESM::ActiveSpells::Type_Permanent) + { + try + { + remove = !spells.hasSpell(spellIt->mId); + } + catch (const std::runtime_error& e) + { + remove = true; + Log(Debug::Error) << "Removing active effect: " << e.what(); + } + } + else if (spellIt->mType == ESM::ActiveSpells::Type_Enchantment) + { + const auto& store = ptr.getClass().getInventoryStore(ptr); + auto slot = store.getSlot(spellIt->mSlot); + remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId; + } + if (remove) + { + auto params = *spellIt; + spellIt = mSpells.erase(spellIt); + for (const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); + applyPurges(ptr, &spellIt); + continue; + } + ++spellIt; } - if (rebuild) - rebuildEffects(); + if (Settings::game().mClassicCalmSpellsBehavior) + { + ESM::MagicEffect::Effects effect + = ptr.getClass().isNpc() ? ESM::MagicEffect::CalmHumanoid : ESM::MagicEffect::CalmCreature; + if (creatureStats.getMagicEffects().getOrDefault(effect).getMagnitude() > 0.f) + creatureStats.getAiSequence().stopCombat(); + } } - void ActiveSpells::rebuildEffects() const + void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) { - mEffects = MagicEffects(); - - for (TIterator iter (begin()); iter!=end(); ++iter) + if (spell.mType != ESM::ActiveSpells::Type_Consumable) { - const std::vector& effects = iter->second.mEffects; - - for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + auto found = std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& existing) { + return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId + && spell.mSlot == existing.mSlot; + }); + if (found != mSpells.end()) { - if (effectIt->mTimeLeft > 0) - mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude)); + if (merge(found->mEffects, spell.mEffects)) + return; + auto params = *found; + mSpells.erase(found); + for (const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); } } + mSpells.emplace_back(spell); } ActiveSpells::ActiveSpells() - : mSpellsChanged (false) - {} - - const MagicEffects& ActiveSpells::getMagicEffects() const + : mIterating(false) { - update(0.f); - return mEffects; } ActiveSpells::TIterator ActiveSpells::begin() const @@ -146,40 +457,23 @@ namespace MWMechanics return mSpells.end(); } - double ActiveSpells::timeToExpire (const TIterator& iterator) const + bool ActiveSpells::isSpellActive(const ESM::RefId& id) const { - const std::vector& effects = iterator->second.mEffects; - - float duration = 0; - - for (std::vector::const_iterator iter (effects.begin()); - iter!=effects.end(); ++iter) - { - if (iter->mTimeLeft > duration) - duration = iter->mTimeLeft; - } - - if (duration < 0) - return 0; - - return duration; + return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { return spell.mId == id; }) + != mSpells.end(); } - bool ActiveSpells::isSpellActive(const std::string& id) const + void ActiveSpells::addSpell(const ActiveSpellParams& params) { - for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) - { - if (Misc::StringUtils::ciEqual(iter->first, id)) - return true; - } - return false; + mQueue.emplace_back(params); } - const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const + void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor) { - return mSpells; + mQueue.emplace_back(ActiveSpellParams{ spell, actor, true }); } +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -211,9 +505,18 @@ namespace MWMechanics */ if (it == end() || stack) +======= + void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr) + { + assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this); + mPurges.emplace(predicate); + if (!mIterating) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - mSpells.insert(std::make_pair(id, params)); + IterationGuard guard{ *this }; + applyPurges(ptr); } +<<<<<<< HEAD else { // addSpell() is called with effects for a range. @@ -269,38 +572,90 @@ namespace MWMechanics */ void ActiveSpells::mergeEffects(std::vector& addTo, const std::vector& from) +======= + } + + void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - for (std::vector::const_iterator effect(from.begin()); effect != from.end(); ++effect) + assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this); + mPurges.emplace(predicate); + if (!mIterating) { - // if effect is not in addTo, add it - bool missing = true; - for (std::vector::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter) - { - if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg)) - { - missing = false; - break; - } - } - if (missing) - { - addTo.push_back(*effect); - } + IterationGuard guard{ *this }; + applyPurges(ptr); } } - void ActiveSpells::removeEffects(const std::string &id) + bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell, + std::vector::iterator* currentEffect) { - for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) + bool removedCurrentSpell = false; + while (!mPurges.empty()) { - if (spell->first == id) + auto predicate = mPurges.front(); + mPurges.pop(); + for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - spell->second.mEffects.clear(); - mSpellsChanged = true; + bool isCurrentSpell = currentSpell && *currentSpell == spellIt; + std::visit( + [&](auto&& variant) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + if (variant(*spellIt)) + { + auto params = *spellIt; + spellIt = mSpells.erase(spellIt); + if (isCurrentSpell) + { + *currentSpell = spellIt; + removedCurrentSpell = true; + } + for (const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); + } + else + ++spellIt; + } + else + { + static_assert(std::is_same_v, "Non-exhaustive visitor"); + for (auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();) + { + if (variant(*spellIt, *effectIt)) + { + auto effect = *effectIt; + if (isCurrentSpell && currentEffect) + { + auto distance = std::distance(spellIt->mEffects.begin(), *currentEffect); + if (effectIt <= *currentEffect) + distance--; + effectIt = spellIt->mEffects.erase(effectIt); + *currentEffect = spellIt->mEffects.begin() + distance; + } + else + effectIt = spellIt->mEffects.erase(effectIt); + onMagicEffectRemoved(ptr, *spellIt, effect); + } + else + ++effectIt; + } + ++spellIt; + } + }, + predicate); } } + return removedCurrentSpell; } + void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id) + { + purge([=](const ActiveSpellParams& params) { return params.mId == id; }, ptr); + } + +<<<<<<< HEAD /* Start of tes3mp addition @@ -331,66 +686,36 @@ namespace MWMechanics */ void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const +======= + void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, int effectId, int effectArg) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - for (TContainer::const_iterator it = begin(); it != end(); ++it) - { - for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end(); ++effectIt) - { - std::string name = it->second.mDisplayName; - - float magnitude = effectIt->mMagnitude; - if (magnitude) - visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration); - } - } - } - - void ActiveSpells::purgeAll(float chance, bool spellOnly) - { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) - { - const std::string spellId = it->first; - - // if spellOnly is true, dispell only spells. Leave potions, enchanted items etc. - if (spellOnly) - { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); - if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) - { - ++it; - continue; - } - } - - if (Misc::Rng::roll0to99() < chance) - mSpells.erase(it++); - else - ++it; - } - mSpellsChanged = true; - } - - void ActiveSpells::purgeEffect(short effectId) - { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) - { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end();) - { - if (effectIt->mEffectId == effectId) - effectIt = it->second.mEffects.erase(effectIt); + purge( + [=](const ActiveSpellParams&, const ESM::ActiveEffect& effect) { + if (effectArg < 0) + return effect.mEffectId == effectId; else - ++effectIt; - } - } - mSpellsChanged = true; + return effect.mEffectId == effectId && effect.mArg == effectArg; + }, + ptr); } - void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex) + void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId) { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + purge([=](const ActiveSpellParams& params) { return params.mCasterActorId == casterActorId; }, ptr); + } + + void ActiveSpells::clear(const MWWorld::Ptr& ptr) + { + mQueue.clear(); + purge([](const ActiveSpellParams& params) { return true; }, ptr); + } + + void ActiveSpells::skipWorsenings(double hours) + { + for (auto& spell : mSpells) { +<<<<<<< HEAD for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { @@ -490,42 +815,38 @@ namespace MWMechanics } else ++iter; +======= + if (spell.mWorsenings >= 0) + spell.mNextWorsening += hours; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } - void ActiveSpells::clear() + void ActiveSpells::writeState(ESM::ActiveSpells& state) const { - mSpells.clear(); - mSpellsChanged = true; + for (const auto& spell : mSpells) + state.mSpells.emplace_back(spell.toEsm()); + for (const auto& spell : mQueue) + state.mQueue.emplace_back(spell.toEsm()); } - void ActiveSpells::writeState(ESM::ActiveSpells &state) const + void ActiveSpells::readState(const ESM::ActiveSpells& state) { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) - { - // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp - ESM::ActiveSpells::ActiveSpellParams params; - params.mEffects = it->second.mEffects; - params.mCasterActorId = it->second.mCasterActorId; - params.mDisplayName = it->second.mDisplayName; - - state.mSpells.insert (std::make_pair(it->first, params)); - } + for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells) + mSpells.emplace_back(ActiveSpellParams{ spell }); + for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue) + mQueue.emplace_back(ActiveSpellParams{ spell }); } - void ActiveSpells::readState(const ESM::ActiveSpells &state) + void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr) { - for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) - { - // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp - ActiveSpellParams params; - params.mEffects = it->second.mEffects; - params.mCasterActorId = it->second.mCasterActorId; - params.mDisplayName = it->second.mDisplayName; - - mSpells.insert (std::make_pair(it->first, params)); - mSpellsChanged = true; - } + purge( + [](const auto& spell) { + return spell.getType() == ESM::ActiveSpells::Type_Consumable + || spell.getType() == ESM::ActiveSpells::Type_Temporary; + }, + ptr); + mQueue.clear(); } /* diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 75a2f22d2..9ebc4829d 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -1,44 +1,59 @@ #ifndef GAME_MWMECHANICS_ACTIVESPELLS_H #define GAME_MWMECHANICS_ACTIVESPELLS_H -#include -#include +#include +#include +#include #include +#include +#include -#include +#include +#include "../mwworld/ptr.hpp" #include "../mwworld/timestamp.hpp" -#include "magiceffects.hpp" +#include "spellcasting.hpp" + +namespace ESM +{ + struct Enchantment; + struct Spell; +} namespace MWMechanics { /// \brief Lasting spell effects /// - /// \note The name of this class is slightly misleading, since it also handels lasting potion + /// \note The name of this class is slightly misleading, since it also handles lasting potion /// effects. class ActiveSpells { - public: + public: + using ActiveEffect = ESM::ActiveEffect; + class ActiveSpellParams + { + ESM::RefId mId; + std::vector mEffects; + std::string mDisplayName; + int mCasterActorId; + int mSlot; + ESM::ActiveSpells::EffectType mType; + int mWorsenings; + MWWorld::TimeStamp mNextWorsening; - typedef ESM::ActiveEffect ActiveEffect; + ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params); - struct ActiveSpellParams - { - std::vector mEffects; - MWWorld::TimeStamp mTimeStamp; - std::string mDisplayName; + ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false); - // The caster that inflicted this spell on us - int mCasterActorId; - }; + ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, + const MWWorld::Ptr& actor); - typedef std::multimap TContainer; - typedef TContainer::const_iterator TIterator; + ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor); - void readState (const ESM::ActiveSpells& state); - void writeState (ESM::ActiveSpells& state) const; + ESM::ActiveSpells::ActiveSpellParams toEsm() const; +<<<<<<< HEAD TIterator begin() const; TIterator end() const; @@ -71,22 +86,19 @@ namespace MWMechanics /// expires. const TContainer& getActiveSpells() const; +======= + friend class ActiveSpells; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 public: + ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster); - ActiveSpells(); + const ESM::RefId& getId() const { return mId; } - /// Add lasting effects - /// - /// \brief addSpell - /// \param id ID for stacking purposes. - /// \param stack If false, the spell is not added if one with the same ID exists already. - /// \param effects - /// \param displayName Name for display in magic menu. - /// - void addSpell (const std::string& id, bool stack, std::vector effects, - const std::string& displayName, int casterActorId); + const std::vector& getEffects() const { return mEffects; } + std::vector& getEffects() { return mEffects; } +<<<<<<< HEAD /* Start of tes3mp addition @@ -114,16 +126,20 @@ namespace MWMechanics /// Remove all active effects with this effect id void purgeEffect (short effectId); +======= + ESM::ActiveSpells::EffectType getType() const { return mType; } - /// Remove all active effects with this effect id and source id - void purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1); + int getCasterActorId() const { return mCasterActorId; } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - /// Remove all active effects, if roll succeeds (for each effect) - void purgeAll(float chance, bool spellOnly = false); + int getWorsenings() const { return mWorsenings; } - /// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId - void purge (int casterActorId); + const std::string& getDisplayName() const { return mDisplayName; } + // Increments worsenings count and sets the next timestamp + void worsen(); + +<<<<<<< HEAD /* Start of tes3mp addition @@ -146,16 +162,22 @@ namespace MWMechanics /// Remove all spells void clear(); +======= + bool shouldWorsen() const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - bool isSpellActive (const std::string& id) const; - ///< case insensitive + void resetWorsenings(); + }; - void purgeCorprusDisease(); + typedef std::list Collection; + typedef Collection::const_iterator TIterator; - const MagicEffects& getMagicEffects() const; + void readState(const ESM::ActiveSpells& state); + void writeState(ESM::ActiveSpells& state) const; - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; + TIterator begin() const; +<<<<<<< HEAD /* Start of tes3mp addition @@ -167,6 +189,69 @@ namespace MWMechanics End of tes3mp addition */ +======= + TIterator end() const; + + void update(const MWWorld::Ptr& ptr, float duration); + + private: + using ParamsPredicate = std::function; + using EffectPredicate = std::function; + using Predicate = std::variant; + + struct IterationGuard + { + ActiveSpells& mActiveSpells; + + IterationGuard(ActiveSpells& spells); + ~IterationGuard(); + }; + + std::list mSpells; + std::vector mQueue; + std::queue mPurges; + bool mIterating; + + void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell); + + bool applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell = nullptr, + std::vector::iterator* currentEffect = nullptr); + + public: + ActiveSpells(); + + /// Add lasting effects + /// + /// \brief addSpell + /// \param id ID for stacking purposes. + /// + void addSpell(const ActiveSpellParams& params); + + /// Bypasses resistances + void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor); + + /// Removes the active effects from this spell/potion/.. with \a id + void removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id); + + /// Remove all active effects with this effect id + void purgeEffect(const MWWorld::Ptr& ptr, int effectId, int effectArg = -1); + + void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr); + void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr); + + /// Remove all effects that were cast by \a casterActorId + void purge(const MWWorld::Ptr& ptr, int casterActorId); + + /// Remove all spells + void clear(const MWWorld::Ptr& ptr); + + bool isSpellActive(const ESM::RefId& id) const; + ///< case insensitive + + void skipWorsenings(double hours); + + void unloadActor(const MWWorld::Ptr& ptr); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; } diff --git a/apps/openmw/mwmechanics/actor.cpp b/apps/openmw/mwmechanics/actor.cpp deleted file mode 100644 index a5c55633a..000000000 --- a/apps/openmw/mwmechanics/actor.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "actor.hpp" - -#include "character.hpp" - -namespace MWMechanics -{ - Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation) - { - mCharacterController.reset(new CharacterController(ptr, animation)); - } - - void Actor::updatePtr(const MWWorld::Ptr &newPtr) - { - mCharacterController->updatePtr(newPtr); - } - - CharacterController* Actor::getCharacterController() - { - return mCharacterController.get(); - } - - int Actor::getGreetingTimer() const - { - return mGreetingTimer; - } - - void Actor::setGreetingTimer(int timer) - { - mGreetingTimer = timer; - } - - float Actor::getAngleToPlayer() const - { - return mTargetAngleRadians; - } - - void Actor::setAngleToPlayer(float angle) - { - mTargetAngleRadians = angle; - } - - GreetingState Actor::getGreetingState() const - { - return mGreetingState; - } - - void Actor::setGreetingState(GreetingState state) - { - mGreetingState = state; - } - - bool Actor::isTurningToPlayer() const - { - return mIsTurningToPlayer; - } - - void Actor::setTurningToPlayer(bool turning) - { - mIsTurningToPlayer = turning; - } -} diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp index be4f42537..d7438712d 100644 --- a/apps/openmw/mwmechanics/actor.hpp +++ b/apps/openmw/mwmechanics/actor.hpp @@ -3,7 +3,13 @@ #include -#include "../mwmechanics/actorutil.hpp" +#include "character.hpp" +#include "creaturestats.hpp" +#include "greetingstate.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/class.hpp" #include @@ -18,43 +24,53 @@ namespace MWWorld namespace MWMechanics { - class CharacterController; - /// @brief Holds temporary state for an actor that will be discarded when the actor leaves the scene. class Actor { public: - Actor(const MWWorld::Ptr& ptr, MWRender::Animation* animation); + Actor(const MWWorld::Ptr& ptr, MWRender::Animation* animation) + : mCharacterController(ptr, animation) + , mPositionAdjusted(ptr.getClass().getCreatureStats(ptr).getFallHeight() > 0) + { + } + + const MWWorld::Ptr& getPtr() const { return mCharacterController.getPtr(); } /// Notify this actor of its new base object Ptr, use when the object changed cells - void updatePtr(const MWWorld::Ptr& newPtr); + void updatePtr(const MWWorld::Ptr& newPtr) { mCharacterController.updatePtr(newPtr); } - CharacterController* getCharacterController(); + CharacterController& getCharacterController() { return mCharacterController; } + const CharacterController& getCharacterController() const { return mCharacterController; } - int getGreetingTimer() const; - void setGreetingTimer(int timer); + int getGreetingTimer() const { return mGreetingTimer; } + void setGreetingTimer(int timer) { mGreetingTimer = timer; } - float getAngleToPlayer() const; - void setAngleToPlayer(float angle); + float getAngleToPlayer() const { return mTargetAngleRadians; } + void setAngleToPlayer(float angle) { mTargetAngleRadians = angle; } - GreetingState getGreetingState() const; - void setGreetingState(GreetingState state); + GreetingState getGreetingState() const { return mGreetingState; } + void setGreetingState(GreetingState state) { mGreetingState = state; } - bool isTurningToPlayer() const; - void setTurningToPlayer(bool turning); + bool isTurningToPlayer() const { return mIsTurningToPlayer; } + void setTurningToPlayer(bool turning) { mIsTurningToPlayer = turning; } Misc::TimerStatus updateEngageCombatTimer(float duration) { - return mEngageCombat.update(duration); + return mEngageCombat.update(duration, MWBase::Environment::get().getWorld()->getPrng()); } + void setPositionAdjusted(bool adjusted) { mPositionAdjusted = adjusted; } + bool getPositionAdjusted() const { return mPositionAdjusted; } + private: - std::unique_ptr mCharacterController; - int mGreetingTimer{0}; - float mTargetAngleRadians{0.f}; - GreetingState mGreetingState{Greet_None}; - bool mIsTurningToPlayer{false}; - Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)}; + CharacterController mCharacterController; + int mGreetingTimer{ 0 }; + float mTargetAngleRadians{ 0.f }; + GreetingState mGreetingState{ Greet_None }; + bool mIsTurningToPlayer{ false }; + Misc::DeviatingPeriodicTimer mEngageCombat{ 1.0f, 0.25f, + Misc::Rng::deviate(0, 0.25f, MWBase::Environment::get().getWorld()->getPrng()) }; + bool mPositionAdjusted; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0bf28e47d..48b9022d4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2,15 +2,17 @@ #include -#include -#include +#include +#include -#include #include -#include #include -#include +#include +#include +#include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -31,306 +33,332 @@ */ #include "../mwworld/esmstore.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwworld/actionequip.hpp" -#include "../mwworld/player.hpp" +======= +#include +#include +#include +#include + +#include "../mwworld/cellstore.hpp" +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/scene.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwbase/soundmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/aibreathe.hpp" #include "../mwrender/vismask.hpp" -#include "spellcasting.hpp" -#include "steering.hpp" -#include "npcstats.hpp" -#include "creaturestats.hpp" -#include "movement.hpp" -#include "character.hpp" -#include "aicombat.hpp" +#include "actor.hpp" +#include "actorutil.hpp" #include "aicombataction.hpp" #include "aifollow.hpp" #include "aipursue.hpp" #include "aiwander.hpp" -#include "actor.hpp" +#include "character.hpp" +#include "creaturestats.hpp" +#include "movement.hpp" +#include "npcstats.hpp" +#include "steering.hpp" #include "summoning.hpp" -#include "actorutil.hpp" -#include "tickableeffects.hpp" namespace { -bool isConscious(const MWWorld::Ptr& ptr) -{ - const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - return !stats.isDead() && !stats.getKnockedDown(); -} - -int getBoundItemSlot (const std::string& itemId) -{ - static std::map boundItemsMap; - if (boundItemsMap.empty()) + bool isConscious(const MWWorld::Ptr& ptr) { - std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; - - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; - - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; - - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; - - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; - - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + return !stats.isDead() && !stats.getKnockedDown(); } - int slot = MWWorld::InventoryStore::Slot_CarriedRight; - std::map::iterator it = boundItemsMap.find(itemId); - if (it != boundItemsMap.end()) - slot = it->second; - - return slot; -} - -class CheckActorCommanded : public MWMechanics::EffectSourceVisitor -{ - MWWorld::Ptr mActor; -public: - bool mCommanded; - - CheckActorCommanded(const MWWorld::Ptr& actor) - : mActor(actor) - , mCommanded(false){} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override + bool isCommanded(const MWWorld::Ptr& actor) { - if (((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) - || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name())) - && magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel()) - mCommanded = true; - } -}; - -// Check for command effects having ended and remove package if necessary -void adjustCommandedActor (const MWWorld::Ptr& actor) -{ - CheckActorCommanded check(actor); - MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - stats.getActiveSpells().visitEffectSources(check); - - bool hasCommandPackage = false; - - auto it = stats.getAiSequence().begin(); - for (; it != stats.getAiSequence().end(); ++it) - { - if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow && - static_cast(it->get())->isCommanded()) + const auto& actorClass = actor.getClass(); + const auto& stats = actorClass.getCreatureStats(actor); + const bool isActorNpc = actorClass.isNpc(); + const auto level = stats.getLevel(); + for (const auto& params : stats.getActiveSpells()) { - hasCommandPackage = true; - break; + for (const auto& effect : params.getEffects()) + { + if (((effect.mEffectId == ESM::MagicEffect::CommandHumanoid && isActorNpc) + || (effect.mEffectId == ESM::MagicEffect::CommandCreature && !isActorNpc)) + && effect.mMagnitude >= level) + return true; + } + } + return false; + } + + // Check for command effects having ended and remove package if necessary + void adjustCommandedActor(const MWWorld::Ptr& actor) + { + if (isCommanded(actor)) + return; + + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + stats.getAiSequence().erasePackageIf([](auto& entry) { + if (entry->getTypeId() == MWMechanics::AiPackageTypeId::Follow + && static_cast(entry.get())->isCommanded()) + { + return true; + } + return false; + }); + } + + std::pair getRestorationPerHourOfSleep(const MWWorld::Ptr& ptr) + { + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const MWWorld::Store& settings + = MWBase::Environment::get().getESMStore()->get(); + + const float endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified(); + const float health = 0.1f * endurance; + + static const float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat(); + const float magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); + + return { health, magicka }; + } + + template + void forEachFollowingPackage( + const std::list& actors, const MWWorld::Ptr& actorPtr, const MWWorld::Ptr& player, T&& func) + { + for (const MWMechanics::Actor& actor : actors) + { + const MWWorld::Ptr& iteratedActor = actor.getPtr(); + if (iteratedActor == player || iteratedActor == actorPtr) + continue; + + const MWMechanics::CreatureStats& stats = iteratedActor.getClass().getCreatureStats(iteratedActor); + if (stats.isDead()) + continue; + + // An actor counts as following if AiFollow is the current AiPackage, + // or there are only Combat and Wander packages before the AiFollow package + for (const auto& package : stats.getAiSequence()) + { + if (!func(actor, package)) + break; + } } } - if (!check.mCommanded && hasCommandPackage) - stats.getAiSequence().erase(it); -} - -void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) -{ - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); - - float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); - health = 0.1f * endurance; - - float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat (); - magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); -} - -template -void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWWorld::Ptr& actor, const MWWorld::Ptr& player, T&& func) -{ - for(auto& iter : actors) + float getStuntedMagickaDuration(const MWWorld::Ptr& actor) { - const MWWorld::Ptr &iteratedActor = iter.first; - if (iteratedActor == player || iteratedActor == actor) - continue; - - const MWMechanics::CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); - if (stats.isDead()) - continue; - - // An actor counts as following if AiFollow is the current AiPackage, - // or there are only Combat and Wander packages before the AiFollow package - for (const auto& package : stats.getAiSequence()) + float remainingTime = 0.f; + for (const auto& params : actor.getClass().getCreatureStats(actor).getActiveSpells()) { - if(!func(iter, package)) - break; + for (const auto& effect : params.getEffects()) + { + if (effect.mEffectId == ESM::MagicEffect::StuntedMagicka) + { + if (effect.mDuration == -1.f) + return -1.f; + remainingTime = std::max(remainingTime, effect.mTimeLeft); + } + } + } + return remainingTime; + } + + void soulTrap(const MWWorld::Ptr& creature) + { + const auto& stats = creature.getClass().getCreatureStats(creature); + if (!stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Soultrap).getMagnitude()) + return; + const int creatureSoulValue = creature.get()->mBase->mData.mSoul; + if (creatureSoulValue == 0) + return; + MWBase::World* const world = MWBase::Environment::get().getWorld(); + static const float fSoulgemMult + = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); + for (const auto& params : stats.getActiveSpells()) + { + for (const auto& effect : params.getEffects()) + { + if (effect.mEffectId != ESM::MagicEffect::Soultrap || effect.mMagnitude <= 0.f) + continue; + MWWorld::Ptr caster = world->searchPtrViaActorId(params.getCasterActorId()); + if (caster.isEmpty() || !caster.getClass().isActor()) + continue; + + // Use the smallest soulgem that is large enough to hold the soul + MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster); + MWWorld::ContainerStoreIterator gem = container.end(); + float gemCapacity = std::numeric_limits::max(); + for (auto it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous); it != container.end(); + ++it) + { + if (it->getClass().isSoulGem(*it)) + { + float thisGemCapacity = it->get()->mBase->mData.mValue * fSoulgemMult; + if (thisGemCapacity >= creatureSoulValue && thisGemCapacity < gemCapacity + && it->getCellRef().getSoul().empty()) + { + gem = it; + gemCapacity = thisGemCapacity; + } + } + } + + if (gem == container.end()) + continue; + + // Set the soul on just one of the gems, not the whole stack + gem->getContainerStore()->unstack(*gem); + gem->getCellRef().setSoul(creature.getCellRef().getRefId()); + + // Restack the gem with other gems with the same soul + gem->getContainerStore()->restack(*gem); + + if (caster == MWMechanics::getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); + + const ESM::Static* const fx + = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Soul_Trap")); + if (fx != nullptr) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), "", + creature.getRefData().getPosition().asVec3()); + } + + MWBase::Environment::get().getSoundManager()->playSound3D( + creature.getRefData().getPosition().asVec3(), ESM::RefId::stringRefId("conjuration hit"), 1.f, 1.f); + return; // remove to get vanilla behaviour + } } } -} + + void removeTemporaryEffects(const MWWorld::Ptr& ptr) + { + ptr.getClass().getCreatureStats(ptr).getActiveSpells().unloadActor(ptr); + } } namespace MWMechanics { - static const int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player - static const int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player - static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement - static const float DECELERATE_DISTANCE = 512.f; + static constexpr int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player + static constexpr int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player + static constexpr int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement + static constexpr float DECELERATE_DISTANCE = 512.f; namespace { - float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents) + float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, + const osg::Vec3f& halfExtents) { - const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length(); + const auto distanceToNextPathPoint + = (package.getNextPathPoint(package.getDestination()) - position).length(); return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed; } - } - class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor - { - public: - float mRemainingTime; - - GetStuntedMagickaDuration(const MWWorld::Ptr& actor) - : mRemainingTime(0.f){} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override + void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, + MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance, bool inCombatOrPursue) { - if (mRemainingTime == -1) return; + const auto& actorRefData = actor.getRefData(); + if (!actorRefData.getBaseNode()) + return; - if (key.mId == ESM::MagicEffect::StuntedMagicka) + if (targetActor.getClass().getCreatureStats(targetActor).isDead()) + return; + + if (isTargetMagicallyHidden(targetActor)) + return; + + static const float fMaxHeadTrackDistance = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fMaxHeadTrackDistance") + ->mValue.getFloat(); + static const float fInteriorHeadTrackMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fInteriorHeadTrackMult") + ->mValue.getFloat(); + float maxDistance = fMaxHeadTrackDistance; + auto currentCell = actor.getCell()->getCell(); + if (!currentCell->isExterior() && !(currentCell->isQuasiExterior())) + maxDistance *= fInteriorHeadTrackMult; + + const osg::Vec3f actor1Pos(actorRefData.getPosition().asVec3()); + const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3()); + const float sqrDist = (actor1Pos - actor2Pos).length2(); + + if (sqrDist > std::min(maxDistance * maxDistance, sqrHeadTrackDistance) && !inCombatOrPursue) + return; + + // stop tracking when target is behind the actor + osg::Vec3f actorDirection = actorRefData.getBaseNode()->getAttitude() * osg::Vec3f(0, 1, 0); + osg::Vec3f targetDirection(actor2Pos - actor1Pos); + actorDirection.z() = 0; + targetDirection.z() = 0; + if ((actorDirection * targetDirection > 0 || inCombatOrPursue) + // check LOS and awareness last as it's the most expensive function + && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor)) { - if (totalTime == -1) - { - mRemainingTime = -1; - return; - } - - if (remainingTime > mRemainingTime) - mRemainingTime = remainingTime; + sqrHeadTrackDistance = sqrDist; + headTrackTarget = targetActor; } } - }; - class GetCurrentMagnitudes : public MWMechanics::EffectSourceVisitor - { - std::string mSpellId; - - public: - GetCurrentMagnitudes(const std::string& spellId) - : mSpellId(spellId) + void updateHeadTracking( + const MWWorld::Ptr& ptr, const std::list& actors, bool isPlayer, CharacterController& ctrl) { - } + float sqrHeadTrackDistance = std::numeric_limits::max(); + MWWorld::Ptr headTrackTarget; - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (magnitude <= 0) - return; + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); - if (sourceId != mSpellId) - return; - - mMagnitudes.emplace_back(key, magnitude); - } - - std::vector> mMagnitudes; - }; - - class GetCorprusSpells : public MWMechanics::EffectSourceVisitor - { - - public: - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (key.mId != ESM::MagicEffect::Corprus) - return; - - mSpells.push_back(sourceId); - } - - std::vector mSpells; - }; - - class SoulTrap : public MWMechanics::EffectSourceVisitor - { - MWWorld::Ptr mCreature; - MWWorld::Ptr mActor; - bool mTrapped; - public: - SoulTrap(const MWWorld::Ptr& trappedCreature) - : mCreature(trappedCreature) - , mTrapped(false) - { - } - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (mTrapped) - return; - if (key.mId != ESM::MagicEffect::Soultrap) - return; - if (magnitude <= 0) - return; - - MWBase::World* world = MWBase::Environment::get().getWorld(); - - MWWorld::Ptr caster = world->searchPtrViaActorId(casterActorId); - if (caster.isEmpty() || !caster.getClass().isActor()) - return; - - static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); - - int creatureSoulValue = mCreature.get()->mBase->mData.mSoul; - if (creatureSoulValue == 0) - return; - - // Use the smallest soulgem that is large enough to hold the soul - MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster); - MWWorld::ContainerStoreIterator gem = container.end(); - float gemCapacity = std::numeric_limits::max(); - std::string soulgemFilter = "misc_soulgem"; // no other way to check for soulgems? :/ - for (MWWorld::ContainerStoreIterator it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous); - it != container.end(); ++it) + // 1. Unconsious actor can not track target + // 2. Actors in combat and pursue mode do not bother to headtrack anyone except their target + // 3. Player character does not use headtracking in the 1st-person view + if (!stats.getKnockedDown() && !firstPersonPlayer) { - const std::string& id = it->getCellRef().getRefId(); - if (id.size() >= soulgemFilter.size() - && id.substr(0,soulgemFilter.size()) == soulgemFilter) + bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().isInPursuit(); + if (inCombatOrPursue) { - float thisGemCapacity = it->get()->mBase->mData.mValue * fSoulgemMult; - if (thisGemCapacity >= creatureSoulValue && thisGemCapacity < gemCapacity - && it->getCellRef().getSoul().empty()) + auto activePackageTarget = stats.getAiSequence().getActivePackage().getTarget(); + if (!activePackageTarget.isEmpty()) { - gem = it; - gemCapacity = thisGemCapacity; + // Track the specified target of package. + updateHeadTracking( + ptr, activePackageTarget, headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue); + } + } + else + { + // Find something nearby. + for (const Actor& otherActor : actors) + { + if (otherActor.getPtr() == ptr) + continue; + + updateHeadTracking( + ptr, otherActor.getPtr(), headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue); } } } +<<<<<<< HEAD if (gem == container.end()) return; @@ -441,86 +469,72 @@ namespace MWMechanics actor.getClass().getInventoryStore(actor).autoEquip(actor); return; +======= + ctrl.setHeadTrackTarget(headTrackTarget); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - std::string prevItemId = player.getPreviousItem(itemId); - player.erasePreviousItem(itemId); - - if (!prevItemId.empty()) + void updateLuaControls(const MWWorld::Ptr& ptr, bool isPlayer, MWBase::LuaManager::ActorControls& controls) { - // Find previous item (or its replacement) by id. - // we should equip previous item only if expired bound item was equipped. - MWWorld::Ptr item = store.findReplacement(prevItemId); - if (!item.isEmpty() && wasEquipped) + Movement& mov = ptr.getClass().getMovementSettings(ptr); + CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const float speedFactor = isPlayer ? 1.f : mov.mSpeedFactor; + const osg::Vec2f movement = osg::Vec2f(mov.mPosition[0], mov.mPosition[1]) * speedFactor; + const float rotationX = mov.mRotation[0]; + const float rotationZ = mov.mRotation[2]; + const bool jump = mov.mPosition[2] == 1; + const bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run); + const bool sneakFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak); + const bool attackingOrSpell = stats.getAttackingOrSpell(); + if (controls.mChanged) { - MWWorld::ActionEquip action(item); - action.execute(actor); + mov.mPosition[0] = controls.mSideMovement; + mov.mPosition[1] = controls.mMovement; + if (controls.mJump) + mov.mPosition[2] = 1; + mov.mRotation[0] = controls.mPitchChange; + mov.mRotation[1] = 0; + mov.mRotation[2] = controls.mYawChange; + mov.mSpeedFactor = osg::Vec2(controls.mMovement, controls.mSideMovement).length(); + stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun); + stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak); + stats.setAttackingOrSpell((controls.mUse & 1) == 1); + controls.mChanged = false; } + // For the player we don't need to copy these values to Lua because mwinput doesn't change them. + // All handling of these player controls was moved from C++ to a built-in Lua script. + if (!isPlayer) + { + controls.mSideMovement = movement.x(); + controls.mMovement = movement.y(); + controls.mJump = jump; + controls.mRun = runFlag; + controls.mSneak = sneakFlag; + controls.mUse = attackingOrSpell ? controls.mUse | 1 : controls.mUse & ~1; + } + // For the player these controls are still handled by mwinput, so we need to update the values. + controls.mPitchChange = rotationX; + controls.mYawChange = rotationZ; } - - store.remove(itemId, 1, actor); } - void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) + void Actors::updateActor(const MWWorld::Ptr& ptr, float duration) const { // magic effects - adjustMagicEffects (ptr); - if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) - calculateDynamicStats (ptr); + adjustMagicEffects(ptr, duration); - calculateCreatureStatModifiers (ptr, duration); // fatigue restoration calculateRestoration(ptr, duration); } - void Actors::updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, - MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance, - bool inCombatOrPursue) + void Actors::playIdleDialogue(const MWWorld::Ptr& actor) const { - if (!actor.getRefData().getBaseNode()) + if (!actor.getClass().isActor() || actor == getPlayer() + || MWBase::Environment::get().getSoundManager()->sayActive(actor)) return; - if (targetActor.getClass().getCreatureStats(targetActor).isDead()) - return; - - static const float fMaxHeadTrackDistance = MWBase::Environment::get().getWorld()->getStore().get() - .find("fMaxHeadTrackDistance")->mValue.getFloat(); - static const float fInteriorHeadTrackMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fInteriorHeadTrackMult")->mValue.getFloat(); - float maxDistance = fMaxHeadTrackDistance; - const ESM::Cell* currentCell = actor.getCell()->getCell(); - if (!currentCell->isExterior() && !(currentCell->mData.mFlags & ESM::Cell::QuasiEx)) - maxDistance *= fInteriorHeadTrackMult; - - const osg::Vec3f actor1Pos(actor.getRefData().getPosition().asVec3()); - const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3()); - float sqrDist = (actor1Pos - actor2Pos).length2(); - - if (sqrDist > std::min(maxDistance * maxDistance, sqrHeadTrackDistance) && !inCombatOrPursue) - return; - - // stop tracking when target is behind the actor - osg::Vec3f actorDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); - osg::Vec3f targetDirection(actor2Pos - actor1Pos); - actorDirection.z() = 0; - targetDirection.z() = 0; - if ((actorDirection * targetDirection > 0 || inCombatOrPursue) - && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor)) - { - sqrHeadTrackDistance = sqrDist; - headTrackTarget = targetActor; - } - } - - void Actors::playIdleDialogue(const MWWorld::Ptr& actor) - { - if (!actor.getClass().isActor() || actor == getPlayer() || MWBase::Environment::get().getSoundManager()->sayActive(actor)) - return; - - const CreatureStats &stats = actor.getClass().getCreatureStats(actor); - if (stats.getAiSetting(CreatureStats::AI_Hello).getModified() == 0) + const CreatureStats& stats = actor.getClass().getCreatureStats(actor); + if (stats.getAiSetting(AiSetting::Hello).getModified() == 0) return; const MWMechanics::AiSequence& seq = stats.getAiSequence(); @@ -529,36 +543,39 @@ namespace MWMechanics const osg::Vec3f playerPos(getPlayer().getRefData().getPosition().asVec3()); const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); - MWBase::World* world = MWBase::Environment::get().getWorld(); + MWBase::World* const world = MWBase::Environment::get().getWorld(); if (world->isSwimming(actor) || (playerPos - actorPos).length2() >= 3000 * 3000) return; // Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated. // We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST. const float delta = MWBase::Environment::get().getFrameDuration() * 6.f; - static const float fVoiceIdleOdds = world->getStore().get().find("fVoiceIdleOdds")->mValue.getFloat(); - if (Misc::Rng::rollProbability() * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor)) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + static const float fVoiceIdleOdds + = world->getStore().get().find("fVoiceIdleOdds")->mValue.getFloat(); + if (Misc::Rng::rollProbability(world->getPrng()) * 10000.f < fVoiceIdleOdds * delta + && world->getLOS(getPlayer(), actor)) + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("idle")); } - void Actors::updateMovementSpeed(const MWWorld::Ptr& actor) + void Actors::updateMovementSpeed(const MWWorld::Ptr& actor) const { - if (mSmoothMovement) + if (Settings::game().mSmoothMovement) return; - CreatureStats &stats = actor.getClass().getCreatureStats(actor); - MWMechanics::AiSequence& seq = stats.getAiSequence(); + const auto& actorClass = actor.getClass(); + const CreatureStats& stats = actorClass.getCreatureStats(actor); + const MWMechanics::AiSequence& seq = stats.getAiSequence(); if (!seq.isEmpty() && seq.getActivePackage().useVariableSpeed()) { - osg::Vec3f targetPos = seq.getActivePackage().getDestination(); - osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); - float distance = (targetPos - actorPos).length(); + const osg::Vec3f targetPos = seq.getActivePackage().getDestination(); + const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); + const float distance = (targetPos - actorPos).length(); if (distance < DECELERATE_DISTANCE) { - float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE); - auto& movement = actor.getClass().getMovementSettings(actor); + const float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE); + auto& movement = actorClass.getMovementSettings(actor); movement.mPosition[0] *= speedCoef; movement.mPosition[1] *= speedCoef; } @@ -567,16 +584,17 @@ namespace MWMechanics void Actors::updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly) { - if (!actor.getClass().isActor() || actor == getPlayer()) + const auto& actorClass = actor.getClass(); + if (!actorClass.isActor() || actor == getPlayer()) return; - CreatureStats &stats = actor.getClass().getCreatureStats(actor); - const MWMechanics::AiSequence& seq = stats.getAiSequence(); + const CreatureStats& actorStats = actorClass.getCreatureStats(actor); + const MWMechanics::AiSequence& seq = actorStats.getAiSequence(); const auto packageId = seq.getTypeId(); - if (seq.isInCombat() || - MWBase::Environment::get().getWorld()->isSwimming(actor) || - (packageId != AiPackageTypeId::Wander && packageId != AiPackageTypeId::Travel && packageId != AiPackageTypeId::None)) + if (seq.isInCombat() || MWBase::Environment::get().getWorld()->isSwimming(actor) + || (packageId != AiPackageTypeId::Wander && packageId != AiPackageTypeId::Travel + && packageId != AiPackageTypeId::None)) { actorState.setTurningToPlayer(false); actorState.setGreetingTimer(0); @@ -584,10 +602,10 @@ namespace MWMechanics return; } - MWWorld::Ptr player = getPlayer(); - osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); - osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); - osg::Vec3f dir = playerPos - actorPos; + const MWWorld::Ptr player = getPlayer(); + const osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + const osg::Vec3f dir = playerPos - actorPos; if (actorState.isTurningToPlayer()) { @@ -598,7 +616,7 @@ namespace MWMechanics { actorState.setTurningToPlayer(false); // An original engine launches an endless idle2 when an actor greets player. - playAnimationGroup (actor, "idle2", 0, std::numeric_limits::max(), false); + playAnimationGroup(actor, "idle2", 0, std::numeric_limits::max(), false); } } @@ -606,17 +624,22 @@ namespace MWMechanics return; // Play a random voice greeting if the player gets too close - static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() - .get().find("iGreetDistanceMultiplier")->mValue.getInteger(); + static const int iGreetDistanceMultiplier = MWBase::Environment::get() + .getESMStore() + ->get() + .find("iGreetDistanceMultiplier") + ->mValue.getInteger(); - float helloDistance = static_cast(stats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier); + const float helloDistance + = static_cast(actorStats.getAiSetting(AiSetting::Hello).getModified() * iGreetDistanceMultiplier); + const auto& playerStats = player.getClass().getCreatureStats(player); int greetingTimer = actorState.getGreetingTimer(); GreetingState greetingState = actorState.getGreetingState(); if (greetingState == Greet_None) { - if ((playerPos - actorPos).length2() <= helloDistance*helloDistance && - !player.getClass().getCreatureStats(player).isDead() && !actor.getClass().getCreatureStats(actor).isParalyzed() + if ((playerPos - actorPos).length2() <= helloDistance * helloDistance && !playerStats.isDead() + && !actorStats.isParalyzed() && !isTargetMagicallyHidden(player) && MWBase::Environment::get().getWorld()->getLOS(player, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) greetingTimer++; @@ -624,7 +647,7 @@ namespace MWMechanics if (greetingTimer >= GREETING_SHOULD_START) { greetingState = Greet_InProgress; - MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("hello")); greetingTimer = 0; } } @@ -633,8 +656,10 @@ namespace MWMechanics { greetingTimer++; - if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak) - && (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor))) + if (!actorStats.getMovementFlag(CreatureStats::Flag_ForceJump) + && !actorStats.getMovementFlag(CreatureStats::Flag_ForceSneak) + && (greetingTimer <= GREETING_SHOULD_END + || MWBase::Environment::get().getSoundManager()->sayActive(actor))) turnActorToFacePlayer(actor, actorState, dir); if (greetingTimer >= GREETING_COOLDOWN) @@ -647,7 +672,7 @@ namespace MWMechanics if (greetingState == Greet_Done) { float resetDist = 2 * helloDistance; - if ((playerPos - actorPos).length2() >= resetDist*resetDist) + if ((playerPos - actorPos).length2() >= resetDist * resetDist) greetingState = Greet_None; } @@ -655,10 +680,11 @@ namespace MWMechanics actorState.setGreetingState(greetingState); } - void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir) + void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir) const { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - actor.getClass().getMovementSettings(actor).mPosition[0] = 0; + auto& movementSettings = actor.getClass().getMovementSettings(actor); + movementSettings.mPosition[1] = 0; + movementSettings.mPosition[0] = 0; if (!actorState.isTurningToPlayer()) { @@ -667,12 +693,29 @@ namespace MWMechanics float angle = std::atan2(from, to); actorState.setAngleToPlayer(angle); float deltaAngle = Misc::normalizeAngle(angle - actor.getRefData().getPosition().rot[2]); - if (!mSmoothMovement || std::abs(deltaAngle) > osg::DegreesToRadians(60.f)) + if (!Settings::game().mSmoothMovement || std::abs(deltaAngle) > osg::DegreesToRadians(60.f)) actorState.setTurningToPlayer(true); } } - void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer) + void Actors::stopCombat(const MWWorld::Ptr& ptr) const + { + auto& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + std::vector targets; + if (ai.getCombatTargets(targets)) + { + std::set allySet; + getActorsSidingWith(ptr, allySet); + std::vector allies(allySet.begin(), allySet.end()); + for (const auto& ally : allies) + ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(targets); + for (const auto& target : targets) + target.getClass().getCreatureStats(target).getAiSequence().stopCombat(allies); + } + } + + void Actors::engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, + std::map>& cachedAllies, bool againstPlayer) const { // No combat for totally static creatures if (!actor1.getClass().isMobile(actor1)) @@ -688,42 +731,48 @@ namespace MWMechanics const osg::Vec3f actor1Pos(actor1.getRefData().getPosition().asVec3()); const osg::Vec3f actor2Pos(actor2.getRefData().getPosition().asVec3()); - float sqrDist = (actor1Pos - actor2Pos).length2(); + const float sqrDist = (actor1Pos - actor2Pos).length2(); + const int actorsProcessingRange = Settings::game().mActorsProcessingRange; - if (sqrDist > mActorsProcessingRange*mActorsProcessingRange) + if (sqrDist > actorsProcessingRange * actorsProcessingRange) return; - // If this is set to true, actor1 will start combat with actor2 if the awareness check at the end of the method returns true + // If this is set to true, actor1 will start combat with actor2 if the awareness check at the end of the method + // returns true bool aggressive = false; - // Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting those actors, (recursive) - // and any actor currently being followed or escorted by actor1 + // Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting + // those actors, (recursive) and any actor currently being followed or escorted by actor1 std::set allies1; getActorsSidingWith(actor1, allies1, cachedAllies); - // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and actor2 - for (const MWWorld::Ptr &ally : allies1) + const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); + // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and + // actor2 + for (const MWWorld::Ptr& ally : allies1) { if (creatureStats1.getAiSequence().isInCombat(ally)) continue; if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); return; } - // If there's been no attack attempt yet but an ally of actor1 is in combat with actor2, become aggressive to actor2 + // If there's been no attack attempt yet but an ally of actor1 is in combat with actor2, become aggressive + // to actor2 if (ally.getClass().getCreatureStats(ally).getAiSequence().isInCombat(actor2)) aggressive = true; } std::set playerAllies; - getActorsSidingWith(MWMechanics::getPlayer(), playerAllies, cachedAllies); + MWWorld::Ptr player = MWMechanics::getPlayer(); + getActorsSidingWith(player, playerAllies, cachedAllies); bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end(); @@ -732,42 +781,46 @@ namespace MWMechanics if (!aggressive && !isPlayerFollowerOrEscorter) { // Check that actor2 is in combat with actor1 - if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1)) + if (creatureStats2.getAiSequence().isInCombat(actor1)) { std::set allies2; getActorsSidingWith(actor2, allies2, cachedAllies); // Check that an ally of actor2 is also in combat with actor1 - for (const MWWorld::Ptr &ally2 : allies2) + for (const MWWorld::Ptr& ally2 : allies2) { - if (ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1)) + if (ally2 != actor2 && ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1)) { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2); // Also have actor1's allies start combat for (const MWWorld::Ptr& ally1 : allies1) - MWBase::Environment::get().getMechanicsManager()->startCombat(ally1, actor2); + if (ally1 != player) + mechanicsManager->startCombat(ally1, actor2); return; } } } } + if (creatureStats2.getMagicEffects().getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude() > 0) + return; + // Stop here if target is unreachable if (!canFight(actor1, actor2)) return; - // If set in the settings file, player followers and escorters will become aggressive toward enemies in combat with them or the player - static const bool followersAttackOnSight = Settings::Manager::getBool("followers attack on sight", "Game"); - if (!aggressive && isPlayerFollowerOrEscorter && followersAttackOnSight) + // If set in the settings file, player followers and escorters will become aggressive toward enemies in combat + // with them or the player + if (!aggressive && isPlayerFollowerOrEscorter && Settings::game().mFollowersAttackOnSight) { - if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1)) + if (creatureStats2.getAiSequence().isInCombat(actor1)) aggressive = true; else { - for (const MWWorld::Ptr &ally : allies1) + for (const MWWorld::Ptr& ally : allies1) { - if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(ally)) + if (ally != actor1 && creatureStats2.getAiSequence().isInCombat(ally)) { aggressive = true; break; @@ -784,7 +837,7 @@ namespace MWMechanics // Player followers and escorters with high fight should not initiate combat with the player or with // other player followers or escorters if (!isPlayerFollowerOrEscorter) - aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); + aggressive = mechanicsManager->isAggressive(actor1, actor2); } /* Start of tes3mp addition @@ -801,10 +854,13 @@ namespace MWMechanics } // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter - if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() && creatureStats2.getAiSequence().isInCombat()) + const auto world = MWBase::Environment::get().getWorld(); + if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() + && creatureStats2.getAiSequence().isInCombat()) { // Check if the creature is too far - static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->mValue.getFloat(); + static const float fAlarmRadius + = world->getStore().get().find("fAlarmRadius")->mValue.getFloat(); if (sqrDist > fAlarmRadius * fAlarmRadius) return; @@ -824,97 +880,117 @@ namespace MWMechanics aggressive = true; } - // If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, start combat with actor2. + // If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, + // start combat with actor2. if (aggressive) { - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); + bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2); } } - void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) + void Actors::adjustMagicEffects(const MWWorld::Ptr& creature, float duration) const { - CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); - if (creatureStats.isDeathAnimationFinished()) + CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); + const bool wasDead = creatureStats.isDead(); + + creatureStats.getActiveSpells().update(creature, duration); + + if (!wasDead && creatureStats.isDead()) + { + // The actor was killed by a magic effect. Figure out if the player was responsible for it. + const ActiveSpells& spells = creatureStats.getActiveSpells(); + const MWWorld::Ptr player = getPlayer(); + std::set playerFollowers; + getActorsSidingWith(player, playerFollowers); + + for (const ActiveSpells::ActiveSpellParams& spell : spells) + { + bool actorKilled = false; + + MWWorld::Ptr caster + = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.getCasterActorId()); + if (caster.isEmpty()) + continue; + for (const auto& effect : spell.getEffects()) + { + static const std::array damageEffects{ + ESM::MagicEffect::FireDamage, + ESM::MagicEffect::ShockDamage, + ESM::MagicEffect::FrostDamage, + ESM::MagicEffect::Poison, + ESM::MagicEffect::SunDamage, + ESM::MagicEffect::DamageHealth, + ESM::MagicEffect::AbsorbHealth, + }; + + const bool isDamageEffect = std::find(damageEffects.begin(), damageEffects.end(), effect.mEffectId) + != damageEffects.end(); + + if (isDamageEffect) + { + if (caster.getClass().isNpc() && caster.getClass().getNpcStats(caster).isWerewolf()) + caster.getClass().getNpcStats(caster).addWerewolfKill(); + if (caster == player || playerFollowers.find(caster) != playerFollowers.end()) + { + MWBase::Environment::get().getMechanicsManager()->actorKilled(creature, player); + actorKilled = true; + break; + } + } + } + + if (actorKilled) + break; + } + } + + // updateSummons assumes the actor belongs to a cell. + // This assumption isn't always valid for the player character. + if (!creature.isInCell()) return; - MagicEffects now = creatureStats.getSpells().getMagicEffects(); - - if (creature.getClass().hasInventoryStore(creature)) - { - MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature); - now += store.getMagicEffects(); - } - - now += creatureStats.getActiveSpells().getMagicEffects(); - - creatureStats.modifyMagicEffects(now); + if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty()) + updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f); } - void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) + void Actors::restoreDynamicStats(const MWWorld::Ptr& ptr, double hours, bool sleep) const { - CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - - float intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); - - float base = 1.f; - if (ptr == getPlayer()) - base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); - else - base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); - - double magickaFactor = base + - creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; - - DynamicStat magicka = creatureStats.getMagicka(); - float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); - float currentToBaseRatio = (magicka.getCurrent() / magicka.getBase()); - magicka.setModified(magicka.getModified() + diff, 0); - magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); - creatureStats.setMagicka(magicka); - } - - void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep) - { - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (stats.isDead()) return; - const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& settings + = MWBase::Environment::get().getESMStore()->get(); if (sleep) { - float health, magicka; - getRestorationPerHourOfSleep(ptr, health, magicka); + const auto [health, magicka] = getRestorationPerHourOfSleep(ptr); DynamicStat stat = stats.getHealth(); stat.setCurrent(stat.getCurrent() + health * hours); stats.setHealth(stat); double restoreHours = hours; - bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; + const bool stunted + = stats.getMagicEffects().getOrDefault(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; if (stunted) { // Stunted Magicka effect should be taken into account. - GetStuntedMagickaDuration visitor(ptr); - stats.getActiveSpells().visitEffectSources(visitor); - stats.getSpells().visitEffectSources(visitor); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).visitEffectSources(visitor); + float remainingTime = getStuntedMagickaDuration(ptr); // Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours. - if (visitor.mRemainingTime > 0) + if (remainingTime > 0) { double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - if(timeScale == 0.0) + if (timeScale == 0.0) timeScale = 1; - restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f); + restoreHours = std::max(0.0, hours - remainingTime * timeScale / 3600.f); } - else if (visitor.mRemainingTime == -1) + else if (remainingTime == -1) restoreHours = 0; } @@ -933,29 +1009,29 @@ namespace MWMechanics return; // Restore fatigue - float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat (); - float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); - float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat (); + static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat(); + static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat(); + static const float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat(); - float endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); + const float endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified(); float normalizedEncumbrance = ptr.getClass().getNormalizedEncumbrance(ptr); if (normalizedEncumbrance > 1) normalizedEncumbrance = 1; - float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance); - x *= fEndFatigueMult * endurance; + const float x + = (fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance)) * (fEndFatigueMult * endurance); - fatigue.setCurrent (fatigue.getCurrent() + 3600 * x * hours); - stats.setFatigue (fatigue); + fatigue.setCurrent(fatigue.getCurrent() + 3600 * x * hours); + stats.setFatigue(fatigue); } - void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration) + void Actors::calculateRestoration(const MWWorld::Ptr& ptr, float duration) const { if (ptr.getClass().getCreatureStats(ptr).isDead()) return; - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); // Current fatigue can be above base value due to a fortify effect. // In that case stop here and don't try to restore. @@ -964,19 +1040,21 @@ namespace MWMechanics return; // Restore fatigue - float endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified(); - const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat (); - static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); + const float endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified(); + const MWWorld::Store& settings + = MWBase::Environment::get().getESMStore()->get(); + static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat(); + static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat(); - float x = fFatigueReturnBase + fFatigueReturnMult * endurance; + const float x = fFatigueReturnBase + fFatigueReturnMult * endurance; - fatigue.setCurrent (fatigue.getCurrent() + duration * x); - stats.setFatigue (fatigue); + fatigue.setCurrent(fatigue.getCurrent() + duration * x); + stats.setFatigue(fatigue); } - class ExpiryVisitor : public EffectSourceVisitor + bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) const { +<<<<<<< HEAD private: MWWorld::Ptr mActor; float mDuration; @@ -1398,80 +1476,86 @@ namespace MWMechanics { PtrActorMap::iterator it = mActors.find(ptr); if (it == mActors.end()) +======= + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 return false; - CharacterController* ctrl = it->second->getCharacterController(); - - return ctrl->isAttackPreparing(); + return it->second->getCharacterController().isAttackPreparing(); } - bool Actors::isRunning(const MWWorld::Ptr& ptr) + bool Actors::isRunning(const MWWorld::Ptr& ptr) const { - PtrActorMap::iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return false; - CharacterController* ctrl = it->second->getCharacterController(); - - return ctrl->isRunning(); + return it->second->getCharacterController().isRunning(); } - bool Actors::isSneaking(const MWWorld::Ptr& ptr) + bool Actors::isSneaking(const MWWorld::Ptr& ptr) const { - PtrActorMap::iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return false; - CharacterController* ctrl = it->second->getCharacterController(); - - return ctrl->isSneaking(); + return it->second->getCharacterController().isSneaking(); } - void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer) + static void updateDrowning(const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer) { - NpcStats &stats = ptr.getClass().getNpcStats(ptr); + const auto& actorClass = ptr.getClass(); + NpcStats& stats = actorClass.getNpcStats(ptr); // When npc stats are just initialized, mTimeToStartDrowning == -1 and we should get value from GMST - static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->mValue.getFloat(); + static const float fHoldBreathTime = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fHoldBreathTime") + ->mValue.getFloat(); if (stats.getTimeToStartDrowning() == -1.f) stats.setTimeToStartDrowning(fHoldBreathTime); if (!isPlayer && stats.getTimeToStartDrowning() < fHoldBreathTime / 2) { - AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - if (seq.getTypeId() != AiPackageTypeId::Breathe) //Only add it once + AiSequence& seq = actorClass.getCreatureStats(ptr).getAiSequence(); + if (seq.getTypeId() != AiPackageTypeId::Breathe) // Only add it once seq.stack(AiBreathe(), ptr); } - MWBase::World *world = MWBase::Environment::get().getWorld(); - bool knockedOutUnderwater = (isKnockedOut && world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))); - if((world->isSubmerged(ptr) || knockedOutUnderwater) - && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) + const MWBase::World* const world = MWBase::Environment::get().getWorld(); + const bool knockedOutUnderwater + = (isKnockedOut && world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))); + if ((world->isSubmerged(ptr) || knockedOutUnderwater) + && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) { float timeLeft = 0.0f; - if(knockedOutUnderwater) + if (knockedOutUnderwater) stats.setTimeToStartDrowning(0); else { timeLeft = stats.getTimeToStartDrowning() - duration; - if(timeLeft < 0.0f) + if (timeLeft < 0.0f) timeLeft = 0.0f; stats.setTimeToStartDrowning(timeLeft); } - bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); + const bool godmode = isPlayer && world->getGodModeState(); - if(timeLeft == 0.0f && !godmode) + if (timeLeft == 0.0f && !godmode) { // If drowning, apply 3 points of damage per second - static const float fSuffocationDamage = world->getStore().get().find("fSuffocationDamage")->mValue.getFloat(); + static const float fSuffocationDamage + = world->getStore().get().find("fSuffocationDamage")->mValue.getFloat(); DynamicStat health = stats.getHealth(); - health.setCurrent(health.getCurrent() - fSuffocationDamage*duration); + health.setCurrent(health.getCurrent() - fSuffocationDamage * duration); stats.setHealth(health); // Play a drowning sound - MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager(); - if(!sndmgr->getSoundPlaying(ptr, "drown")) - sndmgr->playSound3D(ptr, "drown", 1.0f, 1.0f); + MWBase::SoundManager* sndmgr = MWBase::Environment::get().getSoundManager(); + auto soundDrown = ESM::RefId::stringRefId("drown"); + if (!sndmgr->getSoundPlaying(ptr, soundDrown)) + sndmgr->playSound3D(ptr, soundDrown, 1.0f, 1.0f); - if(isPlayer) + if (isPlayer) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); } } @@ -1479,13 +1563,15 @@ namespace MWMechanics stats.setTimeToStartDrowning(fHoldBreathTime); } - void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip) + static void updateEquippedLight(const MWWorld::Ptr& ptr, float duration, bool mayEquip) { - bool isPlayer = (ptr == getPlayer()); + const bool isPlayer = (ptr == getPlayer()); + + const auto& actorClass = ptr.getClass(); + auto& inventoryStore = actorClass.getInventoryStore(ptr); + + auto heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - MWWorld::InventoryStore &inventoryStore = ptr.getClass().getInventoryStore(ptr); - MWWorld::ContainerStoreIterator heldIter = - inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); /** * Automatically equip NPCs torches at night and unequip them at day */ @@ -1499,6 +1585,7 @@ namespace MWMechanics */ if (!isPlayer && !mwmp::PlayerList::isDedicatedPlayer(ptr) && !mwmp::Main::get().getCellController()->isDedicatedActor(ptr)) { +<<<<<<< HEAD /* End of tes3mp change (major) */ @@ -1513,22 +1600,27 @@ namespace MWMechanics } } +======= + auto torchIter = std::find_if(std::begin(inventoryStore), std::end(inventoryStore), [&](auto entry) { + return entry.getType() == ESM::Light::sRecordId && entry.getClass().canBeEquipped(entry, ptr).first; + }); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (mayEquip) { - if (torch != inventoryStore.end()) + if (torchIter != inventoryStore.end()) { - if (!ptr.getClass().getCreatureStats (ptr).getAiSequence().isInCombat()) + if (!actorClass.getCreatureStats(ptr).getAiSequence().isInCombat()) { // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. - if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) - inventoryStore.unequipItem(*heldIter, ptr); + if (heldIter != inventoryStore.end() && heldIter->getType() != ESM::Light::sRecordId) + inventoryStore.unequipItem(*heldIter); } - else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name()) + else if (heldIter == inventoryStore.end() || heldIter->getType() == ESM::Light::sRecordId) { // For hostile NPCs, see if they have anything better to equip first - auto shield = inventoryStore.getPreferredShield(ptr); - if(shield != inventoryStore.end()) - inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr); + auto shield = inventoryStore.getPreferredShield(); + if (shield != inventoryStore.end()) + inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield); } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); @@ -1536,87 +1628,101 @@ namespace MWMechanics // If we have a torch and can equip it, then equip it now. if (heldIter == inventoryStore.end()) { - inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr); + inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torchIter); } } } else { - if (heldIter != inventoryStore.end() && heldIter->getTypeName() == typeid(ESM::Light).name()) + if (heldIter != inventoryStore.end() && heldIter->getType() == ESM::Light::sRecordId) { // At day, unequip lights and auto equip shields or other suitable items // (Note: autoEquip will ignore lights) - inventoryStore.autoEquip(ptr); + inventoryStore.autoEquip(); } } } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - //If holding a light... - if(heldIter.getType() == MWWorld::ContainerStore::Type_Light) + // If holding a light... + const auto world = MWBase::Environment::get().getWorld(); + MWRender::Animation* anim = world->getAnimation(ptr); + if (heldIter.getType() == MWWorld::ContainerStore::Type_Light && anim && anim->getCarriedLeftShown()) { // Use time from the player's light - if(isPlayer) + if (isPlayer) { - // But avoid using it up if the light source is hidden - MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); - if (anim && anim->getCarriedLeftShown()) + float timeRemaining = heldIter->getClass().getRemainingUsageTime(*heldIter); + + // -1 is infinite light source. Other negative values are treated as 0. + if (timeRemaining != -1.0f) { - float timeRemaining = heldIter->getClass().getRemainingUsageTime(*heldIter); - - // -1 is infinite light source. Other negative values are treated as 0. - if (timeRemaining != -1.0f) + timeRemaining -= duration; + if (timeRemaining <= 0.f) { - timeRemaining -= duration; - if (timeRemaining <= 0.f) - { - inventoryStore.remove(*heldIter, 1, ptr); // remove it - return; - } - - heldIter->getClass().setRemainingUsageTime(*heldIter, timeRemaining); + inventoryStore.remove(*heldIter, 1); // remove it + return; } + + heldIter->getClass().setRemainingUsageTime(*heldIter, timeRemaining); } } // Both NPC and player lights extinguish in water. - if(MWBase::Environment::get().getWorld()->isSwimming(ptr)) + if (world->isSwimming(ptr)) { - inventoryStore.remove(*heldIter, 1, ptr); // remove it + inventoryStore.remove(*heldIter, 1); // remove it // ...But, only the player makes a sound. - if(isPlayer) - MWBase::Environment::get().getSoundManager()->playSound("torch out", - 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); + if (isPlayer) + MWBase::Environment::get().getSoundManager()->playSound( + ESM::RefId::stringRefId("torch out"), 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } } } - void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) + void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) const { - MWWorld::Ptr player = getPlayer(); - if (ptr != player && ptr.getClass().isNpc()) + const MWWorld::Ptr player = getPlayer(); + if (ptr == player) + return; + + const auto& actorClass = ptr.getClass(); + if (!actorClass.isNpc()) + return; + + // get stats of witness + CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + + const auto& playerClass = player.getClass(); + const auto& playerStats = playerClass.getNpcStats(player); + if (playerStats.isWerewolf()) + return; + + const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); + const auto world = MWBase::Environment::get().getWorld(); + + if (actorClass.isClass(ptr, "Guard") && !creatureStats.getAiSequence().isInPursuit() + && !creatureStats.getAiSequence().isInCombat() + && creatureStats.getMagicEffects().getOrDefault(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) { - // get stats of witness - CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); - NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); - - if (player.getClass().getNpcStats(player).isWerewolf()) - return; - - if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackageTypeId::Pursue && !creatureStats.getAiSequence().isInCombat() - && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) + const MWWorld::ESMStore& esmStore = world->getStore(); + static const int cutoff = esmStore.get().find("iCrimeThreshold")->mValue.getInteger(); + // Force dialogue on sight if bounty is greater than the cutoff + // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or + // attack (>= 5000 bounty) + if (playerStats.getBounty() >= cutoff + // TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s + // or so? + && world->getLOS(ptr, player) && mechanicsManager->awarenessCheck(player, ptr)) { - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - static const int cutoff = esmStore.get().find("iCrimeThreshold")->mValue.getInteger(); - // Force dialogue on sight if bounty is greater than the cutoff - // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty) - if ( player.getClass().getNpcStats(player).getBounty() >= cutoff - // TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s or so? - && MWBase::Environment::get().getWorld()->getLOS(ptr, player) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) + static const int iCrimeThresholdMultiplier + = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); + if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { +<<<<<<< HEAD static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); /* @@ -1638,25 +1744,37 @@ namespace MWMechanics creatureStats.getAiSequence().stack(AiPursue(player), ptr); creatureStats.setAlarmed(true); npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId()); +======= + mechanicsManager->startCombat(ptr, player); + creatureStats.setHitAttemptActorId( + playerClass.getCreatureStats(player) + .getActorId()); // Stops the guard from quitting combat if player is unreachable +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else + creatureStats.getAiSequence().stack(AiPursue(player), ptr); + creatureStats.setAlarmed(true); + npcStats.setCrimeId(world->getPlayer().getNewCrimeId()); } + } - // if I was a witness to a crime - if (npcStats.getCrimeId() != -1) + // if I was a witness to a crime + if (npcStats.getCrimeId() != -1) + { + // if you've paid for your crimes and I haven't noticed + if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId()) { - // if you've paid for your crimes and I havent noticed - if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ) - { - // Calm witness down - if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stopPursuit(); - creatureStats.getAiSequence().stopCombat(); + // Calm witness down + if (ptr.getClass().isClass(ptr, "Guard")) + creatureStats.getAiSequence().stopPursuit(); + stopCombat(ptr); - // Reset factors to attack - creatureStats.setAttacked(false); - creatureStats.setAlarmed(false); - creatureStats.setAiSetting(CreatureStats::AI_Fight, ptr.getClass().getBaseFightRating(ptr)); + // Reset factors to attack + creatureStats.setAttacked(false); + creatureStats.setAlarmed(false); + creatureStats.setAiSetting(AiSetting::Fight, ptr.getClass().getBaseFightRating(ptr)); +<<<<<<< HEAD // Update witness crime id npcStats.setCrimeId(-1); } @@ -1684,14 +1802,19 @@ namespace MWMechanics /* End of tes3mp addition */ +======= + // Update witness crime id + npcStats.setCrimeId(-1); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } } - Actors::Actors() : mSmoothMovement(Settings::Manager::getBool("smooth movement", "Game")) + void Actors::addActor(const MWWorld::Ptr& ptr, bool updateImmediately) { - mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning + removeActor(ptr, true); +<<<<<<< HEAD updateProcessingRange(); } @@ -1733,13 +1856,16 @@ namespace MWMechanics removeActor(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); +======= + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (!anim) return; - mActors.insert(std::make_pair(ptr, new Actor(ptr, anim))); + const auto it = mActors.emplace(mActors.end(), ptr, anim); + mIndex.emplace(ptr.mRef, it); - CharacterController* ctrl = mActors[ptr]->getCharacterController(); if (updateImmediately) - ctrl->update(0); + it->getCharacterController().update(0); // We should initially hide actors outside of processing range. // Note: since we update player after other actors, distance will be incorrect during teleportation. @@ -1747,17 +1873,19 @@ namespace MWMechanics if (MWBase::Environment::get().getWorld()->getPlayer().wasTeleported()) return; - updateVisibility(ptr, ctrl); + updateVisibility(ptr, it->getCharacterController()); } - void Actors::updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl) + void Actors::updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const { MWWorld::Ptr player = MWMechanics::getPlayer(); if (ptr == player) return; - const float dist = (player.getRefData().getPosition().asVec3() - ptr.getRefData().getPosition().asVec3()).length(); - if (dist > mActorsProcessingRange) + const float dist + = (player.getRefData().getPosition().asVec3() - ptr.getRefData().getPosition().asVec3()).length(); + const int actorsProcessingRange = Settings::game().mActorsProcessingRange; + if (dist > actorsProcessingRange) { ptr.getRefData().getBaseNode()->setNodeMask(0); return; @@ -1767,35 +1895,37 @@ namespace MWMechanics // Fade away actors on large distance (>90% of actor's processing distance) float visibilityRatio = 1.0; - float fadeStartDistance = mActorsProcessingRange*0.9f; - float fadeEndDistance = mActorsProcessingRange; - float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance); + const float fadeStartDistance = actorsProcessingRange * 0.9f; + const float fadeEndDistance = actorsProcessingRange; + const float fadeRatio = (dist - fadeStartDistance) / (fadeEndDistance - fadeStartDistance); if (fadeRatio > 0) visibilityRatio -= std::max(0.f, fadeRatio); visibilityRatio = std::min(1.f, visibilityRatio); - ctrl->setVisibility(visibilityRatio); + ctrl.setVisibility(visibilityRatio); } - void Actors::removeActor (const MWWorld::Ptr& ptr) + void Actors::removeActor(const MWWorld::Ptr& ptr, bool keepActive) { - PtrActorMap::iterator iter = mActors.find(ptr); - if(iter != mActors.end()) + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) { - delete iter->second; - mActors.erase(iter); + if (!keepActive) + removeTemporaryEffects(iter->second->getPtr()); + mActors.erase(iter->second); + mIndex.erase(iter); } } - void Actors::castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) + void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) const { - PtrActorMap::iterator iter = mActors.find(ptr); - if(iter != mActors.end()) - iter->second->getCharacterController()->castSpell(spellId, manualSpell); + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->getCharacterController().castSpell(spellId, manualSpell); } - bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) + bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) const { if (!actor.getClass().isActor()) return false; @@ -1803,22 +1933,21 @@ namespace MWMechanics // If an observer is NPC, check if he detected an actor if (!observer.isEmpty() && observer.getClass().isNpc()) { - return - MWBase::Environment::get().getWorld()->getLOS(observer, actor) && - MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer); + return MWBase::Environment::get().getWorld()->getLOS(observer, actor) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer); } // Otherwise check if any actor in AI processing range sees the target actor std::vector neighbors; - osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, mActorsProcessingRange, neighbors); - for (const MWWorld::Ptr &neighbor : neighbors) + osg::Vec3f position(actor.getRefData().getPosition().asVec3()); + getObjectsInRange(position, Settings::game().mActorsProcessingRange, neighbors); + for (const MWWorld::Ptr& neighbor : neighbors) { if (neighbor == actor) continue; - bool result = MWBase::Environment::get().getWorld()->getLOS(neighbor, actor) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, neighbor); + const bool result = MWBase::Environment::get().getWorld()->getLOS(neighbor, actor) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, neighbor); if (result) return true; @@ -1827,51 +1956,49 @@ namespace MWMechanics return false; } - void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) + void Actors::updateActor(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) const { - PtrActorMap::iterator iter = mActors.find(old); - if(iter != mActors.end()) - { - Actor *actor = iter->second; - mActors.erase(iter); - - actor->updatePtr(ptr); - mActors.insert(std::make_pair(ptr, actor)); - } + const auto iter = mIndex.find(old.mRef); + if (iter != mIndex.end()) + iter->second->updatePtr(ptr); } - void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore) + void Actors::dropActors(const MWWorld::CellStore* cellStore, const MWWorld::Ptr& ignore) { - PtrActorMap::iterator iter = mActors.begin(); - while(iter != mActors.end()) + for (auto iter = mActors.begin(); iter != mActors.end();) { - if((iter->first.isInCell() && iter->first.getCell()==cellStore) && iter->first != ignore) + if ((iter->getPtr().isInCell() && iter->getPtr().getCell() == cellStore) && iter->getPtr() != ignore) { - delete iter->second; - mActors.erase(iter++); + removeTemporaryEffects(iter->getPtr()); + mIndex.erase(iter->getPtr().mRef); + iter = mActors.erase(iter); } else ++iter; } } - void Actors::updateCombatMusic () + void Actors::updateCombatMusic() { - MWWorld::Ptr player = getPlayer(); + const MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); bool hasHostiles = false; // need to know this to play Battle music - bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); + const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); if (aiActive) { - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + const int actorsProcessingRange = Settings::game().mActorsProcessingRange; + for (const Actor& actor : mActors) { - if (iter->first == player) continue; + if (actor.getPtr() == player) + continue; - bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange; + const bool inProcessingRange + = (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length2() + <= actorsProcessingRange * actorsProcessingRange; if (inProcessingRange) { - MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + MWMechanics::CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); if (!stats.isDead() && stats.getAiSequence().isInCombat()) { hasHostiles = true; @@ -1882,23 +2009,21 @@ namespace MWMechanics } // check if we still have any player enemies to switch music - static int currentMusic = 0; - - if (currentMusic != 1 && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() && - MWBase::Environment::get().getSoundManager()->isMusicPlaying())) + if (mCurrentMusic != MusicType::Explore && !hasHostiles + && !(player.getClass().getCreatureStats(player).isDead() + && MWBase::Environment::get().getSoundManager()->isMusicPlaying())) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - currentMusic = 1; + mCurrentMusic = MusicType::Explore; } - else if (currentMusic != 2 && hasHostiles) + else if (mCurrentMusic != MusicType::Battle && hasHostiles) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); - currentMusic = 2; + mCurrentMusic = MusicType::Battle; } - } - void Actors::predictAndAvoidCollisions(float duration) + void Actors::predictAndAvoidCollisions(float duration) const { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) return; @@ -1907,23 +2032,23 @@ namespace MWMechanics const float maxDistForPartialAvoiding = 200.f; const float maxDistForStrictAvoiding = 100.f; const float maxTimeToCheck = 2.0f; - static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game"); + const bool giveWayWhenIdle = Settings::game().mNPCsGiveWay; - MWWorld::Ptr player = getPlayer(); - MWBase::World* world = MWBase::Environment::get().getWorld(); - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + const MWWorld::Ptr player = getPlayer(); + const MWBase::World* const world = MWBase::Environment::get().getWorld(); + for (const Actor& actor : mActors) { - const MWWorld::Ptr& ptr = iter->first; + const MWWorld::Ptr& ptr = actor.getPtr(); if (ptr == player) continue; // Don't interfere with player controls. - float maxSpeed = ptr.getClass().getMaxSpeed(ptr); + const float maxSpeed = ptr.getClass().getMaxSpeed(ptr); if (maxSpeed == 0.0) continue; // Can't move, so there is no sense to predict collisions. Movement& movement = ptr.getClass().getMovementSettings(ptr); - osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]); - bool isMoving = origMovement.length2() > 0.01; + const osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]); + const bool isMoving = origMovement.length2() > 0.01; if (movement.mPosition[1] < 0) continue; // Actors can not see others when move backward. @@ -1935,51 +2060,70 @@ namespace MWMechanics bool shouldTurnToApproachingActor = !isMoving; MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets). const auto& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - for (const auto& package : aiSequence) + if (!aiSequence.isEmpty()) { - if (package->getTypeId() == AiPackageTypeId::Follow) + const auto& package = aiSequence.getActivePackage(); + if (package.getTypeId() == AiPackageTypeId::Follow) + { shouldAvoidCollision = true; +<<<<<<< HEAD else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) { if (!static_cast(package.get())->isStationary()) shouldGiveWay = true; +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue) + else if (package.getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) { - currentTarget = package->getTarget(); + if (!static_cast(package).isStationary()) + shouldGiveWay = true; + } + else if (package.getTypeId() == AiPackageTypeId::Combat + || package.getTypeId() == AiPackageTypeId::Pursue) + { + currentTarget = package.getTarget(); shouldAvoidCollision = isMoving; shouldTurnToApproachingActor = false; - break; } } +<<<<<<< HEAD +======= + +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (!shouldAvoidCollision && !shouldGiveWay) continue; - osg::Vec2f baseSpeed = origMovement * maxSpeed; - osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3(); - float baseRotZ = ptr.getRefData().getPosition().rot[2]; - osg::Vec3f halfExtents = world->getHalfExtents(ptr); - float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding; + const osg::Vec2f baseSpeed = origMovement * maxSpeed; + const osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3(); + const float baseRotZ = ptr.getRefData().getPosition().rot[2]; + const osg::Vec3f halfExtents = world->getHalfExtents(ptr); + const float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding; float timeToCheck = maxTimeToCheck; if (!shouldGiveWay && !aiSequence.isEmpty()) +<<<<<<< HEAD timeToCheck = std::min(timeToCheck, getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents)); +======= + timeToCheck = std::min( + timeToCheck, getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents)); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 float timeToCollision = timeToCheck; osg::Vec2f movementCorrection(0, 0); float angleToApproachingActor = 0; // Iterate through all other actors and predict collisions. - for(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter) + for (const Actor& otherActor : mActors) { - const MWWorld::Ptr& otherPtr = otherIter->first; + const MWWorld::Ptr& otherPtr = otherActor.getPtr(); if (otherPtr == ptr || otherPtr == currentTarget) continue; - osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); - osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos; - osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ); - float dist = deltaPos.length(); + const osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); + const osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos; + const osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ); + const float dist = deltaPos.length(); // Ignore actors which are not close enough or come from behind. if (dist > maxDistToCheck || relPos.y() < 0) @@ -1989,21 +2133,22 @@ namespace MWMechanics if (deltaPos.z() > halfExtents.z() * 2 || deltaPos.z() < -otherHalfExtents.z() * 2) continue; - osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3() * - otherPtr.getClass().getMaxSpeed(otherPtr); - float rotZ = otherPtr.getRefData().getPosition().rot[2]; - osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed; + const osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3() + * otherPtr.getClass().getMaxSpeed(otherPtr); + const float rotZ = otherPtr.getRefData().getPosition().rot[2]; + const osg::Vec2f relSpeed + = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed; - float collisionDist = minGap + world->getHalfExtents(ptr).x() + world->getHalfExtents(otherPtr).x(); + float collisionDist = minGap + halfExtents.x() + otherHalfExtents.x(); collisionDist = std::min(collisionDist, relPos.length()); // Find the earliest `t` when |relPos + relSpeed * t| == collisionDist. - float vr = relPos.x() * relSpeed.x() + relPos.y() * relSpeed.y(); - float v2 = relSpeed.length2(); - float Dh = vr * vr - v2 * (relPos.length2() - collisionDist * collisionDist); + const float vr = relPos.x() * relSpeed.x() + relPos.y() * relSpeed.y(); + const float v2 = relSpeed.length2(); + const float Dh = vr * vr - v2 * (relPos.length2() - collisionDist * collisionDist); if (Dh <= 0 || v2 == 0) continue; // No solution; distance is always >= collisionDist. - float t = (-vr - std::sqrt(Dh)) / v2; + const float t = (-vr - std::sqrt(Dh)) / v2; if (t < 0 || t > timeToCollision) continue; @@ -2016,9 +2161,12 @@ namespace MWMechanics timeToCollision = t; angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y()); - osg::Vec2f posAtT = relPos + relSpeed * t; - float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * collisionDist * maxSpeed); - coef *= osg::clampBetween((maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), 0.f, 1.f); + const osg::Vec2f posAtT = relPos + relSpeed * t; + const float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) + / (collisionDist * collisionDist * maxSpeed) + * std::clamp( + (maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), + 0.f, 1.f); movementCorrection = posAtT * coef; if (otherPtr.getClass().getCreatureStats(otherPtr).isDead()) // In case of dead body still try to go around (it looks natural), but reduce the correction twice. @@ -2029,7 +2177,8 @@ namespace MWMechanics { // Try to evade the nearest collision. osg::Vec2f newMovement = origMovement + movementCorrection; - // Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location. + // Step to the side rather than backward. Otherwise player will be able to push the NPC far away from + // it's original location. newMovement.y() = std::max(newMovement.y(), 0.f); newMovement.normalize(); if (isMoving) @@ -2042,33 +2191,38 @@ namespace MWMechanics } } - void Actors::update (float duration, bool paused) + void Actors::update(float duration, bool paused) { - if(!paused) + if (!paused) { - static float timerUpdateHeadTrack = 0; - static float timerUpdateEquippedLight = 0; - static float timerUpdateHello = 0; const float updateEquippedLightInterval = 1.0f; - if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; - if (timerUpdateHello >= 0.25f) timerUpdateHello = 0; - if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; - if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0; + if (mTimerUpdateHeadTrack >= 0.3f) + mTimerUpdateHeadTrack = 0; + + if (mTimerUpdateHello >= 0.25f) + mTimerUpdateHello = 0; + + if (mTimerDisposeSummonsCorpses >= 0.2f) + mTimerDisposeSummonsCorpses = 0; + + if (mTimerUpdateEquippedLight >= updateEquippedLightInterval) + mTimerUpdateEquippedLight = 0; // show torches only when there are darkness and no precipitations - MWBase::World* world = MWBase::Environment::get().getWorld(); - bool showTorches = world->useTorches(); + MWBase::World* const world = MWBase::Environment::get().getWorld(); + const bool showTorches = world->useTorches(); - MWWorld::Ptr player = getPlayer(); + const MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); /// \todo move update logic to Actor class where appropriate - std::map > cachedAllies; // will be filled as engageCombat iterates + std::map> + cachedAllies; // will be filled as engageCombat iterates - bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); - int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); + const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); + const int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); if (attackedByPlayerId != -1) { const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId); @@ -2076,18 +2230,22 @@ namespace MWMechanics if (!playerHitAttemptActor.isInCell()) player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); } - bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); + const bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); + const int actorsProcessingRange = Settings::game().mActorsProcessingRange; - // AI and magic effects update - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + // AI and magic effects update + for (Actor& actor : mActors) { - bool isPlayer = iter->first == player; - CharacterController* ctrl = iter->second->getCharacterController(); + const bool isPlayer = actor.getPtr() == player; + CharacterController& ctrl = actor.getCharacterController(); + MWBase::LuaManager::ActorControls* luaControls + = MWBase::Environment::get().getLuaManager()->getActorControls(actor.getPtr()); - float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); + const float distSqr = (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length2(); // AI processing is only done within given distance to the player. - bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange; + const bool inProcessingRange = distSqr <= actorsProcessingRange * actorsProcessingRange; +<<<<<<< HEAD /* Start of tes3mp change (minor) @@ -2132,9 +2290,18 @@ namespace MWMechanics if (iter->first != player && !mwmp::PlayerList::isDedicatedPlayer(iter->first) &&(iter->first.getClass().getCreatureStats(iter->first).isDead() || !iter->first.getClass().getCreatureStats(iter->first).getAiSequence().isInCombat() || !inProcessingRange)) +======= + // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for + // the player. + if (!isPlayer + && (actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead() + || !actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getAiSequence().isInCombat() + || !inProcessingRange)) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - iter->first.getClass().getCreatureStats(iter->first).setHitAttemptActorId(-1); - if (player.getClass().getCreatureStats(player).getHitAttemptActorId() == iter->first.getClass().getCreatureStats(iter->first).getActorId()) + actor.getPtr().getClass().getCreatureStats(actor.getPtr()).setHitAttemptActorId(-1); + if (player.getClass().getCreatureStats(player).getHitAttemptActorId() + == actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getActorId()) player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); mwmp::PlayerList::clearHitAttemptActorId(iter->first.getClass().getCreatureStats(iter->first).getActorId()); @@ -2143,32 +2310,32 @@ namespace MWMechanics End of tes3mp change (major) */ - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); - - const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration); + const Misc::TimerStatus engageCombatTimerStatus = actor.updateEngageCombatTimer(duration); // For dead actors we need to update looping spell particles - if (iter->first.getClass().getCreatureStats(iter->first).isDead()) + if (actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead()) { // They can be added during the death animation - if (!iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()) - adjustMagicEffects(iter->first); - ctrl->updateContinuousVfx(); + if (!actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDeathAnimationFinished()) + adjustMagicEffects(actor.getPtr(), duration); + ctrl.updateContinuousVfx(); } else { - bool cellChanged = world->hasCellChanged(); - MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports - updateActor(actor, duration); + MWWorld::Scene* worldScene = MWBase::Environment::get().getWorldScene(); + const bool cellChanged = worldScene->hasCellChanged(); + const MWWorld::Ptr actorPtr = actor.getPtr(); // make a copy of the map key to avoid it being + // invalidated when the player teleports + updateActor(actorPtr, duration); // Looping magic VFX update // Note: we need to do this before any of the animations are updated. // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), // so updating VFX immediately after that would just remove the particle effects instantly. // There needs to be a magic effect update in between. - ctrl->updateContinuousVfx(); + ctrl.updateContinuousVfx(); - if (!cellChanged && world->hasCellChanged()) + if (!cellChanged && worldScene->hasCellChanged()) { return; // for now abort update of the old cell when cell changes by teleportation magic effect // a better solution might be to apply cell changes at the end of the frame @@ -2187,30 +2354,28 @@ namespace MWMechanics if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed && (isLocalActor || aiActive)) { if (!isPlayer) - adjustCommandedActor(iter->first); + adjustCommandedActor(actor.getPtr()); - for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) + for (const Actor& otherActor : mActors) { - if (it->first == iter->first || isPlayer) // player is not AI-controlled + if (otherActor.getPtr() == actor.getPtr() || isPlayer) // player is not AI-controlled continue; - engageCombat(iter->first, it->first, cachedAllies, it->first == player); + engageCombat( + actor.getPtr(), otherActor.getPtr(), cachedAllies, otherActor.getPtr() == player); } } - if (timerUpdateHeadTrack == 0) + if (mTimerUpdateHeadTrack == 0) + updateHeadTracking(actor.getPtr(), mActors, isPlayer, ctrl); + + if (actor.getPtr().getClass().isNpc() && !isPlayer) + updateCrimePursuit(actor.getPtr(), duration); + + if (!isPlayer) { - float sqrHeadTrackDistance = std::numeric_limits::max(); - MWWorld::Ptr headTrackTarget; - - MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); - bool firstPersonPlayer = isPlayer && world->isFirstPerson(); - bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue); - MWWorld::Ptr activePackageTarget; - - // 1. Unconsious actor can not track target - // 2. Actors in combat and pursue mode do not bother to headtrack anyone except their target - // 3. Player character does not use headtracking in the 1st-person view - if (!stats.getKnockedDown() && !firstPersonPlayer) + CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); + if (isConscious(actor.getPtr()) && !(luaControls && luaControls->mDisableAI)) { +<<<<<<< HEAD if (inCombatOrPursue) activePackageTarget = stats.getAiSequence().getActivePackage().getTarget(); @@ -2245,107 +2410,131 @@ namespace MWMechanics } } else if ((isLocalActor || aiActive) && iter->first != player && isConscious(iter->first)) +======= + stats.getAiSequence().execute(actor.getPtr(), ctrl, duration); + updateGreetingState(actor.getPtr(), actor, mTimerUpdateHello > 0); + playIdleDialogue(actor.getPtr()); + updateMovementSpeed(actor.getPtr()); + } + } + } + else if (aiActive && !isPlayer && isConscious(actor.getPtr()) + && !(luaControls && luaControls->mDisableAI)) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); - stats.getAiSequence().execute(iter->first, *ctrl, duration, /*outOfRange*/true); + CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); + stats.getAiSequence().execute(actor.getPtr(), ctrl, duration, /*outOfRange*/ true); } /* End of tes3mp change (major) */ - if(iter->first.getClass().isNpc()) + if (inProcessingRange && actor.getPtr().getClass().isNpc()) { - // We can not update drowning state for actors outside of AI distance - they can not resurface to breathe - if (inProcessingRange) - updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); - - calculateNpcStatModifiers(iter->first, duration); - - if (timerUpdateEquippedLight == 0) - updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); + // We can not update drowning state for actors outside of AI distance - they can not resurface + // to breathe + updateDrowning(actor.getPtr(), duration, ctrl.isKnockedOut(), isPlayer); } + if (mTimerUpdateEquippedLight == 0 && actor.getPtr().getClass().hasInventoryStore(actor.getPtr())) + updateEquippedLight(actor.getPtr(), updateEquippedLightInterval, showTorches); + + if (luaControls != nullptr && isConscious(actor.getPtr())) + updateLuaControls(actor.getPtr(), isPlayer, *luaControls); } } - static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game"); - if (avoidCollisions) + if (Settings::game().mNPCsAvoidCollisions) predictAndAvoidCollisions(duration); - timerUpdateHeadTrack += duration; - timerUpdateEquippedLight += duration; - timerUpdateHello += duration; + mTimerUpdateHeadTrack += duration; + mTimerUpdateEquippedLight += duration; + mTimerUpdateHello += duration; mTimerDisposeSummonsCorpses += duration; // Animation/movement update CharacterController* playerCharacter = nullptr; - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for (Actor& actor : mActors) { - const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length(); - bool isPlayer = iter->first == player; - CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); + const float dist = (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length(); + const bool isPlayer = actor.getPtr() == player; + CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); // Actors with active AI should be able to move. bool alwaysActive = false; - if (!isPlayer && isConscious(iter->first) && !stats.isParalyzed()) + if (!isPlayer && isConscious(actor.getPtr()) && !stats.isParalyzed()) { MWMechanics::AiSequence& seq = stats.getAiSequence(); alwaysActive = !seq.isEmpty() && seq.getActivePackage().alwaysActive(); } - bool inRange = isPlayer || dist <= mActorsProcessingRange || alwaysActive; - 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 = inRange ? activeFlag : 0; + const bool inRange = isPlayer || dist <= actorsProcessingRange || alwaysActive; + const int activeFlag = isPlayer ? 2 : 1; // Can be changed back to '2' to keep updating bounding boxes + // off screen (more accurate, but slower) + const int active = inRange ? activeFlag : 0; - CharacterController* ctrl = iter->second->getCharacterController(); - ctrl->setActive(active); + CharacterController& ctrl = actor.getCharacterController(); + ctrl.setActive(active); if (!inRange) { - iter->first.getRefData().getBaseNode()->setNodeMask(0); - world->setActorCollisionMode(iter->first, false, false); + actor.getPtr().getRefData().getBaseNode()->setNodeMask(0); + world->setActorActive(actor.getPtr(), false); continue; } - else if (!isPlayer) - iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); - const bool isDead = iter->first.getClass().getCreatureStats(iter->first).isDead(); - if (!isDead && (!godmode || !isPlayer) && iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) - ctrl->skipAnim(); + world->setActorActive(actor.getPtr(), true); + + const bool isDead = actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead(); + if (!isDead && (!godmode || !isPlayer) + && actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isParalyzed()) + ctrl.skipAnim(); // Handle player last, in case a cell transition occurs by casting a teleportation spell // (would invalidate the iterator) - if (iter->first == getPlayer()) + if (isPlayer) { - playerCharacter = ctrl; + playerCharacter = &ctrl; continue; } - world->setActorCollisionMode(iter->first, true, !iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()); - ctrl->update(duration); + actor.getPtr().getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); + world->setActorCollisionMode(actor.getPtr(), true, + !actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDeathAnimationFinished()); - updateVisibility(iter->first, ctrl); + if (!actor.getPositionAdjusted()) + { + actor.getPtr().getClass().adjustPosition(actor.getPtr(), false); + actor.setPositionAdjusted(true); + } + + ctrl.update(duration); + + updateVisibility(actor.getPtr(), ctrl); } if (playerCharacter) { + MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(duration); playerCharacter->update(duration); playerCharacter->setVisibility(1.f); + MWBase::LuaManager::ActorControls* luaControls + = MWBase::Environment::get().getLuaManager()->getActorControls(player); + if (luaControls && player.getClass().getMovementSettings(player).mPosition[2] < 1) + luaControls->mJump = false; } - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for (const Actor& actor : mActors) { - const MWWorld::Class &cls = iter->first.getClass(); - CreatureStats &stats = cls.getCreatureStats(iter->first); + const MWWorld::Class& cls = actor.getPtr().getClass(); + CreatureStats& stats = cls.getCreatureStats(actor.getPtr()); - //KnockedOutOneFrameLogic - //Used for "OnKnockedOut" command - //Put here to ensure that it's run for PRECISELY one frame. + // KnockedOutOneFrameLogic + // Used for "OnKnockedOut" command + // Put here to ensure that it's run for PRECISELY one frame. if (stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) - { //Start it for one frame if nessesary + { // Start it for one frame if necessary stats.setKnockedDownOneFrame(true); } else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) - { //Turn off KnockedOutOneframe + { // Turn off KnockedOutOneframe stats.setKnockedDownOneFrame(false); stats.setKnockedDownOverOneFrame(true); } @@ -2358,10 +2547,11 @@ namespace MWMechanics updateCombatMusic(); } - void Actors::notifyDied(const MWWorld::Ptr &actor) + void Actors::notifyDied(const MWWorld::Ptr& actor) { actor.getClass().getCreatureStats(actor).notifyDied(); +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -2372,39 +2562,43 @@ namespace MWMechanics /* End of tes3mp change (major) */ +======= + ++mDeathCount[actor.getCellRef().getRefId()]; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - void Actors::resurrect(const MWWorld::Ptr &ptr) + void Actors::resurrect(const MWWorld::Ptr& ptr) const { - PtrActorMap::iterator iter = mActors.find(ptr); - if(iter != mActors.end()) + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) { - if(iter->second->getCharacterController()->isDead()) + if (iter->second->getCharacterController().isDead()) { // Actor has been resurrected. Notify the CharacterController and re-enable collision. - MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, true); - iter->second->getCharacterController()->resurrect(); + MWBase::Environment::get().getWorld()->enableActorCollision(iter->second->getPtr(), true); + iter->second->getCharacterController().resurrect(); } } } void Actors::killDeadActors() { - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for (Actor& actor : mActors) { - const MWWorld::Class &cls = iter->first.getClass(); - CreatureStats &stats = cls.getCreatureStats(iter->first); + const MWWorld::Class& cls = actor.getPtr().getClass(); + CreatureStats& stats = cls.getCreatureStats(actor.getPtr()); - if(!stats.isDead()) + if (!stats.isDead()) continue; - MWBase::Environment::get().getWorld()->removeActorPath(iter->first); - CharacterController::KillResult killResult = iter->second->getCharacterController()->kill(); + MWBase::Environment::get().getWorld()->removeActorPath(actor.getPtr()); + CharacterController::KillResult killResult = actor.getCharacterController().kill(); if (killResult == CharacterController::Result_DeathAnimStarted) { // Play dying words // Note: It's not known whether the soundgen tags scream, roar, and moan are reliable // for NPCs since some of the npc death animation files are missing them. +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -2416,40 +2610,35 @@ namespace MWMechanics /* End of tes3mp change (major) */ +======= + MWBase::Environment::get().getDialogueManager()->say(actor.getPtr(), ESM::RefId::stringRefId("hit")); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 // Apply soultrap - if (iter->first.getTypeName() == typeid(ESM::Creature).name()) - { - SoulTrap soulTrap (iter->first); - stats.getActiveSpells().visitEffectSources(soulTrap); - } + if (actor.getPtr().getType() == ESM::Creature::sRecordId) + soulTrap(actor.getPtr()); - // Magic effects will be reset later, and the magic effect that could kill the actor - // needs to be determined now - calculateCreatureStatModifiers(iter->first, 0); - - if (cls.isEssential(iter->first)) + if (cls.isEssential(actor.getPtr())) MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } else if (killResult == CharacterController::Result_DeathAnimJustFinished) { - bool isPlayer = iter->first == getPlayer(); - notifyDied(iter->first); + const bool isPlayer = actor.getPtr() == getPlayer(); + notifyDied(actor.getPtr()); // Reset magic effects and recalculate derived effects // One case where we need this is to make sure bound items are removed upon death - stats.modifyMagicEffects(MWMechanics::MagicEffects()); - stats.getActiveSpells().clear(); + const float vampirism + = stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Vampirism).getMagnitude(); + stats.getActiveSpells().clear(actor.getPtr()); // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); - // Reset dynamic stats, attributes and skills - calculateCreatureStatModifiers(iter->first, 0); - if (iter->first.getClass().isNpc()) - calculateNpcStatModifiers(iter->first, 0); + stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism); if (isPlayer) { +<<<<<<< HEAD //player's death animation is over /* @@ -2462,22 +2651,25 @@ namespace MWMechanics /* End of tes3mp change (major) */ +======= + // player's death animation is over + MWBase::Environment::get().getStateManager()->askLoadRecent(); + // Play Death Music if it was the player dying + MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } else { // NPC death animation is over, disable actor collision - MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); + MWBase::Environment::get().getWorld()->enableActorCollision(actor.getPtr(), false); } - - // Play Death Music if it was the player dying - if(iter->first == getPlayer()) - MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); } } } - void Actors::cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) + void Actors::cleanupSummonedCreature(MWMechanics::CreatureStats& casterStats, int creatureActorId) const { +<<<<<<< HEAD MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); /* @@ -2488,6 +2680,10 @@ namespace MWMechanics */ if (!ptr.isEmpty() && (casterStats.getActorId() == getPlayer().getClass().getCreatureStats(getPlayer()).getActorId() || mwmp::Main::get().getCellController()->hasLocalAuthority(*ptr.getCell()->getCell()))) +======= + const MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); + if (!ptr.isEmpty()) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList(); objectList->reset(); @@ -2498,15 +2694,19 @@ namespace MWMechanics End of tes3mp change (major) */ - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); + const ESM::Static* fx = MWBase::Environment::get().getESMStore()->get().search( + ESM::RefId::stringRefId("VFX_Summon_End")); if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", ptr.getRefData().getPosition().asVec3()); + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + MWBase::Environment::get().getWorld()->spawnEffect( + Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), "", + ptr.getRefData().getPosition().asVec3()); + } // Remove the summoned creature's summoned creatures as well MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - std::map& creatureMap = stats.getSummonedCreatureMap(); + auto& creatureMap = stats.getSummonedCreatureMap(); for (const auto& creature : creatureMap) cleanupSummonedCreature(stats, creature.second); creatureMap.clear(); @@ -2522,55 +2722,50 @@ namespace MWMechanics purgeSpellEffects(creatureActorId); } - void Actors::purgeSpellEffects(int casterActorId) + void Actors::purgeSpellEffects(int casterActorId) const { - for (PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + for (const Actor& actor : mActors) { - MWMechanics::ActiveSpells& spells = iter->first.getClass().getCreatureStats(iter->first).getActiveSpells(); - spells.purge(casterActorId); + MWMechanics::ActiveSpells& spells + = actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getActiveSpells(); + spells.purge(actor.getPtr(), casterActorId); } } - void Actors::rest(double hours, bool sleep) + void Actors::rest(double hours, bool sleep) const { float duration = hours * 3600.f; - float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + const float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); if (timeScale != 0.f) duration /= timeScale; const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); + const int actorsProcessingRange = Settings::game().mActorsProcessingRange; - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for (const Actor& actor : mActors) { - if (iter->first.getClass().getCreatureStats(iter->first).isDead()) + if (actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead()) { - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); + adjustMagicEffects(actor.getPtr(), duration); continue; } - if (!sleep || iter->first == player) - restoreDynamicStats(iter->first, hours, sleep); + if (!sleep || actor.getPtr() == player) + restoreDynamicStats(actor.getPtr(), hours, sleep); - if ((!iter->first.getRefData().getBaseNode()) || - (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) + if ((!actor.getPtr().getRefData().getBaseNode()) + || (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length2() + > actorsProcessingRange * actorsProcessingRange) continue; - adjustMagicEffects (iter->first); - if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats()) - calculateDynamicStats (iter->first); + adjustMagicEffects(actor.getPtr(), duration); - calculateCreatureStatModifiers (iter->first, duration); - if (iter->first.getClass().isNpc()) - calculateNpcStatModifiers(iter->first, duration); - - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); - - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(actor.getPtr()); if (animation) { animation->removeEffects(); - MWBase::Environment::get().getWorld()->applyLoopingParticles(iter->first); + MWBase::Environment::get().getWorld()->applyLoopingParticles(actor.getPtr()); } } @@ -2579,15 +2774,13 @@ namespace MWMechanics void Actors::updateSneaking(CharacterController* ctrl, float duration) { - static float sneakTimer = 0.f; // Times update of sneak icon - if (!ctrl) { MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); return; } - MWWorld::Ptr player = getPlayer(); + const MWWorld::Ptr player = getPlayer(); if (!MWBase::Environment::get().getMechanicsManager()->isSneaking(player)) { @@ -2595,32 +2788,34 @@ namespace MWMechanics return; } - static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice" - - MWBase::World* world = MWBase::Environment::get().getWorld(); + MWBase::World* const world = MWBase::Environment::get().getWorld(); const MWWorld::Store& gmst = world->getStore().get(); static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat(); static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat(); - if (sneakTimer >= fSneakUseDelay) - sneakTimer = 0.f; + if (mSneakTimer >= fSneakUseDelay) + mSneakTimer = 0.f; - if (sneakTimer == 0.f) + if (mSneakTimer == 0.f) { // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. bool avoidedNotice = false; bool detected = false; std::vector observers; - osg::Vec3f position(player.getRefData().getPosition().asVec3()); - float radius = std::min(fSneakUseDist, mActorsProcessingRange); + const osg::Vec3f position(player.getRefData().getPosition().asVec3()); + const float radius = std::min(fSneakUseDist, Settings::game().mActorsProcessingRange); getObjectsInRange(position, radius, observers); - for (const MWWorld::Ptr &observer : observers) + std::set sidingActors; + getActorsSidingWith(player, sidingActors); + + for (const MWWorld::Ptr& observer : observers) { if (observer == player || observer.getClass().getCreatureStats(observer).isDead()) continue; +<<<<<<< HEAD /* Start of tes3mp addition @@ -2631,6 +2826,10 @@ namespace MWMechanics /* End of tes3mp addition */ +======= + if (sidingActors.find(observer) != sidingActors.cend()) + continue; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (world->getLOS(player, observer)) { @@ -2648,47 +2847,46 @@ namespace MWMechanics } } - if (sneakSkillTimer >= fSneakUseDelay) - sneakSkillTimer = 0.f; + if (mSneakSkillTimer >= fSneakUseDelay) + mSneakSkillTimer = 0.f; - if (avoidedNotice && sneakSkillTimer == 0.f) + if (avoidedNotice && mSneakSkillTimer == 0.f) player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); if (!detected) MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); } - sneakTimer += duration; - sneakSkillTimer += duration; + mSneakTimer += duration; + mSneakSkillTimer += duration; } - int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const + int Actors::getHoursToRest(const MWWorld::Ptr& ptr) const { - float healthPerHour, magickaPerHour; - getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour); + const auto [healthPerHour, magickaPerHour] = getRestorationPerHourOfSleep(ptr); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; + const bool stunted = stats.getMagicEffects().getOrDefault(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; - float healthHours = healthPerHour > 0 - ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour - : 1.0f; - float magickaHours = magickaPerHour > 0 && !stunted - ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour - : 1.0f; + const float healthHours = healthPerHour > 0 + ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour + : 1.0f; + const float magickaHours = magickaPerHour > 0 && !stunted + ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour + : 1.0f; - int autoHours = static_cast(std::ceil(std::max(1.f, std::max(healthHours, magickaHours)))); - return autoHours; + return static_cast(std::ceil(std::max(1.f, std::max(healthHours, magickaHours)))); } - int Actors::countDeaths (const std::string& id) const + int Actors::countDeaths(const ESM::RefId& id) const { - std::map::const_iterator iter = mDeathCount.find(id); - if(iter != mDeathCount.end()) + const auto iter = mDeathCount.find(id); + if (iter != mDeathCount.end()) return iter->second; return 0; } +<<<<<<< HEAD /* Start of tes3mp addition @@ -2703,81 +2901,88 @@ namespace MWMechanics */ void Actors::forceStateUpdate(const MWWorld::Ptr & ptr) +======= + void Actors::forceStateUpdate(const MWWorld::Ptr& ptr) const +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - PtrActorMap::iterator iter = mActors.find(ptr); - if(iter != mActors.end()) - iter->second->getCharacterController()->forceStateUpdate(); + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->getCharacterController().forceStateUpdate(); } - bool Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) + bool Actors::playAnimationGroup( + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) const { - PtrActorMap::iterator iter = mActors.find(ptr); - if(iter != mActors.end()) + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) { - return iter->second->getCharacterController()->playGroup(groupName, mode, number, persist); + return iter->second->getCharacterController().playGroup(groupName, mode, number, persist); } else { - Log(Debug::Warning) << "Warning: Actors::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId(); + Log(Debug::Warning) << "Warning: Actors::playAnimationGroup: Unable to find " + << ptr.getCellRef().getRefId(); return false; } } - void Actors::skipAnimation(const MWWorld::Ptr& ptr) + void Actors::skipAnimation(const MWWorld::Ptr& ptr) const { - PtrActorMap::iterator iter = mActors.find(ptr); - if(iter != mActors.end()) - iter->second->getCharacterController()->skipAnim(); + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->getCharacterController().skipAnim(); } - bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) + bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const { - PtrActorMap::iterator iter = mActors.find(ptr); - if(iter != mActors.end()) - return iter->second->getCharacterController()->isAnimPlaying(groupName); + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + return iter->second->getCharacterController().isAnimPlaying(groupName); return false; } - void Actors::persistAnimationStates() + void Actors::persistAnimationStates() const { - for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) - iter->second->getCharacterController()->persistAnimationState(); + for (const Actor& actor : mActors) + actor.getCharacterController().persistAnimationState(); } - void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) + void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const { - for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + for (const Actor& actor : mActors) { - if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius) - out.push_back(iter->first); + if ((actor.getPtr().getRefData().getPosition().asVec3() - position).length2() <= radius * radius) + out.push_back(actor.getPtr()); } } - bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius) + bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius) const { - for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + for (const Actor& actor : mActors) { - if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius) + if ((actor.getPtr().getRefData().getPosition().asVec3() - position).length2() <= radius * radius) return true; } return false; } - std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) + std::vector Actors::getActorsSidingWith(const MWWorld::Ptr& actorPtr, bool excludeInfighting) const { - std::list list; - for(PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + std::vector list; + list.push_back(actorPtr); + for (const Actor& actor : mActors) { - const MWWorld::Ptr &iteratedActor = iter->first; + const MWWorld::Ptr& iteratedActor = actor.getPtr(); if (iteratedActor == getPlayer()) continue; - const bool sameActor = (iteratedActor == actor); + const bool sameActor = (iteratedActor == actorPtr); - const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); + const CreatureStats& stats = iteratedActor.getClass().getCreatureStats(iteratedActor); if (stats.isDead()) continue; +<<<<<<< HEAD /* Start of tes3mp addition @@ -2813,71 +3018,95 @@ namespace MWMechanics // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package // Actors that are targeted by this actor's Follow or Escort packages also side with them +======= + // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are + // only Wander packages before the Follow/Escort package Actors that are targeted by this actor's Follow or + // Escort packages also side with them +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 for (const auto& package : stats.getAiSequence()) { + if (excludeInfighting && !sameActor && package->getTypeId() == AiPackageTypeId::Combat + && package->getTarget() == actorPtr) + break; if (package->sideWithTarget() && !package->getTarget().isEmpty()) { if (sameActor) { + if (excludeInfighting) + { + MWWorld::Ptr ally = package->getTarget(); + std::vector enemies; + if (ally.getClass().getCreatureStats(ally).getAiSequence().getCombatTargets(enemies) + && std::find(enemies.begin(), enemies.end(), actorPtr) != enemies.end()) + break; + } list.push_back(package->getTarget()); } - else if (package->getTarget() == actor) + else if (package->getTarget() == actorPtr) { list.push_back(iteratedActor); } break; } - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + else if (package->getTypeId() > AiPackageTypeId::Wander + && package->getTypeId() <= AiPackageTypeId::Activate) // Don't count "fake" package types break; } } return list; } - std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) + std::vector Actors::getActorsFollowing(const MWWorld::Ptr& actorPtr) const { - std::list list; - forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) - { - if (package->followTargetThroughDoors() && package->getTarget() == actor) - list.push_back(iter.first); - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) - return false; - return true; - }); + std::vector list; + forEachFollowingPackage( + mActors, actorPtr, getPlayer(), [&](const Actor& actor, const std::shared_ptr& package) { + if (package->followTargetThroughDoors() && package->getTarget() == actorPtr) + list.push_back(actor.getPtr()); + else if (package->getTypeId() != AiPackageTypeId::Combat + && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); return list; } - void Actors::getActorsFollowing(const MWWorld::Ptr &actor, std::set& out) { - std::list followers = getActorsFollowing(actor); - for(const MWWorld::Ptr &follower : followers) + void Actors::getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) const + { + auto followers = getActorsFollowing(actor); + for (const MWWorld::Ptr& follower : followers) if (out.insert(follower).second) getActorsFollowing(follower, out); } - void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out) { - std::list followers = getActorsSidingWith(actor); - for(const MWWorld::Ptr &follower : followers) - if (out.insert(follower).second) - getActorsSidingWith(follower, out); + void Actors::getActorsSidingWith( + const MWWorld::Ptr& actor, std::set& out, bool excludeInfighting) const + { + auto followers = getActorsSidingWith(actor, excludeInfighting); + for (const MWWorld::Ptr& follower : followers) + if (out.insert(follower).second && follower != actor) + getActorsSidingWith(follower, out, excludeInfighting); } - void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out, std::map >& cachedAllies) { + void Actors::getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out, + std::map>& cachedAllies) const + { // If we have already found actor's allies, use the cache - std::map >::const_iterator search = cachedAllies.find(actor); + std::map>::const_iterator search = cachedAllies.find(actor); if (search != cachedAllies.end()) out.insert(search->second.begin(), search->second.end()); else { - std::list followers = getActorsSidingWith(actor); - for (const MWWorld::Ptr &follower : followers) - if (out.insert(follower).second) + for (const MWWorld::Ptr& follower : getActorsSidingWith(actor, true)) + if (out.insert(follower).second && follower != actor) getActorsSidingWith(follower, out, cachedAllies); // Cache ptrs and their sets of allies cachedAllies.insert(std::make_pair(actor, out)); - for (const MWWorld::Ptr &iter : out) + for (const MWWorld::Ptr& iter : out) { + if (iter == actor) + continue; search = cachedAllies.find(iter); if (search == cachedAllies.end()) cachedAllies.insert(std::make_pair(iter, out)); @@ -2885,106 +3114,109 @@ namespace MWMechanics } } - std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) + std::vector Actors::getActorsFollowingIndices(const MWWorld::Ptr& actor) const { - std::list list; - forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) - { - if (package->followTargetThroughDoors() && package->getTarget() == actor) - { - list.push_back(static_cast(package.get())->getFollowIndex()); - return false; - } - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) - return false; - return true; - }); + std::vector list; + forEachFollowingPackage( + mActors, actor, getPlayer(), [&](const Actor&, const std::shared_ptr& package) { + if (package->followTargetThroughDoors() && package->getTarget() == actor) + { + list.push_back(static_cast(package.get())->getFollowIndex()); + return false; + } + else if (package->getTypeId() != AiPackageTypeId::Combat + && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); return list; } - std::map Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor) + std::map Actors::getActorsFollowingByIndex(const MWWorld::Ptr& actor) const { std::map map; - forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) - { - if (package->followTargetThroughDoors() && package->getTarget() == actor) - { - int index = static_cast(package.get())->getFollowIndex(); - map[index] = iter.first; - return false; - } - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) - return false; - return true; - }); + forEachFollowingPackage( + mActors, actor, getPlayer(), [&](const Actor& otherActor, const std::shared_ptr& package) { + if (package->followTargetThroughDoors() && package->getTarget() == actor) + { + const int index = static_cast(package.get())->getFollowIndex(); + map[index] = otherActor.getPtr(); + return false; + } + else if (package->getTypeId() != AiPackageTypeId::Combat + && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); return map; } - std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { - std::list list; + std::vector Actors::getActorsFighting(const MWWorld::Ptr& actor) const + { + std::vector list; std::vector neighbors; - osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, mActorsProcessingRange, neighbors); - for(const MWWorld::Ptr& neighbor : neighbors) + const osg::Vec3f position(actor.getRefData().getPosition().asVec3()); + getObjectsInRange(position, Settings::game().mActorsProcessingRange, neighbors); + for (const MWWorld::Ptr& neighbor : neighbors) { if (neighbor == actor) continue; - const CreatureStats &stats = neighbor.getClass().getCreatureStats(neighbor); + const CreatureStats& stats = neighbor.getClass().getCreatureStats(neighbor); if (stats.isDead()) continue; if (stats.getAiSequence().isInCombat(actor)) - list.push_front(neighbor); + list.push_back(neighbor); } return list; } - std::list Actors::getEnemiesNearby(const MWWorld::Ptr& actor) + std::vector Actors::getEnemiesNearby(const MWWorld::Ptr& actor) const { - std::list list; + std::vector list; std::vector neighbors; - osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, mActorsProcessingRange, neighbors); + osg::Vec3f position(actor.getRefData().getPosition().asVec3()); + getObjectsInRange(position, Settings::game().mActorsProcessingRange, neighbors); std::set followers; getActorsFollowing(actor, followers); - for (auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor) + for (const MWWorld::Ptr& neighbor : neighbors) { - const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor); - if (stats.isDead() || *neighbor == actor || neighbor->getClass().isPureWaterCreature(*neighbor)) + const CreatureStats& stats = neighbor.getClass().getCreatureStats(neighbor); + if (stats.isDead() || neighbor == actor || neighbor.getClass().isPureWaterCreature(neighbor)) continue; - const bool isFollower = followers.find(*neighbor) != followers.end(); + const bool isFollower = followers.find(neighbor) != followers.end(); - if (stats.getAiSequence().isInCombat(actor) || (MWBase::Environment::get().getMechanicsManager()->isAggressive(*neighbor, actor) && !isFollower)) - list.push_back(*neighbor); + if (stats.getAiSequence().isInCombat(actor) + || (MWBase::Environment::get().getMechanicsManager()->isAggressive(neighbor, actor) && !isFollower)) + list.push_back(neighbor); } return list; } - - void Actors::write (ESM::ESMWriter& writer, Loading::Listener& listener) const + void Actors::write(ESM::ESMWriter& writer, Loading::Listener& listener) const { writer.startRecord(ESM::REC_DCOU); - for (std::map::const_iterator it = mDeathCount.begin(); it != mDeathCount.end(); ++it) + for (const auto& [id, count] : mDeathCount) { - writer.writeHNString("ID__", it->first); - writer.writeHNT ("COUN", it->second); + writer.writeHNRefId("ID__", id); + writer.writeHNT("COUN", count); } writer.endRecord(ESM::REC_DCOU); } - void Actors::readRecord (ESM::ESMReader& reader, uint32_t type) + void Actors::readRecord(ESM::ESMReader& reader, uint32_t type) { if (type == ESM::REC_DCOU) { while (reader.isNextSub("ID__")) { - std::string id = reader.getHString(); + ESM::RefId id = reader.getRefId(); int count; reader.getHNT(count, "COUN"); - if (MWBase::Environment::get().getWorld()->getStore().find(id)) + if (MWBase::Environment::get().getESMStore()->find(id)) mDeathCount[id] = count; } } @@ -2992,50 +3224,41 @@ namespace MWMechanics void Actors::clear() { - PtrActorMap::iterator it(mActors.begin()); - for (; it != mActors.end(); ++it) - { - delete it->second; - it->second = nullptr; - } + mIndex.clear(); mActors.clear(); mDeathCount.clear(); } - void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) + void Actors::updateMagicEffects(const MWWorld::Ptr& ptr) const { - adjustMagicEffects(ptr); - calculateCreatureStatModifiers(ptr, 0.f); - if (ptr.getClass().isNpc()) - calculateNpcStatModifiers(ptr, 0.f); + adjustMagicEffects(ptr, 0.f); } - bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const + bool Actors::isReadyToBlock(const MWWorld::Ptr& ptr) const { - PtrActorMap::const_iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return false; - return it->second->getCharacterController()->isReadyToBlock(); + return it->second->getCharacterController().isReadyToBlock(); } - bool Actors::isCastingSpell(const MWWorld::Ptr &ptr) const + bool Actors::isCastingSpell(const MWWorld::Ptr& ptr) const { - PtrActorMap::const_iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return false; - return it->second->getCharacterController()->isCastingSpell(); + return it->second->getCharacterController().isCastingSpell(); } bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const { - PtrActorMap::const_iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return false; - CharacterController* ctrl = it->second->getCharacterController(); - return ctrl->isAttackingOrSpell(); + return it->second->getCharacterController().isAttackingOrSpell(); } /* @@ -3058,8 +3281,8 @@ namespace MWMechanics int Actors::getGreetingTimer(const MWWorld::Ptr& ptr) const { - PtrActorMap::const_iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return 0; return it->second->getGreetingTimer(); @@ -3067,8 +3290,8 @@ namespace MWMechanics float Actors::getAngleToPlayer(const MWWorld::Ptr& ptr) const { - PtrActorMap::const_iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return 0.f; return it->second->getAngleToPlayer(); @@ -3076,8 +3299,8 @@ namespace MWMechanics GreetingState Actors::getGreetingState(const MWWorld::Ptr& ptr) const { - PtrActorMap::const_iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return Greet_None; return it->second->getGreetingState(); @@ -3085,26 +3308,23 @@ namespace MWMechanics bool Actors::isTurningToPlayer(const MWWorld::Ptr& ptr) const { - PtrActorMap::const_iterator it = mActors.find(ptr); - if (it == mActors.end()) + const auto it = mIndex.find(ptr.mRef); + if (it == mIndex.end()) return false; return it->second->isTurningToPlayer(); } - void Actors::fastForwardAi() + void Actors::fastForwardAi() const { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) return; - // making a copy since fast-forward could move actor to a different cell and invalidate the mActors iterator - PtrActorMap map = mActors; - for (PtrActorMap::iterator it = map.begin(); it != map.end(); ++it) + for (auto it = mActors.begin(); it != mActors.end();) { - MWWorld::Ptr ptr = it->first; - if (ptr == getPlayer() - || !isConscious(ptr) - || ptr.getClass().getCreatureStats(ptr).isParalyzed()) + const MWWorld::Ptr ptr = it->getPtr(); + ++it; + if (ptr == getPlayer() || !isConscious(ptr) || ptr.getClass().getCreatureStats(ptr).isParalyzed()) continue; MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); seq.fastForward(ptr); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index ce831d042..c18ac467a 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -1,13 +1,13 @@ #ifndef GAME_MWMECHANICS_ACTORS_H #define GAME_MWMECHANICS_ACTORS_H -#include -#include -#include #include #include +#include +#include +#include -#include "../mwmechanics/actorutil.hpp" +#include "actor.hpp" namespace ESM { @@ -39,112 +39,127 @@ namespace MWMechanics class Actors { - std::map mDeathCount; + public: + std::list::const_iterator begin() const { return mActors.begin(); } + std::list::const_iterator end() const { return mActors.end(); } + std::size_t size() const { return mActors.size(); } - void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); - void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); + void notifyDied(const MWWorld::Ptr& actor); - void adjustMagicEffects (const MWWorld::Ptr& creature); + /// Check if the target actor was detected by an observer + /// If the observer is a non-NPC, check all actors in AI processing distance as observers + bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) const; - void calculateDynamicStats (const MWWorld::Ptr& ptr); + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + void updateMagicEffects(const MWWorld::Ptr& ptr) const; - void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); - void calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration); + void addActor(const MWWorld::Ptr& ptr, bool updateImmediately = false); + ///< Register an actor for stats management + /// + /// \note Dead actors are ignored. - void calculateRestoration (const MWWorld::Ptr& ptr, float duration); + void removeActor(const MWWorld::Ptr& ptr, bool keepActive); + ///< Deregister an actor for stats management + /// + /// \note Ignored, if \a ptr is not a registered actor. - void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer); + void resurrect(const MWWorld::Ptr& ptr) const; - void updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip); + void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) const; - void updateCrimePursuit (const MWWorld::Ptr& ptr, float duration); + void updateActor(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) const; + ///< Updates an actor with a new Ptr - void killDeadActors (); + void dropActors(const MWWorld::CellStore* cellStore, const MWWorld::Ptr& ignore); + ///< Deregister all actors (except for \a ignore) in the given cell. - void purgeSpellEffects (int casterActorId); + void updateCombatMusic(); + ///< Update combat music state - void predictAndAvoidCollisions(float duration); + void update(float duration, bool paused); + ///< Update actor stats and store desired velocity vectors in \a movement - public: + void updateActor(const MWWorld::Ptr& ptr, float duration) const; + ///< This function is normally called automatically during the update process, but it can + /// also be called explicitly at any time to force an update. - Actors(); - ~Actors(); + /// Removes an actor from combat and makes all of their allies stop fighting the actor's targets + void stopCombat(const MWWorld::Ptr& ptr) const; - typedef std::map PtrActorMap; + void playIdleDialogue(const MWWorld::Ptr& actor) const; + void updateMovementSpeed(const MWWorld::Ptr& actor) const; + void updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly); + void turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir) const; - PtrActorMap::const_iterator begin() { return mActors.begin(); } - PtrActorMap::const_iterator end() { return mActors.end(); } - std::size_t size() const { return mActors.size(); } + void rest(double hours, bool sleep) const; + ///< Update actors while the player is waiting or sleeping. - void notifyDied(const MWWorld::Ptr &actor); + void updateSneaking(CharacterController* ctrl, float duration); + ///< Update the sneaking indicator state according to the given player character controller. - /// Check if the target actor was detected by an observer - /// If the observer is a non-NPC, check all actors in AI processing distance as observers - bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer); + void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep) const; - /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently - /// paused we may want to do it manually (after equipping permanent enchantment) - void updateMagicEffects (const MWWorld::Ptr& ptr); + int getHoursToRest(const MWWorld::Ptr& ptr) const; + ///< Calculate how many hours the given actor needs to rest in order to be fully healed - void updateProcessingRange(); - float getProcessingRange() const; + void fastForwardAi() const; + ///< Simulate the passing of time - void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false); - ///< Register an actor for stats management - /// - /// \note Dead actors are ignored. + int countDeaths(const ESM::RefId& id) const; + ///< Return the number of deaths for actors with the given ID. - void removeActor (const MWWorld::Ptr& ptr); - ///< Deregister an actor for stats management - /// - /// \note Ignored, if \a ptr is not a registered actor. + bool isAttackPreparing(const MWWorld::Ptr& ptr) const; + bool isRunning(const MWWorld::Ptr& ptr) const; + bool isSneaking(const MWWorld::Ptr& ptr) const; - void resurrect (const MWWorld::Ptr& ptr); + void forceStateUpdate(const MWWorld::Ptr& ptr) const; - void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false); + bool playAnimationGroup( + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) const; + void skipAnimation(const MWWorld::Ptr& ptr) const; + bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; + void persistAnimationStates() const; - void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); - ///< Updates an actor with a new Ptr + void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const; - void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore); - ///< Deregister all actors (except for \a ignore) in the given cell. + bool isAnyObjectInRange(const osg::Vec3f& position, float radius) const; - void updateCombatMusic(); - ///< Update combat music state + void cleanupSummonedCreature(CreatureStats& casterStats, int creatureActorId) const; - void update (float duration, bool paused); - ///< Update actor stats and store desired velocity vectors in \a movement + /// Returns the list of actors which are siding with the given actor in fights + /**ie AiFollow or AiEscort is active and the target is the actor **/ + std::vector getActorsSidingWith(const MWWorld::Ptr& actor, bool excludeInfighting = false) const; + std::vector getActorsFollowing(const MWWorld::Ptr& actor) const; - void updateActor (const MWWorld::Ptr& ptr, float duration); - ///< This function is normally called automatically during the update process, but it can - /// also be called explicitly at any time to force an update. + /// Recursive version of getActorsFollowing + void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) const; + /// Recursive version of getActorsSidingWith + void getActorsSidingWith( + const MWWorld::Ptr& actor, std::set& out, bool excludeInfighting = false) const; - /** Start combat between two actors - @Notes: If againstPlayer = true then actor2 should be the Player. - If one of the combatants is creature it should be actor1. - */ - void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer); + /// Get the list of AiFollow::mFollowIndex for all actors following this target + std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor) const; + std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) const; - void playIdleDialogue(const MWWorld::Ptr& actor); - void updateMovementSpeed(const MWWorld::Ptr& actor); - void updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly); - void turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir); + /// Returns the list of actors which are fighting the given actor + /**ie AiCombat is active and the target is the actor **/ + std::vector getActorsFighting(const MWWorld::Ptr& actor) const; - void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, - MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance, - bool inCombatOrPursue); + /// Unlike getActorsFighting, also returns actors that *would* fight the given actor if they saw him. + std::vector getEnemiesNearby(const MWWorld::Ptr& actor) const; - void rest(double hours, bool sleep); - ///< Update actors while the player is waiting or sleeping. + void write(ESM::ESMWriter& writer, Loading::Listener& listener) const; - void updateSneaking(CharacterController* ctrl, float duration); - ///< Update the sneaking indicator state according to the given player character controller. + void readRecord(ESM::ESMReader& reader, uint32_t type); - void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep); + void clear(); // Clear death counter - int getHoursToRest(const MWWorld::Ptr& ptr) const; - ///< Calculate how many hours the given actor needs to rest in order to be fully healed + bool isCastingSpell(const MWWorld::Ptr& ptr) const; + bool isReadyToBlock(const MWWorld::Ptr& ptr) const; + bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; +<<<<<<< HEAD void fastForwardAi(); ///< Simulate the passing of time @@ -225,16 +240,58 @@ namespace MWMechanics float getAngleToPlayer(const MWWorld::Ptr& ptr) const; GreetingState getGreetingState(const MWWorld::Ptr& ptr) const; bool isTurningToPlayer(const MWWorld::Ptr& ptr) const; +======= + int getGreetingTimer(const MWWorld::Ptr& ptr) const; + float getAngleToPlayer(const MWWorld::Ptr& ptr) const; + GreetingState getGreetingState(const MWWorld::Ptr& ptr) const; + bool isTurningToPlayer(const MWWorld::Ptr& ptr) const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 private: - void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); - void applyCureEffects (const MWWorld::Ptr& actor); + enum class MusicType + { + Title, + Explore, + Battle + }; - PtrActorMap mActors; - float mTimerDisposeSummonsCorpses; - float mActorsProcessingRange; + std::map mDeathCount; + std::list mActors; + std::map::iterator> mIndex; + // We should add a delay between summoned creature death and its corpse despawning + float mTimerDisposeSummonsCorpses = 0.2f; + float mTimerUpdateHeadTrack = 0; + float mTimerUpdateEquippedLight = 0; + float mTimerUpdateHello = 0; + float mSneakTimer = 0; // Times update of sneak icon + float mSneakSkillTimer = 0; // Times sneak skill progress from "avoid notice" + MusicType mCurrentMusic = MusicType::Title; - bool mSmoothMovement; + void updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const; + + void adjustMagicEffects(const MWWorld::Ptr& creature, float duration) const; + + void calculateRestoration(const MWWorld::Ptr& ptr, float duration) const; + + void updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) const; + + void killDeadActors(); + + void purgeSpellEffects(int casterActorId) const; + + void predictAndAvoidCollisions(float duration) const; + + /** Start combat between two actors + @Notes: If againstPlayer = true then actor2 should be the Player. + If one of the combatants is creature it should be actor1. + */ + void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, + std::map>& cachedAllies, bool againstPlayer) const; + + /// Recursive version of getActorsSidingWith that takes, adds to and returns a cache of + /// actors mapped to their allies. Excludes infighting + void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out, + std::map>& cachedAllies) const; }; } diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index 04cbb8e9f..c414ff303 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -1,11 +1,16 @@ #include "actorutil.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/magiceffects.hpp" + +#include + namespace MWMechanics { MWWorld::Ptr getPlayer() @@ -27,6 +32,13 @@ namespace MWMechanics bool hasWaterWalking(const MWWorld::Ptr& actor) { const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); - return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; + return effects.getOrDefault(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; + } + + bool isTargetMagicallyHidden(const MWWorld::Ptr& actor) + { + const MagicEffects& magicEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); + return (magicEffects.getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude() > 0) + || (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() > 75); } } diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index a226fc9cb..829204eef 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -1,19 +1,6 @@ #ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H -#include - -#include -#include -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/esmstore.hpp" - -#include "./creaturestats.hpp" - namespace MWWorld { class Ptr; @@ -21,71 +8,11 @@ namespace MWWorld namespace MWMechanics { - enum GreetingState - { - Greet_None, - Greet_InProgress, - Greet_Done - }; - MWWorld::Ptr getPlayer(); bool isPlayerInCombat(); bool canActorMoveByZAxis(const MWWorld::Ptr& actor); bool hasWaterWalking(const MWWorld::Ptr& actor); - - template - void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) - { - T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(id); - switch(setting) - { - case MWMechanics::CreatureStats::AiSetting::AI_Hello: - copy.mAiData.mHello = value; - break; - case MWMechanics::CreatureStats::AiSetting::AI_Fight: - copy.mAiData.mFight = value; - break; - case MWMechanics::CreatureStats::AiSetting::AI_Flee: - copy.mAiData.mFlee = value; - break; - case MWMechanics::CreatureStats::AiSetting::AI_Alarm: - copy.mAiData.mAlarm = value; - break; - default: - assert(0); - } - MWBase::Environment::get().getWorld()->createOverrideRecord(copy); - } - - template - void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) - { - T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(actorId); - for(auto& it : copy.mInventory.mList) - { - if(Misc::StringUtils::ciEqual(it.mItem, itemId)) - { - int sign = it.mCount < 1 ? -1 : 1; - it.mCount = sign * std::max(it.mCount * sign + amount, 0); - MWBase::Environment::get().getWorld()->createOverrideRecord(copy); - return; - } - } - if(amount > 0) - { - ESM::ContItem cont; - cont.mItem = itemId; - cont.mCount = amount; - copy.mInventory.mList.push_back(cont); - MWBase::Environment::get().getWorld()->createOverrideRecord(copy); - } - } - - template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); - template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); - template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); - template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); - template void modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount); + bool isTargetMagicallyHidden(const MWWorld::Ptr& actor); } #endif diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 6fd91465a..5b7d635e1 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -1,3 +1,4 @@ +<<<<<<< HEAD #include "aiactivate.hpp" #include @@ -120,3 +121,73 @@ namespace MWMechanics { } } +======= +#include "aiactivate.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/class.hpp" + +#include "creaturestats.hpp" +#include "movement.hpp" +#include "steering.hpp" + +namespace MWMechanics +{ + AiActivate::AiActivate(const ESM::RefId& objectId, bool repeat) + : TypedAiPackage(repeat) + , mObjectId(objectId) + { + } + + bool AiActivate::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + { + const MWWorld::Ptr target + = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); // The target to follow + + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); + + // Stop if the target doesn't exist + // Really we should be checking whether the target is currently registered with the MechanicsManager + if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + return true; + + // Turn to target and move to it directly, without pathfinding. + const osg::Vec3f targetDir + = target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3(); + + zTurn(actor, std::atan2(targetDir.x(), targetDir.y()), 0.f); + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + actor.getClass().getMovementSettings(actor).mPosition[0] = 0; + + if (MWBase::Environment::get().getWorld()->getMaxActivationDistance() >= targetDir.length()) + { + // Note: we intentionally do not cancel package after activation here for backward compatibility with + // original engine. + MWBase::Environment::get().getWorld()->activate(target, actor); + } + return false; + } + + void AiActivate::writeState(ESM::AiSequence::AiSequence& sequence) const + { + auto activate = std::make_unique(); + activate->mTargetId = mObjectId; + activate->mRepeat = getRepeat(); + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Activate; + package.mPackage = std::move(activate); + sequence.mPackages.push_back(std::move(package)); + } + + AiActivate::AiActivate(const ESM::AiSequence::AiActivate* activate) + : AiActivate(activate->mTargetId, activate->mRepeat) + { + } +} +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 506624435..7fad6ed79 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -1,3 +1,4 @@ +<<<<<<< HEAD #ifndef GAME_MWMECHANICS_AIACTIVATE_H #define GAME_MWMECHANICS_AIACTIVATE_H @@ -70,3 +71,46 @@ namespace MWMechanics }; } #endif // GAME_MWMECHANICS_AIACTIVATE_H +======= +#ifndef GAME_MWMECHANICS_AIACTIVATE_H +#define GAME_MWMECHANICS_AIACTIVATE_H + +#include "typedaipackage.hpp" +#include +#include +#include + +namespace ESM +{ + namespace AiSequence + { + struct AiActivate; + } +} + +namespace MWMechanics +{ + /// \brief Causes actor to walk to activatable object and activate it + /** Will activate when close to object **/ + class AiActivate final : public TypedAiPackage + { + public: + /// Constructor + /** \param objectId Reference to object to activate **/ + explicit AiActivate(const ESM::RefId& objectId, bool repeat); + + explicit AiActivate(const ESM::AiSequence::AiActivate* activate); + + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; + + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Activate; } + + void writeState(ESM::AiSequence::AiSequence& sequence) const override; + + private: + const ESM::RefId mObjectId; + }; +} +#endif // GAME_MWMECHANICS_AIACTIVATE_H +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 6a59ae2bf..1f229f61b 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -2,56 +2,58 @@ #include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" +#include "actorutil.hpp" #include "creaturestats.hpp" #include "movement.hpp" -#include "actorutil.hpp" #include "steering.hpp" static const int MAX_DIRECTIONS = 4; MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr) -: mDuration(1), mDoorPtr(doorPtr), mDirection(0) + : mDuration(1) + , mDoorPtr(doorPtr) + , mDirection(0) { - } -bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) +bool MWMechanics::AiAvoidDoor::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { ESM::Position pos = actor.getRefData().getPosition(); - if(mDuration == 1) //If it just started, get the actor position as the stuck detection thing + if (mDuration == 1) // If it just started, get the actor position as the stuck detection thing mLastPos = pos.asVec3(); - mDuration -= duration; //Update timer + mDuration -= duration; // Update timer if (mDuration < 0) { if (isStuck(pos.asVec3())) { adjustDirection(); - mDuration = 1; //reset timer + mDuration = 1; // reset timer } else return true; // We have tried backing up for more than one second, we've probably cleared it } if (mDoorPtr.getClass().getDoorState(mDoorPtr) == MWWorld::DoorState::Idle) - return true; //Door is no longer opening + return true; // Door is no longer opening - ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door + ESM::Position tPos = mDoorPtr.getRefData().getPosition(); // Position of the door float x = pos.pos[1] - tPos.pos[1]; float y = pos.pos[0] - tPos.pos[0]; actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); // Turn away from the door and move when turn completed - if (zTurn(actor, std::atan2(y,x) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) + if (zTurn(actor, std::atan2(y, x) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) actor.getClass().getMovementSettings(actor).mPosition[1] = 1; else actor.getClass().getMovementSettings(actor).mPosition[1] = 0; @@ -59,8 +61,8 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont // Make all nearby actors also avoid the door std::vector actors; - MWBase::Environment::get().getMechanicsManager()->getActorsInRange(pos.asVec3(),100,actors); - for(auto& neighbor : actors) + MWBase::Environment::get().getMechanicsManager()->getActorsInRange(pos.asVec3(), 100, actors); + for (auto& neighbor : actors) { if (neighbor == getPlayer()) continue; @@ -80,7 +82,8 @@ bool MWMechanics::AiAvoidDoor::isStuck(const osg::Vec3f& actorPos) const void MWMechanics::AiAvoidDoor::adjustDirection() { - mDirection = Misc::Rng::rollDice(MAX_DIRECTIONS); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + mDirection = Misc::Rng::rollDice(MAX_DIRECTIONS, prng); } float MWMechanics::AiAvoidDoor::getAdjustedAngle() const diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 183f429f3..8b268ef81 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -3,46 +3,43 @@ #include "typedaipackage.hpp" -#include "../mwworld/class.hpp" - -#include "pathfinding.hpp" - namespace MWMechanics { /// \brief AiPackage to have an actor avoid an opening door - /** The AI will retreat from the door until it has finished opening, walked far away from it, or one second has passed, in an attempt to avoid it - **/ + /** The AI will retreat from the door until it has finished opening, walked far away from it, or one second has + *passed, in an attempt to avoid it + **/ class AiAvoidDoor final : public TypedAiPackage { - public: - /// Avoid door until the door is fully open - explicit AiAvoidDoor(const MWWorld::ConstPtr& doorPtr); + public: + /// Avoid door until the door is fully open + explicit AiAvoidDoor(const MWWorld::ConstPtr& doorPtr); - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::AvoidDoor; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::AvoidDoor; } - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mPriority = 2; - options.mCanCancel = false; - options.mShouldCancelPreviousAi = false; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 2; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } - private: - float mDuration; - const MWWorld::ConstPtr mDoorPtr; - osg::Vec3f mLastPos; - int mDirection; + private: + float mDuration; + const MWWorld::ConstPtr mDoorPtr; + osg::Vec3f mLastPos; + int mDirection; - bool isStuck(const osg::Vec3f& actorPos) const; + bool isStuck(const osg::Vec3f& actorPos) const; - void adjustDirection(); + void adjustDirection(); - float getAdjustedAngle() const; + float getAdjustedAngle() const; }; } #endif - diff --git a/apps/openmw/mwmechanics/aibreathe.cpp b/apps/openmw/mwmechanics/aibreathe.cpp index 94e4ecd95..ab0344d86 100644 --- a/apps/openmw/mwmechanics/aibreathe.cpp +++ b/apps/openmw/mwmechanics/aibreathe.cpp @@ -1,7 +1,7 @@ #include "aibreathe.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -11,9 +11,11 @@ #include "movement.hpp" #include "steering.hpp" -bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) +bool MWMechanics::AiBreathe::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { - static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->mValue.getFloat(); + static const float fHoldBreathTime + = MWBase::Environment::get().getESMStore()->get().find("fHoldBreathTime")->mValue.getFloat(); const MWWorld::Class& actorClass = actor.getClass(); if (actorClass.isNpc()) diff --git a/apps/openmw/mwmechanics/aibreathe.hpp b/apps/openmw/mwmechanics/aibreathe.hpp index b84c0eb76..6b2bdfed8 100644 --- a/apps/openmw/mwmechanics/aibreathe.hpp +++ b/apps/openmw/mwmechanics/aibreathe.hpp @@ -9,19 +9,20 @@ namespace MWMechanics // The AI will go up if lesser than half breath left class AiBreathe final : public TypedAiPackage { - public: - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + public: + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Breathe; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Breathe; } - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mPriority = 2; - options.mCanCancel = false; - options.mShouldCancelPreviousAi = false; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 2; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } }; } #endif diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 630c04a6a..583a85c53 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -9,14 +9,13 @@ #include "../mwworld/class.hpp" #include "aicombataction.hpp" -#include "creaturestats.hpp" #include "steering.hpp" namespace MWMechanics { namespace { - float getInitialDistance(const std::string& spellId) + float getInitialDistance(const ESM::RefId& spellId) { ActionSpell action = ActionSpell(spellId); bool isRanged; @@ -25,12 +24,17 @@ namespace MWMechanics } } -MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell) - : mTargetId(targetId), mSpellId(spellId), mCasting(false), mManual(manualSpell), mDistance(getInitialDistance(spellId)) +MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell) + : mTargetId(targetId) + , mSpellId(spellId) + , mCasting(false) + , mManual(manualSpell) + , mDistance(getInitialDistance(spellId)) { } -bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& characterController, MWMechanics::AiState& state, float duration) +bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& characterController, + MWMechanics::AiState& state, float duration) { MWWorld::Ptr target; if (actor.getCellRef().getRefId() == mTargetId) @@ -41,7 +45,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac else { target = getTarget(); - if (!target) + if (target.isEmpty()) return true; if (!mManual && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, mDistance)) diff --git a/apps/openmw/mwmechanics/aicast.hpp b/apps/openmw/mwmechanics/aicast.hpp index 9758c2b94..435458cc0 100644 --- a/apps/openmw/mwmechanics/aicast.hpp +++ b/apps/openmw/mwmechanics/aicast.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_AICAST_H #include "typedaipackage.hpp" +#include namespace MWWorld { @@ -11,31 +12,33 @@ namespace MWWorld namespace MWMechanics { /// AiPackage which makes an actor to cast given spell. - class AiCast final : public TypedAiPackage { - public: - AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell=false); + class AiCast final : public TypedAiPackage + { + public: + AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell = false); - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Cast; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Cast; } - MWWorld::Ptr getTarget() const override; + MWWorld::Ptr getTarget() const override; - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mPriority = 3; - options.mCanCancel = false; - options.mShouldCancelPreviousAi = false; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 3; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } - private: - const std::string mTargetId; - const std::string mSpellId; - bool mCasting; - const bool mManual; - const float mDistance; + private: + const ESM::RefId mTargetId; + const ESM::RefId mSpellId; + bool mCasting; + const bool mManual; + const float mDistance; }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 18003e818..c832aa5b9 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -1,15 +1,16 @@ #include "aicombat.hpp" -#include #include +#include -#include +#include #include +#include #include -#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -26,30 +27,35 @@ */ #include "../mwphysics/collisiontype.hpp" +======= +#include "../mwphysics/raycasting.hpp" +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" -#include "pathgrid.hpp" -#include "creaturestats.hpp" -#include "steering.hpp" -#include "movement.hpp" -#include "character.hpp" -#include "aicombataction.hpp" #include "actorutil.hpp" +#include "aicombataction.hpp" +#include "character.hpp" +#include "creaturestats.hpp" +#include "movement.hpp" +#include "pathgrid.hpp" +#include "steering.hpp" +#include "weapontype.hpp" namespace { - //chooses an attack depending on probability to avoid uniformity - std::string chooseBestAttack(const ESM::Weapon* weapon); + // chooses an attack depending on probability to avoid uniformity + std::string_view chooseBestAttack(const ESM::Weapon* weapon); - osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, - float duration, int weapType, float strength); + osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, + const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); } namespace MWMechanics @@ -59,15 +65,12 @@ namespace MWMechanics mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); } - AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) + AiCombat::AiCombat(const ESM::AiSequence::AiCombat* combat) { mTargetActorId = combat->mTargetActorId; } - void AiCombat::init() - { - - } + void AiCombat::init() {} /* * Current AiCombat movement states (as of 0.29.0), ignoring the details of the @@ -116,22 +119,24 @@ namespace MWMechanics * whether the target was hit, etc. */ - bool AiCombat::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + bool AiCombat::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // get or create temporary storage AiCombatStorage& storage = state.get(); - - //General description + + // General description if (actor.getClass().getCreatureStats(actor).isDead()) return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); if (target.isEmpty()) - return false; + return true; - if(!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered - // with the MechanicsManager - || target.getClass().getCreatureStats(target).isDead()) + if (!target.getRefData().getCount() + || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently + // registered with the MechanicsManager + || target.getClass().getCreatureStats(target).isDead()) return true; if (actor == target) // This should never happen. @@ -141,17 +146,20 @@ namespace MWMechanics { if (storage.mCurrentAction.get()) // need to wait to init action with its attack range { - //Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame. + // Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame. updateLOS(actor, target, duration, storage); - const float targetReachedTolerance = storage.mLOS && !storage.mUseCustomDestination - ? storage.mAttackRange : 0.0f; + const float targetReachedTolerance + = storage.mLOS && !storage.mUseCustomDestination ? storage.mAttackRange : 0.0f; const osg::Vec3f destination = storage.mUseCustomDestination - ? storage.mCustomDestination : target.getRefData().getPosition().asVec3(); + ? storage.mCustomDestination + : target.getRefData().getPosition().asVec3(); const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance); - if (is_target_reached) storage.mReadyToAttack = true; + if (is_target_reached) + storage.mReadyToAttack = true; } storage.updateCombatMove(duration); +<<<<<<< HEAD if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); storage.updateAttack(characterController); @@ -171,6 +179,14 @@ namespace MWMechanics /* End of tes3mp addition */ +======= + storage.mRotateMove = false; + if (storage.mReadyToAttack) + updateActorsMovement(actor, duration, storage); + if (storage.mRotateMove) + return false; + storage.updateAttack(actor, characterController); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } else { @@ -184,11 +200,12 @@ namespace MWMechanics return attack(actor, target, storage, characterController); } - bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) + bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, + CharacterController& characterController) { const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); - if(!currentCell || cellChange) + if (!currentCell || cellChange) { currentCell = actor.getCell(); } @@ -215,6 +232,7 @@ namespace MWMechanics if (!canFight(actor, target)) { storage.stopAttack(); +<<<<<<< HEAD characterController.setAttackingOrSpell(false); /* @@ -234,13 +252,21 @@ namespace MWMechanics End of tes3mp addition */ +======= + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 storage.mActionCooldown = 0.f; // Continue combat if target is player or player follower/escorter and an attack has been attempted - const std::list& playerFollowersAndEscorters = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer()); - bool targetSidesWithPlayer = (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target) != playerFollowersAndEscorters.end()); + const auto& playerFollowersAndEscorters + = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer()); + bool targetSidesWithPlayer + = (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target) + != playerFollowersAndEscorters.end()); if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer) - && ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId()) - || (target.getClass().getCreatureStats(target).getHitAttemptActorId() == actor.getClass().getCreatureStats(actor).getActorId()))) + && ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId() + == target.getClass().getCreatureStats(target).getActorId()) + || (target.getClass().getCreatureStats(target).getHitAttemptActorId() + == actor.getClass().getCreatureStats(actor).getActorId()))) forceFlee = true; else // Otherwise end combat return true; @@ -250,7 +276,7 @@ namespace MWMechanics actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); float& actionCooldown = storage.mActionCooldown; - std::shared_ptr& currentAction = storage.mCurrentAction; + std::unique_ptr& currentAction = storage.mCurrentAction; if (!forceFlee) { @@ -265,7 +291,7 @@ namespace MWMechanics } else { - currentAction.reset(new ActionFlee()); + currentAction = std::make_unique(); actionCooldown = currentAction->getActionCooldown(); } @@ -277,7 +303,7 @@ namespace MWMechanics if (currentAction->isFleeing()) { storage.startFleeing(); - MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("flee")); return false; } else @@ -285,7 +311,7 @@ namespace MWMechanics } bool isRangedCombat = false; - float &rangeAttack = storage.mAttackRange; + float& rangeAttack = storage.mAttackRange; rangeAttack = currentAction->getCombatRange(isRangedCombat); @@ -303,7 +329,8 @@ namespace MWMechanics if (isRangedCombat) { // rotate actor taking into account target movement direction and projectile speed - osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); + osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, + (weapon ? weapon->mData.mType : 0), storage.mStrength); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); @@ -312,7 +339,8 @@ namespace MWMechanics { osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); - storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated + storage.mMovement.mRotation[2] = getZAngleToDir( + (vTargetPos - vActorPos)); // using vAimDir results in spastic movements since the head is animated } storage.mLastTargetPos = vTargetPos; @@ -333,23 +361,27 @@ namespace MWMechanics { const MWBase::World* world = MWBase::Environment::get().getWorld(); // Try to build path to the target. - const auto halfExtents = world->getPathfindingHalfExtents(actor); + const auto agentBounds = world->getPathfindingAgentBounds(actor); const auto navigatorFlags = getNavigatorFlags(actor); const auto areaCosts = getAreaCosts(actor); - const auto pathGridGraph = getPathGridGraph(actor.getCell()); - mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts); + const ESM::Pathgrid* pathgrid = world->getStore().get().search(*actor.getCell()->getCell()); + const auto& pathGridGraph = getPathGridGraph(pathgrid); + mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds, + navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full); if (!mPathFinder.isPathConstructed()) { // If there is no path, try to find a point on a line from the actor position to target projected // on navmesh to attack the target from there. const auto navigator = world->getNavigator(); - const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags); + const auto hit + = DetourNavigator::raycast(*navigator, agentBounds, vActorPos, vTargetPos, navigatorFlags); if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack) { // If the point is close enough, try to find a path to that point. - mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts); + mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, agentBounds, + navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full); if (mPathFinder.isPathConstructed()) { // If path to that point is found use it as custom destination. @@ -362,11 +394,11 @@ namespace MWMechanics { storage.mUseCustomDestination = false; storage.stopAttack(); - characterController.setAttackingOrSpell(false); - currentAction.reset(new ActionFlee()); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); + currentAction = std::make_unique(); actionCooldown = currentAction->getActionCooldown(); storage.startFleeing(); - MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("flee")); } } else @@ -378,7 +410,8 @@ namespace MWMechanics return false; } - void MWMechanics::AiCombat::updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) + void MWMechanics::AiCombat::updateLOS( + const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) { static const float LOS_UPDATE_DURATION = 0.5f; if (storage.mUpdateLOSTimer <= 0.f) @@ -390,7 +423,8 @@ namespace MWMechanics storage.mUpdateLOSTimer -= duration; } - void MWMechanics::AiCombat::updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) + void MWMechanics::AiCombat::updateFleeing( + const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) { static const float BLIND_RUN_DURATION = 1.0f; @@ -403,84 +437,97 @@ namespace MWMechanics return; case AiCombatStorage::FleeState_Idle: + { + float triggerDist = getMaxAttackDistance(target); + const MWWorld::Cell* cellVariant = storage.mCell->getCell(); + if (storage.mLOS && (triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist)) { - float triggerDist = getMaxAttackDistance(target); + const ESM::Pathgrid* pathgrid + = MWBase::Environment::get().getESMStore()->get().search(*cellVariant); - if (storage.mLOS && - (triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist)) + bool runFallback = true; + + if (pathgrid != nullptr && !pathgrid->mPoints.empty() + && !actor.getClass().isPureWaterCreature(actor)) { - const ESM::Pathgrid* pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*storage.mCell->getCell()); + ESM::Pathgrid::PointList points; + Misc::CoordinateConverter coords(*storage.mCell->getCell()); - bool runFallback = true; + osg::Vec3f localPos = actor.getRefData().getPosition().asVec3(); + coords.toLocal(localPos); +<<<<<<< HEAD if (pathgrid != nullptr && !pathgrid->mPoints.empty() && !actor.getClass().isPureWaterCreature(actor)) +======= + int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos); + for (int i = 0; i < static_cast(pathgrid->mPoints.size()); i++) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - ESM::Pathgrid::PointList points; - Misc::CoordinateConverter coords(storage.mCell->getCell()); - - osg::Vec3f localPos = actor.getRefData().getPosition().asVec3(); - coords.toLocal(localPos); - - int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos); - for (int i = 0; i < static_cast(pathgrid->mPoints.size()); i++) + if (i != closestPointIndex + && getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i)) { - if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i)) - { - points.push_back(pathgrid->mPoints[static_cast(i)]); - } - } - - if (!points.empty()) - { - ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size())]; - coords.toWorld(dest); - - state = AiCombatStorage::FleeState_RunToDestination; - storage.mFleeDest = ESM::Pathgrid::Point(dest.mX, dest.mY, dest.mZ); - - runFallback = false; + points.push_back(pathgrid->mPoints[static_cast(i)]); } } - if (runFallback) + if (!points.empty()) { - state = AiCombatStorage::FleeState_RunBlindly; - storage.mFleeBlindRunTimer = 0.0f; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size(), prng)]; + coords.toWorld(dest); + + state = AiCombatStorage::FleeState_RunToDestination; + storage.mFleeDest = ESM::Pathgrid::Point(dest.mX, dest.mY, dest.mZ); + + runFallback = false; } } + + if (runFallback) + { + state = AiCombatStorage::FleeState_RunBlindly; + storage.mFleeBlindRunTimer = 0.0f; + } } - break; + } + break; case AiCombatStorage::FleeState_RunBlindly: + { + // timer to prevent twitchy movement that can be observed in vanilla MW + if (storage.mFleeBlindRunTimer < BLIND_RUN_DURATION) { - // timer to prevent twitchy movement that can be observed in vanilla MW - if (storage.mFleeBlindRunTimer < BLIND_RUN_DURATION) - { - storage.mFleeBlindRunTimer += duration; + storage.mFleeBlindRunTimer += duration; - storage.mMovement.mRotation[0] = -actor.getRefData().getPosition().rot[0]; - storage.mMovement.mRotation[2] = osg::PI + getZAngleToDir(target.getRefData().getPosition().asVec3()-actor.getRefData().getPosition().asVec3()); - storage.mMovement.mPosition[1] = 1; - updateActorsMovement(actor, duration, storage); - } - else - state = AiCombatStorage::FleeState_Idle; + storage.mMovement.mRotation[0] = -actor.getRefData().getPosition().rot[0]; + storage.mMovement.mRotation[2] = osg::PI + + getZAngleToDir( + target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()); + storage.mMovement.mPosition[1] = 1; + updateActorsMovement(actor, duration, storage); } - break; + else + state = AiCombatStorage::FleeState_Idle; + } + break; case AiCombatStorage::FleeState_RunToDestination: - { - static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fFleeDistance")->mValue.getFloat(); + { + static const float fFleeDistance = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fFleeDistance") + ->mValue.getFloat(); - float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); - if ((dist > fFleeDistance && !storage.mLOS) - || pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration)) - { - state = AiCombatStorage::FleeState_Idle; - } + float dist + = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); + if ((dist > fFleeDistance && !storage.mLOS) + || pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration)) + { + state = AiCombatStorage::FleeState_Idle; } - break; + } + break; }; } @@ -500,35 +547,69 @@ namespace MWMechanics rotateActorOnAxis(actor, 0, actorMovementSettings, storage); } - void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, - MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage) + void AiCombat::rotateActorOnAxis( + const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage) { actorMovementSettings.mRotation[axis] = 0; bool isRangedCombat = false; storage.mCurrentAction->getCombatRange(isRangedCombat); float eps = isRangedCombat ? osg::DegreesToRadians(0.5) : osg::DegreesToRadians(3.f); float targetAngleRadians = storage.mMovement.mRotation[axis]; - smoothTurn(actor, targetAngleRadians, axis, eps); + storage.mRotateMove = !smoothTurn(actor, targetAngleRadians, axis, eps); } MWWorld::Ptr AiCombat::getTarget() const { - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + if (mCachedTarget.isEmpty() || mCachedTarget.getRefData().isDeleted() + || !mCachedTarget.getRefData().isEnabled()) + { + mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + } + return mCachedTarget; } - void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const + void AiCombat::writeState(ESM::AiSequence::AiSequence& sequence) const { - std::unique_ptr combat(new ESM::AiSequence::AiCombat()); + auto combat = std::make_unique(); combat->mTargetActorId = mTargetActorId; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Combat; - package.mPackage = combat.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(combat); + sequence.mPackages.push_back(std::move(package)); } - void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + AiCombatStorage::AiCombatStorage() + : mAttackCooldown(0.0f) + , mReaction(MWBase::Environment::get().getWorld()->getPrng()) + , mTimerCombatMove(0.0f) + , mReadyToAttack(false) + , mAttack(false) + , mAttackRange(0.0f) + , mCombatMove(false) + , mRotateMove(false) + , mLastTargetPos(0, 0, 0) + , mCell(nullptr) + , mCurrentAction() + , mActionCooldown(0.0f) + , mStrength() + , mForceNoShortcut(false) + , mShortcutFailPos() + , mMovement() + , mFleeState(FleeState_None) + , mLOS(false) + , mUpdateLOSTimer(0.0f) + , mFleeBlindRunTimer(0.0f) + , mUseCustomDestination(false) + , mCustomDestination() { + } + + void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, + const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + // get the range of the target's weapon MWWorld::Ptr targetWeapon = MWWorld::Ptr(); const MWWorld::Class& targetClass = target.getClass(); @@ -543,27 +624,52 @@ namespace MWMechanics bool targetUsesRanged = false; float rangeAttackOfTarget = ActionWeapon(targetWeapon).getCombatRange(targetUsesRanged); - - if (mMovement.mPosition[0] || mMovement.mPosition[1]) + + if (mMovement.mPosition[0]) { - mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); + mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng); mCombatMove = true; } - else if (isDistantCombat) + // dodge movements (for NPCs and bipedal creatures) + // Note: do not use for ranged combat yet since in couple with back up behaviour can move actor out of cliff + else if (actor.getClass().isBipedal(actor) && !isDistantCombat) + { + float moveDuration = 0; + float angleToTarget + = Misc::normalizeAngle(mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2]); + // Apply a big side step if enemy tries to get around and come from behind. + // Otherwise apply a random side step (kind of dodging) with some probability + // if actor is within range of target's weapon. + if (std::abs(angleToTarget) > osg::PI / 4) + moveDuration = 0.2f; + else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability(prng) < 0.25) + moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng); + if (moveDuration > 0) + { + mMovement.mPosition[0] = Misc::Rng::rollProbability(prng) < 0.5 ? 1.0f : -1.0f; // to the left/right + mTimerCombatMove = moveDuration; + mCombatMove = true; + } + } + + mMovement.mPosition[1] = 0; + if (isDistantCombat) { // Backing up behaviour // Actor backs up slightly further away than opponent's weapon range - // (in vanilla - only as far as oponent's weapon range), + // (in vanilla - only as far as opponent's weapon range), // or not at all if opponent is using a ranged weapon - if (targetUsesRanged || distToTarget > rangeAttackOfTarget*1.5) // Don't back up if the target is wielding ranged weapon + if (targetUsesRanged + || distToTarget > rangeAttackOfTarget * 1.5) // Don't back up if the target is wielding ranged weapon return; // actor should not back up into water if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.5f)) return; - int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; + int mask + = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; // Actor can not back up if there is no free space behind // Currently we take the 35% of actor's height from the ground as vector height. @@ -571,10 +677,11 @@ namespace MWMechanics osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); osg::Vec3f pos = actor.getRefData().getPosition().asVec3(); osg::Vec3f source = pos + osg::Vec3f(0, 0, 0.75f * halfExtents.z()); - osg::Vec3f fallbackDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,-1,0); + osg::Vec3f fallbackDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0, -1, 0); osg::Vec3f destination = source + fallbackDirection * (halfExtents.y() + 16); - bool isObstacleDetected = MWBase::Environment::get().getWorld()->castRay(source.x(), source.y(), source.z(), destination.x(), destination.y(), destination.z(), mask); + const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + bool isObstacleDetected = rayCasting->castRay(source, destination, mask).mHit; if (isObstacleDetected) return; @@ -583,32 +690,12 @@ namespace MWMechanics // If we did not hit anything, there is a cliff behind actor. source = pos + osg::Vec3f(0, 0, 0.75f * halfExtents.z()) + fallbackDirection * (halfExtents.y() + 96); destination = source - osg::Vec3f(0, 0, 0.75f * halfExtents.z() + 96); - bool isCliffDetected = !MWBase::Environment::get().getWorld()->castRay(source.x(), source.y(), source.z(), destination.x(), destination.y(), destination.z(), mask); + bool isCliffDetected = !rayCasting->castRay(source, destination, mask).mHit; if (isCliffDetected) return; mMovement.mPosition[1] = -1; } - // dodge movements (for NPCs and bipedal creatures) - // Note: do not use for ranged combat yet since in couple with back up behaviour can move actor out of cliff - else if (actor.getClass().isBipedal(actor)) - { - float moveDuration = 0; - float angleToTarget = Misc::normalizeAngle(mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2]); - // Apply a big side step if enemy tries to get around and come from behind. - // Otherwise apply a random side step (kind of dodging) with some probability - // if actor is within range of target's weapon. - if (std::abs(angleToTarget) > osg::PI / 4) - moveDuration = 0.2f; - else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) - moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); - if (moveDuration > 0) - { - mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right - mTimerCombatMove = moveDuration; - mCombatMove = true; - } - } } void AiCombatStorage::updateCombatMove(float duration) @@ -626,11 +713,11 @@ namespace MWMechanics void AiCombatStorage::stopCombatMove() { mTimerCombatMove = 0; - mMovement.mPosition[1] = mMovement.mPosition[0] = 0; + mMovement.mPosition[0] = 0; mCombatMove = false; } - void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, + void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, const ESM::Weapon* weapon, bool distantCombat) { if (mReadyToAttack && characterController.readyToStartAttack()) @@ -638,11 +725,12 @@ namespace MWMechanics if (mAttackCooldown <= 0) { mAttack = true; // attack starts just now - characterController.setAttackingOrSpell(true); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(true); if (!distantCombat) characterController.setAIAttackType(chooseBestAttack(weapon)); +<<<<<<< HEAD /* Start of tes3mp addition @@ -668,8 +756,12 @@ namespace MWMechanics */ mStrength = Misc::Rng::rollClosedProbability(); +======= + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + mStrength = Misc::Rng::rollClosedProbability(prng); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); float baseDelay = store.get().find("fCombatDelayCreature")->mValue.getFloat(); if (actor.getClass().isNpc()) @@ -678,25 +770,28 @@ namespace MWMechanics } // Say a provoking combat phrase - const int iVoiceAttackOdds = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99() < iVoiceAttackOdds) + const int iVoiceAttackOdds + = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); + if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("attack")); } - mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); + mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9); } else mAttackCooldown -= AI_REACTION_TIME; } } - void AiCombatStorage::updateAttack(CharacterController& characterController) + void AiCombatStorage::updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController) { - if (mAttack && (characterController.getAttackStrength() >= mStrength || characterController.readyToPrepareAttack())) + if (mAttack) { - mAttack = false; + float attackStrength = characterController.calculateWindUp(); + mAttack + = !characterController.readyToPrepareAttack() && attackStrength < mStrength && attackStrength != -1.f; } - characterController.setAttackingOrSpell(mAttack); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); } void AiCombatStorage::stopAttack() @@ -729,98 +824,95 @@ namespace MWMechanics } } - namespace { -std::string chooseBestAttack(const ESM::Weapon* weapon) -{ - std::string attackType; - - if (weapon != nullptr) + std::string_view chooseBestAttack(const ESM::Weapon* weapon) { - //the more damage attackType deals the more probability it has - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + if (weapon != nullptr) + { + // the more damage attackType deals the more probability it has + int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2; + int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2; + int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1]) / 2; - float roll = Misc::Rng::rollClosedProbability() * (slash + chop + thrust); - if(roll <= slash) - attackType = "slash"; - else if(roll <= (slash + thrust)) - attackType = "thrust"; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + float roll = Misc::Rng::rollClosedProbability(prng) * (slash + chop + thrust); + if (roll <= slash) + return "slash"; + else if (roll <= (slash + thrust)) + return "thrust"; + else + return "chop"; + } + return MWMechanics::CharacterController::getRandomAttackType(); + } + + osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, + const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength) + { + float projSpeed; + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + + // get projectile speed (depending on weapon type) + if (MWMechanics::getWeaponType(weapType)->mWeaponClass == ESM::WeaponType::Thrown) + { + static float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); + static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); + + projSpeed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength; + } + else if (weapType != 0) + { + static float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->mValue.getFloat(); + static float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); + + projSpeed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength; + } + else // weapType is 0 ==> it's a target spell projectile + { + projSpeed = gmst.find("fTargetSpellMaxSpeed")->mValue.getFloat(); + } + + // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be + // the same + + osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); + osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true); + float distToTarget = vDirToTarget.length(); + + osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; + vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now + + osg::Vec3f vPerpToDir = vDirToTarget ^ osg::Vec3f(0, 0, 1); // cross product + + vPerpToDir.normalize(); + osg::Vec3f vDirToTargetNormalized = vDirToTarget; + vDirToTargetNormalized.normalize(); + + // dot product + float velPerp = vTargetMoveDir * vPerpToDir; + float velDir = vTargetMoveDir * vDirToTargetNormalized; + + // time to collision between target and projectile + float t_collision; + + float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp; + if (projVelDirSquared > 0) + { + osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir; + vTargetMoveDirNormalized.normalize(); + + float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product + projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); + + t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir); + } else - attackType = "chop"; + t_collision = 0; // speed of projectile is not enough to reach moving target + + return vDirToTarget + vTargetMoveDir * t_collision; } - else - MWMechanics::CharacterController::setAttackTypeRandomly(attackType); - - return attackType; -} - -osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, - float duration, int weapType, float strength) -{ - float projSpeed; - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - - // get projectile speed (depending on weapon type) - if (MWMechanics::getWeaponType(weapType)->mWeaponClass == ESM::WeaponType::Thrown) - { - static float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); - static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); - - projSpeed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength; - } - else if (weapType != 0) - { - static float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->mValue.getFloat(); - static float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); - - projSpeed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength; - } - else // weapType is 0 ==> it's a target spell projectile - { - projSpeed = gmst.find("fTargetSpellMaxSpeed")->mValue.getFloat(); - } - - // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same - - osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); - osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true); - float distToTarget = vDirToTarget.length(); - - osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; - vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now - - osg::Vec3f vPerpToDir = vDirToTarget ^ osg::Vec3f(0,0,1); // cross product - - vPerpToDir.normalize(); - osg::Vec3f vDirToTargetNormalized = vDirToTarget; - vDirToTargetNormalized.normalize(); - - // dot product - float velPerp = vTargetMoveDir * vPerpToDir; - float velDir = vTargetMoveDir * vDirToTargetNormalized; - - // time to collision between target and projectile - float t_collision; - - float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp; - if (projVelDirSquared > 0) - { - osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir; - vTargetMoveDirNormalized.normalize(); - - float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product - projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); - - t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir); - } - else - t_collision = 0; // speed of projectile is not enough to reach moving target - - return vDirToTarget + vTargetMoveDir * t_collision; -} } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 5425f1af0..0ae40e48e 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -1,15 +1,13 @@ #ifndef GAME_MWMECHANICS_AICOMBAT_H #define GAME_MWMECHANICS_AICOMBAT_H +#include "aitemporarybase.hpp" #include "typedaipackage.hpp" #include "../mwworld/cellstore.hpp" // for Doors -#include "../mwbase/world.hpp" - -#include "pathfinding.hpp" -#include "movement.hpp" #include "aitimer.hpp" +#include "movement.hpp" namespace ESM { @@ -33,9 +31,10 @@ namespace MWMechanics bool mAttack; float mAttackRange; bool mCombatMove; + bool mRotateMove; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; - std::shared_ptr mCurrentAction; + std::unique_ptr mCurrentAction; float mActionCooldown; float mStrength; bool mForceNoShortcut; @@ -58,35 +57,15 @@ namespace MWMechanics bool mUseCustomDestination; osg::Vec3f mCustomDestination; - AiCombatStorage(): - mAttackCooldown(0.0f), - mTimerCombatMove(0.0f), - mReadyToAttack(false), - mAttack(false), - mAttackRange(0.0f), - mCombatMove(false), - mLastTargetPos(0,0,0), - mCell(nullptr), - mCurrentAction(), - mActionCooldown(0.0f), - mStrength(), - mForceNoShortcut(false), - mShortcutFailPos(), - mMovement(), - mFleeState(FleeState_None), - mLOS(false), - mUpdateLOSTimer(0.0f), - mFleeBlindRunTimer(0.0f), - mUseCustomDestination(false), - mCustomDestination() - {} + AiCombatStorage(); - void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, + const MWWorld::Ptr& target); void updateCombatMove(float duration); void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, const ESM::Weapon* weapon, bool distantCombat); - void updateAttack(CharacterController& characterController); + void updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController); void stopAttack(); void startFleeing(); @@ -97,48 +76,50 @@ namespace MWMechanics /// \brief Causes the actor to fight another actor class AiCombat final : public TypedAiPackage { - public: - ///Constructor - /** \param actor Actor to fight **/ - explicit AiCombat(const MWWorld::Ptr& actor); + public: + /// Constructor + /** \param actor Actor to fight **/ + explicit AiCombat(const MWWorld::Ptr& actor); - explicit AiCombat (const ESM::AiSequence::AiCombat* combat); + explicit AiCombat(const ESM::AiSequence::AiCombat* combat); - void init(); + void init(); - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Combat; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Combat; } - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mPriority = 1; - options.mCanCancel = false; - options.mShouldCancelPreviousAi = false; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 1; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } - ///Returns target ID - MWWorld::Ptr getTarget() const override; + /// Returns target ID + MWWorld::Ptr getTarget() const override; - void writeState(ESM::AiSequence::AiSequence &sequence) const override; + void writeState(ESM::AiSequence::AiSequence& sequence) const override; - private: - /// Returns true if combat should end - bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); + private: + /// Returns true if combat should end + bool attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, + CharacterController& characterController); - void updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); + void updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); - void updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); + void updateFleeing( + const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); - /// Transfer desired movement (from AiCombatStorage) to Actor - void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); - void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, - MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage); + /// Transfer desired movement (from AiCombatStorage) to Actor + void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); + void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, + AiCombatStorage& storage); }; - - + } #endif diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index c26454aab..251a7861b 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -1,30 +1,39 @@ #include "aicombataction.hpp" -#include -#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/actionequip.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/actionequip.hpp" -#include "../mwworld/cellstore.hpp" -#include "npcstats.hpp" +#include "actorutil.hpp" #include "combat.hpp" -#include "weaponpriority.hpp" +#include "npcstats.hpp" #include "spellpriority.hpp" +#include "weaponpriority.hpp" #include "weapontype.hpp" namespace MWMechanics { float suggestCombatRange(int rangeTypes) { - static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->mValue.getFloat(); - static float fHandToHandReach = MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->mValue.getFloat(); + static const float fCombatDistance = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fCombatDistance") + ->mValue.getFloat(); + static float fHandToHandReach = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fHandToHandReach") + ->mValue.getFloat(); // This distance is a possible distance of melee attack static float distance = fCombatDistance * std::max(2.f, fHandToHandReach); @@ -37,39 +46,40 @@ namespace MWMechanics return distance * 4; } - void ActionSpell::prepare(const MWWorld::Ptr &actor) + void ActionSpell::prepare(const MWWorld::Ptr& actor) { actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(mSpellId); - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Spell); if (actor.getClass().hasInventoryStore(actor)) { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); inv.setSelectedEnchantItem(inv.end()); } - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(mSpellId); MWBase::Environment::get().getWorld()->preloadEffects(&spell->mEffects); } - float ActionSpell::getCombatRange (bool& isRanged) const + float ActionSpell::getCombatRange(bool& isRanged) const { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(mSpellId); int types = getRangeTypes(spell->mEffects); isRanged = (types & RangeTypes::Target) | (types & RangeTypes::Self); return suggestCombatRange(types); } - void ActionEnchantedItem::prepare(const MWWorld::Ptr &actor) + void ActionEnchantedItem::prepare(const MWWorld::Ptr& actor) { - actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(std::string()); + actor.getClass().getCreatureStats(actor).getSpells().setSelectedSpell(ESM::RefId()); actor.getClass().getInventoryStore(actor).setSelectedEnchantItem(mItem); - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Spell); } float ActionEnchantedItem::getCombatRange(bool& isRanged) const { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); + const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().find( + mItem->getClass().getEnchantment(*mItem)); int types = getRangeTypes(enchantment->mEffects); isRanged = (types & RangeTypes::Target) | (types & RangeTypes::Self); @@ -83,18 +93,17 @@ namespace MWMechanics return 600.f; } - void ActionPotion::prepare(const MWWorld::Ptr &actor) + void ActionPotion::prepare(const MWWorld::Ptr& actor) { - actor.getClass().apply(actor, mPotion.getCellRef().getRefId(), actor); - actor.getClass().getContainerStore(actor).remove(mPotion, 1, actor); + actor.getClass().consume(mPotion, actor); } - void ActionWeapon::prepare(const MWWorld::Ptr &actor) + void ActionWeapon::prepare(const MWWorld::Ptr& actor) { if (actor.getClass().hasInventoryStore(actor)) { if (mWeapon.isEmpty()) - actor.getClass().getInventoryStore(actor).unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); + actor.getClass().getInventoryStore(actor).unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight); else { MWWorld::ActionEquip equip(mWeapon); @@ -107,20 +116,31 @@ namespace MWMechanics equip.execute(actor); } } - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Weapon); + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Weapon); } float ActionWeapon::getCombatRange(bool& isRanged) const { isRanged = false; - static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->mValue.getFloat(); - static const float fProjectileMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->mValue.getFloat(); + static const float fCombatDistance = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fCombatDistance") + ->mValue.getFloat(); + static const float fProjectileMaxSpeed = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fProjectileMaxSpeed") + ->mValue.getFloat(); if (mWeapon.isEmpty()) { - static float fHandToHandReach = - MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->mValue.getFloat(); + static float fHandToHandReach = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fHandToHandReach") + ->mValue.getFloat(); return fHandToHandReach * fCombatDistance; } @@ -141,14 +161,14 @@ namespace MWMechanics return mWeapon.get()->mBase; } - std::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) + std::unique_ptr prepareNextAction(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); float bestActionRating = 0.f; float antiFleeRating = 0.f; // Default to hand-to-hand combat - std::shared_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); + std::unique_ptr bestAction = std::make_unique(MWWorld::Ptr()); if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { bestAction->prepare(actor); @@ -165,7 +185,7 @@ namespace MWMechanics if (rating > bestActionRating) { bestActionRating = rating; - bestAction.reset(new ActionPotion(*it)); + bestAction = std::make_unique(*it); antiFleeRating = std::numeric_limits::max(); } } @@ -176,7 +196,7 @@ namespace MWMechanics if (rating > bestActionRating) { bestActionRating = rating; - bestAction.reset(new ActionEnchantedItem(it)); + bestAction = std::make_unique(it); antiFleeRating = std::numeric_limits::max(); } } @@ -202,25 +222,25 @@ namespace MWMechanics ammo = bestBolt; bestActionRating = rating; - bestAction.reset(new ActionWeapon(*it, ammo)); + bestAction = std::make_unique(*it, ammo); antiFleeRating = vanillaRateWeaponAndAmmo(*it, ammo, actor, enemy); } } } - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - float rating = rateSpell(it->first, actor, enemy); + float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; - bestAction.reset(new ActionSpell(it->first->mId)); - antiFleeRating = vanillaRateSpell(it->first, actor, enemy); + bestAction = std::make_unique(spell->mId); + antiFleeRating = vanillaRateSpell(spell, actor, enemy); } } if (makeFleeDecision(actor, enemy, antiFleeRating)) - bestAction.reset(new ActionFlee()); + bestAction = std::make_unique(); if (bestAction.get()) bestAction->prepare(actor); @@ -228,7 +248,7 @@ namespace MWMechanics return bestAction; } - float getBestActionRating(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) + float getBestActionRating(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); @@ -266,9 +286,9 @@ namespace MWMechanics } } - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - float rating = rateSpell(it->first, actor, enemy); + float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; @@ -278,7 +298,6 @@ namespace MWMechanics return bestActionRating; } - float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool minusZDist) { osg::Vec3f actor1Pos = actor1.getRefData().getPosition().asVec3(); @@ -289,17 +308,17 @@ namespace MWMechanics if (minusZDist) dist -= std::abs(actor1Pos.z() - actor2Pos.z()); - return (dist - - MWBase::Environment::get().getWorld()->getHalfExtents(actor1).y() - - MWBase::Environment::get().getWorld()->getHalfExtents(actor2).y()); + return (dist - MWBase::Environment::get().getWorld()->getHalfExtents(actor1).y() + - MWBase::Environment::get().getWorld()->getHalfExtents(actor2).y()); } float getMaxAttackDistance(const MWWorld::Ptr& actor) { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); - std::string selectedSpellId = stats.getSpells().getSelectedSpell(); + const ESM::RefId& selectedSpellId = stats.getSpells().getSelectedSpell(); MWWorld::Ptr selectedEnchItem; MWWorld::Ptr activeWeapon, activeAmmo; @@ -325,18 +344,21 @@ namespace MWMechanics static const float fHandToHandReach = gmst.find("fHandToHandReach")->mValue.getFloat(); dist = fHandToHandReach; } - else if (stats.getDrawState() == MWMechanics::DrawState_Spell) + else if (stats.getDrawState() == MWMechanics::DrawState::Spell) { dist = 1.0f; if (!selectedSpellId.empty()) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(selectedSpellId); - for (std::vector::const_iterator effectIt = - spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + const ESM::Spell* spell + = MWBase::Environment::get().getESMStore()->get().find(selectedSpellId); + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); + effectIt != spell->mEffects.mList.end(); ++effectIt) { if (effectIt->mRange == ESM::RT_Target) { - const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find( + effectIt->mEffectID); dist = effect->mData.mSpeed; break; } @@ -344,16 +366,19 @@ namespace MWMechanics } else if (!selectedEnchItem.isEmpty()) { - std::string enchId = selectedEnchItem.getClass().getEnchantment(selectedEnchItem); + const ESM::RefId& enchId = selectedEnchItem.getClass().getEnchantment(selectedEnchItem); if (!enchId.empty()) { - const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().find(enchId); - for (std::vector::const_iterator effectIt = - ench->mEffects.mList.begin(); effectIt != ench->mEffects.mList.end(); ++effectIt) + const ESM::Enchantment* ench + = MWBase::Environment::get().getESMStore()->get().find(enchId); + for (std::vector::const_iterator effectIt = ench->mEffects.mList.begin(); + effectIt != ench->mEffects.mList.end(); ++effectIt) { if (effectIt->mRange == ESM::RT_Target) { - const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + const ESM::MagicEffect* effect + = MWBase::Environment::get().getESMStore()->get().find( + effectIt->mEffectID); dist = effect->mData.mSpeed; break; } @@ -403,7 +428,8 @@ namespace MWMechanics ESM::Position actorPos = actor.getRefData().getPosition(); ESM::Position enemyPos = enemy.getRefData().getPosition(); - if (isTargetMagicallyHidden(enemy) && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(enemy, actor)) + if (isTargetMagicallyHidden(enemy) + && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(enemy, actor)) { return false; } @@ -415,14 +441,14 @@ namespace MWMechanics } float atDist = getMaxAttackDistance(actor); - if (atDist > getDistanceMinusHalfExtents(actor, enemy) - && atDist > std::abs(actorPos.pos[2] - enemyPos.pos[2])) + if (atDist > getDistanceMinusHalfExtents(actor, enemy) && atDist > std::abs(actorPos.pos[2] - enemyPos.pos[2])) { if (MWBase::Environment::get().getWorld()->getLOS(actor, enemy)) return true; } - if (actor.getClass().isPureLandCreature(actor) && MWBase::Environment::get().getWorld()->isWalkingOnWater(enemy)) + if (actor.getClass().isPureLandCreature(actor) + && MWBase::Environment::get().getWorld()->isWalkingOnWater(enemy)) { return false; } @@ -435,14 +461,21 @@ namespace MWMechanics if (actor.getClass().isBipedal(actor) || !actor.getClass().canFly(actor)) { - if (enemy.getClass().getCreatureStats(enemy).getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0) + if (enemy.getClass() + .getCreatureStats(enemy) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Levitate) + .getMagnitude() + > 0) { float attackDistance = getMaxAttackDistance(actor); if ((attackDistance + actorPos.pos[2]) < enemyPos.pos[2]) { if (enemy.getCell()->isExterior()) { - if (attackDistance < (enemyPos.pos[2] - MWBase::Environment::get().getWorld()->getTerrainHeightAt(enemyPos.asVec3()))) + if (attackDistance < (enemyPos.pos[2] + - MWBase::Environment::get().getWorld()->getTerrainHeightAt( + enemyPos.asVec3(), enemy.getCell()->getCell()->getWorldSpace()))) return false; } } @@ -452,7 +485,12 @@ namespace MWMechanics if (!actor.getClass().canWalk(actor) && !actor.getClass().isBipedal(actor)) return true; - if (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0) + if (actor.getClass() + .getCreatureStats(actor) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Levitate) + .getMagnitude() + > 0) return true; if (MWBase::Environment::get().getWorld()->isSwimming(actor)) @@ -467,17 +505,17 @@ namespace MWMechanics float vanillaRateFlee(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); - int flee = stats.getAiSetting(CreatureStats::AI_Flee).getModified(); + const int flee = stats.getAiSetting(AiSetting::Flee).getModified(); if (flee >= 100) return flee; static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat(); static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat(); - float healthPercentage = (stats.getHealth().getModified() == 0.0f) - ? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified(); + float healthPercentage = stats.getHealth().getRatio(false); float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult; static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger(); diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index d17d5a313..9b91dfd5b 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -3,8 +3,8 @@ #include -#include "../mwworld/ptr.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/ptr.hpp" namespace MWMechanics { @@ -13,7 +13,7 @@ namespace MWMechanics public: virtual ~Action() {} virtual void prepare(const MWWorld::Ptr& actor) = 0; - virtual float getCombatRange (bool& isRanged) const = 0; + virtual float getCombatRange(bool& isRanged) const = 0; virtual float getActionCooldown() { return 0.f; } virtual const ESM::Weapon* getWeapon() const { return nullptr; } virtual bool isAttackingOrSpell() const { return true; } @@ -25,7 +25,7 @@ namespace MWMechanics public: ActionFlee() {} void prepare(const MWWorld::Ptr& actor) override {} - float getCombatRange (bool& isRanged) const override { return 0.0f; } + float getCombatRange(bool& isRanged) const override { return 0.0f; } float getActionCooldown() override { return 3.0f; } bool isAttackingOrSpell() const override { return false; } bool isFleeing() const override { return true; } @@ -34,22 +34,28 @@ namespace MWMechanics class ActionSpell : public Action { public: - ActionSpell(const std::string& spellId) : mSpellId(spellId) {} - std::string mSpellId; + ActionSpell(const ESM::RefId& spellId) + : mSpellId(spellId) + { + } + ESM::RefId mSpellId; /// Sets the given spell as selected on the actor's spell list. void prepare(const MWWorld::Ptr& actor) override; - float getCombatRange (bool& isRanged) const override; + float getCombatRange(bool& isRanged) const override; }; class ActionEnchantedItem : public Action { public: - ActionEnchantedItem(const MWWorld::ContainerStoreIterator& item) : mItem(item) {} + ActionEnchantedItem(const MWWorld::ContainerStoreIterator& item) + : mItem(item) + { + } MWWorld::ContainerStoreIterator mItem; /// Sets the given item as selected enchanted item in the actor's InventoryStore. void prepare(const MWWorld::Ptr& actor) override; - float getCombatRange (bool& isRanged) const override; + float getCombatRange(bool& isRanged) const override; /// Since this action has no animation, apply a small cool down for using it float getActionCooldown() override { return 0.75f; } @@ -58,11 +64,14 @@ namespace MWMechanics class ActionPotion : public Action { public: - ActionPotion(const MWWorld::Ptr& potion) : mPotion(potion) {} + ActionPotion(const MWWorld::Ptr& potion) + : mPotion(potion) + { + } MWWorld::Ptr mPotion; /// Drinks the given potion. void prepare(const MWWorld::Ptr& actor) override; - float getCombatRange (bool& isRanged) const override; + float getCombatRange(bool& isRanged) const override; bool isAttackingOrSpell() const override { return false; } /// Since this action has no animation, apply a small cool down for using it @@ -78,17 +87,20 @@ namespace MWMechanics public: /// \a weapon may be empty for hand-to-hand combat ActionWeapon(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo = MWWorld::Ptr()) - : mAmmunition(ammo), mWeapon(weapon) {} + : mAmmunition(ammo) + , mWeapon(weapon) + { + } /// Equips the given weapon. void prepare(const MWWorld::Ptr& actor) override; - float getCombatRange (bool& isRanged) const override; + float getCombatRange(bool& isRanged) const override; const ESM::Weapon* getWeapon() const override; }; - std::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); - float getBestActionRating(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy); + std::unique_ptr prepareNextAction(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float getBestActionRating(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); - float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool minusZDist=false); + float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool minusZDist = false); float getMaxAttackDistance(const MWWorld::Ptr& actor); bool canFight(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 75c046110..41cb5a59c 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -1,124 +1,140 @@ -#include "aiescort.hpp" - -#include -#include - -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" - -#include "creaturestats.hpp" -#include "movement.hpp" - -/* - TODO: Different behavior for AIEscort a d x y z and AIEscortCell a c d x y z. - TODO: Take account for actors being in different cells. -*/ - -namespace MWMechanics -{ - AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) - { - mTargetActorRefId = actorId; - } - - AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z) - : mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) - { - mTargetActorRefId = actorId; - } - - AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) - : mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) - // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. - // The exact value of mDuration only matters for repeating packages. - // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. - , mDuration(escort->mRemainingDuration > 0) - , mRemainingDuration(escort->mRemainingDuration) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) - { - mTargetActorRefId = escort->mTargetId; - mTargetActorId = escort->mTargetActorId; - } - - bool AiEscort::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) - { - // If AiEscort has ran for as long or longer then the duration specified - // and the duration is not infinite, the package is complete. - if (mDuration > 0) - { - mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); - if (mRemainingDuration <= 0) - { - mRemainingDuration = mDuration; - return true; - } - } - - if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace) - return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door - - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); - actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); - - const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); - const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3(); - const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3(); - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); - - if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist) - { - const osg::Vec3f dest(mX, mY, mZ); - if (pathTo(actor, dest, duration, maxHalfExtent)) //Returns true on path complete - { - mRemainingDuration = mDuration; - return true; - } - mMaxDist = maxHalfExtent + 450.0f; - } - else - { - // Stop moving if the player is too far away - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - mMaxDist = maxHalfExtent + 250.0f; - } - - return false; - } - - void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const - { - std::unique_ptr escort(new ESM::AiSequence::AiEscort()); - escort->mData.mX = mX; - escort->mData.mY = mY; - escort->mData.mZ = mZ; - escort->mTargetId = mTargetActorRefId; - escort->mTargetActorId = mTargetActorId; - escort->mRemainingDuration = mRemainingDuration; - escort->mCellId = mCellId; - - ESM::AiSequence::AiPackageContainer package; - package.mType = ESM::AiSequence::Ai_Escort; - package.mPackage = escort.release(); - sequence.mPackages.push_back(package); - } - - void AiEscort::fastForward(const MWWorld::Ptr& actor, AiState &state) - { - // Update duration counter if this package has a duration - if (mDuration > 0) - mRemainingDuration--; - } -} - +#include "aiescort.hpp" + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" + +#include "creaturestats.hpp" +#include "movement.hpp" + +/* + TODO: Different behavior for AIEscort a d x y z and AIEscortCell a c d x y z. + TODO: Take account for actors being in different cells. +*/ + +namespace MWMechanics +{ + AiEscort::AiEscort(const ESM::RefId& actorId, int duration, float x, float y, float z, bool repeat) + : TypedAiPackage(repeat) + , mX(x) + , mY(y) + , mZ(z) + , mDuration(duration) + , mRemainingDuration(static_cast(duration)) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + { + mTargetActorRefId = actorId; + } + + AiEscort::AiEscort( + const ESM::RefId& actorId, std::string_view cellId, int duration, float x, float y, float z, bool repeat) + : TypedAiPackage(repeat) + , mCellId(cellId) + , mX(x) + , mY(y) + , mZ(z) + , mDuration(duration) + , mRemainingDuration(static_cast(duration)) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + { + mTargetActorRefId = actorId; + } + + AiEscort::AiEscort(const ESM::AiSequence::AiEscort* escort) + : TypedAiPackage(escort->mRepeat) + , mCellId(escort->mCellId) + , mX(escort->mData.mX) + , mY(escort->mData.mY) + , mZ(escort->mData.mZ) + , mDuration(escort->mData.mDuration) + , mRemainingDuration(escort->mRemainingDuration) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + { + mTargetActorRefId = escort->mTargetId; + mTargetActorId = escort->mTargetActorId; + } + + bool AiEscort::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + { + // If AiEscort has ran for as long or longer then the duration specified + // and the duration is not infinite, the package is complete. + if (mDuration > 0) + { + mRemainingDuration -= ((duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); + if (mRemainingDuration <= 0) + { + mRemainingDuration = mDuration; + return true; + } + } + + if (!mCellId.empty() && !Misc::StringUtils::ciEqual(mCellId, actor.getCell()->getCell()->getNameId())) + return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door + + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); + + const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false); + const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3(); + const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3(); + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + + if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist) + { + const osg::Vec3f dest(mX, mY, mZ); + if (pathTo(actor, dest, duration, maxHalfExtent)) // Returns true on path complete + { + mRemainingDuration = mDuration; + return true; + } + mMaxDist = maxHalfExtent + 450.0f; + } + else + { + // Stop moving if the player is too far away + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + mMaxDist = maxHalfExtent + 250.0f; + } + + return false; + } + + void AiEscort::writeState(ESM::AiSequence::AiSequence& sequence) const + { + auto escort = std::make_unique(); + escort->mData.mX = mX; + escort->mData.mY = mY; + escort->mData.mZ = mZ; + escort->mData.mDuration = mDuration; + escort->mTargetId = mTargetActorRefId; + escort->mTargetActorId = mTargetActorId; + escort->mRemainingDuration = mRemainingDuration; + escort->mCellId = mCellId; + escort->mRepeat = getRepeat(); + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Escort; + package.mPackage = std::move(escort); + sequence.mPackages.push_back(std::move(package)); + } + + void AiEscort::fastForward(const MWWorld::Ptr& actor, AiState& state) + { + // Update duration counter if this package has a duration + if (mDuration > 0) + mRemainingDuration--; + } +} diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 27a177893..e22752446 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -4,13 +4,14 @@ #include "typedaipackage.hpp" #include +#include namespace ESM { -namespace AiSequence -{ - struct AiEscort; -} + namespace AiSequence + { + struct AiEscort; + } } namespace MWMechanics @@ -18,47 +19,49 @@ namespace MWMechanics /// \brief AI Package to have an NPC lead the player to a specific point class AiEscort final : public TypedAiPackage { - public: - /// Implementation of AiEscort - /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time - \implement AiEscort **/ - AiEscort(const std::string &actorId, int duration, float x, float y, float z); - /// Implementation of AiEscortCell - /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time - \implement AiEscortCell **/ - AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z); + public: + /// Implementation of AiEscort + /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or + they run out of time \implement AiEscort **/ + AiEscort(const ESM::RefId& actorId, int duration, float x, float y, float z, bool repeat); + /// Implementation of AiEscortCell + /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or + they run out of time \implement AiEscortCell **/ + AiEscort( + const ESM::RefId& actorId, std::string_view cellId, int duration, float x, float y, float z, bool repeat); - AiEscort(const ESM::AiSequence::AiEscort* escort); + AiEscort(const ESM::AiSequence::AiEscort* escort); - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Escort; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Escort; } - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mUseVariableSpeed = true; - options.mSideWithTarget = true; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mUseVariableSpeed = true; + options.mSideWithTarget = true; + return options; + } - void writeState(ESM::AiSequence::AiSequence &sequence) const override; + void writeState(ESM::AiSequence::AiSequence& sequence) const override; - void fastForward(const MWWorld::Ptr& actor, AiState& state) override; + void fastForward(const MWWorld::Ptr& actor, AiState& state) override; - osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); } + osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); } - private: - const std::string mCellId; - const float mX; - const float mY; - const float mZ; - float mMaxDist = 450; - const float mDuration; // In hours - float mRemainingDuration; // In hours + private: + const std::string mCellId; + const float mX; + const float mY; + const float mZ; + float mMaxDist = 450; + const float mDuration; // In hours + float mRemainingDuration; // In hours - const int mCellX; - const int mCellY; + const int mCellX; + const int mCellY; }; } #endif diff --git a/apps/openmw/mwmechanics/aiface.cpp b/apps/openmw/mwmechanics/aiface.cpp index 17b18babc..2cd46a7a0 100644 --- a/apps/openmw/mwmechanics/aiface.cpp +++ b/apps/openmw/mwmechanics/aiface.cpp @@ -5,11 +5,13 @@ #include "steering.hpp" MWMechanics::AiFace::AiFace(float targetX, float targetY) - : mTargetX(targetX), mTargetY(targetY) + : mTargetX(targetX) + , mTargetY(targetY) { } -bool MWMechanics::AiFace::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& /*characterController*/, MWMechanics::AiState& /*state*/, float /*duration*/) +bool MWMechanics::AiFace::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& /*characterController*/, + MWMechanics::AiState& /*state*/, float /*duration*/) { osg::Vec3f dir = osg::Vec3f(mTargetX, mTargetY, 0) - actor.getRefData().getPosition().asVec3(); return zTurn(actor, std::atan2(dir.x(), dir.y()), osg::DegreesToRadians(3.f)); diff --git a/apps/openmw/mwmechanics/aiface.hpp b/apps/openmw/mwmechanics/aiface.hpp index e176eb52e..56747c078 100644 --- a/apps/openmw/mwmechanics/aiface.hpp +++ b/apps/openmw/mwmechanics/aiface.hpp @@ -6,26 +6,28 @@ namespace MWMechanics { /// AiPackage which makes an actor face a certain direction. - class AiFace final : public TypedAiPackage { - public: - AiFace(float targetX, float targetY); + class AiFace final : public TypedAiPackage + { + public: + AiFace(float targetX, float targetY); - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Face; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Face; } - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mPriority = 2; - options.mCanCancel = false; - options.mShouldCancelPreviousAi = false; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mPriority = 2; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } - private: - const float mTargetX; - const float mTargetY; + private: + const float mTargetX; + const float mTargetY; }; } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index ac715ff10..8b8699096 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,109 +1,48 @@ #include "aifollow.hpp" -#include -#include +#include +#include +#include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "creaturestats.hpp" -#include "movement.hpp" #include "steering.hpp" namespace { -osg::Vec3f::value_type getHalfExtents(const MWWorld::ConstPtr& actor) -{ - if(actor.getClass().isNpc()) - return 64; - return MWBase::Environment::get().getWorld()->getHalfExtents(actor).y(); -} + osg::Vec3f::value_type getHalfExtents(const MWWorld::ConstPtr& actor) + { + if (actor.getClass().isNpc()) + return 64; + return MWBase::Environment::get().getWorld()->getHalfExtents(actor).y(); + } } namespace MWMechanics { -int AiFollow::mFollowIndexCounter = 0; + int AiFollow::mFollowIndexCounter = 0; -AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actorId; -} - -AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actorId; -} - -AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actor.getCellRef().getRefId(); - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); -} - -AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actor.getCellRef().getRefId(); - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); -} - -AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) -: TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!commanded)) -, mAlwaysFollow(true), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) -, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actor.getCellRef().getRefId(); - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); -} - -AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) - : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded)) - , mAlwaysFollow(follow->mAlwaysFollow) - // mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration. - // The exact value of mDuration only matters for repeating packages. - // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. - , mDuration(follow->mRemainingDuration) - , mRemainingDuration(follow->mRemainingDuration) - , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) - , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = follow->mTargetId; - mTargetActorId = follow->mTargetActorId; -} - -bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) -{ - const MWWorld::Ptr target = getTarget(); - - // Target is not here right now, wait for it to return - // Really we should be checking whether the target is currently registered with the MechanicsManager - if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) - return false; - - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); - - AiFollowStorage& storage = state.get(); - - bool& rotate = storage.mTurnActorToTarget; - if (rotate) + AiFollow::AiFollow(const ESM::RefId& actorId, float duration, float x, float y, float z, bool repeat) + : TypedAiPackage(repeat) + , mAlwaysFollow(false) + , mDuration(duration) + , mRemainingDuration(duration) + , mX(x) + , mY(y) + , mZ(z) + , mActive(false) + , mFollowIndex(mFollowIndexCounter++) { - if (zTurn(actor, storage.mTargetAngleRadians)) - rotate = false; - - return false; + mTargetActorRefId = actorId; } +<<<<<<< HEAD /* Start of tes3mp addition @@ -121,66 +60,231 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte // AiFollow requires the target to be in range and within sight for the initial activation if (!mActive) +======= + AiFollow::AiFollow( + const ESM::RefId& actorId, std::string_view cellId, float duration, float x, float y, float z, bool repeat) + : TypedAiPackage(repeat) + , mAlwaysFollow(false) + , mDuration(duration) + , mRemainingDuration(duration) + , mX(x) + , mY(y) + , mZ(z) + , mCellId(cellId) + , mActive(false) + , mFollowIndex(mFollowIndexCounter++) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - storage.mTimer -= duration; - - if (storage.mTimer < 0) - { - if (targetDir.length2() < 500*500 && MWBase::Environment::get().getWorld()->getLOS(actor, target)) - mActive = true; - storage.mTimer = 0.5f; - } + mTargetActorRefId = actorId; } - if (!mActive) + + AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) + : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!commanded)) + , mAlwaysFollow(true) + , mDuration(0) + , mRemainingDuration(0) + , mX(0) + , mY(0) + , mZ(0) + , mActive(false) + , mFollowIndex(mFollowIndexCounter++) + { + mTargetActorRefId = actor.getCellRef().getRefId(); + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); + } + + AiFollow::AiFollow(const ESM::AiSequence::AiFollow* follow) + : TypedAiPackage( + makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat)) + , mAlwaysFollow(follow->mAlwaysFollow) + , mDuration(follow->mData.mDuration) + , mRemainingDuration(follow->mRemainingDuration) + , mX(follow->mData.mX) + , mY(follow->mData.mY) + , mZ(follow->mData.mZ) + , mCellId(follow->mCellId) + , mActive(follow->mActive) + , mFollowIndex(mFollowIndexCounter++) + { + mTargetActorRefId = follow->mTargetId; + mTargetActorId = follow->mTargetActorId; + } + + bool AiFollow::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + { + const MWWorld::Ptr target = getTarget(); + + // Target is not here right now, wait for it to return + // Really we should be checking whether the target is currently registered with the MechanicsManager + if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + return false; + + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); + + AiFollowStorage& storage = state.get(); + + bool& rotate = storage.mTurnActorToTarget; + if (rotate) + { + if (zTurn(actor, storage.mTargetAngleRadians)) + rotate = false; + + return false; + } + + const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); + const osg::Vec3f targetDir = targetPos - actorPos; + + // AiFollow requires the target to be in range and within sight for the initial activation + if (!mActive) + { + storage.mTimer -= duration; + + if (storage.mTimer < 0) + { + if (targetDir.length2() < 500 * 500 && MWBase::Environment::get().getWorld()->getLOS(actor, target)) + mActive = true; + storage.mTimer = 0.5f; + } + } + if (!mActive) + return false; + + // In the original engine the first follower stays closer to the player than any subsequent followers. + // Followers beyond the first usually attempt to stand inside each other. + osg::Vec3f::value_type floatingDistance = 0; + auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target); + if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex) + { + for (auto& follower : followers) + { + auto halfExtent = getHalfExtents(follower.second); + if (halfExtent > floatingDistance) + floatingDistance = halfExtent; + } + floatingDistance += 128; + } + floatingDistance += getHalfExtents(target) + 64; + floatingDistance += getHalfExtents(actor) * 2; + short followDistance = static_cast(floatingDistance); + + if (!mAlwaysFollow) // Update if you only follow for a bit + { + // Check if we've run out of time + if (mDuration > 0) + { + mRemainingDuration -= ((duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); + if (mRemainingDuration <= 0) + { + mRemainingDuration = mDuration; + return true; + } + } + + osg::Vec3f finalPos(mX, mY, mZ); + if ((actorPos - finalPos).length2() < followDistance * followDistance) // Close-ish to final position + { + if (actor.getCell()->isExterior()) // Outside? + { + if (mCellId.empty()) // No cell to travel to + { + mRemainingDuration = mDuration; + return true; + } + } + else if (mCellId == actor.getCell()->getCell()->getWorldSpace()) // Cell to travel to + { + mRemainingDuration = mDuration; + return true; + } + } + } + + short baseFollowDistance = followDistance; + short threshold = 30; // to avoid constant switching between moving/stopping + if (storage.mMoving) + followDistance -= threshold; + else + followDistance += threshold; + + if (targetDir.length2() <= followDistance * followDistance) + { + float faceAngleRadians = std::atan2(targetDir.x(), targetDir.y()); + + if (!zTurn(actor, faceAngleRadians, osg::DegreesToRadians(45.f))) + { + storage.mTargetAngleRadians = faceAngleRadians; + storage.mTurnActorToTarget = true; + } + + return false; + } + + storage.mMoving = !pathTo(actor, targetPos, duration, baseFollowDistance); // Go to the destination + + if (storage.mMoving) + { + // Check if you're far away + if (targetDir.length2() > 450 * 450) + actor.getClass().getCreatureStats(actor).setMovementFlag( + MWMechanics::CreatureStats::Flag_Run, true); // Make NPC run + else if (targetDir.length2() + < 325 * 325) // Have a bit of a dead zone, otherwise npc will constantly flip between running and not + // when right on the edge of the running threshold + actor.getClass().getCreatureStats(actor).setMovementFlag( + MWMechanics::CreatureStats::Flag_Run, false); // make NPC walk + } + return false; - - // In the original engine the first follower stays closer to the player than any subsequent followers. - // Followers beyond the first usually attempt to stand inside each other. - osg::Vec3f::value_type floatingDistance = 0; - auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target); - if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex) - { - for(auto& follower : followers) - { - auto halfExtent = getHalfExtents(follower.second); - if(halfExtent > floatingDistance) - floatingDistance = halfExtent; - } - floatingDistance += 128; } - floatingDistance += getHalfExtents(target) + 64; - floatingDistance += getHalfExtents(actor) * 2; - short followDistance = static_cast(floatingDistance); - if (!mAlwaysFollow) //Update if you only follow for a bit + ESM::RefId AiFollow::getFollowedActor() { - //Check if we've run out of time + return mTargetActorRefId; + } + + bool AiFollow::isCommanded() const + { + return !mOptions.mShouldCancelPreviousAi; + } + + void AiFollow::writeState(ESM::AiSequence::AiSequence& sequence) const + { + auto follow = std::make_unique(); + follow->mData.mX = mX; + follow->mData.mY = mY; + follow->mData.mZ = mZ; + follow->mData.mDuration = mDuration; + follow->mTargetId = mTargetActorRefId; + follow->mTargetActorId = mTargetActorId; + follow->mRemainingDuration = mRemainingDuration; + follow->mCellId = mCellId; + follow->mAlwaysFollow = mAlwaysFollow; + follow->mCommanded = isCommanded(); + follow->mActive = mActive; + follow->mRepeat = getRepeat(); + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Follow; + package.mPackage = std::move(follow); + sequence.mPackages.push_back(std::move(package)); + } + + int AiFollow::getFollowIndex() const + { + return mFollowIndex; + } + + void AiFollow::fastForward(const MWWorld::Ptr& actor, AiState& state) + { + // Update duration counter if this package has a duration if (mDuration > 0) - { - mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); - if (mRemainingDuration <= 0) - { - mRemainingDuration = mDuration; - return true; - } - } - - osg::Vec3f finalPos(mX, mY, mZ); - if ((actorPos-finalPos).length2() < followDistance*followDistance) //Close-ish to final position - { - if (actor.getCell()->isExterior()) //Outside? - { - if (mCellId == "") //No cell to travel to - return true; - } - else - { - if (mCellId == actor.getCell()->getCell()->mName) //Cell to travel to - return true; - } - } + mRemainingDuration--; } +<<<<<<< HEAD short baseFollowDistance = followDistance; short threshold = 30; // to avoid constant switching between moving/stopping @@ -271,4 +375,6 @@ void AiFollow::allowAnyDistance(bool state) End of tes3mp addition */ +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index a378211f4..e4a31c485 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -1,21 +1,20 @@ #ifndef GAME_MWMECHANICS_AIFOLLOW_H #define GAME_MWMECHANICS_AIFOLLOW_H +#include "aitemporarybase.hpp" #include "typedaipackage.hpp" #include +#include #include #include "../mwworld/ptr.hpp" -namespace ESM -{ -namespace AiSequence +namespace ESM::AiSequence { struct AiFollow; } -} namespace MWMechanics { @@ -26,64 +25,67 @@ namespace MWMechanics float mTargetAngleRadians; bool mTurnActorToTarget; - AiFollowStorage() : - mTimer(0.f), - mMoving(false), - mTargetAngleRadians(0.f), - mTurnActorToTarget(false) - {} + AiFollowStorage() + : mTimer(0.f) + , mMoving(false) + , mTargetAngleRadians(0.f) + , mTurnActorToTarget(false) + { + } }; /// \brief AiPackage for an actor to follow another actor/the PC - /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely - **/ + /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the + *actor to follow the other indefinitely + **/ class AiFollow final : public TypedAiPackage { - public: - AiFollow(const std::string &actorId, float duration, float x, float y, float z); - AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z); - /// Follow Actor for duration or until you arrive at a world position - AiFollow(const MWWorld::Ptr& actor, float duration, float X, float Y, float Z); - /// Follow Actor for duration or until you arrive at a position in a cell - AiFollow(const MWWorld::Ptr& actor, const std::string &CellId, float duration, float X, float Y, float Z); - /// Follow Actor indefinitively - AiFollow(const MWWorld::Ptr& actor, bool commanded=false); + public: + /// Follow Actor for duration or until you arrive at a world position + AiFollow(const ESM::RefId& actorId, float duration, float x, float y, float z, bool repeat); + /// Follow Actor for duration or until you arrive at a position in a cell + AiFollow( + const ESM::RefId& actorId, std::string_view cellId, float duration, float x, float y, float z, bool repeat); + /// Follow Actor indefinitely + AiFollow(const MWWorld::Ptr& actor, bool commanded = false); - AiFollow(const ESM::AiSequence::AiFollow* follow); + AiFollow(const ESM::AiSequence::AiFollow* follow); - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Follow; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Follow; } - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mUseVariableSpeed = true; - options.mSideWithTarget = true; - options.mFollowTargetThroughDoors = true; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mUseVariableSpeed = true; + options.mSideWithTarget = true; + options.mFollowTargetThroughDoors = true; + return options; + } - /// Returns the actor being followed - std::string getFollowedActor(); + /// Returns the actor being followed + ESM::RefId getFollowedActor(); - void writeState (ESM::AiSequence::AiSequence& sequence) const override; + void writeState(ESM::AiSequence::AiSequence& sequence) const override; - bool isCommanded() const; + bool isCommanded() const; - int getFollowIndex() const; + int getFollowIndex() const; - void fastForward(const MWWorld::Ptr& actor, AiState& state) override; + void fastForward(const MWWorld::Ptr& actor, AiState& state) override; - osg::Vec3f getDestination() const override - { - MWWorld::Ptr target = getTarget(); - if (target.isEmpty()) - return osg::Vec3f(0, 0, 0); + osg::Vec3f getDestination() const override + { + MWWorld::Ptr target = getTarget(); + if (target.isEmpty()) + return osg::Vec3f(0, 0, 0); - return target.getRefData().getPosition().asVec3(); - } + return target.getRefData().getPosition().asVec3(); + } +<<<<<<< HEAD /* Start of tes3mp addition @@ -118,6 +120,22 @@ namespace MWMechanics /* End of tes3mp addition */ +======= + private: + /// This will make the actor always follow. + /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ + const bool mAlwaysFollow; + const float mDuration; // Hours + float mRemainingDuration; // Hours + const float mX; + const float mY; + const float mZ; + const std::string mCellId; + bool mActive; // have we spotted the target? + const int mFollowIndex; + + static int mFollowIndexCounter; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; } #endif diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8ad944751..e5cd8e959 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -1,24 +1,27 @@ #include "aipackage.hpp" -#include -#include +#include #include +#include +#include #include -#include +#include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/action.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" -#include "pathgrid.hpp" +#include "../mwphysics/raycasting.hpp" + +#include "actorutil.hpp" #include "creaturestats.hpp" #include "movement.hpp" +#include "pathgrid.hpp" #include "steering.hpp" -#include "actorutil.hpp" #include @@ -26,7 +29,8 @@ namespace { float divOrMax(float dividend, float divisor) { - return divisor == 0 ? std::numeric_limits::max() * std::numeric_limits::epsilon() : dividend / divisor; + return divisor == 0 ? std::numeric_limits::max() * std::numeric_limits::epsilon() + : dividend / divisor; } float getPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) @@ -34,46 +38,71 @@ namespace const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y()); return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance); } + + bool canOpenDoors(const MWWorld::Ptr& ptr) + { + return ptr.getClass().isBipedal(ptr) || ptr.getClass().hasInventoryStore(ptr); + } } -MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : - mTypeId(typeId), - mOptions(options), - mTargetActorRefId(""), - mTargetActorId(-1), - mRotateOnTheRunChecks(0), - mIsShortcutting(false), - mShortcutProhibited(false), - mShortcutFailPos() +MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) + : mTypeId(typeId) + , mOptions(options) + , mReaction(MWBase::Environment::get().getWorld()->getPrng()) + , mTargetActorId(-1) + , mCachedTarget() + , mRotateOnTheRunChecks(0) + , mIsShortcutting(false) + , mShortcutProhibited(false) + , mShortcutFailPos() { } MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { + if (!mCachedTarget.isEmpty()) + { + if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + mCachedTarget = MWWorld::Ptr(); + else + return mCachedTarget; + } + if (mTargetActorId == -2) return MWWorld::Ptr(); if (mTargetActorId == -1) { if (mTargetActorRefId.empty()) +<<<<<<< HEAD { mTargetActorId = -2; return MWWorld::Ptr(); } MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); if (target.isEmpty()) +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { mTargetActorId = -2; - return target; + return MWWorld::Ptr(); + } + mCachedTarget = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); + if (mCachedTarget.isEmpty()) + { + mTargetActorId = -2; + return mCachedTarget; } else - mTargetActorId = target.getClass().getCreatureStats(target).getActorId(); + mTargetActorId = mCachedTarget.getClass().getCreatureStats(mCachedTarget).getActorId(); } if (mTargetActorId != -1) - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); else return MWWorld::Ptr(); + + return mCachedTarget; } void MWMechanics::AiPackage::reset() @@ -83,29 +112,31 @@ void MWMechanics::AiPackage::reset() mIsShortcutting = false; mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); + mCachedTarget = MWWorld::Ptr(); mPathFinder.clearPath(); mObstacleCheck.clear(); } -bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance) +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, + float destTolerance, float endTolerance, PathType pathType) { const Misc::TimerStatus timerStatus = mReaction.update(duration); - const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor + const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); // position of the actor MWBase::World* world = MWBase::Environment::get().getWorld(); - - const osg::Vec3f halfExtents = world->getHalfExtents(actor); + const DetourNavigator::AgentBounds agentBounds = world->getPathfindingAgentBounds(actor); /// Stops the actor when it gets too close to a unloaded cell - //... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" setting value + //... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" + // setting value //... units from player, and exterior cells are 8192 units long and wide. //... But AI processing distance may increase in the future. if (isNearInactiveCell(position)) { actor.getClass().getMovementSettings(actor).mPosition[0] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); + world->updateActorPath(actor, mPathFinder.getPath(), agentBounds, position, dest); return false; } @@ -117,7 +148,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed) { - if (actor.getClass().isBipedal(actor)) + if (canOpenDoors(actor)) openDoors(actor); const bool wasShortcutting = mIsShortcutting; @@ -131,9 +162,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& { if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path { - const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); - mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), - pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); + const ESM::Pathgrid* pathgrid + = world->getStore().get().search(*actor.getCell()->getCell()); + mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(pathgrid), + agentBounds, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, pathType); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity @@ -142,7 +174,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& // get point just before dest auto pPointBeforeDest = mPathFinder.getPath().rbegin() + 1; - // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target + // if start point is closer to the target then last point of path (excluding target itself) then go + // straight on the target if (distance(position, dest) <= distance(dest, *pPointBeforeDest)) { mPathFinder.clearPath(); @@ -151,22 +184,39 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& } } - if (!mPathFinder.getPath().empty()) //Path has points in it + if (!mPathFinder.getPath().empty()) // Path has points in it { - const osg::Vec3f& lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path + const osg::Vec3f& lastPos = mPathFinder.getPath().back(); // Get the end of the proposed path - if(distance(dest, lastPos) > 100) //End of the path is far from the destination - mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go + if (distance(dest, lastPos) > 100) // End of the path is far from the destination + mPathFinder.addPointToPath( + dest); // Adds the final destination to the path, to try to get to where you want to go } } } - const float pointTolerance = getPointTolerance(actor.getClass().getMaxSpeed(actor), duration, halfExtents); + const float pointTolerance + = getPointTolerance(actor.getClass().getMaxSpeed(actor), duration, world->getHalfExtents(actor)); +<<<<<<< HEAD static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ, halfExtents, getNavigatorFlags(actor)); +======= + const bool smoothMovement = Settings::game().mSmoothMovement; + + PathFinder::UpdateFlags updateFlags{}; + + if (actorCanMoveByZ) + updateFlags |= PathFinder::UpdateFlag_CanMoveByZ; + if (timerStatus == Misc::TimerStatus::Elapsed && smoothMovement) + updateFlags |= PathFinder::UpdateFlag_ShortenIfAlmostStraight; + if (timerStatus == Misc::TimerStatus::Elapsed) + updateFlags |= PathFinder::UpdateFlag_RemoveLoops; + + mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, updateFlags, agentBounds, getNavigatorFlags(actor)); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { @@ -179,13 +229,15 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& else if (mPathFinder.getPath().empty()) return false; - world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); + world->updateActorPath(actor, mPathFinder.getPath(), agentBounds, position, dest); if (mRotateOnTheRunChecks == 0 - || isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point + || isReachableRotatingOnTheRun( + actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point { actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target - if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--; + if (mRotateOnTheRunChecks > 0) + mRotateOnTheRunChecks--; } // turn to next path point by X,Z axes @@ -225,13 +277,14 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor) { // check if stuck due to obstacles - if (!mObstacleCheck.isEvading()) return; + if (!mObstacleCheck.isEvading()) + return; // first check if obstacle is a door static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); const MWWorld::Ptr door = getNearbyDoor(actor, distance); - if (!door.isEmpty() && actor.getClass().isBipedal(actor)) + if (!door.isEmpty() && canOpenDoors(actor)) { openDoors(actor); } @@ -275,17 +328,17 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) if (!isDoorOnTheWay(actor, door, mPathFinder.getPath().front())) return; - if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) + if (door.getCellRef().getTrap().empty() && !door.getCellRef().isLocked()) { world->activate(door, actor); return; } - const std::string keyId = door.getCellRef().getKey(); + const ESM::RefId& keyId = door.getCellRef().getKey(); if (keyId.empty()) return; - MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); + MWWorld::ContainerStore& invStore = actor.getClass().getContainerStore(actor); MWWorld::Ptr keyPtr = invStore.search(keyId); if (!keyPtr.isEmpty()) @@ -293,31 +346,33 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) } } -const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell) +const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const ESM::Pathgrid* pathgrid) const { - const ESM::CellId& id = cell->getCell()->getCellId(); + if (!pathgrid || pathgrid->mPoints.empty()) + return PathgridGraph::sEmpty; // 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); + static std::map> cache; + auto found = cache.find(pathgrid); if (found == cache.end()) - { - cache.insert(std::make_pair(id, std::make_unique(MWMechanics::PathgridGraph(cell)))); - } - return *cache[id].get(); + found = cache.emplace(pathgrid, std::make_unique(*pathgrid)).first; + return *found->second.get(); } bool MWMechanics::AiPackage::shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear) + const MWWorld::Ptr& actor, bool* destInLOS, bool isPathClear) { if (!mShortcutProhibited || (mShortcutFailPos - startPoint).length() >= PATHFIND_SHORTCUT_RETRY_DIST) { // check if target is clearly visible - isPathClear = !MWBase::Environment::get().getWorld()->castRay( - startPoint.x(), startPoint.y(), startPoint.z(), - endPoint.x(), endPoint.y(), endPoint.z()); + isPathClear + = !MWBase::Environment::get() + .getWorld() + ->getRayCasting() + ->castRay(startPoint, endPoint, MWPhysics::CollisionType_World | MWPhysics::CollisionType_Door) + .mHit; - if (destInLOS != nullptr) *destInLOS = isPathClear; + if (destInLOS != nullptr) + *destInLOS = isPathClear; if (!isPathClear) return false; @@ -336,16 +391,18 @@ bool MWMechanics::AiPackage::shortcutPath(const osg::Vec3f& startPoint, const os return false; } -bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor) +bool MWMechanics::AiPackage::checkWayIsClearForActor( + const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor) { if (canActorMoveByZAxis(actor)) return true; const float actorSpeed = actor.getClass().getMaxSpeed(actor); - const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / getAngularVelocity(actorSpeed) * 2; // *2 - for reliability + const float maxAvoidDist + = AI_REACTION_TIME * actorSpeed + actorSpeed / getAngularVelocity(actorSpeed) * 2; // *2 - for reliability const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length(); - const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; + const float offsetXY = distToTarget > maxAvoidDist * 1.5 ? maxAvoidDist : maxAvoidDist / 2; // update shortcut prohibit state if (checkWayIsClear(startPoint, endPoint, offsetXY)) @@ -371,26 +428,25 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::Ptr& actor) const { - return mPathFinder.getPath().empty() - || getPathDistance(actor, mPathFinder.getPath().back(), newDest) > 10 + return mPathFinder.getPath().empty() || getPathDistance(actor, mPathFinder.getPath().back(), newDest) > 10 || mPathFinder.getPathCell() != actor.getCell(); } bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position) { - const ESM::Cell* playerCell(getPlayer().getCell()->getCell()); + const MWWorld::Cell* playerCell = getPlayer().getCell()->getCell(); if (playerCell->isExterior()) { // get actor's distance from origin of center cell - Misc::CoordinateConverter(playerCell).toLocal(position); + Misc::CoordinateConverter(*playerCell).toLocal(position); // currently assumes 3 x 3 grid for exterior cells, with player at center cell. // AI shuts down actors before they reach edges of 3 x 3 grid. const float distanceFromEdge = 200.0; float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge; float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge; - return (position.x() < minThreshold) || (maxThreshold < position.x()) - || (position.y() < minThreshold) || (maxThreshold < position.y()); + return (position.x() < minThreshold) || (maxThreshold < position.x()) || (position.y() < minThreshold) + || (maxThreshold < position.y()); } else { @@ -428,23 +484,24 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const { - static const bool allowToFollowOverWaterSurface = Settings::Manager::getBool("allow actors to follow over water surface", "Game"); - const MWWorld::Class& actorClass = actor.getClass(); DetourNavigator::Flags result = DetourNavigator::Flag_none; if ((actorClass.isPureWaterCreature(actor) - || (getTypeId() != AiPackageTypeId::Wander - && ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow) - || actorClass.canSwim(actor) - || hasWaterWalking(actor))) - ) && actorClass.getSwimSpeed(actor) > 0) + || (getTypeId() != AiPackageTypeId::Wander + && ((Settings::game().mAllowActorsToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow) + || actorClass.canSwim(actor) || hasWaterWalking(actor)))) + && actorClass.getSwimSpeed(actor) > 0) result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0) + { result |= DetourNavigator::Flag_walk; + if (getTypeId() == AiPackageTypeId::Travel) + result |= DetourNavigator::Flag_usePathgrid; + } - if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander) + if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander) result |= DetourNavigator::Flag_openDoor; return result; @@ -456,20 +513,28 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); - if (flags & DetourNavigator::Flag_swim) - costs.mWater = divOrMax(costs.mWater, actorClass.getSwimSpeed(actor)); + const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0 ? 0.0f : actorClass.getSwimSpeed(actor); - if (flags & DetourNavigator::Flag_walk) - { - float walkCost; + const float walkSpeed = [&] { + if ((flags & DetourNavigator::Flag_walk) == 0) + return 0.0f; if (getTypeId() == AiPackageTypeId::Wander) - walkCost = divOrMax(1.0, actorClass.getWalkSpeed(actor)); - else - walkCost = divOrMax(1.0, actorClass.getRunSpeed(actor)); - costs.mDoor = costs.mDoor * walkCost; - costs.mPathgrid = costs.mPathgrid * walkCost; - costs.mGround = costs.mGround * walkCost; - } + return actorClass.getWalkSpeed(actor); + return actorClass.getRunSpeed(actor); + }(); + + const float maxSpeed = std::max(swimSpeed, walkSpeed); + + if (maxSpeed == 0) + return costs; + + const float swimFactor = swimSpeed / maxSpeed; + const float walkFactor = walkSpeed / maxSpeed; + + costs.mWater = divOrMax(costs.mWater, swimFactor); + costs.mDoor = divOrMax(costs.mDoor, walkFactor); + costs.mPathgrid = divOrMax(costs.mPathgrid, walkFactor); + costs.mGround = divOrMax(costs.mGround, walkFactor); return costs; } @@ -479,7 +544,8 @@ osg::Vec3f MWMechanics::AiPackage::getNextPathPoint(const osg::Vec3f& destinatio return mPathFinder.getPath().empty() ? destination : mPathFinder.getPath().front(); } -float MWMechanics::AiPackage::getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const +float MWMechanics::AiPackage::getNextPathPointTolerance( + float speed, float duration, const osg::Vec3f& halfExtents) const { if (mPathFinder.getPathSize() <= 1) return std::max(DEFAULT_TOLERANCE, mLastDestinationTolerance); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 6d8af0d92..c7be6f5dc 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -5,16 +5,13 @@ #include -#include "pathfinding.hpp" -#include "obstacle.hpp" -#include "aistate.hpp" #include "aipackagetypeid.hpp" +#include "aistatefwd.hpp" #include "aitimer.hpp" +#include "obstacle.hpp" +#include "pathfinding.hpp" -namespace MWWorld -{ - class Ptr; -} +#include "../mwworld/ptr.hpp" namespace ESM { @@ -25,7 +22,6 @@ namespace ESM } } - namespace MWMechanics { class CharacterController; @@ -34,148 +30,150 @@ namespace MWMechanics /// \brief Base class for AI packages class AiPackage { - public: - struct Options + public: + struct Options + { + unsigned int mPriority = 0; + bool mUseVariableSpeed = false; + bool mSideWithTarget = false; + bool mFollowTargetThroughDoors = false; + bool mCanCancel = true; + bool mShouldCancelPreviousAi = true; + bool mRepeat = false; + bool mAlwaysActive = false; + + constexpr Options withRepeat(bool value) { - unsigned int mPriority = 0; - bool mUseVariableSpeed = false; - bool mSideWithTarget = false; - bool mFollowTargetThroughDoors = false; - bool mCanCancel = true; - bool mShouldCancelPreviousAi = true; - bool mRepeat = false; - bool mAlwaysActive = false; - - constexpr Options withRepeat(bool value) - { - mRepeat = value; - return *this; - } - - constexpr Options withShouldCancelPreviousAi(bool value) - { - mShouldCancelPreviousAi = value; - return *this; - } - }; - - AiPackage(AiPackageTypeId typeId, const Options& options); - - virtual ~AiPackage() = default; - - static constexpr Options makeDefaultOptions() - { - return Options{}; + mRepeat = value; + return *this; } - ///Clones the package - virtual std::unique_ptr clone() const = 0; + constexpr Options withShouldCancelPreviousAi(bool value) + { + mShouldCancelPreviousAi = value; + return *this; + } + }; - /// Updates and runs the package (Should run every frame) - /// \return Package completed? - virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) = 0; + AiPackage(AiPackageTypeId typeId, const Options& options); - /// Returns the TypeID of the AiPackage - /// \see enum TypeId - AiPackageTypeId getTypeId() const { return mTypeId; } + virtual ~AiPackage() = default; - /// Higher number is higher priority (0 being the lowest) - unsigned int getPriority() const { return mOptions.mPriority; } + static constexpr Options makeDefaultOptions() { return Options{}; } - /// Check if package use movement with variable speed - bool useVariableSpeed() const { return mOptions.mUseVariableSpeed; } + /// Clones the package + virtual std::unique_ptr clone() const = 0; - virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {} + /// Updates and runs the package (Should run every frame) + /// \return Package completed? + virtual bool execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + = 0; - /// Simulates the passing of time - virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} + /// Returns the TypeID of the AiPackage + /// \see enum TypeId + AiPackageTypeId getTypeId() const { return mTypeId; } - /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) - virtual MWWorld::Ptr getTarget() const; + /// Higher number is higher priority (0 being the lowest) + unsigned int getPriority() const { return mOptions.mPriority; } - /// Get the destination point of the AI package (not applicable to all AI packages, default return (0, 0, 0)) - virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); }; + /// Check if package use movement with variable speed + bool useVariableSpeed() const { return mOptions.mUseVariableSpeed; } - /// Return true if having this AiPackage makes the actor side with the target in fights (default false) - bool sideWithTarget() const { return mOptions.mSideWithTarget; } + virtual void writeState(ESM::AiSequence::AiSequence& sequence) const {} - /// Return true if the actor should follow the target through teleport doors (default false) - bool followTargetThroughDoors() const { return mOptions.mFollowTargetThroughDoors; } + /// Simulates the passing of time + virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} - /// Can this Ai package be canceled? (default true) - bool canCancel() const { return mOptions.mCanCancel; } + /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) + virtual MWWorld::Ptr getTarget() const; - /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? - bool shouldCancelPreviousAi() const { return mOptions.mShouldCancelPreviousAi; } + /// Get the destination point of the AI package (not applicable to all AI packages, default return (0, 0, 0)) + virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); } - /// Return true if this package should repeat. Currently only used for Wander packages. - bool getRepeat() const { return mOptions.mRepeat; } + /// Return true if having this AiPackage makes the actor side with the target in fights (default false) + bool sideWithTarget() const { return mOptions.mSideWithTarget; } - virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } + /// Return true if the actor should follow the target through teleport doors (default false) + bool followTargetThroughDoors() const { return mOptions.mFollowTargetThroughDoors; } - /// Return true if any loaded actor with this AI package must be active. - bool alwaysActive() const { return mOptions.mAlwaysActive; } + /// Can this Ai package be canceled? (default true) + bool canCancel() const { return mOptions.mCanCancel; } - /// Reset pathfinding state - void reset(); + /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? + bool shouldCancelPreviousAi() const { return mOptions.mShouldCancelPreviousAi; } - /// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing. - static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest); + /// Return true if this package should repeat. + bool getRepeat() const { return mOptions.mRepeat; } - osg::Vec3f getNextPathPoint(const osg::Vec3f& destination) const; + virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } - float getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const; + /// Return true if any loaded actor with this AI package must be active. + bool alwaysActive() const { return mOptions.mAlwaysActive; } - protected: - /// Handles path building and shortcutting with obstacles avoiding - /** \return If the actor has arrived at his destination **/ - bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f); + /// Reset pathfinding state + void reset(); - /// Check if there aren't any obstacles along the path to make shortcut possible - /// If a shortcut is possible then path will be cleared and filled with the destination point. - /// \param destInLOS If not nullptr function will return ray cast check result - /// \return If can shortcut the path - bool shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor, - bool *destInLOS, bool isPathClear); + /// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise + /// actor should rotate while standing. + static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest); - /// Check if the way to the destination is clear, taking into account actor speed - bool checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor); + osg::Vec3f getNextPathPoint(const osg::Vec3f& destination) const; - bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::Ptr& actor) const; + float getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const; - void evadeObstacles(const MWWorld::Ptr& actor); + protected: + /// Handles path building and shortcutting with obstacles avoiding + /** \return If the actor has arrived at his destination **/ + bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f, + float endTolerance = 0.0f, PathType pathType = PathType::Full); - void openDoors(const MWWorld::Ptr& actor); + /// Check if there aren't any obstacles along the path to make shortcut possible + /// If a shortcut is possible then path will be cleared and filled with the destination point. + /// \param destInLOS If not nullptr function will return ray cast check result + /// \return If can shortcut the path + bool shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor, + bool* destInLOS, bool isPathClear); - const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); + /// Check if the way to the destination is clear, taking into account actor speed + bool checkWayIsClearForActor( + const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor); - DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; + bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::Ptr& actor) const; - DetourNavigator::AreaCosts getAreaCosts(const MWWorld::Ptr& actor) const; + void evadeObstacles(const MWWorld::Ptr& actor); - const AiPackageTypeId mTypeId; - const Options mOptions; + void openDoors(const MWWorld::Ptr& actor); - // TODO: all this does not belong here, move into temporary storage - PathFinder mPathFinder; - ObstacleCheck mObstacleCheck; + const PathgridGraph& getPathGridGraph(const ESM::Pathgrid* pathgrid) const; - AiReactionTimer mReaction; + DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; - std::string mTargetActorRefId; - mutable int mTargetActorId; + DetourNavigator::AreaCosts getAreaCosts(const MWWorld::Ptr& actor) const; - short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility + const AiPackageTypeId mTypeId; + const Options mOptions; - bool mIsShortcutting; // if shortcutting at the moment - bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt - osg::Vec3f mShortcutFailPos; // position of last shortcut fail - float mLastDestinationTolerance = 0; + // TODO: all this does not belong here, move into temporary storage + PathFinder mPathFinder; + ObstacleCheck mObstacleCheck; - private: - bool isNearInactiveCell(osg::Vec3f position); + AiReactionTimer mReaction; + + ESM::RefId mTargetActorRefId; + mutable int mTargetActorId; + mutable MWWorld::Ptr mCachedTarget; + + short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility + + bool mIsShortcutting; // if shortcutting at the moment + bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt + osg::Vec3f mShortcutFailPos; // position of last shortcut fail + float mLastDestinationTolerance = 0; + + private: + bool isNearInactiveCell(osg::Vec3f position); }; } #endif - diff --git a/apps/openmw/mwmechanics/aipackagetypeid.hpp b/apps/openmw/mwmechanics/aipackagetypeid.hpp index 2b9c4fe9c..3c1df5df5 100644 --- a/apps/openmw/mwmechanics/aipackagetypeid.hpp +++ b/apps/openmw/mwmechanics/aipackagetypeid.hpp @@ -3,7 +3,7 @@ namespace MWMechanics { - ///Enumerates the various AITypes available + /// Enumerates the various AITypes available enum class AiPackageTypeId { None = -1, diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index ccc29c3a6..c5a1fa258 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -1,6 +1,6 @@ #include "aipursue.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -9,6 +9,7 @@ #include "../mwworld/class.hpp" +<<<<<<< HEAD /* Start of tes3mp addition @@ -23,12 +24,15 @@ */ #include "movement.hpp" +======= +#include "actorutil.hpp" +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "creaturestats.hpp" -#include "combat.hpp" namespace MWMechanics { +<<<<<<< HEAD AiPursue::AiPursue(const MWWorld::Ptr& actor) { mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); @@ -110,27 +114,88 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, actor); //Arrest player when reached return true; +======= + AiPursue::AiPursue(const MWWorld::Ptr& actor) + { + mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run + AiPursue::AiPursue(const ESM::AiSequence::AiPursue* pursue) + { + mTargetActorId = pursue->mTargetActorId; + } - return false; -} + bool AiPursue::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + { + if (actor.getClass().getCreatureStats(actor).isDead()) + return true; -MWWorld::Ptr AiPursue::getTarget() const -{ - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); -} + const MWWorld::Ptr target + = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); // The target to follow -void AiPursue::writeState(ESM::AiSequence::AiSequence &sequence) const -{ - std::unique_ptr pursue(new ESM::AiSequence::AiPursue()); - pursue->mTargetActorId = mTargetActorId; + // Stop if the target doesn't exist + // Really we should be checking whether the target is currently registered with the MechanicsManager + if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + return true; - ESM::AiSequence::AiPackageContainer package; - package.mType = ESM::AiSequence::Ai_Pursue; - package.mPackage = pursue.release(); - sequence.mPackages.push_back(package); -} + if (isTargetMagicallyHidden(target) + && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(target, actor)) + return false; + + if (target.getClass().getCreatureStats(target).isDead()) + return true; + + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); + + // Set the target destination + const osg::Vec3f dest = target.getRefData().getPosition().asVec3(); + const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); + + const float pathTolerance = 100.f; + + // check the true distance in case the target is far away in Z-direction + bool reached = pathTo(actor, dest, duration, pathTolerance, (actorPos - dest).length(), PathType::Partial) + && std::abs(dest.z() - actorPos.z()) < pathTolerance; + + if (reached) + { + if (!MWBase::Environment::get().getWorld()->getLOS(target, actor)) + return false; + MWBase::Environment::get().getWindowManager()->pushGuiMode( + MWGui::GM_Dialogue, actor); // Arrest player when reached + return true; + } + + actor.getClass().getCreatureStats(actor).setMovementFlag( + MWMechanics::CreatureStats::Flag_Run, true); // Make NPC run + + return false; + } + + MWWorld::Ptr AiPursue::getTarget() const + { + if (!mCachedTarget.isEmpty()) + { + if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + mCachedTarget = MWWorld::Ptr(); + else + return mCachedTarget; + } + mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + return mCachedTarget; + } + + void AiPursue::writeState(ESM::AiSequence::AiSequence& sequence) const + { + auto pursue = std::make_unique(); + pursue->mTargetActorId = mTargetActorId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Pursue; + package.mPackage = std::move(pursue); + sequence.mPackages.push_back(std::move(package)); + } } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 2fbc13b87..d9cf4a40c 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -5,42 +5,43 @@ namespace ESM { -namespace AiSequence -{ - struct AiPursue; -} + namespace AiSequence + { + struct AiPursue; + } } namespace MWMechanics { /// \brief Makes the actor very closely follow the actor /** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them. - Note that while very similar to AiActivate, it will ONLY activate when evry close to target (Not also when the + Note that while very similar to AiActivate, it will ONLY activate when very close to target (Not also when the path is completed). **/ class AiPursue final : public TypedAiPackage { - public: - ///Constructor - /** \param actor Actor to pursue **/ - AiPursue(const MWWorld::Ptr& actor); + public: + /// Constructor + /** \param actor Actor to pursue **/ + AiPursue(const MWWorld::Ptr& actor); - AiPursue(const ESM::AiSequence::AiPursue* pursue); + AiPursue(const ESM::AiSequence::AiPursue* pursue); - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Pursue; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Pursue; } - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mCanCancel = false; - options.mShouldCancelPreviousAi = false; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mCanCancel = false; + options.mShouldCancelPreviousAi = false; + return options; + } - MWWorld::Ptr getTarget() const override; + MWWorld::Ptr getTarget() const override; - void writeState (ESM::AiSequence::AiSequence& sequence) const override; + void writeState(ESM::AiSequence::AiSequence& sequence) const override; }; } #endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index fada7761d..b58927c99 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -1,216 +1,246 @@ #include "aisequence.hpp" +#include #include #include -#include +#include -#include "aipackage.hpp" -#include "aistate.hpp" -#include "aiwander.hpp" -#include "aiescort.hpp" -#include "aitravel.hpp" -#include "aifollow.hpp" +#include "../mwworld/class.hpp" +#include "actorutil.hpp" #include "aiactivate.hpp" #include "aicombat.hpp" #include "aicombataction.hpp" +#include "aiescort.hpp" +#include "aifollow.hpp" +#include "aipackage.hpp" #include "aipursue.hpp" -#include "actorutil.hpp" -#include "../mwworld/class.hpp" +#include "aitravel.hpp" +#include "aiwander.hpp" namespace MWMechanics { -void AiSequence::copy (const AiSequence& sequence) -{ - for (const auto& package : sequence.mPackages) - mPackages.push_back(package->clone()); - - // We need to keep an AiWander storage, if present - it has a state machine. - // Not sure about another temporary storages - sequence.mAiState.copy(mAiState); -} - -AiSequence::AiSequence() : mDone (false), mRepeat(false), mLastAiPackage(AiPackageTypeId::None) {} - -AiSequence::AiSequence (const AiSequence& sequence) -{ - copy (sequence); - mDone = sequence.mDone; - mLastAiPackage = sequence.mLastAiPackage; - mRepeat = sequence.mRepeat; -} - -AiSequence& AiSequence::operator= (const AiSequence& sequence) -{ - if (this!=&sequence) + void AiSequence::copy(const AiSequence& sequence) { - clear(); - copy (sequence); + for (const auto& package : sequence.mPackages) + mPackages.push_back(package->clone()); + + // We need to keep an AiWander storage, if present - it has a state machine. + // Not sure about another temporary storages + sequence.mAiState.copy(mAiState); + + mNumCombatPackages = sequence.mNumCombatPackages; + mNumPursuitPackages = sequence.mNumPursuitPackages; + } + + AiSequence::AiSequence() + : mDone(false) + , mLastAiPackage(AiPackageTypeId::None) + { + } + + AiSequence::AiSequence(const AiSequence& sequence) + { + copy(sequence); mDone = sequence.mDone; mLastAiPackage = sequence.mLastAiPackage; } - return *this; -} - -AiSequence::~AiSequence() -{ - clear(); -} - -AiPackageTypeId AiSequence::getTypeId() const -{ - if (mPackages.empty()) - return AiPackageTypeId::None; - - return mPackages.front()->getTypeId(); -} - -bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const -{ - if (getTypeId() != AiPackageTypeId::Combat) - return false; - - targetActor = mPackages.front()->getTarget(); - - return !targetActor.isEmpty(); -} - -bool AiSequence::getCombatTargets(std::vector &targetActors) const -{ - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + AiSequence& AiSequence::operator=(const AiSequence& sequence) { - if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Combat) - targetActors.push_back((*it)->getTarget()); + if (this != &sequence) + { + clear(); + copy(sequence); + mDone = sequence.mDone; + mLastAiPackage = sequence.mLastAiPackage; + } + + return *this; } - return !targetActors.empty(); -} - -std::list>::const_iterator AiSequence::begin() const -{ - return mPackages.begin(); -} - -std::list>::const_iterator AiSequence::end() const -{ - return mPackages.end(); -} - -void AiSequence::erase(std::list>::const_iterator package) -{ - // Not sure if manually terminated packages should trigger mDone, probably not? - for(auto it = mPackages.begin(); it != mPackages.end(); ++it) + AiSequence::~AiSequence() { - if (package == it) + clear(); + } + + void AiSequence::onPackageAdded(const AiPackage& package) + { + if (package.getTypeId() == AiPackageTypeId::Combat) + mNumCombatPackages++; + else if (package.getTypeId() == AiPackageTypeId::Pursue) + mNumPursuitPackages++; + + assert(mNumCombatPackages >= 0); + assert(mNumPursuitPackages >= 0); + } + + void AiSequence::onPackageRemoved(const AiPackage& package) + { + if (package.getTypeId() == AiPackageTypeId::Combat) + mNumCombatPackages--; + else if (package.getTypeId() == AiPackageTypeId::Pursue) + mNumPursuitPackages--; + + assert(mNumCombatPackages >= 0); + assert(mNumPursuitPackages >= 0); + } + + AiPackageTypeId AiSequence::getTypeId() const + { + if (mPackages.empty()) + return AiPackageTypeId::None; + + return mPackages.front()->getTypeId(); + } + + bool AiSequence::getCombatTarget(MWWorld::Ptr& targetActor) const + { + if (getTypeId() != AiPackageTypeId::Combat) + return false; + + targetActor = mPackages.front()->getTarget(); + + return !targetActor.isEmpty(); + } + + bool AiSequence::getCombatTargets(std::vector& targetActors) const + { + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { - mPackages.erase(it); + if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Combat) + targetActors.push_back((*it)->getTarget()); + } + + return !targetActors.empty(); + } + + AiPackages::iterator AiSequence::erase(AiPackages::iterator package) + { + // Not sure if manually terminated packages should trigger mDone, probably not? + auto& ptr = *package; + onPackageRemoved(*ptr); + + return mPackages.erase(package); + } + + bool AiSequence::isInCombat() const + { + return mNumCombatPackages > 0; + } + + bool AiSequence::isInPursuit() const + { + return mNumPursuitPackages > 0; + } + + bool AiSequence::isEngagedWithActor() const + { + if (!isInCombat()) + return false; + + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == AiPackageTypeId::Combat) + { + MWWorld::Ptr target2 = (*it)->getTarget(); + if (!target2.isEmpty() && target2.getClass().isNpc()) + return true; + } + } + return false; + } + + bool AiSequence::hasPackage(AiPackageTypeId typeId) const + { + auto it = std::find_if(mPackages.begin(), mPackages.end(), + [typeId](const auto& package) { return package->getTypeId() == typeId; }); + return it != mPackages.end(); + } + + bool AiSequence::isInCombat(const MWWorld::Ptr& actor) const + { + if (!isInCombat()) + return false; + + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == AiPackageTypeId::Combat) + { + if ((*it)->getTarget() == actor) + return true; + } + } + return false; + } + + void AiSequence::removePackagesById(AiPackageTypeId id) + { + for (auto it = mPackages.begin(); it != mPackages.end();) + { + if ((*it)->getTypeId() == id) + { + it = erase(it); + } + else + ++it; + } + } + + void AiSequence::stopCombat() + { + removePackagesById(AiPackageTypeId::Combat); + } + + void AiSequence::stopCombat(const std::vector& targets) + { + for (auto it = mPackages.begin(); it != mPackages.end();) + { + if ((*it)->getTypeId() == AiPackageTypeId::Combat + && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end()) + { + it = erase(it); + } + else + ++it; + } + } + + void AiSequence::stopPursuit() + { + removePackagesById(AiPackageTypeId::Pursue); + } + + bool AiSequence::isPackageDone() const + { + return mDone; + } + + namespace + { + bool isActualAiPackage(AiPackageTypeId packageTypeId) + { + return (packageTypeId >= AiPackageTypeId::Wander && packageTypeId <= AiPackageTypeId::Activate); + } + } + + void AiSequence::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange) + { + if (actor == getPlayer()) + { + // Players don't use this. return; } - } - throw std::runtime_error("can't find package to erase"); -} -bool AiSequence::isInCombat() const -{ - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) - { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) - return true; - } - return false; -} - -bool AiSequence::isEngagedWithActor() const -{ - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) - { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) - { - MWWorld::Ptr target2 = (*it)->getTarget(); - if (!target2.isEmpty() && target2.getClass().isNpc()) - return true; - } - } - return false; -} - -bool AiSequence::hasPackage(AiPackageTypeId typeId) const -{ - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) - { - if ((*it)->getTypeId() == typeId) - return true; - } - return false; -} - -bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const -{ - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) - { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) - { - if ((*it)->getTarget() == actor) - return true; - } - } - return false; -} - -void AiSequence::stopCombat() -{ - for(auto it = mPackages.begin(); it != mPackages.end(); ) - { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) - { - it = mPackages.erase(it); - } - else - ++it; - } -} - -void AiSequence::stopPursuit() -{ - for(auto it = mPackages.begin(); it != mPackages.end(); ) - { - if ((*it)->getTypeId() == AiPackageTypeId::Pursue) - { - it = mPackages.erase(it); - } - else - ++it; - } -} - -bool AiSequence::isPackageDone() const -{ - return mDone; -} - -namespace -{ - bool isActualAiPackage(AiPackageTypeId packageTypeId) - { - return (packageTypeId >= AiPackageTypeId::Wander && - packageTypeId <= AiPackageTypeId::Activate); - } -} - -void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange) -{ - if(actor != getPlayer()) - { if (mPackages.empty()) { mLastAiPackage = AiPackageTypeId::None; return; } - auto packageIt = mPackages.begin(); - MWMechanics::AiPackage* package = packageIt->get(); + auto* package = mPackages.front().get(); if (!package->alwaysActive() && outOfRange) return; @@ -230,20 +260,21 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac for (auto it = mPackages.begin(); it != mPackages.end();) { - if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; + if ((*it)->getTypeId() != AiPackageTypeId::Combat) + break; MWWorld::Ptr target = (*it)->getTarget(); // target disappeared (e.g. summoned creatures) if (target.isEmpty()) { - it = mPackages.erase(it); + it = erase(it); } else { float rating = MWMechanics::getBestActionRating(actor, target); - const ESM::Position &targetPos = target.getRefData().getPosition(); + const ESM::Position& targetPos = target.getRefData().getPosition(); float distTo = (targetPos.asVec3() - vActorPos).length2(); @@ -262,17 +293,17 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac } } - assert(!mPackages.empty()); + if (mPackages.empty()) + return; if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) { assert(itActualCombat != mPackages.end()); // move combat package with nearest target to the front - mPackages.splice(mPackages.begin(), mPackages, itActualCombat); + std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat)); } - packageIt = mPackages.begin(); - package = packageIt->get(); + package = mPackages.front().get(); packageTypeId = package->getTypeId(); } @@ -280,15 +311,21 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if (package->execute(actor, characterController, mAiState, duration)) { - // Put repeating noncombat AI packages on the end of the stack so they can be used again - if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) + // Put repeating non-combat AI packages on the end of the stack so they can be used again + if (isActualAiPackage(packageTypeId) && package->getRepeat()) { package->reset(); mPackages.push_back(package->clone()); } - // To account for the rare case where AiPackage::execute() queued another AI package - // (e.g. AiPursue executing a dialogue script that uses startCombat) - mPackages.erase(packageIt); + + // The active package is typically the first entry, this is however not always the case + // e.g. AiPursue executing a dialogue script that uses startCombat adds a combat package to the front + // due to the priority. + auto activePackageIt = std::find_if( + mPackages.begin(), mPackages.end(), [&](auto& entry) { return entry.get() == package; }); + + erase(activePackageIt); + if (isActualAiPackage(packageTypeId)) mDone = true; } @@ -302,239 +339,233 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); } } -} -void AiSequence::clear() -{ - mPackages.clear(); -} - -void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther) -{ - if (actor == getPlayer()) - throw std::runtime_error("Can't add AI packages to player"); - - // Stop combat when a non-combat AI package is added - if (isActualAiPackage(package.getTypeId())) - stopCombat(); - - // We should return a wandering actor back after combat, casting or pursuit. - // The same thing for actors without AI packages. - // Also there is no point to stack return packages. - const auto currentTypeId = getTypeId(); - const auto newTypeId = package.getTypeId(); - if (currentTypeId <= MWMechanics::AiPackageTypeId::Wander - && !hasPackage(MWMechanics::AiPackageTypeId::InternalTravel) - && (newTypeId <= MWMechanics::AiPackageTypeId::Combat - || newTypeId == MWMechanics::AiPackageTypeId::Pursue - || newTypeId == MWMechanics::AiPackageTypeId::Cast)) + void AiSequence::clear() { - osg::Vec3f dest; - if (currentTypeId == MWMechanics::AiPackageTypeId::Wander) - { - dest = getActivePackage().getDestination(actor); - } - else - { - dest = actor.getRefData().getPosition().asVec3(); - } - - MWMechanics::AiInternalTravel travelPackage(dest.x(), dest.y(), dest.z()); - stack(travelPackage, actor, false); + mPackages.clear(); + mNumCombatPackages = 0; + mNumPursuitPackages = 0; } - // remove previous packages if required - if (cancelOther && package.shouldCancelPreviousAi()) + void AiSequence::stack(const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther) { - for (auto it = mPackages.begin(); it != mPackages.end();) + if (actor == getPlayer()) + throw std::runtime_error("Can't add AI packages to player"); + + // Stop combat when a non-combat AI package is added + if (isActualAiPackage(package.getTypeId())) + stopCombat(); + + // We should return a wandering actor back after combat, casting or pursuit. + // The same thing for actors without AI packages. + // Also there is no point to stack return packages. + const auto currentTypeId = getTypeId(); + const auto newTypeId = package.getTypeId(); + if (currentTypeId <= MWMechanics::AiPackageTypeId::Wander + && !hasPackage(MWMechanics::AiPackageTypeId::InternalTravel) + && (newTypeId <= MWMechanics::AiPackageTypeId::Combat || newTypeId == MWMechanics::AiPackageTypeId::Pursue + || newTypeId == MWMechanics::AiPackageTypeId::Cast)) { - if((*it)->canCancel()) + osg::Vec3f dest; + if (currentTypeId == MWMechanics::AiPackageTypeId::Wander) { - it = mPackages.erase(it); + dest = getActivePackage().getDestination(actor); } else - ++it; + { + dest = actor.getRefData().getPosition().asVec3(); + } + + MWMechanics::AiInternalTravel travelPackage(dest.x(), dest.y(), dest.z()); + stack(travelPackage, actor, false); + } + + // remove previous packages if required + if (cancelOther && package.shouldCancelPreviousAi()) + { + for (auto it = mPackages.begin(); it != mPackages.end();) + { + if ((*it)->canCancel()) + { + it = erase(it); + } + else + ++it; + } + } + + // insert new package in correct place depending on priority + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + { + // We should override current AiCast package, if we try to add a new one. + if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Cast + && package.getTypeId() == MWMechanics::AiPackageTypeId::Cast) + { + *it = package.clone(); + return; + } + + if ((*it)->getPriority() <= package.getPriority()) + { + onPackageAdded(package); + mPackages.insert(it, package.clone()); + return; + } + } + + onPackageAdded(package); + mPackages.push_back(package.clone()); + + // Make sure that temporary storage is empty + if (cancelOther) + { + mAiState.moveIn(std::make_unique()); + mAiState.moveIn(std::make_unique()); + mAiState.moveIn(std::make_unique()); } - mRepeat=false; } - // insert new package in correct place depending on priority - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + bool MWMechanics::AiSequence::isEmpty() const { - // We should keep current AiCast package, if we try to add a new one. - if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Cast && - package.getTypeId() == MWMechanics::AiPackageTypeId::Cast) - { - continue; - } - - if((*it)->getPriority() <= package.getPriority()) - { - mPackages.insert(it, package.clone()); - return; - } + return mPackages.empty(); } - mPackages.push_back(package.clone()); - - // Make sure that temporary storage is empty - if (cancelOther) + const AiPackage& MWMechanics::AiSequence::getActivePackage() const { - mAiState.moveIn(new AiCombatStorage()); - mAiState.moveIn(new AiFollowStorage()); - mAiState.moveIn(new AiWanderStorage()); + if (mPackages.empty()) + throw std::runtime_error(std::string("No AI Package!")); + return *mPackages.front(); } -} -bool MWMechanics::AiSequence::isEmpty() const -{ - return mPackages.empty(); -} - -const AiPackage& MWMechanics::AiSequence::getActivePackage() -{ - if(mPackages.empty()) - throw std::runtime_error(std::string("No AI Package!")); - return *mPackages.front(); -} - -void AiSequence::fill(const ESM::AIPackageList &list) -{ - // If there is more than one package in the list, enable repeating - if (list.mList.size() >= 2) - mRepeat = true; - - for (const auto& esmPackage : list.mList) + void AiSequence::fill(const ESM::AIPackageList& list) { - std::unique_ptr package; - if (esmPackage.mType == ESM::AI_Wander) + for (const auto& esmPackage : list.mList) { - ESM::AIWander data = esmPackage.mWander; - std::vector idles; - idles.reserve(8); - for (int i=0; i<8; ++i) - idles.push_back(data.mIdle[i]); - package = std::make_unique(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0); + std::unique_ptr package; + if (esmPackage.mType == ESM::AI_Wander) + { + ESM::AIWander data = esmPackage.mWander; + std::vector idles; + idles.reserve(8); + for (int i = 0; i < 8; ++i) + idles.push_back(data.mIdle[i]); + package = std::make_unique( + data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat != 0); + } + else if (esmPackage.mType == ESM::AI_Escort) + { + ESM::AITarget data = esmPackage.mTarget; + package = std::make_unique(ESM::RefId::stringRefId(data.mId.toStringView()), + data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); + } + else if (esmPackage.mType == ESM::AI_Travel) + { + ESM::AITravel data = esmPackage.mTravel; + package = std::make_unique(data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); + } + else if (esmPackage.mType == ESM::AI_Activate) + { + ESM::AIActivate data = esmPackage.mActivate; + package = std::make_unique( + ESM::RefId::stringRefId(data.mName.toStringView()), data.mShouldRepeat != 0); + } + else // if (esmPackage.mType == ESM::AI_Follow) + { + ESM::AITarget data = esmPackage.mTarget; + package = std::make_unique(ESM::RefId::stringRefId(data.mId.toStringView()), + data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); + } + + onPackageAdded(*package); + mPackages.push_back(std::move(package)); } - else if (esmPackage.mType == ESM::AI_Escort) - { - ESM::AITarget data = esmPackage.mTarget; - package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); - } - else if (esmPackage.mType == ESM::AI_Travel) - { - ESM::AITravel data = esmPackage.mTravel; - package = std::make_unique(data.mX, data.mY, data.mZ); - } - else if (esmPackage.mType == ESM::AI_Activate) - { - ESM::AIActivate data = esmPackage.mActivate; - package = std::make_unique(data.mName.toString()); - } - else //if (esmPackage.mType == ESM::AI_Follow) - { - ESM::AITarget data = esmPackage.mTarget; - package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); - } - mPackages.push_back(std::move(package)); } -} -void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const -{ - for (const auto& package : mPackages) - package->writeState(sequence); - - sequence.mLastAiPackage = static_cast(mLastAiPackage); -} - -void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) -{ - if (!sequence.mPackages.empty()) - clear(); - - // If there is more than one non-combat, non-pursue package in the list, enable repeating. - int count = 0; - for (auto& container : sequence.mPackages) + void AiSequence::writeState(ESM::AiSequence::AiSequence& sequence) const { - switch (container.mType) - { - case ESM::AiSequence::Ai_Wander: - case ESM::AiSequence::Ai_Travel: - case ESM::AiSequence::Ai_Escort: - case ESM::AiSequence::Ai_Follow: - case ESM::AiSequence::Ai_Activate: - ++count; - } + for (const auto& package : mPackages) + package->writeState(sequence); + + sequence.mLastAiPackage = static_cast(mLastAiPackage); } - if (count > 1) - mRepeat = true; - - // Load packages - for (auto& container : sequence.mPackages) + void AiSequence::readState(const ESM::AiSequence::AiSequence& sequence) { - std::unique_ptr package; - switch (container.mType) + if (!sequence.mPackages.empty()) + clear(); + + // Load packages + for (auto& container : sequence.mPackages) { - case ESM::AiSequence::Ai_Wander: - { - package.reset(new AiWander(static_cast(container.mPackage))); - break; - } - case ESM::AiSequence::Ai_Travel: - { - const auto source = static_cast(container.mPackage); - if (source->mHidden) - package.reset(new AiInternalTravel(source)); - else - package.reset(new AiTravel(source)); - break; - } - case ESM::AiSequence::Ai_Escort: - { - package.reset(new AiEscort(static_cast(container.mPackage))); - break; - } - case ESM::AiSequence::Ai_Follow: - { - package.reset(new AiFollow(static_cast(container.mPackage))); - break; - } - case ESM::AiSequence::Ai_Activate: - { - package.reset(new AiActivate(static_cast(container.mPackage))); - break; - } - case ESM::AiSequence::Ai_Combat: - { - package.reset(new AiCombat(static_cast(container.mPackage))); - break; - } - case ESM::AiSequence::Ai_Pursue: - { - package.reset(new AiPursue(static_cast(container.mPackage))); - break; - } - default: - break; + std::unique_ptr package; + switch (container.mType) + { + case ESM::AiSequence::Ai_Wander: + { + package = std::make_unique( + &static_cast(*container.mPackage)); + break; + } + case ESM::AiSequence::Ai_Travel: + { + const ESM::AiSequence::AiTravel& source + = static_cast(*container.mPackage); + if (source.mHidden) + package = std::make_unique(&source); + else + package = std::make_unique(&source); + break; + } + case ESM::AiSequence::Ai_Escort: + { + package = std::make_unique( + &static_cast(*container.mPackage)); + break; + } + case ESM::AiSequence::Ai_Follow: + { + package = std::make_unique( + &static_cast(*container.mPackage)); + break; + } + case ESM::AiSequence::Ai_Activate: + { + package = std::make_unique( + &static_cast(*container.mPackage)); + break; + } + case ESM::AiSequence::Ai_Combat: + { + package = std::make_unique( + &static_cast(*container.mPackage)); + break; + } + case ESM::AiSequence::Ai_Pursue: + { + package = std::make_unique( + &static_cast(*container.mPackage)); + break; + } + default: + break; + } + + if (!package.get()) + continue; + + onPackageAdded(*package); + mPackages.push_back(std::move(package)); } - if (!package.get()) - continue; - - mPackages.push_back(std::move(package)); + mLastAiPackage = static_cast(sequence.mLastAiPackage); } - mLastAiPackage = static_cast(sequence.mLastAiPackage); -} - -void AiSequence::fastForward(const MWWorld::Ptr& actor) -{ - if (!mPackages.empty()) + void AiSequence::fastForward(const MWWorld::Ptr& actor) { - mPackages.front()->fastForward(actor, mAiState); + if (!mPackages.empty()) + { + mPackages.front()->fastForward(actor, mAiState); + } } -} } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 645524d38..ab3cc11e2 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -1,13 +1,14 @@ #ifndef GAME_MWMECHANICS_AISEQUENCE_H #define GAME_MWMECHANICS_AISEQUENCE_H -#include +#include #include +#include -#include "aistate.hpp" #include "aipackagetypeid.hpp" +#include "aistate.hpp" -#include +#include namespace MWWorld { @@ -22,122 +23,155 @@ namespace ESM } } - - namespace MWMechanics { class AiPackage; class CharacterController; - - template< class Base > class DerivedClassStorage; - struct AiTemporaryBase; - typedef DerivedClassStorage AiState; + + using AiPackages = std::vector>; /// \brief Sequence of AI-packages for a single actor /** The top-most AI package is run each frame. When completed, it is removed from the stack. **/ class AiSequence { - ///AiPackages to run though - std::list> mPackages; + /// AiPackages to run though + AiPackages mPackages; - ///Finished with top AIPackage, set for one frame - bool mDone; + /// Finished with top AIPackage, set for one frame + bool mDone{}; - ///Does this AI sequence repeat (repeating of Wander packages handled separately) - bool mRepeat; + int mNumCombatPackages{}; + int mNumPursuitPackages{}; - ///Copy AiSequence - void copy (const AiSequence& sequence); + /// Copy AiSequence + void copy(const AiSequence& sequence); - /// The type of AI package that ran last - AiPackageTypeId mLastAiPackage; - AiState mAiState; + /// The type of AI package that ran last + AiPackageTypeId mLastAiPackage; + AiState mAiState; - public: - ///Default constructor - AiSequence(); + void onPackageAdded(const AiPackage& package); + void onPackageRemoved(const AiPackage& package); - /// Copy Constructor - AiSequence (const AiSequence& sequence); + AiPackages::iterator erase(AiPackages::iterator package); - /// Assignment operator - AiSequence& operator= (const AiSequence& sequence); + public: + /// Default constructor + AiSequence(); - virtual ~AiSequence(); + /// Copy Constructor + AiSequence(const AiSequence& sequence); - /// Iterator may be invalidated by any function calls other than begin() or end(). - std::list>::const_iterator begin() const; - std::list>::const_iterator end() const; + /// Assignment operator + AiSequence& operator=(const AiSequence& sequence); - void erase(std::list>::const_iterator package); + virtual ~AiSequence(); - /// Returns currently executing AiPackage type - /** \see enum class AiPackageTypeId **/ - AiPackageTypeId getTypeId() const; + /// Iterator may be invalidated by any function calls other than begin() or end(). + AiPackages::const_iterator begin() const { return mPackages.begin(); } + AiPackages::const_iterator end() const { return mPackages.end(); } - /// Get the typeid of the Ai package that ran last - /** NOT the currently "active" Ai package that will be run in the next frame. - This difference is important when an Ai package has just finished and been removed. - \see enum class AiPackageTypeId **/ - AiPackageTypeId getLastRunTypeId() const { return mLastAiPackage; } + /// Removes all packages controlled by the predicate. + template + void erasePackagesIf(const F&& pred) + { + mPackages.erase(std::remove_if(mPackages.begin(), mPackages.end(), + [&](auto& entry) { + const bool doRemove = pred(entry); + if (doRemove) + onPackageRemoved(*entry); + return doRemove; + }), + mPackages.end()); + } - /// Return true and assign target if combat package is currently active, return false otherwise - bool getCombatTarget (MWWorld::Ptr &targetActor) const; + /// Removes a single package controlled by the predicate. + template + void erasePackageIf(const F&& pred) + { + auto it = std::find_if(mPackages.begin(), mPackages.end(), pred); + if (it == mPackages.end()) + return; + erase(it); + } - /// Return true and assign targets for all combat packages, or return false if there are no combat packages - bool getCombatTargets(std::vector &targetActors) const; + /// Returns currently executing AiPackage type + /** \see enum class AiPackageTypeId **/ + AiPackageTypeId getTypeId() const; - /// Is there any combat package? - bool isInCombat () const; + /// Get the typeid of the Ai package that ran last + /** NOT the currently "active" Ai package that will be run in the next frame. + This difference is important when an Ai package has just finished and been removed. + \see enum class AiPackageTypeId **/ + AiPackageTypeId getLastRunTypeId() const { return mLastAiPackage; } - /// Are we in combat with any other actor, who's also engaging us? - bool isEngagedWithActor () const; + /// Return true and assign target if combat package is currently active, return false otherwise + bool getCombatTarget(MWWorld::Ptr& targetActor) const; - /// Does this AI sequence have the given package type? - bool hasPackage(AiPackageTypeId typeId) const; + /// Return true and assign targets for all combat packages, or return false if there are no combat packages + bool getCombatTargets(std::vector& targetActors) const; - /// Are we in combat with this particular actor? - bool isInCombat (const MWWorld::Ptr& actor) const; + /// Is there any combat package? + bool isInCombat() const; - bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; - ///< Function assumes that actor can have only 1 target apart player + /// Is there any pursuit package. + bool isInPursuit() const; - /// Removes all combat packages until first non-combat or stack empty. - void stopCombat(); + /// Removes all packages using the specified id. + void removePackagesById(AiPackageTypeId id); - /// Has a package been completed during the last update? - bool isPackageDone() const; + /// Are we in combat with any other actor, who's also engaging us? + bool isEngagedWithActor() const; - /// Removes all pursue packages until first non-pursue or stack empty. - void stopPursuit(); + /// Does this AI sequence have the given package type? + bool hasPackage(AiPackageTypeId typeId) const; - /// Execute current package, switching if needed. - void execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange=false); + /// Are we in combat with this particular actor? + bool isInCombat(const MWWorld::Ptr& actor) const; - /// Simulate the passing of time using the currently active AI package - void fastForward(const MWWorld::Ptr &actor); + bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; + ///< Function assumes that actor can have only 1 target apart player - /// Remove all packages. - void clear(); + /// Removes all combat packages until first non-combat or stack empty. + void stopCombat(); - ///< Add \a package to the front of the sequence - /** Suspends current package - @param actor The actor that owns this AiSequence **/ - void stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther=true); + /// Removes all combat packages with the given targets + void stopCombat(const std::vector& targets); - /// Return the current active package. - /** If there is no active package, it will throw an exception **/ - const AiPackage& getActivePackage(); + /// Has a package been completed during the last update? + bool isPackageDone() const; - /// Fills the AiSequence with packages - /** Typically used for loading from the ESM - \see ESM::AIPackageList **/ - void fill (const ESM::AIPackageList& list); + /// Removes all pursue packages until first non-pursue or stack empty. + void stopPursuit(); - bool isEmpty() const; + /// Execute current package, switching if needed. + void execute(const MWWorld::Ptr& actor, CharacterController& characterController, float duration, + bool outOfRange = false); - void writeState (ESM::AiSequence::AiSequence& sequence) const; - void readState (const ESM::AiSequence::AiSequence& sequence); + /// Simulate the passing of time using the currently active AI package + void fastForward(const MWWorld::Ptr& actor); + + /// Remove all packages. + void clear(); + + ///< Add \a package to the front of the sequence + /** Suspends current package + @param actor The actor that owns this AiSequence **/ + void stack(const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther = true); + + /// Return the current active package. + /** If there is no active package, it will throw an exception **/ + const AiPackage& getActivePackage() const; + + /// Fills the AiSequence with packages + /** Typically used for loading from the ESM + \see ESM::AIPackageList **/ + void fill(const ESM::AIPackageList& list); + + bool isEmpty() const; + + void writeState(ESM::AiSequence::AiSequence& sequence) const; + void readState(const ESM::AiSequence::AiSequence& sequence); }; } diff --git a/apps/openmw/mwmechanics/aisetting.hpp b/apps/openmw/mwmechanics/aisetting.hpp new file mode 100644 index 000000000..3a274722f --- /dev/null +++ b/apps/openmw/mwmechanics/aisetting.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_MWMECHANICS_AISETTING_H +#define OPENMW_MWMECHANICS_AISETTING_H + +namespace MWMechanics +{ + enum class AiSetting + { + Hello = 0, + Fight = 1, + Flee = 2, + Alarm = 3 + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index 976e21c65..d79469a9a 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -1,7 +1,10 @@ #ifndef AISTATE_H #define AISTATE_H -#include +#include "aistatefwd.hpp" +#include "aitemporarybase.hpp" + +#include namespace MWMechanics { @@ -11,92 +14,51 @@ namespace MWMechanics * the stored object if it has the correct type or otherwise replaces * it with an object of the requested type. */ - template< class Base > + template class DerivedClassStorage - { + { private: - Base* mStorage; - - //if needed you have to provide a clone member function - DerivedClassStorage( const DerivedClassStorage& other ); - DerivedClassStorage& operator=( const DerivedClassStorage& ); - + std::unique_ptr mStorage; + public: /// \brief returns reference to stored object or deletes it and creates a fitting - template< class Derived > + template Derived& get() { - Derived* result = dynamic_cast(mStorage); - - if(!result) + Derived* result = dynamic_cast(mStorage.get()); + + if (result == nullptr) { - if(mStorage) - delete mStorage; - mStorage = result = new Derived(); + auto storage = std::make_unique(); + result = storage.get(); + mStorage = std::move(storage); } - - //return a reference to the (new allocated) object + + // return a reference to the (new allocated) object return *result; } - template< class Derived > + template void copy(DerivedClassStorage& destination) const { - Derived* result = dynamic_cast(mStorage); + Derived* result = dynamic_cast(mStorage.get()); if (result != nullptr) destination.store(*result); } - - template< class Derived > - void store( const Derived& payload ) + + template + void store(const Derived& payload) { - if(mStorage) - delete mStorage; - mStorage = new Derived(payload); + mStorage = std::make_unique(payload); } - + /// \brief takes ownership of the passed object - template< class Derived > - void moveIn( Derived* p ) + template + void moveIn(std::unique_ptr&& storage) { - if(mStorage) - delete mStorage; - mStorage = p; - } - - bool empty() const - { - return mStorage == nullptr; - } - - const std::type_info& getType() const - { - return typeid(mStorage); - } - - DerivedClassStorage():mStorage(nullptr){} - ~DerivedClassStorage() - { - if(mStorage) - delete mStorage; + mStorage = std::move(storage); } }; - - - /// \brief base class for the temporary storage of AiPackages. - /** - * Each AI package with temporary values needs a AiPackageStorage class - * which is derived from AiTemporaryBase. The Actor holds a container - * AiState where one of these storages can be stored at a time. - * The execute(...) member function takes this container as an argument. - * */ - struct AiTemporaryBase - { - virtual ~AiTemporaryBase(){} - }; - - /// \brief Container for AI package status. - typedef DerivedClassStorage AiState; } #endif // AISTATE_H diff --git a/apps/openmw/mwmechanics/aistatefwd.hpp b/apps/openmw/mwmechanics/aistatefwd.hpp new file mode 100644 index 000000000..83216d1d2 --- /dev/null +++ b/apps/openmw/mwmechanics/aistatefwd.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_MWMECHANICS_AISTATEFWD_H +#define OPENMW_MWMECHANICS_AISTATEFWD_H + +namespace MWMechanics +{ + template + class DerivedClassStorage; + + struct AiTemporaryBase; + + /// \brief Container for AI package status. + using AiState = DerivedClassStorage; +} + +#endif diff --git a/apps/openmw/mwmechanics/aitemporarybase.hpp b/apps/openmw/mwmechanics/aitemporarybase.hpp new file mode 100644 index 000000000..8ac8e4b71 --- /dev/null +++ b/apps/openmw/mwmechanics/aitemporarybase.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_MWMECHANICS_AISTATE_H +#define OPENMW_MWMECHANICS_AISTATE_H + +namespace MWMechanics +{ + /// \brief base class for the temporary storage of AiPackages. + /** + * Each AI package with temporary values needs a AiPackageStorage class + * which is derived from AiTemporaryBase. The Actor holds a container + * AiState where one of these storages can be stored at a time. + * The execute(...) member function takes this container as an argument. + * */ + struct AiTemporaryBase + { + virtual ~AiTemporaryBase() = default; + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aitimer.hpp b/apps/openmw/mwmechanics/aitimer.hpp index 804cda1bd..d2e023f1c 100644 --- a/apps/openmw/mwmechanics/aitimer.hpp +++ b/apps/openmw/mwmechanics/aitimer.hpp @@ -10,16 +10,22 @@ namespace MWMechanics class AiReactionTimer { - public: - static constexpr float sDeviation = 0.1f; + public: + static constexpr float sDeviation = 0.1f; - Misc::TimerStatus update(float duration) { return mImpl.update(duration); } + AiReactionTimer(Misc::Rng::Generator& prng) + : mPrng{ prng } + , mImpl{ AI_REACTION_TIME, sDeviation, Misc::Rng::deviate(0, sDeviation, prng) } + { + } - void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); } + Misc::TimerStatus update(float duration) { return mImpl.update(duration, mPrng); } - private: - Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation, - Misc::Rng::deviate(0, sDeviation)}; + void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation, mPrng)); } + + private: + Misc::Rng::Generator& mPrng; + Misc::DeviatingPeriodicTimer mImpl; }; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 8e5372c46..01187c98e 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,119 +1,151 @@ #include "aitravel.hpp" -#include +#include + +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" -#include "movement.hpp" #include "creaturestats.hpp" +#include "movement.hpp" namespace { -bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) -{ - // Maximum travel distance for vanilla compatibility. - // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. - // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. - return (pos1 - pos2).length2() <= 7168*7168; -} + constexpr float TRAVEL_FINISH_TIME = 2.f; + + bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) + { + // Maximum travel distance for vanilla compatibility. + // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in + // interior cells as well. We can make this configurable at some point, but the default *must* be the below + // value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. + return (pos1 - pos2).length2() <= 7168 * 7168; + } } namespace MWMechanics { - AiTravel::AiTravel(float x, float y, float z, AiTravel*) - : mX(x), mY(y), mZ(z), mHidden(false) + AiTravel::AiTravel(float x, float y, float z, bool repeat, AiTravel*) + : TypedAiPackage(repeat) + , mX(x) + , mY(y) + , mZ(z) + , mHidden(false) + , mDestinationTimer(TRAVEL_FINISH_TIME) { } AiTravel::AiTravel(float x, float y, float z, AiInternalTravel* derived) - : TypedAiPackage(derived), mX(x), mY(y), mZ(z), mHidden(true) + : TypedAiPackage(derived) + , mX(x) + , mY(y) + , mZ(z) + , mHidden(true) + , mDestinationTimer(TRAVEL_FINISH_TIME) { } - AiTravel::AiTravel(float x, float y, float z) - : AiTravel(x, y, z, this) + AiTravel::AiTravel(float x, float y, float z, bool repeat) + : AiTravel(x, y, z, repeat, this) { } - AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) - : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) + AiTravel::AiTravel(const ESM::AiSequence::AiTravel* travel) + : TypedAiPackage(travel->mRepeat) + , mX(travel->mData.mX) + , mY(travel->mData.mY) + , mZ(travel->mData.mZ) + , mHidden(false) + , mDestinationTimer(TRAVEL_FINISH_TIME) { // Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type assert(!travel->mHidden); } - bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + bool AiTravel::execute( + const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); auto& stats = actor.getClass().getCreatureStats(actor); - if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak) - && (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress)) + if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) + && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak) + && (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress)) return false; const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); const osg::Vec3f targetPos(mX, mY, mZ); stats.setMovementFlag(CreatureStats::Flag_Run, false); - stats.setDrawState(DrawState_Nothing); + stats.setDrawState(DrawState::Nothing); // Note: we should cancel internal "return after combat" package, if original location is too far away if (!isWithinMaxRange(targetPos, actorPos)) return mHidden; - // Unfortunately, with vanilla assets destination is sometimes blocked by other actor. - // If we got close to target, check for actors nearby. If they are, finish AI package. - int destinationTolerance = 64; - if (distance(actorPos, targetPos) <= destinationTolerance) - { - std::vector targetActors; - std::pair result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors); - - if (!result.first.isEmpty()) - { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - return true; - } - } - if (pathTo(actor, targetPos, duration)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return true; } - return false; + + // If we've been close enough to the destination for some time give up like Morrowind. + // The end condition should be pretty much accurate. + // FIXME: But the timing isn't. Right now we're being very generous, + // but Morrowind might stop the actor prematurely under unclear conditions. + + // Note Morrowind uses the halved eye level, but this is close enough. + float dist + = distanceIgnoreZ(actorPos, targetPos) - MWBase::Environment::get().getWorld()->getHalfExtents(actor).z(); + const float endTolerance = std::max(64.f, actor.getClass().getCurrentSpeed(actor) * duration); + + // Even if we have entered the threshold, we might have been pushed away. Reset the timer if we're currently too + // far. + if (dist > endTolerance) + { + mDestinationTimer = TRAVEL_FINISH_TIME; + return false; + } + + mDestinationTimer -= duration; + if (mDestinationTimer > 0) + return false; + + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + return true; } void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) { - if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) + osg::Vec3f pos(mX, mY, mZ); + if (!isWithinMaxRange(pos, actor.getRefData().getPosition().asVec3())) return; // does not do any validation on the travel target (whether it's in air, inside collision geometry, etc), // that is the user's responsibility - MWBase::Environment::get().getWorld()->moveObject(actor, mX, mY, mZ); + MWBase::Environment::get().getWorld()->moveObject(actor, pos); actor.getClass().adjustPosition(actor, false); reset(); } - void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const + void AiTravel::writeState(ESM::AiSequence::AiSequence& sequence) const { - std::unique_ptr travel(new ESM::AiSequence::AiTravel()); + auto travel = std::make_unique(); travel->mData.mX = mX; travel->mData.mY = mY; travel->mData.mZ = mZ; travel->mHidden = mHidden; + travel->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Travel; - package.mPackage = travel.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(travel); + sequence.mPackages.push_back(std::move(package)); } AiInternalTravel::AiInternalTravel(float x, float y, float z) @@ -131,4 +163,3 @@ namespace MWMechanics return std::make_unique(*this); } } - diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 2ea2a8f71..d8e249d46 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -1,69 +1,72 @@ -#ifndef GAME_MWMECHANICS_AITRAVEL_H -#define GAME_MWMECHANICS_AITRAVEL_H - -#include "typedaipackage.hpp" - -namespace ESM -{ -namespace AiSequence -{ - struct AiTravel; -} -} - -namespace MWMechanics -{ - struct AiInternalTravel; - - /// \brief Causes the AI to travel to the specified point - class AiTravel : public TypedAiPackage - { - public: - AiTravel(float x, float y, float z, AiTravel* derived); - - AiTravel(float x, float y, float z, AiInternalTravel* derived); - - AiTravel(float x, float y, float z); - - explicit AiTravel(const ESM::AiSequence::AiTravel* travel); - - /// Simulates the passing of time - void fastForward(const MWWorld::Ptr& actor, AiState& state) override; - - void writeState(ESM::AiSequence::AiSequence &sequence) const override; - - bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; - - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Travel; } - - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mUseVariableSpeed = true; - options.mAlwaysActive = true; - return options; - } - - osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); } - - private: - const float mX; - const float mY; - const float mZ; - - const bool mHidden; - }; - - struct AiInternalTravel final : public AiTravel - { - AiInternalTravel(float x, float y, float z); - - explicit AiInternalTravel(const ESM::AiSequence::AiTravel* travel); - - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::InternalTravel; } - - std::unique_ptr clone() const override; - }; -} - -#endif +#ifndef GAME_MWMECHANICS_AITRAVEL_H +#define GAME_MWMECHANICS_AITRAVEL_H + +#include "typedaipackage.hpp" + +namespace ESM +{ + namespace AiSequence + { + struct AiTravel; + } +} + +namespace MWMechanics +{ + struct AiInternalTravel; + + /// \brief Causes the AI to travel to the specified point + class AiTravel : public TypedAiPackage + { + public: + AiTravel(float x, float y, float z, bool repeat, AiTravel* derived); + + AiTravel(float x, float y, float z, AiInternalTravel* derived); + + AiTravel(float x, float y, float z, bool repeat); + + explicit AiTravel(const ESM::AiSequence::AiTravel* travel); + + /// Simulates the passing of time + void fastForward(const MWWorld::Ptr& actor, AiState& state) override; + + void writeState(ESM::AiSequence::AiSequence& sequence) const override; + + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; + + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Travel; } + + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mUseVariableSpeed = true; + options.mAlwaysActive = true; + return options; + } + + osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); } + + private: + const float mX; + const float mY; + const float mZ; + + const bool mHidden; + + float mDestinationTimer; + }; + + struct AiInternalTravel final : public AiTravel + { + AiInternalTravel(float x, float y, float z); + + explicit AiInternalTravel(const ESM::AiSequence::AiTravel* travel); + + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::InternalTravel; } + + std::unique_ptr clone() const override; + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 175836b11..2467ee802 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -2,26 +2,28 @@ #include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/cellstore.hpp" -#include "../mwphysics/collisiontype.hpp" +#include "../mwphysics/raycasting.hpp" -#include "pathgrid.hpp" +#include "actorutil.hpp" #include "creaturestats.hpp" #include "movement.hpp" -#include "actorutil.hpp" +#include "pathgrid.hpp" namespace MWMechanics { @@ -36,8 +38,7 @@ namespace MWMechanics static const std::size_t MAX_IDLE_SIZE = 8; - const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = - { + const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { std::string("idle2"), std::string("idle3"), std::string("idle4"), @@ -59,44 +60,39 @@ namespace MWMechanics osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance) { - const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * osg::PI; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const float randomDirection = Misc::Rng::rollClosedProbability(prng) * 2.0f * osg::PI; osg::Matrixf rotation; rotation.makeRotate(randomDirection, osg::Vec3f(0.0, 0.0, 1.0)); return position + osg::Vec3f(distance, 0.0, 0.0) * rotation; } - bool isDestinationHidden(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) + bool isDestinationHidden(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination) { const auto position = actor.getRefData().getPosition().asVec3(); const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor); - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + const osg::Vec3f halfExtents + = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor).mHalfExtents; osg::Vec3f direction = destination - position; direction.normalize(); - const auto visibleDestination = ( - isWaterCreature || isFlyingCreature - ? destination - : destination + osg::Vec3f(0, 0, halfExtents.z()) - ) + direction * std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); - const int mask = MWPhysics::CollisionType_World - | MWPhysics::CollisionType_HeightMap - | MWPhysics::CollisionType_Door - | MWPhysics::CollisionType_Actor; - return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor); - } - - bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) - { - const auto world = MWBase::Environment::get().getWorld(); - const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); - const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); - return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor); + const auto visibleDestination + = (isWaterCreature || isFlyingCreature ? destination : destination + osg::Vec3f(0, 0, halfExtents.z())) + + direction * std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + const int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap + | MWPhysics::CollisionType_Door | MWPhysics::CollisionType_Actor; + return MWBase::Environment::get() + .getWorld() + ->getRayCasting() + ->castRay(position, visibleDestination, actor, {}, mask) + .mHit; } void stopMovement(const MWWorld::Ptr& actor) { - actor.getClass().getMovementSettings(actor).mPosition[0] = 0; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + auto& movementSettings = actor.getClass().getMovementSettings(actor); + movementSettings.mPosition[0] = 0; + movementSettings.mPosition[1] = 0; } std::vector getInitialIdle(const std::vector& idle) @@ -110,16 +106,36 @@ namespace MWMechanics { return std::vector(std::begin(idle), std::end(idle)); } + } - AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): - TypedAiPackage(makeDefaultOptions().withRepeat(repeat)), - mDistance(std::max(0, distance)), - mDuration(std::max(0, duration)), - mRemainingDuration(duration), mTimeOfDay(timeOfDay), - mIdle(getInitialIdle(idle)), - mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), - mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false) + AiWanderStorage::AiWanderStorage() + : mReaction(MWBase::Environment::get().getWorld()->getPrng()) + , mState(Wander_ChooseAction) + , mIsWanderingManually(false) + , mCanWanderAlongPathGrid(true) + , mIdleAnimation(0) + , mBadIdles() + , mPopulateAvailableNodes(true) + , mAllowedNodes() + , mTrimCurrentNode(false) + , mCheckIdlePositionTimer(0) + , mStuckCount(0) + { + } + + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat) + : TypedAiPackage(repeat) + , mDistance(std::max(0, distance)) + , mDuration(std::max(0, duration)) + , mRemainingDuration(duration) + , mTimeOfDay(timeOfDay) + , mIdle(getInitialIdle(idle)) + , mStoredInitialActorPosition(false) + , mInitialActorPosition(osg::Vec3f(0, 0, 0)) + , mHasDestination(false) + , mDestination(osg::Vec3f(0, 0, 0)) + , mUsePathgrid(false) { } @@ -173,7 +189,8 @@ namespace MWMechanics * actors will enter combat (i.e. no longer wandering) and different pathfinding * will kick in. */ - bool AiWander::execute (const MWWorld::Ptr& actor, CharacterController& /*characterController*/, AiState& state, float duration) + bool AiWander::execute( + const MWWorld::Ptr& actor, CharacterController& /*characterController*/, AiState& state, float duration) { MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor); if (cStats.isDead() || cStats.getHealth().getCurrent() <= 0) @@ -182,9 +199,9 @@ namespace MWMechanics // get or create temporary storage AiWanderStorage& storage = state.get(); - mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); + mRemainingDuration -= ((duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); - cStats.setDrawState(DrawState_Nothing); + cStats.setDrawState(DrawState::Nothing); cStats.setMovementFlag(CreatureStats::Flag_Run, false); ESM::Position pos = actor.getRefData().getPosition(); @@ -193,23 +210,27 @@ namespace MWMechanics // rebuild a path to it if (!mPathFinder.isPathConstructed() && mHasDestination) { + const ESM::Pathgrid* pathgrid + = MWBase::Environment::get().getESMStore()->get().search(*actor.getCell()->getCell()); if (mUsePathgrid) { - mPathFinder.buildPathByPathgrid(pos.asVec3(), mDestination, actor.getCell(), - getPathGridGraph(actor.getCell())); + mPathFinder.buildPathByPathgrid( + pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid)); } else { - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); - mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), - getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); + const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor); + constexpr float endTolerance = 0; + mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid), + agentBounds, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, PathType::Full); } if (mPathFinder.isPathConstructed()) storage.setState(AiWanderStorage::Wander_Walking); } - if(!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) && !cStats.getMovementFlag(CreatureStats::Flag_ForceSneak)) + if (!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) + && !cStats.getMovementFlag(CreatureStats::Flag_ForceSneak)) { GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor); if (greetingState == Greet_InProgress) @@ -240,7 +261,7 @@ namespace MWMechanics { stopWalking(actor); // Reset package so it can be used again - mRemainingDuration=mDuration; + mRemainingDuration = mDuration; return true; } @@ -253,12 +274,15 @@ namespace MWMechanics // Initialization to discover & store allowed node points for this actor. if (storage.mPopulateAvailableNodes) { - getAllowedNodes(actor, actor.getCell()->getCell(), storage); + getAllowedNodes(actor, storage); } - if (canActorMoveByZAxis(actor) && mDistance > 0) { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (canActorMoveByZAxis(actor) && mDistance > 0) + { // Typically want to idle for a short time before the next wander - if (Misc::Rng::rollDice(100) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) { + if (Misc::Rng::rollDice(100, prng) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) + { wanderNearStart(actor, storage, mDistance); } @@ -266,26 +290,40 @@ namespace MWMechanics } // If the package has a wander distance but no pathgrid is available, // randomly idle or wander near spawn point - else 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) { + if (Misc::Rng::rollDice(100, prng) >= 96) + { wanderNearStart(actor, storage, mDistance); - } else { + } + else + { storage.setState(AiWanderStorage::Wander_IdleNow); } - } else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually) { + } + else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually) + { storage.mCanWanderAlongPathGrid = false; } // If Wandering manually and hit an obstacle, stop - if (storage.mIsWanderingManually && mObstacleCheck.isEvading()) { + if (storage.mIsWanderingManually && mObstacleCheck.isEvading()) + { completeManualWalking(actor, storage); } + if (storage.mState == AiWanderStorage::Wander_Walking && mUsePathgrid) + { + const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor); + mPathFinder.buildPathByNavMeshToNextPoint( + actor, agentBounds, getNavigatorFlags(actor), getAreaCosts(actor)); + } + if (storage.mState == AiWanderStorage::Wander_MoveNow && storage.mCanWanderAlongPathGrid) { // Construct a new path if there isn't one - if(!mPathFinder.isPathConstructed()) + if (!mPathFinder.isPathConstructed()) { if (!storage.mAllowedNodes.empty()) { @@ -298,10 +336,8 @@ namespace MWMechanics completeManualWalking(actor, storage); } - if (storage.mIsWanderingManually - && storage.mState == AiWanderStorage::Wander_Walking - && (mPathFinder.getPathSize() == 0 - || isDestinationHidden(actor, mPathFinder.getPath().back()) + if (storage.mIsWanderingManually && storage.mState == AiWanderStorage::Wander_Walking + && (mPathFinder.getPathSize() == 0 || isDestinationHidden(actor, mPathFinder.getPath().back()) || isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back()))) completeManualWalking(actor, storage); @@ -325,28 +361,47 @@ namespace MWMechanics /* * Commands actor to walk to a random location near original spawn location. */ - void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) { + void AiWander::wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance) + { const auto currentPosition = actor.getRefData().getPosition().asVec3(); std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor); const auto world = MWBase::Environment::get().getWorld(); - const auto halfExtents = world->getPathfindingHalfExtents(actor); + const auto agentBounds = world->getPathfindingAgentBounds(actor); const auto navigator = world->getNavigator(); const auto navigatorFlags = getNavigatorFlags(actor); const auto areaCosts = getAreaCosts(actor); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + + do + { - do { // Determine a random location within radius of original position - const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance; + const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability(prng) * 0.8f) * wanderDistance; if (!isWaterCreature && !isFlyingCreature) { // findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance - if (const auto destination = navigator->findRandomPointAroundCircle(halfExtents, mInitialActorPosition, wanderDistance, navigatorFlags)) - mDestination = *destination; - else - mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius); + const auto getRandom + = []() { return Misc::Rng::rollProbability(MWBase::Environment::get().getWorld()->getPrng()); }; + auto destination = DetourNavigator::findRandomPointAroundCircle( + *navigator, agentBounds, mInitialActorPosition, wanderRadius, navigatorFlags, getRandom); + if (destination.has_value()) + { + osg::Vec3f direction = *destination - mInitialActorPosition; + if (direction.length() > wanderDistance) + { + direction.normalize(); + const osg::Vec3f adjustedDestination = mInitialActorPosition + direction * wanderRadius; + destination = DetourNavigator::raycast( + *navigator, agentBounds, currentPosition, adjustedDestination, navigatorFlags); + if (destination.has_value() && (*destination - mInitialActorPosition).length() > wanderDistance) + continue; + } + } + mDestination = destination.has_value() ? *destination + : getRandomPointAround(mInitialActorPosition, wanderRadius); } else mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius); @@ -361,11 +416,13 @@ namespace MWMechanics if (isAreaOccupiedByOtherActor(actor, mDestination)) continue; + constexpr float endTolerance = 0; + if (isWaterCreature || isFlyingCreature) mPathFinder.buildStraightPath(mDestination); else - mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents, navigatorFlags, - areaCosts); + mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, agentBounds, navigatorFlags, + areaCosts, endTolerance, PathType::Full); if (mPathFinder.isPathConstructed()) { @@ -381,14 +438,17 @@ namespace MWMechanics /* * Returns true if the position provided is above water. */ - bool AiWander::destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination) { - float heightToGroundOrWater = MWBase::Environment::get().getWorld()->getDistToNearestRayHit(destination, osg::Vec3f(0,0,-1), 1000.0, true); + bool AiWander::destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination) + { + float heightToGroundOrWater = MWBase::Environment::get().getWorld()->getDistToNearestRayHit( + destination, osg::Vec3f(0, 0, -1), 1000.0, true); osg::Vec3f positionBelowSurface = destination; positionBelowSurface[2] = positionBelowSurface[2] - heightToGroundOrWater - 1.0f; return MWBase::Environment::get().getWorld()->isUnderwater(actor.getCell(), positionBelowSurface); } - void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { + void AiWander::completeManualWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage) + { stopWalking(actor); mObstacleCheck.clear(); storage.setState(AiWanderStorage::Wander_IdleNow); @@ -411,7 +471,7 @@ namespace MWMechanics break; case AiWanderStorage::Wander_MoveNow: - break; // nothing to do + break; // nothing to do default: // should never get here @@ -427,7 +487,7 @@ namespace MWMechanics if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary()) { - storage.mCheckIdlePositionTimer = 0; // restart timer + storage.mCheckIdlePositionTimer = 0; // restart timer static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f; if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance)) { @@ -451,11 +511,9 @@ namespace MWMechanics bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const { const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); - auto cell = actor.getCell()->getCell(); for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes) { osg::Vec3f point(node.mX, node.mY, node.mZ); - Misc::CoordinateConverter(cell).toWorld(point); if ((actorPos - point).length2() < distance * distance) return true; } @@ -465,7 +523,8 @@ namespace MWMechanics void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { // Is there no destination or are we there yet? - if ((!mPathFinder.isPathConstructed()) || pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE)) + if ((!mPathFinder.isPathConstructed()) + || pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE)) { stopWalking(actor); storage.setState(AiWanderStorage::Wander_ChooseAction); @@ -491,11 +550,11 @@ namespace MWMechanics storage.setState(AiWanderStorage::Wander_MoveNow); return; } - if(idleAnimation) + if (idleAnimation) { - if(std::find(storage.mBadIdles.begin(), storage.mBadIdles.end(), idleAnimation)==storage.mBadIdles.end()) + if (std::find(storage.mBadIdles.begin(), storage.mBadIdles.end(), idleAnimation) == storage.mBadIdles.end()) { - if(!playIdle(actor, idleAnimation)) + if (!playIdle(actor, idleAnimation)) { storage.mBadIdles.push_back(idleAnimation); storage.setState(AiWanderStorage::Wander_ChooseAction); @@ -509,13 +568,6 @@ namespace MWMechanics void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage) { - if (mUsePathgrid) - { - const auto halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - mPathFinder.buildPathByNavMeshToNextPoint(actor, halfExtents, getNavigatorFlags(actor), - getAreaCosts(actor)); - } - if (mObstacleCheck.isEvading()) { // first check if we're walking into a door @@ -530,7 +582,7 @@ namespace MWMechanics storage.setState(AiWanderStorage::Wander_MoveNow); } - storage.mStuckCount++; // TODO: maybe no longer needed + storage.mStuckCount++; // TODO: maybe no longer needed } // if stuck for sufficiently long, act like current location was the destination @@ -543,28 +595,28 @@ namespace MWMechanics } } - - - void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) + void AiWander::setPathToAnAllowedNode( + const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) { - unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); - ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); + auto world = MWBase::Environment::get().getWorld(); + auto& prng = world->getPrng(); + unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng); + const ESM::Pathgrid::Point& dest = storage.mAllowedNodes[randNode]; - ToWorldCoordinates(dest, actor.getCell()->getCell()); - - // actor position is already in world coordinates const osg::Vec3f start = actorPos.asVec3(); // don't take shortcuts for wandering + const ESM::Pathgrid* pathgrid = world->getStore().get().search(*actor.getCell()->getCell()); const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest); - mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell())); + mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(pathgrid)); if (mPathFinder.isPathConstructed()) { mDestination = destVec3f; mHasDestination = true; mUsePathgrid = true; - // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): + // Remove this node as an option and add back the previously used node (stops NPC from picking the same + // node): ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode]; storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); // check if mCurrentNode was taken out of mAllowedNodes @@ -581,13 +633,7 @@ namespace MWMechanics storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); } - void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) - { - Misc::CoordinateConverter(cell).toWorld(point); - } - - void AiWander::trimAllowedNodes(std::vector& nodes, - const PathFinder& pathfinder) + void AiWander::trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder) { // TODO: how to add these back in once the door opens? // Idea: keep a list of detected closed doors (see aicombat.cpp) @@ -595,10 +641,10 @@ namespace MWMechanics // at the end of playing idle?) If the door is opened then re-calculate // allowed nodes starting from the spawn point. auto paths = pathfinder.getPath(); - while(paths.size() >= 2) + while (paths.size() >= 2) { const auto pt = paths.back(); - for(unsigned int j = 0; j < nodes.size(); j++) + for (unsigned int j = 0; j < nodes.size(); j++) { // FIXME: doesn't handle a door with the same X/Y // coordinates but with a different Z @@ -628,7 +674,8 @@ namespace MWMechanics } else { - Log(Debug::Verbose) << "Attempted to play out of range idle animation \"" << idleSelect << "\" for " << actor.getCellRef().getRefId(); + Log(Debug::Verbose) << "Attempted to play out of range idle animation \"" << idleSelect << "\" for " + << actor.getCellRef().getRefId(); return false; } } @@ -646,28 +693,30 @@ namespace MWMechanics } } - short unsigned AiWander::getRandomIdle() + int AiWander::getRandomIdle() const { - unsigned short idleRoll = 0; - short unsigned selectedAnimation = 0; + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fIdleChanceMultiplier + = world->getStore().get().find("fIdleChanceMultiplier")->mValue.getFloat(); + if (Misc::Rng::rollClosedProbability(world->getPrng()) > fIdleChanceMultiplier) + return 0; - for(unsigned int counter = 0; counter < mIdle.size(); counter++) + int newIdle = 0; + float maxRoll = 0.f; + for (size_t i = 0; i < mIdle.size(); i++) { - static float fIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore() - .get().find("fIdleChanceMultiplier")->mValue.getFloat(); - - unsigned short idleChance = static_cast(fIdleChanceMultiplier * mIdle[counter]); - unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); - if(randSelect < idleChance && randSelect > idleRoll) + float roll = Misc::Rng::rollClosedProbability(world->getPrng()) * 100.f; + if (roll <= mIdle[i] && roll > maxRoll) { - selectedAnimation = counter + GroupIndex_MinIdle; - idleRoll = randSelect; + newIdle = GroupIndex_MinIdle + i; + maxRoll = roll; } } - return selectedAnimation; + + return newIdle; } - void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) + void AiWander::fastForward(const MWWorld::Ptr& actor, AiState& state) { // Update duration counter mRemainingDuration--; @@ -676,17 +725,19 @@ namespace MWMechanics AiWanderStorage& storage = state.get(); if (storage.mPopulateAvailableNodes) - getAllowedNodes(actor, actor.getCell()->getCell(), storage); + getAllowedNodes(actor, storage); if (storage.mAllowedNodes.empty()) return; - int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); - ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; - ESM::Pathgrid::Point worldDest = dest; - ToWorldCoordinates(worldDest, actor.getCell()->getCell()); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng); + ESM::Pathgrid::Point worldDest = storage.mAllowedNodes[index]; + auto converter = Misc::CoordinateConverter(*actor.getCell()->getCell()); + ESM::Pathgrid::Point dest = converter.toLocalPoint(worldDest); - bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60); + bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange( + PathFinder::makeOsgVec3(worldDest), 60); // add offset only if the selected pathgrid is occupied by another actor if (isPathGridOccupied) @@ -698,13 +749,12 @@ namespace MWMechanics 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++) + while (!points.empty()) { - int randomIndex = Misc::Rng::rollDice(points.size()); - ESM::Pathgrid::Point connDest = points[randomIndex]; + int randomIndex = Misc::Rng::rollDice(points.size(), prng); + const ESM::Pathgrid::Point& connDest = points[randomIndex]; // add an offset towards random neighboring node osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest); @@ -714,11 +764,12 @@ namespace MWMechanics 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()); + dest + = PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f)); + worldDest = converter.toWorldPoint(dest); - isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60); + isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange( + PathFinder::makeOsgVec3(worldDest), 60); if (!isOccupied) break; @@ -728,7 +779,7 @@ namespace MWMechanics break; // Will try an another neighboring node - points.erase(points.begin()+randomIndex); + points.erase(points.begin() + randomIndex); } // there is no free space, nowhere to move @@ -736,38 +787,42 @@ namespace MWMechanics 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. + // 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; - ToWorldCoordinates(dest, actor.getCell()->getCell()); + converter.toWorld(dest); - state.moveIn(new AiWanderStorage()); + state.moveIn(std::make_unique()); - MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), - static_cast(dest.mY), static_cast(dest.mZ)); + osg::Vec3f pos(static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); + MWBase::Environment::get().getWorld()->moveObject(actor, pos); actor.getClass().adjustPosition(actor, false); } - void AiWander::getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points) + void AiWander::getNeighbouringNodes( + ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points) { - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); + const ESM::Pathgrid* pathgrid + = MWBase::Environment::get().getESMStore()->get().search(*currentCell->getCell()); + + if (pathgrid == nullptr || pathgrid->mPoints.empty()) + return; if (pathgrid == nullptr || pathgrid->mPoints.empty()) return; int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest)); - getPathGridGraph(currentCell).getNeighbouringPoints(index, points); + getPathGridGraph(pathgrid).getNeighbouringPoints(index, points); } - void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage) + void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage) { // infrequently used, therefore no benefit in caching it as a member - const ESM::Pathgrid * - pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); const MWWorld::CellStore* cellStore = actor.getCell(); + const ESM::Pathgrid* pathgrid + = MWBase::Environment::get().getESMStore()->get().search(*cellStore->getCell()); storage.mAllowedNodes.clear(); @@ -775,7 +830,7 @@ namespace MWMechanics // https://forum.openmw.org/viewtopic.php?t=1556 // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 // Note: In order to wander, need at least two points. - if(!pathgrid || (pathgrid->mPoints.size() < 2)) + if (!pathgrid || (pathgrid->mPoints.size() < 2)) storage.mCanWanderAlongPathGrid = false; // A distance value passed into the constructor indicates how far the @@ -786,33 +841,34 @@ namespace MWMechanics if (mDistance && storage.mCanWanderAlongPathGrid && !actor.getClass().isPureWaterCreature(actor)) { // get NPC's position in local (i.e. cell) coordinates - osg::Vec3f npcPos(mInitialActorPosition); - Misc::CoordinateConverter(cell).toLocal(npcPos); + auto converter = Misc::CoordinateConverter(*cellStore->getCell()); + const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition); // Find closest pathgrid point int closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos); // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // and if the point is connected to the closest current point - // NOTE: mPoints and mAllowedNodes are in local coordinates - int pointIndex = 0; - for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) + // NOTE: mPoints is in local coordinates + size_t pointIndex = 0; + for (size_t counter = 0; counter < pathgrid->mPoints.size(); counter++) { osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter])); - if((npcPos - nodePos).length2() <= mDistance * mDistance && - getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter)) + if ((npcPos - nodePos).length2() <= mDistance * mDistance + && getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, counter)) { - storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]); + storage.mAllowedNodes.push_back(converter.toWorldPoint(pathgrid->mPoints[counter])); pointIndex = counter; } } if (storage.mAllowedNodes.size() == 1) { - AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex, storage); + storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(mInitialActorPosition)); + addNonPathGridAllowedPoints(pathgrid, pointIndex, storage, converter); } - if(!storage.mAllowedNodes.empty()) + if (!storage.mAllowedNodes.empty()) { - SetCurrentNodeToClosestAllowedNode(npcPos, storage); + setCurrentNodeToClosestAllowedNode(storage); } } @@ -823,19 +879,21 @@ namespace MWMechanics // additional points for NPC to wander to are: // 1. NPC's initial location // 2. Partway along the path between the point and its connected points. - void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage) + void AiWander::addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, + AiWanderStorage& storage, const Misc::CoordinateConverter& converter) { - storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(npcPos)); - for (auto& edge : pathGrid->mEdges) + for (const auto& edge : pathGrid->mEdges) { if (edge.mV0 == pointIndex) { - AddPointBetweenPathGridPoints(pathGrid->mPoints[edge.mV0], pathGrid->mPoints[edge.mV1], storage); + AddPointBetweenPathGridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]), + converter.toWorldPoint(pathGrid->mPoints[edge.mV1]), storage); } } } - void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage) + void AiWander::AddPointBetweenPathGridPoints( + const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage) { osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start); osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart; @@ -850,17 +908,17 @@ namespace MWMechanics storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta)); } - void AiWander::SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage) + void AiWander::setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage) { float distanceToClosestNode = std::numeric_limits::max(); - unsigned int index = 0; - for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++) + size_t index = 0; + for (size_t i = 0; i < storage.mAllowedNodes.size(); ++i) { - osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[counterThree])); - float tempDist = (npcPos - nodePos).length2(); + osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[i])); + float tempDist = (mInitialActorPosition - nodePos).length2(); if (tempDist < distanceToClosestNode) { - index = counterThree; + index = i; distanceToClosestNode = tempDist; } } @@ -868,7 +926,7 @@ namespace MWMechanics storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index); } - void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const + void AiWander::writeState(ESM::AiSequence::AiSequence& sequence) const { float remainingDuration; if (mRemainingDuration > 0 && mRemainingDuration < 24) @@ -876,13 +934,13 @@ namespace MWMechanics else remainingDuration = mDuration; - std::unique_ptr wander(new ESM::AiSequence::AiWander()); + auto wander = std::make_unique(); wander->mData.mDistance = mDistance; wander->mData.mDuration = mDuration; wander->mData.mTimeOfDay = mTimeOfDay; wander->mDurationData.mRemainingDuration = remainingDuration; - assert (mIdle.size() == 8); - for (int i=0; i<8; ++i) + assert(mIdle.size() == 8); + for (int i = 0; i < 8; ++i) wander->mData.mIdle[i] = mIdle[i]; wander->mData.mShouldRepeat = mOptions.mRepeat; wander->mStoredInitialActorPosition = mStoredInitialActorPosition; @@ -891,11 +949,11 @@ namespace MWMechanics ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Wander; - package.mPackage = wander.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(wander); + sequence.mPackages.push_back(std::move(package)); } - AiWander::AiWander (const ESM::AiSequence::AiWander* wander) + AiWander::AiWander(const ESM::AiSequence::AiWander* wander) : TypedAiPackage(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0)) , mDistance(std::max(static_cast(0), wander->mData.mDistance)) , mDuration(std::max(static_cast(0), wander->mData.mDuration)) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index f8506ff59..8b74ed72d 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -5,10 +5,9 @@ #include -#include "pathfinding.hpp" -#include "obstacle.hpp" -#include "aistate.hpp" +#include "aitemporarybase.hpp" #include "aitimer.hpp" +#include "pathfinding.hpp" namespace ESM { @@ -19,6 +18,15 @@ namespace ESM } } +namespace Misc +{ + class CoordinateConverter; +} + +namespace MWWorld +{ + class Cell; +} namespace MWMechanics { /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. @@ -46,7 +54,6 @@ namespace MWMechanics bool mPopulateAvailableNodes; // allowed pathgrid nodes based on mDistance from the spawn point - // in local coordinates of mCell std::vector mAllowedNodes; ESM::Pathgrid::Point mCurrentNode; @@ -55,18 +62,7 @@ namespace MWMechanics float mCheckIdlePositionTimer; int mStuckCount; - AiWanderStorage(): - mState(Wander_ChooseAction), - mIsWanderingManually(false), - mCanWanderAlongPathGrid(true), - mIdleAnimation(0), - mBadIdles(), - mPopulateAvailableNodes(true), - mAllowedNodes(), - mTrimCurrentNode(false), - mCheckIdlePositionTimer(0), - mStuckCount(0) - {}; + AiWanderStorage(); void setState(const WanderState wanderState, const bool isManualWander = false) { @@ -78,104 +74,106 @@ namespace MWMechanics /// \brief Causes the Actor to wander within a specified range class AiWander final : public TypedAiPackage { - public: - /// Constructor - /** \param distance Max distance the ACtor will wander - \param duration Time, in hours, that this package will be preformed - \param timeOfDay Currently unimplemented. Not functional in the original engine. - \param idle Chances of each idle to play (9 in total) - \param repeat Repeat wander or not **/ - AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); + public: + /// Constructor + /** \param distance Max distance the ACtor will wander + \param duration Time, in hours, that this package will be preformed + \param timeOfDay Currently unimplemented. Not functional in the original engine. + \param idle Chances of each idle to play (9 in total) + \param repeat Repeat wander or not **/ + AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); - explicit AiWander (const ESM::AiSequence::AiWander* wander); + explicit AiWander(const ESM::AiSequence::AiWander* wander); - bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; + bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, + float duration) override; - static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Wander; } + static constexpr AiPackageTypeId getTypeId() { return AiPackageTypeId::Wander; } - static constexpr Options makeDefaultOptions() - { - AiPackage::Options options; - options.mUseVariableSpeed = true; - return options; - } + static constexpr Options makeDefaultOptions() + { + AiPackage::Options options; + options.mUseVariableSpeed = true; + return options; + } - void writeState(ESM::AiSequence::AiSequence &sequence) const override; + void writeState(ESM::AiSequence::AiSequence& sequence) const override; - void fastForward(const MWWorld::Ptr& actor, AiState& state) override; + void fastForward(const MWWorld::Ptr& actor, AiState& state) override; - osg::Vec3f getDestination(const MWWorld::Ptr& actor) const override; + osg::Vec3f getDestination(const MWWorld::Ptr& actor) const override; - osg::Vec3f getDestination() const override - { - if (!mHasDestination) - return osg::Vec3f(0, 0, 0); + osg::Vec3f getDestination() const override + { + if (!mHasDestination) + return osg::Vec3f(0, 0, 0); - return mDestination; - } + return mDestination; + } - bool isStationary() const { return mDistance == 0; } + bool isStationary() const { return mDistance == 0; } - private: - void stopWalking(const MWWorld::Ptr& actor); + private: + void stopWalking(const MWWorld::Ptr& actor); - /// Have the given actor play an idle animation - /// @return Success or error - bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); - bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); - short unsigned getRandomIdle(); - void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); - void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); - void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); - void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); - void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); - bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); - inline bool isPackageCompleted() const; - void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); - bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); - void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage); - bool isNearAllowedNode(const MWWorld::Ptr &actor, const AiWanderStorage& storage, float distance) const; + /// Have the given actor play an idle animation + /// @return Success or error + bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); + bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); + int getRandomIdle() const; + void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); + void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); + void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); + void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); + void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); + bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); + inline bool isPackageCompleted() const; + void wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance); + bool destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination); + void completeManualWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); + bool isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const; - const int mDistance; // how far the actor can wander from the spawn point - const int mDuration; - float mRemainingDuration; - const int mTimeOfDay; - const std::vector mIdle; + const int mDistance; // how far the actor can wander from the spawn point + const int mDuration; + float mRemainingDuration; + const int mTimeOfDay; + const std::vector mIdle; - bool mStoredInitialActorPosition; - osg::Vec3f mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell + bool mStoredInitialActorPosition; + osg::Vec3f + mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell - bool mHasDestination; - osg::Vec3f mDestination; - bool mUsePathgrid; + bool mHasDestination; + osg::Vec3f mDestination; + bool mUsePathgrid; - void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points); + 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 getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); + void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); - // constants for converting idleSelect values into groupNames - enum GroupIndex - { - GroupIndex_MinIdle = 2, - GroupIndex_MaxIdle = 9 - }; + // constants for converting idleSelect values into groupNames + enum GroupIndex + { + GroupIndex_MinIdle = 2, + GroupIndex_MaxIdle = 9 + }; - /// convert point from local (i.e. cell) to world coordinates - void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); + void setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage); - void SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage); + void addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, AiWanderStorage& storage, + const Misc::CoordinateConverter& converter); - void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage); + void AddPointBetweenPathGridPoints( + const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage); - void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage); + /// lookup table for converting idleSelect value to groupName + static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; - /// lookup table for converting idleSelect value to groupName - static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; - - static int OffsetToPreventOvercrowding(); + static int OffsetToPreventOvercrowding(); }; } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index de9709ba2..21365d2a7 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -4,15 +4,15 @@ #include #include -#include #include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include /* Start of tes3mp addition @@ -31,13 +31,12 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" -#include "magiceffects.hpp" #include "creaturestats.hpp" +#include "magiceffects.hpp" MWMechanics::Alchemy::Alchemy() : mValue(0) @@ -49,20 +48,20 @@ std::set MWMechanics::Alchemy::listEffects() const { std::map effects; - for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) + for (TIngredientsIterator iter(mIngredients.begin()); iter != mIngredients.end(); ++iter) { if (!iter->isEmpty()) { - const MWWorld::LiveCellRef *ingredient = iter->get(); + const MWWorld::LiveCellRef* ingredient = iter->get(); std::set seenEffects; - for (int i=0; i<4; ++i) - if (ingredient->mBase->mData.mEffectID[i]!=-1) + for (int i = 0; i < 4; ++i) + if (ingredient->mBase->mData.mEffectID[i] != -1) { - EffectKey key ( - ingredient->mBase->mData.mEffectID[i], ingredient->mBase->mData.mSkills[i]!=-1 ? - ingredient->mBase->mData.mSkills[i] : ingredient->mBase->mData.mAttributes[i]); + EffectKey key(ingredient->mBase->mData.mEffectID[i], + ingredient->mBase->mData.mSkills[i] != -1 ? ingredient->mBase->mData.mSkills[i] + : ingredient->mBase->mData.mAttributes[i]); if (seenEffects.insert(key).second) ++effects[key]; @@ -72,14 +71,14 @@ std::set MWMechanics::Alchemy::listEffects() const std::set effects2; - for (std::map::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) - if (iter->second>1) - effects2.insert (iter->first); + for (std::map::const_iterator iter(effects.begin()); iter != effects.end(); ++iter) + if (iter->second > 1) + effects2.insert(iter->first); return effects2; } -void MWMechanics::Alchemy::applyTools (int flags, float& value) const +void MWMechanics::Alchemy::applyTools(int flags, float& value) const { bool magnitude = !(flags & ESM::MagicEffect::NoMagnitude); bool duration = !(flags & ESM::MagicEffect::NoDuration); @@ -98,9 +97,10 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const else return; - float toolQuality = setup==1 || setup==2 ? mTools[tool].get()->mBase->mData.mQuality : 0; - float calcinatorQuality = setup==1 || setup==3 ? - mTools[ESM::Apparatus::Calcinator].get()->mBase->mData.mQuality : 0; + float toolQuality = setup == 1 || setup == 2 ? mTools[tool].get()->mBase->mData.mQuality : 0; + float calcinatorQuality = setup == 1 || setup == 3 + ? mTools[ESM::Apparatus::Calcinator].get()->mBase->mData.mQuality + : 0; float quality = 1; @@ -108,14 +108,14 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const { case 1: - quality = negative ? 2 * toolQuality + 3 * calcinatorQuality : - (magnitude && duration ? - 2 * toolQuality + calcinatorQuality : 2/3.0f * (toolQuality + calcinatorQuality) + 0.5f); + quality = negative ? 2 * toolQuality + 3 * calcinatorQuality + : (magnitude && duration ? 2 * toolQuality + calcinatorQuality + : 2 / 3.0f * (toolQuality + calcinatorQuality) + 0.5f); break; case 2: - quality = negative ? 1+toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5f); + quality = negative ? 1 + toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5f); break; case 3: @@ -124,14 +124,14 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const break; } - if (setup==3 || !negative) + if (setup == 3 || !negative) { value += quality; } else { - if (quality==0) - throw std::runtime_error ("invalid derived alchemy apparatus quality"); + if (quality == 0) + throw std::runtime_error("invalid derived alchemy apparatus quality"); value /= quality; } @@ -142,61 +142,73 @@ void MWMechanics::Alchemy::updateEffects() mEffects.clear(); mValue = 0; - if (countIngredients()<2 || mAlchemist.isEmpty() || mTools[ESM::Apparatus::MortarPestle].isEmpty()) + if (countIngredients() < 2 || mAlchemist.isEmpty() || mTools[ESM::Apparatus::MortarPestle].isEmpty()) return; // find effects - std::set effects (listEffects()); + std::set effects(listEffects()); // general alchemy factor float x = getAlchemyFactor(); x *= mTools[ESM::Apparatus::MortarPestle].get()->mBase->mData.mQuality; - x *= MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionStrengthMult")->mValue.getFloat(); + x *= MWBase::Environment::get() + .getESMStore() + ->get() + .find("fPotionStrengthMult") + ->mValue.getFloat(); // value - mValue = static_cast ( - x * MWBase::Environment::get().getWorld()->getStore().get().find ("iAlchemyMod")->mValue.getFloat()); + mValue = static_cast( + x * MWBase::Environment::get().getESMStore()->get().find("iAlchemyMod")->mValue.getFloat()); // build quantified effect list - for (std::set::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) + for (std::set::const_iterator iter(effects.begin()); iter != effects.end(); ++iter) { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->mId); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(iter->mId); - if (magicEffect->mData.mBaseCost<=0) + if (magicEffect->mData.mBaseCost <= 0) { const std::string os = "invalid base cost for magic effect " + std::to_string(iter->mId); - throw std::runtime_error (os); + throw std::runtime_error(os); } - float fPotionT1MagMul = - MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1MagMult")->mValue.getFloat(); + float fPotionT1MagMul = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fPotionT1MagMult") + ->mValue.getFloat(); - if (fPotionT1MagMul<=0) - throw std::runtime_error ("invalid gmst: fPotionT1MagMul"); + if (fPotionT1MagMul <= 0) + throw std::runtime_error("invalid gmst: fPotionT1MagMul"); - float fPotionT1DurMult = - MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1DurMult")->mValue.getFloat(); + float fPotionT1DurMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fPotionT1DurMult") + ->mValue.getFloat(); - if (fPotionT1DurMult<=0) - throw std::runtime_error ("invalid gmst: fPotionT1DurMult"); + if (fPotionT1DurMult <= 0) + throw std::runtime_error("invalid gmst: fPotionT1DurMult"); - float magnitude = (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) ? - 1.0f : (x / fPotionT1MagMul) / magicEffect->mData.mBaseCost; - float duration = (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) ? - 1.0f : (x / fPotionT1DurMult) / magicEffect->mData.mBaseCost; + float magnitude = (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + ? 1.0f + : (x / fPotionT1MagMul) / magicEffect->mData.mBaseCost; + float duration = (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + ? 1.0f + : (x / fPotionT1DurMult) / magicEffect->mData.mBaseCost; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) - applyTools (magicEffect->mData.mFlags, magnitude); + applyTools(magicEffect->mData.mFlags, magnitude); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - applyTools (magicEffect->mData.mFlags, duration); + applyTools(magicEffect->mData.mFlags, duration); duration = roundf(duration); magnitude = roundf(magnitude); - if (magnitude>0 && duration>0) + if (magnitude > 0 && duration > 0) { ESM::ENAMstruct effect; effect.mEffectID = iter->mId; @@ -215,15 +227,14 @@ void MWMechanics::Alchemy::updateEffects() effect.mDuration = static_cast(duration); effect.mMagnMin = effect.mMagnMax = static_cast(magnitude); - mEffects.push_back (effect); + mEffects.push_back(effect); } } } -const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) const +const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) const { - const MWWorld::Store &potions = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& potions = MWBase::Environment::get().getESMStore()->get(); MWWorld::Store::iterator iter = potions.begin(); for (; iter != potions.end(); ++iter) @@ -231,11 +242,9 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co if (iter->mEffects.mList.size() != mEffects.size()) continue; - if (iter->mName != toFind.mName - || iter->mScript != toFind.mScript - || iter->mData.mWeight != toFind.mData.mWeight - || iter->mData.mValue != toFind.mData.mValue - || iter->mData.mAutoCalc != toFind.mData.mAutoCalc) + if (iter->mName != toFind.mName || iter->mScript != toFind.mScript + || iter->mData.mWeight != toFind.mData.mWeight || iter->mData.mValue != toFind.mData.mValue + || iter->mData.mAutoCalc != toFind.mData.mAutoCalc) continue; // Don't choose an ID that came from the content files, would have unintended side effects @@ -245,19 +254,15 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co bool mismatch = false; - for (int i=0; i (iter->mEffects.mList.size()); ++i) + for (int i = 0; i < static_cast(iter->mEffects.mList.size()); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; - if (first.mEffectID!=second.mEffectID || - first.mArea!=second.mArea || - first.mRange!=second.mRange || - first.mSkill!=second.mSkill || - first.mAttribute!=second.mAttribute || - first.mMagnMin!=second.mMagnMin || - first.mMagnMax!=second.mMagnMax || - first.mDuration!=second.mDuration) + if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange + || first.mSkill != second.mSkill || first.mAttribute != second.mAttribute + || first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax + || first.mDuration != second.mDuration) { mismatch = true; break; @@ -273,11 +278,12 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co void MWMechanics::Alchemy::removeIngredients() { - for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) + for (TIngredientsContainer::iterator iter(mIngredients.begin()); iter != mIngredients.end(); ++iter) if (!iter->isEmpty()) { - iter->getContainerStore()->remove(*iter, 1, mAlchemist); + iter->getContainerStore()->remove(*iter, 1); +<<<<<<< HEAD /* Start of tes3mp addition @@ -289,19 +295,22 @@ void MWMechanics::Alchemy::removeIngredients() */ if (iter->getRefData().getCount()<1) +======= + if (iter->getRefData().getCount() < 1) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 *iter = MWWorld::Ptr(); } updateEffects(); } -void MWMechanics::Alchemy::addPotion (const std::string& name) +void MWMechanics::Alchemy::addPotion(const std::string& name) { ESM::Potion newRecord; newRecord.mData.mWeight = 0; - for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) + for (TIngredientsIterator iter(beginIngredients()); iter != endIngredients(); ++iter) if (!iter->isEmpty()) newRecord.mData.mWeight += iter->get()->mBase->mData.mWeight; @@ -313,13 +322,14 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) newRecord.mName = name; - int index = Misc::Rng::rollDice(6); - assert (index>=0 && index<6); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int index = Misc::Rng::rollDice(6, prng); + assert(index >= 0 && index < 6); - static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; + static const char* meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; - newRecord.mModel = "m\\misc_potion_" + std::string (meshes[index]) + "_01.nif"; - newRecord.mIcon = "m\\tx_potion_" + std::string (meshes[index]) + "_01.dds"; + newRecord.mModel = "m\\misc_potion_" + std::string(meshes[index]) + "_01.nif"; + newRecord.mIcon = "m\\tx_potion_" + std::string(meshes[index]) + "_01.dds"; newRecord.mEffects.mList = mEffects; @@ -333,6 +343,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) /* const ESM::Potion* record = getRecord(newRecord); if (!record) +<<<<<<< HEAD { record = MWBase::Environment::get().getWorld()->createRecord(newRecord); } @@ -343,28 +354,32 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) /* End of tes3mp change (major) */ +======= + record = MWBase::Environment::get().getESMStore()->insert(newRecord); + + mAlchemist.getClass().getContainerStore(mAlchemist).add(record->mId, 1); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } void MWMechanics::Alchemy::increaseSkill() { - mAlchemist.getClass().skillUsageSucceeded (mAlchemist, ESM::Skill::Alchemy, 0); + mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, 0); } float MWMechanics::Alchemy::getAlchemyFactor() const { - const CreatureStats& creatureStats = mAlchemist.getClass().getCreatureStats (mAlchemist); + const CreatureStats& creatureStats = mAlchemist.getClass().getCreatureStats(mAlchemist); - return - (mAlchemist.getClass().getSkill(mAlchemist, ESM::Skill::Alchemy) + - 0.1f * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() - + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()); + return (mAlchemist.getClass().getSkill(mAlchemist, ESM::Skill::Alchemy) + + 0.1f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified() + + 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified()); } int MWMechanics::Alchemy::countIngredients() const { int ingredients = 0; - for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) + for (TIngredientsIterator iter(beginIngredients()); iter != endIngredients(); ++iter) if (!iter->isEmpty()) ++ingredients; @@ -379,7 +394,7 @@ int MWMechanics::Alchemy::countPotionsToBrew() const int toBrew = -1; - for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) + for (TIngredientsIterator iter(beginIngredients()); iter != endIngredients(); ++iter) if (!iter->isEmpty()) { int count = iter->getRefData().getCount(); @@ -390,34 +405,34 @@ int MWMechanics::Alchemy::countPotionsToBrew() const return toBrew; } -void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc) +void MWMechanics::Alchemy::setAlchemist(const MWWorld::Ptr& npc) { mAlchemist = npc; - mIngredients.resize (4); + mIngredients.resize(4); - std::fill (mIngredients.begin(), mIngredients.end(), MWWorld::Ptr()); + std::fill(mIngredients.begin(), mIngredients.end(), MWWorld::Ptr()); - mTools.resize (4); + mTools.resize(4); - std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr()); + std::fill(mTools.begin(), mTools.end(), MWWorld::Ptr()); mEffects.clear(); - MWWorld::ContainerStore& store = npc.getClass().getContainerStore (npc); + MWWorld::ContainerStore& store = npc.getClass().getContainerStore(npc); - for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus)); - iter!=store.end(); ++iter) + for (MWWorld::ContainerStoreIterator iter(store.begin(MWWorld::ContainerStore::Type_Apparatus)); + iter != store.end(); ++iter) { MWWorld::LiveCellRef* ref = iter->get(); int type = ref->mBase->mData.mType; - if (type<0 || type>=static_cast (mTools.size())) - throw std::runtime_error ("invalid apparatus type"); + if (type < 0 || type >= static_cast(mTools.size())) + throw std::runtime_error("invalid apparatus type"); if (!mTools[type].isEmpty()) - if (ref->mBase->mData.mQuality<=mTools[type].get()->mBase->mData.mQuality) + if (ref->mBase->mData.mQuality <= mTools[type].get()->mBase->mData.mQuality) continue; mTools[type] = *iter; @@ -458,24 +473,23 @@ void MWMechanics::Alchemy::setPotionName(const std::string& name) mPotionName = name; } -int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) +int MWMechanics::Alchemy::addIngredient(const MWWorld::Ptr& ingredient) { // find a free slot int slot = -1; - for (int i=0; i (mIngredients.size()); ++i) + for (int i = 0; i < static_cast(mIngredients.size()); ++i) if (mIngredients[i].isEmpty()) { slot = i; break; } - if (slot==-1) + if (slot == -1) return -1; - for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) - if (!iter->isEmpty() && Misc::StringUtils::ciEqual(ingredient.getCellRef().getRefId(), - iter->getCellRef().getRefId())) + for (TIngredientsIterator iter(mIngredients.begin()); iter != mIngredients.end(); ++iter) + if (!iter->isEmpty() && ingredient.getCellRef().getRefId() == iter->getCellRef().getRefId()) return -1; mIngredients[slot] = ingredient; @@ -485,9 +499,9 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) return slot; } -void MWMechanics::Alchemy::removeIngredient (int index) +void MWMechanics::Alchemy::removeIngredient(int index) { - if (index>=0 && index (mIngredients.size())) + if (index >= 0 && index < static_cast(mIngredients.size())) { mIngredients[index] = MWWorld::Ptr(); updateEffects(); @@ -504,15 +518,15 @@ MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const return mEffects.end(); } -bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr &npc) +bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr& npc) { - float alchemySkill = npc.getClass().getSkill (npc, ESM::Skill::Alchemy); - static const float fWortChanceValue = - MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); + float alchemySkill = npc.getClass().getSkill(npc, ESM::Skill::Alchemy); + static const float fWortChanceValue + = MWBase::Environment::get().getESMStore()->get().find("fWortChanceValue")->mValue.getFloat(); return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue) - || (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue*2) - || (potionEffectIndex <= 5 && alchemySkill >= fWortChanceValue*3) - || (potionEffectIndex <= 7 && alchemySkill >= fWortChanceValue*4); + || (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue * 2) + || (potionEffectIndex <= 5 && alchemySkill >= fWortChanceValue * 3) + || (potionEffectIndex <= 7 && alchemySkill >= fWortChanceValue * 4); } MWMechanics::Alchemy::Result MWMechanics::Alchemy::getReadyStatus() const @@ -520,7 +534,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::getReadyStatus() const if (mTools[ESM::Apparatus::MortarPestle].isEmpty()) return Result_NoMortarAndPestle; - if (countIngredients()<2) + if (countIngredients() < 2) return Result_LessThanTwoIngredients; if (mPotionName.empty()) @@ -532,7 +546,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::getReadyStatus() const return Result_Success; } -MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name, int& count) +MWMechanics::Alchemy::Result MWMechanics::Alchemy::create(const std::string& name, int& count) { /* Start of tes3mp addition @@ -600,7 +614,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na return result; } -MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle () +MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle() { if (beginEffects() == endEffects()) { @@ -608,8 +622,8 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle () removeIngredients(); return Result_RandomFailure; } - - if (getAlchemyFactor() < Misc::Rng::roll0to99()) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (getAlchemyFactor() < Misc::Rng::roll0to99(prng)) { removeIngredients(); return Result_RandomFailure; @@ -628,42 +642,35 @@ std::string MWMechanics::Alchemy::suggestPotionName() { std::set effects = listEffects(); if (effects.empty()) - return ""; + return {}; - int effectId = effects.begin()->mId; - return MWBase::Environment::get().getWorld()->getStore().get().find( - ESM::MagicEffect::effectIdToString(effectId))->mValue.getString(); + return effects.begin()->toString(); } -std::vector MWMechanics::Alchemy::effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySkill) +std::vector MWMechanics::Alchemy::effectsDescription(const MWWorld::ConstPtr& ptr, const int alchemySkill) { std::vector effects; const auto& item = ptr.get()->mBase; - const auto& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - const static auto fWortChanceValue = gmst.find("fWortChanceValue")->mValue.getFloat(); + const auto& store = MWBase::Environment::get().getESMStore(); + const auto& mgef = store->get(); + const static auto fWortChanceValue = store->get().find("fWortChanceValue")->mValue.getFloat(); const auto& data = item->mData; for (auto i = 0; i < 4; ++i) { const auto effectID = data.mEffectID[i]; - const auto skillID = data.mSkills[i]; - const auto attributeID = data.mAttributes[i]; if (alchemySkill < fWortChanceValue * (i + 1)) break; if (effectID != -1) { - std::string effect = gmst.find(ESM::MagicEffect::effectIdToString(effectID))->mValue.getString(); - - if (skillID != -1) - effect += " " + gmst.find(ESM::Skill::sSkillNameIds[skillID])->mValue.getString(); - else if (attributeID != -1) - effect += " " + gmst.find(ESM::Attribute::sGmstAttributeIds[attributeID])->mValue.getString(); + const ESM::Attribute* attribute = store->get().search(data.mAttributes[i]); + const ESM::Skill* skill = store->get().search(ESM::Skill::indexToRefId(data.mSkills[i])); + std::string effect = getMagicEffectString(*mgef.find(effectID), attribute, skill); effects.push_back(effect); - } } return effects; diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index 3fc7ec5e6..43d41600d 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -1,9 +1,10 @@ #ifndef GAME_MWMECHANICS_ALCHEMY_H #define GAME_MWMECHANICS_ALCHEMY_H -#include #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -15,6 +16,9 @@ */ #include +======= +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwworld/ptr.hpp" @@ -30,39 +34,42 @@ namespace MWMechanics /// \brief Potion creation via alchemy skill class Alchemy { - public: + public: + Alchemy(); - Alchemy(); + typedef std::vector TToolsContainer; + typedef TToolsContainer::const_iterator TToolsIterator; - typedef std::vector TToolsContainer; - typedef TToolsContainer::const_iterator TToolsIterator; + typedef std::vector TIngredientsContainer; + typedef TIngredientsContainer::const_iterator TIngredientsIterator; - typedef std::vector TIngredientsContainer; - typedef TIngredientsContainer::const_iterator TIngredientsIterator; + typedef std::vector TEffectsContainer; + typedef TEffectsContainer::const_iterator TEffectsIterator; - typedef std::vector TEffectsContainer; - typedef TEffectsContainer::const_iterator TEffectsIterator; + enum Result + { + Result_Success, - enum Result - { - Result_Success, + Result_NoMortarAndPestle, + Result_LessThanTwoIngredients, + Result_NoName, + Result_NoEffects, + Result_RandomFailure + }; - Result_NoMortarAndPestle, - Result_LessThanTwoIngredients, - Result_NoName, - Result_NoEffects, - Result_RandomFailure - }; + private: + MWWorld::Ptr mAlchemist; + TToolsContainer mTools; + TIngredientsContainer mIngredients; + TEffectsContainer mEffects; + int mValue; + std::string mPotionName; - private: + void applyTools(int flags, float& value) const; - MWWorld::Ptr mAlchemist; - TToolsContainer mTools; - TIngredientsContainer mIngredients; - TEffectsContainer mEffects; - int mValue; - std::string mPotionName; + void updateEffects(); +<<<<<<< HEAD /* Start of tes3mp addition @@ -75,87 +82,85 @@ namespace MWMechanics */ void applyTools (int flags, float& value) const; +======= + Result getReadyStatus() const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - void updateEffects(); + const ESM::Potion* getRecord(const ESM::Potion& toFind) const; + ///< Try to find a potion record similar to \a toFind in the record store, or return 0 if not found + /// \note Does not account for record ID, model or icon - Result getReadyStatus() const; + void removeIngredients(); + ///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and + /// update effect list accordingly. - const ESM::Potion *getRecord(const ESM::Potion& toFind) const; - ///< Try to find a potion record similar to \a toFind in the record store, or return 0 if not found - /// \note Does not account for record ID, model or icon + void addPotion(const std::string& name); + ///< Add a potion to the alchemist's inventory. - void removeIngredients(); - ///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and - /// update effect list accordingly. + void increaseSkill(); + ///< Increase alchemist's skill. - void addPotion (const std::string& name); - ///< Add a potion to the alchemist's inventory. + Result createSingle(); + ///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and + /// adjust the skills of the alchemist accordingly. - void increaseSkill(); - ///< Increase alchemist's skill. + float getAlchemyFactor() const; - Result createSingle (); - ///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and - /// adjust the skills of the alchemist accordingly. + int countIngredients() const; - float getAlchemyFactor() const; + TEffectsIterator beginEffects() const; - int countIngredients() const; + TEffectsIterator endEffects() const; - TEffectsIterator beginEffects() const; + public: + int countPotionsToBrew() const; + ///< calculates maximum amount of potions, which you can make from selected ingredients - TEffectsIterator endEffects() const; + static bool knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr& npc); + ///< Does npc have sufficient alchemy skill to know about this potion effect? - public: - int countPotionsToBrew() const; - ///< calculates maximum amount of potions, which you can make from selected ingredients + void setAlchemist(const MWWorld::Ptr& npc); + ///< Set alchemist and configure alchemy setup accordingly. \a npc may be empty to indicate that + /// there is no alchemist (alchemy session has ended). - static bool knownEffect (unsigned int potionEffectIndex, const MWWorld::Ptr& npc); - ///< Does npc have sufficient alchemy skill to know about this potion effect? + TToolsIterator beginTools() const; + ///< \attention Iterates over tool slots, not over tools. Some of the slots may be empty. - void setAlchemist (const MWWorld::Ptr& npc); - ///< Set alchemist and configure alchemy setup accordingly. \a npc may be empty to indicate that - /// there is no alchemist (alchemy session has ended). + TToolsIterator endTools() const; - TToolsIterator beginTools() const; - ///< \attention Iterates over tool slots, not over tools. Some of the slots may be empty. + TIngredientsIterator beginIngredients() const; + ///< \attention Iterates over ingredient slots, not over ingredients. Some of the slots may be empty. - TToolsIterator endTools() const; + TIngredientsIterator endIngredients() const; - TIngredientsIterator beginIngredients() const; - ///< \attention Iterates over ingredient slots, not over ingredients. Some of the slots may be empty. + void clear(); + ///< Remove alchemist, tools and ingredients. - TIngredientsIterator endIngredients() const; + void setPotionName(const std::string& name); + ///< Set name of potion to create - void clear(); - ///< Remove alchemist, tools and ingredients. + std::set listEffects() const; + ///< List all effects shared by at least two ingredients. - void setPotionName(const std::string& name); - ///< Set name of potion to create + int addIngredient(const MWWorld::Ptr& ingredient); + ///< Add ingredient into the next free slot. + /// + /// \return Slot index or -1, if adding failed because of no free slot or the ingredient type being + /// listed already. - std::set listEffects() const; - ///< List all effects shared by at least two ingredients. + void removeIngredient(int index); + ///< Remove ingredient from slot (calling this function on an empty slot is a no-op). - int addIngredient (const MWWorld::Ptr& ingredient); - ///< Add ingredient into the next free slot. - /// - /// \return Slot index or -1, if adding failed because of no free slot or the ingredient type being - /// listed already. + std::string suggestPotionName(); + ///< Suggest a name for the potion, based on the current effects - void removeIngredient (int index); - ///< Remove ingredient from slot (calling this function on an empty slot is a no-op). + Result create(const std::string& name, int& count); + ///< Try to create potions from the ingredients, place them in the inventory of the alchemist and + /// adjust the skills of the alchemist accordingly. + /// \param name must not be an empty string, or Result_NoName is returned - std::string suggestPotionName (); - ///< Suggest a name for the potion, based on the current effects - - Result create (const std::string& name, int& count); - ///< Try to create potions from the ingredients, place them in the inventory of the alchemist and - /// adjust the skills of the alchemist accordingly. - /// \param name must not be an empty string, or Result_NoName is returned - - static std::vector effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySKill); + static std::vector effectsDescription(const MWWorld::ConstPtr& ptr, const int alchemySKill); }; } #endif - diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index 662cfe473..08c76e900 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -2,10 +2,16 @@ #include +#include +#include +#include +#include +#include + #include "../mwworld/esmstore.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "spellutil.hpp" @@ -18,46 +24,34 @@ namespace MWMechanics int mLimit; bool mReachedLimit; int mMinCost; - std::string mWeakestSpell; + ESM::RefId mWeakestSpell; }; - std::vector autoCalcNpcSpells(const int *actorSkills, const int *actorAttributes, const ESM::Race* race) + std::vector autoCalcNpcSpells(const std::map& actorSkills, + const std::map& actorAttributes, const ESM::Race* race) { - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fNPCbaseMagickaMult = gmst.find("fNPCbaseMagickaMult")->mValue.getFloat(); - float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; + float baseMagicka = fNPCbaseMagickaMult * actorAttributes.at(ESM::Attribute::Intelligence).getBase(); - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - static int iAutoSpellSchoolMax[6]; - static bool init = false; - if (!init) - { - for (int i=0; i<6; ++i) - { - const std::string& gmstName = "iAutoSpell" + schools[i] + "Max"; - iAutoSpellSchoolMax[i] = gmst.find(gmstName)->mValue.getInteger(); - } - init = true; - } - - std::map schoolCaps; - for (int i=0; i<6; ++i) + std::map schoolCaps; + for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get()) { + if (!skill.mSchool) + continue; SchoolCaps caps; caps.mCount = 0; - caps.mLimit = iAutoSpellSchoolMax[i]; - caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0; + caps.mLimit = skill.mSchool->mAutoCalcMax; + caps.mReachedLimit = skill.mSchool->mAutoCalcMax <= 0; caps.mMinCost = std::numeric_limits::max(); - caps.mWeakestSpell.clear(); - schoolCaps[i] = caps; + caps.mWeakestSpell = ESM::RefId(); + schoolCaps[skill.mId] = caps; } - std::vector selectedSpells; + std::vector selectedSpells; - const MWWorld::Store &spells = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& spells = MWBase::Environment::get().getESMStore()->get(); // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the // Store must preserve the record ordering as it was in the content files. @@ -68,7 +62,8 @@ namespace MWMechanics if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc)) continue; static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->mValue.getInteger(); - if (baseMagicka < iAutoSpellTimesCanCast * spell.mData.mCost) + int spellCost = MWMechanics::calcSpellCost(spell); + if (baseMagicka < iAutoSpellTimesCanCast * spellCost) continue; if (race && race->mPowers.exists(spell.mId)) @@ -77,13 +72,13 @@ namespace MWMechanics if (!attrSkillCheck(&spell, actorSkills, actorAttributes)) continue; - int school; + ESM::RefId school; float skillTerm; calcWeakestSchool(&spell, actorSkills, school, skillTerm); - assert(school >= 0 && school < 6); + assert(!school.empty()); SchoolCaps& cap = schoolCaps[school]; - if (cap.mReachedLimit && spell.mData.mCost <= cap.mMinCost) + if (cap.mReachedLimit && spellCost <= cap.mMinCost) continue; static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->mValue.getFloat(); @@ -94,30 +89,32 @@ namespace MWMechanics if (cap.mReachedLimit) { - std::vector::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); + auto found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); if (found != selectedSpells.end()) selectedSpells.erase(found); cap.mMinCost = std::numeric_limits::max(); - for (const std::string& testSpellName : selectedSpells) + for (const ESM::RefId& testSpellName : selectedSpells) { const ESM::Spell* testSpell = spells.find(testSpellName); + int testSpellCost = MWMechanics::calcSpellCost(*testSpell); - //int testSchool; - //float dummySkillTerm; - //calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm); + // int testSchool; + // float dummySkillTerm; + // calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm); // Note: if there are multiple spells with the same cost, we pick the first one we found. // So the algorithm depends on the iteration order of the outer loop. if ( - // There is a huge bug here. It is not checked that weakestSpell is of the correct school. - // As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school - // already erased it, and so the number of spells would often exceed the sum of limits. - // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested. - //testSchool == school && - testSpell->mData.mCost < cap.mMinCost) + // There is a huge bug here. It is not checked that weakestSpell is of the correct school. + // As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell + // would then fail if another school already erased it, and so the number of spells would often + // exceed the sum of limits. This bug cannot be fixed without significantly changing the results + // of the spell autocalc, which will not have been playtested. + // testSchool == school && + testSpellCost < cap.mMinCost) { - cap.mMinCost = testSpell->mData.mCost; + cap.mMinCost = testSpellCost; cap.mWeakestSpell = testSpell->mId; } } @@ -128,10 +125,10 @@ namespace MWMechanics if (cap.mCount == cap.mLimit) cap.mReachedLimit = true; - if (spell.mData.mCost < cap.mMinCost) + if (spellCost < cap.mMinCost) { cap.mWeakestSpell = spell.mId; - cap.mMinCost = spell.mData.mCost; + cap.mMinCost = spellCost; } } } @@ -139,35 +136,42 @@ namespace MWMechanics return selectedSpells; } - std::vector autoCalcPlayerSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race) + std::vector autoCalcPlayerSpells(const std::map& actorSkills, + const std::map& actorAttributes, const ESM::Race* race) { - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); - static const float fPCbaseMagickaMult = esmStore.get().find("fPCbaseMagickaMult")->mValue.getFloat(); + static const float fPCbaseMagickaMult + = esmStore.get().find("fPCbaseMagickaMult")->mValue.getFloat(); - float baseMagicka = fPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; + float baseMagicka = fPCbaseMagickaMult * actorAttributes.at(ESM::Attribute::Intelligence).getBase(); bool reachedLimit = false; const ESM::Spell* weakestSpell = nullptr; int minCost = std::numeric_limits::max(); - std::vector selectedSpells; + std::vector selectedSpells; - const MWWorld::Store &spells = esmStore.get(); + const MWWorld::Store& spells = esmStore.get(); for (const ESM::Spell& spell : spells) { if (spell.mData.mType != ESM::Spell::ST_Spell) continue; if (!(spell.mData.mFlags & ESM::Spell::F_PCStart)) continue; - if (reachedLimit && spell.mData.mCost <= minCost) + + int spellCost = MWMechanics::calcSpellCost(spell); + if (reachedLimit && spellCost <= minCost) continue; - if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell.mId) != race->mPowers.mList.end()) + if (race + && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell.mId) + != race->mPowers.mList.end()) continue; - if (baseMagicka < spell.mData.mCost) + if (baseMagicka < spellCost) continue; - static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->mValue.getFloat(); - if (calcAutoCastChance(&spell, actorSkills, actorAttributes, -1) < fAutoPCSpellChance) + static const float fAutoPCSpellChance + = esmStore.get().find("fAutoPCSpellChance")->mValue.getFloat(); + if (calcAutoCastChance(&spell, actorSkills, actorAttributes, {}) < fAutoPCSpellChance) continue; if (!attrSkillCheck(&spell, actorSkills, actorAttributes)) @@ -177,29 +181,32 @@ namespace MWMechanics if (reachedLimit) { - std::vector::iterator it = std::find(selectedSpells.begin(), selectedSpells.end(), weakestSpell->mId); + std::vector::iterator it + = std::find(selectedSpells.begin(), selectedSpells.end(), weakestSpell->mId); if (it != selectedSpells.end()) selectedSpells.erase(it); minCost = std::numeric_limits::max(); - for (const std::string& testSpellName : selectedSpells) + for (const ESM::RefId& testSpellName : selectedSpells) { const ESM::Spell* testSpell = esmStore.get().find(testSpellName); - if (testSpell->mData.mCost < minCost) + int testSpellCost = MWMechanics::calcSpellCost(*testSpell); + if (testSpellCost < minCost) { - minCost = testSpell->mData.mCost; + minCost = testSpellCost; weakestSpell = testSpell; } } } else { - if (spell.mData.mCost < minCost) + if (spellCost < minCost) { weakestSpell = &spell; - minCost = weakestSpell->mData.mCost; + minCost = MWMechanics::calcSpellCost(*weakestSpell); } - static const unsigned int iAutoPCSpellMax = esmStore.get().find("iAutoPCSpellMax")->mValue.getInteger(); + static const unsigned int iAutoPCSpellMax + = esmStore.get().find("iAutoPCSpellMax")->mValue.getInteger(); if (selectedSpells.size() == iAutoPCSpellMax) reachedLimit = true; } @@ -208,24 +215,31 @@ namespace MWMechanics return selectedSpells; } - bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes) + bool attrSkillCheck(const ESM::Spell* spell, const std::map& actorSkills, + const std::map& actorAttributes) { for (const auto& spellEffect : spell->mEffects.mList) { - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(spellEffect.mEffectID); - static const int iAutoSpellAttSkillMin = MWBase::Environment::get().getWorld()->getStore().get().find("iAutoSpellAttSkillMin")->mValue.getInteger(); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(spellEffect.mEffectID); + static const int iAutoSpellAttSkillMin = MWBase::Environment::get() + .getESMStore() + ->get() + .find("iAutoSpellAttSkillMin") + ->mValue.getInteger(); if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) { - assert (spellEffect.mSkill >= 0 && spellEffect.mSkill < ESM::Skill::Length); - if (actorSkills[spellEffect.mSkill] < iAutoSpellAttSkillMin) + ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mSkill); + auto found = actorSkills.find(skill); + if (found == actorSkills.end() || found->second.getBase() < iAutoSpellAttSkillMin) return false; } if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) { - assert (spellEffect.mAttribute >= 0 && spellEffect.mAttribute < ESM::Attribute::Length); - if (actorAttributes[spellEffect.mAttribute] < iAutoSpellAttSkillMin) + auto found = actorAttributes.find(ESM::Attribute::AttributeID(spellEffect.mAttribute)); + if (found == actorAttributes.end() || found->second.getBase() < iAutoSpellAttSkillMin) return false; } } @@ -233,13 +247,15 @@ namespace MWMechanics return true; } - void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) + void calcWeakestSchool(const ESM::Spell* spell, const std::map& actorSkills, + ESM::RefId& effectiveSchool, float& skillTerm) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float minChance = std::numeric_limits::max(); for (const ESM::ENAMstruct& effect : spell->mEffects.mList) { - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); int minMagn = 1; int maxMagn = 1; @@ -255,8 +271,11 @@ namespace MWMechanics if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) duration = std::max(1, duration); - static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() - .get().find("fEffectCostMult")->mValue.getFloat(); + static const float fEffectCostMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fEffectCostMult") + ->mValue.getFloat(); float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; @@ -267,7 +286,10 @@ namespace MWMechanics if (effect.mRange == ESM::RT_Target) x *= 1.5f; - float s = 2.f * actorSkills[spellSchoolToSkill(magicEffect->mData.mSchool)]; + float s = 0.f; + auto found = actorSkills.find(magicEffect->mData.mSchool); + if (found != actorSkills.end()) + s = 2.f * found->second.getBase(); if (s - x < minChance) { minChance = s - x; @@ -277,7 +299,8 @@ namespace MWMechanics } } - float calcAutoCastChance(const ESM::Spell *spell, const int *actorSkills, const int *actorAttributes, int effectiveSchool) + float calcAutoCastChance(const ESM::Spell* spell, const std::map& actorSkills, + const std::map& actorAttributes, ESM::RefId effectiveSchool) { if (spell->mData.mType != ESM::Spell::ST_Spell) return 100.f; @@ -286,12 +309,19 @@ namespace MWMechanics return 100.f; float skillTerm = 0; - if (effectiveSchool != -1) - skillTerm = 2.f * actorSkills[spellSchoolToSkill(effectiveSchool)]; + if (!effectiveSchool.empty()) + { + auto found = actorSkills.find(effectiveSchool); + if (found != actorSkills.end()) + skillTerm = 2.f * found->second.getBase(); + } else - calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this + calcWeakestSchool( + spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this - float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; + float castChance = skillTerm - MWMechanics::calcSpellCost(*spell) + + 0.2f * actorAttributes.at(ESM::Attribute::Willpower).getBase() + + 0.1f * actorAttributes.at(ESM::Attribute::Luck).getBase(); return castChance; } } diff --git a/apps/openmw/mwmechanics/autocalcspell.hpp b/apps/openmw/mwmechanics/autocalcspell.hpp index 460713ae3..7edfd8b75 100644 --- a/apps/openmw/mwmechanics/autocalcspell.hpp +++ b/apps/openmw/mwmechanics/autocalcspell.hpp @@ -1,6 +1,9 @@ #ifndef OPENMW_AUTOCALCSPELL_H #define OPENMW_AUTOCALCSPELL_H +#include "creaturestats.hpp" +#include +#include #include #include @@ -13,20 +16,25 @@ namespace ESM namespace MWMechanics { -/// Contains algorithm for calculating an NPC's spells based on stats -/// @note We might want to move this code to a component later, so the editor can use it for preview purposes + /// Contains algorithm for calculating an NPC's spells based on stats + /// @note We might want to move this code to a component later, so the editor can use it for preview purposes -std::vector autoCalcNpcSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race); + std::vector autoCalcNpcSpells(const std::map& actorSkills, + const std::map& actorAttributes, const ESM::Race* race); -std::vector autoCalcPlayerSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race); + std::vector autoCalcPlayerSpells(const std::map& actorSkills, + const std::map& actorAttributes, const ESM::Race* race); -// Helpers + // Helpers -bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes); + bool attrSkillCheck(const ESM::Spell* spell, const std::map& actorSkills, + const std::map& actorAttributes); -void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm); + void calcWeakestSchool(const ESM::Spell* spell, const std::map& actorSkills, + ESM::RefId& effectiveSchool, float& skillTerm); -float calcAutoCastChance(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool); + float calcAutoCastChance(const ESM::Spell* spell, const std::map& actorSkills, + const std::map& actorAttributes, ESM::RefId effectiveSchool); } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 068929ce6..914e930e3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -19,12 +19,14 @@ #include "character.hpp" -#include - +#include #include +#include #include +#include +#include -#include +#include #include @@ -49,535 +51,667 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/spellcaststate.hpp" +#include "actorutil.hpp" #include "aicombataction.hpp" +#include "creaturestats.hpp" #include "movement.hpp" #include "npcstats.hpp" -#include "creaturestats.hpp" #include "security.hpp" -#include "actorutil.hpp" #include "spellcasting.hpp" +#include "weapontype.hpp" namespace { -std::string getBestAttack (const ESM::Weapon* weapon) -{ - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; - if (slash == chop && slash == thrust) - return "slash"; - else if (thrust >= chop && thrust >= slash) - return "thrust"; - else if (slash >= chop && slash >= thrust) - return "slash"; - else - return "chop"; -} - -// Converts a movement Run state to its equivalent Walk state. -MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) -{ - using namespace MWMechanics; - CharacterState ret = state; - switch (state) + std::string_view getBestAttack(const ESM::Weapon* weapon) { - case CharState_RunForward: - ret = CharState_WalkForward; - break; - case CharState_RunBack: - ret = CharState_WalkBack; - break; - case CharState_RunLeft: - ret = CharState_WalkLeft; - break; - case CharState_RunRight: - ret = CharState_WalkRight; - break; - case CharState_SwimRunForward: - ret = CharState_SwimWalkForward; - break; - case CharState_SwimRunBack: - ret = CharState_SwimWalkBack; - break; - case CharState_SwimRunLeft: - ret = CharState_SwimWalkLeft; - break; - case CharState_SwimRunRight: - ret = CharState_SwimWalkRight; - break; - default: - break; + int slash = weapon->mData.mSlash[0] + weapon->mData.mSlash[1]; + int chop = weapon->mData.mChop[0] + weapon->mData.mChop[1]; + int thrust = weapon->mData.mThrust[0] + weapon->mData.mThrust[1]; + if (slash == chop && slash == thrust) + return "slash"; + else if (thrust >= chop && thrust >= slash) + return "thrust"; + else if (slash >= chop && slash >= thrust) + return "slash"; + else + return "chop"; } - return ret; -} -float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) -{ - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); - - const float fallDistanceMin = store.find("fFallDamageDistanceMin")->mValue.getFloat(); - - if (fallHeight >= fallDistanceMin) + // Converts a movement Run state to its equivalent Walk state, if there is one. + MWMechanics::CharacterState runStateToWalkState(MWMechanics::CharacterState state) { - const float acrobaticsSkill = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics)); - const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); - const float fallAcroBase = store.find("fFallAcroBase")->mValue.getFloat(); - const float fallAcroMult = store.find("fFallAcroMult")->mValue.getFloat(); - const float fallDistanceBase = store.find("fFallDistanceBase")->mValue.getFloat(); - const float fallDistanceMult = store.find("fFallDistanceMult")->mValue.getFloat(); - - float x = fallHeight - fallDistanceMin; - x -= (1.5f * acrobaticsSkill) + jumpSpellBonus; - x = std::max(0.0f, x); - - float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); - x = fallDistanceBase + fallDistanceMult * x; - x *= a; - - return x; + using namespace MWMechanics; + switch (state) + { + case CharState_RunForward: + return CharState_WalkForward; + case CharState_RunBack: + return CharState_WalkBack; + case CharState_RunLeft: + return CharState_WalkLeft; + case CharState_RunRight: + return CharState_WalkRight; + case CharState_SwimRunForward: + return CharState_SwimWalkForward; + case CharState_SwimRunBack: + return CharState_SwimWalkBack; + case CharState_SwimRunLeft: + return CharState_SwimWalkLeft; + case CharState_SwimRunRight: + return CharState_SwimWalkRight; + default: + return state; + } + } + + // Converts a Hit state to its equivalent Death state. + MWMechanics::CharacterState hitStateToDeathState(MWMechanics::CharacterState state) + { + using namespace MWMechanics; + switch (state) + { + case CharState_SwimKnockDown: + return CharState_SwimDeathKnockDown; + case CharState_SwimKnockOut: + return CharState_SwimDeathKnockOut; + case CharState_KnockDown: + return CharState_DeathKnockDown; + case CharState_KnockOut: + return CharState_DeathKnockOut; + default: + return CharState_None; + } + } + + // Converts a movement state to its equivalent base animation group as long as it is a movement state. + std::string_view movementStateToAnimGroup(MWMechanics::CharacterState state) + { + using namespace MWMechanics; + switch (state) + { + case CharState_WalkForward: + return "walkforward"; + case CharState_WalkBack: + return "walkback"; + case CharState_WalkLeft: + return "walkleft"; + case CharState_WalkRight: + return "walkright"; + + case CharState_SwimWalkForward: + return "swimwalkforward"; + case CharState_SwimWalkBack: + return "swimwalkback"; + case CharState_SwimWalkLeft: + return "swimwalkleft"; + case CharState_SwimWalkRight: + return "swimwalkright"; + + case CharState_RunForward: + return "runforward"; + case CharState_RunBack: + return "runback"; + case CharState_RunLeft: + return "runleft"; + case CharState_RunRight: + return "runright"; + + case CharState_SwimRunForward: + return "swimrunforward"; + case CharState_SwimRunBack: + return "swimrunback"; + case CharState_SwimRunLeft: + return "swimrunleft"; + case CharState_SwimRunRight: + return "swimrunright"; + + case CharState_SneakForward: + return "sneakforward"; + case CharState_SneakBack: + return "sneakback"; + case CharState_SneakLeft: + return "sneakleft"; + case CharState_SneakRight: + return "sneakright"; + + case CharState_TurnLeft: + return "turnleft"; + case CharState_TurnRight: + return "turnright"; + case CharState_SwimTurnLeft: + return "swimturnleft"; + case CharState_SwimTurnRight: + return "swimturnright"; + default: + return {}; + } + } + + // Converts a death state to its equivalent animation group as long as it is a death state. + std::string_view deathStateToAnimGroup(MWMechanics::CharacterState state) + { + using namespace MWMechanics; + switch (state) + { + case CharState_SwimDeath: + return "swimdeath"; + case CharState_SwimDeathKnockDown: + return "swimdeathknockdown"; + case CharState_SwimDeathKnockOut: + return "swimdeathknockout"; + case CharState_DeathKnockDown: + return "deathknockdown"; + case CharState_DeathKnockOut: + return "deathknockout"; + case CharState_Death1: + return "death1"; + case CharState_Death2: + return "death2"; + case CharState_Death3: + return "death3"; + case CharState_Death4: + return "death4"; + case CharState_Death5: + return "death5"; + default: + return {}; + } + } + + // Converts a hit state to its equivalent animation group as long as it is a hit state. + std::string hitStateToAnimGroup(MWMechanics::CharacterState state) + { + using namespace MWMechanics; + switch (state) + { + case CharState_SwimHit: + return "swimhit"; + case CharState_SwimKnockDown: + return "swimknockdown"; + case CharState_SwimKnockOut: + return "swimknockout"; + + case CharState_Hit: + return "hit"; + case CharState_KnockDown: + return "knockdown"; + case CharState_KnockOut: + return "knockout"; + + case CharState_Block: + return "shield"; + + default: + return {}; + } + } + + // Converts an idle state to its equivalent animation group. + std::string idleStateToAnimGroup(MWMechanics::CharacterState state) + { + using namespace MWMechanics; + switch (state) + { + case CharState_IdleSwim: + return "idleswim"; + case CharState_IdleSneak: + return "idlesneak"; + case CharState_Idle: + case CharState_SpecialIdle: + return "idle"; + default: + return {}; + } + } + + MWRender::Animation::AnimPriority getIdlePriority(MWMechanics::CharacterState state) + { + using namespace MWMechanics; + MWRender::Animation::AnimPriority priority(Priority_Default); + switch (state) + { + case CharState_IdleSwim: + return Priority_SwimIdle; + case CharState_IdleSneak: + priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; + [[fallthrough]]; + default: + return priority; + } + } + + float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& store = world->getStore().get(); + + const float fallDistanceMin = store.find("fFallDamageDistanceMin")->mValue.getFloat(); + + if (fallHeight >= fallDistanceMin) + { + const float acrobaticsSkill = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics)); + const float jumpSpellBonus = ptr.getClass() + .getCreatureStats(ptr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Jump) + .getMagnitude(); + const float fallAcroBase = store.find("fFallAcroBase")->mValue.getFloat(); + const float fallAcroMult = store.find("fFallAcroMult")->mValue.getFloat(); + const float fallDistanceBase = store.find("fFallDistanceBase")->mValue.getFloat(); + const float fallDistanceMult = store.find("fFallDistanceMult")->mValue.getFloat(); + + float x = fallHeight - fallDistanceMin; + x -= (1.5f * acrobaticsSkill) + jumpSpellBonus; + x = std::max(0.0f, x); + + float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); + x = fallDistanceBase + fallDistanceMult * x; + x *= a; + + return x; + } + return 0.f; + } + + bool isRealWeapon(int weaponType) + { + return weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell + && weaponType != ESM::Weapon::None; } - return 0.f; -} } namespace MWMechanics { -struct StateInfo { - CharacterState state; - const char groupname[32]; -}; - -static const StateInfo sMovementList[] = { - { CharState_WalkForward, "walkforward" }, - { CharState_WalkBack, "walkback" }, - { CharState_WalkLeft, "walkleft" }, - { CharState_WalkRight, "walkright" }, - - { CharState_SwimWalkForward, "swimwalkforward" }, - { CharState_SwimWalkBack, "swimwalkback" }, - { CharState_SwimWalkLeft, "swimwalkleft" }, - { CharState_SwimWalkRight, "swimwalkright" }, - - { CharState_RunForward, "runforward" }, - { CharState_RunBack, "runback" }, - { CharState_RunLeft, "runleft" }, - { CharState_RunRight, "runright" }, - - { CharState_SwimRunForward, "swimrunforward" }, - { CharState_SwimRunBack, "swimrunback" }, - { CharState_SwimRunLeft, "swimrunleft" }, - { CharState_SwimRunRight, "swimrunright" }, - - { CharState_SneakForward, "sneakforward" }, - { CharState_SneakBack, "sneakback" }, - { CharState_SneakLeft, "sneakleft" }, - { CharState_SneakRight, "sneakright" }, - - { CharState_Jump, "jump" }, - - { CharState_TurnLeft, "turnleft" }, - { CharState_TurnRight, "turnright" }, - { CharState_SwimTurnLeft, "swimturnleft" }, - { CharState_SwimTurnRight, "swimturnright" }, -}; -static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; - - -class FindCharState { - CharacterState state; - -public: - FindCharState(CharacterState _state) : state(_state) { } - - bool operator()(const StateInfo &info) const - { return info.state == state; } -}; - - -std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const -{ - int numAnims=0; - while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1))) - ++numAnims; - - int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] - if (num) - *num = roll; - return prefix + std::to_string(roll); -} - -void CharacterController::refreshHitRecoilAnims(CharacterState& idle) -{ - bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); - bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); - bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); - bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); - if(mHitState == CharState_None) + std::string CharacterController::chooseRandomGroup(const std::string& prefix, int* num) const { - if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 - || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)) - { - mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds - if (isSwimming && mAnimation->hasAnimation("swimknockout")) - { - mHitState = CharState_SwimKnockOut; - mCurrentHit = "swimknockout"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); - } - else if (!isSwimming && mAnimation->hasAnimation("knockout")) - { - mHitState = CharState_KnockOut; - mCurrentHit = "knockout"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); - } - else - { - // Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH. - mCurrentHit.erase(); - } + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); + int numAnims = 0; + while (mAnimation->hasAnimation(prefix + std::to_string(numAnims + 1))) + ++numAnims; + + int roll = Misc::Rng::rollDice(numAnims, prng) + 1; // [1, numAnims] + if (num) + *num = roll; + return prefix + std::to_string(roll); + } + + void CharacterController::clearStateAnimation(std::string& anim) const + { + if (anim.empty()) + return; + if (mAnimation) + mAnimation->disable(anim); + anim.clear(); + } + + void CharacterController::resetCurrentJumpState() + { + clearStateAnimation(mCurrentJump); + mJumpState = JumpState_None; + } + + void CharacterController::resetCurrentMovementState() + { + clearStateAnimation(mCurrentMovement); + mMovementState = CharState_None; + } + + void CharacterController::resetCurrentIdleState() + { + clearStateAnimation(mCurrentIdle); + mIdleState = CharState_None; + } + + void CharacterController::resetCurrentHitState() + { + clearStateAnimation(mCurrentHit); + mHitState = CharState_None; + } + + void CharacterController::resetCurrentWeaponState() + { + clearStateAnimation(mCurrentWeapon); + mUpperBodyState = UpperBodyState::None; + } + + void CharacterController::resetCurrentDeathState() + { + clearStateAnimation(mCurrentDeath); + mDeathState = CharState_None; + } + + void CharacterController::refreshHitRecoilAnims() + { + auto& charClass = mPtr.getClass(); + if (!charClass.isActor()) + return; + const auto world = MWBase::Environment::get().getWorld(); + auto& stats = charClass.getCreatureStats(mPtr); + bool knockout = stats.getFatigue().getCurrent() < 0 || stats.getFatigue().getBase() == 0; + bool recovery = stats.getHitRecovery(); + bool knockdown = stats.getKnockedDown(); + bool block = stats.getBlock() && !knockout && !recovery && !knockdown; + bool isSwimming = world->isSwimming(mPtr); + + stats.setBlock(false); + + if (mPtr == getPlayer() && mHitState == CharState_Block && block) + { + mHitState = CharState_None; + resetCurrentIdleState(); + } + + if (mHitState != CharState_None) + { + if (!mAnimation->isPlaying(mCurrentHit)) + { + if (isKnockedOut() && mCurrentHit.empty() && knockout) + return; + + mHitState = CharState_None; + mCurrentHit.clear(); + stats.setKnockedDown(false); + stats.setHitRecovery(false); + resetCurrentIdleState(); + } + else if (isKnockedOut()) + mAnimation->setLoopingEnabled(mCurrentHit, knockout); + return; + } + + if (!knockout && !knockdown && !recovery && !block) + return; + + MWRender::Animation::AnimPriority priority(Priority_Knockdown); + std::string_view startKey = "start"; + std::string_view stopKey = "stop"; + if (knockout) + { + mHitState = isSwimming ? CharState_SwimKnockOut : CharState_KnockOut; + stats.setKnockedDown(true); } else if (knockdown) { - if (isSwimming && mAnimation->hasAnimation("swimknockdown")) - { - mHitState = CharState_SwimKnockDown; - mCurrentHit = "swimknockdown"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else if (!isSwimming && mAnimation->hasAnimation("knockdown")) - { - mHitState = CharState_KnockDown; - mCurrentHit = "knockdown"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else - { - // Knockdown animation is missing. Cancel knockdown state. - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); - } + mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; } else if (recovery) { - std::string anim = chooseRandomGroup("swimhit"); - if (isSwimming && mAnimation->hasAnimation(anim)) - { - mHitState = CharState_SwimHit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else - { - anim = chooseRandomGroup("hit"); - if (mAnimation->hasAnimation(anim)) - { - mHitState = CharState_Hit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - } + mHitState = isSwimming ? CharState_SwimHit : CharState_Hit; + priority = Priority_Hit; } - else if (block && mAnimation->hasAnimation("shield")) + else if (block) { mHitState = CharState_Block; - mCurrentHit = "shield"; - MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); - priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; - priorityBlock[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; - mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); + priority = Priority_Hit; + priority[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; + priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + startKey = "block start"; + stopKey = "block stop"; + } + + mCurrentHit = hitStateToAnimGroup(mHitState); + + if (isRecovery()) + { + mCurrentHit = chooseRandomGroup(mCurrentHit); + if (mHitState == CharState_SwimHit && !mAnimation->hasAnimation(mCurrentHit)) + mCurrentHit = chooseRandomGroup(hitStateToAnimGroup(CharState_Hit)); } // Cancel upper body animations if (isKnockedOut() || isKnockedDown()) { - if (mUpperBodyState > UpperCharState_WeapEquiped) - { + if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + if (mUpperBodyState > UpperBodyState::WeaponEquipped) + { + mUpperBodyState = UpperBodyState::WeaponEquipped; if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } - else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) + else if (mUpperBodyState < UpperBodyState::WeaponEquipped) { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_Nothing; + mUpperBodyState = UpperBodyState::None; } } - if (mHitState != CharState_None) - idle = CharState_None; - } - else if(!mAnimation->isPlaying(mCurrentHit)) - { - mCurrentHit.erase(); - if (knockdown) - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); - if (recovery) - mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); - if (block) - mPtr.getClass().getCreatureStats(mPtr).setBlock(false); - mHitState = CharState_None; - } - else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0 - && mTimeUntilWake <= 0) - { - mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; - mAnimation->disable(mCurrentHit); - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); - } -} -void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force) -{ - if (!force && jump == mJumpState && idle == CharState_None) - return; - - std::string jumpAnimName; - MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; - if (jump != JumpState_None) - { - jumpAnimName = "jump"; - if(!weapShortGroup.empty()) + if (!mAnimation->hasAnimation(mCurrentHit)) { - jumpAnimName += weapShortGroup; - if(!mAnimation->hasAnimation(jumpAnimName)) - { - jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); - - // If we apply jump only for lower body, do not reset idle animations. - // For upper body there will be idle animation. - if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) - idle = CharState_Idle; - } + mCurrentHit.clear(); + return; } + + mAnimation->play( + mCurrentHit, priority, MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); } - if (!force && jump == mJumpState) - return; - - bool startAtLoop = (jump == mJumpState); - mJumpState = jump; - - if (!mCurrentJump.empty()) + void CharacterController::refreshJumpAnims(JumpingState jump, bool force) { - mAnimation->disable(mCurrentJump); - 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.f, ~0ul); - mCurrentJump = jumpAnimName; - } - } - else if (mJumpState == JumpState_Landing) - { - if (mAnimation->hasAnimation(jumpAnimName)) - { - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, - 1.0f, "loop stop", "stop", 0.0f, 0); - mCurrentJump = jumpAnimName; - } - } -} - -bool CharacterController::onOpen() -{ - if (mPtr.getTypeName() == typeid(ESM::Container).name()) - { - if (!mAnimation->hasAnimation("containeropen")) - return true; - - if (mAnimation->isPlaying("containeropen")) - return false; - - if (mAnimation->isPlaying("containerclose")) - return false; - - mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0); - if (mAnimation->isPlaying("containeropen")) - return false; - } - - return true; -} - -void CharacterController::onClose() -{ - if (mPtr.getTypeName() == typeid(ESM::Container).name()) - { - if (!mAnimation->hasAnimation("containerclose")) + if (!force && jump == mJumpState) return; - float complete, startPoint = 0.f; - bool animPlaying = mAnimation->getInfo("containeropen", &complete); - if (animPlaying) - startPoint = 1.f - complete; + if (jump == JumpState_None) + { + if (!mCurrentJump.empty()) + resetCurrentIdleState(); + resetCurrentJumpState(); + return; + } - mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, 0); + std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType); + std::string jumpAnimName = "jump"; + jumpAnimName += weapShortGroup; + MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; + if (!weapShortGroup.empty() && !mAnimation->hasAnimation(jumpAnimName)) + jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); + + if (!mAnimation->hasAnimation(jumpAnimName)) + { + if (!mCurrentJump.empty()) + resetCurrentIdleState(); + resetCurrentJumpState(); + return; + } + + bool startAtLoop = (jump == mJumpState); + mJumpState = jump; + clearStateAnimation(mCurrentJump); + + mCurrentJump = jumpAnimName; + if (mJumpState == JumpState_InAir) + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, startAtLoop ? "loop start" : "start", + "stop", 0.f, ~0ul); + else if (mJumpState == JumpState_Landing) + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); } -} -std::string CharacterController::getWeaponAnimation(int weaponType) const -{ - std::string weaponGroup = getWeaponType(weaponType)->mLongGroup; - bool isRealWeapon = weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell && weaponType != ESM::Weapon::None; - if (isRealWeapon && !mAnimation->hasAnimation(weaponGroup)) + bool CharacterController::onOpen() const { - static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; - static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; + if (mPtr.getType() == ESM::Container::sRecordId) + { + if (!mAnimation->hasAnimation("containeropen")) + return true; - const ESM::WeaponType* weapInfo = getWeaponType(weaponType); + if (mAnimation->isPlaying("containeropen")) + return false; + + if (mAnimation->isPlaying("containerclose")) + return false; + + mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + "start", "stop", 0.f, 0); + if (mAnimation->isPlaying("containeropen")) + return false; + } + + return true; + } + + void CharacterController::onClose() const + { + if (mPtr.getType() == ESM::Container::sRecordId) + { + if (!mAnimation->hasAnimation("containerclose")) + return; + + float complete, startPoint = 0.f; + bool animPlaying = mAnimation->getInfo("containeropen", &complete); + if (animPlaying) + startPoint = 1.f - complete; + + mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + "start", "stop", startPoint, 0); + } + } + + std::string_view CharacterController::getWeaponAnimation(int weaponType) const + { + std::string_view weaponGroup = getWeaponType(weaponType)->mLongGroup; + if (isRealWeapon(weaponType) && !mAnimation->hasAnimation(weaponGroup)) + { + static const std::string_view oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; + static const std::string_view twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; + + const ESM::WeaponType* weapInfo = getWeaponType(weaponType); + + // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones + if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) + weaponGroup = twoHandFallback; + else + weaponGroup = oneHandFallback; + } + else if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + return "attack1"; + + return weaponGroup; + } + + std::string_view CharacterController::getWeaponShortGroup(int weaponType) const + { + if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + return {}; + return getWeaponType(weaponType)->mShortGroup; + } + + std::string CharacterController::fallbackShortWeaponGroup( + const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask) const + { + if (!isRealWeapon(mWeaponType)) + { + if (blendMask != nullptr) + *blendMask = MWRender::Animation::BlendMask_LowerBody; + + return baseGroupName; + } + + static const std::string_view oneHandFallback = getWeaponShortGroup(ESM::Weapon::LongBladeOneHand); + static const std::string_view twoHandFallback = getWeaponShortGroup(ESM::Weapon::LongBladeTwoHand); + + std::string groupName = baseGroupName; + const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) - weaponGroup = twoHandFallback; - else if (isRealWeapon) - weaponGroup = oneHandFallback; - } + groupName += twoHandFallback; + else + groupName += oneHandFallback; - return weaponGroup; -} - -std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask) -{ - bool isRealWeapon = mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; - if (!isRealWeapon) - { - if (blendMask != nullptr) + // Special case for crossbows - we should apply 1h animations a fallback only for lower body + if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr) *blendMask = MWRender::Animation::BlendMask_LowerBody; - return baseGroupName; - } - - static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mShortGroup; - static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mShortGroup; - - std::string groupName = baseGroupName; - const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); - - // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones - if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) - groupName += twoHandFallback; - else - groupName += oneHandFallback; - - // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body - if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr) - *blendMask = MWRender::Animation::BlendMask_LowerBody; - - if (!mAnimation->hasAnimation(groupName)) - { - groupName = baseGroupName; - if (blendMask != nullptr) - *blendMask = MWRender::Animation::BlendMask_LowerBody; - } - - return groupName; -} - -void CharacterController::refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force) -{ - if (movement == mMovementState && idle == mIdleState && !force) - return; - - // Reset idle if we actually play movement animations excepts of these cases: - // 1. When we play turning animations - // 2. When we use a fallback animation for lower body since movement animation for given weapon is missing (e.g. for crossbows and spellcasting) - bool resetIdle = (movement != CharState_None && !isTurning()); - - std::string movementAnimName; - MWRender::Animation::BlendMask movemask; - const StateInfo *movestate; - - movemask = MWRender::Animation::BlendMask_All; - movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(movement)); - if(movestate != sMovementListEnd) - { - movementAnimName = movestate->groupname; - if(!weapShortGroup.empty()) + if (!mAnimation->hasAnimation(groupName)) { - std::string::size_type swimpos = movementAnimName.find("swim"); - if (swimpos == std::string::npos) + groupName = baseGroupName; + if (blendMask != nullptr) + *blendMask = MWRender::Animation::BlendMask_LowerBody; + } + + return groupName; + } + + void CharacterController::refreshMovementAnims(CharacterState movement, bool force) + { + if (movement == mMovementState && !force) + return; + + std::string_view movementAnimGroup = movementStateToAnimGroup(movement); + + if (movementAnimGroup.empty()) + { + if (!mCurrentMovement.empty()) + resetCurrentIdleState(); + resetCurrentMovementState(); + return; + } + std::string movementAnimName{ movementAnimGroup }; + + mMovementState = movement; + std::string::size_type swimpos = movementAnimName.find("swim"); + if (!mAnimation->hasAnimation(movementAnimName)) + { + if (swimpos != std::string::npos) { - if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case - movementAnimName = weapShortGroup + movementAnimName; - else - movementAnimName += weapShortGroup; - } - - if(!mAnimation->hasAnimation(movementAnimName)) - { - movementAnimName = movestate->groupname; - if (swimpos == std::string::npos) - { - movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); - - // If we apply movement only for lower body, do not reset idle animations. - // For upper body there will be idle animation. - if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) - idle = CharState_Idle; - - if (movemask == MWRender::Animation::BlendMask_LowerBody) - resetIdle = false; - } + movementAnimName.erase(swimpos, 4); + swimpos = std::string::npos; } } - } - if(force || movement != mMovementState) - { - mMovementState = movement; - if(movestate != sMovementListEnd) + MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; + + std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType); + + // Non-biped creatures don't use spellcasting-specific movement animations. + if (!isRealWeapon(mWeaponType) && !mPtr.getClass().isBipedal(mPtr)) + weapShortGroup = {}; + + if (swimpos == std::string::npos && !weapShortGroup.empty()) { - if(!mAnimation->hasAnimation(movementAnimName)) + std::string weapMovementAnimName; + // Spellcasting stance turning is a special case + if (mWeaponType == ESM::Weapon::Spell && isTurning()) { - std::string::size_type swimpos = movementAnimName.find("swim"); - if (swimpos != std::string::npos) - { - movementAnimName.erase(swimpos, 4); - if (!weapShortGroup.empty()) - { - std::string weapMovementAnimName = movementAnimName + weapShortGroup; - if(mAnimation->hasAnimation(weapMovementAnimName)) - movementAnimName = weapMovementAnimName; - else - { - movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); - if (movemask == MWRender::Animation::BlendMask_LowerBody) - resetIdle = false; - } - } - } + weapMovementAnimName = weapShortGroup; + weapMovementAnimName += movementAnimName; + } + else + { + weapMovementAnimName = movementAnimName; + weapMovementAnimName += weapShortGroup; + } - if (swimpos == std::string::npos || !mAnimation->hasAnimation(movementAnimName)) - { - std::string::size_type runpos = movementAnimName.find("run"); - if (runpos != std::string::npos) - { - movementAnimName.replace(runpos, runpos+3, "walk"); - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - } - else - movementAnimName.clear(); - } + if (!mAnimation->hasAnimation(weapMovementAnimName)) + weapMovementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); + + movementAnimName = weapMovementAnimName; + } + + if (!mAnimation->hasAnimation(movementAnimName)) + { + std::string::size_type runpos = movementAnimName.find("run"); + if (runpos != std::string::npos) + movementAnimName.replace(runpos, 3, "walk"); + + if (!mAnimation->hasAnimation(movementAnimName)) + { + if (!mCurrentMovement.empty()) + resetCurrentIdleState(); + resetCurrentMovementState(); + return; } } @@ -588,126 +722,119 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup mMovementAnimationControlled = true; - mAnimation->disable(mCurrentMovement); - - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - + clearStateAnimation(mCurrentMovement); mCurrentMovement = movementAnimName; - if(!mCurrentMovement.empty()) + + // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity + // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. + mAdjustMovementAnimSpeed = true; + if (mPtr.getClass().getType() == ESM::Creature::sRecordId + && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) { - if (resetIdle) + CharacterState walkState = runStateToWalkState(mMovementState); + std::string_view anim = movementStateToAnimGroup(walkState); + + mMovementAnimSpeed = mAnimation->getVelocity(anim); + if (mMovementAnimSpeed <= 1.0f) { - mAnimation->disable(mCurrentIdle); - mIdleState = CharState_None; - idle = CharState_None; + // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), + // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist + // we will play without any scaling. + // Makes the speed attribute of most water creatures totally useless. + // And again, this can not be fixed without patching game data. + mAdjustMovementAnimSpeed = false; + mMovementAnimSpeed = 1.f; } - - // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity - // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. - std::string anim = mCurrentMovement; - mAdjustMovementAnimSpeed = true; - if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() - && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) - { - CharacterState walkState = runStateToWalkState(mMovementState); - const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); - anim = stateinfo->groupname; - - mMovementAnimSpeed = mAnimation->getVelocity(anim); - if (mMovementAnimSpeed <= 1.0f) - { - // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), - // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist - // we will play without any scaling. - // Makes the speed attribute of most water creatures totally useless. - // And again, this can not be fixed without patching game data. - mAdjustMovementAnimSpeed = false; - mMovementAnimSpeed = 1.f; - } - } - else - { - mMovementAnimSpeed = mAnimation->getVelocity(anim); - - if (mMovementAnimSpeed <= 1.0f) - { - // The first person anims don't have any velocity to calculate a speed multiplier from. - // We use the third person velocities instead. - // FIXME: should be pulled from the actual animation, but it is not presently loaded. - bool sneaking = mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack - || mMovementState == CharState_SneakLeft || mMovementState == CharState_SneakRight; - mMovementAnimSpeed = (sneaking ? 33.5452f : (isRunning() ? 222.857f : 154.064f)); - mMovementAnimationControlled = false; - } - } - - mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, - 1.f, "start", "stop", startpoint, ~0ul, true); } else - mMovementState = CharState_None; + { + mMovementAnimSpeed = mAnimation->getVelocity(mCurrentMovement); + + if (mMovementAnimSpeed <= 1.0f) + { + // The first person anims don't have any velocity to calculate a speed multiplier from. + // We use the third person velocities instead. + // FIXME: should be pulled from the actual animation, but it is not presently loaded. + bool sneaking = mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack + || mMovementState == CharState_SneakLeft || mMovementState == CharState_SneakRight; + mMovementAnimSpeed = (sneaking ? 33.5452f : (isRunning() ? 222.857f : 154.064f)); + mMovementAnimationControlled = false; + } + } + + mAnimation->play( + mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); } -} -void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force) -{ - // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), - // the idle animation should be displayed - if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped) - || (mMovementState != CharState_None && !isTurning()) - || mHitState != CharState_None) - && !mPtr.getClass().isBipedal(mPtr)) - idle = CharState_None; - - if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) + void CharacterController::refreshIdleAnims(CharacterState idle, bool force) { - mIdleState = idle; - size_t numLoops = ~0ul; + // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming + // update), the idle animation should be displayed + if (((mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped) + || mMovementState != CharState_None || !mCurrentHit.empty()) + && !mPtr.getClass().isBipedal(mPtr)) + { + resetCurrentIdleState(); + return; + } + + if (!force && idle == mIdleState && (mAnimation->isPlaying(mCurrentIdle) || !mAnimQueue.empty())) + return; + + mIdleState = idle; + + std::string idleGroup = idleStateToAnimGroup(mIdleState); + if (idleGroup.empty()) + { + resetCurrentIdleState(); + return; + } + + MWRender::Animation::AnimPriority priority = getIdlePriority(mIdleState); + size_t numLoops = std::numeric_limits::max(); - std::string idleGroup; - MWRender::Animation::AnimPriority idlePriority (Priority_Default); // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to // "idle"+weapon or "idle". - if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) + bool fallback = mIdleState != CharState_Idle && !mAnimation->hasAnimation(idleGroup); + if (fallback) { - idleGroup = "idleswim"; - idlePriority = Priority_SwimIdle; + priority = getIdlePriority(CharState_Idle); + idleGroup = idleStateToAnimGroup(CharState_Idle); } - else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) + + if (fallback || mIdleState == CharState_Idle || mIdleState == CharState_SpecialIdle) { - idleGroup = "idlesneak"; - idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; - } - else if(mIdleState != CharState_None) - { - idleGroup = "idle"; - if(!weapShortGroup.empty()) + std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType); + if (!weapShortGroup.empty()) { - idleGroup += weapShortGroup; - if(!mAnimation->hasAnimation(idleGroup)) - { - idleGroup = fallbackShortWeaponGroup("idle"); - } + std::string weapIdleGroup = idleGroup; + weapIdleGroup += weapShortGroup; + if (!mAnimation->hasAnimation(weapIdleGroup)) + weapIdleGroup = fallbackShortWeaponGroup(idleGroup); + idleGroup = weapIdleGroup; // play until the Loop Stop key 2 to 5 times, then play until the Stop key // this replicates original engine behavior for the "Idle1h" 1st-person animation - numLoops = 1 + Misc::Rng::rollDice(4); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + numLoops = 1 + Misc::Rng::rollDice(4, prng); } } - // There is no need to restart anim if the new and old anims are the same. - // Just update a number of loops. - float startPoint = 0; - if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup) + if (!mAnimation->hasAnimation(idleGroup)) { - mAnimation->getInfo(mCurrentIdle, &startPoint); + resetCurrentIdleState(); + return; } - if(!mCurrentIdle.empty()) - mAnimation->disable(mCurrentIdle); + float startPoint = 0.f; + // There is no need to restart anim if the new and old anims are the same. + // Just update the number of loops. + if (mCurrentIdle == idleGroup) + mAnimation->getInfo(mCurrentIdle, &startPoint); + clearStateAnimation(mCurrentIdle); mCurrentIdle = idleGroup; +<<<<<<< HEAD if(!mCurrentIdle.empty()) mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true); @@ -838,30 +965,28 @@ void CharacterController::playRandomDeath(float startpoint) /* End of tes3mp change (major) */ - { - mDeathState = CharState_SwimDeathKnockDown; - } - else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout")) - { - mDeathState = CharState_SwimDeathKnockOut; - } - else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) - { - mDeathState = CharState_SwimDeath; - } - else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown")) - { - mDeathState = CharState_DeathKnockDown; - } - else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout")) - { - mDeathState = CharState_DeathKnockOut; - } - else - { - mDeathState = chooseRandomDeathState(); +======= + mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", + startPoint, numLoops, true); } + void CharacterController::refreshCurrentAnims( + CharacterState idle, CharacterState movement, JumpingState jump, bool force) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 + { + // If the current animation is persistent, do not touch it + if (isPersistentAnimPlaying()) + return; + + refreshHitRecoilAnims(); + refreshJumpAnims(jump, force); + refreshMovementAnims(movement, force); + + // idle handled last as it can depend on the other states + refreshIdleAnims(idle, force); + } + +<<<<<<< HEAD /* Start of tes3mp addition @@ -936,50 +1061,211 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim const MWWorld::Class &cls = mPtr.getClass(); if(cls.isActor()) +======= + void CharacterController::playDeath(float startpoint, CharacterState death) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - /* Accumulate along X/Y only for now, until we can figure out how we should - * handle knockout and death which moves the character down. */ - mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f)); + mDeathState = death; + mCurrentDeath = deathStateToAnimGroup(mDeathState); + mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); - if (cls.hasInventoryStore(mPtr)) + // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. + // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). + // However, they could still trigger text keys, such as Hit events, or sounds. + resetCurrentMovementState(); + resetCurrentWeaponState(); + resetCurrentHitState(); + resetCurrentIdleState(); + resetCurrentJumpState(); + mMovementAnimationControlled = true; + + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, false, 1.0f, "start", + "stop", startpoint, 0); + } + + CharacterState CharacterController::chooseRandomDeathState() const + { + int selected = 0; + chooseRandomGroup("death", &selected); + return static_cast(CharState_Death1 + (selected - 1)); + } + + void CharacterController::playRandomDeath(float startpoint) + { + if (mPtr == getPlayer()) { - getActiveWeapon(mPtr, &mWeaponType); - if (mWeaponType != ESM::Weapon::None) - { - mUpperBodyState = UpperCharState_WeapEquiped; - mCurrentWeapon = getWeaponAnimation(mWeaponType); - } - - if(mWeaponType != ESM::Weapon::None && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::HandToHand) - { - mAnimation->showWeapons(true); - // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, - // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) - ESM::WeaponType::Class weaponClass = getWeaponType(mWeaponType)->mWeaponClass; - bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; - mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration); - } - - mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); + // The first-person animations do not include death, so we need to + // force-switch to third person before playing the death animation. + MWBase::Environment::get().getWorld()->useDeathCamera(); } - if(!cls.getCreatureStats(mPtr).isDead()) + mDeathState = hitStateToDeathState(mHitState); + if (mDeathState == CharState_None && MWBase::Environment::get().getWorld()->isSwimming(mPtr)) + mDeathState = CharState_SwimDeath; + + if (mDeathState == CharState_None || !mAnimation->hasAnimation(deathStateToAnimGroup(mDeathState))) + mDeathState = chooseRandomDeathState(); + + // Do not interrupt scripted animation by death + if (isPersistentAnimPlaying()) + return; + + playDeath(startpoint, mDeathState); + } + + std::string CharacterController::chooseRandomAttackAnimation() const + { + std::string result; + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); + + if (isSwimming) + result = chooseRandomGroup("swimattack"); + + if (!isSwimming || !mAnimation->hasAnimation(result)) + result = chooseRandomGroup("attack"); + + return result; + } + + CharacterController::CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim) + : mPtr(ptr) + , mAnimation(anim) + { + if (!mAnimation) + return; + + mAnimation->setTextKeyListener(this); + + const MWWorld::Class& cls = mPtr.getClass(); + if (cls.isActor()) { - mIdleState = CharState_Idle; - if (cls.getCreatureStats(mPtr).getFallHeight() > 0) - mJumpState = JumpState_InAir; + /* Accumulate along X/Y only for now, until we can figure out how we should + * handle knockout and death which moves the character down. */ + mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f)); + + if (cls.hasInventoryStore(mPtr)) + { + getActiveWeapon(mPtr, &mWeaponType); + if (mWeaponType != ESM::Weapon::None) + { + mUpperBodyState = UpperBodyState::WeaponEquipped; + mCurrentWeapon = getWeaponAnimation(mWeaponType); + } + + if (mWeaponType != ESM::Weapon::None && mWeaponType != ESM::Weapon::Spell + && mWeaponType != ESM::Weapon::HandToHand) + { + mAnimation->showWeapons(true); + // Note: controllers for ranged weapon should use time for beginning of animation to play shooting + // properly, for other weapons they should use absolute time. Some mods rely on this behaviour (to + // rotate throwing projectiles, for example) + ESM::WeaponType::Class weaponClass = getWeaponType(mWeaponType)->mWeaponClass; + bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; + mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration); + } + + mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); + } + + if (!cls.getCreatureStats(mPtr).isDead()) + { + mIdleState = CharState_Idle; + if (cls.getCreatureStats(mPtr).getFallHeight() > 0) + mJumpState = JumpState_InAir; + } + else + { + const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); + if (cStats.isDeathAnimationFinished()) + { + // Set the death state, but don't play it yet + // We will play it in the first frame, but only if no script set the skipAnim flag + signed char deathanim = cStats.getDeathAnimation(); + if (deathanim == -1) + mDeathState = chooseRandomDeathState(); + else + mDeathState = static_cast(CharState_Death1 + deathanim); + + mFloatToSurface = cStats.getHealth().getBase() != 0; + } + // else: nothing to do, will detect death in the next frame and start playing death animation + } } else { - const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); - if (cStats.isDeathAnimationFinished()) + /* Don't accumulate with non-actors. */ + mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f)); + + mIdleState = CharState_Idle; + } + + // Do not update animation status for dead actors + if (mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead())) + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); + + mAnimation->runAnimation(0.f); + + unpersistAnimationState(); + } + + CharacterController::~CharacterController() + { + if (mAnimation) + { + persistAnimationState(); + mAnimation->setTextKeyListener(nullptr); + } + } + + void CharacterController::handleTextKey( + std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) + { + std::string_view evt = key->second; + + if (evt.substr(0, 7) == "sound: ") + { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, ESM::RefId::stringRefId(evt.substr(7)), 1.0f, 1.0f); + return; + } + + auto& charClass = mPtr.getClass(); + if (evt.substr(0, 10) == "soundgen: ") + { + std::string_view soundgen = evt.substr(10); + + // The event can optionally contain volume and pitch modifiers + float volume = 1.0f; + float pitch = 1.0f; + + if (soundgen.find(' ') != std::string::npos) { - // Set the death state, but don't play it yet - // We will play it in the first frame, but only if no script set the skipAnim flag - signed char deathanim = cStats.getDeathAnimation(); - if (deathanim == -1) - mDeathState = chooseRandomDeathState(); + std::vector tokens; + Misc::StringUtils::split(soundgen, tokens); + soundgen = tokens[0]; + + if (tokens.size() >= 2) + { + volume = Misc::StringUtils::toNumeric(tokens[1], volume); + } + + if (tokens.size() >= 3) + { + pitch = Misc::StringUtils::toNumeric(tokens[2], pitch); + } + } + + const ESM::RefId sound = charClass.getSoundIdFromSndGen(mPtr, soundgen); + if (!sound.empty()) + { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + if (soundgen == "left" || soundgen == "right") + { + sndMgr->playSound3D( + mPtr, sound, volume, pitch, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); + } else +<<<<<<< HEAD mDeathState = static_cast(CharState_Death1 + deathanim); mFloatToSurface = false; @@ -1201,50 +1487,73 @@ void CharacterController::updateIdleStormState(bool inwater) else { mAnimation->setLoopingEnabled("idlestorm", true); +======= + { + sndMgr->playSound3D(mPtr, sound, volume, pitch); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } return; } - } - if (mAnimation->isPlaying("idlestorm")) - { - mAnimation->setLoopingEnabled("idlestorm", false); - } -} - -bool CharacterController::updateCreatureState() -{ - const MWWorld::Class &cls = mPtr.getClass(); - CreatureStats &stats = cls.getCreatureStats(mPtr); - - int weapType = ESM::Weapon::None; - if(stats.getDrawState() == DrawState_Weapon) - weapType = ESM::Weapon::HandToHand; - else if (stats.getDrawState() == DrawState_Spell) - weapType = ESM::Weapon::Spell; - - if (weapType != mWeaponType) - { - mWeaponType = weapType; - if (mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->disable(mCurrentWeapon); - } - - if(mAttackingOrSpell) - { - if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) + if (evt.substr(0, groupname.size()) != groupname || evt.substr(groupname.size(), 2) != ": ") { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); + // Not ours, skip it + return; + } - std::string startKey = "start"; - std::string stopKey = "stop"; - if (weapType == ESM::Weapon::Spell) + std::string_view action = evt.substr(groupname.size() + 2); + if (action == "equip attach") + { + if (groupname == "shield") + mAnimation->showCarriedLeft(true); + else + mAnimation->showWeapons(true); + } + else if (action == "unequip detach") + { + if (groupname == "shield") + mAnimation->showCarriedLeft(false); + else + mAnimation->showWeapons(false); + } + else if (action == "chop hit" || action == "slash hit" || action == "thrust hit" || action == "hit") + { + int attackType = -1; + if (action == "hit") { - const std::string spellid = stats.getSpells().getSelectedSpell(); - bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); + if (groupname == "attack1" || groupname == "swimattack1") + attackType = ESM::Weapon::AT_Chop; + else if (groupname == "attack2" || groupname == "swimattack2") + attackType = ESM::Weapon::AT_Slash; + else if (groupname == "attack3" || groupname == "swimattack3") + attackType = ESM::Weapon::AT_Thrust; + } + else if (action == "chop hit") + attackType = ESM::Weapon::AT_Chop; + else if (action == "slash hit") + attackType = ESM::Weapon::AT_Slash; + else if (action == "thrust hit") + attackType = ESM::Weapon::AT_Thrust; + // We want to avoid hit keys that come out of nowhere (e.g. in the follow animation) + // and processing multiple hit keys for a single attack + if (mAttackStrength != -1.f) + { + charClass.hit(mPtr, mAttackStrength, attackType, mAttackVictim, mAttackHitPos, mAttackSuccess); + mAttackStrength = -1.f; + } + } + else if (isRandomAttackAnimation(groupname) && action == "start") + { + std::multimap::const_iterator hitKey = key; - if (!spellid.empty() && canCast) + // Not all animations have a hit key defined. If there is none, the hit happens with the start key. + bool hasHitKey = false; + while (hitKey != map.end()) + { + if (hitKey->second.starts_with(groupname)) { +<<<<<<< HEAD /* Start of tes3mp addition @@ -1273,347 +1582,407 @@ bool CharacterController::updateCreatureState() cast.playSpellCastingEffects(spellid, false); if (!mAnimation->hasAnimation("spellcast")) +======= + std::string_view suffix = std::string_view(hitKey->second).substr(groupname.size()); + if (suffix == ": hit") +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; + hasHitKey = true; + break; } - else - { - const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + if (suffix == ": stop") + break; + } + ++hitKey; + } + if (!hasHitKey && mAttackStrength != -1.f) + { + if (groupname == "attack1" || groupname == "swimattack1") + charClass.hit( + mPtr, mAttackStrength, ESM::Weapon::AT_Chop, mAttackVictim, mAttackHitPos, mAttackSuccess); + else if (groupname == "attack2" || groupname == "swimattack2") + charClass.hit( + mPtr, mAttackStrength, ESM::Weapon::AT_Slash, mAttackVictim, mAttackHitPos, mAttackSuccess); + else if (groupname == "attack3" || groupname == "swimattack3") + charClass.hit( + mPtr, mAttackStrength, ESM::Weapon::AT_Thrust, mAttackVictim, mAttackHitPos, mAttackSuccess); + mAttackStrength = -1.f; + } + } + else if (action == "shoot attach") + mAnimation->attachArrow(); + else if (action == "shoot release") + { + // See notes for melee release above + if (mAttackStrength != -1.f) + { + mAnimation->releaseArrow(mAttackStrength); + mAttackStrength = -1.f; + } + } + else if (action == "shoot follow attach") + mAnimation->attachArrow(); + // Make sure this key is actually for the RangeType we are casting. The flame atronach has + // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range + // type. + else if (groupname == "spellcast" && action == mAttackType + " release") + { + if (mCanCast) + MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); + mCastingManualSpell = false; + mCanCast = false; + } + else if (groupname == "containeropen" && action == "loot") + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, mPtr); + } - switch(effectentry.mRange) - { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; - } + void CharacterController::updatePtr(const MWWorld::Ptr& ptr) + { + mPtr = ptr; + } - startKey = mAttackType + " " + startKey; - stopKey = mAttackType + " " + stopKey; - mCurrentWeapon = "spellcast"; - } + void CharacterController::updateIdleStormState(bool inwater) const + { + if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperBodyState::None || inwater) + { + mAnimation->disable("idlestorm"); + return; + } + + const auto world = MWBase::Environment::get().getWorld(); + if (world->isInStorm()) + { + osg::Vec3f stormDirection = world->getStormDirection(); + osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0, 1, 0); + stormDirection.normalize(); + characterDirection.normalize(); + if (stormDirection * characterDirection < -0.5f) + { + if (!mAnimation->isPlaying("idlestorm")) + { + int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm; + mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul); } else - mCurrentWeapon = ""; + { + mAnimation->setLoopingEnabled("idlestorm", true); + } + return; } + } - if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation + if (mAnimation->isPlaying("idlestorm")) + { + mAnimation->setLoopingEnabled("idlestorm", false); + } + } + + bool CharacterController::updateCarriedLeftVisible(const int weaptype) const + { + // Shields/torches shouldn't be visible during any operation involving two hands + // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", + // but they are also present in weapon drawing animation. + return mAnimation->updateCarriedLeftVisible(weaptype); + } + + float CharacterController::calculateWindUp() const + { + if (mCurrentWeapon.empty() || mWeaponType == ESM::Weapon::PickProbe || isRandomAttackAnimation(mCurrentWeapon)) + return -1.f; + + float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " min attack"); + float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " max attack"); + if (minAttackTime == -1.f || minAttackTime >= maxAttackTime) + return -1.f; + + return std::clamp( + (mAnimation->getCurrentTime(mCurrentWeapon) - minAttackTime) / (maxAttackTime - minAttackTime), 0.f, 1.f); + } + + bool CharacterController::updateWeaponState() + { + const auto world = MWBase::Environment::get().getWorld(); + auto& prng = world->getPrng(); + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + + const MWWorld::Class& cls = mPtr.getClass(); + CreatureStats& stats = cls.getCreatureStats(mPtr); + int weaptype = ESM::Weapon::None; + if (stats.getDrawState() == DrawState::Weapon) + weaptype = ESM::Weapon::HandToHand; + else if (stats.getDrawState() == DrawState::Spell) + weaptype = ESM::Weapon::Spell; + + const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); + + const ESM::RefId* downSoundId = nullptr; + bool weaponChanged = false; + bool ammunition = true; + float weapSpeed = 1.f; + if (cls.hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); + if (stats.getDrawState() == DrawState::Spell) + weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + + MWWorld::Ptr newWeapon; + if (weapon != inv.end()) { - mCurrentWeapon = chooseRandomAttackAnimation(); + newWeapon = *weapon; + if (isRealWeapon(mWeaponType)) + downSoundId = &newWeapon.getClass().getDownSoundId(newWeapon); + } + // weapon->HtH switch: weapon is empty already, so we need to take sound from previous weapon + else if (!mWeapon.isEmpty() && weaptype == ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell) + downSoundId = &mWeapon.getClass().getDownSoundId(mWeapon); + + if (mWeapon != newWeapon) + { + mWeapon = newWeapon; + weaponChanged = true; } + if (stats.getDrawState() == DrawState::Weapon && !mWeapon.isEmpty() + && mWeapon.getType() == ESM::Weapon::sRecordId) + { + weapSpeed = mWeapon.get()->mBase->mData.mSpeed; + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + int ammotype = getWeaponType(mWeapon.get()->mBase->mData.mType)->mAmmoType; + if (ammotype != ESM::Weapon::None) + ammunition = ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype; + // Cancel attack if we no longer have ammunition + if (!ammunition) + { + if (mUpperBodyState == UpperBodyState::AttackWindUp) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperBodyState::WeaponEquipped; + } + setAttackingOrSpell(false); + } + } + + MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId + && updateCarriedLeftVisible(mWeaponType)) + { + if (mAnimation->isPlaying("shield")) + mAnimation->disable("shield"); + + mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, false, 1.0f, "start", + "stop", 0.0f, std::numeric_limits::max(), true); + } + else if (mAnimation->isPlaying("torch")) + { + mAnimation->disable("torch"); + } + } + + // For biped actors, blend weapon animations with lower body animations with higher priority + MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); + if (cls.isBipedal(mPtr)) + priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + + bool forcestateupdate = false; + + // We should not play equipping animation and sound during weapon->weapon transition + const bool isStillWeapon = isRealWeapon(mWeaponType) && isRealWeapon(weaptype); + + // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound + // spell expires), we should force actor to the "weapon equipped" state, interrupt attack and update animations. + if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperBodyState::WeaponEquipped) + { + forcestateupdate = true; if (!mCurrentWeapon.empty()) - { - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::BlendMask_All, true, - 1, startKey, stopKey, - 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; - - mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - - if (weapType == ESM::Weapon::HandToHand) - playSwishSound(0.0f); - } - } - - mAttackingOrSpell = false; - } - - bool animPlaying = mAnimation->getInfo(mCurrentWeapon); - if (!animPlaying) - mUpperBodyState = UpperCharState_Nothing; - return false; -} - -bool CharacterController::updateCarriedLeftVisible(const int weaptype) const -{ - // Shields/torches shouldn't be visible during any operation involving two hands - // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", - // but they are also present in weapon drawing animation. - return mAnimation->updateCarriedLeftVisible(weaptype); -} - -bool CharacterController::updateWeaponState(CharacterState& idle) -{ - const MWWorld::Class &cls = mPtr.getClass(); - CreatureStats &stats = cls.getCreatureStats(mPtr); - int weaptype = ESM::Weapon::None; - if(stats.getDrawState() == DrawState_Weapon) - weaptype = ESM::Weapon::HandToHand; - else if (stats.getDrawState() == DrawState_Spell) - weaptype = ESM::Weapon::Spell; - - const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); - - std::string upSoundId; - std::string downSoundId; - bool weaponChanged = false; - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); - if(stats.getDrawState() == DrawState_Spell) - weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - - if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None) - upSoundId = weapon->getClass().getUpSoundId(*weapon); - - if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None) - downSoundId = weapon->getClass().getDownSoundId(*weapon); - - // weapon->HtH switch: weapon is empty already, so we need to take sound from previous weapon - if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell) - downSoundId = mWeapon.getClass().getDownSoundId(mWeapon); - - MWWorld::Ptr newWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); - - if (mWeapon != newWeapon) - { - mWeapon = newWeapon; - weaponChanged = true; - } - } - - // For biped actors, blend weapon animations with lower body animations with higher priority - MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); - if (mPtr.getClass().isBipedal(mPtr)) - priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; - - bool forcestateupdate = false; - - // We should not play equipping animation and sound during weapon->weapon transition - bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None && - mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; - - // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires), - // we should force actor to the "weapon equipped" state, interrupt attack and update animations. - if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped) - { - forcestateupdate = true; - mUpperBodyState = UpperCharState_WeapEquiped; - mAttackingOrSpell = false; - mAnimation->disable(mCurrentWeapon); - mAnimation->showWeapons(true); - if (mPtr == getPlayer()) - MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); - } - - if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) - { - std::string weapgroup; - if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell) - && weaptype != mWeaponType - && mUpperBodyState != UpperCharState_UnEquipingWeap - && !isStillWeapon) - { - // We can not play un-equip animation if weapon changed since last update - if (!weaponChanged) - { - // Note: we do not disable unequipping animation automatically to avoid body desync - weapgroup = getWeaponAnimation(mWeaponType); - int unequipMask = MWRender::Animation::BlendMask_All; - bool useShieldAnims = mAnimation->useShieldAnimations(); - if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell)) - { - unequipMask = unequipMask |~MWRender::Animation::BlendMask_LeftArm; - mAnimation->play("shield", Priority_Block, - MWRender::Animation::BlendMask_LeftArm, true, - 1.0f, "unequip start", "unequip stop", 0.0f, 0); - } - else if (mWeaponType == ESM::Weapon::HandToHand) - mAnimation->showCarriedLeft(false); - - mAnimation->play(weapgroup, priorityWeapon, unequipMask, false, - 1.0f, "unequip start", "unequip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_UnEquipingWeap; - - mAnimation->detachArrow(); - - // If we do not have the "unequip detach" key, hide weapon manually. - if (mAnimation->getTextKeyTime(weapgroup+": unequip detach") < 0) - mAnimation->showWeapons(false); - } - - if(!downSoundId.empty()) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, downSoundId, 1.0f, 1.0f); - } - } - - float complete; - bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if (!animPlaying || complete >= 1.0f) - { - // Weapon is changed, no current animation (e.g. unequipping or attack). - // Start equipping animation now. - if (weaptype != mWeaponType) - { - forcestateupdate = true; - bool useShieldAnims = mAnimation->useShieldAnimations(); - if (!useShieldAnims) - mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); - - weapgroup = getWeaponAnimation(weaptype); - - // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, - // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) - ESM::WeaponType::Class weaponClass = getWeaponType(weaptype)->mWeaponClass; - bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; - mAnimation->setWeaponGroup(weapgroup, useRelativeDuration); - - if (!isStillWeapon) - { - mAnimation->disable(mCurrentWeapon); - if (weaptype != ESM::Weapon::None) - { - mAnimation->showWeapons(false); - int equipMask = MWRender::Animation::BlendMask_All; - if (useShieldAnims && weaptype != ESM::Weapon::Spell) - { - equipMask = equipMask |~MWRender::Animation::BlendMask_LeftArm; - mAnimation->play("shield", Priority_Block, - MWRender::Animation::BlendMask_LeftArm, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - } - - mAnimation->play(weapgroup, priorityWeapon, equipMask, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; - - // If we do not have the "equip attach" key, show weapon manually. - if (weaptype != ESM::Weapon::Spell) - { - if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) - mAnimation->showWeapons(true); - } - } - } - - if(isWerewolf) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); - if(sound) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); - } - } - - mWeaponType = weaptype; - mCurrentWeapon = getWeaponAnimation(mWeaponType); - - if(!upSoundId.empty() && !isStillWeapon) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); - } - } - - // Make sure that we disabled unequipping animation - if (mUpperBodyState == UpperCharState_UnEquipingWeap) - { - mUpperBodyState = UpperCharState_Nothing; mAnimation->disable(mCurrentWeapon); - mWeaponType = ESM::Weapon::None; - mCurrentWeapon = getWeaponAnimation(mWeaponType); - } - } - } - - if(isWerewolf) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) - && mHasMovedInXY - && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) - && mWeaponType == ESM::Weapon::None) - { - if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) - sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, - MWSound::PlayMode::Loop); - } - else - sndMgr->stopSound3D(mPtr, "WolfRun"); - } - - // Cancel attack if we no longer have ammunition - bool ammunition = true; - bool isWeapon = false; - float weapSpeed = 1.f; - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); - isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); - if (isWeapon) - { - weapSpeed = weapon->get()->mBase->mData.mSpeed; - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - int ammotype = getWeaponType(weapon->get()->mBase->mData.mType)->mAmmoType; - if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get()->mBase->mData.mType != ammotype)) - ammunition = false; + mUpperBodyState = UpperBodyState::WeaponEquipped; + setAttackingOrSpell(false); + mAnimation->showWeapons(true); } - if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + if (!isKnockedOut() && !isKnockedDown() && !isRecovery()) { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; - } - } - - // Combat for actors with persistent animations obviously will be buggy - if (isPersistentAnimPlaying()) - return forcestateupdate; - - float complete; - bool animPlaying; - ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; - if(mAttackingOrSpell) - { - MWWorld::Ptr player = getPlayer(); - - bool resetIdle = ammunition; - if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) - { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - mAttackStrength = 0; - - // Randomize attacks for non-bipedal creatures with Weapon flag - if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() && - !mPtr.getClass().isBipedal(mPtr) && - (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) + std::string weapgroup; + if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell) && weaptype != mWeaponType + && mUpperBodyState <= UpperBodyState::AttackWindUp && mUpperBodyState != UpperBodyState::Unequipping + && !isStillWeapon) { - mCurrentWeapon = chooseRandomAttackAnimation(); - } - - if(mWeaponType == ESM::Weapon::Spell) - { - // Unset casting flag, otherwise pressing the mouse button down would - // continue casting every frame if there is no animation - mAttackingOrSpell = false; - if (mPtr == player) + // We can not play un-equip animation if weapon changed since last update + if (!weaponChanged) { - MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); - - // 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) - std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); - stats.getSpells().setSelectedSpell(selectedSpell); - } - std::string spellid = stats.getSpells().getSelectedSpell(); - bool isMagicItem = false; - bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); - - if (spellid.empty()) - { - if (mPtr.getClass().hasInventoryStore(mPtr)) + // Note: we do not disable unequipping animation automatically to avoid body desync + weapgroup = getWeaponAnimation(mWeaponType); + int unequipMask = MWRender::Animation::BlendMask_All; + bool useShieldAnims = mAnimation->useShieldAnimations(); + if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell + && !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell)) { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + unequipMask = unequipMask | ~MWRender::Animation::BlendMask_LeftArm; + mAnimation->play("shield", Priority_Block, MWRender::Animation::BlendMask_LeftArm, true, 1.0f, + "unequip start", "unequip stop", 0.0f, 0); + } + else if (mWeaponType == ESM::Weapon::HandToHand) + mAnimation->showCarriedLeft(false); + + mAnimation->play( + weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); + mUpperBodyState = UpperBodyState::Unequipping; + + mAnimation->detachArrow(); + + // If we do not have the "unequip detach" key, hide weapon manually. + if (mAnimation->getTextKeyTime(weapgroup + ": unequip detach") < 0) + mAnimation->showWeapons(false); + } + + if (downSoundId && !downSoundId->empty()) + { + sndMgr->playSound3D(mPtr, *downSoundId, 1.0f, 1.0f); + } + } + + float complete; + bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if (!animPlaying || complete >= 1.0f) + { + // Weapon is changed, no current animation (e.g. unequipping or attack). + // Start equipping animation now. + if (weaptype != mWeaponType && mUpperBodyState <= UpperBodyState::WeaponEquipped) + { + forcestateupdate = true; + bool useShieldAnims = mAnimation->useShieldAnimations(); + if (!useShieldAnims) + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + + weapgroup = getWeaponAnimation(weaptype); + + // Note: controllers for ranged weapon should use time for beginning of animation to play shooting + // properly, for other weapons they should use absolute time. Some mods rely on this behaviour (to + // rotate throwing projectiles, for example) + ESM::WeaponType::Class weaponClass = getWeaponType(weaptype)->mWeaponClass; + bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; + mAnimation->setWeaponGroup(weapgroup, useRelativeDuration); + + if (!isStillWeapon) + { + if (animPlaying) + mAnimation->disable(mCurrentWeapon); + if (weaptype != ESM::Weapon::None) + { + mAnimation->showWeapons(false); + int equipMask = MWRender::Animation::BlendMask_All; + if (useShieldAnims && weaptype != ESM::Weapon::Spell) + { + equipMask = equipMask | ~MWRender::Animation::BlendMask_LeftArm; + mAnimation->play("shield", Priority_Block, MWRender::Animation::BlendMask_LeftArm, true, + 1.0f, "equip start", "equip stop", 0.0f, 0); + } + + mAnimation->play( + weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start", "equip stop", 0.0f, 0); + mUpperBodyState = UpperBodyState::Equipping; + + // If we do not have the "equip attach" key, show weapon manually. + if (weaptype != ESM::Weapon::Spell + && mAnimation->getTextKeyTime(weapgroup + ": equip attach") < 0) + { + mAnimation->showWeapons(true); + } + + if (!mWeapon.isEmpty() && mWeaponType != ESM::Weapon::HandToHand && isRealWeapon(weaptype)) + { + const ESM::RefId& upSoundId = mWeapon.getClass().getUpSoundId(mWeapon); + if (!upSoundId.empty()) + sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); + } + } + } + + if (isWerewolf) + { + const MWWorld::ESMStore& store = world->getStore(); + const ESM::Sound* sound = store.get().searchRandom("WolfEquip", prng); + if (sound) + { + sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); + } + } + + mWeaponType = weaptype; + mCurrentWeapon = weapgroup; + } + + // Make sure that we disabled unequipping animation + if (mUpperBodyState == UpperBodyState::Unequipping) + { + resetCurrentWeaponState(); + mWeaponType = ESM::Weapon::None; + } + } + } + + if (isWerewolf) + { + const ESM::RefId wolfRun = ESM::RefId::stringRefId("WolfRun"); + if (isRunning() && !world->isSwimming(mPtr) && mWeaponType == ESM::Weapon::None) + { + if (!sndMgr->getSoundPlaying(mPtr, wolfRun)) + sndMgr->playSound3D(mPtr, wolfRun, 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop); + } + else + sndMgr->stopSound3D(mPtr, wolfRun); + } + + // Combat for actors with persistent animations obviously will be buggy + if (isPersistentAnimPlaying()) + return forcestateupdate; + + float complete = 0.f; + bool animPlaying = false; + ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; + if (getAttackingOrSpell()) + { + bool resetIdle = true; + if (mUpperBodyState == UpperBodyState::WeaponEquipped + && (mHitState == CharState_None || mHitState == CharState_Block)) + { + mAttackStrength = -1.f; + + // Randomize attacks for non-bipedal creatures + if (!cls.isBipedal(mPtr) + && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) + { + mCurrentWeapon = chooseRandomAttackAnimation(); + } + + if (mWeaponType == ESM::Weapon::Spell) + { + // Unset casting flag, otherwise pressing the mouse button down would + // continue casting every frame if there is no animation + setAttackingOrSpell(false); + if (mPtr == getPlayer()) + { + // 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) + const ESM::RefId& selectedSpell + = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); + stats.getSpells().setSelectedSpell(selectedSpell); + } + ESM::RefId spellid = stats.getSpells().getSelectedSpell(); + bool isMagicItem = false; + + // Play hand VFX and allow castSpell use (assuming an animation is going to be played) if + // spellcasting is successful. Manual spellcasting bypasses restrictions. + MWWorld::SpellCastState spellCastResult = MWWorld::SpellCastState::Success; + if (!mCastingManualSpell) + spellCastResult = world->startSpellCast(mPtr); + mCanCast = spellCastResult == MWWorld::SpellCastState::Success; + + if (spellid.empty() && cls.hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); if (inv.getSelectedEnchantItem() != inv.end()) { const MWWorld::Ptr& enchantItem = *inv.getSelectedEnchantItem(); @@ -1621,8 +1990,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle) isMagicItem = true; } } - } +<<<<<<< HEAD static const bool useCastingAnimations = Settings::Manager::getBool("use magic item animations", "Game"); if (isMagicItem && !useCastingAnimations) { @@ -1658,127 +2027,40 @@ bool CharacterController::updateWeaponState(CharacterState& idle) std::vector effects; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); if (isMagicItem) +======= + if (isMagicItem && !Settings::game().mUseMagicItemAnimations) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - const ESM::Enchantment *enchantment = store.get().find(spellid); - effects = enchantment->mEffects.mList; + world->breakInvisibility(mPtr); + // Enchanted items by default do not use casting animations + world->castSpell(mPtr); + resetIdle = false; + // Spellcasting animation needs to "play" for at least one frame to reset the aiming factor + animPlaying = true; + mUpperBodyState = UpperBodyState::Casting; } - else + // Play the spellcasting animation/VFX if the spellcasting was successful or failed due to + // insufficient magicka. Used up powers are exempt from this from some reason. + else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed) { - const ESM::Spell *spell = store.get().find(spellid); - effects = spell->mEffects.mList; - } + world->breakInvisibility(mPtr); + MWMechanics::CastSpell cast(mPtr, {}, false, mCastingManualSpell); - const ESM::MagicEffect *effect = store.get().find(effects.back().mEffectID); // use last effect of list for color of VFX_Hands - - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); - - for (size_t iter = 0; iter < effects.size(); ++iter) // play hands vfx for each effect - { - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); - - if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); - } - - const ESM::ENAMstruct &firstEffect = effects.at(0); // first effect used for casting animation - - std::string startKey; - std::string stopKey; - if (isRandomAttackAnimation(mCurrentWeapon)) - { - startKey = "start"; - stopKey = "stop"; - MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; - } - else - { - switch(firstEffect.mRange) + const std::vector* effects{ nullptr }; + const MWWorld::ESMStore& store = world->getStore(); + if (isMagicItem) { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; - } - - startKey = mAttackType+" start"; - stopKey = mAttackType+" stop"; - } - - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1, startKey, stopKey, - 0.0f, 0); - mUpperBodyState = UpperCharState_CastingSpell; - } - else - { - resetIdle = false; - } - } - else if(mWeaponType == ESM::Weapon::PickProbe) - { - MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - MWWorld::Ptr item = *weapon; - // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); - std::string resultMessage, resultSound; - - if(!target.isEmpty()) - { - if(item.getTypeName() == typeid(ESM::Lockpick).name()) - Security(mPtr).pickLock(target, item, resultMessage, resultSound); - else if(item.getTypeName() == typeid(ESM::Probe).name()) - Security(mPtr).probeTrap(target, item, resultMessage, resultSound); - } - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "start", "stop", 0.0, 0); - mUpperBodyState = UpperCharState_FollowStartToFollowStop; - - if(!resultMessage.empty()) - MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); - if(!resultSound.empty()) - MWBase::Environment::get().getSoundManager()->playSound3D(target, resultSound, - 1.0f, 1.0f); - } - else if (ammunition) - { - std::string startKey; - std::string stopKey; - - if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) - { - mAttackType = "shoot"; - startKey = mAttackType+" start"; - stopKey = mAttackType+" min attack"; - } - else if (isRandomAttackAnimation(mCurrentWeapon)) - { - startKey = "start"; - stopKey = "stop"; - } - else - { - if(mPtr == getPlayer()) - { - if (Settings::Manager::getBool("best attack", "Game")) - { - if (isWeapon) - { - MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - mAttackType = getBestAttack(weapon->get()->mBase); - } - else - { - // There is no "best attack" for Hand-to-Hand - setAttackTypeRandomly(mAttackType); - } + const ESM::Enchantment* enchantment = store.get().find(spellid); + effects = &enchantment->mEffects.mList; + cast.playSpellCastingEffects(enchantment); } else { - setAttackTypeBasedOnMovement(); + const ESM::Spell* spell = store.get().find(spellid); + effects = &spell->mEffects.mList; + cast.playSpellCastingEffects(spell); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -1862,271 +2144,339 @@ bool CharacterController::updateWeaponState(CharacterState& idle) const ESM::Sound *sound = store.get().searchRandom("WolfSwing"); if(sound) sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); +======= + if (mCanCast) + { + const ESM::MagicEffect* effect = store.get().find( + effects->back().mEffectID); // use last effect of list for color of VFX_Hands + + const ESM::Static* castStatic + = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); + + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + if (!effects->empty()) + { + if (mAnimation->getNode("Bip01 L Hand")) + mAnimation->addEffect( + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, + "Bip01 L Hand", effect->mParticle); + + if (mAnimation->getNode("Bip01 R Hand")) + mAnimation->addEffect( + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, + "Bip01 R Hand", effect->mParticle); + } + } + + const ESM::ENAMstruct& firstEffect = effects->at(0); // first effect used for casting animation + + std::string startKey; + std::string stopKey; + if (isRandomAttackAnimation(mCurrentWeapon)) + { + startKey = "start"; + stopKey = "stop"; + if (mCanCast) + world->castSpell( + mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately + mCastingManualSpell = false; + mCanCast = false; + } + else + { + switch (firstEffect.mRange) + { + case 0: + mAttackType = "self"; + break; + case 1: + mAttackType = "touch"; + break; + case 2: + mAttackType = "target"; + break; + } + + startKey = mAttackType + " start"; + stopKey = mAttackType + " stop"; + } + + mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, 1, + startKey, stopKey, 0.0f, 0); + mUpperBodyState = UpperBodyState::Casting; + } + else + { + resetIdle = false; + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } else { - playSwishSound(attackStrength); + std::string startKey = "start"; + std::string stopKey = "stop"; + + if (mWeaponType != ESM::Weapon::PickProbe && !isRandomAttackAnimation(mCurrentWeapon)) + { + if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) + mAttackType = "shoot"; + else if (mPtr == getPlayer()) + { + if (Settings::game().mBestAttack) + { + if (!mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId) + { + mAttackType = getBestAttack(mWeapon.get()->mBase); + } + else + { + // There is no "best attack" for Hand-to-Hand + mAttackType = getRandomAttackType(); + } + } + else + { + mAttackType = getMovementBasedAttackType(); + } + } + // else if (mPtr != getPlayer()) use mAttackType set by AiCombat + startKey = mAttackType + ' ' + startKey; + stopKey = mAttackType + " max attack"; + } + + mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, + weapSpeed, startKey, stopKey, 0.0f, 0); + mUpperBodyState = UpperBodyState::AttackWindUp; + + // Reset the attack results when the attack starts. + // Strictly speaking this should probably be done when the attack ends, + // but the attack animation might be cancelled in a myriad different ways. + mAttackSuccess = false; + mAttackVictim = MWWorld::Ptr(); + mAttackHitPos = osg::Vec3f(); } } - mAttackStrength = attackStrength; - mAnimation->disable(mCurrentWeapon); - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - weapSpeed, mAttackType+" max attack", mAttackType+" min hit", - 1.0f-complete, 0); - - complete = 0.f; - mUpperBodyState = UpperCharState_MaxAttackToMinHit; - } - else if (isKnockedDown()) - { - if (mUpperBodyState > UpperCharState_WeapEquiped) + // We should not break swim and sneak animations + if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) { - mUpperBodyState = UpperCharState_WeapEquiped; - if (mWeaponType > ESM::Weapon::None) - mAnimation->showWeapons(true); + resetCurrentIdleState(); } - mAnimation->disable(mCurrentWeapon); } - } - mAnimation->setPitchFactor(0.f); - if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) - { - switch (mUpperBodyState) + // Random attack and pick/probe animations never have wind up and are played to their end. + // Other animations must be released when the attack state is unset. + if (mUpperBodyState == UpperBodyState::AttackWindUp + && (mWeaponType == ESM::Weapon::PickProbe || isRandomAttackAnimation(mCurrentWeapon) + || !getAttackingOrSpell())) + { + mUpperBodyState = UpperBodyState::AttackRelease; + world->breakInvisibility(mPtr); + if (mWeaponType == ESM::Weapon::PickProbe) + { + // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use + // lockpicks/probes. + MWWorld::Ptr target = world->getFacedObject(); + + if (!target.isEmpty()) + { + std::string_view resultMessage, resultSound; + if (mWeapon.getType() == ESM::Lockpick::sRecordId) + Security(mPtr).pickLock(target, mWeapon, resultMessage, resultSound); + else if (mWeapon.getType() == ESM::Probe::sRecordId) + Security(mPtr).probeTrap(target, mWeapon, resultMessage, resultSound); + if (!resultMessage.empty()) + MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); + if (!resultSound.empty()) + sndMgr->playSound3D(target, ESM::RefId::stringRefId(resultSound), 1.0f, 1.0f); + } + } + else + { + mAttackStrength = calculateWindUp(); + if (mAttackStrength == -1.f) + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); + if (weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) + { + mAttackSuccess = cls.evaluateHit(mPtr, mAttackVictim, mAttackHitPos); + if (!mAttackSuccess) + mAttackStrength = 0.f; + playSwishSound(); + } + } + + if (mWeaponType == ESM::Weapon::PickProbe || isRandomAttackAnimation(mCurrentWeapon)) + mUpperBodyState = UpperBodyState::AttackEnd; + } + + if (mUpperBodyState == UpperBodyState::AttackRelease) + { + // The release state might have been reached before reaching the wind-up section. We'll play the new section + // only when the wind-up section is reached. + float currentTime = mAnimation->getCurrentTime(mCurrentWeapon); + float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " min attack"); + float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " max attack"); + if (minAttackTime <= currentTime && currentTime <= maxAttackTime) + { + std::string hit = mAttackType != "shoot" ? "hit" : "release"; + + float startPoint = 0.f; + + // Skip a bit of the pre-hit section based on the attack strength + if (minAttackTime != -1.f && minAttackTime < maxAttackTime) + { + startPoint = 1.f - mAttackStrength; + float minHitTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " min hit"); + float hitTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + ' ' + hit); + if (maxAttackTime <= minHitTime && minHitTime < hitTime) + startPoint *= (minHitTime - maxAttackTime) / (hitTime - maxAttackTime); + } + + mAnimation->disable(mCurrentWeapon); + mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, weapSpeed, + mAttackType + " max attack", mAttackType + ' ' + hit, startPoint, 0); + } + + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + + // Try playing the "follow" section if the attack animation ended naturally or didn't play at all. + if (!animPlaying || (currentTime >= maxAttackTime && complete >= 1.f)) + { + std::string start = "follow start"; + std::string stop = "follow stop"; + + if (mAttackType != "shoot") + { + std::string strength = mAttackStrength < 0.33f ? "small" + : mAttackStrength < 0.66f ? "medium" + : "large"; + start = strength + ' ' + start; + stop = strength + ' ' + stop; + } + + // Reset attack strength to make extra sure hits that come out of nowhere aren't processed + mAttackStrength = -1.f; + + if (animPlaying) + mAnimation->disable(mCurrentWeapon); + MWRender::Animation::AnimPriority priorityFollow(priorityWeapon); + // Follow animations have lower priority than movement for non-biped creatures, logic be damned + if (!cls.isBipedal(mPtr)) + priorityFollow = Priority_Default; + mAnimation->play(mCurrentWeapon, priorityFollow, MWRender::Animation::BlendMask_All, false, weapSpeed, + mAttackType + ' ' + start, mAttackType + ' ' + stop, 0.0f, 0); + mUpperBodyState = UpperBodyState::AttackEnd; + + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + } + } + + if (!animPlaying) + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + + if (!animPlaying || complete >= 1.f) + { + if (mUpperBodyState == UpperBodyState::Equipping || mUpperBodyState == UpperBodyState::AttackEnd + || mUpperBodyState == UpperBodyState::Casting) + { + if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow) + mAnimation->attachArrow(); + + // Cancel stagger animation at the end of an attack to avoid abrupt transitions + // in favor of a different abrupt transition, like Morrowind + if (mUpperBodyState != UpperBodyState::Equipping && isRecovery()) + mAnimation->disable(mCurrentHit); + + if (animPlaying) + mAnimation->disable(mCurrentWeapon); + + mUpperBodyState = UpperBodyState::WeaponEquipped; + } + else if (mUpperBodyState == UpperBodyState::Unequipping) + { + if (animPlaying) + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperBodyState::None; + } + } + + mAnimation->setPitchFactor(0.f); + if (mUpperBodyState > UpperBodyState::WeaponEquipped + && (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)) { - case UpperCharState_StartToMinAttack: - mAnimation->setPitchFactor(complete); - break; - case UpperCharState_MinAttackToMaxAttack: - case UpperCharState_MaxAttackToMinHit: - case UpperCharState_MinHitToHit: mAnimation->setPitchFactor(1.f); - break; - case UpperCharState_FollowStartToFollowStop: - if (animPlaying) + + // A smooth transition can be provided if a pre-wind-up section is defined. Random attack animations never + // have one. + if (mUpperBodyState == UpperBodyState::AttackWindUp && !isRandomAttackAnimation(mCurrentWeapon)) + { + float currentTime = mAnimation->getCurrentTime(mCurrentWeapon); + float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " min attack"); + float startTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " start"); + if (startTime <= currentTime && currentTime < minAttackTime) + mAnimation->setPitchFactor((currentTime - startTime) / (minAttackTime - startTime)); + } + else if (mUpperBodyState == UpperBodyState::AttackEnd) { // technically we do not need a pitch for crossbow reload animation, // but we should avoid abrupt repositioning if (mWeaponType == ESM::Weapon::MarksmanCrossbow) - mAnimation->setPitchFactor(std::max(0.f, 1.f-complete*10.f)); + mAnimation->setPitchFactor(std::max(0.f, 1.f - complete * 10.f)); else - mAnimation->setPitchFactor(1.f-complete); + mAnimation->setPitchFactor(1.f - complete); } - break; - default: - break; } + + mAnimation->setAccurateAiming(mUpperBodyState > UpperBodyState::WeaponEquipped); + + return forcestateupdate; } - if(!animPlaying) + void CharacterController::updateAnimQueue() { - if(mUpperBodyState == UpperCharState_EquipingWeap || - mUpperBodyState == UpperCharState_FollowStartToFollowStop || - mUpperBodyState == UpperCharState_CastingSpell) + if (mAnimQueue.size() > 1) { - if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow) - mAnimation->attachArrow(); - - mUpperBodyState = UpperCharState_WeapEquiped; - } - else if(mUpperBodyState == UpperCharState_UnEquipingWeap) - mUpperBodyState = UpperCharState_Nothing; - } - else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon)) - { - std::string start, stop; - switch(mUpperBodyState) - { - case UpperCharState_MinAttackToMaxAttack: - //hack to avoid body pos desync when jumping/sneaking in 'max attack' state - if(!mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); - break; - case UpperCharState_StartToMinAttack: - case UpperCharState_MaxAttackToMinHit: + if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) { - if (mUpperBodyState == UpperCharState_StartToMinAttack) - { - // If actor is already stopped preparing attack, do not play the "min attack -> max attack" part. - // Happens if the player did not hold the attack button. - // Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be random. - float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); - float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack"); - if (mAttackingOrSpell || minAttackTime == maxAttackTime) - { - start = mAttackType+" min attack"; - stop = mAttackType+" max attack"; - mUpperBodyState = UpperCharState_MinAttackToMaxAttack; - break; - } + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.pop_front(); - if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) - playSwishSound(0.0f); - } - - if(mAttackType == "shoot") - { - start = mAttackType+" min hit"; - stop = mAttackType+" release"; - } - else - { - start = mAttackType+" min hit"; - stop = mAttackType+" hit"; - } - mUpperBodyState = UpperCharState_MinHitToHit; - break; + bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - case UpperCharState_MinHitToHit: - if(mAttackType == "shoot") - { - start = mAttackType+" follow start"; - stop = mAttackType+" follow stop"; - } - else - { - float str = mAttackStrength; - start = mAttackType+((str < 0.5f) ? " small follow start" - : (str < 1.0f) ? " medium follow start" - : " large follow start"); - stop = mAttackType+((str < 0.5f) ? " small follow stop" - : (str < 1.0f) ? " medium follow stop" - : " large follow stop"); - } - mUpperBodyState = UpperCharState_FollowStartToFollowStop; - break; - default: - break; } - // Note: apply crossbow reload animation only for upper body - // since blending with movement animations can give weird result. - if(!start.empty()) - { - int mask = MWRender::Animation::BlendMask_All; - if (mWeaponType == ESM::Weapon::MarksmanCrossbow) - mask = MWRender::Animation::BlendMask_UpperBody; - - mAnimation->disable(mCurrentWeapon); - if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) - mAnimation->play(mCurrentWeapon, priorityWeapon, - mask, true, - weapSpeed, start, stop, 0.0f, 0); - else - mAnimation->play(mCurrentWeapon, priorityWeapon, - mask, false, - weapSpeed, start, stop, 0.0f, 0); - } - } - else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + if (!mAnimQueue.empty()) + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } - if (mPtr.getClass().hasInventoryStore(mPtr)) + void CharacterController::update(float duration) { - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() - && updateCarriedLeftVisible(mWeaponType)) + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + const MWWorld::Class& cls = mPtr.getClass(); + osg::Vec3f movement(0.f, 0.f, 0.f); + float speed = 0.f; + + updateMagicEffects(); + + bool isPlayer = mPtr == MWMechanics::getPlayer(); + bool isFirstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); + bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); + + float scale = mPtr.getCellRef().getScale(); + + if (!Settings::game().mNormaliseRaceSpeed && cls.isNpc()) { - if (mAnimation->isPlaying("shield")) - mAnimation->disable("shield"); - - mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, - false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); - } - else if (mAnimation->isPlaying("torch")) - { - mAnimation->disable("torch"); - } - } - - mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); - - return forcestateupdate; -} - -void CharacterController::updateAnimQueue() -{ - if(mAnimQueue.size() > 1) - { - if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, - MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - } - - if(!mAnimQueue.empty()) - mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); -} - -void CharacterController::update(float duration) -{ - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Class &cls = mPtr.getClass(); - osg::Vec3f movement(0.f, 0.f, 0.f); - float speed = 0.f; - - updateMagicEffects(); - - if (isKnockedOut()) - mTimeUntilWake -= duration; - - bool isPlayer = mPtr == MWMechanics::getPlayer(); - bool isFirstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); - bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); - - float scale = mPtr.getCellRef().getScale(); - - static const bool normalizeSpeed = Settings::Manager::getBool("normalise race speed", "Game"); - if (!normalizeSpeed && mPtr.getClass().isNpc()) - { - const ESM::NPC* npc = mPtr.get()->mBase; - const ESM::Race* race = world->getStore().get().find(npc->mRace); - float weight = npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; - scale *= weight; - } - - if(!cls.isActor()) - updateAnimQueue(); - else if(!cls.getCreatureStats(mPtr).isDead()) - { - bool onground = world->isOnGround(mPtr); - bool incapacitated = ((!godmode && cls.getCreatureStats(mPtr).isParalyzed()) || cls.getCreatureStats(mPtr).getKnockedDown()); - bool inwater = world->isSwimming(mPtr); - bool flying = world->isFlying(mPtr); - bool solid = world->isActorCollisionEnabled(mPtr); - // Can't run and sneak while flying (see speed formula in Npc/Creature::getSpeed) - bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying; - bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; - CreatureStats &stats = cls.getCreatureStats(mPtr); - Movement& movementSettings = cls.getMovementSettings(mPtr); - - //Force Jump Logic - - bool isMoving = (std::abs(movementSettings.mPosition[0]) > .5 || std::abs(movementSettings.mPosition[1]) > .5); - if(!inwater && !flying && solid) - { - //Force Jump - if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) - movementSettings.mPosition[2] = onground ? 1 : 0; - //Force Move Jump, only jump if they're otherwise moving - if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) - movementSettings.mPosition[2] = onground ? 1 : 0; + const ESM::NPC* npc = mPtr.get()->mBase; + const ESM::Race* race = world->getStore().get().find(npc->mRace); + float weight = npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; + scale *= weight; } +<<<<<<< HEAD /* Start of tes3mp addition @@ -2173,377 +2523,657 @@ void CharacterController::update(float duration) static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); if (smoothMovement) +======= + if (cls.isActor() && cls.getCreatureStats(mPtr).wasTeleported()) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - static const float playerTurningCoef = 1.0 / std::max(0.01f, Settings::Manager::getFloat("smooth movement player turning delay", "Game")); - float angle = mPtr.getRefData().getPosition().rot[2]; - osg::Vec2f targetSpeed = Misc::rotateVec2f(osg::Vec2f(vec.x(), vec.y()), -angle) * movementSettings.mSpeedFactor; - osg::Vec2f delta = targetSpeed - mSmoothedSpeed; - float speedDelta = movementSettings.mSpeedFactor - mSmoothedSpeed.length(); - float deltaLen = delta.length(); + mSmoothedSpeed = osg::Vec2f(); + cls.getCreatureStats(mPtr).setTeleported(false); + } - float maxDelta; - if (isFirstPersonPlayer) - maxDelta = 1; - else if (std::abs(speedDelta) < deltaLen / 2) - // Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point). - maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f); - else if (isPlayer && speedDelta < -deltaLen / 2) - // As soon as controls are released, mwinput switches player from running to walking. - // So stopping should be instant for player, otherwise it causes a small twitch. - maxDelta = 1; - else // In all other cases speeding up and stopping are smooth. - maxDelta = duration * 3.f; + if (!cls.isActor()) + updateAnimQueue(); + else if (!cls.getCreatureStats(mPtr).isDead()) + { + bool onground = world->isOnGround(mPtr); + bool inwater = world->isSwimming(mPtr); + bool flying = world->isFlying(mPtr); + bool solid = world->isActorCollisionEnabled(mPtr); + // Can't run and sneak while flying (see speed formula in Npc/Creature::getSpeed) + bool sneak + = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying && !inwater; + bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; + CreatureStats& stats = cls.getCreatureStats(mPtr); + Movement& movementSettings = cls.getMovementSettings(mPtr); - if (deltaLen > maxDelta) - delta *= maxDelta / deltaLen; - mSmoothedSpeed += delta; + // Force Jump Logic - osg::Vec2f newSpeed = Misc::rotateVec2f(mSmoothedSpeed, angle); - movementSettings.mSpeedFactor = newSpeed.normalize(); - vec.x() = newSpeed.x(); - vec.y() = newSpeed.y(); - - const float eps = 0.001f; - if (movementSettings.mSpeedFactor < eps) + bool isMoving + = (std::abs(movementSettings.mPosition[0]) > .5 || std::abs(movementSettings.mPosition[1]) > .5); + if (!inwater && !flying) { - movementSettings.mSpeedFactor = 0; - vec.x() = 0; - vec.y() = 1; - } - else if ((vec.y() < 0) != mIsMovingBackward) - { - if (targetSpeed.length() < eps || (movementSettings.mPosition[1] < 0) == mIsMovingBackward) - vec.y() = mIsMovingBackward ? -eps : eps; + // Force Jump + if (stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) + movementSettings.mPosition[2] = onground ? 1 : 0; + // Force Move Jump, only jump if they're otherwise moving + if (stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) + movementSettings.mPosition[2] = onground ? 1 : 0; } + + osg::Vec3f rot = cls.getRotationVector(mPtr); + osg::Vec3f vec(movementSettings.asVec3()); + movementSettings.mSpeedFactor = std::min(vec.length(), 1.f); vec.normalize(); - } - float effectiveRotation = rot.z(); - bool canMove = cls.getMaxSpeed(mPtr) > 0; - static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game"); - if (!turnToMovementDirection || isFirstPersonPlayer) - { - movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2; - stats.setSideMovementAngle(0); - } - else if (canMove) - { - float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y()); - movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState_Nothing || inwater) - && std::abs(targetMovementAngle) > osg::DegreesToRadians(60.0f); - if (movementSettings.mIsStrafing) - targetMovementAngle = 0; - float delta = targetMovementAngle - stats.getSideMovementAngle(); - float cosDelta = cosf(delta); - - if ((vec.y() < 0) == mIsMovingBackward) - movementSettings.mSpeedFactor *= std::min(std::max(cosDelta, 0.f) + 0.3f, 1.f); // slow down when turn - if (std::abs(delta) < osg::DegreesToRadians(20.0f)) - mIsMovingBackward = vec.y() < 0; - - float maxDelta = osg::PI * duration * (2.5f - cosDelta); - delta = osg::clampBetween(delta, -maxDelta, maxDelta); - stats.setSideMovementAngle(stats.getSideMovementAngle() + delta); - effectiveRotation += delta; - } - - mAnimation->setLegsYawRadians(stats.getSideMovementAngle()); - if (stats.getDrawState() == MWMechanics::DrawState_Nothing || inwater) - mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2); - else - mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4); - if (smoothMovement && !isPlayer && !inwater) - mAnimation->setUpperBodyYawRadians(mAnimation->getUpperBodyYawRadians() + mAnimation->getHeadYaw() / 2); - - speed = cls.getCurrentSpeed(mPtr); - vec.x() *= speed; - vec.y() *= speed; - - if(mHitState != CharState_None && mJumpState == JumpState_None) - vec = osg::Vec3f(); - - CharacterState movestate = CharState_None; - CharacterState idlestate = CharState_SpecialIdle; - JumpingState jumpstate = JumpState_None; - - bool forcestateupdate = false; - - mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; - isrunning = isrunning && mHasMovedInXY; - - // advance athletics - if(mHasMovedInXY && isPlayer) - { - if(inwater) + const bool smoothMovement = Settings::game().mSmoothMovement; + if (smoothMovement) { - mSecondsOfSwimming += duration; - while(mSecondsOfSwimming > 1) + float angle = mPtr.getRefData().getPosition().rot[2]; + osg::Vec2f targetSpeed + = Misc::rotateVec2f(osg::Vec2f(vec.x(), vec.y()), -angle) * movementSettings.mSpeedFactor; + osg::Vec2f delta = targetSpeed - mSmoothedSpeed; + float speedDelta = movementSettings.mSpeedFactor - mSmoothedSpeed.length(); + float deltaLen = delta.length(); + + float maxDelta; + if (isFirstPersonPlayer) + maxDelta = 1; + else if (std::abs(speedDelta) < deltaLen / 2) + // Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point). + maxDelta = duration * (isPlayer ? 1.0 / Settings::game().mSmoothMovementPlayerTurningDelay : 6.f); + else if (isPlayer && speedDelta < -deltaLen / 2) + // As soon as controls are released, mwinput switches player from running to walking. + // So stopping should be instant for player, otherwise it causes a small twitch. + maxDelta = 1; + else // In all other cases speeding up and stopping are smooth. + maxDelta = duration * 3.f; + + if (deltaLen > maxDelta) + delta *= maxDelta / deltaLen; + mSmoothedSpeed += delta; + + osg::Vec2f newSpeed = Misc::rotateVec2f(mSmoothedSpeed, angle); + movementSettings.mSpeedFactor = newSpeed.normalize(); + vec.x() = newSpeed.x(); + vec.y() = newSpeed.y(); + + const float eps = 0.001f; + if (movementSettings.mSpeedFactor < eps) { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); - mSecondsOfSwimming -= 1; + movementSettings.mSpeedFactor = 0; + vec.x() = 0; + vec.y() = 1; } + else if ((vec.y() < 0) != mIsMovingBackward) + { + if (targetSpeed.length() < eps || (movementSettings.mPosition[1] < 0) == mIsMovingBackward) + vec.y() = mIsMovingBackward ? -eps : eps; + } + vec.normalize(); } - else if(isrunning && !sneak) + + float effectiveRotation = rot.z(); + bool canMove = cls.getMaxSpeed(mPtr) > 0; + const bool turnToMovementDirection = Settings::game().mTurnToMovementDirection; + if (!turnToMovementDirection || isFirstPersonPlayer) { - mSecondsOfRunning += duration; - while(mSecondsOfRunning > 1) - { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); - mSecondsOfRunning -= 1; - } + movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2; + stats.setSideMovementAngle(0); } - } + else if (canMove) + { + float targetMovementAngle + = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y()); + movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState::Nothing || inwater) + && std::abs(targetMovementAngle) > osg::DegreesToRadians(60.0f); + if (movementSettings.mIsStrafing) + targetMovementAngle = 0; + float delta = targetMovementAngle - stats.getSideMovementAngle(); + float cosDelta = cosf(delta); - // reduce fatigue - const MWWorld::Store &gmst = world->getStore().get(); - float fatigueLoss = 0; - static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->mValue.getFloat(); - static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->mValue.getFloat(); - static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->mValue.getFloat(); - static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->mValue.getFloat(); - static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->mValue.getFloat(); - static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->mValue.getFloat(); - static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->mValue.getFloat(); - static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->mValue.getFloat(); + if ((vec.y() < 0) == mIsMovingBackward) + movementSettings.mSpeedFactor + *= std::min(std::max(cosDelta, 0.f) + 0.3f, 1.f); // slow down when turn + if (std::abs(delta) < osg::DegreesToRadians(20.0f)) + mIsMovingBackward = vec.y() < 0; - if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) - { - const float encumbrance = cls.getNormalizedEncumbrance(mPtr); - if (sneak) - fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; + float maxDelta = osg::PI * duration * (2.5f - cosDelta); + delta = std::clamp(delta, -maxDelta, maxDelta); + stats.setSideMovementAngle(stats.getSideMovementAngle() + delta); + effectiveRotation += delta; + } + + mAnimation->setLegsYawRadians(stats.getSideMovementAngle()); + if (stats.getDrawState() == MWMechanics::DrawState::Nothing || inwater) + mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2); else + mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4); + if (smoothMovement && !isPlayer && !inwater) + mAnimation->setUpperBodyYawRadians(mAnimation->getUpperBodyYawRadians() + mAnimation->getHeadYaw() / 2); + + speed = cls.getCurrentSpeed(mPtr); + vec.x() *= speed; + vec.y() *= speed; + + if (isKnockedOut() || isKnockedDown() || isRecovery()) + vec = osg::Vec3f(); + + CharacterState movestate = CharState_None; + CharacterState idlestate = CharState_None; + JumpingState jumpstate = JumpState_None; + + const MWWorld::Store& gmst = world->getStore().get(); + if (vec.x() != 0.f || vec.y() != 0.f) { - if (inwater) + // advance athletics + if (isPlayer) { - if (!isrunning) - fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult; - else - fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; + if (inwater) + { + mSecondsOfSwimming += duration; + while (mSecondsOfSwimming > 1) + { + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); + mSecondsOfSwimming -= 1; + } + } + else if (isrunning && !sneak) + { + mSecondsOfRunning += duration; + while (mSecondsOfRunning > 1) + { + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); + mSecondsOfRunning -= 1; + } + } } - else if (isrunning) - fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; - } - } - fatigueLoss *= duration; - fatigueLoss *= movementSettings.mSpeedFactor; - DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); - if (!godmode) - { - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); - cls.getCreatureStats(mPtr).setFatigue(fatigue); - } - - float z = cls.getJump(mPtr); - if(sneak || inwater || flying || incapacitated || !solid || z <= 0) - vec.z() = 0.0f; - - bool inJump = true; - bool playLandingSound = false; - if(!onground && !flying && !inwater && solid) - { - // In the air (either getting up —ascending part of jump— or falling). - - forcestateupdate = (mJumpState != JumpState_InAir); - jumpstate = JumpState_InAir; - - static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->mValue.getFloat(); - static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->mValue.getFloat(); - float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; - factor = std::min(1.f, factor); - vec.x() *= factor; - vec.y() *= factor; - vec.z() = 0.0f; - } - else if(vec.z() > 0.0f && mJumpState != JumpState_InAir) - { - // Started a jump. - if (z > 0) - { - if(vec.x() == 0 && vec.y() == 0) - vec = osg::Vec3f(0.0f, 0.0f, z); - else - { - osg::Vec3f lat (vec.x(), vec.y(), 0.0f); - lat.normalize(); - vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; - } - } - } - else if(mJumpState == JumpState_InAir && !inwater && !flying && solid) - { - forcestateupdate = true; - jumpstate = JumpState_Landing; - vec.z() = 0.0f; - - // We should reset idle animation during landing - mAnimation->disable(mCurrentIdle); - - float height = cls.getCreatureStats(mPtr).land(isPlayer); - float healthLost = getFallDamage(mPtr, height); - - if (healthLost > 0.0f) - { - const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); - - // inflict fall damages if (!godmode) { - float realHealthLost = static_cast(healthLost * (1.0f - 0.25f * fatigueTerm)); - cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true); - } + // reduce fatigue + float fatigueLoss = 0.f; + static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->mValue.getFloat(); + static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->mValue.getFloat(); + static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->mValue.getFloat(); + static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->mValue.getFloat(); + static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->mValue.getFloat(); + static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->mValue.getFloat(); + static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->mValue.getFloat(); + static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->mValue.getFloat(); - const float acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); - if (healthLost > (acrobaticsSkill * fatigueTerm)) - { - if (!godmode) - cls.getCreatureStats(mPtr).setKnockedDown(true); - } - else - { - // report acrobatics progression - if (isPlayer) - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) + { + const float encumbrance = cls.getNormalizedEncumbrance(mPtr); + if (sneak) + fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; + else + { + if (inwater) + { + if (!isrunning) + fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult; + else + fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; + } + else if (isrunning) + fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; + } + } + fatigueLoss *= duration; + fatigueLoss *= movementSettings.mSpeedFactor; + DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); + cls.getCreatureStats(mPtr).setFatigue(fatigue); } } - if (mPtr.getClass().isNpc()) - playLandingSound = true; - } - else - { - if(mPtr.getClass().isNpc() && mJumpState == JumpState_InAir && !flying && solid) - playLandingSound = true; - - jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; - - vec.x() *= scale; - vec.y() *= scale; - vec.z() = 0.0f; - - inJump = false; - - if (movementSettings.mIsStrafing) + bool wasInJump = mInJump; + mInJump = false; + if (!inwater && !flying && solid) { - if(vec.x() > 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) - : (sneak ? CharState_SneakRight - : (isrunning ? CharState_RunRight : CharState_WalkRight))); - else if(vec.x() < 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) - : (sneak ? CharState_SneakLeft - : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); + // In the air (either getting up —ascending part of jump— or falling). + if (!onground) + { + mInJump = true; + jumpstate = JumpState_InAir; + + static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->mValue.getFloat(); + static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->mValue.getFloat(); + float factor = fJumpMoveBase + + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics) / 100.f; + factor = std::min(1.f, factor); + vec.x() *= factor; + vec.y() *= factor; + vec.z() = 0.0f; + } + // Started a jump. + else if (mJumpState != JumpState_InAir && vec.z() > 0.f && !sneak) + { + float z = cls.getJump(mPtr); + if (z > 0.f) + { + mInJump = true; + if (vec.x() == 0 && vec.y() == 0) + vec.z() = z; + else + { + osg::Vec3f lat(vec.x(), vec.y(), 0.0f); + lat.normalize(); + vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; + } + } + } } - else if (vec.length2() > 0.0f) + + if (!mInJump) { - if (vec.y() >= 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) - : (sneak ? CharState_SneakForward - : (isrunning ? CharState_RunForward : CharState_WalkForward))); + if (mJumpState == JumpState_InAir && !flying && solid && wasInJump) + { + float height = cls.getCreatureStats(mPtr).land(isPlayer); + float healthLost = 0.f; + if (!inwater) + healthLost = getFallDamage(mPtr, height); + + if (healthLost > 0.0f) + { + const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); + + // inflict fall damages + if (!godmode) + { + DynamicStat health = cls.getCreatureStats(mPtr).getHealth(); + float realHealthLost = healthLost * (1.0f - 0.25f * fatigueTerm); + health.setCurrent(health.getCurrent() - realHealthLost); + cls.getCreatureStats(mPtr).setHealth(health); + sndMgr->playSound3D(mPtr, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f); + if (isPlayer) + MWBase::Environment::get().getWindowManager()->activateHitOverlay(); + } + + const float acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); + if (healthLost > (acrobaticsSkill * fatigueTerm)) + { + if (!godmode) + cls.getCreatureStats(mPtr).setKnockedDown(true); + } + else + { + // report acrobatics progression + if (isPlayer) + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + } + } + + if (mPtr.getClass().isNpc()) + { + std::string_view sound; + osg::Vec3f pos(mPtr.getRefData().getPosition().asVec3()); + if (world->isUnderwater(mPtr.getCell(), pos) || world->isWalkingOnWater(mPtr)) + sound = "DefaultLandWater"; + else if (onground) + sound = "DefaultLand"; + + if (!sound.empty()) + sndMgr->playSound3D(mPtr, ESM::RefId::stringRefId(sound), 1.f, 1.f, MWSound::Type::Foot, + MWSound::PlayMode::NoPlayerLocal); + } + } + + if (mAnimation->isPlaying(mCurrentJump)) + jumpstate = JumpState_Landing; + + vec.x() *= scale; + vec.y() *= scale; + vec.z() = 0.0f; + + if (movementSettings.mIsStrafing) + { + if (vec.x() > 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) + : (sneak ? CharState_SneakRight + : (isrunning ? CharState_RunRight : CharState_WalkRight))); + else if (vec.x() < 0.0f) + movestate = (inwater + ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) + : (sneak ? CharState_SneakLeft : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); + } + else if (vec.length2() > 0.0f) + { + if (vec.y() >= 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) + : (sneak ? CharState_SneakForward + : (isrunning ? CharState_RunForward : CharState_WalkForward))); + else + movestate = (inwater + ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) + : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); + } else - movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) - : (sneak ? CharState_SneakBack - : (isrunning ? CharState_RunBack : CharState_WalkBack))); + { + // Do not play turning animation for player if rotation speed is very slow. + // Actual threshold should take framerate in account. + float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration; + + // It seems only bipedal actors use turning animations. + // Also do not use turning animations in the first-person view and when sneaking. + if (!sneak && !isFirstPersonPlayer && mPtr.getClass().isBipedal(mPtr)) + { + if (effectiveRotation > rotationThreshold) + movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; + else if (effectiveRotation < -rotationThreshold) + movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; + } + } + } + + if (turnToMovementDirection && !isFirstPersonPlayer + && (movestate == CharState_SwimRunForward || movestate == CharState_SwimWalkForward + || movestate == CharState_SwimRunBack || movestate == CharState_SwimWalkBack)) + { + float swimmingPitch = mAnimation->getBodyPitchRadians(); + float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0]; + float maxSwimPitchDelta = 3.0f * duration; + swimmingPitch += std::clamp(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta); + mAnimation->setBodyPitchRadians(swimmingPitch); + } + else + mAnimation->setBodyPitchRadians(0); + + if (inwater && isPlayer && !isFirstPersonPlayer && Settings::game().mSwimUpwardCorrection) + { + const float swimUpwardCoef = Settings::game().mSwimUpwardCoef; + vec.z() = std::abs(vec.y()) * swimUpwardCoef; + vec.y() *= std::sqrt(1.0f - swimUpwardCoef * swimUpwardCoef); + } + + // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering + if (isPlayer) + { + float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f; + float complete; + bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete); + if (movestate == CharState_None && jumpstate == JumpState_None && isTurning()) + { + if (animPlaying && complete < threshold) + movestate = mMovementState; + } } else { - // Do not play turning animation for player if rotation speed is very slow. - // Actual threshold should take framerate in account. - float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration; - - // It seems only bipedal actors use turning animations. - // Also do not use turning animations in the first-person view and when sneaking. - if (!sneak && jumpstate == JumpState_None && !isFirstPersonPlayer && mPtr.getClass().isBipedal(mPtr)) + if (mPtr.getClass().isBipedal(mPtr)) { - if(effectiveRotation > rotationThreshold) - movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - else if(effectiveRotation < -rotationThreshold) - movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; + if (mTurnAnimationThreshold > 0) + mTurnAnimationThreshold -= duration; + + if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft + || movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft) + { + mTurnAnimationThreshold = 0.05f; + } + else if (movestate == CharState_None && isTurning() && mTurnAnimationThreshold > 0) + { + movestate = mMovementState; + } } } - } - if (playLandingSound) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - std::string sound; - osg::Vec3f pos(mPtr.getRefData().getPosition().asVec3()); - if (world->isUnderwater(mPtr.getCell(), pos) || world->isWalkingOnWater(mPtr)) - sound = "DefaultLandWater"; - else if (onground) - sound = "DefaultLand"; - - if (!sound.empty()) - sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); - } - - if (turnToMovementDirection && !isFirstPersonPlayer && - (movestate == CharState_SwimRunForward || movestate == CharState_SwimWalkForward || - movestate == CharState_SwimRunBack || movestate == CharState_SwimWalkBack)) - { - float swimmingPitch = mAnimation->getBodyPitchRadians(); - float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0]; - float maxSwimPitchDelta = 3.0f * duration; - swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta); - mAnimation->setBodyPitchRadians(swimmingPitch); - } - else - mAnimation->setBodyPitchRadians(0); - - static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game"); - if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection) - { - static const float swimUpwardCoef = Settings::Manager::getFloat("swim upward coef", "Game"); - static const float swimForwardCoef = sqrtf(1.0f - swimUpwardCoef * swimUpwardCoef); - vec.z() = std::abs(vec.y()) * swimUpwardCoef; - vec.y() *= swimForwardCoef; - } - - // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering - if (isPlayer) - { - float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f; - float complete; - bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete); - if (movestate == CharState_None && jumpstate == JumpState_None && isTurning()) + if (movestate != CharState_None) { - if (animPlaying && complete < threshold) - movestate = mMovementState; + clearAnimQueue(); + jumpstate = JumpState_None; } - } - else - { - if (mPtr.getClass().isBipedal(mPtr)) + + if (mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle)) { - if (mTurnAnimationThreshold > 0) - mTurnAnimationThreshold -= duration; + if (inwater) + idlestate = CharState_IdleSwim; + else if (sneak && !mInJump) + idlestate = CharState_IdleSneak; + else + idlestate = CharState_Idle; + } + else + updateAnimQueue(); - if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft || - movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft) + if (!mSkipAnim) + { + refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState()); + updateIdleStormState(inwater); + } + + if (mInJump) + mMovementAnimationControlled = false; + + if (isTurning()) + { + // Adjust animation speed from 1.0 to 1.5 multiplier + if (duration > 0) { - mTurnAnimationThreshold = 0.05f; + float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI)); + mAnimation->adjustSpeedMult(mCurrentMovement, std::max(turnSpeed, 1.0f)); } - else if (movestate == CharState_None && isTurning() - && mTurnAnimationThreshold > 0) + } + else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) + { + // Vanilla caps the played animation speed. + const float maxSpeedMult = 10.f; + const float speedMult = speed / mMovementAnimSpeed; + mAnimation->adjustSpeedMult(mCurrentMovement, std::min(maxSpeedMult, speedMult)); + // Make sure the actual speed is the "expected" speed even though the animation is slower + scale *= std::max(1.f, speedMult / maxSpeedMult); + } + + if (!mSkipAnim) + { + if (!isKnockedDown() && !isKnockedOut()) { - movestate = mMovementState; + if (rot != osg::Vec3f()) + world->rotateObject(mPtr, rot, true); } + else // avoid z-rotating for knockdown + { + if (rot.x() != 0 && rot.y() != 0) + { + rot.z() = 0.0f; + world->rotateObject(mPtr, rot, true); + } + } + + if (!mMovementAnimationControlled) + world->queueMovement(mPtr, vec); + } + + movement = vec; + movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; + if (movement.z() == 0.f) + movementSettings.mPosition[2] = 0; + // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will + // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will + // be reset in PhysicSystem::move once the jump is handled. + + if (!mSkipAnim) + updateHeadTracking(duration); + } + else if (cls.getCreatureStats(mPtr).isDead()) + { + // initial start of death animation for actors that started the game as dead + // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag + if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) + { + // Fast-forward death animation to end for persisting corpses or corpses after end of death animation + if (cls.isPersistent(mPtr) || cls.getCreatureStats(mPtr).isDeathAnimationFinished()) + playDeath(1.f, mDeathState); } } - if(movestate != CharState_None && !isTurning()) + bool isPersist = isPersistentAnimPlaying(); + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); + if (duration > 0.0f) + moved /= duration; + else + moved = osg::Vec3f(0.f, 0.f, 0.f); + + moved.x() *= scale; + moved.y() *= scale; + + // Ensure we're moving in generally the right direction... + if (speed > 0.f && moved != osg::Vec3f()) + { + float l = moved.length(); + if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 + || std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 + || std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2) + { + moved = movement; + // For some creatures getSpeed doesn't work, so we adjust speed to the animation. + // TODO: Fix Creature::getSpeed. + float newLength = moved.length(); + if (newLength > 0 && !cls.isNpc()) + moved *= (l / newLength); + } + } + + if (mFloatToSurface && cls.isActor()) + { + if (cls.getCreatureStats(mPtr).isDead() + || (!godmode + && cls.getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Paralyze) + .getModifier() + > 0)) + { + moved.z() = 1.0; + } + } + + // Update movement + if (mMovementAnimationControlled && mPtr.getClass().isActor()) + world->queueMovement(mPtr, moved); + + mSkipAnim = false; + + mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); + } + + void CharacterController::persistAnimationState() const + { + ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); + + state.mScriptedAnims.clear(); + for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) + { + if (!iter->mPersist) + continue; + + ESM::AnimationState::ScriptedAnimation anim; + anim.mGroup = iter->mGroup; + + if (iter == mAnimQueue.begin()) + { + anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); + float complete; + mAnimation->getInfo(anim.mGroup, &complete, nullptr); + anim.mTime = complete; + } + else + { + anim.mLoopCount = iter->mLoopCount; + anim.mTime = 0.f; + } + + state.mScriptedAnims.push_back(anim); + } + } + + void CharacterController::unpersistAnimationState() + { + const ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); + + if (!state.mScriptedAnims.empty()) + { clearAnimQueue(); + for (ESM::AnimationState::ScriptedAnimations::const_iterator iter = state.mScriptedAnims.begin(); + iter != state.mScriptedAnims.end(); ++iter) + { + AnimationQueueEntry entry; + entry.mGroup = iter->mGroup; + entry.mLoopCount = iter->mLoopCount; + entry.mPersist = true; - if(mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle)) + mAnimQueue.push_back(entry); + } + + const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); + float complete = anim.mTime; + if (anim.mAbsolute) + { + float start = mAnimation->getTextKeyTime(anim.mGroup + ": start"); + float stop = mAnimation->getTextKeyTime(anim.mGroup + ": stop"); + float time = std::clamp(anim.mTime, start, stop); + complete = (time - start) / (stop - start); + } + + clearStateAnimation(mCurrentIdle); + mIdleState = CharState_SpecialIdle; + + bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); + mAnimation->play(anim.mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", + "stop", complete, anim.mLoopCount, loopfallback); + } + } + + bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool persist) + { + if (!mAnimation || !mAnimation->hasAnimation(groupname)) + return false; + + // We should not interrupt persistent animations by non-persistent ones + if (isPersistentAnimPlaying() && !persist) + return true; + + // If this animation is a looped animation (has a "loop start" key) that is already playing + // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count + // and remove any other animations that were queued. + // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners + // correctly. + if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname + && mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 + && mAnimation->isPlaying(groupname)) { - if (inwater) - idlestate = CharState_IdleSwim; - else if (sneak && !inJump) - idlestate = CharState_IdleSneak; - else - idlestate = CharState_Idle; + float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop stop"); + + if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key + endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": stop"); + + if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) + { + mAnimQueue.resize(1); + return true; + } + } + + count = std::max(count, 1); + + AnimationQueueEntry entry; + entry.mGroup = groupname; + entry.mLoopCount = count - 1; + entry.mPersist = persist; + + if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) + { + clearAnimQueue(persist); + + clearStateAnimation(mCurrentIdle); + + mIdleState = CharState_SpecialIdle; + bool loopfallback = entry.mGroup.starts_with("idle"); + mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, ((mode == 2) ? "loop start" : "start"), "stop", 0.0f, + count - 1, loopfallback); } else +<<<<<<< HEAD updateAnimQueue(); if (!mSkipAnim) @@ -2756,21 +3386,24 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { mAnimQueue.resize(1); - return true; } + + // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing + if (groupname == "idle") + entry.mPersist = false; + + mAnimQueue.push_back(entry); + + return true; } - count = std::max(count, 1); - - AnimationQueueEntry entry; - entry.mGroup = groupname; - entry.mLoopCount = count-1; - entry.mPersist = persist; - - if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) + void CharacterController::skipAnim() { +<<<<<<< HEAD clearAnimQueue(persist); mAnimation->disable(mCurrentIdle); @@ -2803,26 +3436,14 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int else { mAnimQueue.resize(1); +======= + mSkipAnim = true; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing - if (groupname == "idle") - entry.mPersist = false; - - mAnimQueue.push_back(entry); - - return true; -} - -void CharacterController::skipAnim() -{ - mSkipAnim = true; -} - -bool CharacterController::isPersistentAnimPlaying() -{ - if (!mAnimQueue.empty()) + bool CharacterController::isPersistentAnimPlaying() const { +<<<<<<< HEAD AnimationQueueEntry& first = mAnimQueue.front(); return first.mPersist && isAnimPlaying(first.mGroup); } @@ -2962,21 +3583,18 @@ void CharacterController::setVisibility(float visibility) { float alpha = 1.f; if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). +======= + if (!mAnimQueue.empty()) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - if (mPtr == getPlayer()) - alpha = 0.25f; - else - alpha = 0.05f; - } - float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); - if (chameleon) - { - alpha *= std::min(0.75f, std::max(0.25f, (100.f - chameleon)/100.f)); + const AnimationQueueEntry& first = mAnimQueue.front(); + return first.mPersist && isAnimPlaying(first.mGroup); } - visibility = std::min(visibility, alpha); + return false; } +<<<<<<< HEAD // TODO: implement a dithering shader rather than just change object transparency. mAnimation->setAlpha(visibility); } @@ -3165,54 +3783,385 @@ void CharacterController::updateHeadTracking(float duration) double xAngleRadians = 0.f; if (!mHeadTrackTarget.isEmpty()) +======= + bool CharacterController::isAnimPlaying(std::string_view groupName) const +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - osg::NodePathList nodepaths = head->getParentalNodePaths(); - if (nodepaths.empty()) - return; - osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); - osg::Vec3f headPos = mat.getTrans(); - - osg::Vec3f direction; - if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) - { - const osg::Node* node = anim->getNode("Head"); - if (node == nullptr) - node = anim->getNode("Bip01 Head"); - if (node != nullptr) - { - nodepaths = node->getParentalNodePaths(); - if (!nodepaths.empty()) - direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; - } - else - // no head node to look at, fall back to look at center of collision box - direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false); - } - direction.normalize(); - - if (!mPtr.getRefData().getBaseNode()) - return; - const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); - - zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y()); - zAngleRadians = Misc::normalizeAngle(zAngleRadians - mAnimation->getHeadYaw()) + mAnimation->getHeadYaw(); - zAngleRadians *= (1 - direction.z() * direction.z()); - xAngleRadians = std::asin(direction.z()); + if (mAnimation == nullptr) + return false; + return mAnimation->isPlaying(groupName); } - const double xLimit = osg::DegreesToRadians(40.0); - const double zLimit = osg::DegreesToRadians(30.0); - double zLimitOffset = mAnimation->getUpperBodyYawRadians(); - xAngleRadians = osg::clampBetween(xAngleRadians, -xLimit, xLimit); - zAngleRadians = osg::clampBetween(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset); + void CharacterController::clearAnimQueue(bool clearPersistAnims) + { + // Do not interrupt scripted animations, if we want to keep them + if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) + mAnimation->disable(mAnimQueue.front().mGroup); - float factor = duration*5; - factor = std::min(factor, 1.f); - xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * xAngleRadians; - zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * zAngleRadians; + if (clearPersistAnims) + { + mAnimQueue.clear(); + return; + } - mAnimation->setHeadPitch(xAngleRadians); - mAnimation->setHeadYaw(zAngleRadians); -} + for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) + { + if (!it->mPersist) + it = mAnimQueue.erase(it); + else + ++it; + } + } + + void CharacterController::forceStateUpdate() + { + if (!mAnimation) + return; + clearAnimQueue(); + + // Make sure we canceled the current attack or spellcasting, + // because we disabled attack animations anyway. + mCanCast = false; + mCastingManualSpell = false; + setAttackingOrSpell(false); + if (mUpperBodyState != UpperBodyState::None) + mUpperBodyState = UpperBodyState::WeaponEquipped; + + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); + + if (mDeathState != CharState_None) + { + playRandomDeath(); + } + + mAnimation->runAnimation(0.f); + } + + CharacterController::KillResult CharacterController::kill() + { + if (mDeathState == CharState_None) + { + playRandomDeath(); + resetCurrentIdleState(); + return Result_DeathAnimStarted; + } + + MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); + if (isAnimPlaying(mCurrentDeath)) + return Result_DeathAnimPlaying; + if (!cStats.isDeathAnimationFinished()) + { + cStats.setDeathAnimationFinished(true); + return Result_DeathAnimJustFinished; + } + return Result_DeathAnimFinished; + } + + void CharacterController::resurrect() + { + if (mDeathState == CharState_None) + return; + + resetCurrentDeathState(); + mWeaponType = ESM::Weapon::None; + } + + void CharacterController::updateContinuousVfx() const + { + // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code, + // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. + + // Stop any effects that are no longer active + std::vector effects; + mAnimation->getLoopingEffects(effects); + + for (int effectId : effects) + { + if (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished() + || mPtr.getClass() + .getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(MWMechanics::EffectKey(effectId)) + .getMagnitude() + <= 0) + mAnimation->removeEffect(effectId); + } + } + + void CharacterController::updateMagicEffects() const + { + if (!mPtr.getClass().isActor()) + return; + + float light = mPtr.getClass() + .getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Light) + .getMagnitude(); + mAnimation->setLightEffect(light); + + // If you're dead you don't care about whether you've started/stopped being a vampire or not + if (mPtr.getClass().getCreatureStats(mPtr).isDead()) + return; + + bool vampire = mPtr.getClass() + .getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Vampirism) + .getMagnitude() + > 0.0f; + mAnimation->setVampire(vampire); + } + + void CharacterController::setVisibility(float visibility) const + { + // We should take actor's invisibility in account + if (mPtr.getClass().isActor()) + { + float alpha = 1.f; + if (mPtr.getClass() + .getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Invisibility) + .getModifier()) // Ignore base magnitude (see bug #3555). + { + if (mPtr == getPlayer()) + alpha = 0.25f; + else + alpha = 0.05f; + } + float chameleon = mPtr.getClass() + .getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Chameleon) + .getMagnitude(); + if (chameleon) + { + alpha *= std::clamp(1.f - chameleon / 100.f, 0.25f, 0.75f); + } + + visibility = std::min(visibility, alpha); + } + + // TODO: implement a dithering shader rather than just change object transparency. + mAnimation->setAlpha(visibility); + } + + std::string_view CharacterController::getMovementBasedAttackType() const + { + float* move = mPtr.getClass().getMovementSettings(mPtr).mPosition; + if (std::abs(move[1]) > std::abs(move[0]) + 0.2f) // forward-backward + return "thrust"; + if (std::abs(move[0]) > std::abs(move[1]) + 0.2f) // sideway + return "slash"; + return "chop"; + } + + bool CharacterController::isRandomAttackAnimation(std::string_view group) + { + return (group == "attack1" || group == "swimattack1" || group == "attack2" || group == "swimattack2" + || group == "attack3" || group == "swimattack3"); + } + + bool CharacterController::isAttackPreparing() const + { + return mUpperBodyState == UpperBodyState::AttackWindUp; + } + + bool CharacterController::isCastingSpell() const + { + return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting; + } + + bool CharacterController::isReadyToBlock() const + { + return updateCarriedLeftVisible(mWeaponType); + } + + bool CharacterController::isKnockedDown() const + { + return mHitState == CharState_KnockDown || mHitState == CharState_SwimKnockDown; + } + + bool CharacterController::isKnockedOut() const + { + return mHitState == CharState_KnockOut || mHitState == CharState_SwimKnockOut; + } + + bool CharacterController::isTurning() const + { + return mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight + || mMovementState == CharState_SwimTurnLeft || mMovementState == CharState_SwimTurnRight; + } + + bool CharacterController::isRecovery() const + { + return mHitState == CharState_Hit || mHitState == CharState_SwimHit; + } + + bool CharacterController::isAttackingOrSpell() const + { + return mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped; + } + + bool CharacterController::isSneaking() const + { + return mIdleState == CharState_IdleSneak || mMovementState == CharState_SneakForward + || mMovementState == CharState_SneakBack || mMovementState == CharState_SneakLeft + || mMovementState == CharState_SneakRight; + } + + bool CharacterController::isRunning() const + { + return mMovementState == CharState_RunForward || mMovementState == CharState_RunBack + || mMovementState == CharState_RunLeft || mMovementState == CharState_RunRight + || mMovementState == CharState_SwimRunForward || mMovementState == CharState_SwimRunBack + || mMovementState == CharState_SwimRunLeft || mMovementState == CharState_SwimRunRight; + } + + void CharacterController::setAttackingOrSpell(bool attackingOrSpell) const + { + mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell); + } + + void CharacterController::castSpell(const ESM::RefId& spellId, bool manualSpell) + { + setAttackingOrSpell(true); + mCastingManualSpell = manualSpell; + ActionSpell action = ActionSpell(spellId); + action.prepare(mPtr); + } + + void CharacterController::setAIAttackType(std::string_view attackType) + { + mAttackType = attackType; + } + + std::string_view CharacterController::getRandomAttackType() + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + float random = Misc::Rng::rollProbability(world->getPrng()); + if (random >= 2 / 3.f) + return "thrust"; + if (random >= 1 / 3.f) + return "slash"; + return "chop"; + } + + bool CharacterController::readyToPrepareAttack() const + { + return (mHitState == CharState_None || mHitState == CharState_Block) + && mUpperBodyState <= UpperBodyState::WeaponEquipped; + } + + bool CharacterController::readyToStartAttack() const + { + if (mHitState != CharState_None && mHitState != CharState_Block) + return false; + + return mUpperBodyState == UpperBodyState::WeaponEquipped; + } + + float CharacterController::getAttackStrength() const + { + return mAttackStrength; + } + + bool CharacterController::getAttackingOrSpell() const + { + return mPtr.getClass().getCreatureStats(mPtr).getAttackingOrSpell(); + } + + void CharacterController::setActive(int active) const + { + mAnimation->setActive(active); + } + + void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr& target) + { + mHeadTrackTarget = target; + } + + void CharacterController::playSwishSound() const + { + static ESM::RefId weaponSwish = ESM::RefId::stringRefId("Weapon Swish"); + const ESM::RefId* soundId = &weaponSwish; + float volume = 0.98f + mAttackStrength * 0.02f; + float pitch = 0.75f + mAttackStrength * 0.4f; + + const MWWorld::Class& cls = mPtr.getClass(); + if (cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf()) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::ESMStore& store = world->getStore(); + const ESM::Sound* sound = store.get().searchRandom("WolfSwing", world->getPrng()); + if (sound) + soundId = &sound->mId; + } + + if (!soundId->empty()) + MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch); + } + + void CharacterController::updateHeadTracking(float duration) + { + const osg::Node* head = mAnimation->getNode("Bip01 Head"); + if (!head) + return; + + double zAngleRadians = 0.f; + double xAngleRadians = 0.f; + + if (!mHeadTrackTarget.isEmpty()) + { + osg::NodePathList nodepaths = head->getParentalNodePaths(); + if (nodepaths.empty()) + return; + osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); + osg::Vec3f headPos = mat.getTrans(); + + osg::Vec3f direction; + if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) + { + const osg::Node* node = anim->getNode("Head"); + if (node == nullptr) + node = anim->getNode("Bip01 Head"); + if (node != nullptr) + { + nodepaths = node->getParentalNodePaths(); + if (!nodepaths.empty()) + direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; + } + else + // no head node to look at, fall back to look at center of collision box + direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false); + } + direction.normalize(); + + if (!mPtr.getRefData().getBaseNode()) + return; + const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0, 1, 0); + + zAngleRadians + = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y()); + zAngleRadians = Misc::normalizeAngle(zAngleRadians - mAnimation->getHeadYaw()) + mAnimation->getHeadYaw(); + zAngleRadians *= (1 - direction.z() * direction.z()); + xAngleRadians = std::asin(direction.z()); + } + + const double xLimit = osg::DegreesToRadians(40.0); + const double zLimit = osg::DegreesToRadians(30.0); + double zLimitOffset = mAnimation->getUpperBodyYawRadians(); + xAngleRadians = std::clamp(xAngleRadians, -xLimit, xLimit); + zAngleRadians = std::clamp(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset); + + float factor = duration * 5; + factor = std::min(factor, 1.f); + xAngleRadians = (1.f - factor) * mAnimation->getHeadPitch() + factor * xAngleRadians; + zAngleRadians = (1.f - factor) * mAnimation->getHeadYaw() + factor * zAngleRadians; + + mAnimation->setHeadPitch(xAngleRadians); + mAnimation->setHeadYaw(zAngleRadians); + } } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 105d40a15..fd6f35103 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -3,13 +3,12 @@ #include +#include + #include "../mwworld/ptr.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwrender/animation.hpp" -#include "weapontype.hpp" - namespace MWWorld { class InventoryStore; @@ -23,278 +22,125 @@ namespace MWRender namespace MWMechanics { -struct Movement; -class CreatureStats; + struct Movement; + class CreatureStats; -enum Priority { - Priority_Default, - Priority_WeaponLowerBody, - Priority_SneakIdleLowerBody, - Priority_SwimIdle, - Priority_Jump, - Priority_Movement, - Priority_Hit, - Priority_Weapon, - Priority_Block, - Priority_Knockdown, - Priority_Torch, - Priority_Storm, - Priority_Death, - Priority_Persistent, - - Num_Priorities -}; - -enum CharacterState { - CharState_None, - - CharState_SpecialIdle, - CharState_Idle, - CharState_Idle2, - CharState_Idle3, - CharState_Idle4, - CharState_Idle5, - CharState_Idle6, - CharState_Idle7, - CharState_Idle8, - CharState_Idle9, - CharState_IdleSwim, - CharState_IdleSneak, - - CharState_WalkForward, - CharState_WalkBack, - CharState_WalkLeft, - CharState_WalkRight, - - CharState_SwimWalkForward, - CharState_SwimWalkBack, - CharState_SwimWalkLeft, - CharState_SwimWalkRight, - - CharState_RunForward, - CharState_RunBack, - CharState_RunLeft, - CharState_RunRight, - - CharState_SwimRunForward, - CharState_SwimRunBack, - CharState_SwimRunLeft, - CharState_SwimRunRight, - - CharState_SneakForward, - CharState_SneakBack, - CharState_SneakLeft, - CharState_SneakRight, - - CharState_TurnLeft, - CharState_TurnRight, - CharState_SwimTurnLeft, - CharState_SwimTurnRight, - - CharState_Jump, - - CharState_Death1, - CharState_Death2, - CharState_Death3, - CharState_Death4, - CharState_Death5, - CharState_SwimDeath, - CharState_SwimDeathKnockDown, - CharState_SwimDeathKnockOut, - CharState_DeathKnockDown, - CharState_DeathKnockOut, - - CharState_Hit, - CharState_SwimHit, - CharState_KnockDown, - CharState_KnockOut, - CharState_SwimKnockDown, - CharState_SwimKnockOut, - CharState_Block -}; - -enum UpperBodyCharacterState { - UpperCharState_Nothing, - UpperCharState_EquipingWeap, - UpperCharState_UnEquipingWeap, - UpperCharState_WeapEquiped, - UpperCharState_StartToMinAttack, - UpperCharState_MinAttackToMaxAttack, - UpperCharState_MaxAttackToMinHit, - UpperCharState_MinHitToHit, - UpperCharState_FollowStartToFollowStop, - UpperCharState_CastingSpell -}; - -enum JumpingState { - JumpState_None, - JumpState_InAir, - JumpState_Landing -}; - -struct WeaponInfo; - -class CharacterController : public MWRender::Animation::TextKeyListener -{ - MWWorld::Ptr mPtr; - MWWorld::Ptr mWeapon; - MWRender::Animation *mAnimation; - - struct AnimationQueueEntry + enum Priority { - std::string mGroup; - size_t mLoopCount; - bool mPersist; + Priority_Default, + Priority_WeaponLowerBody, + Priority_SneakIdleLowerBody, + Priority_SwimIdle, + Priority_Jump, + Priority_Movement, + Priority_Hit, + Priority_Weapon, + Priority_Block, + Priority_Knockdown, + Priority_Torch, + Priority_Storm, + Priority_Death, + Priority_Persistent, + + Num_Priorities }; - typedef std::deque AnimationQueue; - AnimationQueue mAnimQueue; - CharacterState mIdleState; - std::string mCurrentIdle; - - CharacterState mMovementState; - std::string mCurrentMovement; - float mMovementAnimSpeed; - bool mAdjustMovementAnimSpeed; - bool mHasMovedInXY; - bool mMovementAnimationControlled; - - CharacterState mDeathState; - std::string mCurrentDeath; - bool mFloatToSurface; - - CharacterState mHitState; - std::string mCurrentHit; - - UpperBodyCharacterState mUpperBodyState; - - JumpingState mJumpState; - std::string mCurrentJump; - - int mWeaponType; - std::string mCurrentWeapon; - - float mAttackStrength; - - bool mSkipAnim; - - // counted for skill increase - float mSecondsOfSwimming; - float mSecondsOfRunning; - - MWWorld::ConstPtr mHeadTrackTarget; - - float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning - - std::string mAttackType; // slash, chop or thrust - - bool mAttackingOrSpell; - bool mCastingManualSpell; - - float mTimeUntilWake; - - bool mIsMovingBackward; - osg::Vec2f mSmoothedSpeed; - - void setAttackTypeBasedOnMovement(); - - void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); - void refreshHitRecoilAnims(CharacterState& idle); - void refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force=false); - void refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force=false); - void refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force=false); - - void clearAnimQueue(bool clearPersistAnims = false); - - bool updateWeaponState(CharacterState& idle); - bool updateCreatureState(); - void updateIdleStormState(bool inwater); - - std::string chooseRandomAttackAnimation() const; - bool isRandomAttackAnimation(const std::string& group) const; - - bool isPersistentAnimPlaying(); - - void updateAnimQueue(); - - void updateHeadTracking(float duration); - - void updateMagicEffects(); - - void playDeath(float startpoint, CharacterState death); - CharacterState chooseRandomDeathState() const; - void playRandomDeath(float startpoint = 0.0f); - - /// choose a random animation group with \a prefix and numeric suffix - /// @param num if non-nullptr, the chosen animation number will be written here - std::string chooseRandomGroup (const std::string& prefix, int* num = nullptr) const; - - bool updateCarriedLeftVisible(int weaptype) const; - - std::string fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask = nullptr); - - std::string getWeaponAnimation(int weaponType) const; - -public: - CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); - virtual ~CharacterController(); - - void handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) override; - - // Be careful when to call this, see comment in Actors - void updateContinuousVfx(); - - void updatePtr(const MWWorld::Ptr &ptr); - - void update(float duration); - - bool onOpen(); - void onClose(); - - void persistAnimationState(); - void unpersistAnimationState(); - - bool playGroup(const std::string &groupname, int mode, int count, bool persist=false); - void skipAnim(); - bool isAnimPlaying(const std::string &groupName); - - enum KillResult + enum CharacterState { - Result_DeathAnimStarted, - Result_DeathAnimPlaying, - Result_DeathAnimJustFinished, - Result_DeathAnimFinished + CharState_None, + + CharState_SpecialIdle, + CharState_Idle, + CharState_IdleSwim, + CharState_IdleSneak, + + CharState_WalkForward, + CharState_WalkBack, + CharState_WalkLeft, + CharState_WalkRight, + + CharState_SwimWalkForward, + CharState_SwimWalkBack, + CharState_SwimWalkLeft, + CharState_SwimWalkRight, + + CharState_RunForward, + CharState_RunBack, + CharState_RunLeft, + CharState_RunRight, + + CharState_SwimRunForward, + CharState_SwimRunBack, + CharState_SwimRunLeft, + CharState_SwimRunRight, + + CharState_SneakForward, + CharState_SneakBack, + CharState_SneakLeft, + CharState_SneakRight, + + CharState_TurnLeft, + CharState_TurnRight, + CharState_SwimTurnLeft, + CharState_SwimTurnRight, + + CharState_Death1, + CharState_Death2, + CharState_Death3, + CharState_Death4, + CharState_Death5, + CharState_SwimDeath, + CharState_SwimDeathKnockDown, + CharState_SwimDeathKnockOut, + CharState_DeathKnockDown, + CharState_DeathKnockOut, + + CharState_Hit, + CharState_SwimHit, + CharState_KnockDown, + CharState_KnockOut, + CharState_SwimKnockDown, + CharState_SwimKnockOut, + CharState_Block }; - KillResult kill(); - void resurrect(); - bool isDead() const - { return mDeathState != CharState_None; } + enum class UpperBodyState + { + None, + Equipping, + Unequipping, + WeaponEquipped, + AttackWindUp, + AttackRelease, + AttackEnd, + Casting + }; - void forceStateUpdate(); - - bool isAttackPreparing() const; - bool isCastingSpell() const; - bool isReadyToBlock() const; - bool isKnockedDown() const; - bool isKnockedOut() const; - bool isRecovery() const; - bool isSneaking() const; - bool isRunning() const; - bool isTurning() const; - bool isAttackingOrSpell() const; + enum JumpingState + { + JumpState_None, + JumpState_InAir, + JumpState_Landing + }; - void setVisibility(float visibility); - void setAttackingOrSpell(bool attackingOrSpell); - void castSpell(const std::string spellId, bool manualSpell=false); - void setAIAttackType(const std::string& attackType); - static void setAttackTypeRandomly(std::string& attackType); + struct WeaponInfo; - bool readyToPrepareAttack() const; - bool readyToStartAttack() const; + class CharacterController : public MWRender::Animation::TextKeyListener + { + MWWorld::Ptr mPtr; + MWWorld::Ptr mWeapon; + MWRender::Animation* mAnimation; - float getAttackStrength() const; + struct AnimationQueueEntry + { + std::string mGroup; + size_t mLoopCount; + bool mPersist; + }; + typedef std::deque AnimationQueue; + AnimationQueue mAnimQueue; +<<<<<<< HEAD /* Start of tes3mp addition @@ -307,12 +153,184 @@ public: /// @see Animation::setActive void setActive(int active); +======= + CharacterState mIdleState{ CharState_None }; + std::string mCurrentIdle; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. - void setHeadTrackTarget(const MWWorld::ConstPtr& target); + CharacterState mMovementState{ CharState_None }; + std::string mCurrentMovement; + float mMovementAnimSpeed{ 0.f }; + bool mAdjustMovementAnimSpeed{ false }; + bool mMovementAnimationControlled{ true }; - void playSwishSound(float attackStrength); -}; + CharacterState mDeathState{ CharState_None }; + std::string mCurrentDeath; + bool mFloatToSurface{ true }; + + CharacterState mHitState{ CharState_None }; + std::string mCurrentHit; + + UpperBodyState mUpperBodyState{ UpperBodyState::None }; + + JumpingState mJumpState{ JumpState_None }; + std::string mCurrentJump; + bool mInJump{ false }; + + int mWeaponType{ ESM::Weapon::None }; + std::string mCurrentWeapon; + + float mAttackStrength{ -1.f }; + MWWorld::Ptr mAttackVictim; + osg::Vec3f mAttackHitPos; + bool mAttackSuccess{ false }; + + bool mSkipAnim{ false }; + + // counted for skill increase + float mSecondsOfSwimming{ 0.f }; + float mSecondsOfRunning{ 0.f }; + + MWWorld::ConstPtr mHeadTrackTarget; + + float mTurnAnimationThreshold{ + 0.f + }; // how long to continue playing turning animation after actor stopped turning + + std::string mAttackType; // slash, chop or thrust + + bool mCanCast{ false }; + + bool mCastingManualSpell{ false }; + + bool mIsMovingBackward{ false }; + osg::Vec2f mSmoothedSpeed; + + std::string_view getMovementBasedAttackType() const; + + void clearStateAnimation(std::string& anim) const; + void resetCurrentJumpState(); + void resetCurrentMovementState(); + void resetCurrentIdleState(); + void resetCurrentHitState(); + void resetCurrentWeaponState(); + void resetCurrentDeathState(); + + void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force = false); + void refreshHitRecoilAnims(); + void refreshJumpAnims(JumpingState jump, bool force = false); + void refreshMovementAnims(CharacterState movement, bool force = false); + void refreshIdleAnims(CharacterState idle, bool force = false); + + void clearAnimQueue(bool clearPersistAnims = false); + + bool updateWeaponState(); + void updateIdleStormState(bool inwater) const; + + std::string chooseRandomAttackAnimation() const; + static bool isRandomAttackAnimation(std::string_view group); + + bool isPersistentAnimPlaying() const; + + void updateAnimQueue(); + + void updateHeadTracking(float duration); + + void updateMagicEffects() const; + + void playDeath(float startpoint, CharacterState death); + CharacterState chooseRandomDeathState() const; + void playRandomDeath(float startpoint = 0.0f); + + /// choose a random animation group with \a prefix and numeric suffix + /// @param num if non-nullptr, the chosen animation number will be written here + std::string chooseRandomGroup(const std::string& prefix, int* num = nullptr) const; + + bool updateCarriedLeftVisible(int weaptype) const; + + std::string fallbackShortWeaponGroup( + const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask = nullptr) const; + + std::string_view getWeaponAnimation(int weaponType) const; + std::string_view getWeaponShortGroup(int weaponType) const; + + bool getAttackingOrSpell() const; + void setAttackingOrSpell(bool attackingOrSpell) const; + + public: + CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim); + virtual ~CharacterController(); + + CharacterController(const CharacterController&) = delete; + CharacterController(CharacterController&&) = delete; + + const MWWorld::Ptr& getPtr() const { return mPtr; } + + void handleTextKey(std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key, + const SceneUtil::TextKeyMap& map) override; + + // Be careful when to call this, see comment in Actors + void updateContinuousVfx() const; + + void updatePtr(const MWWorld::Ptr& ptr); + + void update(float duration); + + bool onOpen() const; + void onClose() const; + + void persistAnimationState() const; + void unpersistAnimationState(); + + bool playGroup(std::string_view groupname, int mode, int count, bool persist = false); + void skipAnim(); + bool isAnimPlaying(std::string_view groupName) const; + + enum KillResult + { + Result_DeathAnimStarted, + Result_DeathAnimPlaying, + Result_DeathAnimJustFinished, + Result_DeathAnimFinished + }; + KillResult kill(); + + void resurrect(); + bool isDead() const { return mDeathState != CharState_None; } + + void forceStateUpdate(); + + bool isAttackPreparing() const; + bool isCastingSpell() const; + bool isReadyToBlock() const; + bool isKnockedDown() const; + bool isKnockedOut() const; + bool isRecovery() const; + bool isSneaking() const; + bool isRunning() const; + bool isTurning() const; + bool isAttackingOrSpell() const; + + void setVisibility(float visibility) const; + void castSpell(const ESM::RefId& spellId, bool manualSpell = false); + void setAIAttackType(std::string_view attackType); + static std::string_view getRandomAttackType(); + + bool readyToPrepareAttack() const; + bool readyToStartAttack() const; + + float calculateWindUp() const; + + float getAttackStrength() const; + + /// @see Animation::setActive + void setActive(int active) const; + + /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. + void setHeadTrackTarget(const MWWorld::ConstPtr& target); + + void playSwishSound() const; + }; } #endif /* GAME_MWMECHANICS_CHARACTER_HPP */ diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index c563da0f7..d3458d9b9 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -1,10 +1,12 @@ + #include "combat.hpp" #include -#include +#include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -19,57 +21,68 @@ /* End of tes3mp addition */ +======= +#include +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/globals.hpp" +#include "../mwworld/inventorystore.hpp" -#include "npcstats.hpp" +#include "actorutil.hpp" +#include "difficultyscaling.hpp" #include "movement.hpp" +#include "npcstats.hpp" +#include "pathfinding.hpp" #include "spellcasting.hpp" #include "spellresistance.hpp" -#include "difficultyscaling.hpp" -#include "actorutil.hpp" -#include "pathfinding.hpp" namespace { -float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg::Vec3f& normal) -{ - return std::atan2((normal * (v1 ^ v2)), (v1 * v2)); -} + float signedAngleRadians(const osg::Vec3f& v1, const osg::Vec3f& v2, const osg::Vec3f& normal) + { + return std::atan2((normal * (v1 ^ v2)), (v1 * v2)); + } } namespace MWMechanics { - bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition, const bool fromProjectile) + bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, + const osg::Vec3f& hitPosition, const bool fromProjectile) { - std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; + const ESM::RefId enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ESM::RefId(); if (!enchantmentName.empty()) { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); + const ESM::Enchantment* enchantment + = MWBase::Environment::get().getESMStore()->get().find(enchantmentName); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { MWMechanics::CastSpell cast(attacker, victim, fromProjectile); cast.mHitPosition = hitPosition; - cast.cast(object, false); + cast.cast(object, 0, false); + // Apply magic effects directly instead of waiting a frame to allow soul trap to work on one-hit kills + if (!victim.isEmpty() && victim.getClass().isActor()) + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim); return true; } } return false; } - bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength) + bool blockMeleeAttack(const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, + float damage, float attackStrength) { if (!blocker.getClass().hasInventoryStore(blocker)) return false; @@ -77,8 +90,7 @@ namespace MWMechanics MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker); if (blockerStats.getKnockedDown() // Used for both knockout or knockdown - || blockerStats.getHitRecovery() - || blockerStats.isParalyzed()) + || blockerStats.getHitRecovery() || blockerStats.isParalyzed()) return false; if (!MWBase::Environment::get().getMechanicsManager()->isReadyToBlock(blocker)) @@ -86,34 +98,41 @@ namespace MWMechanics MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker); MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) + if (shield == inv.end() || shield->getType() != ESM::Armor::sRecordId) return false; if (!blocker.getRefData().getBaseNode()) return false; // shouldn't happen - float angleDegrees = osg::RadiansToDegrees( - signedAngleRadians ( - (attacker.getRefData().getPosition().asVec3() - blocker.getRefData().getPosition().asVec3()), - blocker.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0), - osg::Vec3f(0,0,1))); + float angleDegrees = osg::RadiansToDegrees(signedAngleRadians( + (attacker.getRefData().getPosition().asVec3() - blocker.getRefData().getPosition().asVec3()), + blocker.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0, 1, 0), osg::Vec3f(0, 0, 1))); - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - if (angleDegrees < gmst.find("fCombatBlockLeftAngle")->mValue.getFloat()) + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + static const float fCombatBlockLeftAngle = gmst.find("fCombatBlockLeftAngle")->mValue.getFloat(); + if (angleDegrees < fCombatBlockLeftAngle) return false; - if (angleDegrees > gmst.find("fCombatBlockRightAngle")->mValue.getFloat()) + static const float fCombatBlockRightAngle = gmst.find("fCombatBlockRightAngle")->mValue.getFloat(); + if (angleDegrees > fCombatBlockRightAngle) return false; MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); - float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2f * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() + float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + + 0.2f * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() + 0.1f * blockerStats.getAttribute(ESM::Attribute::Luck).getModified(); float enemySwing = attackStrength; - float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->mValue.getFloat() + gmst.find("fSwingBlockBase")->mValue.getFloat(); + static const float fSwingBlockMult = gmst.find("fSwingBlockMult")->mValue.getFloat(); + static const float fSwingBlockBase = gmst.find("fSwingBlockBase")->mValue.getFloat(); + float swingTerm = enemySwing * fSwingBlockMult + fSwingBlockBase; float blockerTerm = blockTerm * swingTerm; if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0) - blockerTerm *= gmst.find("fBlockStillBonus")->mValue.getFloat(); + { + static const float fBlockStillBonus = gmst.find("fBlockStillBonus")->mValue.getFloat(); + blockerTerm *= fBlockStillBonus; + } blockerTerm *= blockerStats.getFatigueTerm(); float attackerSkill = 0; @@ -122,14 +141,14 @@ namespace MWMechanics else attackerSkill = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); float attackerTerm = attackerSkill + 0.2f * attackerStats.getAttribute(ESM::Attribute::Agility).getModified() - + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); + + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); attackerTerm *= attackerStats.getFatigueTerm(); - int x = int(blockerTerm - attackerTerm); - int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); - int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); - x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); + static const int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); + static const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); + int x = std::clamp(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance); +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -139,6 +158,10 @@ namespace MWMechanics mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(attacker); if (localAttack) +======= + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) < x) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { localAttack->block = false; } @@ -161,11 +184,11 @@ namespace MWMechanics shieldhealth -= std::min(shieldhealth, int(damage)); shield->getCellRef().setCharge(shieldhealth); if (shieldhealth == 0) - inv.unequipItem(*shield, blocker); + inv.unequipItem(*shield); // Reduce blocker fatigue - const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat(); - const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat(); - const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->mValue.getFloat(); + static const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat(); + static const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat(); + static const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->mValue.getFloat(); MWMechanics::DynamicStat fatigue = blockerStats.getFatigue(); float normalizedEncumbrance = blocker.getClass().getNormalizedEncumbrance(blocker); normalizedEncumbrance = std::min(1.f, normalizedEncumbrance); @@ -185,7 +208,7 @@ namespace MWMechanics return false; } - bool isNormalWeapon(const MWWorld::Ptr &weapon) + bool isNormalWeapon(const MWWorld::Ptr& weapon) { if (weapon.isEmpty()) return false; @@ -195,25 +218,26 @@ namespace MWMechanics bool isMagical = flags & ESM::Weapon::Magical; bool isEnchanted = !weapon.getClass().getEnchantment(weapon).empty(); - return !isSilver && !isMagical && (!isEnchanted || !Settings::Manager::getBool("enchanted weapons are magical", "Game")); + return !isSilver && !isMagical && (!isEnchanted || !Settings::game().mEnchantedWeaponsAreMagical); } - void resistNormalWeapon(const MWWorld::Ptr &actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr &weapon, float &damage) + void resistNormalWeapon( + const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage) { if (damage == 0 || weapon.isEmpty() || !isNormalWeapon(weapon)) return; const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); - const float resistance = effects.get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() / 100.f; - const float weakness = effects.get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude() / 100.f; + const float resistance = effects.getOrDefault(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() / 100.f; + const float weakness = effects.getOrDefault(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude() / 100.f; - damage *= 1.f - std::min(1.f, resistance-weakness); + damage *= 1.f - std::min(1.f, resistance - weakness); if (damage == 0 && attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); } - void applyWerewolfDamageMult(const MWWorld::Ptr &actor, const MWWorld::Ptr &weapon, float &damage) + void applyWerewolfDamageMult(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon, float& damage) { if (damage == 0 || weapon.isEmpty() || !actor.getClass().isNpc()) return; @@ -223,14 +247,15 @@ namespace MWMechanics if (isSilver && actor.getClass().getNpcStats(actor).isWerewolf()) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); damage *= store.get().find("fWereWolfSilverWeaponDamageMult")->mValue.getFloat(); } } - void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, - const osg::Vec3f& hitPosition, float attackStrength) + void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, + const MWWorld::Ptr& projectile, const osg::Vec3f& hitPosition, float attackStrength) { +<<<<<<< HEAD /* Start of tes3mp addition @@ -255,19 +280,26 @@ namespace MWMechanics MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &gmst = world->getStore().get(); +======= + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& gmst = world->getStore().get(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 bool validVictim = !victim.isEmpty() && victim.getClass().isActor(); + ESM::RefId weaponSkill = ESM::Skill::Marksman; + if (!weapon.isEmpty()) + weaponSkill = weapon.getClass().getEquipmentSkill(weapon); + float damage = 0.f; if (validVictim) { if (attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); - int weaponSkill = ESM::Skill::Marksman; - if (!weapon.isEmpty()) - weaponSkill = weapon.getClass().getEquipmentSkill(weapon); + int skillValue = attacker.getClass().getSkill(attacker, weaponSkill); +<<<<<<< HEAD int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); /* @@ -282,6 +314,9 @@ namespace MWMechanics */ if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) +======= + if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue)) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { /* Start of tes3mp addition @@ -308,7 +343,14 @@ namespace MWMechanics damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); adjustWeaponDamage(damage, weapon, attacker); - if (weapon == projectile || Settings::Manager::getBool("only appropriate ammunition bypasses resistance", "Game") || isNormalWeapon(weapon)) + } + + reduceWeaponCondition(damage, validVictim, weapon, attacker); + + if (validVictim) + { + if (weapon == projectile || Settings::game().mOnlyAppropriateAmmunitionBypassesResistance + || isNormalWeapon(weapon)) resistNormalWeapon(victim, attacker, projectile, damage); applyWerewolfDamageMult(victim, projectile, damage); @@ -321,14 +363,14 @@ namespace MWMechanics bool knockedDown = victim.getClass().getCreatureStats(victim).getKnockedDown(); if (knockedDown || unaware) { - damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat(); + static const float fCombatKODamageMult = gmst.find("fCombatKODamageMult")->mValue.getFloat(); + damage *= fCombatKODamageMult; if (!knockedDown) - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + MWBase::Environment::get().getSoundManager()->playSound3D( + victim, ESM::RefId::stringRefId("critical damage"), 1.0f, 1.0f); } } - reduceWeaponCondition(damage, validVictim, weapon, attacker); - // Apply "On hit" effect of the projectile bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true); @@ -349,9 +391,10 @@ namespace MWMechanics // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory if (victim != getPlayer() && !appliedEnchantment) { - float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); - if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) - victim.getClass().getContainerStore(victim).add(projectile, 1, victim); + static const float fProjectileThrownStoreChance + = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); + if (Misc::Rng::rollProbability(world->getPrng()) < fProjectileThrownStoreChance / 100.f) + victim.getClass().getContainerStore(victim).add(projectile, 1); } victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true); @@ -371,71 +414,73 @@ namespace MWMechanics */ } - float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue) + float getHitChance(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue) { - MWMechanics::CreatureStats &stats = attacker.getClass().getCreatureStats(attacker); - const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); + MWMechanics::CreatureStats& stats = attacker.getClass().getCreatureStats(attacker); + const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& gmst = world->getStore().get(); float defenseTerm = 0; MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim); if (victimStats.getFatigue().getCurrent() >= 0) { // Maybe we should keep an aware state for actors updated every so often instead of testing every time - bool unaware = (!victimStats.getAiSequence().isInCombat()) - && (attacker == getPlayer()) - && (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim)); - if (!(victimStats.getKnockedDown() || - victimStats.isParalyzed() - || unaware )) + bool unaware = (!victimStats.getAiSequence().isInCombat()) && (attacker == getPlayer()) + && (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim)); + if (!(victimStats.getKnockedDown() || victimStats.isParalyzed() || unaware)) { defenseTerm = victimStats.getEvasion(); } + static const float fCombatInvisoMult = gmst.find("fCombatInvisoMult")->mValue.getFloat(); defenseTerm += std::min(100.f, - gmst.find("fCombatInvisoMult")->mValue.getFloat() * - victimStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude()); + fCombatInvisoMult + * victimStats.getMagicEffects().getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude()); defenseTerm += std::min(100.f, - gmst.find("fCombatInvisoMult")->mValue.getFloat() * - victimStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude()); + fCombatInvisoMult + * victimStats.getMagicEffects().getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude()); } - float attackTerm = skillValue + - (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + - (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + float attackTerm = skillValue + (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); attackTerm *= stats.getFatigueTerm(); - attackTerm += mageffects.get(ESM::MagicEffect::FortifyAttack).getMagnitude() - - mageffects.get(ESM::MagicEffect::Blind).getMagnitude(); + attackTerm += mageffects.getOrDefault(ESM::MagicEffect::FortifyAttack).getMagnitude() + - mageffects.getOrDefault(ESM::MagicEffect::Blind).getMagnitude(); return round(attackTerm - defenseTerm); } - void applyElementalShields(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim) + void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) { // Don't let elemental shields harm the player in god mode. bool godmode = attacker == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if (godmode) return; - for (int i=0; i<3; ++i) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + for (int i = 0; i < 3; ++i) { - float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude(); + float magnitude = victim.getClass() + .getCreatureStats(victim) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::FireShield + i) + .getMagnitude(); if (!magnitude) continue; CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); float saveTerm = attacker.getClass().getSkill(attacker, ESM::Skill::Destruction) - + 0.2f * attackerStats.getAttribute(ESM::Attribute::Willpower).getModified() - + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); + + 0.2f * attackerStats.getAttribute(ESM::Attribute::Willpower).getModified() + + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); float fatigueMax = attackerStats.getFatigue().getModified(); float fatigueCurrent = attackerStats.getFatigue().getCurrent(); - float normalisedFatigue = floor(fatigueMax)==0 ? 1 : std::max (0.0f, (fatigueCurrent/fatigueMax)); + float normalisedFatigue = floor(fatigueMax) == 0 ? 1 : std::max(0.0f, (fatigueCurrent / fatigueMax)); saveTerm *= 1.25f * normalisedFatigue; - float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99()); + float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99(prng)); int element = ESM::MagicEffect::FireDamage; if (i == 1) @@ -443,11 +488,16 @@ namespace MWMechanics if (i == 2) element = ESM::MagicEffect::FrostDamage; - float elementResistance = MWMechanics::getEffectResistanceAttribute(element, &attackerStats.getMagicEffects()); + float elementResistance + = MWMechanics::getEffectResistanceAttribute(element, &attackerStats.getMagicEffects()); x = std::min(100.f, x + elementResistance); - static const float fElementalShieldMult = MWBase::Environment::get().getWorld()->getStore().get().find("fElementalShieldMult")->mValue.getFloat(); + static const float fElementalShieldMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fElementalShieldMult") + ->mValue.getFloat(); x = fElementalShieldMult * magnitude * (1.f - 0.01f * x); // Note swapped victim and attacker, since the attacker takes the damage here. @@ -457,11 +507,12 @@ namespace MWMechanics health.setCurrent(health.getCurrent() - x); attackerStats.setHealth(health); - MWBase::Environment::get().getSoundManager()->playSound3D(attacker, "Health Damage", 1.0f, 1.0f); + MWBase::Environment::get().getSoundManager()->playSound3D( + attacker, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f); } } - void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr &weapon, const MWWorld::Ptr &attacker) + void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker) { if (weapon.isEmpty()) return; @@ -470,16 +521,21 @@ namespace MWMechanics damage = 0.f; const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); - if(weaphashealth) + if (weaphashealth) { int weaphealth = weapon.getClass().getItemHealth(weapon); - bool godmode = attacker == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + bool godmode + = attacker == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); // weapon condition does not degrade when godmode is on if (!godmode) { - const float fWeaponDamageMult = MWBase::Environment::get().getWorld()->getStore().get().find("fWeaponDamageMult")->mValue.getFloat(); + const float fWeaponDamageMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fWeaponDamageMult") + ->mValue.getFloat(); float x = std::max(1.f, fWeaponDamageMult * damage); weaphealth -= std::min(int(x), weaphealth); @@ -488,11 +544,11 @@ namespace MWMechanics // Weapon broken? unequip it if (weaphealth == 0) - weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon, attacker); + weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon); } } - void adjustWeaponDamage(float &damage, const MWWorld::Ptr &weapon, const MWWorld::Ptr& attacker) + void adjustWeaponDamage(float& damage, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker) { if (weapon.isEmpty()) return; @@ -503,63 +559,79 @@ namespace MWMechanics damage *= weapon.getClass().getItemNormalizedHealth(weapon); } - static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get() - .find("fDamageStrengthBase")->mValue.getFloat(); - static const float fDamageStrengthMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fDamageStrengthMult")->mValue.getFloat(); - damage *= fDamageStrengthBase + - (attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1f); + static const float fDamageStrengthBase = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fDamageStrengthBase") + ->mValue.getFloat(); + static const float fDamageStrengthMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fDamageStrengthMult") + ->mValue.getFloat(); + damage *= fDamageStrengthBase + + (attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() + * fDamageStrengthMult * 0.1f); } - void getHandToHandDamage(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, float &damage, bool &healthdmg, float attackStrength) + void getHandToHandDamage( + const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, float& damage, bool& healthdmg, float attackStrength) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - float minstrike = store.get().find("fMinHandToHandMult")->mValue.getFloat(); - float maxstrike = store.get().find("fMaxHandToHandMult")->mValue.getFloat(); - damage = static_cast(attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand)); - damage *= minstrike + ((maxstrike-minstrike)*attackStrength); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + static const float minstrike = store.get().find("fMinHandToHandMult")->mValue.getFloat(); + static const float maxstrike = store.get().find("fMaxHandToHandMult")->mValue.getFloat(); + damage = static_cast(attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand)); + damage *= minstrike + ((maxstrike - minstrike) * attackStrength); MWMechanics::CreatureStats& otherstats = victim.getClass().getCreatureStats(victim); - healthdmg = otherstats.isParalyzed() - || otherstats.getKnockedDown(); + healthdmg = otherstats.isParalyzed() || otherstats.getKnockedDown(); bool isWerewolf = (attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()); // Options in the launcher's combo box: unarmedFactorsStrengthComboBox // 0 = Do not factor strength into hand-to-hand combat. // 1 = Factor into werewolf hand-to-hand combat. // 2 = Ignore werewolves. - int factorStrength = Settings::Manager::getInt("strength influences hand to hand", "Game"); - if (factorStrength == 1 || (factorStrength == 2 && !isWerewolf)) { - damage *= attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() / 40.0f; + const int factorStrength = Settings::game().mStrengthInfluencesHandToHand; + if (factorStrength == 1 || (factorStrength == 2 && !isWerewolf)) + { + damage + *= attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() + / 40.0f; } - if(isWerewolf) + if (isWerewolf) { healthdmg = true; // GLOB instead of GMST because it gets updated during a quest - damage *= MWBase::Environment::get().getWorld()->getGlobalFloat("werewolfclawmult"); + damage *= MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sWerewolfClawMult); } - if(healthdmg) - damage *= store.get().find("fHandtoHandHealthPer")->mValue.getFloat(); - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(isWerewolf) + if (healthdmg) { - const ESM::Sound *sound = store.get().searchRandom("WolfHit"); - if(sound) + static const float fHandtoHandHealthPer + = store.get().find("fHandtoHandHealthPer")->mValue.getFloat(); + damage *= fHandtoHandHealthPer; + } + + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + if (isWerewolf) + { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound* sound = store.get().searchRandom("WolfHit", prng); + if (sound) sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f); } else if (!healthdmg) - sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f); + sndMgr->playSound3D(victim, ESM::RefId::stringRefId("Hand To Hand Hit"), 1.0f, 1.0f); } - void applyFatigueLoss(const MWWorld::Ptr &attacker, const MWWorld::Ptr &weapon, float attackStrength) + void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float attackStrength) { // somewhat of a guess, but using the weapon weight makes sense - const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); - const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat(); - const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat(); - const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->mValue.getFloat(); + const MWWorld::Store& store + = MWBase::Environment::get().getESMStore()->get(); + static const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat(); + static const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat(); + static const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->mValue.getFloat(); CreatureStats& stats = attacker.getClass().getCreatureStats(attacker); MWMechanics::DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = attacker.getClass().getNormalizedEncumbrance(attacker); @@ -578,26 +650,25 @@ namespace MWMechanics float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2) { - osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); - osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3()); + osg::Vec3f pos1(actor1.getRefData().getPosition().asVec3()); + osg::Vec3f pos2(actor2.getRefData().getPosition().asVec3()); float d = getAggroDistance(actor1, pos1, pos2); - static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( - "iFightDistanceBase")->mValue.getInteger(); - static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( - "fFightDistanceMultiplier")->mValue.getFloat(); + static const int iFightDistanceBase = MWBase::Environment::get() + .getESMStore() + ->get() + .find("iFightDistanceBase") + ->mValue.getInteger(); + static const float fFightDistanceMultiplier = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fFightDistanceMultiplier") + ->mValue.getFloat(); return (iFightDistanceBase - fFightDistanceMultiplier * d); } - bool isTargetMagicallyHidden(const MWWorld::Ptr& target) - { - const MagicEffects& magicEffects = target.getClass().getCreatureStats(target).getMagicEffects(); - return (magicEffects.get(ESM::MagicEffect::Invisibility).getMagnitude() > 0) - || (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75); - } - float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs) { if (canActorMoveByZAxis(actor)) diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 3d4a1bd77..2e7caf618 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -14,49 +14,50 @@ namespace MWWorld namespace MWMechanics { -bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition, - const bool fromProjectile=false); + bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, + const osg::Vec3f& hitPosition, const bool fromProjectile = false); -/// @return can we block the attack? -bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); + /// @return can we block the attack? + bool blockMeleeAttack(const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, + float damage, float attackStrength); -/// @return does normal weapon resistance and weakness apply to the weapon? -bool isNormalWeapon (const MWWorld::Ptr& weapon); + /// @return does normal weapon resistance and weakness apply to the weapon? + bool isNormalWeapon(const MWWorld::Ptr& weapon); -void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); + void resistNormalWeapon( + const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); -void applyWerewolfDamageMult (const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon, float &damage); + void applyWerewolfDamageMult(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon, float& damage); -/// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt -/// @note \a victim may be empty (e.g. for a hit on terrain), a non-actor (environment objects) or an actor -void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, - const osg::Vec3f& hitPosition, float attackStrength); + /// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt + /// @note \a victim may be empty (e.g. for a hit on terrain), a non-actor (environment objects) or an actor + void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, + const MWWorld::Ptr& projectile, const osg::Vec3f& hitPosition, float attackStrength); -/// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value -float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue); + /// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value + float getHitChance(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue); -/// Applies damage to attacker based on the victim's elemental shields. -void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); + /// Applies damage to attacker based on the victim's elemental shields. + void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); -/// @param damage Unmitigated weapon damage of the attack -/// @param hit Was the attack successful? -/// @param weapon The weapon used. -/// @note if the weapon is unequipped as result of condition damage, a new Ptr will be assigned to \a weapon. -void reduceWeaponCondition (float damage, bool hit, MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); + /// @param damage Unmitigated weapon damage of the attack + /// @param hit Was the attack successful? + /// @param weapon The weapon used. + /// @note if the weapon is unequipped as result of condition damage, a new Ptr will be assigned to \a weapon. + void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); -/// Adjust weapon damage based on its condition. A used weapon will be less effective. -void adjustWeaponDamage (float& damage, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); + /// Adjust weapon damage based on its condition. A used weapon will be less effective. + void adjustWeaponDamage(float& damage, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); -void getHandToHandDamage (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, float& damage, bool& healthdmg, float attackStrength); + void getHandToHandDamage( + const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, float& damage, bool& healthdmg, float attackStrength); -/// Apply the fatigue loss incurred by attacking with the given weapon (weapon may be empty = hand-to-hand) -void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float attackStrength); + /// Apply the fatigue loss incurred by attacking with the given weapon (weapon may be empty = hand-to-hand) + void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float attackStrength); -float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2); + float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2); -bool isTargetMagicallyHidden(const MWWorld::Ptr& target); - -float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs); + float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs); } diff --git a/apps/openmw/mwmechanics/creaturecustomdataresetter.hpp b/apps/openmw/mwmechanics/creaturecustomdataresetter.hpp new file mode 100644 index 000000000..667a0b9c4 --- /dev/null +++ b/apps/openmw/mwmechanics/creaturecustomdataresetter.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_MWMECHANICS_CREATURECUSTOMDATARESETTER_H +#define OPENMW_MWMECHANICS_CREATURECUSTOMDATARESETTER_H + +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + struct CreatureCustomDataResetter + { + MWWorld::Ptr mPtr; + + ~CreatureCustomDataResetter() + { + if (!mPtr.isEmpty()) + mPtr.getRefData().setCustomData({}); + } + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index d06689c54..ccf4e4509 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -1,7 +1,9 @@ #include "creaturestats.hpp" #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -16,7 +18,14 @@ #include #include #include +======= +#include +#include +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" @@ -28,15 +37,36 @@ namespace MWMechanics int CreatureStats::sActorId = 0; CreatureStats::CreatureStats() - : mDrawState (DrawState_Nothing), mDead (false), mDeathAnimationFinished(false), mDied (false), mMurdered(false), mFriendlyHits (0), - mTalkedTo (false), mAlarmed (false), mAttacked (false), - mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), - mHitRecovery(false), mBlock(false), mMovementFlags(0), - mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), - mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) + : mDrawState(DrawState::Nothing) + , mDead(false) + , mDeathAnimationFinished(false) + , mDied(false) + , mMurdered(false) + , mFriendlyHits(0) + , mTalkedTo(false) + , mAlarmed(false) + , mAttacked(false) + , mKnockdown(false) + , mKnockdownOneFrame(false) + , mKnockdownOverOneFrame(false) + , mHitRecovery(false) + , mBlock(false) + , mMovementFlags(0) + , mFallHeight(0) + , mLastRestock(0, 0) + , mGoldPool(0) + , mActorId(-1) + , mHitAttemptActorId(-1) + , mDeathAnimation(-1) + , mTimeOfDeath() + , mSideMovementAngle(0) + , mLevel(0) + , mAttackingOrSpell(false) { - for (int i=0; i<4; ++i) - mAiSettings[i] = 0; + for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) + { + mAttributes.emplace(attribute.mId, AttributeValue{}); + } } const AiSequence& CreatureStats::getAiSequence() const @@ -54,51 +84,48 @@ namespace MWMechanics float max = getFatigue().getModified(); float current = getFatigue().getCurrent(); - float normalised = floor(max) == 0 ? 1 : std::max (0.0f, current / max); + float normalised = std::floor(max) == 0 ? 1 : std::max(0.0f, current / max); - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fFatigueBase = gmst.find("fFatigueBase")->mValue.getFloat(); static const float fFatigueMult = gmst.find("fFatigueMult")->mValue.getFloat(); - return fFatigueBase - fFatigueMult * (1-normalised); + return fFatigueBase - fFatigueMult * (1 - normalised); } - const AttributeValue &CreatureStats::getAttribute(int index) const + const AttributeValue& CreatureStats::getAttribute(ESM::Attribute::AttributeID id) const { - if (index < 0 || index > 7) { - throw std::runtime_error("attribute index is out of range"); - } - return mAttributes[index]; + return mAttributes.at(id); } - const DynamicStat &CreatureStats::getHealth() const + const DynamicStat& CreatureStats::getHealth() const { return mDynamic[0]; } - const DynamicStat &CreatureStats::getMagicka() const + const DynamicStat& CreatureStats::getMagicka() const { return mDynamic[1]; } - const DynamicStat &CreatureStats::getFatigue() const + const DynamicStat& CreatureStats::getFatigue() const { return mDynamic[2]; } - const Spells &CreatureStats::getSpells() const + const Spells& CreatureStats::getSpells() const { return mSpells; } - const ActiveSpells &CreatureStats::getActiveSpells() const + const ActiveSpells& CreatureStats::getActiveSpells() const { return mActiveSpells; } - const MagicEffects &CreatureStats::getMagicEffects() const + const MagicEffects& CreatureStats::getMagicEffects() const { return mMagicEffects; } @@ -108,25 +135,26 @@ namespace MWMechanics return mLevel; } - Stat CreatureStats::getAiSetting (AiSetting index) const + Stat CreatureStats::getAiSetting(AiSetting index) const { - return mAiSettings[index]; + return mAiSettings[static_cast>(index)]; } - const DynamicStat &CreatureStats::getDynamic(int index) const + const DynamicStat& CreatureStats::getDynamic(int index) const { - if (index < 0 || index > 2) { + if (index < 0 || index > 2) + { throw std::runtime_error("dynamic stat index is out of range"); } return mDynamic[index]; } - Spells &CreatureStats::getSpells() + Spells& CreatureStats::getSpells() { return mSpells; } - ActiveSpells &CreatureStats::getActiveSpells() + ActiveSpells& CreatureStats::getActiveSpells() { /* Start of tes3mp addition @@ -140,82 +168,73 @@ namespace MWMechanics return mActiveSpells; } - MagicEffects &CreatureStats::getMagicEffects() + MagicEffects& CreatureStats::getMagicEffects() { return mMagicEffects; } - void CreatureStats::setAttribute(int index, float base) + void CreatureStats::setAttribute(ESM::Attribute::AttributeID id, float base) { - AttributeValue current = getAttribute(index); + AttributeValue current = getAttribute(id); current.setBase(base); - setAttribute(index, current); + setAttribute(id, current); } - void CreatureStats::setAttribute(int index, const AttributeValue &value) + void CreatureStats::setAttribute(ESM::Attribute::AttributeID id, const AttributeValue& value) { - if (index < 0 || index > 7) { - throw std::runtime_error("attribute index is out of range"); - } - - const AttributeValue& currentValue = mAttributes[index]; + const AttributeValue& currentValue = mAttributes.at(id); if (value != currentValue) { - mAttributes[index] = value; + mAttributes[id] = value; - if (index == ESM::Attribute::Intelligence) - mRecalcMagicka = true; - else if (index == ESM::Attribute::Strength || - index == ESM::Attribute::Willpower || - index == ESM::Attribute::Agility || - index == ESM::Attribute::Endurance) + if (id == ESM::Attribute::Intelligence) + recalculateMagicka(); + else if (id == ESM::Attribute::Strength || id == ESM::Attribute::Willpower || id == ESM::Attribute::Agility + || id == ESM::Attribute::Endurance) { - float strength = getAttribute(ESM::Attribute::Strength).getModified(); - float willpower = getAttribute(ESM::Attribute::Willpower).getModified(); - float agility = getAttribute(ESM::Attribute::Agility).getModified(); - float endurance = getAttribute(ESM::Attribute::Endurance).getModified(); + float strength = getAttribute(ESM::Attribute::Strength).getModified(); + float willpower = getAttribute(ESM::Attribute::Willpower).getModified(); + float agility = getAttribute(ESM::Attribute::Agility).getModified(); + float endurance = getAttribute(ESM::Attribute::Endurance).getModified(); DynamicStat fatigue = getFatigue(); - float diff = (strength+willpower+agility+endurance) - fatigue.getBase(); float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0; - fatigue.setModified(fatigue.getModified() + diff, 0); - fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio); + fatigue.setBase(std::max(0.f, strength + willpower + agility + endurance)); + fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio, false, true); setFatigue(fatigue); } } } - void CreatureStats::setHealth(const DynamicStat &value) + void CreatureStats::setHealth(const DynamicStat& value) { - setDynamic (0, value); + setDynamic(0, value); } - void CreatureStats::setMagicka(const DynamicStat &value) + void CreatureStats::setMagicka(const DynamicStat& value) { - setDynamic (1, value); + setDynamic(1, value); } - void CreatureStats::setFatigue(const DynamicStat &value) + void CreatureStats::setFatigue(const DynamicStat& value) { - setDynamic (2, value); + setDynamic(2, value); } - void CreatureStats::setDynamic (int index, const DynamicStat &value) + void CreatureStats::setDynamic(int index, const DynamicStat& value) { if (index < 0 || index > 2) throw std::runtime_error("dynamic stat index is out of range"); mDynamic[index] = value; - if (index==0 && mDynamic[index].getCurrent()<1) + if (index == 0 && mDynamic[index].getCurrent() < 1) { if (!mDead) mTimeOfDeath = MWBase::Environment::get().getWorld()->getTimeStamp(); mDead = true; - mDynamic[index].setModifier(0); - mDynamic[index].setCurrentModifier(0); mDynamic[index].setCurrent(0); } } @@ -225,21 +244,21 @@ namespace MWMechanics mLevel = level; } - void CreatureStats::modifyMagicEffects(const MagicEffects &effects) + void CreatureStats::modifyMagicEffects(const MagicEffects& effects) { - if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() - != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier()) - mRecalcMagicka = true; - + bool recalc = effects.getOrDefault(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() + != mMagicEffects.getOrDefault(ESM::MagicEffect::FortifyMaximumMagicka).getModifier(); mMagicEffects.setModifiers(effects); + if (recalc) + recalculateMagicka(); } - void CreatureStats::setAiSetting (AiSetting index, Stat value) + void CreatureStats::setAiSetting(AiSetting index, Stat value) { - mAiSettings[index] = value; + mAiSettings[static_cast>(index)] = value; } - void CreatureStats::setAiSetting (AiSetting index, int base) + void CreatureStats::setAiSetting(AiSetting index, int base) { Stat stat = getAiSetting(index); stat.setBase(base); @@ -248,7 +267,7 @@ namespace MWMechanics bool CreatureStats::isParalyzed() const { - return mMagicEffects.get(ESM::MagicEffect::Paralyze).getMagnitude() > 0; + return mMagicEffects.getOrDefault(ESM::MagicEffect::Paralyze).getMagnitude() > 0; } bool CreatureStats::isDead() const @@ -300,10 +319,7 @@ namespace MWMechanics { if (mDead) { - if (mDynamic[0].getModified() < 1) - mDynamic[0].setModified(1, 0); - - mDynamic[0].setCurrent(mDynamic[0].getModified()); + mDynamic[0].setCurrent(mDynamic[0].getBase()); mDead = false; mDeathAnimationFinished = false; } @@ -357,7 +373,7 @@ namespace MWMechanics return mAlarmed; } - void CreatureStats::setAlarmed (bool alarmed) + void CreatureStats::setAlarmed(bool alarmed) { mAlarmed = alarmed; } @@ -367,37 +383,47 @@ namespace MWMechanics return mAttacked; } - void CreatureStats::setAttacked (bool attacked) + void CreatureStats::setAttacked(bool attacked) { mAttacked = attacked; } float CreatureStats::getEvasion() const { - float evasion = (getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + - (getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + float evasion = (getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); evasion *= getFatigueTerm(); - evasion += std::min(100.f, mMagicEffects.get(ESM::MagicEffect::Sanctuary).getMagnitude()); + evasion += std::min(100.f, mMagicEffects.getOrDefault(ESM::MagicEffect::Sanctuary).getMagnitude()); return evasion; } - void CreatureStats::setLastHitObject(const std::string& objectid) + void CreatureStats::setLastHitObject(const ESM::RefId& objectid) { mLastHitObject = objectid; } - const std::string &CreatureStats::getLastHitObject() const + void CreatureStats::clearLastHitObject() + { + mLastHitObject = ESM::RefId(); + } + + const ESM::RefId& CreatureStats::getLastHitObject() const { return mLastHitObject; } - void CreatureStats::setLastHitAttemptObject(const std::string& objectid) + void CreatureStats::setLastHitAttemptObject(const ESM::RefId& objectid) { mLastHitAttemptObject = objectid; } - const std::string &CreatureStats::getLastHitAttemptObject() const + void CreatureStats::clearLastHitAttemptObject() + { + mLastHitAttemptObject = ESM::RefId(); + } + + const ESM::RefId& CreatureStats::getLastHitAttemptObject() const { return mLastHitAttemptObject; } @@ -432,25 +458,32 @@ namespace MWMechanics return height; } - bool CreatureStats::needToRecalcDynamicStats() + void CreatureStats::recalculateMagicka() { - if (mRecalcMagicka) - { - mRecalcMagicka = false; - return true; - } - return false; - } + auto world = MWBase::Environment::get().getWorld(); + float intelligence = getAttribute(ESM::Attribute::Intelligence).getModified(); - void CreatureStats::setNeedRecalcDynamicStats(bool val) - { - mRecalcMagicka = val; + float base = 1.f; + const auto& player = world->getPlayerPtr(); + if (this == &player.getClass().getCreatureStats(player)) + base = world->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); + else + base = world->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); + + double magickaFactor = base + + mMagicEffects.getOrDefault(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; + + DynamicStat magicka = getMagicka(); + float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0; + magicka.setBase(magickaFactor * intelligence); + magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); + setMagicka(magicka); } void CreatureStats::setKnockedDown(bool value) { mKnockdown = value; - if(!value) //Resets the "OverOneFrame" flag + if (!value) // Resets the "OverOneFrame" flag setKnockedDownOverOneFrame(false); } @@ -469,10 +502,12 @@ namespace MWMechanics return mKnockdownOneFrame; } - void CreatureStats::setKnockedDownOverOneFrame(bool value) { + void CreatureStats::setKnockedDownOverOneFrame(bool value) + { mKnockdownOverOneFrame = value; } - bool CreatureStats::getKnockedDownOverOneFrame() const { + bool CreatureStats::getKnockedDownOverOneFrame() const + { return mKnockdownOverOneFrame; } @@ -496,12 +531,12 @@ namespace MWMechanics return mBlock; } - bool CreatureStats::getMovementFlag (Flag flag) const + bool CreatureStats::getMovementFlag(Flag flag) const { return (mMovementFlags & flag) != 0; } - void CreatureStats::setMovementFlag (Flag flag, bool state) + void CreatureStats::setMovementFlag(Flag flag, bool state) { if (state) mMovementFlags |= flag; @@ -514,31 +549,31 @@ namespace MWMechanics switch (flag) { case Stance_Run: - return getMovementFlag (Flag_Run) || getMovementFlag (Flag_ForceRun); + return getMovementFlag(Flag_Run) || getMovementFlag(Flag_ForceRun); case Stance_Sneak: - return getMovementFlag (Flag_Sneak) || getMovementFlag (Flag_ForceSneak); + return getMovementFlag(Flag_Sneak) || getMovementFlag(Flag_ForceSneak); default: return false; } } - DrawState_ CreatureStats::getDrawState() const + DrawState CreatureStats::getDrawState() const { return mDrawState; } - void CreatureStats::setDrawState(DrawState_ state) + void CreatureStats::setDrawState(DrawState state) { mDrawState = state; } - void CreatureStats::writeState (ESM::CreatureStats& state) const + void CreatureStats::writeState(ESM::CreatureStats& state) const { - for (int i=0; i(i)).writeState(state.mAttributes[i]); - for (int i=0; i<3; ++i) - mDynamic[i].writeState (state.mDynamic[i]); + for (size_t i = 0; i < state.mDynamic.size(); ++i) + mDynamic[i].writeState(state.mDynamic[i]); state.mTradeTime = mLastRestock.toEsm(); state.mGoldPool = mGoldPool; @@ -550,7 +585,7 @@ namespace MWMechanics // The vanilla engine does not store friendly hits in the save file. Since there's no other mechanism // that ever resets the friendly hits (at least not to my knowledge) this should be regarded a feature // rather than a bug. - //state.mFriendlyHits = mFriendlyHits; + // state.mFriendlyHits = mFriendlyHits; state.mTalkedTo = mTalkedTo; state.mAlarmed = mAlarmed; state.mAttacked = mAttacked; @@ -564,53 +599,51 @@ namespace MWMechanics state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?) state.mLastHitObject = mLastHitObject; state.mLastHitAttemptObject = mLastHitAttemptObject; - state.mRecalcDynamicStats = mRecalcMagicka; - state.mDrawState = mDrawState; + state.mRecalcDynamicStats = false; + state.mDrawState = static_cast(mDrawState); state.mLevel = mLevel; state.mActorId = mActorId; state.mDeathAnimation = mDeathAnimation; state.mTimeOfDeath = mTimeOfDeath.toEsm(); - //state.mHitAttemptActorId = mHitAttemptActorId; + // state.mHitAttemptActorId = mHitAttemptActorId; mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); mAiSequence.writeState(state.mAiSequence); mMagicEffects.writeState(state.mMagicEffects); - state.mSummonedCreatureMap = mSummonedCreatures; + state.mSummonedCreatures = mSummonedCreatures; state.mSummonGraveyard = mSummonGraveyard; state.mHasAiSettings = true; - for (int i=0; i<4; ++i) - mAiSettings[i].writeState (state.mAiSettings[i]); + for (size_t i = 0; i < state.mAiSettings.size(); ++i) + mAiSettings[i].writeState(state.mAiSettings[i]); - for (auto it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) - { - for (int i=0; ifirst].mWorsenings[i] = mCorprusSpells.at(it->first).mWorsenings[i]; - - state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); - } + state.mMissingACDT = false; } - void CreatureStats::readState (const ESM::CreatureStats& state) + void CreatureStats::readState(const ESM::CreatureStats& state) { - for (int i=0; i(i)].readState(state.mAttributes[i]); - for (int i=0; i<3; ++i) - mDynamic[i].readState (state.mDynamic[i]); + for (size_t i = 0; i < state.mDynamic.size(); ++i) + mDynamic[i].readState(state.mDynamic[i]); + + mGoldPool = state.mGoldPool; + mTalkedTo = state.mTalkedTo; + mAttacked = state.mAttacked; + } mLastRestock = MWWorld::TimeStamp(state.mTradeTime); - mGoldPool = state.mGoldPool; mDead = state.mDead; mDeathAnimationFinished = state.mDeathAnimationFinished; mDied = state.mDied; mMurdered = state.mMurdered; - mTalkedTo = state.mTalkedTo; mAlarmed = state.mAlarmed; - mAttacked = state.mAttacked; // TODO: rewrite. does this really need 3 separate bools? mKnockdown = state.mKnockdown; mKnockdownOneFrame = state.mKnockdownOneFrame; @@ -621,56 +654,26 @@ namespace MWMechanics mFallHeight = state.mFallHeight; mLastHitObject = state.mLastHitObject; mLastHitAttemptObject = state.mLastHitAttemptObject; - mRecalcMagicka = state.mRecalcDynamicStats; - mDrawState = DrawState_(state.mDrawState); + mDrawState = DrawState(state.mDrawState); mLevel = state.mLevel; mActorId = state.mActorId; mDeathAnimation = state.mDeathAnimation; mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath); - //mHitAttemptActorId = state.mHitAttemptActorId; + // mHitAttemptActorId = state.mHitAttemptActorId; mSpells.readState(state.mSpells, this); mActiveSpells.readState(state.mActiveSpells); mAiSequence.readState(state.mAiSequence); mMagicEffects.readState(state.mMagicEffects); - // Rebuild the bound item cache - for(int effectId = ESM::MagicEffect::BoundDagger; effectId <= ESM::MagicEffect::BoundGloves; effectId++) - { - if(mMagicEffects.get(effectId).getMagnitude() > 0) - mBoundItems.insert(effectId); - else - { - // Check active spell effects - // We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects - auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell) - { - const auto& effects = spell.second.mEffects; - return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect) - { - return effect.mEffectId == effectId; - }) != effects.end(); - }); - if(spell != mActiveSpells.end()) - mBoundItems.insert(effectId); - } - } - - mSummonedCreatures = state.mSummonedCreatureMap; + mSummonedCreatures = state.mSummonedCreatures; mSummonGraveyard = state.mSummonGraveyard; if (state.mHasAiSettings) - for (int i=0; i<4; ++i) + for (size_t i = 0; i < state.mAiSettings.size(); ++i) mAiSettings[i].readState(state.mAiSettings[i]); - - mCorprusSpells.clear(); - for (auto it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) - { - for (int i=0; ifirst].mWorsenings[i] = state.mCorprusSpells.at(it->first).mWorsenings[i]; - - mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - } + if (state.mRecalcDynamicStats) + recalculateMagicka(); } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) @@ -694,15 +697,15 @@ namespace MWMechanics int CreatureStats::getActorId() { - if (mActorId==-1) + if (mActorId == -1) mActorId = sActorId++; return mActorId; } - bool CreatureStats::matchesActorId (int id) const + bool CreatureStats::matchesActorId(int id) const { - return mActorId!=-1 && id==mActorId; + return mActorId != -1 && id == mActorId; } void CreatureStats::cleanup() @@ -710,14 +713,14 @@ namespace MWMechanics sActorId = 0; } - void CreatureStats::writeActorIdCounter (ESM::ESMWriter& esm) + void CreatureStats::writeActorIdCounter(ESM::ESMWriter& esm) { esm.startRecord(ESM::REC_ACTC); esm.writeHNT("COUN", sActorId); esm.endRecord(ESM::REC_ACTC); } - void CreatureStats::readActorIdCounter (ESM::ESMReader& esm) + void CreatureStats::readActorIdCounter(ESM::ESMReader& esm) { esm.getHNT(sActorId, "COUN"); } @@ -737,7 +740,7 @@ namespace MWMechanics return mTimeOfDeath; } - std::map& CreatureStats::getSummonedCreatureMap() + std::multimap& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } @@ -746,6 +749,7 @@ namespace MWMechanics { return mSummonGraveyard; } +<<<<<<< HEAD /* Start of tes3mp addition @@ -788,4 +792,6 @@ namespace MWMechanics mCorprusSpells.erase(corprusIt); } } +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index da0d49142..8c4ad8106 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -1,19 +1,22 @@ #ifndef GAME_MWMECHANICS_CREATURESTATS_H #define GAME_MWMECHANICS_CREATURESTATS_H +#include #include -#include #include +#include -#include "stat.hpp" -#include "magiceffects.hpp" -#include "spells.hpp" #include "activespells.hpp" #include "aisequence.hpp" +#include "aisetting.hpp" #include "drawstate.hpp" +#include "magiceffects.hpp" +#include "spells.hpp" +#include "stat.hpp" #include -#include +#include +#include namespace ESM { @@ -36,8 +39,8 @@ namespace MWMechanics class CreatureStats { static int sActorId; - DrawState_ mDrawState; - AttributeValue mAttributes[ESM::Attribute::Length]; + DrawState mDrawState; + std::map mAttributes; DynamicStat mDynamic[3]; // health, magicka, fatigue Spells mSpells; ActiveSpells mActiveSpells; @@ -61,10 +64,8 @@ namespace MWMechanics float mFallHeight; - std::string mLastHitObject; // The last object to hit this actor - std::string mLastHitAttemptObject; // The last object to attempt to hit this actor - - bool mRecalcMagicka; + ESM::RefId mLastHitObject; // The last object to hit this actor + ESM::RefId mLastHitAttemptObject; // The last object to attempt to hit this actor // For merchants: the last time items were restocked and gold pool refilled. MWWorld::TimeStamp mLastRestock; @@ -84,89 +85,82 @@ namespace MWMechanics // The difference between view direction and lower body direction. float mSideMovementAngle; + bool mTeleported = false; + private: - std::map mSummonedCreatures; // + std::multimap mSummonedCreatures; // // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. // This may be necessary when the creature is in an inactive cell. std::vector mSummonGraveyard; - std::map mCorprusSpells; - protected: int mLevel; + bool mAttackingOrSpell; public: CreatureStats(); - DrawState_ getDrawState() const; - void setDrawState(DrawState_ state); + DrawState getDrawState() const; + void setDrawState(DrawState state); - bool needToRecalcDynamicStats(); - void setNeedRecalcDynamicStats(bool val); + void recalculateMagicka(); float getFallHeight() const; void addToFallHeight(float height); /// Reset the fall height /// @return total fall height - float land(bool isPlayer=false); + float land(bool isPlayer = false); - const AttributeValue & getAttribute(int index) const; + const AttributeValue& getAttribute(ESM::Attribute::AttributeID id) const; - const DynamicStat & getHealth() const; + const DynamicStat& getHealth() const; - const DynamicStat & getMagicka() const; + const DynamicStat& getMagicka() const; - const DynamicStat & getFatigue() const; + const DynamicStat& getFatigue() const; - const DynamicStat & getDynamic (int index) const; + const DynamicStat& getDynamic(int index) const; - const Spells & getSpells() const; + const Spells& getSpells() const; - const ActiveSpells & getActiveSpells() const; + const ActiveSpells& getActiveSpells() const; - const MagicEffects & getMagicEffects() const; + const MagicEffects& getMagicEffects() const; - bool getAttackingOrSpell() const; + bool getAttackingOrSpell() const { return mAttackingOrSpell; } int getLevel() const; - Spells & getSpells(); + Spells& getSpells(); - ActiveSpells & getActiveSpells(); + ActiveSpells& getActiveSpells(); - MagicEffects & getMagicEffects(); + MagicEffects& getMagicEffects(); - void setAttribute(int index, const AttributeValue &value); + void setAttribute(ESM::Attribute::AttributeID id, const AttributeValue& value); // Shortcut to set only the base - void setAttribute(int index, float base); + void setAttribute(ESM::Attribute::AttributeID id, float base); - void setHealth(const DynamicStat &value); + void setHealth(const DynamicStat& value); - void setMagicka(const DynamicStat &value); + void setMagicka(const DynamicStat& value); - void setFatigue(const DynamicStat &value); + void setFatigue(const DynamicStat& value); - void setDynamic (int index, const DynamicStat &value); + void setDynamic(int index, const DynamicStat& value); /// Set Modifier for each magic effect according to \a effects. Does not touch Base values. - void modifyMagicEffects(const MagicEffects &effects); + void modifyMagicEffects(const MagicEffects& effects); - void setAttackingOrSpell(bool attackingOrSpell); + void setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; } void setLevel(int level); - enum AiSetting - { - AI_Hello = 0, - AI_Fight = 1, - AI_Flee = 2, - AI_Alarm = 3 - }; - void setAiSetting (AiSetting index, Stat value); - void setAiSetting (AiSetting index, int base); - Stat getAiSetting (AiSetting index) const; + void setAiSetting(AiSetting index, Stat value); + void setAiSetting(AiSetting index, int base); + Stat getAiSetting(AiSetting index) const; const AiSequence& getAiSequence() const; @@ -222,10 +216,10 @@ namespace MWMechanics void talkedToPlayer(); bool isAlarmed() const; - void setAlarmed (bool alarmed); + void setAlarmed(bool alarmed); bool getAttacked() const; - void setAttacked (bool attacked); + void setAttacked(bool attacked); float getEvasion() const; @@ -234,17 +228,18 @@ namespace MWMechanics /// including transition animations (falling down & standing up) bool getKnockedDown() const; void setKnockedDownOneFrame(bool value); - ///Returns true only for the first frame of the actor being knocked out; used for "onKnockedOut" command + /// Returns true only for the first frame of the actor being knocked out; used for "onKnockedOut" command bool getKnockedDownOneFrame() const; void setKnockedDownOverOneFrame(bool value); - ///Returns true for all but the first frame of being knocked out; used to know to not reset mKnockedDownOneFrame + /// Returns true for all but the first frame of being knocked out; used to know to not reset + /// mKnockedDownOneFrame bool getKnockedDownOverOneFrame() const; void setHitRecovery(bool value); bool getHitRecovery() const; void setBlock(bool value); bool getBlock() const; - std::map& getSummonedCreatureMap(); // + std::multimap& getSummonedCreatureMap(); // std::vector& getSummonedCreatureGraveyard(); // ActorIds /* @@ -273,28 +268,26 @@ namespace MWMechanics Stance_Sneak }; - bool getMovementFlag (Flag flag) const; - void setMovementFlag (Flag flag, bool state); + bool getMovementFlag(Flag flag) const; + void setMovementFlag(Flag flag, bool state); /// Like getMovementFlag, but also takes into account if the flag is Forced - bool getStance (Stance flag) const; + bool getStance(Stance flag) const; - void setLastHitObject(const std::string &objectid); - const std::string &getLastHitObject() const; - void setLastHitAttemptObject(const std::string &objectid); - const std::string &getLastHitAttemptObject() const; + void setLastHitObject(const ESM::RefId& objectid); + void clearLastHitObject(); + const ESM::RefId& getLastHitObject() const; + void setLastHitAttemptObject(const ESM::RefId& objectid); + void clearLastHitAttemptObject(); + const ESM::RefId& getLastHitAttemptObject() const; void setHitAttemptActorId(const int actorId); int getHitAttemptActorId() const; - // Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves. - // TODO: Put it somewhere else? - std::set mBoundItems; + void writeState(ESM::CreatureStats& state) const; - void writeState (ESM::CreatureStats& state) const; + void readState(const ESM::CreatureStats& state); - void readState (const ESM::CreatureStats& state); - - static void writeActorIdCounter (ESM::ESMWriter& esm); - static void readActorIdCounter (ESM::ESMReader& esm); + static void writeActorIdCounter(ESM::ESMWriter& esm); + static void readActorIdCounter(ESM::ESMReader& esm); void setLastRestockTime(MWWorld::TimeStamp tradeTime); MWWorld::TimeStamp getLastRestockTime() const; @@ -310,20 +303,19 @@ namespace MWMechanics int getActorId(); ///< Will generate an actor ID, if the actor does not have one yet. - bool matchesActorId (int id) const; + bool matchesActorId(int id) const; ///< Check if \a id matches the actor ID of *this (if the actor does not have an ID /// assigned this function will return false). static void cleanup(); - std::map & getCorprusSpells(); - - void addCorprusSpell(const std::string& sourceId, CorprusStats& stats); - - void removeCorprusSpell(const std::string& sourceId); - float getSideMovementAngle() const { return mSideMovementAngle; } void setSideMovementAngle(float angle) { mSideMovementAngle = angle; } + + bool wasTeleported() const { return mTeleported; } + void setTeleported(bool v) { mTeleported = v; } + + const std::map getAttributes() const { return mAttributes; } }; } diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp index b44e53fa4..b460ff229 100644 --- a/apps/openmw/mwmechanics/difficultyscaling.cpp +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -1,7 +1,8 @@ #include "difficultyscaling.hpp" -#include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -14,8 +15,11 @@ */ #include "../mwbase/world.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/ptr.hpp" #include "actorutil.hpp" @@ -23,11 +27,10 @@ float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr { const MWWorld::Ptr& player = MWMechanics::getPlayer(); - // [-500, 500] - int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); - difficultySetting = std::min(difficultySetting, 500); - difficultySetting = std::max(difficultySetting, -500); + static const float fDifficultyMult + = MWBase::Environment::get().getESMStore()->get().find("fDifficultyMult")->mValue.getFloat(); +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -41,6 +44,9 @@ float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get().find("fDifficultyMult")->mValue.getFloat(); float difficultyTerm = 0.01f * difficultySetting; +======= + const float difficultyTerm = 0.01f * Settings::game().mDifficulty; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 float x = 0; if (victim == player) diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 55fe86387..b150d0093 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -1,8 +1,11 @@ #ifndef OPENMW_MECHANICS_DISEASE_H #define OPENMW_MECHANICS_DISEASE_H +#include #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -15,16 +18,19 @@ */ #include "../mwbase/windowmanager.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/ptr.hpp" -#include "spells.hpp" -#include "creaturestats.hpp" #include "actorutil.hpp" +#include "creaturestats.hpp" +#include "spells.hpp" namespace MWMechanics { @@ -32,44 +38,53 @@ namespace MWMechanics /// Call when \a actor has got in contact with \a carrier (e.g. hit by him, or loots him) /// @param actor The actor that will potentially catch diseases. Currently only the player can catch diseases. /// @param carrier The disease carrier. - inline void diseaseContact (MWWorld::Ptr actor, MWWorld::Ptr carrier) + inline void diseaseContact(const MWWorld::Ptr& actor, const MWWorld::Ptr& carrier) { if (!carrier.getClass().isActor() || actor != getPlayer()) return; - float fDiseaseXferChance = - MWBase::Environment::get().getWorld()->getStore().get().find( - "fDiseaseXferChance")->mValue.getFloat(); + float fDiseaseXferChance = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fDiseaseXferChance") + ->mValue.getFloat(); - MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); + const MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId)) continue; float resist = 0.f; if (Spells::hasCorprusEffect(spell)) - resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude() - - actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude()); + resist = 1.f + - 0.01f + * (actorEffects.getOrDefault(ESM::MagicEffect::ResistCorprusDisease).getMagnitude() + - actorEffects.getOrDefault(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude()); else if (spell->mData.mType == ESM::Spell::ST_Disease) - resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCommonDisease).getMagnitude() - - actorEffects.get(ESM::MagicEffect::WeaknessToCommonDisease).getMagnitude()); + resist = 1.f + - 0.01f + * (actorEffects.getOrDefault(ESM::MagicEffect::ResistCommonDisease).getMagnitude() + - actorEffects.getOrDefault(ESM::MagicEffect::WeaknessToCommonDisease).getMagnitude()); else if (spell->mData.mType == ESM::Spell::ST_Blight) - resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistBlightDisease).getMagnitude() - - actorEffects.get(ESM::MagicEffect::WeaknessToBlightDisease).getMagnitude()); + resist = 1.f + - 0.01f + * (actorEffects.getOrDefault(ESM::MagicEffect::ResistBlightDisease).getMagnitude() + - actorEffects.getOrDefault(ESM::MagicEffect::WeaknessToBlightDisease).getMagnitude()); else continue; int x = static_cast(fDiseaseXferChance * 100 * resist); - if (Misc::Rng::rollDice(10000) < x) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::rollDice(10000, prng) < x) { // Contracted disease! - actor.getClass().getCreatureStats(actor).getSpells().add(it->first); + actor.getClass().getCreatureStats(actor).getSpells().add(spell); MWBase::Environment::get().getWorld()->applyLoopingParticles(actor); +<<<<<<< HEAD /* Start of tes3mp addition @@ -82,6 +97,13 @@ namespace MWMechanics std::string msg = "sMagicContractDisease"; msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->mValue.getString(); +======= + std::string msg = MWBase::Environment::get() + .getESMStore() + ->get() + .find("sMagicContractDisease") + ->mValue.getString(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 msg = Misc::StringUtils::format(msg, spell->mName); MWBase::Environment::get().getWindowManager()->messageBox(msg); } @@ -89,5 +111,4 @@ namespace MWMechanics } } - #endif diff --git a/apps/openmw/mwmechanics/drawstate.hpp b/apps/openmw/mwmechanics/drawstate.hpp index 7f59d8d78..1373d59ef 100644 --- a/apps/openmw/mwmechanics/drawstate.hpp +++ b/apps/openmw/mwmechanics/drawstate.hpp @@ -3,12 +3,12 @@ namespace MWMechanics { - /// \note The _ suffix is required to avoid a collision with a Windoze macro. Die, Microsoft! Die! - enum DrawState_ + + enum class DrawState { - DrawState_Nothing = 0, - DrawState_Weapon = 1, - DrawState_Spell = 2 + Nothing = 0, + Weapon = 1, + Spell = 2 }; } diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 7f1000489..ea56d0ffb 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -1,8 +1,11 @@ #include "enchanting.hpp" +#include +#include #include -#include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -18,17 +21,19 @@ */ #include "../mwworld/manualref.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" +#include "actorutil.hpp" #include "creaturestats.hpp" #include "spellutil.hpp" -#include "actorutil.hpp" #include "weapontype.hpp" namespace MWMechanics @@ -36,30 +41,32 @@ namespace MWMechanics Enchanting::Enchanting() : mCastStyle(ESM::Enchantment::CastOnce) , mSelfEnchanting(false) + , mObjectType(0) , mWeaponType(-1) - {} + { + } void Enchanting::setOldItem(const MWWorld::Ptr& oldItem) { - mOldItemPtr=oldItem; + mOldItemPtr = oldItem; mWeaponType = -1; - mObjectType.clear(); - if(!itemEmpty()) + mObjectType = 0; + if (!itemEmpty()) { - mObjectType = mOldItemPtr.getTypeName(); - if (mObjectType == typeid(ESM::Weapon).name()) + mObjectType = mOldItemPtr.getType(); + if (mObjectType == ESM::Weapon::sRecordId) mWeaponType = mOldItemPtr.get()->mBase->mData.mType; } } void Enchanting::setNewItemName(const std::string& s) { - mNewItemName=s; + mNewItemName = s; } void Enchanting::setEffect(const ESM::EffectList& effectList) { - mEffectList=effectList; + mEffectList = effectList; } int Enchanting::getCastStyle() const @@ -69,7 +76,7 @@ namespace MWMechanics void Enchanting::setSoulGem(const MWWorld::Ptr& soulGem) { - mSoulGemPtr=soulGem; + mSoulGemPtr = soulGem; } bool Enchanting::create() @@ -81,25 +88,27 @@ namespace MWMechanics enchantment.mData.mType = mCastStyle; enchantment.mData.mCost = getBaseCastCost(); - store.remove(mSoulGemPtr, 1, player); + store.remove(mSoulGemPtr, 1); - //Exception for Azura Star, new one will be added after enchanting - if(Misc::StringUtils::ciEqual(mSoulGemPtr.get()->mBase->mId, "Misc_SoulGem_Azura")) - store.add("Misc_SoulGem_Azura", 1, player); + // Exception for Azura Star, new one will be added after enchanting + auto azurasStarId = ESM::RefId::stringRefId("Misc_SoulGem_Azura"); + if (mSoulGemPtr.get()->mBase->mId == azurasStarId) + store.add(azurasStarId, 1); - if(mSelfEnchanting) + if (mSelfEnchanting) { - if(getEnchantChance() <= (Misc::Rng::roll0to99())) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (getEnchantChance() <= (Misc::Rng::roll0to99(prng))) return false; - mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); + mEnchanter.getClass().skillUsageSucceeded(mEnchanter, ESM::Skill::Enchant, 2); } enchantment.mEffects = mEffectList; int count = getEnchantItemsCount(); - if(mCastStyle==ESM::Enchantment::ConstantEffect) + if (mCastStyle == ESM::Enchantment::ConstantEffect) enchantment.mData.mCharge = 0; else enchantment.mData.mCharge = getGemCharge() / count; @@ -107,9 +116,10 @@ namespace MWMechanics // Try to find a dynamic enchantment with the same stats, create a new one if not found. const ESM::Enchantment* enchantmentPtr = getRecord(enchantment); if (enchantmentPtr == nullptr) - enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); + enchantmentPtr = MWBase::Environment::get().getESMStore()->insert(enchantment); // Apply the enchantment +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -129,8 +139,16 @@ namespace MWMechanics mwmp::Main::get().getNetworking()->getWorldstate()->sendEnchantmentRecord(enchantmentPtr); store.remove(mOldItemPtr, count, player); +======= + const ESM::RefId& newItemId + = mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); - if(!mSelfEnchanting) + // Add the new item to player inventory and remove the old one + store.remove(mOldItemPtr, count); + store.add(newItemId, count); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 + + if (!mSelfEnchanting) payForEnchantment(); mwmp::Main::get().getLocalPlayer()->storeLastEnchantmentQuantity(count); @@ -142,17 +160,20 @@ namespace MWMechanics return true; } - + void Enchanting::nextCastStyle() { if (itemEmpty()) return; - const bool powerfulSoul = getGemCharge() >= \ - MWBase::Environment::get().getWorld()->getStore().get().find ("iSoulAmountForConstantEffect")->mValue.getInteger(); - if ((mObjectType == typeid(ESM::Armor).name()) || (mObjectType == typeid(ESM::Clothing).name())) + const bool powerfulSoul = getGemCharge() >= MWBase::Environment::get() + .getESMStore() + ->get() + .find("iSoulAmountForConstantEffect") + ->mValue.getInteger(); + if ((mObjectType == ESM::Armor::sRecordId) || (mObjectType == ESM::Clothing::sRecordId)) { // Armor or Clothing - switch(mCastStyle) + switch (mCastStyle) { case ESM::Enchantment::WhenUsed: if (powerfulSoul) @@ -166,7 +187,7 @@ namespace MWMechanics else if (mWeaponType != -1) { // Weapon ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; - switch(mCastStyle) + switch (mCastStyle) { case ESM::Enchantment::WhenStrikes: if (weapclass == ESM::WeaponType::Melee || weapclass == ESM::WeaponType::Ranged) @@ -185,7 +206,7 @@ namespace MWMechanics return; } } - else if(mObjectType == typeid(ESM::Book).name()) + else if (mObjectType == ESM::Book::sRecordId) { // Scroll or Book mCastStyle = ESM::Enchantment::CastOnce; return; @@ -215,9 +236,10 @@ namespace MWMechanics // No effects added, cost = 0 return 0; - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); - const float fEnchantmentConstantDurationMult = store.get().find("fEnchantmentConstantDurationMult")->mValue.getFloat(); + const float fEnchantmentConstantDurationMult + = store.get().find("fEnchantmentConstantDurationMult")->mValue.getFloat(); float enchantmentCost = 0.f; float cost = 0.f; @@ -246,18 +268,17 @@ namespace MWMechanics const ESM::Enchantment* Enchanting::getRecord(const ESM::Enchantment& toFind) const { - const MWWorld::Store& enchantments = MWBase::Environment::get().getWorld()->getStore().get(); - MWWorld::Store::iterator iter (enchantments.begin()); + const MWWorld::Store& enchantments + = MWBase::Environment::get().getESMStore()->get(); + MWWorld::Store::iterator iter(enchantments.begin()); iter += (enchantments.getSize() - enchantments.getDynamicSize()); for (; iter != enchantments.end(); ++iter) { if (iter->mEffects.mList.size() != toFind.mEffects.mList.size()) continue; - if (iter->mData.mFlags != toFind.mData.mFlags - || iter->mData.mType != toFind.mData.mType - || iter->mData.mCost != toFind.mData.mCost - || iter->mData.mCharge != toFind.mData.mCharge) + if (iter->mData.mFlags != toFind.mData.mFlags || iter->mData.mType != toFind.mData.mType + || iter->mData.mCost != toFind.mData.mCost || iter->mData.mCharge != toFind.mData.mCharge) continue; // Don't choose an ID that came from the content files, would have unintended side effects @@ -266,19 +287,15 @@ namespace MWMechanics bool mismatch = false; - for (int i=0; i (iter->mEffects.mList.size()); ++i) + for (int i = 0; i < static_cast(iter->mEffects.mList.size()); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = toFind.mEffects.mList[i]; - if (first.mEffectID!=second.mEffectID || - first.mArea!=second.mArea || - first.mRange!=second.mRange || - first.mSkill!=second.mSkill || - first.mAttribute!=second.mAttribute || - first.mMagnMin!=second.mMagnMin || - first.mMagnMax!=second.mMagnMax || - first.mDuration!=second.mDuration) + if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange + || first.mSkill != second.mSkill || first.mAttribute != second.mAttribute + || first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax + || first.mDuration != second.mDuration) { mismatch = true; break; @@ -307,27 +324,31 @@ namespace MWMechanics return getEffectiveEnchantmentCastCost(static_cast(baseCost), player); } - int Enchanting::getEnchantPrice() const { - if(mEnchanter.isEmpty()) + if (mEnchanter.isEmpty()) return 0; - float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get().find ("fEnchantmentValueMult")->mValue.getFloat(); - int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, static_cast(getEnchantPoints() * priceMultipler), true); + float priceMultipler = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fEnchantmentValueMult") + ->mValue.getFloat(); + int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer( + mEnchanter, static_cast(getEnchantPoints() * priceMultipler), true); price *= getEnchantItemsCount() * getTypeMultiplier(); return std::max(1, price); } int Enchanting::getGemCharge() const { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - if(soulEmpty()) + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + if (soulEmpty()) return 0; - if(mSoulGemPtr.getCellRef().getSoul()=="") + if (mSoulGemPtr.getCellRef().getSoul().empty()) return 0; const ESM::Creature* soul = store.get().search(mSoulGemPtr.getCellRef().getSoul()); - if(soul) + if (soul) return soul->mData.mSoul; else return 0; @@ -338,9 +359,10 @@ namespace MWMechanics if (itemEmpty()) return 0; - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - return static_cast(mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) * store.get().find("fEnchantmentMult")->mValue.getFloat()); + return static_cast(mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) + * store.get().find("fEnchantmentMult")->mValue.getFloat()); } bool Enchanting::soulEmpty() const { @@ -367,14 +389,17 @@ namespace MWMechanics int Enchanting::getEnchantChance() const { const CreatureStats& stats = mEnchanter.getClass().getCreatureStats(mEnchanter); - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); const float a = static_cast(mEnchanter.getClass().getSkill(mEnchanter, ESM::Skill::Enchant)); - const float b = static_cast(stats.getAttribute (ESM::Attribute::Intelligence).getModified()); - const float c = static_cast(stats.getAttribute (ESM::Attribute::Luck).getModified()); + const float b = static_cast(stats.getAttribute(ESM::Attribute::Intelligence).getModified()); + const float c = static_cast(stats.getAttribute(ESM::Attribute::Luck).getModified()); const float fEnchantmentChanceMult = gmst.find("fEnchantmentChanceMult")->mValue.getFloat(); const float fEnchantmentConstantChanceMult = gmst.find("fEnchantmentConstantChanceMult")->mValue.getFloat(); - float x = (a - getEnchantPoints() * fEnchantmentChanceMult * getTypeMultiplier() * getEnchantItemsCount() + 0.2f * b + 0.1f * c) * stats.getFatigueTerm(); + float x = (a - getEnchantPoints() * fEnchantmentChanceMult * getTypeMultiplier() * getEnchantItemsCount() + + 0.2f * b + 0.1f * c) + * stats.getFatigueTerm(); if (mCastStyle == ESM::Enchantment::ConstantEffect) x *= fEnchantmentConstantChanceMult; @@ -390,10 +415,10 @@ namespace MWMechanics ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) { - static const float multiplier = std::max(0.f, std::min(1.0f, Settings::Manager::getFloat("projectiles enchant multiplier", "Game"))); MWWorld::Ptr player = getPlayer(); - int itemsInInventoryCount = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId()); - count = std::min(itemsInInventoryCount, std::max(1, int(getGemCharge() * multiplier / enchantPoints))); + count = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId()); + count = std::clamp( + getGemCharge() * Settings::game().mProjectilesEnchantMultiplier / enchantPoints, 1, count); } } @@ -402,8 +427,7 @@ namespace MWMechanics float Enchanting::getTypeMultiplier() const { - static const bool useMultiplier = Settings::Manager::getFloat("projectiles enchant multiplier", "Game") > 0; - if (useMultiplier && mWeaponType != -1 && getEnchantPoints() > 0) + if (Settings::game().mProjectilesEnchantMultiplier > 0 && mWeaponType != -1 && getEnchantPoints() > 0) { ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) @@ -418,7 +442,7 @@ namespace MWMechanics const MWWorld::Ptr& player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); - store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); + store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice()); // add gold to NPC trading gold pool CreatureStats& enchanterStats = mEnchanter.getClass().getCreatureStats(mEnchanter); diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 33a282093..53c3fc3f9 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -3,8 +3,8 @@ #include -#include -#include +#include +#include #include "../mwworld/ptr.hpp" @@ -12,47 +12,48 @@ namespace MWMechanics { class Enchanting { - MWWorld::Ptr mOldItemPtr; - MWWorld::Ptr mSoulGemPtr; - MWWorld::Ptr mEnchanter; + MWWorld::Ptr mOldItemPtr; + MWWorld::Ptr mSoulGemPtr; + MWWorld::Ptr mEnchanter; - int mCastStyle; + int mCastStyle; - bool mSelfEnchanting; + bool mSelfEnchanting; - ESM::EffectList mEffectList; + ESM::EffectList mEffectList; - std::string mNewItemName; - std::string mObjectType; - int mWeaponType; + std::string mNewItemName; + unsigned int mObjectType; + int mWeaponType; - const ESM::Enchantment* getRecord(const ESM::Enchantment& newEnchantment) const; + const ESM::Enchantment* getRecord(const ESM::Enchantment& newEnchantment) const; - public: - Enchanting(); - void setEnchanter(const MWWorld::Ptr& enchanter); - void setSelfEnchanting(bool selfEnchanting); - void setOldItem(const MWWorld::Ptr& oldItem); - MWWorld::Ptr getOldItem() { return mOldItemPtr; } - MWWorld::Ptr getGem() { return mSoulGemPtr; } - void setNewItemName(const std::string& s); - void setEffect(const ESM::EffectList& effectList); - void setSoulGem(const MWWorld::Ptr& soulGem); - bool create(); //Return true if created, false if failed. - void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) - int getCastStyle() const; - float getEnchantPoints(bool precise = true) const; - int getBaseCastCost() const; // To be saved in the enchantment's record - int getEffectiveCastCost() const; // Effective cost taking player Enchant skill into account, used for preview purposes in the UI - int getEnchantPrice() const; - int getMaxEnchantValue() const; - int getGemCharge() const; - int getEnchantChance() const; - int getEnchantItemsCount() const; - float getTypeMultiplier() const; - bool soulEmpty() const; //Return true if empty - bool itemEmpty() const; //Return true if empty - void payForEnchantment() const; + public: + Enchanting(); + void setEnchanter(const MWWorld::Ptr& enchanter); + void setSelfEnchanting(bool selfEnchanting); + void setOldItem(const MWWorld::Ptr& oldItem); + MWWorld::Ptr getOldItem() { return mOldItemPtr; } + MWWorld::Ptr getGem() { return mSoulGemPtr; } + void setNewItemName(const std::string& s); + void setEffect(const ESM::EffectList& effectList); + void setSoulGem(const MWWorld::Ptr& soulGem); + bool create(); // Return true if created, false if failed. + void nextCastStyle(); // Set enchant type to next possible type (for mOldItemPtr object) + int getCastStyle() const; + float getEnchantPoints(bool precise = true) const; + int getBaseCastCost() const; // To be saved in the enchantment's record + int getEffectiveCastCost() + const; // Effective cost taking player Enchant skill into account, used for preview purposes in the UI + int getEnchantPrice() const; + int getMaxEnchantValue() const; + int getGemCharge() const; + int getEnchantChance() const; + int getEnchantItemsCount() const; + float getTypeMultiplier() const; + bool soulEmpty() const; // Return true if empty + bool itemEmpty() const; // Return true if empty + void payForEnchantment() const; }; } #endif diff --git a/apps/openmw/mwmechanics/greetingstate.hpp b/apps/openmw/mwmechanics/greetingstate.hpp new file mode 100644 index 000000000..9b3709632 --- /dev/null +++ b/apps/openmw/mwmechanics/greetingstate.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_MWMECHANICS_GREETINGSTATE_H +#define OPENMW_MWMECHANICS_GREETINGSTATE_H + +namespace MWMechanics +{ + enum GreetingState + { + Greet_None, + Greet_InProgress, + Greet_Done + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/inventory.hpp b/apps/openmw/mwmechanics/inventory.hpp new file mode 100644 index 000000000..5185d540c --- /dev/null +++ b/apps/openmw/mwmechanics/inventory.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_MWMECHANICS_INVENTORY_H +#define OPENMW_MWMECHANICS_INVENTORY_H + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" + +#include +#include + +#include +#include + +namespace MWMechanics +{ + template + void modifyBaseInventory(const ESM::RefId& actorId, const ESM::RefId& itemId, int amount) + { + T copy = *MWBase::Environment::get().getESMStore()->get().find(actorId); + for (auto& it : copy.mInventory.mList) + { + if (it.mItem == itemId) + { + const int sign = it.mCount < 1 ? -1 : 1; + it.mCount = sign * std::max(it.mCount * sign + amount, 0); + MWBase::Environment::get().getESMStore()->overrideRecord(copy); + return; + } + } + if (amount > 0) + { + ESM::ContItem cont; + cont.mItem = itemId; + cont.mCount = amount; + copy.mInventory.mList.push_back(cont); + MWBase::Environment::get().getESMStore()->overrideRecord(copy); + } + } +} + +#endif diff --git a/apps/openmw/mwmechanics/levelledlist.cpp b/apps/openmw/mwmechanics/levelledlist.cpp new file mode 100644 index 000000000..26f759425 --- /dev/null +++ b/apps/openmw/mwmechanics/levelledlist.cpp @@ -0,0 +1,81 @@ +#include +#include + +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/ptr.hpp" + +#include "../mwbase/environment.hpp" + +#include "actorutil.hpp" +#include "creaturestats.hpp" +#include "levelledlist.hpp" + +namespace MWMechanics +{ + + ESM::RefId getLevelledItem( + const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Generator& prng, std::optional level) + { + const std::vector& items = levItem->mList; + + int playerLevel; + if (level.has_value()) + playerLevel = *level; + else + { + const MWWorld::Ptr& player = getPlayer(); + playerLevel = player.getClass().getCreatureStats(player).getLevel(); + level = playerLevel; + } + + if (Misc::Rng::roll0to99(prng) < levItem->mChanceNone) + return ESM::RefId(); + + std::vector candidates; + int highestLevel = 0; + for (const auto& levelledItem : items) + { + if (levelledItem.mLevel > highestLevel && levelledItem.mLevel <= playerLevel) + highestLevel = levelledItem.mLevel; + } + + // For levelled creatures, the flags are swapped. This file format just makes so much sense. + bool allLevels = (levItem->mFlags & ESM::ItemLevList::AllLevels) != 0; + if (creature) + allLevels = levItem->mFlags & ESM::CreatureLevList::AllLevels; + + for (const auto& levelledItem : items) + { + if (playerLevel >= levelledItem.mLevel && (allLevels || levelledItem.mLevel == highestLevel)) + candidates.push_back(&levelledItem.mId); + } + if (candidates.empty()) + return ESM::RefId(); + const ESM::RefId& item = *candidates[Misc::Rng::rollDice(candidates.size(), prng)]; + + // Vanilla doesn't fail on nonexistent items in levelled lists + if (!MWBase::Environment::get().getESMStore()->find(item)) + { + Log(Debug::Warning) << "Warning: ignoring nonexistent item " << item << " in levelled list " + << levItem->mId; + return ESM::RefId(); + } + + // Is this another levelled item or a real item? + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), item, 1); + if (ref.getPtr().getType() != ESM::ItemLevList::sRecordId + && ref.getPtr().getType() != ESM::CreatureLevList::sRecordId) + { + return item; + } + else + { + if (ref.getPtr().getType() == ESM::ItemLevList::sRecordId) + return getLevelledItem(ref.getPtr().get()->mBase, false, prng, level); + else + return getLevelledItem(ref.getPtr().get()->mBase, true, prng, level); + } + } +} diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index c8368101a..5a739dd41 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -1,84 +1,22 @@ #ifndef OPENMW_MECHANICS_LEVELLEDLIST_H #define OPENMW_MECHANICS_LEVELLEDLIST_H -#include #include -#include "../mwworld/ptr.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/manualref.hpp" -#include "../mwworld/class.hpp" +#include -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - -#include "creaturestats.hpp" -#include "actorutil.hpp" +namespace ESM +{ + struct LevelledListBase; + class RefId; +} namespace MWMechanics { /// @return ID of resulting item, or empty if none - inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Seed& seed = Misc::Rng::getSeed()) - { - const std::vector& items = levItem->mList; - - const MWWorld::Ptr& player = getPlayer(); - int playerLevel = player.getClass().getCreatureStats(player).getLevel(); - - if (Misc::Rng::roll0to99(seed) < levItem->mChanceNone) - return std::string(); - - std::vector candidates; - int highestLevel = 0; - for (const auto& levelledItem : items) - { - if (levelledItem.mLevel > highestLevel && levelledItem.mLevel <= playerLevel) - highestLevel = levelledItem.mLevel; - } - - // For levelled creatures, the flags are swapped. This file format just makes so much sense. - bool allLevels = (levItem->mFlags & ESM::ItemLevList::AllLevels) != 0; - if (creature) - allLevels = levItem->mFlags & ESM::CreatureLevList::AllLevels; - - std::pair highest = std::make_pair(-1, ""); - for (const auto& levelledItem : items) - { - if (playerLevel >= levelledItem.mLevel - && (allLevels || levelledItem.mLevel == highestLevel)) - { - candidates.push_back(levelledItem.mId); - if (levelledItem.mLevel >= highest.first) - highest = std::make_pair(levelledItem.mLevel, levelledItem.mId); - } - } - if (candidates.empty()) - return std::string(); - std::string item = candidates[Misc::Rng::rollDice(candidates.size(), seed)]; - - // Vanilla doesn't fail on nonexistent items in levelled lists - if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) - { - Log(Debug::Warning) << "Warning: ignoring nonexistent item '" << item << "' in levelled list '" << levItem->mId << "'"; - return std::string(); - } - - // Is this another levelled item or a real item? - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); - if (ref.getPtr().getTypeName() != typeid(ESM::ItemLevList).name() - && ref.getPtr().getTypeName() != typeid(ESM::CreatureLevList).name()) - { - return item; - } - else - { - if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) - return getLevelledItem(ref.getPtr().get()->mBase, false, seed); - else - return getLevelledItem(ref.getPtr().get()->mBase, true, seed); - } - } + ESM::RefId getLevelledItem( + const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Generator& prng, std::optional level = {}); } diff --git a/apps/openmw/mwmechanics/linkedeffects.cpp b/apps/openmw/mwmechanics/linkedeffects.cpp deleted file mode 100644 index b0defac7d..000000000 --- a/apps/openmw/mwmechanics/linkedeffects.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "linkedeffects.hpp" - -#include -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwrender/animation.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "creaturestats.hpp" - -namespace MWMechanics -{ - - bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects) - { - if (caster.isEmpty() || caster == target || !target.getClass().isActor()) - return false; - - bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; - bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable; - if (!isHarmful || isUnreflectable) - return false; - - float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); - if (Misc::Rng::roll0to99() >= reflect) - return false; - - const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation && !reflectStatic->mModel.empty()) - animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string()); - reflectedEffects.mList.emplace_back(effect); - return true; - } - - void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source) - { - if (caster.isEmpty() || caster == target) - return; - - if (!target.getClass().isActor() || !caster.getClass().isActor()) - return; - - // Make sure callers don't do something weird - if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill) - throw std::runtime_error("invalid absorb stat effect"); - - if (appliedEffect.mMagnitude == 0) - return; - - std::vector absorbEffects; - ActiveSpells::ActiveEffect absorbEffect = appliedEffect; - absorbEffect.mMagnitude *= -1; - absorbEffect.mEffectIndex = appliedEffect.mEffectIndex; - absorbEffects.emplace_back(absorbEffect); - - // Morrowind negates reflected Absorb spells so the original caster won't be harmed. - if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game")) - { - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true, - absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId()); - return; - } - - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true, - absorbEffects, source, target.getClass().getCreatureStats(target).getActorId()); - } -} diff --git a/apps/openmw/mwmechanics/linkedeffects.hpp b/apps/openmw/mwmechanics/linkedeffects.hpp deleted file mode 100644 index a6dea2a3a..000000000 --- a/apps/openmw/mwmechanics/linkedeffects.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef MWMECHANICS_LINKEDEFFECTS_H -#define MWMECHANICS_LINKEDEFFECTS_H - -#include - -namespace ESM -{ - struct ActiveEffect; - struct EffectList; - struct ENAMstruct; - struct MagicEffect; - struct Spell; -} - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - - // Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list. - bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects); - - // Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster. - void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source); -} - -#endif diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 1a1a44f63..e75a1184a 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -1,41 +1,71 @@ #include "magiceffects.hpp" +#include #include -#include -#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" + +namespace +{ + // Round value to prevent precision issues + void truncate(float& value) + { + value = static_cast(value * 1024.f) / 1024.f; + } +} namespace MWMechanics { - EffectKey::EffectKey() : mId (0), mArg (-1) {} + EffectKey::EffectKey() + : mId(0) + , mArg(-1) + { + } - EffectKey::EffectKey (const ESM::ENAMstruct& effect) + EffectKey::EffectKey(const ESM::ENAMstruct& effect) { mId = effect.mEffectID; mArg = -1; - if (effect.mSkill!=-1) + if (effect.mSkill != -1) mArg = effect.mSkill; - if (effect.mAttribute!=-1) + if (effect.mAttribute != -1) { - if (mArg!=-1) - throw std::runtime_error ( - "magic effect can't have both a skill and an attribute argument"); + if (mArg != -1) + throw std::runtime_error("magic effect can't have both a skill and an attribute argument"); mArg = effect.mAttribute; } } - bool operator< (const EffectKey& left, const EffectKey& right) + std::string EffectKey::toString() const { - if (left.mIdget().search(mId); + return getMagicEffectString(*magicEffect, store->get().search(mArg), + store->get().search(ESM::Skill::indexToRefId(mArg))); + } + + bool operator<(const EffectKey& left, const EffectKey& right) + { + if (left.mId < right.mId) return true; - if (left.mId>right.mId) + if (left.mId > right.mId) return false; - return left.mArgsecond.setModifier(effects.get(it->first).getModifier()); + it->second.setModifier(effects.getOrDefault(it->first).getModifier()); } for (Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) @@ -121,94 +157,130 @@ namespace MWMechanics } } - MagicEffects& MagicEffects::operator+= (const MagicEffects& effects) + EffectParam MagicEffects::getOrDefault(const EffectKey& key) const { - if (this==&effects) - { - MagicEffects temp (effects); - *this += temp; - return *this; - } - - for (Collection::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) - { - Collection::iterator result = mCollection.find (iter->first); - - if (result!=mCollection.end()) - result->second += iter->second; - else - mCollection.insert (*iter); - } - - return *this; + return get(key).value_or(EffectParam()); } - EffectParam MagicEffects::get (const EffectKey& key) const + std::optional MagicEffects::get(const EffectKey& key) const { - Collection::const_iterator iter = mCollection.find (key); + Collection::const_iterator iter = mCollection.find(key); - if (iter==mCollection.end()) - { - return EffectParam(); - } - else + if (iter != mCollection.end()) { return iter->second; } + return std::nullopt; } - MagicEffects MagicEffects::diff (const MagicEffects& prev, const MagicEffects& now) + MagicEffects MagicEffects::diff(const MagicEffects& prev, const MagicEffects& now) { MagicEffects result; // adding/changing - for (Collection::const_iterator iter (now.begin()); iter!=now.end(); ++iter) + for (Collection::const_iterator iter(now.begin()); iter != now.end(); ++iter) { - Collection::const_iterator other = prev.mCollection.find (iter->first); + Collection::const_iterator other = prev.mCollection.find(iter->first); - if (other==prev.end()) + if (other == prev.end()) { // adding - result.add (iter->first, iter->second); + result.add(iter->first, iter->second); } else { // changing - result.add (iter->first, iter->second - other->second); + result.add(iter->first, iter->second - other->second); } } // removing - for (Collection::const_iterator iter (prev.begin()); iter!=prev.end(); ++iter) + for (Collection::const_iterator iter(prev.begin()); iter != prev.end(); ++iter) { - Collection::const_iterator other = now.mCollection.find (iter->first); - if (other==now.end()) + Collection::const_iterator other = now.mCollection.find(iter->first); + if (other == now.end()) { - result.add (iter->first, EffectParam() - iter->second); + result.add(iter->first, EffectParam() - iter->second); } } return result; } - void MagicEffects::writeState(ESM::MagicEffects &state) const + void MagicEffects::writeState(ESM::MagicEffects& state) const { - // Don't need to save Modifiers, they are recalculated every frame anyway. - for (Collection::const_iterator iter (begin()); iter!=end(); ++iter) + for (const auto& [key, params] : mCollection) { - if (iter->second.getBase() != 0) + if (params.getBase() != 0 || params.getModifier() != 0.f) { // Don't worry about mArg, never used by magic effect script instructions - state.mEffects.insert(std::make_pair(iter->first.mId, iter->second.getBase())); + state.mEffects[key.mId] = { params.getBase(), params.getModifier() }; } } } - void MagicEffects::readState(const ESM::MagicEffects &state) + void MagicEffects::readState(const ESM::MagicEffects& state) { - for (std::map::const_iterator it = state.mEffects.begin(); it != state.mEffects.end(); ++it) + for (const auto& [key, params] : state.mEffects) { - mCollection[EffectKey(it->first)].setBase(it->second); + mCollection[EffectKey(key)].setBase(params.first); + mCollection[EffectKey(key)].setModifier(params.second); } } + + std::string getMagicEffectString( + const ESM::MagicEffect& effect, const ESM::Attribute* attribute, const ESM::Skill* skill) + { + const bool targetsSkill = effect.mData.mFlags & ESM::MagicEffect::TargetSkill && skill; + const bool targetsAttribute = effect.mData.mFlags & ESM::MagicEffect::TargetAttribute && attribute; + + std::string spellLine; + + auto windowManager = MWBase::Environment::get().getWindowManager(); + + if (targetsSkill || targetsAttribute) + { + switch (effect.mIndex) + { + case ESM::MagicEffect::AbsorbAttribute: + case ESM::MagicEffect::AbsorbSkill: + spellLine = windowManager->getGameSettingString("sAbsorb", {}); + break; + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::DamageSkill: + spellLine = windowManager->getGameSettingString("sDamage", {}); + break; + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DrainSkill: + spellLine = windowManager->getGameSettingString("sDrain", {}); + break; + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::FortifySkill: + spellLine = windowManager->getGameSettingString("sFortify", {}); + break; + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::RestoreSkill: + spellLine = windowManager->getGameSettingString("sRestore", {}); + break; + } + } + + if (spellLine.empty()) + { + auto& effectIDStr = ESM::MagicEffect::indexToGmstString(effect.mIndex); + spellLine = windowManager->getGameSettingString(effectIDStr, {}); + } + + if (targetsSkill) + { + spellLine += ' '; + spellLine += skill->mName; + } + else if (targetsAttribute) + { + spellLine += ' '; + spellLine += attribute->mName; + } + return spellLine; + } } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 12735a87f..e03f3db21 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -2,14 +2,17 @@ #define GAME_MWMECHANICS_MAGICEFFECTS_H #include +#include #include namespace ESM { + struct Attribute; struct ENAMstruct; struct EffectList; - + struct MagicEffect; struct MagicEffects; + struct Skill; } namespace MWMechanics @@ -21,12 +24,18 @@ namespace MWMechanics EffectKey(); - EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {} + EffectKey(int id, int arg = -1) + : mId(id) + , mArg(arg) + { + } - EffectKey (const ESM::ENAMstruct& effect); + EffectKey(const ESM::ENAMstruct& effect); + + std::string toString() const; }; - bool operator< (const EffectKey& left, const EffectKey& right); + bool operator<(const EffectKey& left, const EffectKey& right); struct EffectParam { @@ -50,71 +59,64 @@ namespace MWMechanics EffectParam(); - EffectParam(float magnitude) : mModifier(magnitude), mBase(0) {} + EffectParam(float magnitude) + : mModifier(magnitude) + , mBase(0) + { + } - EffectParam& operator+= (const EffectParam& param); + EffectParam& operator+=(const EffectParam& param); - EffectParam& operator-= (const EffectParam& param); + EffectParam& operator-=(const EffectParam& param); }; - inline EffectParam operator+ (const EffectParam& left, const EffectParam& right) + inline EffectParam operator+(const EffectParam& left, const EffectParam& right) { - EffectParam param (left); + EffectParam param(left); return param += right; } - inline EffectParam operator- (const EffectParam& left, const EffectParam& right) + inline EffectParam operator-(const EffectParam& left, const EffectParam& right) { - EffectParam param (left); + EffectParam param(left); return param -= right; } - // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display - struct EffectSourceVisitor - { - virtual ~EffectSourceVisitor() { } - - virtual void visit (EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) = 0; - }; - /// \brief Effects currently affecting a NPC or creature class MagicEffects { - public: + public: + typedef std::map Collection; - typedef std::map Collection; + private: + Collection mCollection; - private: + public: + Collection::const_iterator begin() const { return mCollection.begin(); } - Collection mCollection; + Collection::const_iterator end() const { return mCollection.end(); } - public: + void readState(const ESM::MagicEffects& state); + void writeState(ESM::MagicEffects& state) const; - Collection::const_iterator begin() const { return mCollection.begin(); } + void add(const EffectKey& key, const EffectParam& param); + void remove(const EffectKey& key); - Collection::const_iterator end() const { return mCollection.end(); } + void modifyBase(const EffectKey& key, int diff); - void readState (const ESM::MagicEffects& state); - void writeState (ESM::MagicEffects& state) const; + /// Copy Modifier values from \a effects, but keep original mBase values. + void setModifiers(const MagicEffects& effects); - void add (const EffectKey& key, const EffectParam& param); - void remove (const EffectKey& key); + EffectParam getOrDefault(const EffectKey& key) const; + std::optional get(const EffectKey& key) const; + ///< This function can safely be used for keys that are not present. - void modifyBase (const EffectKey& key, int diff); - - /// Copy Modifier values from \a effects, but keep original mBase values. - void setModifiers(const MagicEffects& effects); - - MagicEffects& operator+= (const MagicEffects& effects); - - EffectParam get (const EffectKey& key) const; - ///< This function can safely be used for keys that are not present. - - static MagicEffects diff (const MagicEffects& prev, const MagicEffects& now); - ///< Return changes from \a prev to \a now. + static MagicEffects diff(const MagicEffects& prev, const MagicEffects& now); + ///< Return changes from \a prev to \a now. }; + + std::string getMagicEffectString( + const ESM::MagicEffect& effect, const ESM::Attribute* attribute, const ESM::Skill* skill); } #endif diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9260d8389..f91c9c1e4 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -4,13 +4,16 @@ #include -#include -#include - -#include +#include +#include +#include +#include +#include +#include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -27,41 +30,53 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/globals.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" #include "../mwworld/ptr.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/world.hpp" +#include "actor.hpp" +#include "actorutil.hpp" #include "aicombat.hpp" #include "aipursue.hpp" -#include "spellutil.hpp" #include "autocalcspell.hpp" -#include "npcstats.hpp" -#include "actorutil.hpp" #include "combat.hpp" +#include "npcstats.hpp" +#include "spellutil.hpp" namespace { float getFightDispositionBias(float disposition) { - static const float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fFightDispMult")->mValue.getFloat(); - return ((50.f - disposition) * fFightDispMult); + static const float fFightDispMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fFightDispMult") + ->mValue.getFloat(); + return ((50.f - disposition) * fFightDispMult); } - void getPersuasionRatings(const MWMechanics::NpcStats& stats, float& rating1, float& rating2, float& rating3, bool player) + void getPersuasionRatings( + const MWMechanics::NpcStats& stats, float& rating1, float& rating2, float& rating3, bool player) { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); - float persTerm = stats.getAttribute(ESM::Attribute::Personality).getModified() / gmst.find("fPersonalityMod")->mValue.getFloat(); - float luckTerm = stats.getAttribute(ESM::Attribute::Luck).getModified() / gmst.find("fLuckMod")->mValue.getFloat(); + float persTerm = stats.getAttribute(ESM::Attribute::Personality).getModified() + / gmst.find("fPersonalityMod")->mValue.getFloat(); + float luckTerm + = stats.getAttribute(ESM::Attribute::Luck).getModified() / gmst.find("fLuckMod")->mValue.getFloat(); float repTerm = stats.getReputation() * gmst.find("fReputationMod")->mValue.getFloat(); float fatigueTerm = stats.getFatigueTerm(); float levelTerm = stats.getLevel() * gmst.find("fLevelMod")->mValue.getFloat(); @@ -75,11 +90,43 @@ namespace } else { - rating2 = (levelTerm + repTerm + luckTerm + persTerm + stats.getSkill(ESM::Skill::Speechcraft).getModified()) * fatigueTerm; - rating3 = (stats.getSkill(ESM::Skill::Mercantile).getModified() + repTerm + luckTerm + persTerm) * fatigueTerm; + rating2 + = (levelTerm + repTerm + luckTerm + persTerm + stats.getSkill(ESM::Skill::Speechcraft).getModified()) + * fatigueTerm; + rating3 + = (stats.getSkill(ESM::Skill::Mercantile).getModified() + repTerm + luckTerm + persTerm) * fatigueTerm; } } + bool isOwned(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) + { + const MWWorld::CellRef& cellref = target.getCellRef(); + + const ESM::RefId& owner = cellref.getOwner(); + bool isOwned = !owner.empty() && owner != ESM::RefId::stringRefId("Player"); + + const ESM::RefId& faction = cellref.getFaction(); + bool isFactionOwned = false; + if (!faction.empty() && ptr.getClass().isNpc()) + { + const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); + auto found = factions.find(faction); + if (found == factions.end() || found->second < cellref.getFactionRank()) + isFactionOwned = true; + } + + const std::string& globalVariable = cellref.getGlobalVariable(); + if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(globalVariable)) + { + isOwned = false; + isFactionOwned = false; + } + + if (!cellref.getOwner().empty()) + victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true, false); + + return isOwned || isFactionOwned; + } } namespace MWMechanics @@ -88,20 +135,20 @@ namespace MWMechanics { MWWorld::Ptr ptr = getPlayer(); - MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr); + MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); - npcStats.setNeedRecalcDynamicStats(true); + npcStats.recalculateMagicka(); - const ESM::NPC *player = ptr.get()->mBase; + const ESM::NPC* player = ptr.get()->mBase; // reset creatureStats.setLevel(player->mNpdt.mLevel); creatureStats.getSpells().clear(true); - creatureStats.modifyMagicEffects(MagicEffects()); + creatureStats.getActiveSpells().clear(ptr); - for (int i=0; i<27; ++i) - npcStats.getSkill (i).setBase (player->mNpdt.mSkills[i]); + for (size_t i = 0; i < player->mNpdt.mSkills.size(); ++i) + npcStats.getSkill(ESM::Skill::indexToRefId(i)).setBase(player->mNpdt.mSkills[i]); creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt.mStrength); creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt.mIntelligence); @@ -111,54 +158,48 @@ namespace MWMechanics creatureStats.setAttribute(ESM::Attribute::Endurance, player->mNpdt.mEndurance); creatureStats.setAttribute(ESM::Attribute::Personality, player->mNpdt.mPersonality); creatureStats.setAttribute(ESM::Attribute::Luck, player->mNpdt.mLuck); - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); // race if (mRaceSelected) { - const ESM::Race *race = - esmStore.get().find(player->mRace); + const ESM::Race* race = esmStore.get().find(player->mRace); bool male = (player->mFlags & ESM::NPC::Female) == 0; - for (int i=0; i<8; ++i) + for (const ESM::Attribute& attribute : esmStore.get()) { - const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i]; + const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[attribute.mId]; - creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); + creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); } - for (int i=0; i<27; ++i) + for (const ESM::Skill& skill : esmStore.get()) { int bonus = 0; - for (int i2=0; i2<7; ++i2) - if (race->mData.mBonus[i2].mSkill==i) - { - bonus = race->mData.mBonus[i2].mBonus; - break; - } + auto bonusIt = std::find_if(race->mData.mBonus.begin(), race->mData.mBonus.end(), + [&](const auto& bonus) { return bonus.mSkill == skill.mIndex; }); + if (bonusIt != race->mData.mBonus.end()) + bonus = bonusIt->mBonus; - npcStats.getSkill (i).setBase (5 + bonus); + npcStats.getSkill(skill.mId).setBase(5 + bonus); } - for (const std::string &power : race->mPowers.mList) + for (const ESM::RefId& power : race->mPowers.mList) { creatureStats.getSpells().add(power); } } // birthsign - const std::string &signId = - MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); + const ESM::RefId& signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); if (!signId.empty()) { - const ESM::BirthSign *sign = - esmStore.get().find(signId); + const ESM::BirthSign* sign = esmStore.get().find(signId); - for (const std::string &power : sign->mPowers.mList) + for (const ESM::RefId& power : sign->mPowers.mList) { creatureStats.getSpells().add(power); } @@ -167,51 +208,33 @@ namespace MWMechanics // class if (mClassSelected) { - const ESM::Class *class_ = - esmStore.get().find(player->mClass); + const ESM::Class* class_ = esmStore.get().find(player->mClass); - for (int i=0; i<2; ++i) + for (int attribute : class_->mData.mAttribute) { - int attribute = class_->mData.mAttribute[i]; - if (attribute>=0 && attribute<8) + if (attribute >= 0 && attribute < ESM::Attribute::Length) { - creatureStats.setAttribute(attribute, - creatureStats.getAttribute(attribute).getBase() + 10); + auto id = static_cast(attribute); + creatureStats.setAttribute(id, creatureStats.getAttribute(id).getBase() + 10); } } - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { - int bonus = i==0 ? 10 : 25; + int bonus = i == 0 ? 10 : 25; - for (int i2=0; i2<5; ++i2) + for (const auto& skills : class_->mData.mSkills) { - int index = class_->mData.mSkills[i2][i]; - - if (index>=0 && index<27) - { - npcStats.getSkill (index).setBase ( - npcStats.getSkill (index).getBase() + bonus); - } + ESM::RefId id = ESM::Skill::indexToRefId(skills[i]); + if (!id.empty()) + npcStats.getSkill(id).setBase(npcStats.getSkill(id).getBase() + bonus); } } - const MWWorld::Store &skills = - esmStore.get(); - - MWWorld::Store::iterator iter = skills.begin(); - for (; iter != skills.end(); ++iter) + for (const ESM::Skill& skill : esmStore.get()) { - if (iter->second.mData.mSpecialization==class_->mData.mSpecialization) - { - int index = iter->first; - - if (index>=0 && index<27) - { - npcStats.getSkill (index).setBase ( - npcStats.getSkill (index).getBase() + 5); - } - } + if (skill.mData.mSpecialization == class_->mData.mSpecialization) + npcStats.getSkill(skill.mId).setBase(npcStats.getSkill(skill.mId).getBase() + 5); } } @@ -220,113 +243,88 @@ namespace MWMechanics if (mRaceSelected) race = esmStore.get().find(player->mRace); - int skills[ESM::Skill::Length]; - for (int i=0; i selectedSpells + = autoCalcPlayerSpells(npcStats.getSkills(), npcStats.getAttributes(), race); - std::vector selectedSpells = autoCalcPlayerSpells(skills, attributes, race); - - for (const std::string &spell : selectedSpells) + for (const ESM::RefId& spell : selectedSpells) creatureStats.getSpells().add(spell); // forced update and current value adjustments - mActors.updateActor (ptr, 0); + mActors.updateActor(ptr, 0); - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) { - DynamicStat stat = creatureStats.getDynamic (i); - stat.setCurrent (stat.getModified()); - creatureStats.setDynamic (i, stat); + DynamicStat stat = creatureStats.getDynamic(i); + stat.setCurrent(stat.getModified()); + creatureStats.setDynamic(i, stat); } - // auto-equip again. we need this for when the race is changed to a beast race and shoes are no longer equippable + // auto-equip again. we need this for when the race is changed to a beast race and shoes are no longer + // equippable MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); - for (int i=0; igetWatchedActor()) + if (ptr == MWBase::Environment::get().getWindowManager()->getWatchedActor()) MWBase::Environment::get().getWindowManager()->watchActor(MWWorld::Ptr()); - mActors.removeActor(ptr); + mActors.removeActor(ptr, keepActive); mObjects.removeObject(ptr); } - void MechanicsManager::updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) + void MechanicsManager::updateCell(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) { - if(old == MWBase::Environment::get().getWindowManager()->getWatchedActor()) + if (old == MWBase::Environment::get().getWindowManager()->getWatchedActor()) MWBase::Environment::get().getWindowManager()->watchActor(ptr); - if(ptr.getClass().isActor()) + if (ptr.getClass().isActor()) mActors.updateActor(old, ptr); else mObjects.updateObject(old, ptr); } - void MechanicsManager::drop(const MWWorld::CellStore *cellStore) + void MechanicsManager::drop(const MWWorld::CellStore* cellStore) { mActors.dropActors(cellStore, getPlayer()); mObjects.dropObjects(cellStore); } - void MechanicsManager::restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) - { - auto& stats = actor.getClass().getCreatureStats (actor); - auto& corprusSpells = stats.getCorprusSpells(); - - auto corprusIt = corprusSpells.find(sourceId); - - if (corprusIt != corprusSpells.end()) - { - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - MWMechanics::AttributeValue attr = stats.getAttribute(i); - attr.restore(corprusIt->second.mWorsenings[i]); - actor.getClass().getCreatureStats(actor).setAttribute(i, attr); - } - } - } - void MechanicsManager::update(float duration, bool paused) { // Note: we should do it here since game mechanics and world updates use these values MWWorld::Ptr ptr = getPlayer(); - MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); - // Update the equipped weapon icon MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon == inv.end()) - winMgr->unsetSelectedWeapon(); - else - winMgr->setSelectedWeapon(*weapon); // Update the selected spell icon MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem(); @@ -334,20 +332,26 @@ namespace MWMechanics winMgr->setSelectedEnchantItem(*enchantItem); else { - const std::string& spell = winMgr->getSelectedSpell(); + const ESM::RefId& spell = winMgr->getSelectedSpell(); if (!spell.empty()) winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, ptr))); else winMgr->unsetSelectedSpell(); } + // Update the equipped weapon icon + if (weapon == inv.end()) + winMgr->unsetSelectedWeapon(); + else + winMgr->setSelectedWeapon(*weapon); + if (mUpdatePlayer) { mUpdatePlayer = false; // HACK? The player has been changed, so a new Animation object may // have been made for them. Make sure they're properly updated. - mActors.removeActor(ptr); + mActors.removeActor(ptr, true); mActors.addActor(ptr, true); } @@ -355,7 +359,7 @@ namespace MWMechanics mObjects.update(duration, paused); } - void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector &changed) + void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector& changed) { for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { @@ -365,8 +369,6 @@ namespace MWMechanics if (state != MWBase::StateManager::State_Running) continue; - mActors.updateProcessingRange(); - // Update mechanics for new processing range immediately update(0.f, false); } @@ -378,11 +380,6 @@ namespace MWMechanics mActors.notifyDied(actor); } - float MechanicsManager::getActorsProcessingRange() const - { - return mActors.getProcessingRange(); - } - bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) { return mActors.isActorDetected(actor, observer); @@ -416,7 +413,7 @@ namespace MWMechanics mActors.rest(hours, sleep); } - void MechanicsManager::restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) + void MechanicsManager::restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep) { mActors.restoreDynamicStats(actor, hours, sleep); } @@ -426,78 +423,75 @@ namespace MWMechanics return mActors.getHoursToRest(getPlayer()); } - void MechanicsManager::setPlayerName (const std::string& name) + void MechanicsManager::setPlayerName(const std::string& name) { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); - ESM::NPC player = - *world->getPlayerPtr().get()->mBase; + ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mName = name; - world->createRecord(player); + world->getStore().insert(player); mUpdatePlayer = true; } - void MechanicsManager::setPlayerRace (const std::string& race, bool male, const std::string &head, const std::string &hair) + void MechanicsManager::setPlayerRace( + const ESM::RefId& race, bool male, const ESM::RefId& head, const ESM::RefId& hair) { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); - ESM::NPC player = - *world->getPlayerPtr().get()->mBase; + ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mRace = race; player.mHead = head; player.mHair = hair; player.setIsMale(male); - world->createRecord(player); + world->getStore().insert(player); mRaceSelected = true; buildPlayer(); mUpdatePlayer = true; } - void MechanicsManager::setPlayerBirthsign (const std::string& id) + void MechanicsManager::setPlayerBirthsign(const ESM::RefId& id) { MWBase::Environment::get().getWorld()->getPlayer().setBirthSign(id); buildPlayer(); mUpdatePlayer = true; } - void MechanicsManager::setPlayerClass (const std::string& id) + void MechanicsManager::setPlayerClass(const ESM::RefId& id) { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); - ESM::NPC player = - *world->getPlayerPtr().get()->mBase; + ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mClass = id; - world->createRecord(player); + world->getStore().insert(player); mClassSelected = true; buildPlayer(); mUpdatePlayer = true; } - void MechanicsManager::setPlayerClass (const ESM::Class &cls) + void MechanicsManager::setPlayerClass(const ESM::Class& cls) { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); - const ESM::Class *ptr = world->createRecord(cls); + const ESM::Class* ptr = world->getStore().insert(cls); - ESM::NPC player = - *world->getPlayerPtr().get()->mBase; + ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mClass = ptr->mId; - world->createRecord(player); + world->getStore().insert(player); mClassSelected = true; buildPlayer(); mUpdatePlayer = true; } - int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange) + int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp) { const MWMechanics::NpcStats& npcSkill = ptr.getClass().getNpcStats(ptr); float x = static_cast(npcSkill.getBaseDisposition()); @@ -505,45 +499,47 @@ namespace MWMechanics MWWorld::LiveCellRef* npc = ptr.get(); MWWorld::Ptr playerPtr = getPlayer(); MWWorld::LiveCellRef* player = playerPtr.get(); - const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); + const MWMechanics::NpcStats& playerStats = playerPtr.getClass().getNpcStats(playerPtr); - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fDispRaceMod = gmst.find("fDispRaceMod")->mValue.getFloat(); - if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace)) + if (npc->mBase->mRace == player->mBase->mRace) x += fDispRaceMod; static const float fDispPersonalityMult = gmst.find("fDispPersonalityMult")->mValue.getFloat(); static const float fDispPersonalityBase = gmst.find("fDispPersonalityBase")->mValue.getFloat(); - x += fDispPersonalityMult * (playerStats.getAttribute(ESM::Attribute::Personality).getModified() - fDispPersonalityBase); + x += fDispPersonalityMult + * (playerStats.getAttribute(ESM::Attribute::Personality).getModified() - fDispPersonalityBase); float reaction = 0; int rank = 0; - std::string npcFaction = ptr.getClass().getPrimaryFaction(ptr); - - Misc::StringUtils::lowerCaseInPlace(npcFaction); + const ESM::RefId& npcFaction = ptr.getClass().getPrimaryFaction(ptr); if (playerStats.getFactionRanks().find(npcFaction) != playerStats.getFactionRanks().end()) { if (!playerStats.getExpelled(npcFaction)) { // faction reaction towards itself. yes, that exists - reaction = static_cast(MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, npcFaction)); + reaction = static_cast( + MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, npcFaction)); rank = playerStats.getFactionRanks().find(npcFaction)->second; } } else if (!npcFaction.empty()) { - std::map::const_iterator playerFactionIt = playerStats.getFactionRanks().begin(); + auto playerFactionIt = playerStats.getFactionRanks().begin(); for (; playerFactionIt != playerStats.getFactionRanks().end(); ++playerFactionIt) { - std::string itFaction = playerFactionIt->first; + const ESM::RefId& 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); + int itReaction + = MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, itFaction); if (playerFactionIt == playerStats.getFactionRanks().begin() || itReaction < reaction) { reaction = static_cast(itReaction); @@ -560,9 +556,7 @@ namespace MWMechanics static const float fDispFactionRankMult = gmst.find("fDispFactionRankMult")->mValue.getFloat(); static const float fDispFactionRankBase = gmst.find("fDispFactionRankBase")->mValue.getFloat(); static const float fDispFactionMod = gmst.find("fDispFactionMod")->mValue.getFloat(); - x += (fDispFactionRankMult * rank - + fDispFactionRankBase) - * fDispFactionMod * reaction; + x += (fDispFactionRankMult * rank + fDispFactionRankBase) * fDispFactionMod * reaction; static const float fDispCrimeMod = gmst.find("fDispCrimeMod")->mValue.getFloat(); static const float fDispDiseaseMod = gmst.find("fDispDiseaseMod")->mValue.getFloat(); @@ -571,32 +565,34 @@ namespace MWMechanics x += fDispDiseaseMod; static const float fDispWeaponDrawn = gmst.find("fDispWeaponDrawn")->mValue.getFloat(); - if (playerStats.getDrawState() == MWMechanics::DrawState_Weapon) + if (playerStats.getDrawState() == MWMechanics::DrawState::Weapon) x += fDispWeaponDrawn; - x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).getMagnitude(); + x += ptr.getClass() + .getCreatureStats(ptr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Charm) + .getMagnitude(); - if(addTemporaryDispositionChange) - x += MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(); - - int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used - return effective_disposition; + if (clamp) + return std::clamp(x, 0, 100); //, normally clamped to [0..100] when used + return static_cast(x); } - int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) + int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr, int basePrice, bool buying) { // Make sure zero base price items/services can't be bought/sold for 1 gold // and return the intended base price for creature merchants - if (basePrice == 0 || ptr.getTypeName() == typeid(ESM::Creature).name()) + if (basePrice == 0 || ptr.getType() == ESM::Creature::sRecordId) return basePrice; - const MWMechanics::NpcStats &sellerStats = ptr.getClass().getNpcStats(ptr); + const MWMechanics::NpcStats& sellerStats = ptr.getClass().getNpcStats(ptr); MWWorld::Ptr playerPtr = getPlayer(); - const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); + const MWMechanics::NpcStats& playerStats = playerPtr.getClass().getNpcStats(playerPtr); - // I suppose the temporary disposition change (second param to getDerivedDisposition()) _has_ to be considered here, - // otherwise one would get different prices when exiting and re-entering the dialogue window... + // I suppose the temporary disposition change (second param to getDerivedDisposition()) _has_ to be considered + // here, otherwise one would get different prices when exiting and re-entering the dialogue window... int clampedDisposition = getDerivedDisposition(ptr); float a = std::min(playerPtr.getClass().getSkill(playerPtr, ESM::Skill::Mercantile), 100.f); float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); @@ -612,11 +608,12 @@ namespace MWMechanics return std::max(1, offerPrice); } - int MechanicsManager::countDeaths (const std::string& id) const + int MechanicsManager::countDeaths(const ESM::RefId& id) const { - return mActors.countDeaths (id); + return mActors.countDeaths(id); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -631,14 +628,18 @@ namespace MWMechanics */ void MechanicsManager::getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange) +======= + void MechanicsManager::getPersuasionDispositionChange( + const MWWorld::Ptr& npc, PersuasionType type, bool& success, int& tempChange, int& permChange) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); MWWorld::Ptr playerPtr = getPlayer(); - const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); + const MWMechanics::NpcStats& playerStats = playerPtr.getClass().getNpcStats(playerPtr); float npcRating1, npcRating2, npcRating3; getPersuasionRatings(npcStats, npcRating1, npcRating2, npcRating3, false); @@ -646,28 +647,32 @@ namespace MWMechanics float playerRating1, playerRating2, playerRating3; getPersuasionRatings(playerStats, playerRating1, playerRating2, playerRating3, true); - int currentDisposition = getDerivedDisposition(npc); + const int currentDisposition = getDerivedDisposition(npc); float d = 1 - 0.02f * abs(currentDisposition - 50); float target1 = d * (playerRating1 - npcRating1 + 50); float target2 = d * (playerRating2 - npcRating2 + 50); float bribeMod; - if (type == PT_Bribe10) bribeMod = gmst.find("fBribe10Mod")->mValue.getFloat(); - else if (type == PT_Bribe100) bribeMod = gmst.find("fBribe100Mod")->mValue.getFloat(); - else bribeMod = gmst.find("fBribe1000Mod")->mValue.getFloat(); + if (type == PT_Bribe10) + bribeMod = gmst.find("fBribe10Mod")->mValue.getFloat(); + else if (type == PT_Bribe100) + bribeMod = gmst.find("fBribe100Mod")->mValue.getFloat(); + else + bribeMod = gmst.find("fBribe1000Mod")->mValue.getFloat(); float target3 = d * (playerRating3 - npcRating3 + 50) + bribeMod; - float iPerMinChance = floor(gmst.find("iPerMinChance")->mValue.getFloat()); - float iPerMinChange = floor(gmst.find("iPerMinChange")->mValue.getFloat()); - float fPerDieRollMult = gmst.find("fPerDieRollMult")->mValue.getFloat(); - float fPerTempMult = gmst.find("fPerTempMult")->mValue.getFloat(); + const float iPerMinChance = gmst.find("iPerMinChance")->mValue.getFloat(); + const float iPerMinChange = gmst.find("iPerMinChange")->mValue.getFloat(); + const float fPerDieRollMult = gmst.find("fPerDieRollMult")->mValue.getFloat(); + const float fPerTempMult = gmst.find("fPerTempMult")->mValue.getFloat(); float x = 0; float y = 0; - int roll = Misc::Rng::roll0to99(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); if (type == PT_Admire) { @@ -680,7 +685,7 @@ namespace MWMechanics { target2 = std::max(iPerMinChance, target2); - success = (roll <= target2); + success = (roll <= target2); float r; if (roll != target2) @@ -692,12 +697,12 @@ namespace MWMechanics { float s = floor(r * fPerDieRollMult * fPerTempMult); - int flee = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Flee).getBase(); - int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase(); - npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, - std::max(0, std::min(100, flee + int(std::max(iPerMinChange, s))))); - npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, - std::max(0, std::min(100, fight + int(std::min(-iPerMinChange, -s))))); + const int flee = npcStats.getAiSetting(MWMechanics::AiSetting::Flee).getBase(); + const int fight = npcStats.getAiSetting(MWMechanics::AiSetting::Fight).getBase(); + npcStats.setAiSetting( + MWMechanics::AiSetting::Flee, std::clamp(flee + int(std::max(iPerMinChange, s)), 0, 100)); + npcStats.setAiSetting( + MWMechanics::AiSetting::Fight, std::clamp(fight + int(std::min(-iPerMinChange, -s)), 0, 100)); } float c = -std::abs(floor(r * fPerDieRollMult)); @@ -733,12 +738,12 @@ namespace MWMechanics if (success) { float s = c * fPerDieRollMult * fPerTempMult; - int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase(); - int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase(); - npcStats.setAiSetting (CreatureStats::AI_Flee, - std::max(0, std::min(100, flee + std::min(-int(iPerMinChange), int(-s))))); - npcStats.setAiSetting (CreatureStats::AI_Fight, - std::max(0, std::min(100, fight + std::max(int(iPerMinChange), int(s))))); + const int flee = npcStats.getAiSetting(AiSetting::Flee).getBase(); + const int fight = npcStats.getAiSetting(AiSetting::Fight).getBase(); + npcStats.setAiSetting( + AiSetting::Flee, std::clamp(flee + std::min(-int(iPerMinChange), int(-s)), 0, 100)); + npcStats.setAiSetting( + AiSetting::Fight, std::clamp(fight + std::max(int(iPerMinChange), int(s)), 0, 100)); } x = floor(-c * fPerDieRollMult); @@ -754,45 +759,50 @@ namespace MWMechanics x = success ? std::max(iPerMinChange, c) : c; } - tempChange = type == PT_Intimidate ? x : int(x * fPerTempMult); - - - float cappedDispositionChange = tempChange; - if (currentDisposition + tempChange > 100.f) - cappedDispositionChange = static_cast(100 - currentDisposition); - if (currentDisposition + tempChange < 0.f) - cappedDispositionChange = static_cast(-currentDisposition); - - permChange = floor(cappedDispositionChange / fPerTempMult); if (type == PT_Intimidate) { - permChange = success ? -int(cappedDispositionChange/ fPerTempMult) : y; + tempChange = int(x); + if (currentDisposition + tempChange > 100) + tempChange = 100 - currentDisposition; + else if (currentDisposition + tempChange < 0) + tempChange = -currentDisposition; + permChange = success ? -int(tempChange / fPerTempMult) : int(y); + } + else + { + tempChange = int(x * fPerTempMult); + if (currentDisposition + tempChange > 100) + tempChange = 100 - currentDisposition; + else if (currentDisposition + tempChange < 0) + tempChange = -currentDisposition; + permChange = int(tempChange / fPerTempMult); } } - void MechanicsManager::forceStateUpdate(const MWWorld::Ptr &ptr) + void MechanicsManager::forceStateUpdate(const MWWorld::Ptr& ptr) { - if(ptr.getClass().isActor()) + if (ptr.getClass().isActor()) mActors.forceStateUpdate(ptr); } - bool MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) + bool MechanicsManager::playAnimationGroup( + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) { - if(ptr.getClass().isActor()) + if (ptr.getClass().isActor()) return mActors.playAnimationGroup(ptr, groupName, mode, number, persist); else return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { - if(ptr.getClass().isActor()) + if (ptr.getClass().isActor()) mActors.skipAnimation(ptr); else mObjects.skipAnimation(ptr); } - bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName) + bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) { - if(ptr.getClass().isActor()) + if (ptr.getClass().isActor()) return mActors.checkAnimationPlaying(ptr, groupName); else return false; @@ -800,7 +810,7 @@ namespace MWMechanics bool MechanicsManager::onOpen(const MWWorld::Ptr& ptr) { - if(ptr.getClass().isActor()) + if (ptr.getClass().isActor()) return true; else return mObjects.onOpen(ptr); @@ -808,7 +818,7 @@ namespace MWMechanics void MechanicsManager::onClose(const MWWorld::Ptr& ptr) { - if(!ptr.getClass().isActor()) + if (!ptr.getClass().isActor()) mObjects.onClose(ptr); } @@ -818,7 +828,7 @@ namespace MWMechanics mObjects.persistAnimationStates(); } - void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr) + void MechanicsManager::updateMagicEffects(const MWWorld::Ptr& ptr) { mActors.updateMagicEffects(ptr); } @@ -826,12 +836,6 @@ namespace MWMechanics bool MechanicsManager::toggleAI() { mAI = !mAI; - - MWBase::World* world = MWBase::Environment::get().getWorld(); - world->getNavigator()->setUpdatesEnabled(mAI); - if (mAI) - world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); - return mAI; } @@ -867,36 +871,39 @@ namespace MWMechanics bool MechanicsManager::isBoundItem(const MWWorld::Ptr& item) { +<<<<<<< HEAD /* End of tes3mp change (major) */ // 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 +======= + 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 +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (boundItemIDCache.empty()) { // Build a list of known bound item ID's - const MWWorld::Store &gameSettings = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gameSettings + = MWBase::Environment::get().getESMStore()->get(); - for (const ESM::GameSetting ¤tSetting : gameSettings) + for (const ESM::GameSetting& currentSetting : gameSettings) { - 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) + if (!currentSetting.mId.startsWith("smagicbound")) continue; // All sMagicBound* GMST's should be of type string std::string currentGMSTValue = currentSetting.mValue.getString(); Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); - boundItemIDCache.insert(currentGMSTValue); + boundItemIDCache.insert(ESM::RefId::stringRefId(currentGMSTValue)); } } // Perform bound item check and assign the Flag_Bound bit if it passes - std::string tempItemID = item.getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(tempItemID); + const ESM::RefId& tempItemID = item.getCellRef().getRefId(); if (boundItemIDCache.count(tempItemID) != 0) return true; @@ -904,6 +911,7 @@ namespace MWMechanics return false; } +<<<<<<< HEAD /* Start of tes3mp addition @@ -923,16 +931,16 @@ namespace MWMechanics */ bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) +======= + bool MechanicsManager::isAllowedToUse(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { if (target.isEmpty()) return true; const MWWorld::CellRef& cellref = target.getCellRef(); // there is no harm to use unlocked doors - int lockLevel = cellref.getLockLevel(); - if (target.getClass().isDoor() && - (lockLevel <= 0 || lockLevel == ESM::UnbreakableLock) && - ptr.getCellRef().getTrap().empty()) + if (target.getClass().isDoor() && !cellref.isLocked() && cellref.getTrap().empty()) { return true; } @@ -941,7 +949,7 @@ namespace MWMechanics return true; // TODO: implement a better check to check if target is owned bed - if (target.getClass().isActivator() && target.getClass().getScript(target).compare(0, 3, "Bed") != 0) + if (target.getClass().isActivator() && !target.getClass().getScript(target).startsWith("Bed")) return true; if (target.getClass().isNpc()) @@ -959,38 +967,14 @@ namespace MWMechanics return true; } - const std::string& owner = cellref.getOwner(); - bool isOwned = !owner.empty() && owner != "player"; - - const std::string& faction = cellref.getFaction(); - bool isFactionOwned = false; - if (!faction.empty() && ptr.getClass().isNpc()) - { - const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); - std::map::const_iterator found = factions.find(Misc::StringUtils::lowerCase(faction)); - if (found == factions.end() - || found->second < cellref.getFactionRank()) - isFactionOwned = true; - } - - const std::string& globalVariable = cellref.getGlobalVariable(); - if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(Misc::StringUtils::lowerCase(globalVariable)) == 1) - { - isOwned = false; - isFactionOwned = false; - } - - if (!cellref.getOwner().empty()) - victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true, false); - - // A special case for evidence chest - we should not allow to take items even if it is technically permitted - if (Misc::StringUtils::ciEqual(cellref.getRefId(), "stolen_goods")) + if (isOwned(ptr, target, victim)) return false; - return (!isOwned && !isFactionOwned); + // A special case for evidence chest - we should not allow to take items even if it is technically permitted + return !(cellref.getRefId() == "stolen_goods"); } - bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed) + bool MechanicsManager::sleepInBed(const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) { if (ptr.getClass().getNpcStats(ptr).isWerewolf()) { @@ -998,7 +982,8 @@ namespace MWMechanics return true; } - if(MWBase::Environment::get().getWorld()->getPlayer().enemiesNearby()) { + if (MWBase::Environment::get().getWorld()->getPlayer().enemiesNearby()) + { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); return true; } @@ -1007,7 +992,7 @@ namespace MWMechanics if (isAllowedToUse(ptr, bed, victim)) return false; - if(commitCrime(ptr, victim, OT_SleepingInOwnedBed, bed.getCellRef().getFaction())) + if (commitCrime(ptr, victim, OT_SleepingInOwnedBed, bed.getCellRef().getFaction())) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage64}"); return true; @@ -1016,18 +1001,23 @@ namespace MWMechanics return false; } - void MechanicsManager::unlockAttempted(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item) + void MechanicsManager::unlockAttempted(const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) { MWWorld::Ptr victim; - if (isAllowedToUse(ptr, item, victim)) - return; - commitCrime(ptr, victim, OT_Trespassing, item.getCellRef().getFaction()); + if (isOwned(ptr, item, victim)) + { + // Note that attempting to unlock something that has ever been locked is a crime even if it's already + // unlocked. Likewise, it's illegal to unlock something that has a trap but isn't otherwise locked. + const auto& cellref = item.getCellRef(); + if (cellref.getLockLevel() || cellref.isLocked() || !cellref.getTrap().empty()) + commitCrime(ptr, victim, OT_Trespassing, item.getCellRef().getFaction()); + } } - std::vector > MechanicsManager::getStolenItemOwners(const std::string& itemid) + std::vector> MechanicsManager::getStolenItemOwners(const ESM::RefId& itemid) { - std::vector > result; - StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid)); + std::vector> result; + StolenItemsMap::const_iterator it = mStolenItems.find(itemid); if (it == mStolenItems.end()) return result; else @@ -1039,34 +1029,35 @@ namespace MWMechanics } } - bool MechanicsManager::isItemStolenFrom(const std::string &itemid, const MWWorld::Ptr& ptr) + bool MechanicsManager::isItemStolenFrom(const ESM::RefId& itemid, const MWWorld::Ptr& ptr) { - StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid)); + StolenItemsMap::const_iterator it = mStolenItems.find(itemid); if (it == mStolenItems.end()) return false; const OwnerMap& owners = it->second; - const std::string ownerid = ptr.getCellRef().getRefId(); - OwnerMap::const_iterator ownerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); + const ESM::RefId& ownerid = ptr.getCellRef().getRefId(); + OwnerMap::const_iterator ownerFound = owners.find(std::make_pair(ownerid, false)); if (ownerFound != owners.end()) return true; - const std::string factionid = ptr.getClass().getPrimaryFaction(ptr); + const ESM::RefId& factionid = ptr.getClass().getPrimaryFaction(ptr); if (!factionid.empty()) { - OwnerMap::const_iterator factionOwnerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(factionid), true)); + OwnerMap::const_iterator factionOwnerFound = owners.find(std::make_pair(factionid, true)); return factionOwnerFound != owners.end(); } return false; } - void MechanicsManager::confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) + void MechanicsManager::confiscateStolenItemToOwner( + const MWWorld::Ptr& player, const MWWorld::Ptr& item, const MWWorld::Ptr& victim, int count) { if (player != getPlayer()) return; - const std::string itemId = Misc::StringUtils::lowerCase(item.getCellRef().getRefId()); + const ESM::RefId& itemId = item.getCellRef().getRefId(); StolenItemsMap::iterator stolenIt = mStolenItems.find(itemId); if (stolenIt == mStolenItems.end()) @@ -1076,15 +1067,13 @@ namespace MWMechanics owner.first = victim.getCellRef().getRefId(); owner.second = false; - const std::string victimFaction = victim.getClass().getPrimaryFaction(victim); - if (!victimFaction.empty() && Misc::StringUtils::ciEqual(item.getCellRef().getFaction(), victimFaction)) // Is the item faction-owned? + const ESM::RefId& victimFaction = victim.getClass().getPrimaryFaction(victim); + if (!victimFaction.empty() && item.getCellRef().getFaction() == victimFaction) // Is the item faction-owned? { owner.first = victimFaction; owner.second = true; } - Misc::StringUtils::lowerCaseInPlace(owner.first); - // decrease count of stolen items int toRemove = std::min(count, mStolenItems[itemId][owner]); mStolenItems[itemId][owner] -= toRemove; @@ -1100,18 +1089,19 @@ namespace MWMechanics MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); // move items from player to owner and report about theft - victim.getClass().getContainerStore(victim).add(item, toRemove, victim); - store.remove(item, toRemove, player); - commitCrime(player, victim, OT_Theft, item.getCellRef().getFaction(), item.getClass().getValue(item) * toRemove); + victim.getClass().getContainerStore(victim).add(item, toRemove); + store.remove(item, toRemove); + commitCrime( + player, victim, OT_Theft, item.getCellRef().getFaction(), item.getClass().getValue(item) * toRemove); } - void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer) + void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& containerStore = targetContainer.getClass().getContainerStore(targetContainer); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId())); + StolenItemsMap::iterator stolenIt = mStolenItems.find(it->getCellRef().getRefId()); if (stolenIt == mStolenItems.end()) continue; OwnerMap& owners = stolenIt->second; @@ -1129,15 +1119,15 @@ namespace MWMechanics int toMove = it->getRefData().getCount() - itemCount; - containerStore.add(*it, toMove, targetContainer); - store.remove(*it, toMove, player); + containerStore.add(*it, toMove); + store.remove(*it, toMove); } // TODO: unhardcode the locklevel targetContainer.getCellRef().lock(50); } - void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, const MWWorld::Ptr& container, - int count, bool alarm) + void MechanicsManager::itemTaken( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, int count, bool alarm) { if (ptr != getPlayer()) return; @@ -1155,11 +1145,6 @@ namespace MWMechanics else { isAllowed = isAllowedToUse(ptr, item, victim); - if (!item.getCellRef().hasContentFile()) - { - // this is a manually placed item, which means it was already stolen - return; - } } if (isAllowed) @@ -1182,19 +1167,19 @@ namespace MWMechanics } } - Misc::StringUtils::lowerCaseInPlace(owner.first); - - if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) + if (!(item.getCellRef().getRefId() == MWWorld::ContainerStore::sGoldId)) { - if (victim.isEmpty() || (victim.getClass().isActor() && victim.getRefData().getCount() > 0 && !victim.getClass().getCreatureStats(victim).isDead())) - mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; + if (victim.isEmpty() + || (victim.getClass().isActor() && victim.getRefData().getCount() > 0 + && !victim.getClass().getCreatureStats(victim).isDead())) + mStolenItems[item.getCellRef().getRefId()][owner] += count; } if (alarm) commitCrime(ptr, victim, OT_Theft, ownerCellRef->getFaction(), item.getClass().getValue(item) * count); } - - bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, const std::string& factionId, int arg, bool victimAware) + bool MechanicsManager::commitCrime(const MWWorld::Ptr& player, const MWWorld::Ptr& victim, OffenseType type, + const ESM::RefId& factionId, int arg, bool victimAware) { // NOTE: victim may be empty @@ -1202,17 +1187,20 @@ namespace MWMechanics if (player != getPlayer()) return false; + if (type == OT_Assault) + victimAware = true; + // Find all the actors within the alarm radius std::vector neighbors; - osg::Vec3f from (player.getRefData().getPosition().asVec3()); - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + osg::Vec3f from(player.getRefData().getPosition().asVec3()); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); float radius = esmStore.get().find("fAlarmRadius")->mValue.getFloat(); mActors.getObjectsInRange(from, radius, neighbors); // victim should be considered even beyond alarm radius - if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius*radius) + if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius * radius) neighbors.push_back(victim); // get the player's followers / allies (works recursively) that will not report crimes @@ -1221,16 +1209,17 @@ namespace MWMechanics // Did anyone see it? bool crimeSeen = false; - for (const MWWorld::Ptr &neighbor : neighbors) + for (const MWWorld::Ptr& neighbor : neighbors) { if (!canReportCrime(neighbor, victim, playerFollowers)) continue; if ((neighbor == victim && victimAware) - // Murder crime can be reported even if no one saw it (hearing is enough, I guess). - // TODO: Add mod support for stealth executions! - || (type == OT_Murder && neighbor != victim) - || (MWBase::Environment::get().getWorld()->getLOS(player, neighbor) && awarenessCheck(player, neighbor))) + // Murder crime can be reported even if no one saw it (hearing is enough, I guess). + // TODO: Add mod support for stealth executions! + || (type == OT_Murder && neighbor != victim) + || (MWBase::Environment::get().getWorld()->getLOS(player, neighbor) + && awarenessCheck(player, neighbor))) { /* Start of tes3mp addition @@ -1245,7 +1234,7 @@ namespace MWMechanics // NPC will complain about theft even if he will do nothing about it if (type == OT_Theft || type == OT_Pickpocket) - MWBase::Environment::get().getDialogueManager()->say(neighbor, "thief"); + MWBase::Environment::get().getDialogueManager()->say(neighbor, ESM::RefId::stringRefId("thief")); crimeSeen = true; } @@ -1258,18 +1247,19 @@ namespace MWMechanics bool reported = false; if (victim.getClass().isClass(victim, "guard") && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) - reported = reportCrime(player, victim, type, std::string(), arg); + reported = reportCrime(player, victim, type, ESM::RefId(), arg); if (!reported) - startCombat(victim, player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? + startCombat(victim, + player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? } return crimeSeen; } - bool MechanicsManager::canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set &playerFollowers) + bool MechanicsManager::canReportCrime( + const MWWorld::Ptr& actor, const MWWorld::Ptr& victim, std::set& playerFollowers) { - if (actor == getPlayer() - || !actor.getClass().isNpc() || actor.getClass().getCreatureStats(actor).isDead()) + if (actor == getPlayer() || !actor.getClass().isNpc() || actor.getClass().getCreatureStats(actor).isDead()) return false; if (actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(victim)) @@ -1289,9 +1279,11 @@ namespace MWMechanics return true; } - bool MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, const std::string& factionId, int arg) + bool MechanicsManager::reportCrime( + const MWWorld::Ptr& player, const MWWorld::Ptr& victim, OffenseType type, const ESM::RefId& factionId, int arg) { - const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& store + = MWBase::Environment::get().getESMStore()->get(); if (type == OT_Murder && !victim.isEmpty()) victim.getClass().getCreatureStats(victim).notifyMurder(); @@ -1329,15 +1321,15 @@ namespace MWMechanics // Make surrounding actors within alarm distance respond to the crime std::vector neighbors; - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); - osg::Vec3f from (player.getRefData().getPosition().asVec3()); + osg::Vec3f from(player.getRefData().getPosition().asVec3()); float radius = esmStore.get().find("fAlarmRadius")->mValue.getFloat(); mActors.getObjectsInRange(from, radius, neighbors); // victim should be considered even beyond alarm radius - if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius*radius) + if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius * radius) neighbors.push_back(victim); int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); @@ -1350,7 +1342,8 @@ namespace MWMechanics else if (type == OT_Pickpocket) { fight = esmStore.get().find("iFightPickpocket")->mValue.getInteger(); - fightVictim = esmStore.get().find("iFightPickpocket")->mValue.getInteger() * 4; // *4 according to research wiki + fightVictim = esmStore.get().find("iFightPickpocket")->mValue.getInteger() + * 4; // *4 according to research wiki } else if (type == OT_Assault) { @@ -1368,22 +1361,22 @@ namespace MWMechanics getActorsSidingWith(player, playerFollowers); // Tell everyone (including the original reporter) in alarm range - for (const MWWorld::Ptr &actor : neighbors) + for (const MWWorld::Ptr& actor : neighbors) { if (!canReportCrime(actor, victim, playerFollowers)) continue; // Will the witness report the crime? - if (actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) + if (actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Alarm).getBase() >= 100) { reported = true; if (type == OT_Trespassing) - MWBase::Environment::get().getDialogueManager()->say(actor, "intruder"); + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("intruder")); } } - for (const MWWorld::Ptr &actor : neighbors) + for (const MWWorld::Ptr& actor : neighbors) { if (!canReportCrime(actor, victim, playerFollowers)) continue; @@ -1397,7 +1390,7 @@ namespace MWMechanics // once the bounty has been paid. actor.getClass().getNpcStats(actor).setCrimeId(id); - if (!actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit()) { actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); } @@ -1406,7 +1399,8 @@ namespace MWMechanics { float dispTerm = (actor == victim) ? dispVictim : disp; - float alarmTerm = 0.01f * actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Alarm).getBase(); + float alarmTerm + = 0.01f * actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Alarm).getBase(); if (type == OT_Pickpocket && alarmTerm <= 0) alarmTerm = 1.0; @@ -1418,7 +1412,8 @@ namespace MWMechanics fightTerm += getFightDistanceBias(actor, player); fightTerm *= alarmTerm; - int observerFightRating = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Fight).getBase(); + const int observerFightRating + = actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Fight).getBase(); if (observerFightRating + fightTerm > 100) fightTerm = static_cast(100 - observerFightRating); fightTerm = std::max(0.f, fightTerm); @@ -1430,7 +1425,7 @@ namespace MWMechanics NpcStats& observerStats = actor.getClass().getNpcStats(actor); // Apply aggression value to the base Fight rating, so that the actor can continue fighting // after a Calm spell wears off - observerStats.setAiSetting(CreatureStats::AI_Fight, observerFightRating + static_cast(fightTerm)); + observerStats.setAiSetting(AiSetting::Fight, observerFightRating + static_cast(fightTerm)); observerStats.setBaseDisposition(observerStats.getBaseDisposition() + static_cast(dispTerm)); @@ -1446,16 +1441,15 @@ namespace MWMechanics if (reported) { - player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() - + arg); + player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() + arg); // If committing a crime against a faction member, expell from the faction if (!victim.isEmpty() && victim.getClass().isNpc()) { - std::string factionID = victim.getClass().getPrimaryFaction(victim); + const ESM::RefId& factionID = victim.getClass().getPrimaryFaction(victim); - const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); - if (playerRanks.find(Misc::StringUtils::lowerCase(factionID)) != playerRanks.end()) + const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); + if (playerRanks.find(factionID) != playerRanks.end()) { /* Start of tes3mp addition @@ -1472,20 +1466,20 @@ namespace MWMechanics } else if (!factionId.empty()) { - const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); - if (playerRanks.find(Misc::StringUtils::lowerCase(factionId)) != playerRanks.end()) + const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); + if (playerRanks.find(factionId) != playerRanks.end()) { player.getClass().getNpcStats(player).expell(factionId); } } if (type == OT_Assault && !victim.isEmpty() - && !victim.getClass().getCreatureStats(victim).getAiSequence().isInCombat(player) - && victim.getClass().isNpc()) + && !victim.getClass().getCreatureStats(victim).getAiSequence().isInCombat(player) + && victim.getClass().isNpc()) { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. - if (!victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit()) startCombat(victim, player); // Set the crime ID, which we will use to calm down participants @@ -1497,7 +1491,7 @@ namespace MWMechanics return reported; } - bool MechanicsManager::actorAttacked(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker) + bool MechanicsManager::actorAttacked(const MWWorld::Ptr& target, const MWWorld::Ptr& attacker) { const MWWorld::Ptr& player = getPlayer(); if (target == player || !attacker.getClass().isActor()) @@ -1560,7 +1554,7 @@ namespace MWMechanics End of tes3mp change (major) */ { - MWBase::Environment::get().getDialogueManager()->say(target, "hit"); + MWBase::Environment::get().getDialogueManager()->say(target, ESM::RefId::stringRefId("hit")); return false; } } @@ -1587,35 +1581,47 @@ namespace MWMechanics { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. - if (!target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!target.getClass().getCreatureStats(target).getAiSequence().isInPursuit()) { // If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player, // he will attack the player only if we will force him (e.g. via StartCombat console command) bool peaceful = false; - std::string script = target.getClass().getScript(target); - if (!script.empty() && target.getRefData().getLocals().hasVar(script, "onpchitme") && attacker == player) + const ESM::RefId& script = target.getClass().getScript(target); + if (!script.empty() && target.getRefData().getLocals().hasVar(script, "onpchitme") + && attacker == player) { - int fight = std::max(0, target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified()); + const int fight + = target.getClass().getCreatureStats(target).getAiSetting(AiSetting::Fight).getModified(); peaceful = (fight == 0); } if (!peaceful) + { startCombat(target, attacker); + // Force friendly actors into combat to prevent infighting between followers + std::set followersTarget; + getActorsSidingWith(target, followersTarget); + for (const auto& follower : followersTarget) + { + if (follower != attacker && follower != player) + startCombat(follower, attacker); + } + } } } return true; } - bool MechanicsManager::canCommitCrimeAgainst(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker) + bool MechanicsManager::canCommitCrimeAgainst(const MWWorld::Ptr& target, const MWWorld::Ptr& attacker) { const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) - && !isAggressive(target, attacker) && !seq.isEngagedWithActor() - && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue); + && !isAggressive(target, attacker) && !seq.isEngagedWithActor() + && !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit(); } - void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) + void MechanicsManager::actorKilled(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) { if (attacker.isEmpty() || victim.isEmpty()) return; @@ -1627,7 +1633,7 @@ namespace MWMechanics return; // TODO: implement animal rights const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); - const MWWorld::Ptr &player = getPlayer(); + const MWWorld::Ptr& player = getPlayer(); bool canCommit = attacker == player && canCommitCrimeAgainst(victim, attacker); // For now we report only about crimes of player and player's followers @@ -1648,19 +1654,16 @@ namespace MWMechanics commitCrime(player, victim, MWBase::MechanicsManager::OT_Murder); } - bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) + bool MechanicsManager::awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) { if (observer.getClass().getCreatureStats(observer).isDead() || !observer.getRefData().isEnabled()) return false; - const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& store + = MWBase::Environment::get().getESMStore()->get(); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - float invisibility = stats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude(); - if (invisibility > 0) - return false; - float sneakTerm = 0; if (isSneaking(ptr)) { @@ -1683,17 +1686,20 @@ namespace MWMechanics static float fSneakDistBase = store.find("fSneakDistanceBase")->mValue.getFloat(); static float fSneakDistMult = store.find("fSneakDistanceMultiplier")->mValue.getFloat(); - osg::Vec3f pos1 (ptr.getRefData().getPosition().asVec3()); - osg::Vec3f pos2 (observer.getRefData().getPosition().asVec3()); + osg::Vec3f pos1(ptr.getRefData().getPosition().asVec3()); + osg::Vec3f pos2(observer.getRefData().getPosition().asVec3()); float distTerm = fSneakDistBase + fSneakDistMult * (pos1 - pos2).length(); - float chameleon = stats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); - float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility; + float chameleon = stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude(); + float invisibility = stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude(); + float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon; + if (invisibility > 0.f) + x += 100.f; CreatureStats& observerStats = observer.getClass().getCreatureStats(observer); float obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified(); float obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified(); - float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude(); + float obsBlind = observerStats.getMagicEffects().getOrDefault(ESM::MagicEffect::Blind).getMagnitude(); float obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak); float obsTerm = obsSneak + 0.2f * obsAgility + 0.1f * obsLuck - obsBlind; @@ -1705,7 +1711,7 @@ namespace MWMechanics osg::Vec3f vec = pos1 - pos2; if (observer.getRefData().getBaseNode()) { - osg::Vec3f observerDir = (observer.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0)); + osg::Vec3f observerDir = (observer.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0, 1, 0)); float angleRadians = std::acos(observerDir * vec / (observerDir.length() * vec.length())); if (angleRadians > osg::DegreesToRadians(90.f)) @@ -1715,11 +1721,11 @@ namespace MWMechanics } float target = x - y; - - return (Misc::Rng::roll0to99() >= target); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + return (Misc::Rng::roll0to99(prng) >= target); } - void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) + void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); @@ -1732,7 +1738,7 @@ namespace MWMechanics { // We don't care about dialogue filters since the target is invalid. // We still want to play the combat taunt. - MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); + MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); return; } @@ -1742,17 +1748,27 @@ namespace MWMechanics // if guard starts combat with player, guards pursuing player should do the same if (ptr.getClass().isClass(ptr, "Guard")) { - stats.setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); // Stops guard from ending combat if player is unreachable - for (Actors::PtrActorMap::const_iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + stats.setHitAttemptActorId( + target.getClass() + .getCreatureStats(target) + .getActorId()); // Stops guard from ending combat if player is unreachable + for (const Actor& actor : mActors) { - if (iter->first.getClass().isClass(iter->first, "Guard")) + if (actor.getPtr().getClass().isClass(actor.getPtr(), "Guard")) { - MWMechanics::AiSequence& aiSeq = iter->first.getClass().getCreatureStats(iter->first).getAiSequence(); + MWMechanics::AiSequence& aiSeq + = actor.getPtr().getClass().getCreatureStats(actor.getPtr()).getAiSequence(); if (aiSeq.getTypeId() == MWMechanics::AiPackageTypeId::Pursue) { aiSeq.stopPursuit(); aiSeq.stack(MWMechanics::AiCombat(target), ptr); - iter->first.getClass().getCreatureStats(iter->first).setHitAttemptActorId(target.getClass().getCreatureStats(target).getActorId()); // Stops guard from ending combat if player is unreachable + actor.getPtr() + .getClass() + .getCreatureStats(actor.getPtr()) + .setHitAttemptActorId( + target.getClass() + .getCreatureStats(target) + .getActorId()); // Stops guard from ending combat if player is unreachable } } } @@ -1760,36 +1776,43 @@ namespace MWMechanics } // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly - MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); + MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); } - void MechanicsManager::getObjectsInRange(const osg::Vec3f &position, float radius, std::vector &objects) + void MechanicsManager::stopCombat(const MWWorld::Ptr& actor) + { + mActors.stopCombat(actor); + } + + void MechanicsManager::getObjectsInRange( + const osg::Vec3f& position, float radius, std::vector& objects) { mActors.getObjectsInRange(position, radius, objects); mObjects.getObjectsInRange(position, radius, objects); } - void MechanicsManager::getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) + void MechanicsManager::getActorsInRange( + const osg::Vec3f& position, float radius, std::vector& objects) { mActors.getObjectsInRange(position, radius, objects); } - bool MechanicsManager::isAnyActorInRange(const osg::Vec3f &position, float radius) + bool MechanicsManager::isAnyActorInRange(const osg::Vec3f& position, float radius) { return mActors.isAnyObjectInRange(position, radius); } - std::list MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) + std::vector MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) { return mActors.getActorsSidingWith(actor); } - std::list MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) + std::vector MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) { return mActors.getActorsFollowing(actor); } - std::list MechanicsManager::getActorsFollowingIndices(const MWWorld::Ptr& actor) + std::vector MechanicsManager::getActorsFollowingIndices(const MWWorld::Ptr& actor) { return mActors.getActorsFollowingIndices(actor); } @@ -1799,29 +1822,33 @@ namespace MWMechanics return mActors.getActorsFollowingByIndex(actor); } - std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { + std::vector MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) + { return mActors.getActorsFighting(actor); } - std::list MechanicsManager::getEnemiesNearby(const MWWorld::Ptr& actor) { + std::vector MechanicsManager::getEnemiesNearby(const MWWorld::Ptr& actor) + { return mActors.getEnemiesNearby(actor); } - void MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) { + void MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) + { mActors.getActorsFollowing(actor, out); } - void MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) { + void MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) + { mActors.getActorsSidingWith(actor, out); } int MechanicsManager::countSavedGameRecords() const { return 1 // Death counter - +1; // Stolen items + + 1; // Stolen items } - void MechanicsManager::write(ESM::ESMWriter &writer, Loading::Listener &listener) const + void MechanicsManager::write(ESM::ESMWriter& writer, Loading::Listener& listener) const { mActors.write(writer, listener); @@ -1832,7 +1859,7 @@ namespace MWMechanics writer.endRecord(ESM::REC_STLN); } - void MechanicsManager::readRecord(ESM::ESMReader &reader, uint32_t type) + void MechanicsManager::readRecord(ESM::ESMReader& reader, uint32_t type) { if (type == ESM::REC_STLN) { @@ -1852,28 +1879,42 @@ namespace MWMechanics mRaceSelected = false; } - bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) + 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)) + if ((ptr.getClass().isNpc() + && ptr.getClass() + .getCreatureStats(ptr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::CalmHumanoid) + .getMagnitude() + > 0) + || (!ptr.getClass().isNpc() + && ptr.getClass() + .getCreatureStats(ptr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::CalmCreature) + .getMagnitude() + > 0)) return false; int disposition = 50; if (ptr.getClass().isNpc()) - disposition = getDerivedDisposition(ptr, true); + disposition = getDerivedDisposition(ptr); - int fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() - + static_cast(getFightDistanceBias(ptr, target) + getFightDispositionBias(static_cast(disposition))); + int fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(AiSetting::Fight).getModified() + + static_cast( + getFightDistanceBias(ptr, target) + getFightDispositionBias(static_cast(disposition))); if (ptr.getClass().isNpc() && target.getClass().isNpc()) { - if (target.getClass().getNpcStats(target).isWerewolf() || - (target == getPlayer() && - MWBase::Environment::get().getWorld()->getGlobalInt("pcknownwerewolf"))) + if (target.getClass().getNpcStats(target).isWerewolf() + || (target == getPlayer() + && MWBase::Environment::get().getWorld()->getGlobalInt(MWWorld::Globals::sPCKnownWerewolf))) { - const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().find("iWerewolfFightMod"); + const ESM::GameSetting* iWerewolfFightMod + = MWBase::Environment::get().getESMStore()->get().find("iWerewolfFightMod"); fight += iWerewolfFightMod->mValue.getInteger(); } } @@ -1881,7 +1922,7 @@ namespace MWMechanics return (fight >= 100); } - void MechanicsManager::resurrect(const MWWorld::Ptr &ptr) + void MechanicsManager::resurrect(const MWWorld::Ptr& ptr) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (stats.isDead()) @@ -1891,17 +1932,17 @@ namespace MWMechanics } } - bool MechanicsManager::isCastingSpell(const MWWorld::Ptr &ptr) const + bool MechanicsManager::isCastingSpell(const MWWorld::Ptr& ptr) const { return mActors.isCastingSpell(ptr); } - bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const + bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr& ptr) const { return mActors.isReadyToBlock(ptr); } - bool MechanicsManager::isAttackingOrSpell(const MWWorld::Ptr &ptr) const + bool MechanicsManager::isAttackingOrSpell(const MWWorld::Ptr& ptr) const { return mActors.isAttackingOrSpell(ptr); } @@ -1929,50 +1970,52 @@ namespace MWMechanics MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); - if (actor == player->getPlayer()) - { - if (werewolf) - { - player->saveStats(); - player->setWerewolfStats(); - } - else - player->restoreStats(); - } - // Werewolfs can not cast spells, so we need to unset the prepared spell if there is one. - if (npcStats.getDrawState() == MWMechanics::DrawState_Spell) - npcStats.setDrawState(MWMechanics::DrawState_Nothing); + if (npcStats.getDrawState() == MWMechanics::DrawState::Spell) + npcStats.setDrawState(MWMechanics::DrawState::Nothing); npcStats.setWerewolf(werewolf); - MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); - if(werewolf) + if (werewolf) { - inv.unequipAll(actor); - inv.equip(MWWorld::InventoryStore::Slot_Robe, inv.ContainerStore::add("werewolfrobe", 1, actor), actor); + inv.unequipAll(); + inv.equip(MWWorld::InventoryStore::Slot_Robe, + inv.ContainerStore::add(ESM::RefId::stringRefId("werewolfrobe"), 1)); } else { - inv.unequipSlot(MWWorld::InventoryStore::Slot_Robe, actor); - inv.ContainerStore::remove("werewolfrobe", 1, actor); + inv.unequipSlot(MWWorld::InventoryStore::Slot_Robe); + inv.ContainerStore::remove(ESM::RefId::stringRefId("werewolfrobe"), 1); } - if(actor == player->getPlayer()) + if (actor == player->getPlayer()) { MWBase::Environment::get().getWorld()->reattachPlayerCamera(); // Update the GUI only when called on the player MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); + // Transforming removes all temporary effects + actor.getClass().getCreatureStats(actor).getActiveSpells().purge( + [](const auto& params) { + return params.getType() == ESM::ActiveSpells::Type_Consumable + || params.getType() == ESM::ActiveSpells::Type_Temporary; + }, + actor); + mActors.updateActor(actor, 0.f); + if (werewolf) { + player->saveStats(); + player->setWerewolfStats(); windowManager->forceHide(MWGui::GW_Inventory); windowManager->forceHide(MWGui::GW_Magic); } else { + player->restoreStats(); windowManager->unsetForceHide(MWGui::GW_Inventory); windowManager->unsetForceHide(MWGui::GW_Magic); } @@ -1981,8 +2024,10 @@ namespace MWMechanics // Witnesses of the player's transformation will make them a globally known werewolf std::vector neighbors; - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - getActorsInRange(actor.getRefData().getPosition().asVec3(), gmst.find("fAlarmRadius")->mValue.getFloat(), neighbors); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + getActorsInRange( + actor.getRefData().getPosition().asVec3(), gmst.find("fAlarmRadius")->mValue.getFloat(), neighbors); bool detected = false, reported = false; for (const MWWorld::Ptr& neighbor : neighbors) @@ -1993,7 +2038,11 @@ namespace MWMechanics if (MWBase::Environment::get().getWorld()->getLOS(neighbor, actor) && awarenessCheck(actor, neighbor)) { detected = true; - if (neighbor.getClass().getCreatureStats(neighbor).getAiSetting(MWMechanics::CreatureStats::AI_Alarm).getModified() > 0) + if (neighbor.getClass() + .getCreatureStats(neighbor) + .getAiSetting(MWMechanics::AiSetting::Alarm) + .getModified() + > 0) { reported = true; break; @@ -2004,26 +2053,26 @@ namespace MWMechanics if (detected) { windowManager->messageBox("#{sWerewolfAlarmMessage}"); - MWBase::Environment::get().getWorld()->setGlobalInt("pcknownwerewolf", 1); + MWBase::Environment::get().getWorld()->setGlobalInt(MWWorld::Globals::sPCKnownWerewolf, 1); if (reported) { - npcStats.setBounty(npcStats.getBounty()+ - gmst.find("iWereWolfBounty")->mValue.getInteger()); + npcStats.setBounty(npcStats.getBounty() + gmst.find("iWereWolfBounty")->mValue.getInteger()); } } } } - void MechanicsManager::applyWerewolfAcrobatics(const MWWorld::Ptr &actor) + void MechanicsManager::applyWerewolfAcrobatics(const MWWorld::Ptr& actor) { - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - MWMechanics::NpcStats &stats = actor.getClass().getNpcStats(actor); - - stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->mValue.getInteger()); + const ESM::Skill* acrobatics + = MWBase::Environment::get().getESMStore()->get().find(ESM::Skill::Acrobatics); + MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor); + auto& skill = stats.getSkill(acrobatics->mId); + skill.setModifier(acrobatics->mWerewolfValue - skill.getModified()); } - void MechanicsManager::cleanupSummonedCreature(const MWWorld::Ptr &caster, int creatureActorId) + void MechanicsManager::cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) { mActors.cleanupSummonedCreature(caster.getClass().getCreatureStats(caster), creatureActorId); } @@ -2034,22 +2083,22 @@ namespace MWMechanics stats.setAttribute(frameNumber, "Mechanics Objects", mObjects.size()); } - int MechanicsManager::getGreetingTimer(const MWWorld::Ptr &ptr) const + int MechanicsManager::getGreetingTimer(const MWWorld::Ptr& ptr) const { return mActors.getGreetingTimer(ptr); } - float MechanicsManager::getAngleToPlayer(const MWWorld::Ptr &ptr) const + float MechanicsManager::getAngleToPlayer(const MWWorld::Ptr& ptr) const { return mActors.getAngleToPlayer(ptr); } - GreetingState MechanicsManager::getGreetingState(const MWWorld::Ptr &ptr) const + GreetingState MechanicsManager::getGreetingState(const MWWorld::Ptr& ptr) const { return mActors.getGreetingState(ptr); } - bool MechanicsManager::isTurningToPlayer(const MWWorld::Ptr &ptr) const + bool MechanicsManager::isTurningToPlayer(const MWWorld::Ptr& ptr) const { return mActors.isTurningToPlayer(ptr); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 1087fd6fc..e803dbe09 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -7,10 +7,9 @@ #include "../mwworld/ptr.hpp" -#include "creaturestats.hpp" +#include "actors.hpp" #include "npcstats.hpp" #include "objects.hpp" -#include "actors.hpp" namespace MWWorld { @@ -21,78 +20,83 @@ namespace MWMechanics { class MechanicsManager : public MWBase::MechanicsManager { - bool mUpdatePlayer; - bool mClassSelected; - bool mRaceSelected; - bool mAI;///< is AI active? + bool mUpdatePlayer; + bool mClassSelected; + bool mRaceSelected; + bool mAI; ///< is AI active? - Objects mObjects; - Actors mActors; + Objects mObjects; + Actors mActors; - typedef std::pair Owner; // < Owner id, bool isFaction > - typedef std::map OwnerMap; // < Owner, number of stolen items with this id from this owner > - typedef std::map StolenItemsMap; - StolenItemsMap mStolenItems; + typedef std::pair Owner; // < Owner id, bool isFaction > + typedef std::map OwnerMap; // < Owner, number of stolen items with this id from this owner > + typedef std::map StolenItemsMap; + StolenItemsMap mStolenItems; - public: + public: + void buildPlayer(); + ///< build player according to stored class/race/birthsign information. Will + /// default to the values of the ESM::NPC object, if no explicit information is given. - void buildPlayer(); - ///< build player according to stored class/race/birthsign information. Will - /// default to the values of the ESM::NPC object, if no explicit information is given. + MechanicsManager(); - MechanicsManager(); + void add(const MWWorld::Ptr& ptr) override; + ///< Register an object for management - void add (const MWWorld::Ptr& ptr) override; - ///< Register an object for management + void remove(const MWWorld::Ptr& ptr, bool keepActive) override; + ///< Deregister an object for management - void remove (const MWWorld::Ptr& ptr) override; - ///< Deregister an object for management + void updateCell(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) override; + ///< Moves an object to a new cell - void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) override; - ///< Moves an object to a new cell + void drop(const MWWorld::CellStore* cellStore) override; + ///< Deregister all objects in the given cell. - void drop(const MWWorld::CellStore *cellStore) override; - ///< Deregister all objects in the given cell. + void update(float duration, bool paused); + ///< Update objects + /// + /// \param paused In game type does not currently advance (this usually means some GUI + /// component is up). - void update (float duration, bool paused) override; - ///< Update objects - /// - /// \param paused In game type does not currently advance (this usually means some GUI - /// component is up). + void setPlayerName(const std::string& name) override; + ///< Set player name. - void setPlayerName (const std::string& name) override; - ///< Set player name. + void setPlayerRace(const ESM::RefId& id, bool male, const ESM::RefId& head, const ESM::RefId& hair) override; + ///< Set player race. - void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair) override; - ///< Set player race. + void setPlayerBirthsign(const ESM::RefId& id) override; + ///< Set player birthsign. - void setPlayerBirthsign (const std::string& id) override; - ///< Set player birthsign. + void setPlayerClass(const ESM::RefId& id) override; + ///< Set player class to stock class. - void setPlayerClass (const std::string& id) override; - ///< Set player class to stock class. + void setPlayerClass(const ESM::Class& class_) override; + ///< Set player class to custom class. - void setPlayerClass (const ESM::Class& class_) override; - ///< Set player class to custom class. + void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep) override; - void restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) override; + void rest(double hours, bool sleep) override; + ///< If the player is sleeping or waiting, this should be called every hour. + /// @param sleep is the player sleeping or waiting? - void rest(double hours, bool sleep) override; - ///< If the player is sleeping or waiting, this should be called every hour. - /// @param sleep is the player sleeping or waiting? + int getHoursToRest() const override; + ///< Calculate how many hours the player needs to rest in order to be fully healed - int getHoursToRest() const override; - ///< Calculate how many hours the player needs to rest in order to be fully healed + int getBarterOffer(const MWWorld::Ptr& ptr, int basePrice, bool buying) override; + ///< This is used by every service to determine the price of objects given the trading skills of the player and + ///< NPC. - int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) override; - ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC. + int getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp = true) override; + ///< Calculate the diposition of an NPC toward the player. - int getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange = true) override; - ///< Calculate the diposition of an NPC toward the player. + int countDeaths(const ESM::RefId& id) const override; + ///< Return the number of deaths for actors with the given ID. - int countDeaths (const std::string& id) const override; - ///< Return the number of deaths for actors with the given ID. + void getPersuasionDispositionChange( + const MWWorld::Ptr& npc, PersuasionType type, bool& success, int& tempChange, int& permChange) override; + ///< Perform a persuasion action on NPC +<<<<<<< HEAD /* Start of tes3mp addition @@ -105,97 +109,102 @@ namespace MWMechanics void getPersuasionDispositionChange(const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange) override; ///< Perform a persuasion action on NPC +======= + /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! + bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! - bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override; + /// Makes \a ptr fight \a target. Also shouts a combat taunt. + void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; - /// Makes \a ptr fight \a target. Also shouts a combat taunt. - void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; + void stopCombat(const MWWorld::Ptr& ptr) override; - /** - * @note victim may be empty - * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. - * @param victimAware Is the victim already aware of the crime? - * If this parameter is false, it will be determined by a line-of-sight and awareness check. - * @return was the crime seen? - */ - bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, - OffenseType type, const std::string& factionId="", int arg=0, bool victimAware=false) override; - /// @return false if the attack was considered a "friendly hit" and forgiven - bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override; + /** + * @note victim may be empty + * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. + * @param victimAware Is the victim already aware of the crime? + * If this parameter is false, it will be determined by a line-of-sight and awareness check. + * @return was the crime seen? + */ + bool commitCrime(const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, + const ESM::RefId& factionId = ESM::RefId(), int arg = 0, bool victimAware = false) override; + /// @return false if the attack was considered a "friendly hit" and forgiven + bool actorAttacked(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override; - /// Notify that actor was killed, add a murder bounty if applicable - /// @note No-op for non-player attackers - void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override; + /// Notify that actor was killed, add a murder bounty if applicable + /// @note No-op for non-player attackers + void actorKilled(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override; - /// Utility to check if taking this item is illegal and calling commitCrime if so - /// @param container The container the item is in; may be empty for an item in the world - void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, - int count, bool alarm = true) override; - /// Utility to check if unlocking this object is illegal and calling commitCrime if so - void unlockAttempted (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override; - /// Attempt sleeping in a bed. If this is illegal, call commitCrime. - /// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby - bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) override; + /// Utility to check if taking this item is illegal and calling commitCrime if so + /// @param container The container the item is in; may be empty for an item in the world + void itemTaken(const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, int count, + bool alarm = true) override; + /// Utility to check if unlocking this object is illegal and calling commitCrime if so + void unlockAttempted(const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override; + /// Attempt sleeping in a bed. If this is illegal, call commitCrime. + /// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby + bool sleepInBed(const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) override; - void forceStateUpdate(const MWWorld::Ptr &ptr) override; + void forceStateUpdate(const MWWorld::Ptr& ptr) override; - /// Attempt to play an animation group - /// @return Success or error - bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false) override; - void skipAnimation(const MWWorld::Ptr& ptr) override; - bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName) override; - void persistAnimationStates() override; + /// Attempt to play an animation group + /// @return Success or error + bool playAnimationGroup( + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) override; + void skipAnimation(const MWWorld::Ptr& ptr) override; + bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; + void persistAnimationStates() override; - /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently - /// paused we may want to do it manually (after equipping permanent enchantment) - void updateMagicEffects (const MWWorld::Ptr& ptr) override; + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + void updateMagicEffects(const MWWorld::Ptr& ptr) override; - void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects) override; - void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) override; + void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& objects) override; + void getActorsInRange(const osg::Vec3f& position, float radius, std::vector& objects) override; - /// Check if there are actors in selected range - bool isAnyActorInRange(const osg::Vec3f &position, float radius) override; + /// Check if there are actors in selected range + bool isAnyActorInRange(const osg::Vec3f& position, float radius) override; - std::list getActorsSidingWith(const MWWorld::Ptr& actor) override; - std::list getActorsFollowing(const MWWorld::Ptr& actor) override; - std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) override; - std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) override; + std::vector getActorsSidingWith(const MWWorld::Ptr& actor) override; + std::vector getActorsFollowing(const MWWorld::Ptr& actor) override; + std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor) override; + std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) override; - std::list getActorsFighting(const MWWorld::Ptr& actor) override; - std::list getEnemiesNearby(const MWWorld::Ptr& actor) override; + std::vector getActorsFighting(const MWWorld::Ptr& actor) override; + std::vector getEnemiesNearby(const MWWorld::Ptr& actor) override; - /// Recursive version of getActorsFollowing - void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) override; - /// Recursive version of getActorsSidingWith - void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) override; + /// Recursive version of getActorsFollowing + void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) override; + /// Recursive version of getActorsSidingWith + void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out) override; - bool toggleAI() override; - bool isAIActive() override; + bool toggleAI() override; + bool isAIActive() override; - void playerLoaded() override; + void playerLoaded() override; - bool onOpen(const MWWorld::Ptr& ptr) override; - void onClose(const MWWorld::Ptr& ptr) override; + bool onOpen(const MWWorld::Ptr& ptr) override; + void onClose(const MWWorld::Ptr& ptr) override; - int countSavedGameRecords() const override; + int countSavedGameRecords() const override; - void write (ESM::ESMWriter& writer, Loading::Listener& listener) const override; + void write(ESM::ESMWriter& writer, Loading::Listener& listener) const override; - void readRecord (ESM::ESMReader& reader, uint32_t type) override; + void readRecord(ESM::ESMReader& reader, uint32_t type) override; - void clear() override; + void clear() override; - bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; + bool isAggressive(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; - void resurrect(const MWWorld::Ptr& ptr) override; + void resurrect(const MWWorld::Ptr& ptr) override; - bool isCastingSpell (const MWWorld::Ptr& ptr) const override; + bool isCastingSpell(const MWWorld::Ptr& ptr) const override; - bool isReadyToBlock (const MWWorld::Ptr& ptr) const override; - /// Is \a ptr casting spell or using weapon now? - bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const override; + bool isReadyToBlock(const MWWorld::Ptr& ptr) const override; + /// Is \a ptr casting spell or using weapon now? + bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const override; +<<<<<<< HEAD /* Start of tes3mp addition @@ -207,28 +216,33 @@ namespace MWMechanics */ void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false) override; +======= + void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) override; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - void processChangedSettings(const Settings::CategorySettingVector& settings) override; + void processChangedSettings(const Settings::CategorySettingVector& settings) override; - float getActorsProcessingRange() const override; + void notifyDied(const MWWorld::Ptr& actor) override; - void notifyDied(const MWWorld::Ptr& actor) override; + /// Check if the target actor was detected by an observer + /// If the observer is a non-NPC, check all actors in AI processing distance as observers + bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) override; - /// Check if the target actor was detected by an observer - /// If the observer is a non-NPC, check all actors in AI processing distance as observers - bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) override; + void confiscateStolenItems(const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) override; - void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) override; + /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). + /// + std::vector> getStolenItemOwners(const ESM::RefId& itemid) override; - /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). - /// - std::vector > getStolenItemOwners(const std::string& itemid) override; + /// Has the player stolen this item from the given owner? + bool isItemStolenFrom(const ESM::RefId& itemid, const MWWorld::Ptr& ptr) override; - /// Has the player stolen this item from the given owner? - bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr) override; + bool isBoundItem(const MWWorld::Ptr& item) override; - bool isBoundItem(const MWWorld::Ptr& item) override; + /// @return is \a ptr allowed to take/use \a target or is it a crime? + bool isAllowedToUse(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) override; +<<<<<<< HEAD /* Start of tes3mp addition @@ -241,33 +255,34 @@ namespace MWMechanics /// @return is \a ptr allowed to take/use \a target or is it a crime? bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) override; +======= + void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) override; + void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) override; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) override; - void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) override; + void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) override; - void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) override; + void confiscateStolenItemToOwner( + const MWWorld::Ptr& player, const MWWorld::Ptr& item, const MWWorld::Ptr& victim, int count) override; - void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) override; + bool isAttackPreparing(const MWWorld::Ptr& ptr) override; + bool isRunning(const MWWorld::Ptr& ptr) override; + bool isSneaking(const MWWorld::Ptr& ptr) override; - bool isAttackPreparing(const MWWorld::Ptr& ptr) override; - bool isRunning(const MWWorld::Ptr& ptr) override; - bool isSneaking(const MWWorld::Ptr& ptr) override; + void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; - void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; + int getGreetingTimer(const MWWorld::Ptr& ptr) const override; + float getAngleToPlayer(const MWWorld::Ptr& ptr) const override; + GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override; + bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override; - int getGreetingTimer(const MWWorld::Ptr& ptr) const override; - float getAngleToPlayer(const MWWorld::Ptr& ptr) const override; - GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override; - bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override; + private: + bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + bool canReportCrime( + const MWWorld::Ptr& actor, const MWWorld::Ptr& victim, std::set& playerFollowers); - void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) override; - - private: - bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); - bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set &playerFollowers); - - bool reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, - OffenseType type, const std::string& factionId, int arg=0); + bool reportCrime(const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, + const ESM::RefId& factionId, int arg = 0); }; } diff --git a/apps/openmw/mwmechanics/movement.hpp b/apps/openmw/mwmechanics/movement.hpp index 57e106cde..20606cff4 100644 --- a/apps/openmw/mwmechanics/movement.hpp +++ b/apps/openmw/mwmechanics/movement.hpp @@ -27,10 +27,7 @@ namespace MWMechanics mIsStrafing = false; } - osg::Vec3f asVec3() - { - return osg::Vec3f(mPosition[0], mPosition[1], mPosition[2]); - } + osg::Vec3f asVec3() { return osg::Vec3f(mPosition[0], mPosition[1], mPosition[2]); } }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index b3a234a1b..807df34f4 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -1,30 +1,36 @@ #include "npcstats.hpp" #include +#include -#include -#include -#include -#include +#include +#include +#include +#include + +#include + +#include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" MWMechanics::NpcStats::NpcStats() - : mDisposition (0) -, mReputation(0) -, mCrimeId(-1) -, mBounty(0) -, mWerewolfKills (0) -, mLevelProgress(0) -, mTimeToStartDrowning(-1.0) // set breath to special value, it will be replaced during actor update + : mDisposition(0) + , mReputation(0) + , mCrimeId(-1) + , mBounty(0) + , mWerewolfKills(0) + , mLevelProgress(0) + , mTimeToStartDrowning(-1.0) // set breath to special value, it will be replaced during actor update , mIsWerewolf(false) { - mSkillIncreases.resize (ESM::Attribute::Length, 0); mSpecIncreases.resize(3, 0); + for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get()) + mSkills.emplace(skill.mId, SkillValue{}); } int MWMechanics::NpcStats::getBaseDisposition() const @@ -37,216 +43,201 @@ void MWMechanics::NpcStats::setBaseDisposition(int disposition) mDisposition = disposition; } -const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) const +const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) const { - if (index<0 || index>=ESM::Skill::Length) - throw std::runtime_error ("skill index out of range"); - - return mSkill[index]; + auto it = mSkills.find(id); + if (it == mSkills.end()) + throw std::runtime_error("skill not found"); + return it->second; } -MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) +MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) { - if (index<0 || index>=ESM::Skill::Length) - throw std::runtime_error ("skill index out of range"); - - return mSkill[index]; + auto it = mSkills.find(id); + if (it == mSkills.end()) + throw std::runtime_error("skill not found"); + return it->second; } -void MWMechanics::NpcStats::setSkill(int index, const MWMechanics::SkillValue &value) +void MWMechanics::NpcStats::setSkill(ESM::RefId id, const MWMechanics::SkillValue& value) { - if (index<0 || index>=ESM::Skill::Length) - throw std::runtime_error ("skill index out of range"); - - mSkill[index] = value; + auto it = mSkills.find(id); + if (it == mSkills.end()) + throw std::runtime_error("skill not found"); + it->second = value; } -const std::map& MWMechanics::NpcStats::getFactionRanks() const +const std::map& MWMechanics::NpcStats::getFactionRanks() const { return mFactionRank; } -int MWMechanics::NpcStats::getFactionRank(const std::string &faction) const +int MWMechanics::NpcStats::getFactionRank(const ESM::RefId& faction) const { - const std::string lower = Misc::StringUtils::lowerCase(faction); - std::map::const_iterator it = mFactionRank.find(lower); + auto it = mFactionRank.find(faction); if (it != mFactionRank.end()) return it->second; return -1; } -void MWMechanics::NpcStats::raiseRank(const std::string &faction) +void MWMechanics::NpcStats::joinFaction(const ESM::RefId& faction) { - const std::string lower = Misc::StringUtils::lowerCase(faction); - std::map::iterator it = mFactionRank.find(lower); - if (it != mFactionRank.end()) - { - // Does the next rank exist? - const ESM::Faction* factionPtr = MWBase::Environment::get().getWorld()->getStore().get().find(lower); - if (it->second+1 < 10 && !factionPtr->mRanks[it->second+1].empty()) - it->second += 1; - } + auto it = mFactionRank.find(faction); + if (it == mFactionRank.end()) + mFactionRank[faction] = 0; } -void MWMechanics::NpcStats::lowerRank(const std::string &faction) +void MWMechanics::NpcStats::setFactionRank(const ESM::RefId& faction, int newRank) { - const std::string lower = Misc::StringUtils::lowerCase(faction); - std::map::iterator it = mFactionRank.find(lower); + auto it = mFactionRank.find(faction); if (it != mFactionRank.end()) { - it->second = it->second-1; - if (it->second < 0) + const ESM::Faction* factionPtr = MWBase::Environment::get().getESMStore()->get().find(faction); + if (newRank < 0) { mFactionRank.erase(it); - mExpelled.erase(lower); + mExpelled.erase(faction); } + else if (newRank < static_cast(factionPtr->mData.mRankData.size())) + do + it->second = newRank; + // Does the new rank exist? + while (newRank > 0 && factionPtr->mRanks[newRank--].empty()); } } -void MWMechanics::NpcStats::joinFaction(const std::string& faction) +bool MWMechanics::NpcStats::getExpelled(const ESM::RefId& factionID) const { - const std::string lower = Misc::StringUtils::lowerCase(faction); - std::map::iterator it = mFactionRank.find(lower); - if (it == mFactionRank.end()) - mFactionRank[lower] = 0; + return mExpelled.find(factionID) != mExpelled.end(); } -bool MWMechanics::NpcStats::getExpelled(const std::string& factionID) const +void MWMechanics::NpcStats::expell(const ESM::RefId& factionID) { - return mExpelled.find(Misc::StringUtils::lowerCase(factionID)) != mExpelled.end(); -} - -void MWMechanics::NpcStats::expell(const std::string& factionID) -{ - std::string lower = Misc::StringUtils::lowerCase(factionID); - if (mExpelled.find(lower) == mExpelled.end()) + if (mExpelled.find(factionID) == mExpelled.end()) { std::string message = "#{sExpelledMessage}"; - message += MWBase::Environment::get().getWorld()->getStore().get().find(factionID)->mName; + message += MWBase::Environment::get().getESMStore()->get().find(factionID)->mName; MWBase::Environment::get().getWindowManager()->messageBox(message); - mExpelled.insert(lower); + mExpelled.insert(factionID); } } -void MWMechanics::NpcStats::clearExpelled(const std::string& factionID) +void MWMechanics::NpcStats::clearExpelled(const ESM::RefId& factionID) { - mExpelled.erase(Misc::StringUtils::lowerCase(factionID)); + mExpelled.erase(factionID); } -bool MWMechanics::NpcStats::isInFaction (const std::string& faction) const +bool MWMechanics::NpcStats::isInFaction(const ESM::RefId& faction) const { - return (mFactionRank.find(Misc::StringUtils::lowerCase(faction)) != mFactionRank.end()); + return (mFactionRank.find(faction) != mFactionRank.end()); } -int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const +int MWMechanics::NpcStats::getFactionReputation(const ESM::RefId& faction) const { - std::map::const_iterator iter = mFactionReputation.find (Misc::StringUtils::lowerCase(faction)); + auto iter = mFactionReputation.find(faction); - if (iter==mFactionReputation.end()) + if (iter == mFactionReputation.end()) return 0; return iter->second; } -void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, int value) +void MWMechanics::NpcStats::setFactionReputation(const ESM::RefId& faction, int value) { - mFactionReputation[Misc::StringUtils::lowerCase(faction)] = value; + mFactionReputation[faction] = value; } -float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const ESM::Class& class_) const +float MWMechanics::NpcStats::getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const { - float progressRequirement = static_cast(1 + getSkill(skillIndex).getBase()); + float progressRequirement = 1.f + getSkill(id).getBase(); - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); + const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); - float typeFactor = gmst.find ("fMiscSkillBonus")->mValue.getFloat(); + float typeFactor = gmst.find("fMiscSkillBonus")->mValue.getFloat(); - for (int i=0; i<5; ++i) + for (const auto& skills : class_.mData.mSkills) { - if (class_.mData.mSkills[i][0]==skillIndex) + if (skills[0] == skill->mIndex) { - typeFactor = gmst.find ("fMinorSkillBonus")->mValue.getFloat(); + typeFactor = gmst.find("fMinorSkillBonus")->mValue.getFloat(); break; } - else if (class_.mData.mSkills[i][1]==skillIndex) + else if (skills[1] == skill->mIndex) { - typeFactor = gmst.find ("fMajorSkillBonus")->mValue.getFloat(); + typeFactor = gmst.find("fMajorSkillBonus")->mValue.getFloat(); break; } } progressRequirement *= typeFactor; - if (typeFactor<=0) - throw std::runtime_error ("invalid skill type factor"); + if (typeFactor <= 0) + throw std::runtime_error("invalid skill type factor"); float specialisationFactor = 1; - const ESM::Skill *skill = - MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); - if (skill->mData.mSpecialization==class_.mData.mSpecialization) + if (skill->mData.mSpecialization == class_.mData.mSpecialization) { - specialisationFactor = gmst.find ("fSpecialSkillBonus")->mValue.getFloat(); + specialisationFactor = gmst.find("fSpecialSkillBonus")->mValue.getFloat(); - if (specialisationFactor<=0) - throw std::runtime_error ("invalid skill specialisation factor"); + if (specialisationFactor <= 0) + throw std::runtime_error("invalid skill specialisation factor"); } progressRequirement *= specialisationFactor; return progressRequirement; } -void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType, float extraFactor) +void MWMechanics::NpcStats::useSkill(ESM::RefId id, const ESM::Class& class_, int usageType, float extraFactor) { - const ESM::Skill *skill = - MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); + const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); float skillGain = 1; - if (usageType>=4) - throw std::runtime_error ("skill usage type out of range"); - if (usageType>=0) + if (usageType >= 4) + throw std::runtime_error("skill usage type out of range"); + if (usageType >= 0) { skillGain = skill->mData.mUseValue[usageType]; - if (skillGain<0) - throw std::runtime_error ("invalid skill gain factor"); + if (skillGain < 0) + throw std::runtime_error("invalid skill gain factor"); } skillGain *= extraFactor; - MWMechanics::SkillValue& value = getSkill (skillIndex); + MWMechanics::SkillValue& value = getSkill(skill->mId); value.setProgress(value.getProgress() + skillGain); - if (int(value.getProgress())>=int(getSkillProgressRequirement(skillIndex, class_))) + if (int(value.getProgress()) >= int(getSkillProgressRequirement(skill->mId, class_))) { // skill levelled up - increaseSkill(skillIndex, class_, false); + increaseSkill(skill->mId, class_, false); } } -void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress, bool readBook) +void MWMechanics::NpcStats::increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook) { - float base = getSkill (skillIndex).getBase(); + const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); + float base = getSkill(skill->mId).getBase(); if (base >= 100.f) return; base += 1; - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); // is this a minor or major skill? int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo - for (int k=0; k<5; ++k) + for (const auto& skills : class_.mData.mSkills) { - if (class_.mData.mSkills[k][0] == skillIndex) + if (skills[0] == skill->mIndex) { mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); break; } - else if (class_.mData.mSkills[k][1] == skillIndex) + else if (skills[1] == skill->mIndex) { mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); @@ -254,36 +245,35 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas } } - const ESM::Skill* skill = - MWBase::Environment::get().getWorld ()->getStore ().get().find(skillIndex); - mSkillIncreases[skill->mData.mAttribute] += increase; + mSkillIncreases[ESM::Attribute::AttributeID(skill->mData.mAttribute)] += increase; mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger(); // Play sound & skill progress notification /// \todo check if character is the player, if levelling is ever implemented for NPCs - MWBase::Environment::get().getWindowManager()->playSound("skillraise"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("skillraise")); - std::string message = MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", ""); - message = Misc::StringUtils::format(message, ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}"), static_cast(base)); + std::string message{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sNotifyMessage39", {}) }; + message = Misc::StringUtils::format( + message, MyGUI::TextIterator::toTagsString(skill->mName).asUTF8(), static_cast(base)); if (readBook) message = "#{sBookSkillMessage}\n" + message; - - MWBase::Environment::get().getWindowManager ()->messageBox(message, MWGui::ShowInDialogueMode_Never); + + MWBase::Environment::get().getWindowManager()->messageBox(message, MWGui::ShowInDialogueMode_Never); if (mLevelProgress >= gmst.find("iLevelUpTotal")->mValue.getInteger()) { // levelup is possible now - MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); } - getSkill(skillIndex).setBase (base); + getSkill(skill->mId).setBase(base); if (!preserveProgress) - getSkill(skillIndex).setProgress(0); + getSkill(skill->mId).setProgress(0); } -int MWMechanics::NpcStats::getLevelProgress () const +int MWMechanics::NpcStats::getLevelProgress() const { return mLevelProgress; } @@ -351,14 +341,12 @@ void MWMechanics::NpcStats::setCrimeTime(std::time_t crimeTime) void MWMechanics::NpcStats::levelUp() { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); mLevelProgress -= gmst.find("iLevelUpTotal")->mValue.getInteger(); mLevelProgress = std::max(0, mLevelProgress); // might be necessary when levelup was invoked via console - for (int i=0; isecond == 0) return 1; - - num = std::min(10, num); + int num = std::min(10, it->second); // iLevelUp01Mult - iLevelUp10Mult std::stringstream gmst; gmst << "iLevelUp" << std::setfill('0') << std::setw(2) << num << "Mult"; - return MWBase::Environment::get().getWorld()->getStore().get().find(gmst.str())->mValue.getInteger(); + return MWBase::Environment::get().getESMStore()->get().find(gmst.str())->mValue.getInteger(); } int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const @@ -404,14 +390,14 @@ int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const return mSpecIncreases[spec]; } -void MWMechanics::NpcStats::flagAsUsed (const std::string& id) +void MWMechanics::NpcStats::flagAsUsed(const ESM::RefId& id) { - mUsedIds.insert (id); + mUsedIds.insert(id); } -bool MWMechanics::NpcStats::hasBeenUsed (const std::string& id) const +bool MWMechanics::NpcStats::hasBeenUsed(const ESM::RefId& id) const { - return mUsedIds.find (id)!=mUsedIds.end(); + return mUsedIds.find(id) != mUsedIds.end(); } int MWMechanics::NpcStats::getBounty() const @@ -419,7 +405,7 @@ int MWMechanics::NpcStats::getBounty() const return mBounty; } -void MWMechanics::NpcStats::setBounty (int bounty) +void MWMechanics::NpcStats::setBounty(int bounty) { mBounty = bounty; } @@ -432,7 +418,7 @@ int MWMechanics::NpcStats::getReputation() const void MWMechanics::NpcStats::setReputation(int reputation) { // Reputation is capped in original engine - mReputation = std::min(255, std::max(0, reputation)); + mReputation = std::clamp(reputation, 0, 255); } int MWMechanics::NpcStats::getCrimeId() const @@ -456,46 +442,43 @@ void MWMechanics::NpcStats::setCrimeId(int id) */ } -bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int rank) const +bool MWMechanics::NpcStats::hasSkillsForRank(const ESM::RefId& factionId, int rank) const { - if (rank<0 || rank>=10) - throw std::runtime_error ("rank index out of range"); + const ESM::Faction& faction = *MWBase::Environment::get().getESMStore()->get().find(factionId); - const ESM::Faction& faction = - *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); + const ESM::RankData& rankData = faction.mData.mRankData.at(rank); std::vector skills; - for (int i=0; i<7; ++i) + for (int index : faction.mData.mSkills) { - if (faction.mData.mSkills[i] != -1) - skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getBase())); + ESM::RefId id = ESM::Skill::indexToRefId(index); + if (!id.empty()) + skills.push_back(static_cast(getSkill(id).getBase())); } if (skills.empty()) return true; - std::sort (skills.begin(), skills.end()); + std::sort(skills.begin(), skills.end()); std::vector::const_reverse_iterator iter = skills.rbegin(); - const ESM::RankData& rankData = faction.mData.mRankData[rank]; - - if (*iter::const_iterator iter (mFactionRank.begin()); - iter!=mFactionRank.end(); ++iter) + for (std::map::const_iterator iter(mFactionRank.begin()); iter != mFactionRank.end(); ++iter) state.mFactions[iter->first].mRank = iter->second; state.mDisposition = mDisposition; - for (int i=0; i()->getValue(); + value.writeState(state.mSkills[index]); + } state.mIsWerewolf = mIsWerewolf; @@ -560,55 +546,57 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mBounty = mBounty; - for (std::set::const_iterator iter (mExpelled.begin()); - iter!=mExpelled.end(); ++iter) + for (auto iter(mExpelled.begin()); iter != mExpelled.end(); ++iter) state.mFactions[*iter].mExpelled = true; - for (std::map::const_iterator iter (mFactionReputation.begin()); - iter!=mFactionReputation.end(); ++iter) + for (auto iter(mFactionReputation.begin()); iter != mFactionReputation.end(); ++iter) state.mFactions[iter->first].mReputation = iter->second; state.mReputation = mReputation; state.mWerewolfKills = mWerewolfKills; state.mLevelProgress = mLevelProgress; - for (int i=0; igetStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - for (std::map::const_iterator iter (state.mFactions.begin()); - iter!=state.mFactions.end(); ++iter) - if (store.get().search (iter->first)) + for (auto iter(state.mFactions.begin()); iter != state.mFactions.end(); ++iter) + if (store.get().search(iter->first)) { if (iter->second.mExpelled) - mExpelled.insert (iter->first); + mExpelled.insert(iter->first); if (iter->second.mRank >= 0) mFactionRank[iter->first] = iter->second.mRank; if (iter->second.mReputation) - mFactionReputation[Misc::StringUtils::lowerCase(iter->first)] = iter->second.mReputation; + mFactionReputation[iter->first] = iter->second.mReputation; } mDisposition = state.mDisposition; - for (int i=0; i(i)] = state.mSkillIncrease[i]; - for (int i=0; i<3; ++i) + for (size_t i = 0; i < state.mSpecIncreases.size(); ++i) mSpecIncreases[i] = state.mSpecIncreases[i]; - for (std::vector::const_iterator iter (state.mUsedIds.begin()); - iter!=state.mUsedIds.end(); ++iter) - if (store.find (*iter)) - mUsedIds.insert (*iter); + for (auto iter(state.mUsedIds.begin()); iter != state.mUsedIds.end(); ++iter) + if (store.find(*iter)) + mUsedIds.insert(*iter); mTimeToStartDrowning = state.mTimeToStartDrowning; } diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index b693cf271..fdeb260a5 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -1,11 +1,15 @@ #ifndef GAME_MWMECHANICS_NPCSTATS_H #define GAME_MWMECHANICS_NPCSTATS_H +#include "creaturestats.hpp" +#include +#include #include #include #include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -18,6 +22,8 @@ #include "creaturestats.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 namespace ESM { struct Class; @@ -30,12 +36,13 @@ namespace MWMechanics class NpcStats : public CreatureStats { - int mDisposition; - SkillValue mSkill[ESM::Skill::Length]; // SkillValue.mProgress used by the player only + int mDisposition; + std::map mSkills; // SkillValue.mProgress used by the player only - int mReputation; - int mCrimeId; + int mReputation; + int mCrimeId; +<<<<<<< HEAD /* Start of tes3mp addition @@ -58,55 +65,72 @@ namespace MWMechanics std::vector mSpecIncreases; // number of skill increases for each specialization (accumulates throughout the entire game) std::set mUsedIds; // --------------------------------------------------------------------------- +======= + // ----- used by the player only, maybe should be moved at some point ------- + int mBounty; + int mWerewolfKills; + /// Used only for the player and for NPC's with ranks, modified by scripts; other NPCs have maximum one faction + /// defined in their NPC record + std::map mFactionRank; + std::set mExpelled; + std::map mFactionReputation; + int mLevelProgress; // 0-10 + std::map + mSkillIncreases; // number of skill increases for each attribute (resets after leveling up) + std::vector mSpecIncreases; // number of skill increases for each specialization (accumulates throughout + // the entire game) + std::set mUsedIds; + // --------------------------------------------------------------------------- +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - /// Countdown to getting damage while underwater - float mTimeToStartDrowning; + /// Countdown to getting damage while underwater + float mTimeToStartDrowning; - bool mIsWerewolf; + bool mIsWerewolf; - public: + public: + NpcStats(); - NpcStats(); + int getBaseDisposition() const; + void setBaseDisposition(int disposition); - int getBaseDisposition() const; - void setBaseDisposition(int disposition); + int getReputation() const; + void setReputation(int reputation); - int getReputation() const; - void setReputation(int reputation); + int getCrimeId() const; + void setCrimeId(int id); - int getCrimeId() const; - void setCrimeId(int id); + const SkillValue& getSkill(ESM::RefId id) const; + SkillValue& getSkill(ESM::RefId id); + void setSkill(ESM::RefId id, const SkillValue& value); - const SkillValue& getSkill (int index) const; - SkillValue& getSkill (int index); - void setSkill(int index, const SkillValue& value); + int getFactionRank(const ESM::RefId& faction) const; + const std::map& getFactionRanks() const; - int getFactionRank(const std::string &faction) const; - const std::map& getFactionRanks() const; + /// Join this faction, setting the initial rank to 0. + void joinFaction(const ESM::RefId& faction); + /// Sets the rank in this faction to a specified value, if such a rank exists. + void setFactionRank(const ESM::RefId& faction, int value); - /// Increase the rank in this faction by 1, if such a rank exists. - void raiseRank(const std::string& faction); - /// Lower the rank in this faction by 1, if such a rank exists. - void lowerRank(const std::string& faction); - /// Join this faction, setting the initial rank to 0. - void joinFaction(const std::string& faction); + const std::set& getExpelled() const { return mExpelled; } + bool getExpelled(const ESM::RefId& factionID) const; + void expell(const ESM::RefId& factionID); + void clearExpelled(const ESM::RefId& factionID); - const std::set& getExpelled() const { return mExpelled; } - bool getExpelled(const std::string& factionID) const; - void expell(const std::string& factionID); - void clearExpelled(const std::string& factionID); + bool isInFaction(const ESM::RefId& faction) const; - bool isInFaction (const std::string& faction) const; + float getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const; - float getSkillProgressRequirement (int skillIndex, const ESM::Class& class_) const; + void useSkill(ESM::RefId id, const ESM::Class& class_, int usageType = -1, float extraFactor = 1.f); + ///< Increase skill by usage. - void useSkill (int skillIndex, const ESM::Class& class_, int usageType = -1, float extraFactor=1.f); - ///< Increase skill by usage. + void increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook = false); - void increaseSkill (int skillIndex, const ESM::Class& class_, bool preserveProgress, bool readBook = false); + int getLevelProgress() const; - int getLevelProgress() const; + int getLevelupAttributeMultiplier(ESM::Attribute::AttributeID attribute) const; +<<<<<<< HEAD /* Start of tes3mp addition @@ -132,50 +156,53 @@ namespace MWMechanics */ int getLevelupAttributeMultiplier(int attribute) const; +======= + int getSkillIncreasesForSpecialization(int spec) const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - int getSkillIncreasesForSpecialization(int spec) const; + void levelUp(); - void levelUp(); + void updateHealth(); + ///< Calculate health based on endurance and strength. + /// Called at character creation. - void updateHealth(); - ///< Calculate health based on endurance and strength. - /// Called at character creation. + void flagAsUsed(const ESM::RefId& id); + ///< @note Id must be lower-case - void flagAsUsed (const std::string& id); - ///< @note Id must be lower-case + bool hasBeenUsed(const ESM::RefId& id) const; + ///< @note Id must be lower-case - bool hasBeenUsed (const std::string& id) const; - ///< @note Id must be lower-case + int getBounty() const; - int getBounty() const; + void setBounty(int bounty); - void setBounty (int bounty); + int getFactionReputation(const ESM::RefId& faction) const; - int getFactionReputation (const std::string& faction) const; + void setFactionReputation(const ESM::RefId& faction, int value); - void setFactionReputation (const std::string& faction, int value); + bool hasSkillsForRank(const ESM::RefId& factionId, int rank) const; - bool hasSkillsForRank (const std::string& factionId, int rank) const; + bool isWerewolf() const; - bool isWerewolf() const; + void setWerewolf(bool set); - void setWerewolf(bool set); + int getWerewolfKills() const; - int getWerewolfKills() const; + /// Increments mWerewolfKills by 1. + void addWerewolfKill(); - /// Increments mWerewolfKills by 1. - void addWerewolfKill(); + float getTimeToStartDrowning() const; + /// Sets time left for the creature to drown if it stays underwater. + /// @param time value from [0,20] + void setTimeToStartDrowning(float time); - float getTimeToStartDrowning() const; - /// Sets time left for the creature to drown if it stays underwater. - /// @param time value from [0,20] - void setTimeToStartDrowning(float time); + void writeState(ESM::CreatureStats& state) const; + void writeState(ESM::NpcStats& state) const; - void writeState (ESM::CreatureStats& state) const; - void writeState (ESM::NpcStats& state) const; + void readState(const ESM::CreatureStats& state); + void readState(const ESM::NpcStats& state); - void readState (const ESM::CreatureStats& state); - void readState (const ESM::NpcStats& state); + const std::map& getSkills() const { return mSkills; } }; } diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 5b18fc2c3..ab981dd45 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -1,153 +1,136 @@ #include "objects.hpp" #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "character.hpp" -#include "movement.hpp" namespace MWMechanics { -Objects::Objects() -{ -} - -Objects::~Objects() -{ - for(auto& object : mObjects) - { - delete object.second; - object.second = nullptr; - } -} - -void Objects::addObject(const MWWorld::Ptr& ptr) -{ - removeObject(ptr); - - MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); - if(anim) mObjects.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); -} - -void Objects::removeObject(const MWWorld::Ptr& ptr) -{ - PtrControllerMap::iterator iter = mObjects.find(ptr); - if(iter != mObjects.end()) + void Objects::addObject(const MWWorld::Ptr& ptr) { - delete iter->second; - mObjects.erase(iter); - } -} + removeObject(ptr); -void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) -{ - PtrControllerMap::iterator iter = mObjects.find(old); - if(iter != mObjects.end()) - { - CharacterController *ctrl = iter->second; - mObjects.erase(iter); - - ctrl->updatePtr(ptr); - mObjects.insert(std::make_pair(ptr, ctrl)); - } -} - -void Objects::dropObjects (const MWWorld::CellStore *cellStore) -{ - PtrControllerMap::iterator iter = mObjects.begin(); - while(iter != mObjects.end()) - { - if(iter->first.getCell()==cellStore) - { - delete iter->second; - mObjects.erase(iter++); - } - else - ++iter; - } -} - -void Objects::update(float duration, bool paused) -{ - if(!paused) - { - for(auto& object : mObjects) - object.second->update(duration); - } - else - { - // We still should play container opening animation in the Container GUI mode. - MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); - if(mode != MWGui::GM_Container) + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if (anim == nullptr) return; - for(auto& object : mObjects) - { - if (object.first.getTypeName() != typeid(ESM::Container).name()) - continue; + const auto it = mObjects.emplace(mObjects.end(), ptr, anim); + mIndex.emplace(ptr.mRef, it); + } - if (object.second->isAnimPlaying("containeropen")) + void Objects::removeObject(const MWWorld::Ptr& ptr) + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + { + mObjects.erase(iter->second); + mIndex.erase(iter); + } + } + + void Objects::updateObject(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) + { + const auto iter = mIndex.find(old.mRef); + if (iter != mIndex.end()) + iter->second->updatePtr(ptr); + } + + void Objects::dropObjects(const MWWorld::CellStore* cellStore) + { + for (auto iter = mObjects.begin(); iter != mObjects.end();) + { + if (iter->getPtr().getCell() == cellStore) { - object.second->update(duration); - MWBase::Environment::get().getWorld()->updateAnimatedCollisionShape(object.first); + mIndex.erase(iter->getPtr().mRef); + iter = mObjects.erase(iter); + } + else + ++iter; + } + } + + void Objects::update(float duration, bool paused) + { + if (!paused) + { + for (CharacterController& object : mObjects) + object.update(duration); + } + else + { + // We still should play container opening animation in the Container GUI mode. + MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); + if (mode != MWGui::GM_Container) + return; + + for (CharacterController& object : mObjects) + { + if (object.getPtr().getType() != ESM::Container::sRecordId) + continue; + + if (object.isAnimPlaying("containeropen")) + { + object.update(duration); + MWBase::Environment::get().getWorld()->updateAnimatedCollisionShape(object.getPtr()); + } } } } -} -bool Objects::onOpen(const MWWorld::Ptr& ptr) -{ - PtrControllerMap::iterator iter = mObjects.find(ptr); - if(iter != mObjects.end()) - return iter->second->onOpen(); - return true; -} - -void Objects::onClose(const MWWorld::Ptr& ptr) -{ - PtrControllerMap::iterator iter = mObjects.find(ptr); - if(iter != mObjects.end()) - iter->second->onClose(); -} - -bool Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) -{ - PtrControllerMap::iterator iter = mObjects.find(ptr); - if(iter != mObjects.end()) + bool Objects::onOpen(const MWWorld::Ptr& ptr) { - return iter->second->playGroup(groupName, mode, number, persist); + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + return iter->second->onOpen(); + return true; } - else + + void Objects::onClose(const MWWorld::Ptr& ptr) { - Log(Debug::Warning) << "Warning: Objects::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId(); - return false; + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->onClose(); } -} -void Objects::skipAnimation(const MWWorld::Ptr& ptr) -{ - PtrControllerMap::iterator iter = mObjects.find(ptr); - if(iter != mObjects.end()) - iter->second->skipAnim(); -} -void Objects::persistAnimationStates() -{ - for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) - iter->second->persistAnimationState(); -} - -void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) -{ - for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + bool Objects::playAnimationGroup( + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) { - if ((position - iter->first.getRefData().getPosition().asVec3()).length2() <= radius*radius) - out.push_back(iter->first); + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + { + return iter->second->playGroup(groupName, mode, number, persist); + } + else + { + Log(Debug::Warning) << "Warning: Objects::playAnimationGroup: Unable to find " + << ptr.getCellRef().getRefId(); + return false; + } + } + void Objects::skipAnimation(const MWWorld::Ptr& ptr) + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->skipAnim(); + } + + void Objects::persistAnimationStates() + { + for (CharacterController& object : mObjects) + object.persistAnimationState(); + } + + void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const + { + for (const CharacterController& object : mObjects) + if ((position - object.getPtr().getRefData().getPosition().asVec3()).length2() <= radius * radius) + out.push_back(object.getPtr()); } -} } diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 5160114a3..8b5962109 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -1,8 +1,11 @@ #ifndef GAME_MWMECHANICS_ACTIVATORS_H #define GAME_MWMECHANICS_ACTIVATORS_H -#include +#include "character.hpp" + +#include #include +#include #include namespace osg @@ -18,27 +21,22 @@ namespace MWWorld namespace MWMechanics { - class CharacterController; - class Objects { - typedef std::map PtrControllerMap; - PtrControllerMap mObjects; + std::list mObjects; + std::map::iterator> mIndex; public: - Objects(); - ~Objects(); - - void addObject (const MWWorld::Ptr& ptr); + void addObject(const MWWorld::Ptr& ptr); ///< Register an animated object - void removeObject (const MWWorld::Ptr& ptr); + void removeObject(const MWWorld::Ptr& ptr); ///< Deregister an object - void updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); + void updateObject(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); ///< Updates an object with a new Ptr - void dropObjects(const MWWorld::CellStore *cellStore); + void dropObjects(const MWWorld::CellStore* cellStore); ///< Deregister all objects in the given cell. void update(float duration, bool paused); @@ -47,16 +45,14 @@ namespace MWMechanics bool onOpen(const MWWorld::Ptr& ptr); void onClose(const MWWorld::Ptr& ptr); - bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); + bool playAnimationGroup( + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false); void skipAnimation(const MWWorld::Ptr& ptr); void persistAnimationStates(); - void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& out); + void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const; - std::size_t size() const - { - return mObjects.size(); - } + std::size_t size() const { return mObjects.size(); } }; } diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 88325ee7c..ae06c05dc 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -1,9 +1,15 @@ #include "obstacle.hpp" +#include + +#include +#include #include -#include "../mwworld/class.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "movement.hpp" @@ -14,17 +20,16 @@ namespace MWMechanics static const float DURATION_SAME_SPOT = 1.5f; static const float DURATION_TO_EVADE = 0.4f; - const float ObstacleCheck::evadeDirections[NUM_EVADE_DIRECTIONS][2] = - { - { 1.0f, 0.0f }, // move to side - { 1.0f, -1.0f }, // move to side and backwards - { -1.0f, 0.0f }, // move to other side - { -1.0f, -1.0f } // move to side and backwards + const float ObstacleCheck::evadeDirections[NUM_EVADE_DIRECTIONS][2] = { + { 1.0f, 0.0f }, // move to side + { 1.0f, -1.0f }, // move to side and backwards + { -1.0f, 0.0f }, // move to other side + { -1.0f, -1.0f } // move to side and backwards }; bool proximityToDoor(const MWWorld::Ptr& actor, float minDist) { - if(getNearbyDoor(actor, minDist).isEmpty()) + if (getNearbyDoor(actor, minDist).isEmpty()) return false; else return true; @@ -32,21 +37,22 @@ namespace MWMechanics const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) { - MWWorld::CellStore *cell = actor.getCell(); + MWWorld::CellStore* cell = actor.getCell(); // Check all the doors in this cell const MWWorld::CellRefList& doors = cell->getReadOnlyDoors(); osg::Vec3f pos(actor.getRefData().getPosition().asVec3()); pos.z() = 0; - osg::Vec3f actorDir = (actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0)); + osg::Vec3f actorDir = (actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0, 1, 0)); for (const auto& ref : doors.mList) { osg::Vec3f doorPos(ref.mData.getPosition().asVec3()); // FIXME: cast - const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); + const MWWorld::Ptr doorPtr + = MWWorld::Ptr(&const_cast&>(ref), actor.getCell()); const auto doorState = doorPtr.getClass().getDoorState(doorPtr); float doorRot = ref.mData.getPosition().rot[2] - doorPtr.getCellRef().getPosition().rot[2]; @@ -63,7 +69,7 @@ namespace MWMechanics continue; // Door is not close enough - if ((pos - doorPos).length2() > minDist*minDist) + if ((pos - doorPos).length2() > minDist * minDist) continue; return doorPtr; // found, stop searching @@ -72,10 +78,25 @@ namespace MWMechanics return MWWorld::Ptr(); // none found } + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, bool ignorePlayer, + std::vector* occupyingActors) + { + const auto world = MWBase::Environment::get().getWorld(); + const osg::Vec3f halfExtents = world->getPathfindingAgentBounds(actor).mHalfExtents; + const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + if (ignorePlayer) + { + const std::array ignore{ actor, world->getPlayerConstPtr() }; + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors); + } + const std::array ignore{ actor }; + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors); + } + ObstacleCheck::ObstacleCheck() - : mWalkState(WalkState::Initial) - , mStateDuration(0) - , mEvadeDirectionIndex(0) + : mWalkState(WalkState::Initial) + , mStateDuration(0) + , mEvadeDirectionIndex(0) { } @@ -117,11 +138,18 @@ namespace MWMechanics mStateDuration = 0; mPrev = position; mInitialDistance = (destination - position).length(); + mDestination = destination; return; } if (mWalkState != WalkState::Evade) { + if (mDestination != destination) + { + mInitialDistance = (destination - mPrev).length(); + mDestination = destination; + } + const float distSameSpot = DIST_SAME_SPOT * actor.getClass().getCurrentSpeed(actor) * duration; const float prevDistance = (destination - mPrev).length(); const float currentDistance = (destination - position).length(); @@ -158,7 +186,7 @@ namespace MWMechanics } mStateDuration += duration; - if(mStateDuration >= DURATION_TO_EVADE) + if (mStateDuration >= DURATION_TO_EVADE) { // tried to evade, assume all is ok and start again mWalkState = WalkState::Norm; diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index b574bab67..be9680f52 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -3,9 +3,12 @@ #include +#include + namespace MWWorld { class Ptr; + class ConstPtr; } namespace MWMechanics @@ -21,42 +24,46 @@ namespace MWMechanics /** \return Pointer to the door, or empty pointer if none exists **/ const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + bool ignorePlayer = false, std::vector* occupyingActors = nullptr); + class ObstacleCheck { - public: - ObstacleCheck(); + public: + ObstacleCheck(); - // Clear the timers and set the state machine to default - void clear(); + // Clear the timers and set the state machine to default + void clear(); - bool isEvading() const; + bool isEvading() const; - // Updates internal state, call each frame for moving actor - void update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration); + // Updates internal state, call each frame for moving actor + void update(const MWWorld::Ptr& actor, const osg::Vec3f& destination, float duration); - // change direction to try to fix "stuck" actor - void takeEvasiveAction(MWMechanics::Movement& actorMovement) const; + // change direction to try to fix "stuck" actor + void takeEvasiveAction(MWMechanics::Movement& actorMovement) const; - private: - osg::Vec3f mPrev; + private: + osg::Vec3f mPrev; + osg::Vec3f mDestination; - // directions to try moving in when get stuck - static const float evadeDirections[NUM_EVADE_DIRECTIONS][2]; + // directions to try moving in when get stuck + static const float evadeDirections[NUM_EVADE_DIRECTIONS][2]; - enum class WalkState - { - Initial, - Norm, - CheckStuck, - Evade - }; - WalkState mWalkState; + enum class WalkState + { + Initial, + Norm, + CheckStuck, + Evade + }; + WalkState mWalkState; - float mStateDuration; - int mEvadeDirectionIndex; - float mInitialDistance = 0; + float mStateDuration; + int mEvadeDirectionIndex; + float mInitialDistance = 0; - void chooseEvasionDirection(); + void chooseEvasionDirection(); }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 0e704cd46..01a38378c 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -3,28 +3,30 @@ #include #include -#include -#include +#include + #include +#include +#include #include +#include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" -#include "../mwphysics/collisiontype.hpp" +#include "../mwphysics/raycasting.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" -#include "pathgrid.hpp" #include "actorutil.hpp" +#include "pathgrid.hpp" namespace { // Chooses a reachable end pathgrid point. start is assumed reachable. - std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, - const MWMechanics::PathgridGraph *graph, - const osg::Vec3f& pos, int start) + std::pair getClosestReachablePoint( + const ESM::Pathgrid* grid, const MWMechanics::PathgridGraph* graph, const osg::Vec3f& pos, int start) { assert(grid && !grid->mPoints.empty()); @@ -34,7 +36,7 @@ namespace int closestReachableIndex = 0; // TODO: if this full scan causes performance problems mapping pathgrid // points to a quadtree may help - for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++) + for (size_t counter = 0; counter < grid->mPoints.size(); counter++) { float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos); if (potentialDistBetween < closestDistanceReachable) @@ -60,8 +62,7 @@ namespace // allowed nodes if not. Hence a path needs to be created even if the start // and the end points are the same. - return std::pair - (closestReachableIndex, closestReachableIndex == closestIndex); + return std::pair(closestReachableIndex, closestReachableIndex == closestIndex); } float sqrDistance(const osg::Vec2f& lhs, const osg::Vec2f& rhs) @@ -89,7 +90,8 @@ namespace } // Returns true if turn in `p2` is less than 10 degrees and all the 3 points are almost on one line. - bool isAlmostStraight(const osg::Vec3f& p1, const osg::Vec3f& p2, const osg::Vec3f& p3, float pointTolerance) { + bool isAlmostStraight(const osg::Vec3f& p1, const osg::Vec3f& p2, const osg::Vec3f& p3, float pointTolerance) + { osg::Vec3f v1 = p1 - p2; osg::Vec3f v3 = p3 - p2; v1.z() = v3.z() = 0; @@ -109,13 +111,23 @@ namespace struct IsValidShortcut { const DetourNavigator::Navigator* mNavigator; +<<<<<<< HEAD const osg::Vec3f mHalfExtents; +======= + const DetourNavigator::AgentBounds mAgentBounds; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 const DetourNavigator::Flags mFlags; bool operator()(const osg::Vec3f& start, const osg::Vec3f& end) const { +<<<<<<< HEAD const auto position = mNavigator->raycast(mHalfExtents, start, end, mFlags); return position.has_value() && std::abs((position.value() - start).length2() - (end - start).length2()) <= 1; +======= + const auto position = DetourNavigator::raycast(*mNavigator, mAgentBounds, start, end, mFlags); + return position.has_value() + && std::abs((position.value() - start).length2() - (end - start).length2()) <= 1; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } }; } @@ -135,10 +147,12 @@ namespace MWMechanics dir.z() = 0; dir.normalize(); float verticalOffset = 200; // instead of '200' here we want the height of the actor - osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset; + osg::Vec3f _from = from + dir * offsetXY + osg::Z_AXIS * verticalOffset; // cast up-down ray and find height of hit in world space - float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1); + float h = _from.z() + - MWBase::Environment::get().getWorld()->getDistToNearestRayHit( + _from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1); return (std::abs(from.z() - h) <= PATHFIND_Z_REACH); } @@ -184,14 +198,14 @@ namespace MWMechanics { const auto pathgrid = pathgridGraph.getPathgrid(); - // Refer to AiWander reseach topic on openmw forums for some background. + // Refer to AiWander research topic on openmw forums for some background. // Maybe there is no pathgrid for this cell. Just go to destination and let // physics take care of any blockages. - if(!pathgrid || pathgrid->mPoints.empty()) + if (!pathgrid || pathgrid->mPoints.empty()) return; // NOTE: getClosestPoint expects local coordinates - Misc::CoordinateConverter converter(mCell->getCell()); + Misc::CoordinateConverter converter(*mCell->getCell()); // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing @@ -202,9 +216,14 @@ namespace MWMechanics int startNode = getClosestPoint(pathgrid, startPointInLocalCoords); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); +<<<<<<< HEAD std::pair endNode = getClosestReachablePoint(pathgrid, &pathgridGraph, endPointInLocalCoords, startNode); +======= + std::pair endNode + = getClosestReachablePoint(pathgrid, &pathgridGraph, endPointInLocalCoords, startNode); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. @@ -222,7 +241,7 @@ namespace MWMechanics // even if the start and the end points are the same. // NOTE: aStarSearch will return an empty path if the start and end // nodes are the same - if(startNode == endNode.first) + if (startNode == endNode.first) { ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]); converter.toWorld(temp); @@ -248,20 +267,22 @@ namespace MWMechanics // Add Z offset since path node can overlap with other objects. // Also ignore doors in raytesting. const int mask = MWPhysics::CollisionType_World; - bool isPathClear = !MWBase::Environment::get().getWorld()->castRay( - startPoint.x(), startPoint.y(), startPoint.z() + 16, temp.mX, temp.mY, temp.mZ + 16, mask); + bool isPathClear = !MWBase::Environment::get() + .getWorld() + ->getRayCasting() + ->castRay(osg::Vec3f(startPoint.x(), startPoint.y(), startPoint.z() + 16), + osg::Vec3f(temp.mX, temp.mY, temp.mZ + 16), mask) + .mHit; if (isPathClear) path.pop_front(); } } // convert supplied path to world coordinates - std::transform(path.begin(), path.end(), out, - [&] (ESM::Pathgrid::Point& point) - { - converter.toWorld(point); - return makeOsgVec3(point); - }); + std::transform(path.begin(), path.end(), out, [&](ESM::Pathgrid::Point& point) { + converter.toWorld(point); + return makeOsgVec3(point); + }); } // If endNode found is NOT the closest PathGrid point to the endPoint, @@ -284,7 +305,7 @@ namespace MWMechanics { // This should never happen (programmers should have an if statement checking // isPathConstructed that prevents this call if otherwise). - if(mPath.empty()) + if (mPath.empty()) return 0.; const auto& nextPoint = mPath.front(); @@ -298,7 +319,7 @@ namespace MWMechanics { // This should never happen (programmers should have an if statement checking // isPathConstructed that prevents this call if otherwise). - if(mPath.empty()) + if (mPath.empty()) return 0.; const osg::Vec3f dir = mPath.front() - osg::Vec3f(x, y, z); @@ -307,8 +328,12 @@ namespace MWMechanics } void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, +<<<<<<< HEAD bool shortenIfAlmostStraight, bool canMoveByZ, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags) +======= + UpdateFlags updateFlags, const DetourNavigator::AgentBounds& agentBounds, DetourNavigator::Flags pathFlags) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { if (mPath.empty()) return; @@ -316,8 +341,12 @@ namespace MWMechanics while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance) mPath.pop_front(); - if (shortenIfAlmostStraight) + const IsValidShortcut isValidShortcut{ MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, + pathFlags }; + + if ((updateFlags & UpdateFlag_ShortenIfAlmostStraight) != 0) { +<<<<<<< HEAD const IsValidShortcut isValidShortcut { MWBase::Environment::get().getWorld()->getNavigator(), halfExtents, flags @@ -327,13 +356,33 @@ namespace MWMechanics mPath.erase(mPath.begin() + 1); if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance) && isValidShortcut(position, mPath[1])) +======= + while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance) + && isValidShortcut(mPath[0], mPath[2])) + mPath.erase(mPath.begin() + 1); + if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance) + && isValidShortcut(position, mPath[1])) + mPath.pop_front(); + } + + if ((updateFlags & UpdateFlag_RemoveLoops) != 0 && mPath.size() > 1) + { + std::size_t begin = 0; + for (std::size_t i = 1; i < mPath.size(); ++i) + { + const float sqrDistance = Misc::getVectorToLine(position, mPath[i - 1], mPath[i]).length2(); + if (sqrDistance < pointTolerance * pointTolerance && isValidShortcut(position, mPath[i])) + begin = i; + } + for (std::size_t i = 0; i < begin; ++i) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mPath.pop_front(); } if (mPath.size() == 1) { float distSqr; - if (canMoveByZ) + if ((updateFlags & UpdateFlag_CanMoveByZ) != 0) distSqr = (mPath.front() - position).length2(); else distSqr = sqrDistanceIgnoreZ(mPath.front(), position); @@ -361,14 +410,19 @@ namespace MWMechanics } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, - const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, - const DetourNavigator::AreaCosts& areaCosts) + const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType) { mPath.clear(); // If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path +<<<<<<< HEAD DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)); +======= + DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, + areaCosts, endTolerance, pathType, std::back_inserter(mPath)); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (status != DetourNavigator::Status::Success) mPath.clear(); @@ -380,8 +434,9 @@ namespace MWMechanics } void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, + const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType) { mPath.clear(); mCell = cell; @@ -390,15 +445,29 @@ namespace MWMechanics if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor)) { +<<<<<<< HEAD status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)); +======= + status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, areaCosts, endTolerance, + pathType, std::back_inserter(mPath)); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (status != DetourNavigator::Status::Success) mPath.clear(); } +<<<<<<< HEAD if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty()) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags | DetourNavigator::Flag_usePathgrid, areaCosts, std::back_inserter(mPath)); +======= + if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty() + && (flags & DetourNavigator::Flag_usePathgrid) == 0) + { + status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, + flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, + std::back_inserter(mPath)); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (status != DetourNavigator::Status::Success) mPath.clear(); } @@ -412,28 +481,43 @@ namespace MWMechanics mConstructed = !mPath.empty(); } +<<<<<<< HEAD DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out) +======= + DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, + const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, + PathType pathType, std::back_insert_iterator> out) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { const auto world = MWBase::Environment::get().getWorld(); const auto stepSize = getPathStepSize(actor); const auto navigator = world->getNavigator(); - const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, areaCosts, out); + const auto status = DetourNavigator::findPath( + *navigator, agentBounds, stepSize, startPoint, endPoint, flags, areaCosts, endTolerance, out); +<<<<<<< HEAD +======= + if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath) + return DetourNavigator::Status::Success; + +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (status != DetourNavigator::Status::Success) { Log(Debug::Debug) << "Build path by navigator error: \"" << DetourNavigator::getMessage(status) - << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() - << ") from " << startPoint << " to " << endPoint << " with flags (" - << DetourNavigator::WriteFlags {flags} << ")"; + << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() + << ") from " << startPoint << " to " << endPoint << " with flags (" + << DetourNavigator::WriteFlags{ flags } << ")"; } return status; } - void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) + void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, + const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts) { if (mPath.empty()) return; @@ -447,8 +531,9 @@ namespace MWMechanics const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); std::deque prePath; auto prePathInserter = std::back_inserter(prePath); - const auto status = navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, areaCosts, - prePathInserter); + const float endTolerance = 0; + const auto status = DetourNavigator::findPath(*navigator, agentBounds, stepSize, startPoint, mPath.front(), + flags, areaCosts, endTolerance, prePathInserter); if (status == DetourNavigator::Status::NavMeshNotFound) return; @@ -456,9 +541,9 @@ namespace MWMechanics if (status != DetourNavigator::Status::Success) { Log(Debug::Debug) << "Build path by navigator error: \"" << DetourNavigator::getMessage(status) - << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() - << ") from " << startPoint << " to " << mPath.front() << " with flags (" - << DetourNavigator::WriteFlags {flags} << ")"; + << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() + << ") from " << startPoint << " to " << mPath.front() << " with flags (" + << DetourNavigator::WriteFlags{ flags } << ")"; return; } @@ -471,20 +556,20 @@ namespace MWMechanics std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); } - void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) + void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, + const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType) { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - const auto maxDistance = std::min( - navigator->getMaxNavmeshAreaRealRadius(), - static_cast(Constants::CellSizeInUnits) - ); + const auto maxDistance + = std::min(navigator->getMaxNavmeshAreaRealRadius(), static_cast(Constants::CellSizeInUnits)); const auto startToEnd = endPoint - startPoint; const auto distance = startToEnd.length(); if (distance <= maxDistance) - return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, halfExtents, flags, areaCosts); + return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, agentBounds, flags, areaCosts, + endTolerance, pathType); const auto end = startPoint + startToEnd * maxDistance / distance; - buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts); + buildPath(actor, startPoint, end, cell, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType); } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 0d281d1e2..28315c6e2 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -1,15 +1,18 @@ #ifndef GAME_MWMECHANICS_PATHFINDING_H #define GAME_MWMECHANICS_PATHFINDING_H -#include #include +#include #include -#include #include +<<<<<<< HEAD +======= +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include #include -#include +#include namespace MWWorld { @@ -18,6 +21,11 @@ namespace MWWorld class Ptr; } +namespace DetourNavigator +{ + struct AgentBounds; +} + namespace MWMechanics { class PathgridGraph; @@ -25,9 +33,7 @@ namespace MWMechanics template inline float distance(const T& lhs, const T& rhs) { - static_assert(std::is_same::value - || std::is_same::value, - "T is not a position"); + static_assert(std::is_same::value || std::is_same::value, "T is not a position"); return (lhs - rhs).length(); } @@ -70,13 +76,127 @@ namespace MWMechanics // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY); + enum class PathType + { + Full, + Partial, + }; + class PathFinder { - public: - PathFinder() - : mConstructed(false) - , mCell(nullptr) + public: + using UpdateFlags = unsigned; + + enum UpdateFlag : UpdateFlags + { + UpdateFlag_CanMoveByZ = 1 << 0, + UpdateFlag_ShortenIfAlmostStraight = 1 << 1, + UpdateFlag_RemoveLoops = 1 << 2, + }; + + PathFinder() = default; + + void clearPath() + { + mConstructed = false; + mPath.clear(); + mCell = nullptr; + } + + void buildStraightPath(const osg::Vec3f& endPoint); + + void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); + + void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, + PathType pathType); + + void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, + const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType); + + void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, + const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts); + + void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, + const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags, + const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType); + + /// Remove front point if exist and within tolerance + void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, + UpdateFlags updateFlags, const DetourNavigator::AgentBounds& agentBounds, DetourNavigator::Flags pathFlags); + + bool checkPathCompleted() const { return mConstructed && mPath.empty(); } + + /// In radians + float getZAngleToNext(float x, float y) const; + + float getXAngleToNext(float x, float y, float z) const; + + bool isPathConstructed() const { return mConstructed && !mPath.empty(); } + + std::size_t getPathSize() const { return mPath.size(); } + + const std::deque& getPath() const { return mPath; } + + const MWWorld::CellStore* getPathCell() const { return mCell; } + + void addPointToPath(const osg::Vec3f& point) + { + mConstructed = true; + mPath.push_back(point); + } + + /// utility function to convert a osg::Vec3f to a Pathgrid::Point + static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v) + { + return ESM::Pathgrid::Point(static_cast(v[0]), static_cast(v[1]), static_cast(v[2])); + } + + /// utility function to convert an ESM::Position to a Pathgrid::Point + static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p) + { + return ESM::Pathgrid::Point( + static_cast(p.pos[0]), static_cast(p.pos[1]), static_cast(p.pos[2])); + } + + static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p) + { + return osg::Vec3f(static_cast(p.mX), static_cast(p.mY), static_cast(p.mZ)); + } + + // Slightly cheaper version for comparisons. + // Caller needs to be careful for very short distances (i.e. less than 1) + // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 + // + static float distanceSquared(const ESM::Pathgrid::Point& point, const osg::Vec3f& pos) + { + return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2(); + } + + // Return the closest pathgrid point index from the specified position + // coordinates. NOTE: Does not check if there is a sensible way to get there + // (e.g. a cliff in front). + // + // NOTE: pos is expected to be in local coordinates, as is grid->mPoints + // + static int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) + { + assert(grid && !grid->mPoints.empty()); + + float distanceBetween = distanceSquared(grid->mPoints[0], pos); + int closestIndex = 0; + + // TODO: if this full scan causes performance problems mapping pathgrid + // points to a quadtree may help + for (unsigned int counter = 1; counter < grid->mPoints.size(); counter++) { +<<<<<<< HEAD } void clearPath() @@ -189,31 +309,38 @@ namespace MWMechanics // TODO: if this full scan causes performance problems mapping pathgrid // points to a quadtree may help for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) +======= + float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); + if (potentialDistBetween < distanceBetween) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); - if(potentialDistBetween < distanceBetween) - { - distanceBetween = potentialDistBetween; - closestIndex = counter; - } + distanceBetween = potentialDistBetween; + closestIndex = counter; } - - return closestIndex; } - private: - bool mConstructed; - std::deque mPath; + return closestIndex; + } - const MWWorld::CellStore* mCell; + private: + bool mConstructed = false; + std::deque mPath; + const MWWorld::CellStore* mCell = nullptr; - void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const PathgridGraph& pathgridGraph, std::back_insert_iterator> out); + void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const PathgridGraph& pathgridGraph, std::back_insert_iterator> out); +<<<<<<< HEAD [[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out); +======= + [[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, + const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, + PathType pathType, std::back_insert_iterator> out); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; } diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index ee1de3b5a..8282e32e8 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -1,10 +1,7 @@ #include "pathgrid.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - -#include "../mwworld/cellstore.hpp" -#include "../mwworld/esmstore.hpp" +#include +#include namespace { @@ -42,23 +39,100 @@ namespace // - faster but not the shortest path float costAStar(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) { - //return distance(a, b); + // return distance(a, b); return manhattan(a, b); } + + constexpr size_t NoIndex = static_cast(-1); } namespace MWMechanics { - PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell) - : mCell(nullptr) - , mPathgrid(nullptr) - , mGraph(0) - , mIsGraphConstructed(false) - , mSCCId(0) - , mSCCIndex(0) + + class PathgridGraph::Builder { - load(cell); - } + std::vector& mGraph; + + // variables used to calculate connected components + int mSCCId = 0; + size_t mSCCIndex = 0; + std::vector mSCCStack; + std::vector> mSCCPoint; // first is index, second is lowlink + + // v is the pathgrid point index (some call them vertices) + void recursiveStrongConnect(const size_t v) + { + mSCCPoint[v].first = mSCCIndex; // index + mSCCPoint[v].second = mSCCIndex; // lowlink + mSCCIndex++; + mSCCStack.push_back(v); + size_t w; + + for (const auto& edge : mGraph[v].edges) + { + w = edge.index; + if (mSCCPoint[w].first == NoIndex) // not visited + { + recursiveStrongConnect(w); // recurse + mSCCPoint[v].second = std::min(mSCCPoint[v].second, mSCCPoint[w].second); + } + else if (std::find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) + mSCCPoint[v].second = std::min(mSCCPoint[v].second, mSCCPoint[w].first); + } + + if (mSCCPoint[v].second == mSCCPoint[v].first) + { // new component + do + { + w = mSCCStack.back(); + mSCCStack.pop_back(); + mGraph[w].componentId = mSCCId; + } while (w != v); + mSCCId++; + } + } + + public: + /* + * mGraph contains the strongly connected component group id's along + * with pre-calculated edge costs. + * + * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 + * + * mGraph for Seyda Neen will therefore have 3 different values. When + * selecting a random pathgrid point for AiWander, mGraph can be checked + * for quickly finding whether the destination is reachable. + * + * Otherwise, buildPath can automatically select a closest reachable end + * pathgrid point (reachable from the closest start point). + * + * Using Tarjan's algorithm: + * + * mGraph | graph G | + * mSCCPoint | V | derived from mPoints + * mGraph[v].edges | E (for v) | + * mSCCIndex | index | tracking smallest unused index + * mSCCStack | S | + * mGraph[v].edges[i].index | w | + * + */ + explicit Builder(PathgridGraph& graph) + : mGraph(graph.mGraph) + { + // both of these are set to zero in the constructor + // mSCCId = 0; // how many strongly connected components in this cell + // mSCCIndex = 0; + size_t pointsSize = graph.mPathgrid->mPoints.size(); + mSCCPoint.resize(pointsSize, std::pair(NoIndex, NoIndex)); + mSCCStack.reserve(pointsSize); + + for (size_t v = 0; v < pointsSize; ++v) + { + if (mSCCPoint[v].first == NoIndex) // undefined (haven't visited) + recursiveStrongConnect(v); + } + } + }; /* * mGraph is populated with the cost of each allowed edge. @@ -96,135 +170,38 @@ namespace MWMechanics * +----------------> * high cost */ - bool PathgridGraph::load(const MWWorld::CellStore *cell) + PathgridGraph::PathgridGraph(const ESM::Pathgrid& pathgrid) + : mPathgrid(&pathgrid) { - if(!cell) - return false; - - if(mIsGraphConstructed) - return true; - - mCell = cell->getCell(); - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell->getCell()); - if(!mPathgrid) - return false; - - mGraph.resize(mPathgrid->mPoints.size()); - for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) + for (const auto& edge : mPathgrid->mEdges) { ConnectedPoint neighbour; - neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], - mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); + neighbour.cost = costAStar(mPathgrid->mPoints[edge.mV0], mPathgrid->mPoints[edge.mV1]); // forward path of the edge - neighbour.index = mPathgrid->mEdges[i].mV1; - mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); + neighbour.index = edge.mV1; + mGraph[edge.mV0].edges.push_back(neighbour); // reverse path of the edge // NOTE: These are redundant, ESM already contains the required reverse paths - //neighbour.index = mPathgrid->mEdges[i].mV0; - //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); + // neighbour.index = edge.mV0; + // mGraph[edge.mV1].edges.push_back(neighbour); } - buildConnectedPoints(); - mIsGraphConstructed = true; - return true; + Builder(*this); } - const ESM::Pathgrid *PathgridGraph::getPathgrid() const - { - return mPathgrid; - } + const PathgridGraph PathgridGraph::sEmpty = {}; - // v is the pathgrid point index (some call them vertices) - void PathgridGraph::recursiveStrongConnect(int v) - { - mSCCPoint[v].first = mSCCIndex; // index - mSCCPoint[v].second = mSCCIndex; // lowlink - mSCCIndex++; - mSCCStack.push_back(v); - int w; - - for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) - { - w = mGraph[v].edges[i].index; - if(mSCCPoint[w].first == -1) // not visited - { - recursiveStrongConnect(w); // recurse - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].second); - } - else - { - if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].first); - } - } - - if(mSCCPoint[v].second == mSCCPoint[v].first) - { // new component - do - { - w = mSCCStack.back(); - mSCCStack.pop_back(); - mGraph[w].componentId = mSCCId; - } - while(w != v); - mSCCId++; - } - return; - } - - /* - * mGraph contains the strongly connected component group id's along - * with pre-calculated edge costs. - * - * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 - * - * mGraph for Seyda Neen will therefore have 3 different values. When - * selecting a random pathgrid point for AiWander, mGraph can be checked - * for quickly finding whether the destination is reachable. - * - * Otherwise, buildPath can automatically select a closest reachable end - * pathgrid point (reachable from the closest start point). - * - * Using Tarjan's algorithm: - * - * mGraph | graph G | - * mSCCPoint | V | derived from mPoints - * mGraph[v].edges | E (for v) | - * mSCCIndex | index | tracking smallest unused index - * mSCCStack | S | - * mGraph[v].edges[i].index | w | - * - */ - void PathgridGraph::buildConnectedPoints() - { - // both of these are set to zero in the constructor - //mSCCId = 0; // how many strongly connected components in this cell - //mSCCIndex = 0; - int pointsSize = static_cast (mPathgrid->mPoints.size()); - mSCCPoint.resize(pointsSize, std::pair (-1, -1)); - mSCCStack.reserve(pointsSize); - - for(int v = 0; v < pointsSize; v++) - { - if(mSCCPoint[v].first == -1) // undefined (haven't visited) - recursiveStrongConnect(v); - } - } - - bool PathgridGraph::isPointConnected(const int start, const int end) const + bool PathgridGraph::isPointConnected(const size_t start, const size_t end) const { return (mGraph[start].componentId == mGraph[end].componentId); } - void PathgridGraph::getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const + void PathgridGraph::getNeighbouringPoints(const size_t index, ESM::Pathgrid::PointList& nodes) const { - for(int i = 0; i < static_cast (mGraph[index].edges.size()); i++) + for (const auto& edge : mGraph[index].edges) { - int neighbourIndex = mGraph[index].edges[i].index; - if (neighbourIndex != index) - nodes.push_back(mPathgrid->mPoints[neighbourIndex]); + if (edge.index != index) + nodes.push_back(mPathgrid->mPoints[edge.index]); } } @@ -250,69 +227,66 @@ namespace MWMechanics * gScore - past accumulated costs vector indexed by point index * fScore - future estimated costs vector indexed by point index * - * TODO: An intersting exercise might be to cache the paths created for a + * TODO: An interesting exercise might be to cache the paths created for a * start/goal pair. To cache the results the paths need to be in * pathgrid points form (currently they are converted to world * coordinates). Essentially trading speed w/ memory. */ - std::deque PathgridGraph::aStarSearch(const int start, const int goal) const + std::deque PathgridGraph::aStarSearch(const size_t start, const size_t goal) const { std::deque path; - if(!isPointConnected(start, goal)) + if (!isPointConnected(start, goal)) { return path; // there is no path, return an empty path } - int graphSize = static_cast (mGraph.size()); - std::vector gScore (graphSize, -1); - std::vector fScore (graphSize, -1); - std::vector graphParent (graphSize, -1); + size_t graphSize = mGraph.size(); + std::vector gScore(graphSize, -1); + std::vector fScore(graphSize, -1); + std::vector graphParent(graphSize, NoIndex); // gScore & fScore keep costs for each pathgrid point in mPoints gScore[start] = 0; fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); - std::list openset; - std::list closedset; + std::list openset; + std::set closedset; openset.push_back(start); - int current = -1; + size_t current = start; - while(!openset.empty()) + while (!openset.empty()) { current = openset.front(); // front has the lowest cost openset.pop_front(); - if(current == goal) + if (current == goal) break; - closedset.push_back(current); // remember we've been here + closedset.insert(current); // remember we've been here // check all edges for the current point index - for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) + for (const auto& edge : mGraph[current].edges) { - if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == - closedset.end()) + if (!closedset.contains(edge.index)) { // not in closedset - i.e. have not traversed this edge destination - int dest = mGraph[current].edges[j].index; - float tentative_g = gScore[current] + mGraph[current].edges[j].cost; + size_t dest = edge.index; + float tentative_g = gScore[current] + edge.cost; bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); - if(!isInOpenSet - || tentative_g < gScore[dest]) + if (!isInOpenSet || tentative_g < gScore[dest]) { graphParent[dest] = current; gScore[dest] = tentative_g; - fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], - mPathgrid->mPoints[goal]); - if(!isInOpenSet) + fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], mPathgrid->mPoints[goal]); + if (!isInOpenSet) { // add this edge to openset, lowest cost goes to the front // TODO: if this causes performance problems a hash table may help - std::list::iterator it = openset.begin(); - for(it = openset.begin(); it!= openset.end(); ++it) + auto it = openset.begin(); + for (; it != openset.end(); ++it) { - if(fScore[*it] > fScore[dest]) + if (fScore[*it] > fScore[dest]) break; } openset.insert(it, dest); @@ -322,11 +296,11 @@ namespace MWMechanics } } - if(current != goal) + if (current != goal) return path; // for some reason couldn't build a path // reconstruct path to return, using local coordinates - while(graphParent[current] != -1) + while (graphParent[current] != NoIndex) { path.push_front(mPathgrid->mPoints[current]); current = graphParent[current]; @@ -337,4 +311,3 @@ namespace MWMechanics return path; } } - diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 050504617..942b6426b 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -3,81 +3,65 @@ #include -#include - -namespace ESM -{ - struct Cell; -} - -namespace MWWorld -{ - class CellStore; -} +#include namespace MWMechanics { class PathgridGraph { - public: - PathgridGraph(const MWWorld::CellStore* cell); + PathgridGraph() + : mPathgrid(nullptr) + { + } - bool load(const MWWorld::CellStore *cell); + public: + explicit PathgridGraph(const ESM::Pathgrid& pathGrid); - const ESM::Pathgrid* getPathgrid() const; + const ESM::Pathgrid* getPathgrid() const { return mPathgrid; } - // 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; + // 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 size_t start, const size_t end) const; - // get neighbouring nodes for index node and put them to "nodes" vector - void getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const; + // get neighbouring nodes for index node and put them to "nodes" vector + void getNeighbouringPoints(const size_t 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 - // - // NOTE: if start equals end an empty path is returned - std::deque aStarSearch(const int start, const int end) const; + // the input parameters are pathgrid point indexes + // the output list is in local (internal cells) or world (external + // cells) coordinates + // + // NOTE: if start equals end an empty path is returned + std::deque aStarSearch(const size_t start, const size_t end) const; - private: + static const PathgridGraph sEmpty; - const ESM::Cell *mCell; - const ESM::Pathgrid *mPathgrid; + private: + const ESM::Pathgrid* mPathgrid; - struct ConnectedPoint // edge - { - int index; // pathgrid point index of neighbour - float cost; - }; + class Builder; - struct Node // point - { - int componentId; - std::vector edges; // neighbours - }; + struct ConnectedPoint // edge + { + size_t index; // pathgrid point index of neighbour + float cost; + }; - // componentId is an integer indicating the groups of connected - // pathgrid points (all connected points will have the same value) - // - // In Seyda Neen there are 3: - // - // 52, 53 and 54 are one set (enclosed yard) - // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) - // all other pathgrid points are the third set - // - std::vector mGraph; - bool mIsGraphConstructed; + struct Node // point + { + int componentId; + std::vector edges; // neighbours + }; - // variables used to calculate connected components - int mSCCId; - int mSCCIndex; - std::vector mSCCStack; - typedef std::pair VPair; // first is index, second is lowlink - std::vector mSCCPoint; - // methods used to calculate connected components - void recursiveStrongConnect(int v); - void buildConnectedPoints(); + // componentId is an integer indicating the groups of connected + // pathgrid points (all connected points will have the same value) + // + // In Seyda Neen there are 3: + // + // 52, 53 and 54 are one set (enclosed yard) + // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) + // all other pathgrid points are the third set + // + std::vector mGraph; }; } diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp index 05e8a0393..5e153191c 100644 --- a/apps/openmw/mwmechanics/pickpocket.cpp +++ b/apps/openmw/mwmechanics/pickpocket.cpp @@ -5,21 +5,21 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "npcstats.hpp" namespace MWMechanics { - Pickpocket::Pickpocket(const MWWorld::Ptr &thief, const MWWorld::Ptr &victim) + Pickpocket::Pickpocket(const MWWorld::Ptr& thief, const MWWorld::Ptr& victim) : mThief(thief) , mVictim(victim) { } - float Pickpocket::getChanceModifier(const MWWorld::Ptr &ptr, float add) + float Pickpocket::getChanceModifier(const MWWorld::Ptr& ptr, float add) { NpcStats& stats = ptr.getClass().getNpcStats(ptr); float agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); @@ -33,15 +33,22 @@ namespace MWMechanics float x = getChanceModifier(mThief); float y = getChanceModifier(mVictim, valueTerm); - float t = 2*x - y; + float t = 2 * x - y; float pcSneak = static_cast(mThief.getClass().getSkill(mThief, ESM::Skill::Sneak)); - int iPickMinChance = MWBase::Environment::get().getWorld()->getStore().get() - .find("iPickMinChance")->mValue.getInteger(); - int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get() - .find("iPickMaxChance")->mValue.getInteger(); + int iPickMinChance = MWBase::Environment::get() + .getESMStore() + ->get() + .find("iPickMinChance") + ->mValue.getInteger(); + int iPickMaxChance = MWBase::Environment::get() + .getESMStore() + ->get() + .find("iPickMaxChance") + ->mValue.getInteger(); - int roll = Misc::Rng::roll0to99(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); if (t < pcSneak / iPickMinChance) { return (roll > int(pcSneak / iPickMinChance)); @@ -53,11 +60,14 @@ namespace MWMechanics } } - bool Pickpocket::pick(MWWorld::Ptr item, int count) + bool Pickpocket::pick(const MWWorld::Ptr& item, int count) { float stackValue = static_cast(item.getClass().getValue(item) * count); - float fPickPocketMod = MWBase::Environment::get().getWorld()->getStore().get() - .find("fPickPocketMod")->mValue.getFloat(); + float fPickPocketMod = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fPickPocketMod") + ->mValue.getFloat(); float valueTerm = 10 * fPickPocketMod * stackValue; return getDetected(valueTerm); diff --git a/apps/openmw/mwmechanics/pickpocket.hpp b/apps/openmw/mwmechanics/pickpocket.hpp index 4de1e37f8..83dbca69c 100644 --- a/apps/openmw/mwmechanics/pickpocket.hpp +++ b/apps/openmw/mwmechanics/pickpocket.hpp @@ -9,18 +9,18 @@ namespace MWMechanics class Pickpocket { public: - Pickpocket (const MWWorld::Ptr& thief, const MWWorld::Ptr& victim); + Pickpocket(const MWWorld::Ptr& thief, const MWWorld::Ptr& victim); /// Steal some items /// @return Was the thief detected? - bool pick (MWWorld::Ptr item, int count); + bool pick(const MWWorld::Ptr& item, int count); /// End the pickpocketing process /// @return Was the thief detected? - bool finish (); + bool finish(); private: bool getDetected(float valueTerm); - float getChanceModifier(const MWWorld::Ptr& ptr, float add=0); + float getChanceModifier(const MWWorld::Ptr& ptr, float add = 0); MWWorld::Ptr mThief; MWWorld::Ptr mVictim; }; diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index 86db81bb8..106131ec6 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -1,7 +1,11 @@ #include "recharge.hpp" +#include +#include #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -16,65 +20,77 @@ */ #include "../mwbase/world.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "creaturestats.hpp" #include "actorutil.hpp" +#include "creaturestats.hpp" +#include "spellutil.hpp" namespace MWMechanics { -bool rechargeItem(const MWWorld::Ptr &item, const float maxCharge, const float duration) -{ - float charge = item.getCellRef().getEnchantmentCharge(); - if (charge == -1 || charge == maxCharge) - return false; - - static const float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( - "fMagicItemRechargePerSecond")->mValue.getFloat(); - - item.getCellRef().setEnchantmentCharge(std::min(charge + fMagicItemRechargePerSecond * duration, maxCharge)); - return true; -} - -bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem) -{ - if (!gem.getRefData().getCount()) - return false; - - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - - float luckTerm = 0.1f * stats.getAttribute(ESM::Attribute::Luck).getModified(); - if (luckTerm < 1 || luckTerm > 10) - luckTerm = 1; - - float intelligenceTerm = 0.2f * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); - - if (intelligenceTerm > 20) - intelligenceTerm = 20; - if (intelligenceTerm < 1) - intelligenceTerm = 1; - - float x = (player.getClass().getSkill(player, ESM::Skill::Enchant) + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); - int roll = Misc::Rng::roll0to99(); - if (roll < x) + bool rechargeItem(const MWWorld::Ptr& item, const float maxCharge, const float duration) { - std::string soul = gem.getCellRef().getSoul(); - const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); + float charge = item.getCellRef().getEnchantmentCharge(); + if (charge == -1 || charge == maxCharge) + return false; - float restored = creature->mData.mSoul * (roll / x); + static const float fMagicItemRechargePerSecond = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fMagicItemRechargePerSecond") + ->mValue.getFloat(); - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + item.getCellRef().setEnchantmentCharge(std::min(charge + fMagicItemRechargePerSecond * duration, maxCharge)); + return true; + } + + bool rechargeItem(const MWWorld::Ptr& item, const MWWorld::Ptr& gem) + { + if (!gem.getRefData().getCount()) + return false; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + + float luckTerm = 0.1f * stats.getAttribute(ESM::Attribute::Luck).getModified(); + if (luckTerm < 1 || luckTerm > 10) + luckTerm = 1; + + float intelligenceTerm = 0.2f * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); + + if (intelligenceTerm > 20) + intelligenceTerm = 20; + if (intelligenceTerm < 1) + intelligenceTerm = 1; + + float x = (player.getClass().getSkill(player, ESM::Skill::Enchant) + intelligenceTerm + luckTerm) + * stats.getFatigueTerm(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); + if (roll < x) + { + const ESM::RefId& soul = gem.getCellRef().getSoul(); + const ESM::Creature* creature = MWBase::Environment::get().getESMStore()->get().find(soul); + + float restored = creature->mData.mSoul * (roll / x); + + const ESM::Enchantment* enchantment + = MWBase::Environment::get().getESMStore()->get().find( item.getClass().getEnchantment(item)); - item.getCellRef().setEnchantmentCharge( - std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast(enchantment->mData.mCharge))); + const int maxCharge = MWMechanics::getEnchantmentCharge(*enchantment); + item.getCellRef().setEnchantmentCharge( + std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast(maxCharge))); +<<<<<<< HEAD /* Start of tes3mp change (minor) @@ -148,5 +164,38 @@ bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem) return true; } +======= + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Enchant Success")); + + player.getClass().getContainerStore(player).restack(item); + } + else + { + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Enchant Fail")); + } + + player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0); + gem.getContainerStore()->remove(gem, 1); + + if (gem.getRefData().getCount() == 0) + { + std::string message = MWBase::Environment::get() + .getESMStore() + ->get() + .find("sNotifyMessage51") + ->mValue.getString(); + message = Misc::StringUtils::format(message, gem.getClass().getName(gem)); + + MWBase::Environment::get().getWindowManager()->messageBox(message); + + const ESM::RefId soulGemAzura = ESM::RefId::stringRefId("Misc_SoulGem_Azura"); + // special case: readd Azura's Star + if (gem.get()->mBase->mId == soulGemAzura) + player.getClass().getContainerStore(player).add(soulGemAzura, 1); + } + + return true; + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } diff --git a/apps/openmw/mwmechanics/recharge.hpp b/apps/openmw/mwmechanics/recharge.hpp index 913f2109b..ac1c7ced5 100644 --- a/apps/openmw/mwmechanics/recharge.hpp +++ b/apps/openmw/mwmechanics/recharge.hpp @@ -6,9 +6,9 @@ namespace MWMechanics { - bool rechargeItem(const MWWorld::Ptr &item, const float maxCharge, const float duration); + bool rechargeItem(const MWWorld::Ptr& item, const float maxCharge, const float duration); - bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem); + bool rechargeItem(const MWWorld::Ptr& item, const MWWorld::Ptr& gem); } diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index a6b4e483d..91b6e652f 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -1,7 +1,9 @@ #include "repair.hpp" #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -16,53 +18,28 @@ */ #include "../mwbase/world.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "creaturestats.hpp" #include "actorutil.hpp" +#include "creaturestats.hpp" namespace MWMechanics { -void Repair::repair(const MWWorld::Ptr &itemToRepair) -{ - MWWorld::Ptr player = getPlayer(); - MWWorld::LiveCellRef *ref = - mTool.get(); - - // unstack tool if required - player.getClass().getContainerStore(player).unstack(mTool, player); - - // reduce number of uses left - int uses = mTool.getClass().getItemHealth(mTool); - uses -= std::min(uses, 1); - mTool.getCellRef().setCharge(uses); - - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - - float fatigueTerm = stats.getFatigueTerm(); - float pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified(); - float pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); - float armorerSkill = player.getClass().getSkill(player, ESM::Skill::Armorer); - - float fRepairAmountMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fRepairAmountMult")->mValue.getFloat(); - - float toolQuality = ref->mBase->mData.mQuality; - - float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm; - - int roll = Misc::Rng::roll0to99(); - if (roll <= x) + void Repair::repair(const MWWorld::Ptr& itemToRepair) { - int y = static_cast(fRepairAmountMult * toolQuality * roll); - y = std::max(1, y); + MWWorld::Ptr player = getPlayer(); + MWWorld::LiveCellRef* ref = mTool.get(); +<<<<<<< HEAD // repair by 'y' points int charge = itemToRepair.getClass().getItemHealth(itemToRepair); charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair)); @@ -87,15 +64,24 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) // attempt to re-stack item, in case it was fully repaired MWWorld::ContainerStoreIterator stacked = player.getClass().getContainerStore(player).restack(itemToRepair); +======= + // unstack tool if required + player.getClass().getContainerStore(player).unstack(mTool); - // set the OnPCRepair variable on the item's script - std::string script = stacked->getClass().getScript(itemToRepair); - if(script != "") - stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); + // reduce number of uses left + int uses = mTool.getClass().getItemHealth(mTool); + uses -= std::min(uses, 1); + mTool.getCellRef().setCharge(uses); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - // increase skill - player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + float fatigueTerm = stats.getFatigueTerm(); + float pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified(); + float pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float armorerSkill = player.getClass().getSkill(player, ESM::Skill::Armorer); + +<<<<<<< HEAD MWBase::Environment::get().getWindowManager()->playSound("Repair"); MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}"); @@ -132,34 +118,79 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) End of tes3mp addition */ } +======= + float fRepairAmountMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fRepairAmountMult") + ->mValue.getFloat(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - // tool used up? - if (mTool.getCellRef().getCharge() == 0) - { - MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); + float toolQuality = ref->mBase->mData.mQuality; - store.remove(mTool, 1, player); + float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm; - std::string message = MWBase::Environment::get().getWorld()->getStore().get() - .find("sNotifyMessage51")->mValue.getString(); - message = Misc::StringUtils::format(message, mTool.getClass().getName(mTool)); - - MWBase::Environment::get().getWindowManager()->messageBox(message); - - // try to find a new tool of the same ID - for (MWWorld::ContainerStoreIterator iter (store.begin()); - iter!=store.end(); ++iter) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); + if (roll <= x) { - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), mTool.getCellRef().getRefId())) + int y = static_cast(fRepairAmountMult * toolQuality * roll); + y = std::max(1, y); + + // repair by 'y' points + int charge = itemToRepair.getClass().getItemHealth(itemToRepair); + charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair)); + itemToRepair.getCellRef().setCharge(charge); + + // attempt to re-stack item, in case it was fully repaired + MWWorld::ContainerStoreIterator stacked = player.getClass().getContainerStore(player).restack(itemToRepair); + + // set the OnPCRepair variable on the item's script + const ESM::RefId& script = stacked->getClass().getScript(itemToRepair); + if (!script.empty()) + stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); + + // increase skill + player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); + + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair")); + MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}"); + } + else + { + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair Fail")); + MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairFailed}"); + } + + // tool used up? + if (mTool.getCellRef().getCharge() == 0) + { + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); + + store.remove(mTool, 1); + + std::string message = MWBase::Environment::get() + .getESMStore() + ->get() + .find("sNotifyMessage51") + ->mValue.getString(); + message = Misc::StringUtils::format(message, mTool.getClass().getName(mTool)); + + MWBase::Environment::get().getWindowManager()->messageBox(message); + + // try to find a new tool of the same ID + for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) { - mTool = *iter; + if (iter->getCellRef().getRefId() == mTool.getCellRef().getRefId()) + { + mTool = *iter; - MWBase::Environment::get().getWindowManager()->playSound("Item Repair Up"); + MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Item Repair Up")); - break; + break; + } } } } -} } diff --git a/apps/openmw/mwmechanics/repair.hpp b/apps/openmw/mwmechanics/repair.hpp index 6f9a866af..a596f0d28 100644 --- a/apps/openmw/mwmechanics/repair.hpp +++ b/apps/openmw/mwmechanics/repair.hpp @@ -9,10 +9,10 @@ namespace MWMechanics class Repair { public: - void setTool (const MWWorld::Ptr& tool) { mTool = tool; } + void setTool(const MWWorld::Ptr& tool) { mTool = tool; } MWWorld::Ptr getTool() { return mTool; } - void repair (const MWWorld::Ptr& itemToRepair); + void repair(const MWWorld::Ptr& itemToRepair); private: MWWorld::Ptr mTool; diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index 577cdc269..3dc86ada2 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -20,16 +20,16 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" #include "creaturestats.hpp" namespace MWMechanics { - Security::Security(const MWWorld::Ptr &actor) + Security::Security(const MWWorld::Ptr& actor) : mActor(actor) { CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); @@ -39,12 +39,12 @@ namespace MWMechanics mFatigueTerm = creatureStats.getFatigueTerm(); } - void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick, - std::string& resultMessage, std::string& resultSound) + void Security::pickLock(const MWWorld::Ptr& lock, const MWWorld::Ptr& lockpick, std::string_view& resultMessage, + std::string_view& resultSound) { - if (lock.getCellRef().getLockLevel() <= 0 || - lock.getCellRef().getLockLevel() == ESM::UnbreakableLock || - !lock.getClass().hasToolTip(lock)) //If it's unlocked or can not be unlocked back out immediately + // If it's unlocked or can not be unlocked back out immediately. Note that we're not strictly speaking checking + // if the ref is locked, lock levels <= 0 can exist but they cannot be picked + if (lock.getCellRef().getLockLevel() <= 0 || !lock.getClass().hasToolTip(lock)) return; int uses = lockpick.getClass().getItemHealth(lockpick); @@ -55,7 +55,11 @@ namespace MWMechanics float pickQuality = lockpick.get()->mBase->mData.mQuality; - float fPickLockMult = MWBase::Environment::get().getWorld()->getStore().get().find("fPickLockMult")->mValue.getFloat(); + float fPickLockMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fPickLockMult") + ->mValue.getFloat(); float x = 0.2f * mAgility + 0.1f * mLuck + mSecuritySkill; x *= pickQuality * mFatigueTerm; @@ -68,7 +72,8 @@ namespace MWMechanics resultMessage = "#{sLockImpossible}"; else { - if (Misc::Rng::roll0to99() <= x) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) <= x) { /* Start of tes3mp change (major) @@ -105,11 +110,11 @@ namespace MWMechanics lockpick.getCellRef().setCharge(--uses); if (!uses) - lockpick.getContainerStore()->remove(lockpick, 1, mActor); + lockpick.getContainerStore()->remove(lockpick, 1); } - void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe, - std::string& resultMessage, std::string& resultSound) + void Security::probeTrap(const MWWorld::Ptr& trap, const MWWorld::Ptr& probe, std::string_view& resultMessage, + std::string_view& resultSound) { if (trap.getCellRef().getTrap().empty()) return; @@ -120,10 +125,15 @@ namespace MWMechanics float probeQuality = probe.get()->mBase->mData.mQuality; - const ESM::Spell* trapSpell = MWBase::Environment::get().getWorld()->getStore().get().find(trap.getCellRef().getTrap()); + const ESM::Spell* trapSpell + = MWBase::Environment::get().getESMStore()->get().find(trap.getCellRef().getTrap()); int trapSpellPoints = trapSpell->mData.mCost; - float fTrapCostMult = MWBase::Environment::get().getWorld()->getStore().get().find("fTrapCostMult")->mValue.getFloat(); + float fTrapCostMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fTrapCostMult") + ->mValue.getFloat(); float x = 0.2f * mAgility + 0.1f * mLuck + mSecuritySkill; x += fTrapCostMult * trapSpellPoints; @@ -136,8 +146,10 @@ namespace MWMechanics resultMessage = "#{sTrapImpossible}"; else { - if (Misc::Rng::roll0to99() <= x) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) <= x) { +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -148,6 +160,9 @@ namespace MWMechanics /* End of tes3mp change (major) */ +======= + trap.getCellRef().setTrap(ESM::RefId()); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 resultSound = "Disarm Trap"; resultMessage = "#{sTrapSuccess}"; @@ -173,7 +188,7 @@ namespace MWMechanics probe.getCellRef().setCharge(--uses); if (!uses) - probe.getContainerStore()->remove(probe, 1, mActor); + probe.getContainerStore()->remove(probe, 1); } } diff --git a/apps/openmw/mwmechanics/security.hpp b/apps/openmw/mwmechanics/security.hpp index f3efb04ed..7765bcb5d 100644 --- a/apps/openmw/mwmechanics/security.hpp +++ b/apps/openmw/mwmechanics/security.hpp @@ -10,12 +10,12 @@ namespace MWMechanics class Security { public: - Security (const MWWorld::Ptr& actor); + Security(const MWWorld::Ptr& actor); - void pickLock (const MWWorld::Ptr& lock, const MWWorld::Ptr& lockpick, - std::string& resultMessage, std::string& resultSound); - void probeTrap (const MWWorld::Ptr& trap, const MWWorld::Ptr& probe, - std::string& resultMessage, std::string& resultSound); + void pickLock(const MWWorld::Ptr& lock, const MWWorld::Ptr& lockpick, std::string_view& resultMessage, + std::string_view& resultSound); + void probeTrap(const MWWorld::Ptr& trap, const MWWorld::Ptr& probe, std::string_view& resultMessage, + std::string_view& resultSound); private: float mAgility, mLuck, mSecuritySkill, mFatigueTerm; diff --git a/apps/openmw/mwmechanics/setbaseaisetting.hpp b/apps/openmw/mwmechanics/setbaseaisetting.hpp new file mode 100644 index 000000000..07e69dddd --- /dev/null +++ b/apps/openmw/mwmechanics/setbaseaisetting.hpp @@ -0,0 +1,40 @@ +#ifndef OPENMW_MWMECHANICS_SETBASEAISETTING_H +#define OPENMW_MWMECHANICS_SETBASEAISETTING_H + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" + +#include "aisetting.hpp" + +namespace MWMechanics +{ + template + void setBaseAISetting(const ESM::RefId& id, MWMechanics::AiSetting setting, int value) + { + T copy = *MWBase::Environment::get().getESMStore()->get().find(id); + switch (setting) + { + case MWMechanics::AiSetting::Hello: + copy.mAiData.mHello = value; + break; + case MWMechanics::AiSetting::Fight: + copy.mAiData.mFight = value; + break; + case MWMechanics::AiSetting::Flee: + copy.mAiData.mFlee = value; + break; + case MWMechanics::AiSetting::Alarm: + copy.mAiData.mAlarm = value; + break; + default: + assert(false); + } + MWBase::Environment::get().getESMStore()->overrideRecord(copy); + } +} + +#endif diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp deleted file mode 100644 index 1a2dfe65a..000000000 --- a/apps/openmw/mwmechanics/spellabsorption.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "spellabsorption.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwrender/animation.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" - -#include "creaturestats.hpp" -#include "spellutil.hpp" - -namespace MWMechanics -{ - - class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor - { - public: - float mProbability{0.f}; - - GetAbsorptionProbability() = default; - - void visit (MWMechanics::EffectKey key, int /*effectIndex*/, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float magnitude, float /*remainingTime*/, float /*totalTime*/) override - { - if (key.mId == ESM::MagicEffect::SpellAbsorption) - { - if (mProbability == 0.f) - mProbability = magnitude / 100; - else - { - // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. - // Real absorption probability will be the (1 - total fail chance) in this case. - float failProbability = 1.f - mProbability; - failProbability *= 1.f - magnitude / 100; - mProbability = 1.f - failProbability; - } - } - } - }; - - int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) - { - if(target.isEmpty() || caster == target || !target.getClass().isActor()) - return 0; - - CreatureStats& stats = target.getClass().getCreatureStats(target); - if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f) - return 0; - - GetAbsorptionProbability check; - stats.getActiveSpells().visitEffectSources(check); - stats.getSpells().visitEffectSources(check); - if (target.getClass().hasInventoryStore(target)) - target.getClass().getInventoryStore(target).visitEffectSources(check); - - return check.mProbability * 100; - } - - void absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) - { - CreatureStats& stats = target.getClass().getCreatureStats(target); - - const auto& esmStore = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Static* absorbStatic = esmStore.get().find("VFX_Absorb"); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation && !absorbStatic->mModel.empty()) - animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string()); - const ESM::Spell* spell = esmStore.get().search(spellId); - int spellCost = 0; - if (spell) - { - spellCost = spell->mData.mCost; - } - else - { - const ESM::Enchantment* enchantment = esmStore.get().search(spellId); - if (enchantment) - spellCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), caster); - } - - // Magicka is increased by the cost of the spell - DynamicStat magicka = stats.getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spellCost); - stats.setMagicka(magicka); - } - -} diff --git a/apps/openmw/mwmechanics/spellabsorption.hpp b/apps/openmw/mwmechanics/spellabsorption.hpp deleted file mode 100644 index 5537483a4..000000000 --- a/apps/openmw/mwmechanics/spellabsorption.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef MWMECHANICS_SPELLABSORPTION_H -#define MWMECHANICS_SPELLABSORPTION_H - -#include - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target); - // Calculate the chance to absorb a spell based on the magnitude of every Spell Absorption effect source on the target. - int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); -} - -#endif diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 818e80b81..52e853987 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1,8 +1,14 @@ #include "spellcasting.hpp" +#include +#include +#include #include +#include #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -23,33 +29,30 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/actionteleport.hpp" -#include "../mwworld/player.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" #include "actorutil.hpp" -#include "aifollow.hpp" #include "creaturestats.hpp" -#include "linkedeffects.hpp" -#include "spellabsorption.hpp" -#include "spellresistance.hpp" +#include "spelleffects.hpp" #include "spellutil.hpp" -#include "summoning.hpp" -#include "tickableeffects.hpp" #include "weapontype.hpp" namespace MWMechanics { - CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) + CastSpell::CastSpell( + const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool manualSpell) : mCaster(caster) , mTarget(target) , mFromProjectile(fromProjectile) @@ -57,7 +60,103 @@ namespace MWMechanics { } - void CastSpell::launchMagicBolt () + void CastSpell::explodeSpell( + const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const + { + const auto world = MWBase::Environment::get().getWorld(); + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + std::map> toApply; + int index = -1; + for (const ESM::ENAMstruct& effectInfo : effects.mList) + { + ++index; + const ESM::MagicEffect* effect = world->getStore().get().find(effectInfo.mEffectID); + + if (effectInfo.mRange != rangeType + || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) + continue; // Not right range type, or not area effect and hit an actor + + if (mFromProjectile && effectInfo.mArea <= 0) + continue; // Don't play explosion for projectiles with 0-area effects + + if (!mFromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() + && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore) + && (mCaster.isEmpty() || mCaster.getClass().isActor())) + continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from + // a projectile enchantment or ExplodeSpell + + // Spawn the explosion orb effect + const ESM::Static* areaStatic; + if (!effect->mArea.empty()) + areaStatic = world->getStore().get().find(effect->mArea); + else + areaStatic = world->getStore().get().find(ESM::RefId::stringRefId("VFX_DefaultArea")); + + const std::string& texture = effect->mParticle; + + if (effectInfo.mArea <= 0) + { + if (effectInfo.mRange == ESM::RT_Target) + world->spawnEffect( + Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, mHitPosition, 1.0f); + continue; + } + else + world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, + mHitPosition, static_cast(effectInfo.mArea * 2)); + + // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) + { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + if (!effect->mAreaSound.empty()) + sndMgr->playSound3D(mHitPosition, effect->mAreaSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(mHitPosition, + world->getStore().get().find(effect->mData.mSchool)->mSchool->mAreaSound, 1.0f, + 1.0f); + } + // Get the actors in range of the effect + std::vector objects; + static const int unitsPerFoot = ceil(Constants::UnitsPerFoot); + MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( + mHitPosition, static_cast(effectInfo.mArea * unitsPerFoot), objects); + for (const MWWorld::Ptr& affected : objects) + { + // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing + // range. + if (affected.getClass().isActor() && !world->isActorCollisionEnabled(affected)) + continue; + + auto& list = toApply[affected]; + while (list.size() < static_cast(index)) + { + // Insert dummy effects to preserve indices + auto& dummy = list.emplace_back(effectInfo); + dummy.mRange = ESM::RT_Self; + assert(dummy.mRange != rangeType); + } + list.push_back(effectInfo); + } + } + + // Now apply the appropriate effects to each actor in range + for (auto& applyPair : toApply) + { + // Vanilla-compatible behaviour of never applying the spell to the caster + // (could be changed by mods later) + if (applyPair.first == mCaster) + continue; + + if (applyPair.first == ignore) + continue; + + ESM::EffectList effectsToApply; + effectsToApply.mList = applyPair.second; + inflict(applyPair.first, effectsToApply, rangeType, true); + } + } + + void CastSpell::launchMagicBolt() const { osg::Vec3f fallbackDirection(0, 1, 0); osg::Vec3f offset(0, 0, 0); @@ -67,15 +166,14 @@ namespace MWMechanics // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) if (!mTarget.isEmpty()) - fallbackDirection = - (mTarget.getRefData().getPosition().asVec3() + offset) - - (mCaster.getRefData().getPosition().asVec3()); + fallbackDirection = (mTarget.getRefData().getPosition().asVec3() + offset) + - (mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot); } - void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, - const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) + void CastSpell::inflict( + const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded) const { const bool targetIsActor = !target.isEmpty() && target.getClass().isActor(); if (targetIsActor) @@ -88,34 +186,49 @@ namespace MWMechanics // If none of the effects need to apply, we can early-out bool found = false; + bool containsRecastable = false; + std::vector magicEffects; + magicEffects.reserve(effects.mList.size()); + const auto& store = MWBase::Environment::get().getESMStore()->get(); for (const ESM::ENAMstruct& effect : effects.mList) { if (effect.mRange == range) { found = true; - break; + const ESM::MagicEffect* magicEffect = store.find(effect.mEffectID); + // caster needs to be an actor for linked effects (e.g. Absorb) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked + && (mCaster.isEmpty() || !mCaster.getClass().isActor())) + { + magicEffects.push_back(nullptr); + continue; + } + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable)) + containsRecastable = true; + magicEffects.push_back(magicEffect); } + else + magicEffects.push_back(nullptr); } if (!found) return; - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); - if (spell && targetIsActor && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) - { - int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? - ESM::MagicEffect::ResistCommonDisease - : ESM::MagicEffect::ResistBlightDisease; - float x = target.getClass().getCreatureStats(target).getMagicEffects().get(requiredResistance).getMagnitude(); + ActiveSpells::ActiveSpellParams params(*this, mCaster); + bool castByPlayer = (!mCaster.isEmpty() && mCaster == getPlayer()); - if (Misc::Rng::roll0to99() <= x) - { - // Fully resisted, show message - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - return; - } + const ActiveSpells* targetSpells = nullptr; + if (targetIsActor) + targetSpells = &target.getClass().getCreatureStats(target).getActiveSpells(); + + // Re-casting a bound equipment effect has no effect if the spell is still active + if (!containsRecastable && targetSpells && targetSpells->isSpellActive(mId)) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); + return; } +<<<<<<< HEAD ESM::EffectList reflectedEffects; std::vector appliedLastingEffects; @@ -141,23 +254,53 @@ namespace MWMechanics int currentEffectIndex = 0; for (std::vector::const_iterator effectIt (effects.mList.begin()); !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) +======= + for (size_t currentEffectIndex = 0; !target.isEmpty() && currentEffectIndex < effects.mList.size(); + ++currentEffectIndex) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - if (effectIt->mRange != range) + const ESM::ENAMstruct& enam = effects.mList[currentEffectIndex]; + if (enam.mRange != range) continue; - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); + const ESM::MagicEffect* magicEffect = magicEffects[currentEffectIndex]; + if (!magicEffect) + continue; - // Re-casting a bound equipment effect has no effect if the spell is still active - if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells.isSpellActive(mId)) + ActiveSpells::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mMagnMin; + effect.mMaxMagnitude = enam.mMagnMax; + effect.mTimeLeft = 0.f; + effect.mEffectIndex = static_cast(currentEffectIndex); + effect.mFlags = ESM::ActiveEffect::Flag_None; + if (mManualSpell) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; + + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + effect.mDuration = hasDuration ? static_cast(enam.mDuration) : 1.f; + + bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; + if (!appliedOnce) + effect.mDuration = std::max(1.f, effect.mDuration); + + effect.mTimeLeft = effect.mDuration; + + // add to list of active effects, to apply in next frame + params.getEffects().emplace_back(effect); + + bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful + || enam.mEffectID == ESM::MagicEffect::RestoreHealth; + if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth) { - if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); - continue; + // If player is attempting to cast a harmful spell on or is healing a living target, show the target's + // HP bar. + MWBase::Environment::get().getWindowManager()->setEnemy(target); } - canCastAnEffect = true; +<<<<<<< HEAD // Try absorbing the effect if(absorbChance && Misc::Rng::roll0to99() < absorbChance) { @@ -388,19 +531,22 @@ namespace MWMechanics if (anim && !castStatic->mModel.empty()) anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); } +======= + if (!targetIsActor && magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + { + playEffects(target, *magicEffect); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } if (!exploded) - MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); + explodeSpell(effects, target, range); if (!target.isEmpty()) { - if (!reflectedEffects.mList.empty()) - inflict(caster, target, reflectedEffects, range, true, exploded); - - if (!appliedLastingEffects.empty()) + if (!params.getEffects().empty()) { +<<<<<<< HEAD int casterActorId = -1; if (!caster.isEmpty() && caster.getClass().isActor()) casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); @@ -501,14 +647,20 @@ namespace MWMechanics End of tes3mp addition */ } +======= + if (targetIsActor) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 else { - MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + // Apply effects instantly. We can ignore effect deletion since the entire params object gets + // deleted afterwards anyway and we can ignore reflection since non-actors cannot reflect spells + for (auto& effect : params.getEffects()) + applyMagicEffect(target, mCaster, params, effect, 0.f); } - - return true; } } +<<<<<<< HEAD else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel) { target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude, true); @@ -584,12 +736,13 @@ namespace MWMechanics } } return false; +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - - bool CastSpell::cast(const std::string &id) + bool CastSpell::cast(const ESM::RefId& id) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); if (const auto spell = store.get().search(id)) return cast(spell); @@ -602,22 +755,23 @@ namespace MWMechanics throw std::runtime_error("ID type cannot be casted"); } - bool CastSpell::cast(const MWWorld::Ptr &item, bool launchProjectile) + bool CastSpell::cast(const MWWorld::Ptr& item, int slot, bool launchProjectile) { - std::string enchantmentName = item.getClass().getEnchantment(item); + const ESM::RefId& enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) throw std::runtime_error("can't cast an item without an enchantment"); mSourceName = item.getClass().getName(item); mId = item.getCellRef().getRefId(); - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + const auto& store = MWBase::Environment::get().getESMStore(); + const ESM::Enchantment* enchantment = store->get().find(enchantmentName); - mStack = false; + mSlot = slot; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool isProjectile = false; - if (item.getTypeName() == typeid(ESM::Weapon).name()) + if (item.getType() == ESM::Weapon::sRecordId) { int type = item.get()->mBase->mData.mType; ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; @@ -626,12 +780,14 @@ namespace MWMechanics int type = enchantment->mData.mType; // Check if there's enough charge left - if (!godmode && (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes))) + if (!godmode + && (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes))) { - int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), mCaster); + int castCost = getEffectiveEnchantmentCastCost(*enchantment, mCaster); if (item.getCellRef().getEnchantmentCharge() == -1) - item.getCellRef().setEnchantmentCharge(static_cast(enchantment->mData.mCharge)); + item.getCellRef().setEnchantmentCharge( + static_cast(MWMechanics::getEnchantmentCharge(*enchantment))); if (item.getCellRef().getEnchantmentCharge() < castCost) { @@ -640,14 +796,15 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); // Failure sound - int school = 0; + ESM::RefId school = ESM::Skill::Alteration; if (!enchantment->mEffects.mList.empty()) { short effectId = enchantment->mEffects.mList.front().mEffectID; - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); + const ESM::MagicEffect* magicEffect = store->get().find(effectId); school = magicEffect->mData.mSchool; } +<<<<<<< HEAD static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; @@ -667,6 +824,11 @@ namespace MWMechanics /* End of tes3mp addition */ +======= + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D( + mCaster, store->get().find(school)->mSchool->mFailureSound, 1.0f, 1.0f); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } return false; } @@ -677,28 +839,31 @@ namespace MWMechanics if (type == ESM::Enchantment::WhenUsed) { if (mCaster == getPlayer()) - mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); + mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 1); } else if (type == ESM::Enchantment::CastOnce) { if (!godmode) - item.getContainerStore()->remove(item, 1, mCaster); + item.getContainerStore()->remove(item, 1); } else if (type == ESM::Enchantment::WhenStrikes) { if (mCaster == getPlayer()) - mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); + mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 3); } - inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); + if (isProjectile) + inflict(mTarget, enchantment->mEffects, ESM::RT_Self); + else + inflict(mCaster, enchantment->mEffects, ESM::RT_Self); if (isProjectile || !mTarget.isEmpty()) - inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); + inflict(mTarget, enchantment->mEffects, ESM::RT_Touch); if (launchProjectile) launchMagicBolt(); else if (isProjectile || !mTarget.isEmpty()) - inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); + inflict(mTarget, enchantment->mEffects, ESM::RT_Target); return true; } @@ -707,9 +872,9 @@ namespace MWMechanics { mSourceName = potion->mName; mId = potion->mId; - mStack = true; + mType = ESM::ActiveSpells::Type_Consumable; - inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); + inflict(mCaster, potion->mEffects, ESM::RT_Self); return true; } @@ -718,11 +883,8 @@ namespace MWMechanics { mSourceName = spell->mName; mId = spell->mId; - mStack = false; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - - int school = 0; + ESM::RefId school = ESM::Skill::Alteration; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); @@ -734,16 +896,6 @@ namespace MWMechanics if (!godmode) { - // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) - static const float fFatigueSpellBase = store.get().find("fFatigueSpellBase")->mValue.getFloat(); - static const float fFatigueSpellMult = store.get().find("fFatigueSpellMult")->mValue.getFloat(); - DynamicStat fatigue = stats.getFatigue(); - const float normalizedEncumbrance = mCaster.getClass().getNormalizedEncumbrance(mCaster); - - float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); - stats.setFatigue(fatigue); - bool fail = false; /* @@ -766,8 +918,14 @@ namespace MWMechanics } // Check success +<<<<<<< HEAD if ((localCast && localCast->success == false) || (dedicatedCast && dedicatedCast->success == false)) +======= + float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) >= successChance) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); @@ -780,6 +938,7 @@ namespace MWMechanics if (fail) { // Failure sound +<<<<<<< HEAD static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; @@ -801,6 +960,11 @@ namespace MWMechanics End of tes3mp addition */ +======= + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(school); + sndMgr->playSound3D(mCaster, skill->mSchool->mFailureSound, 1.0f, 1.0f); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 return false; } } @@ -811,26 +975,26 @@ namespace MWMechanics } if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) - mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); + mCaster.getClass().skillUsageSucceeded(mCaster, school, 0); // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) playSpellCastingEffects(spell->mEffects.mList); - inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); + inflict(mCaster, spell->mEffects, ESM::RT_Self); if (!mTarget.isEmpty()) - inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); + inflict(mTarget, spell->mEffects, ESM::RT_Touch); launchMagicBolt(); return true; } - bool CastSpell::cast (const ESM::Ingredient* ingredient) + bool CastSpell::cast(const ESM::Ingredient* ingredient) { mId = ingredient->mId; - mStack = true; + mType = ESM::ActiveSpells::Type_Consumable; mSourceName = ingredient->mName; ESM::ENAMstruct effect; @@ -840,16 +1004,17 @@ namespace MWMechanics effect.mRange = ESM::RT_Self; effect.mArea = 0; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const auto magicEffect = store.get().find(effect.mEffectID); const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); - float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) + - 0.2f * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() - + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) - * creatureStats.getFatigueTerm(); + float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) + + 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified() + + 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified()) + * creatureStats.getFatigueTerm(); - int roll = Misc::Rng::roll0to99(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); if (roll > x) { // "X has no effect on you" @@ -883,30 +1048,27 @@ namespace MWMechanics ESM::EffectList effects; effects.mList.push_back(effect); - inflict(mCaster, mCaster, effects, ESM::RT_Self); + inflict(mCaster, effects, ESM::RT_Self); return true; } - void CastSpell::playSpellCastingEffects(const std::string &spellid, bool enchantment) + void CastSpell::playSpellCastingEffects(const ESM::Enchantment* enchantment) const { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - if (enchantment) - { - if (const auto spell = store.get().search(spellid)) - playSpellCastingEffects(spell->mEffects.mList); - } - else - { - if (const auto spell = store.get().search(spellid)) - playSpellCastingEffects(spell->mEffects.mList); - } + playSpellCastingEffects(enchantment->mEffects.mList); } - void CastSpell::playSpellCastingEffects(const std::vector& effects) + void CastSpell::playSpellCastingEffects(const ESM::Spell* spell) const { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + playSpellCastingEffects(spell->mEffects.mList); + } + + void CastSpell::playSpellCastingEffects(const std::vector& effects) const + { + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); std::vector addedEffects; + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + for (const ESM::ENAMstruct& effectData : effects) { const auto effect = store.get().find(effectData.mEffectID); @@ -914,44 +1076,98 @@ namespace MWMechanics const ESM::Static* castStatic; if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); + castStatic = store.get().find(effect->mCasting); else - castStatic = store.get().find ("VFX_DefaultCast"); + castStatic = store.get().find(ESM::RefId::stringRefId("VFX_DefaultCast")); // check if the effect was already added - if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end()) + if (std::find(addedEffects.begin(), addedEffects.end(), + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs)) + != addedEffects.end()) continue; MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation) { - animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", effect->mParticle); + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mIndex, + false, {}, effect->mParticle); } else { // If the caster has no animation, add the effect directly to the effectManager - // We should scale it manually - osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f / Constants::UnitsPerFoot); - float scale = std::max({ bounds.x()/3.f, bounds.y()/3.f, bounds.z()/6.f }); - float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f; - osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale); + // We must scale and position it manually + float scale = mCaster.getCellRef().getScale(); + osg::Vec3f pos(mCaster.getRefData().getPosition().asVec3()); + if (!mCaster.getClass().isNpc()) + { + osg::Vec3f bounds(MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f); + scale *= std::max({ bounds.x(), bounds.y(), bounds.z() / 2.f }) / 64.f; + float offset = 0.f; + if (bounds.z() < 128.f) + offset = bounds.z() - 128.f; + else if (bounds.z() < bounds.x() + bounds.y()) + offset = 128.f - bounds.z(); + if (MWBase::Environment::get().getWorld()->isFlying(mCaster)) + offset /= 20.f; + pos.z() += offset * scale; + } + else + { + // Additionally use the NPC's height + osg::Vec3f npcScaleVec(1.f, 1.f, 1.f); + mCaster.getClass().adjustScale(mCaster, npcScaleVec, true); + scale *= npcScaleVec.z(); + } + scale = std::max(scale, 1.f); + MWBase::Environment::get().getWorld()->spawnEffect( + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mParticle, pos, scale); } if (animation && !mCaster.getClass().isActor()) animation->addSpellCastGlow(effect); - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; + addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs)); - addedEffects.push_back("meshes\\" + castStatic->mModel); - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!effect->mCastSound.empty()) + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + if (!effect->mCastSound.empty()) sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); else - sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); + sndMgr->playSound3D( + mCaster, store.get().find(effect->mData.mSchool)->mSchool->mCastSound, 1.0f, 1.0f); + } + } + + void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping) + { + const auto& store = MWBase::Environment::get().getESMStore(); + if (playNonLooping) + { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + if (!magicEffect.mHitSound.empty()) + sndMgr->playSound3D(target, magicEffect.mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D( + target, store->get().find(magicEffect.mData.mSchool)->mSchool->mHitSound, 1.0f, 1.0f); + } + + // Add VFX + const ESM::Static* castStatic; + if (!magicEffect.mHit.empty()) + castStatic = store->get().find(magicEffect.mHit); + else + castStatic = store->get().find(ESM::RefId::stringRefId("VFX_DefaultHit")); + + bool loop = (magicEffect.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); + if (anim && !castStatic->mModel.empty()) + { + // Don't play particle VFX unless the effect is new or it should be looping. + if (playNonLooping || loop) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), magicEffect.mIndex, + loop, {}, magicEffect.mParticle); + } } } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index cc525d2db..adad2de0e 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -1,7 +1,8 @@ #ifndef MWMECHANICS_SPELLCASTING_H #define MWMECHANICS_SPELLCASTING_H -#include +#include +#include #include "../mwworld/ptr.hpp" @@ -11,6 +12,8 @@ namespace ESM struct Ingredient; struct Potion; struct EffectList; + struct Enchantment; + struct MagicEffect; } namespace MWMechanics @@ -23,48 +26,54 @@ namespace MWMechanics MWWorld::Ptr mCaster; // May be empty MWWorld::Ptr mTarget; // May be empty - void playSpellCastingEffects(const std::vector& effects); + void playSpellCastingEffects(const std::vector& effects) const; - public: - bool mStack{false}; - std::string mId; // ID of spell, potion, item etc - std::string mSourceName; // Display name for spell, potion, etc - osg::Vec3f mHitPosition{0,0,0}; // Used for spawning area orb - bool mAlwaysSucceed{false}; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) - bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) - bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.) - - public: - CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false); - - bool cast (const ESM::Spell* spell); - - /// @note mCaster must be an actor - /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched as projectile originating from the caster. - bool cast (const MWWorld::Ptr& item, bool launchProjectile=true); - - /// @note mCaster must be an NPC - bool cast (const ESM::Ingredient* ingredient); - - bool cast (const ESM::Potion* potion); - - /// @note Auto detects if spell, ingredient or potion - bool cast (const std::string& id); - - void playSpellCastingEffects(const std::string &spellid, bool enchantment); + void explodeSpell(const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const; /// Launch a bolt with the given effects. - void launchMagicBolt (); + void launchMagicBolt() const; + + public: + ESM::RefId mId; // ID of spell, potion, item etc + std::string mSourceName; // Display name for spell, potion, etc + osg::Vec3f mHitPosition{ 0, 0, 0 }; // Used for spawning area orb + bool mAlwaysSucceed{ + false + }; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) + bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) + bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, + // etc.) + int mSlot{ 0 }; + ESM::ActiveSpells::EffectType mType{ ESM::ActiveSpells::Type_Temporary }; + + CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile = false, + const bool manualSpell = false); + + bool cast(const ESM::Spell* spell); + + /// @note mCaster must be an actor + /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched + /// as projectile originating from the caster. + bool cast(const MWWorld::Ptr& item, int slot, bool launchProjectile = true); + + /// @note mCaster must be an NPC + bool cast(const ESM::Ingredient* ingredient); + + bool cast(const ESM::Potion* potion); + + /// @note Auto detects if spell, ingredient or potion + bool cast(const ESM::RefId& id); + + void playSpellCastingEffects(const ESM::Enchantment* enchantment) const; + + void playSpellCastingEffects(const ESM::Spell* spell) const; /// @note \a target can be any type of object, not just actors. - /// @note \a caster can be any type of object, or even an empty object. - void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, - const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); - - /// @note \a caster can be any type of object, or even an empty object. - /// @return was the target suitable for the effect? - bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); + void inflict(const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, + bool exploded = false) const; }; + + void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true); } #endif diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp new file mode 100644 index 000000000..89dff6443 --- /dev/null +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -0,0 +1,1289 @@ +#include "spelleffects.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/aifollow.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellresistance.hpp" +#include "../mwmechanics/spellutil.hpp" +#include "../mwmechanics/summoning.hpp" + +#include "../mwrender/animation.hpp" + +#include "../mwworld/actionequip.hpp" +#include "../mwworld/actionteleport.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" + +namespace +{ + float roll(const ESM::ActiveEffect& effect) + { + if (effect.mMinMagnitude == effect.mMaxMagnitude) + return effect.mMinMagnitude; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1, prng); + } + + void modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, + ESM::MagicEffect::Effects creatureEffect, MWMechanics::AiSetting setting, float magnitude, bool& invalid) + { + if (target == MWMechanics::getPlayer() || (effect.mEffectId == creatureEffect) == target.getClass().isNpc()) + invalid = true; + else + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getAiSetting(setting); + stat.setModifier(static_cast(stat.getModifier() + magnitude)); + creatureStats.setAiSetting(setting, stat); + } + } + + void adjustDynamicStat(const MWWorld::Ptr& target, int index, float magnitude, bool allowDecreaseBelowZero = false, + bool allowIncreaseAboveModified = false) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getDynamic(index); + stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero, allowIncreaseAboveModified); + creatureStats.setDynamic(index, stat); + } + + void modDynamicStat(const MWWorld::Ptr& target, int index, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getDynamic(index); + float current = stat.getCurrent(); + stat.setBase(std::max(0.f, stat.getBase() + magnitude)); + stat.setCurrent(current + magnitude); + creatureStats.setDynamic(index, stat); + } + + void damageAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attribute = static_cast(effect.mArg); + auto attr = creatureStats.getAttribute(attribute); + if (effect.mEffectId == ESM::MagicEffect::DamageAttribute) + magnitude = std::min(attr.getModified(), magnitude); + attr.damage(magnitude); + creatureStats.setAttribute(attribute, attr); + } + + void restoreAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attribute = static_cast(effect.mArg); + auto attr = creatureStats.getAttribute(attribute); + attr.restore(magnitude); + creatureStats.setAttribute(attribute, attr); + } + + void fortifyAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attribute = static_cast(effect.mArg); + auto attr = creatureStats.getAttribute(attribute); + attr.setModifier(attr.getModifier() + magnitude); + creatureStats.setAttribute(attribute, attr); + } + + void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg)); + if (effect.mEffectId == ESM::MagicEffect::DamageSkill) + magnitude = std::min(skill.getModified(), magnitude); + skill.damage(magnitude); + } + + void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg)); + skill.restore(magnitude); + } + + void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg)); + skill.setModifier(skill.getModifier() + magnitude); + } + + bool disintegrateSlot(const MWWorld::Ptr& ptr, int slot, float disintegrate) + { + MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStoreIterator item = inv.getSlot(slot); + + if (item != inv.end() + && (item.getType() == MWWorld::ContainerStore::Type_Armor + || item.getType() == MWWorld::ContainerStore::Type_Weapon)) + { + if (!item->getClass().hasItemHealth(*item)) + return false; + int charge = item->getClass().getItemHealth(*item); + if (charge == 0) + return false; + + // Store remainder of disintegrate amount (automatically subtracted if > 1) + item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); + + charge = item->getClass().getItemHealth(*item); + charge -= std::min(static_cast(disintegrate), charge); + item->getCellRef().setCharge(charge); + + if (charge == 0) + { + // Will unequip the broken item and try to find a replacement + if (ptr != MWMechanics::getPlayer()) + inv.autoEquip(); + else + inv.unequipItem(*item); + } + + return true; + } + + return false; + } + + int getBoundItemSlot(const MWWorld::Ptr& boundPtr) + { + const auto [slots, _] = boundPtr.getClass().getEquipmentSlots(boundPtr); + if (!slots.empty()) + return slots[0]; + return -1; + } + + void addBoundItem(const ESM::RefId& itemId, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1); + + int slot = getBoundItemSlot(boundPtr); + auto prevItem = slot >= 0 ? store.getSlot(slot) : store.end(); + + MWWorld::ActionEquip action(boundPtr); + action.execute(actor); + + if (actor != MWMechanics::getPlayer()) + return; + + MWWorld::Ptr newItem; + auto it = slot >= 0 ? store.getSlot(slot) : store.end(); + // Equip can fail because beast races cannot equip boots/helmets + if (it != store.end()) + newItem = *it; + + if (newItem.isEmpty() || boundPtr != newItem) + return; + + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + + // change draw state only if the item is in player's right hand + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + player.setDrawState(MWMechanics::DrawState::Weapon); + + if (prevItem != store.end()) + player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); + } + + void removeBoundItem(const ESM::RefId& itemId, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + auto item = std::find_if( + store.begin(), store.end(), [&](const auto& it) { return it.getCellRef().getRefId() == itemId; }); + if (item == store.end()) + return; + int slot = getBoundItemSlot(*item); + + auto currentItem = store.getSlot(slot); + + bool wasEquipped = currentItem != store.end() && currentItem->getCellRef().getRefId() == itemId; + + if (wasEquipped) + store.remove(*currentItem, 1); + else + store.remove(itemId, 1); + + if (actor != MWMechanics::getPlayer()) + { + // Equip a replacement + if (!wasEquipped) + return; + + auto type = currentItem->getType(); + if (type != ESM::Weapon::sRecordId && type != ESM::Armor::sRecordId && type != ESM::Clothing::sRecordId) + return; + + if (actor.getClass().getCreatureStats(actor).isDead()) + return; + + if (!actor.getClass().hasInventoryStore(actor)) + return; + + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + return; + + actor.getClass().getInventoryStore(actor).autoEquip(); + + return; + } + + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + ESM::RefId prevItemId = player.getPreviousItem(itemId); + player.erasePreviousItem(itemId); + + if (!prevItemId.empty() && wasEquipped) + { + // Find previous item (or its replacement) by id. + // we should equip previous item only if expired bound item was equipped. + MWWorld::Ptr prevItem = store.findReplacement(prevItemId); + if (!prevItem.isEmpty()) + { + MWWorld::ActionEquip action(prevItem); + action.execute(actor); + } + } + } + + bool isCorprusEffect(const MWMechanics::ActiveSpells::ActiveEffect& effect, bool harmfulOnly = false) + { + if (effect.mFlags & ESM::ActiveEffect::Flag_Applied && effect.mEffectId != ESM::MagicEffect::Corprus) + { + const auto* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectId); + if (magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce + && (!harmfulOnly || magicEffect->mData.mFlags & ESM::MagicEffect::Flags::Harmful)) + return true; + } + return false; + } + + void absorbSpell(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + { + const auto& esmStore = *MWBase::Environment::get().getESMStore(); + const ESM::Static* absorbStatic = esmStore.get().find(ESM::RefId::stringRefId("VFX_Absorb")); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + if (animation && !absorbStatic->mModel.empty()) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel, vfs), + ESM::MagicEffect::SpellAbsorption, false); + } + const ESM::Spell* spell = esmStore.get().search(spellId); + int spellCost = 0; + if (spell) + { + spellCost = MWMechanics::calcSpellCost(*spell); + } + else + { + const ESM::Enchantment* enchantment = esmStore.get().search(spellId); + if (enchantment) + spellCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchantment, caster); + } + + // Magicka is increased by the cost of the spell + auto& stats = target.getClass().getCreatureStats(target); + auto magicka = stats.getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spellCost); + stats.setMagicka(magicka); + } + + MWMechanics::MagicApplicationResult::Type applyProtections(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, + const ESM::MagicEffect* magicEffect) + { + auto& stats = target.getClass().getCreatureStats(target); + auto& magnitudes = stats.getMagicEffects(); + // Apply reflect and spell absorption + if (target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment + && spellParams.getType() != ESM::ActiveSpells::Type_Permanent) + { + bool canReflect = !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) + && !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) + && magnitudes.getOrDefault(ESM::MagicEffect::Reflect).getMagnitude() > 0.f && !caster.isEmpty(); + bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) + && magnitudes.getOrDefault(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f; + if (canReflect || canAbsorb) + { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + for (const auto& activeParam : stats.getActiveSpells()) + { + for (const auto& activeEffect : activeParam.getEffects()) + { + if (!(activeEffect.mFlags & ESM::ActiveEffect::Flag_Applied)) + continue; + if (activeEffect.mEffectId == ESM::MagicEffect::Reflect) + { + if (canReflect && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) + { + return MWMechanics::MagicApplicationResult::Type::REFLECTED; + } + } + else if (activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption) + { + if (canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) + { + absorbSpell(spellParams.getId(), caster, target); + return MWMechanics::MagicApplicationResult::Type::REMOVED; + } + } + } + } + } + } + // Notify the target actor they've been hit + bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; + if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) + target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); + // Apply resistances + if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances)) + { + const ESM::Spell* spell = nullptr; + if (spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + spell = MWBase::Environment::get().getESMStore()->get().search(spellParams.getId()); + float magnitudeMult + = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target == MWMechanics::getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else if (caster == MWMechanics::getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + return MWMechanics::MagicApplicationResult::Type::REMOVED; + } + effect.mMinMagnitude *= magnitudeMult; + effect.mMaxMagnitude *= magnitudeMult; + } + return MWMechanics::MagicApplicationResult::Type::APPLIED; + } + + static const std::map sBoundItemsMap{ + { ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID" }, + { ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID" }, + { ESM::MagicEffect::BoundCuirass, "sMagicBoundCuirassID" }, + { ESM::MagicEffect::BoundDagger, "sMagicBoundDaggerID" }, + { ESM::MagicEffect::BoundGloves, "sMagicBoundLeftGauntletID" }, + { ESM::MagicEffect::BoundHelm, "sMagicBoundHelmID" }, + { ESM::MagicEffect::BoundLongbow, "sMagicBoundLongbowID" }, + { ESM::MagicEffect::BoundLongsword, "sMagicBoundLongswordID" }, + { ESM::MagicEffect::BoundMace, "sMagicBoundMaceID" }, + { ESM::MagicEffect::BoundShield, "sMagicBoundShieldID" }, + { ESM::MagicEffect::BoundSpear, "sMagicBoundSpearID" }, + }; +} + +namespace MWMechanics +{ + + void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, + bool& receivedMagicDamage, bool& affectedHealth, bool& recalculateMagicka) + { + const auto world = MWBase::Environment::get().getWorld(); + bool godmode = target == getPlayer() && world->getGodModeState(); + switch (effect.mEffectId) + { + case ESM::MagicEffect::CureCommonDisease: + target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); + break; + case ESM::MagicEffect::CureBlightDisease: + target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); + break; + case ESM::MagicEffect::RemoveCurse: + target.getClass().getCreatureStats(target).getSpells().purgeCurses(); + break; + case ESM::MagicEffect::CureCorprusDisease: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( + target, ESM::MagicEffect::Corprus); + break; + case ESM::MagicEffect::CurePoison: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( + target, ESM::MagicEffect::Poison); + break; + case ESM::MagicEffect::CureParalyzation: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( + target, ESM::MagicEffect::Paralyze); + break; + case ESM::MagicEffect::Dispel: + // Dispel removes entire spells at once + target.getClass().getCreatureStats(target).getActiveSpells().purge( + [magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) { + if (params.getType() == ESM::ActiveSpells::Type_Temporary) + { + const ESM::Spell* spell + = MWBase::Environment::get().getESMStore()->get().search(params.getId()); + if (spell && spell->mData.mType == ESM::Spell::ST_Spell) + { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + return Misc::Rng::roll0to99(prng) < magnitude; + } + } + return false; + }, + target); + break; + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + { + std::string_view marker + = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; + world->teleportToClosestMarker(target, ESM::RefId::stringRefId(marker)); + if (!caster.isEmpty()) + { + MWRender::Animation* anim = world->getAnimation(caster); + anim->removeEffect(effect.mEffectId); + const ESM::Static* fx + = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_end")); + if (fx) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1); + } + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::Mark: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition()); + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::Recall: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + { + MWWorld::CellStore* markedCell = nullptr; + ESM::Position markedPosition; + + world->getPlayer().getMarkedPosition(markedCell, markedPosition); + if (markedCell) + { + ESM::RefId dest = markedCell->getCell()->getId(); + MWWorld::ActionTeleport action(dest, markedPosition, false); + action.execute(target); + if (!caster.isEmpty()) + { + MWRender::Animation* anim = world->getAnimation(caster); + anim->removeEffect(effect.mEffectId); + } + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::CommandCreature: + case ESM::MagicEffect::CommandHumanoid: + if (caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() + || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc()) + invalid = true; + else if (effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) + { + MWMechanics::AiFollow package(caster, true); + target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); + } + break; + case ESM::MagicEffect::ExtraSpell: + if (target.getClass().hasInventoryStore(target)) + { + auto& store = target.getClass().getInventoryStore(target); + store.unequipAll(); + } + else + invalid = true; + break; + case ESM::MagicEffect::TurnUndead: + if (target.getClass().isNpc() + || target.get()->mBase->mData.mType != ESM::Creature::Undead) + invalid = true; + else + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(AiSetting::Flee); + stat.setModifier(static_cast(stat.getModifier() + effect.mMagnitude)); + creatureStats.setAiSetting(AiSetting::Flee, stat); + } + break; + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::FrenzyHumanoid: + modifyAiSetting( + target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::CalmHumanoid: + modifyAiSetting( + target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, -effect.mMagnitude, invalid); + if (!invalid && effect.mMagnitude > 0) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + creatureStats.getAiSequence().stopCombat(); + } + break; + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + modifyAiSetting( + target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::RallyCreature: + case ESM::MagicEffect::RallyHumanoid: + modifyAiSetting( + target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::Sound: + if (target == getPlayer()) + { + const auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + float volume = std::clamp( + (magnitudes.getOrDefault(effect.mEffectId).getModifier() + effect.mMagnitude) / 100.f, 0.f, + 1.f); + MWBase::Environment::get().getSoundManager()->playSound3D(target, + ESM::RefId::stringRefId("magic sound"), volume, 1.f, MWSound::Type::Sfx, + MWSound::PlayMode::LoopNoEnv); + } + break; + case ESM::MagicEffect::SummonScamp: + case ESM::MagicEffect::SummonClannfear: + case ESM::MagicEffect::SummonDaedroth: + case ESM::MagicEffect::SummonDremora: + case ESM::MagicEffect::SummonAncestralGhost: + case ESM::MagicEffect::SummonSkeletalMinion: + case ESM::MagicEffect::SummonBonewalker: + case ESM::MagicEffect::SummonGreaterBonewalker: + case ESM::MagicEffect::SummonBonelord: + case ESM::MagicEffect::SummonWingedTwilight: + case ESM::MagicEffect::SummonHunger: + case ESM::MagicEffect::SummonGoldenSaint: + case ESM::MagicEffect::SummonFlameAtronach: + case ESM::MagicEffect::SummonFrostAtronach: + case ESM::MagicEffect::SummonStormAtronach: + case ESM::MagicEffect::SummonCenturionSphere: + case ESM::MagicEffect::SummonFabricant: + case ESM::MagicEffect::SummonWolf: + case ESM::MagicEffect::SummonBear: + case ESM::MagicEffect::SummonBonewolf: + case ESM::MagicEffect::SummonCreature04: + case ESM::MagicEffect::SummonCreature05: + if (!target.isInCell()) + invalid = true; + else + effect.mArg = summonCreature(effect.mEffectId, target); + break; + case ESM::MagicEffect::BoundGloves: + if (!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + addBoundItem(ESM::RefId::stringRefId(world->getStore() + .get() + .find("sMagicBoundRightGauntletID") + ->mValue.getString()), + target); + // left gauntlet added below + [[fallthrough]]; + case ESM::MagicEffect::BoundDagger: + case ESM::MagicEffect::BoundLongsword: + case ESM::MagicEffect::BoundMace: + case ESM::MagicEffect::BoundBattleAxe: + case ESM::MagicEffect::BoundSpear: + case ESM::MagicEffect::BoundLongbow: + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundHelm: + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundShield: + if (!target.getClass().hasInventoryStore(target)) + invalid = true; + else + { + const std::string& item = sBoundItemsMap.at(effect.mEffectId); + addBoundItem(ESM::RefId::stringRefId( + world->getStore().get().find(item)->mValue.getString()), + target); + } + break; + case ESM::MagicEffect::FireDamage: + case ESM::MagicEffect::ShockDamage: + case ESM::MagicEffect::FrostDamage: + case ESM::MagicEffect::DamageHealth: + case ESM::MagicEffect::Poison: + case ESM::MagicEffect::DamageMagicka: + case ESM::MagicEffect::DamageFatigue: + if (!godmode) + { + int index = 0; + if (effect.mEffectId == ESM::MagicEffect::DamageMagicka) + index = 1; + else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue) + index = 2; + // Damage "Dynamic" abilities reduce the base value + if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, index, -effect.mMagnitude); + else + { + adjustDynamicStat( + target, index, -effect.mMagnitude, index == 2 && Settings::game().mUncappedDamageFatigue); + if (index == 0) + receivedMagicDamage = affectedHealth = true; + } + } + break; + case ESM::MagicEffect::DamageAttribute: + if (!godmode) + damageAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::DamageSkill: + if (!target.getClass().isNpc()) + invalid = true; + else if (!godmode) + { + // Damage Skill abilities reduce base skill :todd: + if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& npcStats = target.getClass().getNpcStats(target); + SkillValue& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg)); + // Damage Skill abilities reduce base skill :todd: + skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); + } + else + damageSkill(target, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::RestoreAttribute: + restoreAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::RestoreSkill: + if (!target.getClass().isNpc()) + invalid = true; + else + restoreSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::RestoreHealth: + affectedHealth = true; + [[fallthrough]]; + case ESM::MagicEffect::RestoreMagicka: + case ESM::MagicEffect::RestoreFatigue: + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::RestoreHealth, effect.mMagnitude); + break; + case ESM::MagicEffect::SunDamage: + { + // isInCell shouldn't be needed, but updateActor called during game start + if (!target.isInCell() || !(target.getCell()->isExterior() || target.getCell()->isQuasiExterior()) + || godmode) + break; + const float sunRisen = world->getSunPercentage(); + static float fMagicSunBlockedMult + = world->getStore().get().find("fMagicSunBlockedMult")->mValue.getFloat(); + const float damageScale = std::clamp( + std::max(world->getSunVisibility() * sunRisen, fMagicSunBlockedMult * sunRisen), 0.f, 1.f); + float damage = effect.mMagnitude * damageScale; + adjustDynamicStat(target, 0, -damage); + if (damage > 0.f) + receivedMagicDamage = affectedHealth = true; + } + break; + case ESM::MagicEffect::DrainHealth: + case ESM::MagicEffect::DrainMagicka: + case ESM::MagicEffect::DrainFatigue: + if (!godmode) + { + int index = effect.mEffectId - ESM::MagicEffect::DrainHealth; + adjustDynamicStat( + target, index, -effect.mMagnitude, Settings::game().mUncappedDamageFatigue && index == 2); + if (index == 0) + receivedMagicDamage = affectedHealth = true; + } + break; + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude); + else + adjustDynamicStat( + target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude, false, true); + break; + case ESM::MagicEffect::DrainAttribute: + if (!godmode) + damageAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyAttribute: + // Abilities affect base stats, but not for drain + if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attribute = static_cast(effect.mArg); + AttributeValue attr = creatureStats.getAttribute(attribute); + attr.setBase(attr.getBase() + effect.mMagnitude); + creatureStats.setAttribute(attribute, attr); + } + else + fortifyAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::DrainSkill: + if (!target.getClass().isNpc()) + invalid = true; + else if (!godmode) + damageSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifySkill: + if (!target.getClass().isNpc()) + invalid = true; + else if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + // Abilities affect base stats, but not for drain + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg)); + skill.setBase(skill.getBase() + effect.mMagnitude); + } + else + fortifySkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyMaximumMagicka: + recalculateMagicka = true; + break; + case ESM::MagicEffect::AbsorbHealth: + case ESM::MagicEffect::AbsorbMagicka: + case ESM::MagicEffect::AbsorbFatigue: + if (!godmode) + { + int index = effect.mEffectId - ESM::MagicEffect::AbsorbHealth; + adjustDynamicStat(target, index, -effect.mMagnitude); + if (!caster.isEmpty()) + adjustDynamicStat(caster, index, effect.mMagnitude); + if (index == 0) + receivedMagicDamage = affectedHealth = true; + } + break; + case ESM::MagicEffect::AbsorbAttribute: + if (!godmode) + { + damageAttribute(target, effect, effect.mMagnitude); + if (!caster.isEmpty()) + fortifyAttribute(caster, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::AbsorbSkill: + if (!target.getClass().isNpc()) + invalid = true; + else if (!godmode) + { + damageSkill(target, effect, effect.mMagnitude); + if (!caster.isEmpty()) + fortifySkill(caster, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::DisintegrateArmor: + { + if (!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + if (godmode) + break; + static const std::array priorities{ + MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, + MWWorld::InventoryStore::Slot_RightGauntlet, + MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots, + }; + for (const int priority : priorities) + { + if (disintegrateSlot(target, priority, effect.mMagnitude)) + break; + } + break; + } + case ESM::MagicEffect::DisintegrateWeapon: + if (!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + if (!godmode) + disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude); + break; + } + } + + bool shouldRemoveEffect(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect) + { + const auto world = MWBase::Environment::get().getWorld(); + switch (effect.mEffectId) + { + case ESM::MagicEffect::Levitate: + { + if (!world->isLevitationEnabled()) + { + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + return true; + } + break; + } + case ESM::MagicEffect::Recall: + case ESM::MagicEffect::DivineIntervention: + case ESM::MagicEffect::AlmsiviIntervention: + { + return effect.mFlags & ESM::ActiveEffect::Flag_Applied; + } + case ESM::MagicEffect::WaterWalking: + { + if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target)) + return true; + if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) + break; + if (!world->isWaterWalkingCastableOnTarget(target)) + { + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}"); + return true; + } + break; + } + } + return false; + } + + MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) + { + const auto world = MWBase::Environment::get().getWorld(); + bool invalid = false; + bool receivedMagicDamage = false; + bool recalculateMagicka = false; + bool affectedHealth = false; + if (effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen()) + { + spellParams.worsen(); + for (auto& otherEffect : spellParams.getEffects()) + { + if (isCorprusEffect(otherEffect)) + applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage, + affectedHealth, recalculateMagicka); + } + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); + return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; + } + else if (shouldRemoveEffect(target, effect)) + { + onMagicEffectRemoved(target, spellParams, effect); + return { MagicApplicationResult::Type::REMOVED, receivedMagicDamage, affectedHealth }; + } + const auto* magicEffect = world->getStore().get().find(effect.mEffectId); + if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) + { + if (magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + { + effect.mTimeLeft -= dt; + return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; + } + else if (!dt) + return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; + } + if (effect.mEffectId == ESM::MagicEffect::Lock) + { + if (target.getClass().canLock(target)) + { + MWRender::Animation* animation = world->getAnimation(target); + if (animation) + animation->addSpellCastGlow(magicEffect); + int magnitude = static_cast(roll(effect)); + if (target.getCellRef().getLockLevel() + < magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude + { + if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); + target.getCellRef().lock(magnitude); + } + } + else + invalid = true; + } + else if (effect.mEffectId == ESM::MagicEffect::Open) + { + if (target.getClass().canLock(target)) + { + // Use the player instead of the caster for vanilla crime compatibility + MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target); + + MWRender::Animation* animation = world->getAnimation(target); + if (animation) + animation->addSpellCastGlow(magicEffect); + int magnitude = static_cast(roll(effect)); + if (target.getCellRef().getLockLevel() <= magnitude) + { + if (target.getCellRef().isLocked()) + { + MWBase::Environment::get().getSoundManager()->playSound3D( + target, ESM::RefId::stringRefId("Open Lock"), 1.f, 1.f); + + if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); + target.getCellRef().unlock(); + } + } + else + { + MWBase::Environment::get().getSoundManager()->playSound3D( + target, ESM::RefId::stringRefId("Open Lock Fail"), 1.f, 1.f); + } + } + else + invalid = true; + } + else if (!target.getClass().isActor()) + { + invalid = true; + } + else + { + // Morrowind.exe doesn't apply magic effects while the menu is open, we do because we like to see stats + // updated instantly. We don't want to teleport instantly though + if (!dt + && (effect.mEffectId == ESM::MagicEffect::Recall + || effect.mEffectId == ESM::MagicEffect::DivineIntervention + || effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention)) + return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; + auto& stats = target.getClass().getCreatureStats(target); + auto& magnitudes = stats.getMagicEffects(); + if (spellParams.getType() != ESM::ActiveSpells::Type_Ability + && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) + { + MagicApplicationResult::Type result + = applyProtections(target, caster, spellParams, effect, magicEffect); + if (result != MagicApplicationResult::Type::APPLIED) + return { result, receivedMagicDamage, affectedHealth }; + } + float oldMagnitude = 0.f; + if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) + oldMagnitude = effect.mMagnitude; + else + { + if (spellParams.getType() != ESM::ActiveSpells::Type_Enchantment) + playEffects(target, *magicEffect, + spellParams.getType() == ESM::ActiveSpells::Type_Consumable + || spellParams.getType() == ESM::ActiveSpells::Type_Temporary); + if (effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() + && target.getType() == ESM::Creature::sRecordId + && target.get()->mBase->mData.mSoul == 0 && caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); + } + float magnitude = roll(effect); + // Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here + effect.mMagnitude = magnitude; + if (!(magicEffect->mData.mFlags + & (ESM::MagicEffect::Flags::NoMagnitude | ESM::MagicEffect::Flags::AppliedOnce))) + { + if (effect.mDuration != 0) + { + float mult = dt; + if (spellParams.getType() == ESM::ActiveSpells::Type_Consumable + || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + mult = std::min(effect.mTimeLeft, dt); + effect.mMagnitude *= mult; + } + if (effect.mMagnitude == 0) + { + effect.mMagnitude = oldMagnitude; + effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; + effect.mTimeLeft -= dt; + return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; + } + } + if (effect.mEffectId == ESM::MagicEffect::Corprus) + spellParams.worsen(); + else + applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, affectedHealth, + recalculateMagicka); + effect.mMagnitude = magnitude; + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude)); + } + effect.mTimeLeft -= dt; + if (invalid) + { + effect.mTimeLeft = 0; + effect.mFlags |= ESM::ActiveEffect::Flag_Remove; + auto anim = world->getAnimation(target); + if (anim) + anim->removeEffect(effect.mEffectId); + } + else + effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; + if (recalculateMagicka) + target.getClass().getCreatureStats(target).recalculateMagicka(); + return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; + } + + void removeMagicEffect( + const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) + { + const auto world = MWBase::Environment::get().getWorld(); + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + bool invalid; + switch (effect.mEffectId) + { + case ESM::MagicEffect::CommandCreature: + case ESM::MagicEffect::CommandHumanoid: + if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f) + { + auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); + seq.erasePackageIf([&](const auto& package) { + return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow + && static_cast(package.get())->isCommanded(); + }); + } + break; + case ESM::MagicEffect::ExtraSpell: + if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f) + target.getClass().getInventoryStore(target).autoEquip(); + break; + case ESM::MagicEffect::TurnUndead: + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(AiSetting::Flee); + stat.setModifier(static_cast(stat.getModifier() - effect.mMagnitude)); + creatureStats.setAiSetting(AiSetting::Flee, stat); + } + break; + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::FrenzyHumanoid: + modifyAiSetting( + target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::CalmHumanoid: + modifyAiSetting( + target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + modifyAiSetting( + target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::NightEye: + { + const MWMechanics::EffectParam nightEye = magnitudes.getOrDefault(effect.mEffectId); + if (nightEye.getMagnitude() < 0.f && nightEye.getBase() < 0) + { + // The PCVisionBonus functions are different from every other magic effect function in that they + // clamp the value to [0, 1]. Morrowind.exe applies the same clamping to the night-eye effect, which + // can create situations where an effect is still active (i.e. shown in the menu) but the screen is + // no longer bright. Modifying the base value here should prevent that while preserving their + // function. + float delta = std::clamp(-nightEye.getMagnitude(), 0.f, -static_cast(nightEye.getBase())); + magnitudes.modifyBase(effect.mEffectId, static_cast(delta)); + } + } + break; + case ESM::MagicEffect::RallyCreature: + case ESM::MagicEffect::RallyHumanoid: + modifyAiSetting( + target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::Sound: + if (magnitudes.getOrDefault(effect.mEffectId).getModifier() <= 0.f && target == getPlayer()) + MWBase::Environment::get().getSoundManager()->stopSound3D( + target, ESM::RefId::stringRefId("magic sound")); + break; + case ESM::MagicEffect::SummonScamp: + case ESM::MagicEffect::SummonClannfear: + case ESM::MagicEffect::SummonDaedroth: + case ESM::MagicEffect::SummonDremora: + case ESM::MagicEffect::SummonAncestralGhost: + case ESM::MagicEffect::SummonSkeletalMinion: + case ESM::MagicEffect::SummonBonewalker: + case ESM::MagicEffect::SummonGreaterBonewalker: + case ESM::MagicEffect::SummonBonelord: + case ESM::MagicEffect::SummonWingedTwilight: + case ESM::MagicEffect::SummonHunger: + case ESM::MagicEffect::SummonGoldenSaint: + case ESM::MagicEffect::SummonFlameAtronach: + case ESM::MagicEffect::SummonFrostAtronach: + case ESM::MagicEffect::SummonStormAtronach: + case ESM::MagicEffect::SummonCenturionSphere: + case ESM::MagicEffect::SummonFabricant: + case ESM::MagicEffect::SummonWolf: + case ESM::MagicEffect::SummonBear: + case ESM::MagicEffect::SummonBonewolf: + case ESM::MagicEffect::SummonCreature04: + case ESM::MagicEffect::SummonCreature05: + { + if (effect.mArg != -1) + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, effect.mArg); + auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap(); + auto [begin, end] = summons.equal_range(effect.mEffectId); + for (auto it = begin; it != end; ++it) + { + if (it->second == effect.mArg) + { + summons.erase(it); + break; + } + } + } + break; + case ESM::MagicEffect::BoundGloves: + removeBoundItem(ESM::RefId::stringRefId(world->getStore() + .get() + .find("sMagicBoundRightGauntletID") + ->mValue.getString()), + target); + [[fallthrough]]; + case ESM::MagicEffect::BoundDagger: + case ESM::MagicEffect::BoundLongsword: + case ESM::MagicEffect::BoundMace: + case ESM::MagicEffect::BoundBattleAxe: + case ESM::MagicEffect::BoundSpear: + case ESM::MagicEffect::BoundLongbow: + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundHelm: + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundShield: + { + const std::string& item = sBoundItemsMap.at(effect.mEffectId); + removeBoundItem( + ESM::RefId::stringRefId(world->getStore().get().find(item)->mValue.getString()), + target); + } + break; + case ESM::MagicEffect::DrainHealth: + case ESM::MagicEffect::DrainMagicka: + case ESM::MagicEffect::DrainFatigue: + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::DrainHealth, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude); + else + adjustDynamicStat( + target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude, true); + break; + case ESM::MagicEffect::DrainAttribute: + restoreAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyAttribute: + // Abilities affect base stats, but not for drain + if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attribute = static_cast(effect.mArg); + AttributeValue attr = creatureStats.getAttribute(attribute); + attr.setBase(attr.getBase() - effect.mMagnitude); + creatureStats.setAttribute(attribute, attr); + } + else + fortifyAttribute(target, effect, -effect.mMagnitude); + break; + case ESM::MagicEffect::DrainSkill: + restoreSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifySkill: + // Abilities affect base stats, but not for drain + if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg)); + skill.setBase(skill.getBase() - effect.mMagnitude); + } + else + fortifySkill(target, effect, -effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyMaximumMagicka: + target.getClass().getCreatureStats(target).recalculateMagicka(); + break; + case ESM::MagicEffect::AbsorbAttribute: + { + const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); + restoreAttribute(target, effect, effect.mMagnitude); + if (!caster.isEmpty()) + fortifyAttribute(caster, effect, -effect.mMagnitude); + } + break; + case ESM::MagicEffect::AbsorbSkill: + { + const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); + restoreSkill(target, effect, effect.mMagnitude); + if (!caster.isEmpty()) + fortifySkill(caster, effect, -effect.mMagnitude); + } + break; + case ESM::MagicEffect::Corprus: + { + int worsenings = spellParams.getWorsenings(); + spellParams.resetWorsenings(); + if (worsenings > 0) + { + for (const auto& otherEffect : spellParams.getEffects()) + { + if (isCorprusEffect(otherEffect, true)) + { + for (int i = 0; i < worsenings; i++) + removeMagicEffect(target, spellParams, otherEffect); + } + } + } + // Note that we remove the effects, but keep the params + target.getClass().getCreatureStats(target).getActiveSpells().purge( + [&spellParams]( + const ActiveSpells::ActiveSpellParams& params, const auto&) { return &spellParams == ¶ms; }, + target); + } + break; + } + } + + void onMagicEffectRemoved( + const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) + { + if (!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) + return; + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMagnitude)); + removeMagicEffect(target, spellParams, effect); + if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f) + { + auto anim = MWBase::Environment::get().getWorld()->getAnimation(target); + if (anim) + anim->removeEffect(effect.mEffectId); + } + } + +} diff --git a/apps/openmw/mwmechanics/spelleffects.hpp b/apps/openmw/mwmechanics/spelleffects.hpp new file mode 100644 index 000000000..2dafedf31 --- /dev/null +++ b/apps/openmw/mwmechanics/spelleffects.hpp @@ -0,0 +1,38 @@ +#ifndef GAME_MWMECHANICS_SPELLEFFECTS_H +#define GAME_MWMECHANICS_SPELLEFFECTS_H + +#include "activespells.hpp" + +// These functions should probably be split up into separate Lua functions for each magic effect when magic is +// dehardcoded. That way ESM::MGEF could point to two Lua scripts for each effect. Needs discussion. + +namespace MWWorld +{ + class Ptr; +} + +namespace MWMechanics +{ + struct MagicApplicationResult + { + enum class Type + { + APPLIED, + REMOVED, + REFLECTED + }; + Type mType; + bool mShowHit; + bool mShowHealth; + }; + + // Applies a tick of a single effect. Returns true if the effect should be removed immediately + MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt); + + // Undoes permanent effects created by ESM::MagicEffect::AppliedOnce + void onMagicEffectRemoved( + const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect); +} + +#endif diff --git a/apps/openmw/mwmechanics/spelllist.cpp b/apps/openmw/mwmechanics/spelllist.cpp index 9328d533e..1e27ca576 100644 --- a/apps/openmw/mwmechanics/spelllist.cpp +++ b/apps/openmw/mwmechanics/spelllist.cpp @@ -2,7 +2,9 @@ #include -#include +#include +#include +#include #include "spells.hpp" @@ -13,86 +15,88 @@ namespace { - template - const std::vector getSpellList(const std::string& id) + template + const std::vector getSpellList(const ESM::RefId& id) { - return MWBase::Environment::get().getWorld()->getStore().get().find(id)->mSpells.mList; + return MWBase::Environment::get().getESMStore()->get().find(id)->mSpells.mList; } - template - bool withBaseRecord(const std::string& id, const std::function&)>& function) + template + bool withBaseRecord(const ESM::RefId& id, const std::function&)>& function) { - T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(id); + T copy = *MWBase::Environment::get().getESMStore()->get().find(id); bool changed = function(copy.mSpells.mList); - if(changed) - MWBase::Environment::get().getWorld()->createOverrideRecord(copy); + if (changed) + MWBase::Environment::get().getESMStore()->overrideRecord(copy); return changed; } } namespace MWMechanics { - SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {} - - bool SpellList::withBaseRecord(const std::function&)>& function) + SpellList::SpellList(const ESM::RefId& id, int type) + : mId(id) + , mType(type) { - switch(mType) + } + + bool SpellList::withBaseRecord(const std::function&)>& function) + { + switch (mType) { case ESM::REC_CREA: return ::withBaseRecord(mId, function); case ESM::REC_NPC_: return ::withBaseRecord(mId, function); default: - throw std::logic_error("failed to update base record for " + mId); + throw std::logic_error("failed to update base record for " + mId.toDebugString()); } } - const std::vector SpellList::getSpells() const + const std::vector SpellList::getSpells() const { - switch(mType) + switch (mType) { case ESM::REC_CREA: return getSpellList(mId); case ESM::REC_NPC_: return getSpellList(mId); default: - throw std::logic_error("failed to get spell list for " + mId); + throw std::logic_error("failed to get spell list for " + mId.toDebugString()); } } - const ESM::Spell* SpellList::getSpell(const std::string& id) + const ESM::Spell* SpellList::getSpell(const ESM::RefId& id) { - return MWBase::Environment::get().getWorld()->getStore().get().find(id); + return MWBase::Environment::get().getESMStore()->get().find(id); } - void SpellList::add (const ESM::Spell* spell) + void SpellList::add(const ESM::Spell* spell) { auto& id = spell->mId; - bool changed = withBaseRecord([&] (auto& spells) - { - for(const auto& it : spells) + bool changed = withBaseRecord([&](auto& spells) { + for (const auto& it : spells) { - if(Misc::StringUtils::ciEqual(id, it)) + if (id == it) return false; } spells.push_back(id); return true; }); - if(changed) + if (changed) { - for(auto listener : mListeners) + for (auto listener : mListeners) listener->addSpell(spell); } } - void SpellList::remove (const ESM::Spell* spell) + void SpellList::remove(const ESM::Spell* spell) { auto& id = spell->mId; - bool changed = withBaseRecord([&] (auto& spells) - { - for(auto it = spells.begin(); it != spells.end(); it++) + bool changed = withBaseRecord([&](auto& spells) { + for (auto it = spells.begin(); it != spells.end(); it++) { - if(Misc::StringUtils::ciEqual(id, *it)) + if (id == *it) { spells.erase(it); return true; @@ -100,20 +104,18 @@ namespace MWMechanics } return false; }); - if(changed) + if (changed) { - for(auto listener : mListeners) + for (auto listener : mListeners) listener->removeSpell(spell); } } - void SpellList::removeAll (const std::vector& ids) + void SpellList::removeAll(const std::vector& ids) { - bool changed = withBaseRecord([&] (auto& spells) - { - const auto it = std::remove_if(spells.begin(), spells.end(), [&] (const auto& spell) - { - const auto isSpell = [&] (const auto& id) { return Misc::StringUtils::ciEqual(spell, id); }; + bool changed = withBaseRecord([&](auto& spells) { + const auto it = std::remove_if(spells.begin(), spells.end(), [&](const auto& spell) { + const auto isSpell = [&](const auto& id) { return spell == id; }; return ids.end() != std::find_if(ids.begin(), ids.end(), isSpell); }); if (it == spells.end()) @@ -121,11 +123,11 @@ namespace MWMechanics spells.erase(it, spells.end()); return true; }); - if(changed) + if (changed) { - for(auto listener : mListeners) + for (auto listener : mListeners) { - for(auto& id : ids) + for (auto& id : ids) { const auto spell = getSpell(id); listener->removeSpell(spell); @@ -136,16 +138,15 @@ namespace MWMechanics void SpellList::clear() { - bool changed = withBaseRecord([] (auto& spells) - { - if(spells.empty()) + bool changed = withBaseRecord([](auto& spells) { + if (spells.empty()) return false; spells.clear(); return true; }); - if(changed) + if (changed) { - for(auto listener : mListeners) + for (auto listener : mListeners) listener->removeAllSpells(); } } diff --git a/apps/openmw/mwmechanics/spelllist.hpp b/apps/openmw/mwmechanics/spelllist.hpp index 5920949d6..49e40f0e7 100644 --- a/apps/openmw/mwmechanics/spelllist.hpp +++ b/apps/openmw/mwmechanics/spelllist.hpp @@ -3,11 +3,11 @@ #include #include -#include #include +#include #include -#include +#include namespace ESM { @@ -16,52 +16,49 @@ namespace ESM namespace MWMechanics { - struct SpellParams - { - std::map mEffectRands; // - std::set mPurgedEffects; // indices of purged effects - }; - class Spells; /// Multiple instances of the same actor share the same spell list in Morrowind. /// The most obvious result of this is that adding a spell or ability to one instance adds it to all instances. - /// @note The original game will only update visual effects associated with any added abilities for the originally targeted actor, + /// @note The original game will only update visual effects associated with any added abilities for the originally + /// targeted actor, /// changing cells applies the update to all actors. - /// Aside from sharing the same active spell list, changes made to this list are also written to the actor's base record. - /// Interestingly, it is not just scripted changes that are persisted to the base record. Curing one instance's disease will cure all instances. + /// Aside from sharing the same active spell list, changes made to this list are also written to the actor's base + /// record. Interestingly, it is not just scripted changes that are persisted to the base record. Curing one + /// instance's disease will cure all instances. /// @note The original game is inconsistent in persisting this example; /// saving and loading the game might reapply the cured disease depending on which instance was cured. class SpellList { - const std::string mId; - const int mType; - std::vector mListeners; + ESM::RefId mId; + const int mType; + std::vector mListeners; - bool withBaseRecord(const std::function&)>& function); - public: - SpellList(const std::string& id, int type); + bool withBaseRecord(const std::function&)>& function); - /// Get spell from ID, throws exception if not found - static const ESM::Spell* getSpell(const std::string& id); + public: + SpellList(const ESM::RefId& id, int type); - void add (const ESM::Spell* spell); - ///< Adding a spell that is already listed in *this is a no-op. + /// Get spell from ID, throws exception if not found + static const ESM::Spell* getSpell(const ESM::RefId& id); - void remove (const ESM::Spell* spell); + void add(const ESM::Spell* spell); + ///< Adding a spell that is already listed in *this is a no-op. - void removeAll(const std::vector& spells); + void remove(const ESM::Spell* spell); - void clear(); - ///< Remove all spells of all types. + void removeAll(const std::vector& spells); - void addListener(Spells* spells); + void clear(); + ///< Remove all spells of all types. - void removeListener(Spells* spells); + void addListener(Spells* spells); - void updateListener(Spells* before, Spells* after); + void removeListener(Spells* spells); - const std::vector getSpells() const; + void updateListener(Spells* before, Spells* after); + + const std::vector getSpells() const; }; } diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index bd9c5f7cb..9bd449565 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -1,51 +1,52 @@ #include "spellpriority.hpp" #include "weaponpriority.hpp" -#include -#include -#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" #include "spellresistance.hpp" -#include "weapontype.hpp" -#include "summoning.hpp" #include "spellutil.hpp" +#include "summoning.hpp" +#include "weapontype.hpp" namespace { - int numEffectsToDispel (const MWWorld::Ptr& actor, int effectFilter=-1, bool negative = true) + int numEffectsToDispel(const MWWorld::Ptr& actor, int effectFilter = -1, bool negative = true) { - int toCure=0; + int toCure = 0; const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { - // if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted items etc. + // if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted + // items etc. if (effectFilter == -1) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + const ESM::Spell* spell + = MWBase::Environment::get().getESMStore()->get().search(it->getId()); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) continue; } - const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; - for (std::vector::const_iterator effectIt = params.mEffects.begin(); - effectIt != params.mEffects.end(); ++effectIt) + const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; + for (const auto& effect : params.getEffects()) { - int effectId = effectIt->mEffectId; + int effectId = effect.mEffectId; if (effectFilter != -1 && effectId != effectFilter) continue; - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effectId); - if (effectIt->mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway + if (effect.mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway continue; if (negative && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) @@ -58,30 +59,38 @@ namespace return toCure; } - float getSpellDuration (const MWWorld::Ptr& actor, const std::string& spellId) + float getSpellDuration(const MWWorld::Ptr& actor, const ESM::RefId& spellId) { float duration = 0; const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { - if (it->first != spellId) + if (it->getId() != spellId) continue; - const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; - for (std::vector::const_iterator effectIt = params.mEffects.begin(); - effectIt != params.mEffects.end(); ++effectIt) + const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; + for (const auto& effect : params.getEffects()) { - if (effectIt->mDuration > duration) - duration = effectIt->mDuration; + if (effect.mDuration > duration) + duration = effect.mDuration; } } return duration; } + + bool isSpellActive(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const ESM::RefId& id) + { + int actorId = caster.getClass().getCreatureStats(caster).getActorId(); + const auto& active = target.getClass().getCreatureStats(target).getActiveSpells(); + return std::find_if(active.begin(), active.end(), [&](const auto& spell) { + return spell.getCasterActorId() == actorId && spell.getId() == id; + }) != active.end(); + } } namespace MWMechanics { - int getRangeTypes (const ESM::EffectList& effects) + int getRangeTypes(const ESM::EffectList& effects) { int types = 0; for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) @@ -96,19 +105,17 @@ namespace MWMechanics return types; } - float ratePotion (const MWWorld::Ptr &item, const MWWorld::Ptr& actor) + float ratePotion(const MWWorld::Ptr& item, const MWWorld::Ptr& actor) { - if (item.getTypeName() != typeid(ESM::Potion).name()) + if (item.getType() != ESM::Potion::sRecordId) return 0.f; const ESM::Potion* potion = item.get()->mBase; return rateEffects(potion->mEffects, actor, MWWorld::Ptr()); } - float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) + float rateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { - const CreatureStats& stats = actor.getClass().getCreatureStats(actor); - float successChance = MWMechanics::getSpellSuccessChance(spell, actor); if (successChance == 0.f) return 0.f; @@ -119,35 +126,37 @@ namespace MWMechanics // Don't make use of racial bonus spells, like MW. Can be made optional later if (actor.getClass().isNpc()) { - std::string raceid = actor.get()->mBase->mRace; - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceid); + const ESM::RefId& raceid = actor.get()->mBase->mRace; + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(raceid); if (race->mPowers.exists(spell->mId)) return 0.f; } // Spells don't stack, so early out if the spell is still active on the target int types = getRangeTypes(spell->mEffects); - if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId)) + if ((types & Self) && isSpellActive(actor, actor, spell->mId)) return 0.f; - if ( ((types & Touch) || (types & Target)) && enemy.getClass().getCreatureStats(enemy).getActiveSpells().isSpellActive(spell->mId)) + if (((types & Touch) || (types & Target)) && isSpellActive(actor, enemy, spell->mId)) return 0.f; return rateEffects(spell->mEffects, actor, enemy) * (successChance / 100.f); } - float rateMagicItem(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) + float rateMagicItem(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { if (ptr.getClass().getEnchantment(ptr).empty()) return 0.f; - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getClass().getEnchantment(ptr)); + const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().find( + ptr.getClass().getEnchantment(ptr)); // Spells don't stack, so early out if the spell is still active on the target int types = getRangeTypes(enchantment->mEffects); - if ((types & Self) && actor.getClass().getCreatureStats(actor).getActiveSpells().isSpellActive(ptr.getCellRef().getRefId())) + if ((types & Self) + && actor.getClass().getCreatureStats(actor).getActiveSpells().isSpellActive(ptr.getCellRef().getRefId())) return 0.f; - if (types & (Touch|Target) && getSpellDuration(enemy, ptr.getCellRef().getRefId()) > 3) + if (types & (Touch | Target) && getSpellDuration(enemy, ptr.getCellRef().getRefId()) > 3) return 0.f; if (enchantment->mData.mType == ESM::Enchantment::CastOnce) @@ -158,80 +167,79 @@ namespace MWMechanics { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - // Creatures can not wear armor/clothing, so allow creatures to use non-equipped items, + // Creatures can not wear armor/clothing, so allow creatures to use non-equipped items, if (actor.getClass().isNpc() && !store.isEquipped(ptr)) return 0.f; - int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + int castCost = getEffectiveEnchantmentCastCost(*enchantment, actor); - if (ptr.getCellRef().getEnchantmentCharge() != -1 - && ptr.getCellRef().getEnchantmentCharge() < castCost) + if (ptr.getCellRef().getEnchantmentCharge() != -1 && ptr.getCellRef().getEnchantmentCharge() < castCost) return 0.f; float rating = rateEffects(enchantment->mEffects, actor, enemy); - rating *= 1.25f; // prefer rechargable magic items over spells + rating *= 1.25f; // prefer rechargeable magic items over spells return rating; } return 0.f; } - float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) + float rateEffect(const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { // NOTE: enemy may be empty float rating = 1; switch (effect.mEffectID) { - case ESM::MagicEffect::Soultrap: - case ESM::MagicEffect::AlmsiviIntervention: - case ESM::MagicEffect::DivineIntervention: - case ESM::MagicEffect::CalmHumanoid: - case ESM::MagicEffect::CalmCreature: - case ESM::MagicEffect::FrenzyHumanoid: - case ESM::MagicEffect::FrenzyCreature: - case ESM::MagicEffect::DemoralizeHumanoid: - case ESM::MagicEffect::DemoralizeCreature: - case ESM::MagicEffect::RallyHumanoid: - case ESM::MagicEffect::RallyCreature: - case ESM::MagicEffect::Charm: - case ESM::MagicEffect::DetectAnimal: - case ESM::MagicEffect::DetectEnchantment: - case ESM::MagicEffect::DetectKey: - case ESM::MagicEffect::Telekinesis: - case ESM::MagicEffect::Mark: - case ESM::MagicEffect::Recall: - case ESM::MagicEffect::Jump: - case ESM::MagicEffect::WaterBreathing: - case ESM::MagicEffect::SwiftSwim: - case ESM::MagicEffect::WaterWalking: - case ESM::MagicEffect::SlowFall: - case ESM::MagicEffect::Light: - case ESM::MagicEffect::Lock: - case ESM::MagicEffect::Open: - case ESM::MagicEffect::TurnUndead: - case ESM::MagicEffect::WeaknessToCommonDisease: - case ESM::MagicEffect::WeaknessToBlightDisease: - case ESM::MagicEffect::WeaknessToCorprusDisease: - case ESM::MagicEffect::CureCommonDisease: - case ESM::MagicEffect::CureBlightDisease: - case ESM::MagicEffect::CureCorprusDisease: - case ESM::MagicEffect::ResistBlightDisease: - case ESM::MagicEffect::ResistCommonDisease: - case ESM::MagicEffect::ResistCorprusDisease: - case ESM::MagicEffect::Invisibility: - case ESM::MagicEffect::Chameleon: - case ESM::MagicEffect::NightEye: - case ESM::MagicEffect::Vampirism: - case ESM::MagicEffect::StuntedMagicka: - case ESM::MagicEffect::ExtraSpell: - case ESM::MagicEffect::RemoveCurse: - case ESM::MagicEffect::CommandCreature: - case ESM::MagicEffect::CommandHumanoid: - return 0.f; + case ESM::MagicEffect::Soultrap: + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + case ESM::MagicEffect::CalmHumanoid: + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::FrenzyHumanoid: + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::RallyHumanoid: + case ESM::MagicEffect::RallyCreature: + case ESM::MagicEffect::Charm: + case ESM::MagicEffect::DetectAnimal: + case ESM::MagicEffect::DetectEnchantment: + case ESM::MagicEffect::DetectKey: + case ESM::MagicEffect::Telekinesis: + case ESM::MagicEffect::Mark: + case ESM::MagicEffect::Recall: + case ESM::MagicEffect::Jump: + case ESM::MagicEffect::WaterBreathing: + case ESM::MagicEffect::SwiftSwim: + case ESM::MagicEffect::WaterWalking: + case ESM::MagicEffect::SlowFall: + case ESM::MagicEffect::Light: + case ESM::MagicEffect::Lock: + case ESM::MagicEffect::Open: + case ESM::MagicEffect::TurnUndead: + case ESM::MagicEffect::WeaknessToCommonDisease: + case ESM::MagicEffect::WeaknessToBlightDisease: + case ESM::MagicEffect::WeaknessToCorprusDisease: + case ESM::MagicEffect::CureCommonDisease: + case ESM::MagicEffect::CureBlightDisease: + case ESM::MagicEffect::CureCorprusDisease: + case ESM::MagicEffect::ResistBlightDisease: + case ESM::MagicEffect::ResistCommonDisease: + case ESM::MagicEffect::ResistCorprusDisease: + case ESM::MagicEffect::Invisibility: + case ESM::MagicEffect::Chameleon: + case ESM::MagicEffect::NightEye: + case ESM::MagicEffect::Vampirism: + case ESM::MagicEffect::StuntedMagicka: + case ESM::MagicEffect::ExtraSpell: + case ESM::MagicEffect::RemoveCurse: + case ESM::MagicEffect::CommandCreature: + case ESM::MagicEffect::CommandHumanoid: + return 0.f; - case ESM::MagicEffect::Blind: + case ESM::MagicEffect::Blind: { if (enemy.isEmpty()) return 0.f; @@ -243,13 +251,13 @@ namespace MWMechanics return 0.f; // Enemy doesn't attack - if (stats.getDrawState() != MWMechanics::DrawState_Weapon) + if (stats.getDrawState() != MWMechanics::DrawState::Weapon) return 0.f; break; } - case ESM::MagicEffect::Sound: + case ESM::MagicEffect::Sound: { if (enemy.isEmpty()) return 0.f; @@ -257,20 +265,20 @@ namespace MWMechanics const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); // Enemy can't cast spells - if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() > 0) + if (stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Silence).getMagnitude() > 0) return 0.f; if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; // Enemy doesn't cast spells - if (stats.getDrawState() != MWMechanics::DrawState_Spell) + if (stats.getDrawState() != MWMechanics::DrawState::Spell) return 0.f; break; } - case ESM::MagicEffect::Silence: + case ESM::MagicEffect::Silence: { if (enemy.isEmpty()) return 0.f; @@ -282,38 +290,39 @@ namespace MWMechanics return 0.f; // Enemy doesn't cast spells - if (stats.getDrawState() != MWMechanics::DrawState_Spell) + if (stats.getDrawState() != MWMechanics::DrawState::Spell) return 0.f; break; } - case ESM::MagicEffect::RestoreAttribute: - return 0.f; // TODO: implement based on attribute damage - case ESM::MagicEffect::RestoreSkill: - return 0.f; // TODO: implement based on skill damage + case ESM::MagicEffect::RestoreAttribute: + return 0.f; // TODO: implement based on attribute damage + case ESM::MagicEffect::RestoreSkill: + return 0.f; // TODO: implement based on skill damage - case ESM::MagicEffect::ResistFire: - case ESM::MagicEffect::ResistFrost: - case ESM::MagicEffect::ResistMagicka: - case ESM::MagicEffect::ResistNormalWeapons: - case ESM::MagicEffect::ResistParalysis: - case ESM::MagicEffect::ResistPoison: - case ESM::MagicEffect::ResistShock: - case ESM::MagicEffect::SpellAbsorption: - case ESM::MagicEffect::Reflect: - return 0.f; // probably useless since we don't know in advance what the enemy will cast + case ESM::MagicEffect::ResistFire: + case ESM::MagicEffect::ResistFrost: + case ESM::MagicEffect::ResistMagicka: + case ESM::MagicEffect::ResistNormalWeapons: + case ESM::MagicEffect::ResistParalysis: + case ESM::MagicEffect::ResistPoison: + case ESM::MagicEffect::ResistShock: + case ESM::MagicEffect::SpellAbsorption: + case ESM::MagicEffect::Reflect: + return 0.f; // probably useless since we don't know in advance what the enemy will cast - // don't cast these for now as they would make the NPC cast the same effect over and over again, especially when they have potions - case ESM::MagicEffect::FortifyAttribute: - case ESM::MagicEffect::FortifyHealth: - case ESM::MagicEffect::FortifyMagicka: - case ESM::MagicEffect::FortifyFatigue: - case ESM::MagicEffect::FortifySkill: - case ESM::MagicEffect::FortifyMaximumMagicka: - case ESM::MagicEffect::FortifyAttack: - return 0.f; + // don't cast these for now as they would make the NPC cast the same effect over and over again, especially + // when they have potions + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::FortifyMaximumMagicka: + case ESM::MagicEffect::FortifyAttack: + return 0.f; - case ESM::MagicEffect::Burden: + case ESM::MagicEffect::Burden: { if (enemy.isEmpty()) return 0.f; @@ -327,7 +336,7 @@ namespace MWMechanics if (burden > 0) return 0.f; - if ((effect.mMagnMin + effect.mMagnMax)/2.f > -burden) + if ((effect.mMagnMin + effect.mMagnMax) / 2.f > -burden) rating *= 3; else return 0.f; @@ -335,7 +344,7 @@ namespace MWMechanics break; } - case ESM::MagicEffect::Feather: + case ESM::MagicEffect::Feather: { // Ignore actors without inventory if (!actor.getClass().hasInventoryStore(actor)) @@ -346,7 +355,7 @@ namespace MWMechanics if (burden <= 0) return 0.f; - if ((effect.mMagnMin + effect.mMagnMax)/2.f >= burden) + if ((effect.mMagnMin + effect.mMagnMax) / 2.f >= burden) rating *= 3; else return 0.f; @@ -354,102 +363,117 @@ namespace MWMechanics break; } - case ESM::MagicEffect::Levitate: - return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway - case ESM::MagicEffect::BoundBoots: - case ESM::MagicEffect::BoundHelm: - if (actor.getClass().isNpc()) - { - // Beast races can't wear helmets or boots - std::string raceid = actor.get()->mBase->mRace; - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceid); - if (race->mData.mFlags & ESM::Race::Beast) - return 0.f; - } - else - return 0.f; - - break; - // Creatures can not wear armor - case ESM::MagicEffect::BoundCuirass: - case ESM::MagicEffect::BoundGloves: - if (!actor.getClass().isNpc()) - return 0.f; - break; - - case ESM::MagicEffect::BoundLongbow: - // AI should not summon the bow if there is no suitable ammo. - if (rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f) - return 0.f; - break; - - case ESM::MagicEffect::RestoreHealth: - case ESM::MagicEffect::RestoreMagicka: - case ESM::MagicEffect::RestoreFatigue: - if (effect.mRange == ESM::RT_Self) - { - int priority = 1; - if (effect.mEffectID == ESM::MagicEffect::RestoreHealth) - priority = 10; - const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - const DynamicStat& current = stats.getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); - // NB: this currently assumes the hardcoded magic effect flags are used - const float magnitude = (effect.mMagnMin + effect.mMagnMax)/2.f; - const float toHeal = magnitude * std::max(1, effect.mDuration); - // Effect doesn't heal more than we need, *or* we are below 1/2 health - if (current.getModified() - current.getCurrent() > toHeal - || current.getCurrent() < current.getModified()*0.5) + case ESM::MagicEffect::Levitate: + return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundHelm: + if (actor.getClass().isNpc()) { - return 10000.f * priority - - (toHeal - (current.getModified()-current.getCurrent())); // prefer the most fitting potion + // Beast races can't wear helmets or boots + const ESM::RefId& raceid = actor.get()->mBase->mRace; + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(raceid); + if (race->mData.mFlags & ESM::Race::Beast) + return 0.f; } else - return -10000.f * priority; // Save for later - } - break; - - case ESM::MagicEffect::Dispel: - { - int numPositive = 0; - int numNegative = 0; - int diff = 0; - - if (effect.mRange == ESM::RT_Self) - { - numPositive = numEffectsToDispel(actor, -1, false); - numNegative = numEffectsToDispel(actor); - - diff = numNegative - numPositive; - } - else - { - if (enemy.isEmpty()) return 0.f; - numPositive = numEffectsToDispel(enemy, -1, false); - numNegative = numEffectsToDispel(enemy); + break; + case ESM::MagicEffect::BoundShield: + if (!actor.getClass().hasInventoryStore(actor)) + return 0.f; + else if (!actor.getClass().isNpc()) + { + // If the actor is an NPC they can benefit from the armor rating, otherwise check if we've got a + // one-handed weapon to use with the shield + const auto& store = actor.getClass().getInventoryStore(actor); + auto oneHanded = std::find_if(store.cbegin(MWWorld::ContainerStore::Type_Weapon), store.cend(), + [](const MWWorld::ConstPtr& weapon) { + if (weapon.getClass().getItemHealth(weapon) <= 0.f) + return false; + short type = weapon.get()->mBase->mData.mType; + return !(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded); + }); + if (oneHanded == store.cend()) + return 0.f; + } + break; + // Creatures can not wear armor + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundGloves: + if (!actor.getClass().isNpc()) + return 0.f; + break; - diff = numPositive - numNegative; + case ESM::MagicEffect::RestoreHealth: + case ESM::MagicEffect::RestoreMagicka: + case ESM::MagicEffect::RestoreFatigue: + if (effect.mRange == ESM::RT_Self) + { + int priority = 1; + if (effect.mEffectID == ESM::MagicEffect::RestoreHealth) + priority = 10; + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const DynamicStat& current + = stats.getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); + // NB: this currently assumes the hardcoded magic effect flags are used + const float magnitude = (effect.mMagnMin + effect.mMagnMax) / 2.f; + const float toHeal = magnitude * std::max(1, effect.mDuration); + // Effect doesn't heal more than we need, *or* we are below 1/2 health + if (current.getModified() - current.getCurrent() > toHeal + || current.getCurrent() < current.getModified() * 0.5) + { + return 10000.f * priority + - (toHeal + - (current.getModified() - current.getCurrent())); // prefer the most fitting potion + } + else + return -10000.f * priority; // Save for later + } + break; - // if rating < 0 here, the spell will be considered as negative later - rating *= -1; + case ESM::MagicEffect::Dispel: + { + int numPositive = 0; + int numNegative = 0; + int diff = 0; + + if (effect.mRange == ESM::RT_Self) + { + numPositive = numEffectsToDispel(actor, -1, false); + numNegative = numEffectsToDispel(actor); + + diff = numNegative - numPositive; + } + else + { + if (enemy.isEmpty()) + return 0.f; + + numPositive = numEffectsToDispel(enemy, -1, false); + numNegative = numEffectsToDispel(enemy); + + diff = numPositive - numNegative; + + // if rating < 0 here, the spell will be considered as negative later + rating *= -1; + } + + if (diff <= 0) + return 0.f; + + rating *= (diff) / 5.f; + + break; } - if (diff <= 0) - return 0.f; + // Prefer Cure effects over Dispel, because Dispel also removes positive effects + case ESM::MagicEffect::CureParalyzation: + return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Paralyze); - rating *= (diff) / 5.f; - - break; - } - - // Prefer Cure effects over Dispel, because Dispel also removes positive effects - case ESM::MagicEffect::CureParalyzation: - return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Paralyze); - - case ESM::MagicEffect::CurePoison: - return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Poison); - case ESM::MagicEffect::DisintegrateArmor: + case ESM::MagicEffect::CurePoison: + return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Poison); + case ESM::MagicEffect::DisintegrateArmor: { if (enemy.isEmpty()) return 0.f; @@ -470,13 +494,13 @@ namespace MWMechanics MWWorld::InventoryStore::Slot_RightGauntlet, MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Greaves, - MWWorld::InventoryStore::Slot_Boots + MWWorld::InventoryStore::Slot_Boots, }; bool enemyHasArmor = false; // Ignore enemy without armor - for (unsigned int i=0; i= 0 && effect.mAttribute < ESM::Attribute::Length) + case ESM::MagicEffect::AbsorbAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::DrainAttribute: + if (!enemy.isEmpty() + && enemy.getClass() + .getCreatureStats(enemy) + .getAttribute(ESM::Attribute::AttributeID(effect.mAttribute)) + .getModified() + <= 0) + return 0.f; { - const float attributePriorities[ESM::Attribute::Length] = { - 1.0f, // Strength - 0.5f, // Intelligence - 0.6f, // Willpower - 0.7f, // Agility - 0.5f, // Speed - 0.8f, // Endurance - 0.7f, // Personality - 0.3f // Luck - }; - rating *= attributePriorities[effect.mAttribute]; + if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length) + { + const float attributePriorities[ESM::Attribute::Length] = { + 1.0f, // Strength + 0.5f, // Intelligence + 0.6f, // Willpower + 0.7f, // Agility + 0.5f, // Speed + 0.8f, // Endurance + 0.7f, // Personality + 0.3f // Luck + }; + rating *= attributePriorities[effect.mAttribute]; + } } - } - break; + break; - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::DrainSkill: - if (enemy.isEmpty() || !enemy.getClass().isNpc()) - return 0.f; - if (enemy.getClass().getSkill(enemy, effect.mSkill) <= 0) - return 0.f; - break; + case ESM::MagicEffect::AbsorbSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::DrainSkill: + if (enemy.isEmpty() || !enemy.getClass().isNpc()) + return 0.f; + if (enemy.getClass().getSkill(enemy, ESM::Skill::indexToRefId(effect.mSkill)) <= 0) + return 0.f; + break; - default: - break; + default: + break; } // Allow only one summoned creature at time @@ -553,6 +584,52 @@ namespace MWMechanics if (!creatureStats.getSummonedCreatureMap().empty()) return 0.f; + // But rate summons higher than other effects + rating = 3.f; + } + if (effect.mEffectID >= ESM::MagicEffect::BoundDagger && effect.mEffectID <= ESM::MagicEffect::BoundGloves) + { + // Prefer casting bound items over other spells + rating = 2.f; + // While rateSpell prevents actors from recasting the same spell, it doesn't prevent them from casting + // different spells with the same effect. Multiple instances of the same bound item don't stack so if the + // effect is already active, rate it as useless. Likewise, if the actor already has a bound weapon, don't + // summon another of a different kind unless what we have is a bow and the actor is out of ammo. + // FIXME: This code assumes the summoned item is of the usual type (i.e. a mod hasn't changed Bound Bow to + // summon an Axe instead) + if (effect.mEffectID <= ESM::MagicEffect::BoundLongbow) + { + for (int e = ESM::MagicEffect::BoundDagger; e <= ESM::MagicEffect::BoundLongbow; ++e) + if (actor.getClass().getCreatureStats(actor).getMagicEffects().getOrDefault(e).getMagnitude() > 0.f + && (e != ESM::MagicEffect::BoundLongbow || effect.mEffectID == e + || rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f)) + return 0.f; + ESM::RefId skill = ESM::Skill::ShortBlade; + if (effect.mEffectID == ESM::MagicEffect::BoundLongsword) + skill = ESM::Skill::LongBlade; + else if (effect.mEffectID == ESM::MagicEffect::BoundMace) + skill = ESM::Skill::BluntWeapon; + else if (effect.mEffectID == ESM::MagicEffect::BoundBattleAxe) + skill = ESM::Skill::Axe; + else if (effect.mEffectID == ESM::MagicEffect::BoundSpear) + skill = ESM::Skill::Spear; + else if (effect.mEffectID == ESM::MagicEffect::BoundLongbow) + { + // AI should not summon the bow if there is no suitable ammo. + if (rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f) + return 0.f; + skill = ESM::Skill::Marksman; + } + // Prefer summoning items we know how to use + rating *= (50.f + actor.getClass().getSkill(actor, skill)) / 100.f; + } + else if (actor.getClass() + .getCreatureStats(actor) + .getMagicEffects() + .getOrDefault(effect.mEffectID) + .getMagnitude() + > 0.f) + return 0.f; } // Underwater casting not possible @@ -568,7 +645,8 @@ namespace MWMechanics return 0.f; } - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) { rating *= -1.f; @@ -592,14 +670,14 @@ namespace MWMechanics { CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); - if (stats.getMagicEffects().get(effect.mEffectID).getMagnitude() > 0) + if (stats.getMagicEffects().getOrDefault(effect.mEffectID).getMagnitude() > 0) return 0.f; } else { CreatureStats& stats = actor.getClass().getCreatureStats(actor); - if (stats.getMagicEffects().get(effect.mEffectID).getMagnitude() > 0) + if (stats.getMagicEffects().getOrDefault(effect.mEffectID).getMagnitude() > 0) return 0.f; } } @@ -614,37 +692,45 @@ namespace MWMechanics return rating; } - float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) + float rateEffects( + const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool useSpellMult) { // NOTE: enemy may be empty float rating = 0.f; - float ratingMult = 1.f; // NB: this multiplier is applied to the effect rating, not the final rating - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); - for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) + for (const ESM::ENAMstruct& effect : list.mList) { - ratingMult = (it->mRange == ESM::RT_Target) ? fAIRangeMagicSpellMult : fAIMagicSpellMult; - - rating += rateEffect(*it, actor, enemy) * ratingMult; + float effectRating = rateEffect(effect, actor, enemy); + if (useSpellMult) + { + if (effect.mRange == ESM::RT_Target) + effectRating *= fAIRangeMagicSpellMult; + else + effectRating *= fAIMagicSpellMult; + } + rating += effectRating; } return rating; } float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); float mult = fAIMagicSpellMult; - for (std::vector::const_iterator effectIt = - spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); + effectIt != spell->mEffects.mList.end(); ++effectIt) { if (effectIt->mRange == ESM::RT_Target) { diff --git a/apps/openmw/mwmechanics/spellpriority.hpp b/apps/openmw/mwmechanics/spellpriority.hpp index 0305f24b5..32cd17155 100644 --- a/apps/openmw/mwmechanics/spellpriority.hpp +++ b/apps/openmw/mwmechanics/spellpriority.hpp @@ -23,16 +23,17 @@ namespace MWMechanics Target = 0x100 }; - int getRangeTypes (const ESM::EffectList& effects); + int getRangeTypes(const ESM::EffectList& effects); - float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); - float rateMagicItem (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); - float ratePotion (const MWWorld::Ptr& item, const MWWorld::Ptr &actor); + float rateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float rateMagicItem(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float ratePotion(const MWWorld::Ptr& item, const MWWorld::Ptr& actor); /// @note target may be empty - float rateEffect (const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float rateEffect(const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); /// @note target may be empty - float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float rateEffects( + const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool useSpellMult = true); float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } diff --git a/apps/openmw/mwmechanics/spellresistance.cpp b/apps/openmw/mwmechanics/spellresistance.cpp index 1edf14091..e55d851ec 100644 --- a/apps/openmw/mwmechanics/spellresistance.cpp +++ b/apps/openmw/mwmechanics/spellresistance.cpp @@ -2,6 +2,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -15,7 +17,7 @@ namespace MWMechanics { float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell, const MagicEffects* effects) + const ESM::Spell* spell, const MagicEffects* effects) { if (!actor.getClass().isActor()) return 1; @@ -24,14 +26,14 @@ namespace MWMechanics return 1 - resistance / 100.f; } - float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell, const MagicEffects* effects) + float getEffectResistance(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell, const MagicEffects* effects) { // Effects with no resistance attribute belonging to them can not be resisted if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) return 0.f; - const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); + const auto magicEffect = MWBase::Environment::get().getESMStore()->get().find(effectId); const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects(); @@ -51,7 +53,8 @@ namespace MWMechanics if (castChance > 0) x *= 50 / castChance; - float roll = Misc::Rng::rollClosedProbability() * 100; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + float roll = Misc::Rng::rollClosedProbability(prng) * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) roll -= resistance; @@ -69,23 +72,23 @@ namespace MWMechanics return x; } - float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects) + float getEffectResistanceAttribute(short effectId, const MagicEffects* actorEffects) { short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); float resistance = 0; if (resistanceEffect != -1) - resistance += actorEffects->get(resistanceEffect).getMagnitude(); + resistance += actorEffects->getOrDefault(resistanceEffect).getMagnitude(); if (weaknessEffect != -1) - resistance -= actorEffects->get(weaknessEffect).getMagnitude(); + resistance -= actorEffects->getOrDefault(weaknessEffect).getMagnitude(); if (effectId == ESM::MagicEffect::FireDamage) - resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude(); + resistance += actorEffects->getOrDefault(ESM::MagicEffect::FireShield).getMagnitude(); if (effectId == ESM::MagicEffect::ShockDamage) - resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude(); + resistance += actorEffects->getOrDefault(ESM::MagicEffect::LightningShield).getMagnitude(); if (effectId == ESM::MagicEffect::FrostDamage) - resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude(); + resistance += actorEffects->getOrDefault(ESM::MagicEffect::FrostShield).getMagnitude(); return resistance; } diff --git a/apps/openmw/mwmechanics/spellresistance.hpp b/apps/openmw/mwmechanics/spellresistance.hpp index 8e74c2260..6966a456d 100644 --- a/apps/openmw/mwmechanics/spellresistance.hpp +++ b/apps/openmw/mwmechanics/spellresistance.hpp @@ -20,18 +20,18 @@ namespace MWMechanics /// @param effects Override the actor's current magicEffects. Useful if there are effects currently /// being applied (but not applied yet) that should also be considered. float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); + const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); /// Get the effective resistance against an effect casted by the given actor in the given spell (optional). /// @return >=100 for fully resisted. can also return negative value for damage amplification. /// @param effects Override the actor's current magicEffects. Useful if there are effects currently /// being applied (but not applied yet) that should also be considered. - float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); + float getEffectResistance(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); /// Get the resistance attribute against an effect for a given actor. This will add together /// ResistX and Weakness to X effects relevant against the given effect. - float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects); + float getEffectResistanceAttribute(short effectId, const MagicEffects* actorEffects); } #endif diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index df354cdb8..05f5864d1 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,10 +1,10 @@ #include "spells.hpp" #include -#include -#include -#include -#include +#include +#include + +#include /* Start of tes3mp addition @@ -25,222 +25,133 @@ #include "actorutil.hpp" #include "creaturestats.hpp" -#include "magiceffects.hpp" #include "stat.hpp" namespace MWMechanics { - Spells::Spells() - : mSpellsChanged(false) - { - } + Spells::Spells() {} - Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells), - mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers), - mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects) + Spells::Spells(const Spells& spells) + : mSpellList(spells.mSpellList) + , mSpells(spells.mSpells) + , mSelectedSpell(spells.mSelectedSpell) + , mUsedPowers(spells.mUsedPowers) { - if(mSpellList) + if (mSpellList) mSpellList->addListener(this); } - Spells::Spells(Spells&& spells) : mSpellList(std::move(spells.mSpellList)), mSpells(std::move(spells.mSpells)), - mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers)), - mSpellsChanged(std::move(spells.mSpellsChanged)), mEffects(std::move(spells.mEffects)), - mSourcedEffects(std::move(spells.mSourcedEffects)) + Spells::Spells(Spells&& spells) + : mSpellList(std::move(spells.mSpellList)) + , mSpells(std::move(spells.mSpells)) + , mSelectedSpell(std::move(spells.mSelectedSpell)) + , mUsedPowers(std::move(spells.mUsedPowers)) { if (mSpellList) mSpellList->updateListener(&spells, this); } - std::map::const_iterator Spells::begin() const + std::vector::const_iterator Spells::begin() const { return mSpells.begin(); } - std::map::const_iterator Spells::end() const + std::vector::const_iterator Spells::end() const { return mSpells.end(); } - void Spells::rebuildEffects() const - { - mEffects = MagicEffects(); - mSourcedEffects.clear(); - - for (const auto& iter : mSpells) - { - const ESM::Spell *spell = iter.first; - - if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || - spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) - { - int i=0; - for (const auto& effect : spell->mEffects.mList) - { - if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end()) - { - ++i; - continue; // effect was purged - } - - float random = 1.f; - if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end()) - random = iter.second.mEffectRands.at(i); - - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random; - mEffects.add (effect, magnitude); - mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude); - - ++i; - } - } - } - } - - bool Spells::hasSpell(const std::string &spell) const + bool Spells::hasSpell(const ESM::RefId& spell) const { return hasSpell(SpellList::getSpell(spell)); } - bool Spells::hasSpell(const ESM::Spell *spell) const + bool Spells::hasSpell(const ESM::Spell* spell) const { - return mSpells.find(spell) != mSpells.end(); + return std::find(mSpells.begin(), mSpells.end(), spell) != mSpells.end(); } - void Spells::add (const ESM::Spell* spell) + void Spells::add(const ESM::Spell* spell) { mSpellList->add(spell); } - void Spells::add (const std::string& spellId) + void Spells::add(const ESM::RefId& spellId) { add(SpellList::getSpell(spellId)); } void Spells::addSpell(const ESM::Spell* spell) { - if (mSpells.find (spell)==mSpells.end()) - { - std::map random; - - // Determine the random magnitudes (unless this is a castable spell, in which case - // they will be determined when the spell is cast) - if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) - { - for (unsigned int i=0; imEffects.mList.size();++i) - { - if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax) - { - int delta = spell->mEffects.mList[i].mMagnMax - spell->mEffects.mList[i].mMagnMin; - random[i] = Misc::Rng::rollDice(delta + 1) / static_cast(delta); - } - } - } - - SpellParams params; - params.mEffectRands = random; - mSpells.emplace(spell, params); - mSpellsChanged = true; - } + if (!hasSpell(spell)) + mSpells.emplace_back(spell); } - void Spells::remove (const std::string& spellId) + void Spells::remove(const ESM::RefId& spellId) { const auto spell = SpellList::getSpell(spellId); removeSpell(spell); mSpellList->remove(spell); - if (spellId==mSelectedSpell) - mSelectedSpell.clear(); + if (spellId == mSelectedSpell) + mSelectedSpell = ESM::RefId(); } void Spells::removeSpell(const ESM::Spell* spell) { - const auto it = mSpells.find(spell); - if(it != mSpells.end()) - { + const auto it = std::find(mSpells.begin(), mSpells.end(), spell); + if (it != mSpells.end()) mSpells.erase(it); - mSpellsChanged = true; - } - } - - MagicEffects Spells::getMagicEffects() const - { - if (mSpellsChanged) { - rebuildEffects(); - mSpellsChanged = false; - } - return mEffects; } void Spells::removeAllSpells() { mSpells.clear(); - mSpellsChanged = true; } void Spells::clear(bool modifyBase) { removeAllSpells(); - if(modifyBase) + if (modifyBase) mSpellList->clear(); } - void Spells::setSelectedSpell (const std::string& spellId) + void Spells::setSelectedSpell(const ESM::RefId& spellId) { mSelectedSpell = spellId; } - const std::string Spells::getSelectedSpell() const + const ESM::RefId& Spells::getSelectedSpell() const { return mSelectedSpell; } - bool Spells::isSpellActive(const std::string &id) const + bool Spells::hasSpellType(const ESM::Spell::SpellType type) const { - if (id.empty()) - return false; - - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); - if (spell && hasSpell(spell)) - { - auto type = spell->mData.mType; - return (type==ESM::Spell::ST_Ability || type==ESM::Spell::ST_Blight || type==ESM::Spell::ST_Disease || type==ESM::Spell::ST_Curse); - } - - return false; - } - - bool Spells::hasDisease(const ESM::Spell::SpellType type) const - { - for (const auto& iter : mSpells) - { - const ESM::Spell *spell = iter.first; - if (spell->mData.mType == type) - return true; - } - - return false; + auto it = std::find_if(std::begin(mSpells), std::end(mSpells), + [=](const ESM::Spell* spell) { return spell->mData.mType == type; }); + return it != std::end(mSpells); } bool Spells::hasCommonDisease() const { - return hasDisease(ESM::Spell::ST_Disease); + return hasSpellType(ESM::Spell::ST_Disease); } bool Spells::hasBlightDisease() const { - return hasDisease(ESM::Spell::ST_Blight); + return hasSpellType(ESM::Spell::ST_Blight); } void Spells::purge(const SpellFilter& filter) { - std::vector purged; - for (auto iter = mSpells.begin(); iter!=mSpells.end();) + std::vector purged; + for (auto iter = mSpells.begin(); iter != mSpells.end();) { - const ESM::Spell *spell = iter->first; + const ESM::Spell* spell = *iter; if (filter(spell)) { +<<<<<<< HEAD /* Start of tes3mp addition @@ -252,13 +163,15 @@ namespace MWMechanics */ mSpells.erase(iter++); +======= + iter = mSpells.erase(iter); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 purged.push_back(spell->mId); - mSpellsChanged = true; } else ++iter; } - if(!purged.empty()) + if (!purged.empty()) mSpellList->removeAll(purged); } @@ -282,44 +195,7 @@ namespace MWMechanics purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; }); } - void Spells::removeEffects(const std::string &id) - { - if (isSpellActive(id)) - { - for (auto& spell : mSpells) - { - if (spell.first == SpellList::getSpell(id)) - { - for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++) - { - spell.second.mPurgedEffects.insert(i); - } - } - } - - mSpellsChanged = true; - } - } - - void Spells::visitEffectSources(EffectSourceVisitor &visitor) const - { - if (mSpellsChanged) { - rebuildEffects(); - mSpellsChanged = false; - } - - for (const auto& it : mSourcedEffects) - { - const ESM::Spell * spell = it.first; - for (const auto& effectIt : it.second) - { - // FIXME: since Spells merges effects with the same ID, there is no sense to use multiple effects with same ID here - visitor.visit(effectIt.first, -1, spell->mName, spell->mId, -1, effectIt.second.getMagnitude()); - } - } - } - - bool Spells::hasCorprusEffect(const ESM::Spell *spell) + bool Spells::hasCorprusEffect(const ESM::Spell* spell) { for (const auto& effectIt : spell->mEffects.mList) { @@ -331,54 +207,16 @@ namespace MWMechanics return false; } - void Spells::purgeEffect(int effectId) - { - for (auto& spellIt : mSpells) - { - int i = 0; - for (auto& effectIt : spellIt.first->mEffects.mList) - { - if (effectIt.mEffectID == effectId) - { - spellIt.second.mPurgedEffects.insert(i); - mSpellsChanged = true; - } - ++i; - } - } - } - - void Spells::purgeEffect(int effectId, const std::string & sourceId) - { - // Effect source may be not a spell - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(sourceId); - if (spell == nullptr) - return; - - auto spellIt = mSpells.find(spell); - if (spellIt == mSpells.end()) - return; - - int index = 0; - for (auto& effectIt : spellIt->first->mEffects.mList) - { - if (effectIt.mEffectID == effectId) - { - spellIt->second.mPurgedEffects.insert(index); - mSpellsChanged = true; - } - ++index; - } - } - bool Spells::canUsePower(const ESM::Spell* spell) const { - const auto it = mUsedPowers.find(spell); + const auto it = std::find_if( + std::begin(mUsedPowers), std::end(mUsedPowers), [&](auto& pair) { return pair.first == spell; }); return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::usePower(const ESM::Spell* spell) { +<<<<<<< HEAD mUsedPowers[spell] = MWBase::Environment::get().getWorld()->getTimeStamp(); /* @@ -411,68 +249,55 @@ namespace MWMechanics */ void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) +======= + // Updates or inserts a new entry with the current timestamp. + const auto it = std::find_if( + std::begin(mUsedPowers), std::end(mUsedPowers), [&](auto& pair) { return pair.first == spell; }); + const auto timestamp = MWBase::Environment::get().getWorld()->getTimeStamp(); + if (it == mUsedPowers.end()) + mUsedPowers.emplace_back(spell, timestamp); + else + it->second = timestamp; + } + + void Spells::readState(const ESM::SpellState& state, CreatureStats* creatureStats) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { const auto& baseSpells = mSpellList->getSpells(); - for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + for (const ESM::RefId& id : state.mSpells) { // Discard spells that are no longer available due to changed content files - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().search(id); if (spell) { - mSpells[spell].mEffectRands = it->second.mEffectRands; - mSpells[spell].mPurgedEffects = it->second.mPurgedEffects; + addSpell(spell); - if (it->first == state.mSelectedSpell) - mSelectedSpell = it->first; + if (id == state.mSelectedSpell) + mSelectedSpell = id; } } // Add spells from the base record - for(const std::string& id : baseSpells) + for (const ESM::RefId& id : baseSpells) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); - if(spell) + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().search(id); + if (spell) addSpell(spell); } - for (std::map::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) + for (auto it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().search(it->first); if (!spell) continue; - mUsedPowers[spell] = MWWorld::TimeStamp(it->second); + mUsedPowers.emplace_back(spell, MWWorld::TimeStamp(it->second)); } - for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) + // Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and + // only in old saves. Convert data to the new approach. + for (auto it = state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) { - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); - if (!spell) - continue; - - CorprusStats stats; - - int worsening = state.mCorprusSpells.at(it->first).mWorsenings; - - for (int i=0; imEffects.mList) - { - if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) - stats.mWorsenings[effect.mAttribute] = worsening; - } - stats.mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - - creatureStats->addCorprusSpell(it->first, stats); - } - - mSpellsChanged = true; - - // Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and only in old saves. Convert data to the new approach. - for (std::map >::const_iterator it = - state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) - { - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().search(it->first); if (!spell) continue; @@ -481,41 +306,40 @@ namespace MWMechanics if (creatureStats->getActorId() != player.getClass().getCreatureStats(player).getActorId()) return; - // Note: if target actor has the Restore attirbute effects, stats will be restored. - for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) + // Note: if target actor has the Restore attribute effects, stats will be restored. + for (const ESM::SpellState::PermanentSpellEffectInfo& info : it->second) { // Applied corprus effects are already in loaded stats modifiers - if (effectIt->mId == ESM::MagicEffect::FortifyAttribute) + if (info.mId == ESM::MagicEffect::FortifyAttribute) { - AttributeValue attr = creatureStats->getAttribute(effectIt->mArg); - attr.setModifier(attr.getModifier() - effectIt->mMagnitude); - attr.damage(-effectIt->mMagnitude); - creatureStats->setAttribute(effectIt->mArg, attr); + auto id = static_cast(info.mArg); + AttributeValue attr = creatureStats->getAttribute(id); + attr.setModifier(attr.getModifier() - info.mMagnitude); + attr.damage(-info.mMagnitude); + creatureStats->setAttribute(id, attr); } - else if (effectIt->mId == ESM::MagicEffect::DrainAttribute) + else if (info.mId == ESM::MagicEffect::DrainAttribute) { - AttributeValue attr = creatureStats->getAttribute(effectIt->mArg); - attr.setModifier(attr.getModifier() + effectIt->mMagnitude); - attr.damage(effectIt->mMagnitude); - creatureStats->setAttribute(effectIt->mArg, attr); + auto id = static_cast(info.mArg); + AttributeValue attr = creatureStats->getAttribute(id); + attr.setModifier(attr.getModifier() + info.mMagnitude); + attr.damage(info.mMagnitude); + creatureStats->setAttribute(id, attr); } } } } - void Spells::writeState(ESM::SpellState &state) const + void Spells::writeState(ESM::SpellState& state) const { const auto& baseSpells = mSpellList->getSpells(); - for (const auto& it : mSpells) + for (const auto spell : mSpells) { // Don't save spells and powers stored in the base record - if((it.first->mData.mType != ESM::Spell::ST_Spell && it.first->mData.mType != ESM::Spell::ST_Power) || - std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end()) + if ((spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power) + || std::find(baseSpells.begin(), baseSpells.end(), spell->mId) == baseSpells.end()) { - ESM::SpellState::SpellParams params; - params.mEffectRands = it.second.mEffectRands; - params.mPurgedEffects = it.second.mPurgedEffects; - state.mSpells.emplace(it.first->mId, params); + state.mSpells.emplace_back(spell->mId); } } @@ -525,30 +349,30 @@ namespace MWMechanics state.mUsedPowers[it.first->mId] = it.second.toEsm(); } - bool Spells::setSpells(const std::string& actorId) + bool Spells::setSpells(const ESM::RefId& actorId) { bool result; - std::tie(mSpellList, result) = MWBase::Environment::get().getWorld()->getStore().getSpellList(actorId); + std::tie(mSpellList, result) = MWBase::Environment::get().getESMStore()->getSpellList(actorId); mSpellList->addListener(this); addAllToInstance(mSpellList->getSpells()); return result; } - void Spells::addAllToInstance(const std::vector& spells) + void Spells::addAllToInstance(const std::vector& spells) { - for(const std::string& id : spells) + for (const ESM::RefId& id : spells) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); - if(spell) + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().search(id); + if (spell) addSpell(spell); else - Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << id << "'"; + Log(Debug::Warning) << "Warning: ignoring nonexistent spell " << id; } } Spells::~Spells() { - if(mSpellList) + if (mSpellList) mSpellList->removeListener(this); } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index fd2dd31a9..d3229d675 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -1,14 +1,13 @@ #ifndef GAME_MWMECHANICS_SPELLS_H #define GAME_MWMECHANICS_SPELLS_H -#include #include +#include #include #include #include "../mwworld/timestamp.hpp" -#include "magiceffects.hpp" #include "spelllist.hpp" namespace ESM @@ -28,48 +27,47 @@ namespace MWMechanics /// diseases. It also keeps track of used powers (which can only be used every 24h). class Spells { - std::shared_ptr mSpellList; - std::map mSpells; + std::shared_ptr mSpellList; + std::vector mSpells; - // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) - std::string mSelectedSpell; + // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) + ESM::RefId mSelectedSpell; - std::map mUsedPowers; + std::vector> mUsedPowers; - mutable bool mSpellsChanged; - mutable MagicEffects mEffects; - mutable std::map mSourcedEffects; - void rebuildEffects() const; + bool hasSpellType(const ESM::Spell::SpellType type) const; - bool hasDisease(const ESM::Spell::SpellType type) const; + using SpellFilter = bool (*)(const ESM::Spell*); + void purge(const SpellFilter& filter); - using SpellFilter = bool (*)(const ESM::Spell*); - void purge(const SpellFilter& filter); + void addSpell(const ESM::Spell* spell); + void removeSpell(const ESM::Spell* spell); + void removeAllSpells(); - void addSpell(const ESM::Spell* spell); - void removeSpell(const ESM::Spell* spell); - void removeAllSpells(); + friend class SpellList; - friend class SpellList; - public: - using TIterator = std::map::const_iterator; + public: + using Collection = std::vector; - Spells(); + Spells(); - Spells(const Spells&); + Spells(const Spells&); - Spells(Spells&& spells); + Spells(Spells&& spells); - ~Spells(); + ~Spells(); - static bool hasCorprusEffect(const ESM::Spell *spell); + static bool hasCorprusEffect(const ESM::Spell* spell); - void purgeEffect(int effectId); - void purgeEffect(int effectId, const std::string & sourceId); + bool canUsePower(const ESM::Spell* spell) const; + void usePower(const ESM::Spell* spell); - bool canUsePower (const ESM::Spell* spell) const; - void usePower (const ESM::Spell* spell); + void purgeCommonDisease(); + void purgeBlightDisease(); + void purgeCorprusDisease(); + void purgeCurses(); +<<<<<<< HEAD /* Start of tes3mp addition @@ -84,53 +82,48 @@ namespace MWMechanics void purgeBlightDisease(); void purgeCorprusDisease(); void purgeCurses(); +======= + Collection::const_iterator begin() const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - TIterator begin() const; + Collection::const_iterator end() const; - TIterator end() const; + bool hasSpell(const ESM::RefId& spell) const; + bool hasSpell(const ESM::Spell* spell) const; - bool hasSpell(const std::string& spell) const; - bool hasSpell(const ESM::Spell* spell) const; + void add(const ESM::RefId& spell); + ///< Adding a spell that is already listed in *this is a no-op. - void add (const std::string& spell); - ///< Adding a spell that is already listed in *this is a no-op. + void add(const ESM::Spell* spell); + ///< Adding a spell that is already listed in *this is a no-op. - void add (const ESM::Spell* spell); - ///< Adding a spell that is already listed in *this is a no-op. + void remove(const ESM::RefId& spell); + ///< If the spell to be removed is the selected spell, the selected spell will be changed to + /// no spell (empty string). - void remove (const std::string& spell); - ///< If the spell to be removed is the selected spell, the selected spell will be changed to - /// no spell (empty string). + void clear(bool modifyBase = false); + ///< Remove all spells of al types. - MagicEffects getMagicEffects() const; - ///< Return sum of magic effects resulting from abilities, blights, deseases and curses. + void setSelectedSpell(const ESM::RefId& spellId); + ///< This function does not verify, if the spell is available. - void clear(bool modifyBase = false); - ///< Remove all spells of al types. + const ESM::RefId& getSelectedSpell() const; + ///< May return an empty string. - void setSelectedSpell (const std::string& spellId); - ///< This function does not verify, if the spell is available. + bool hasCommonDisease() const; - const std::string getSelectedSpell() const; - ///< May return an empty string. + bool hasBlightDisease() const; - bool isSpellActive(const std::string& id) const; - ///< Are we under the effects of the given spell ID? + /// Iteration methods for lua + size_t count() const { return mSpells.size(); } + const ESM::Spell* at(size_t index) const { return mSpells.at(index); } - bool hasCommonDisease() const; + void readState(const ESM::SpellState& state, CreatureStats* creatureStats); + void writeState(ESM::SpellState& state) const; - bool hasBlightDisease() const; + bool setSpells(const ESM::RefId& id); - void removeEffects(const std::string& id); - - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - - void readState (const ESM::SpellState& state, CreatureStats* creatureStats); - void writeState (ESM::SpellState& state) const; - - bool setSpells(const std::string& id); - - void addAllToInstance(const std::vector& spells); + void addAllToInstance(const std::vector& spells); }; } diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 0c667e680..2a63a3a44 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -2,8 +2,10 @@ #include +#include +#include + #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" @@ -14,19 +16,31 @@ namespace MWMechanics { - ESM::Skill::SkillEnum spellSchoolToSkill(int school) + namespace { - static const std::array schoolSkillArray + float getTotalCost(const ESM::EffectList& list, const EffectCostMethod method = EffectCostMethod::GameSpell) { - ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction, - ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration - }; - return schoolSkillArray.at(school); + float cost = 0; + + for (const ESM::ENAMstruct& effect : list.mList) + { + float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect, nullptr, method)); + + // This is applied to the whole spell cost for each effect when + // creating spells, but is only applied on the effect itself in TES:CS. + if (effect.mRange == ESM::RT_Target) + effectCost *= 1.5; + + cost += effectCost; + } + return cost; + } } - float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect) + float calcEffectCost( + const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, const EffectCostMethod method) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); if (!magicEffect) magicEffect = store.get().find(effect.mEffectID); bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude); @@ -34,20 +48,43 @@ namespace MWMechanics bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; int minMagn = hasMagnitude ? effect.mMagnMin : 1; int maxMagn = hasMagnitude ? effect.mMagnMax : 1; + if (method != EffectCostMethod::GameEnchantment) + { + minMagn = std::max(1, minMagn); + maxMagn = std::max(1, maxMagn); + } int duration = hasDuration ? effect.mDuration : 1; if (!appliedOnce) duration = std::max(1, duration); static const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); - float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); + int durationOffset = 0; + int minArea = 0; + if (method == EffectCostMethod::PlayerSpell) + { + durationOffset = 1; + minArea = 1; + } + + float x = 0.5 * (minMagn + maxMagn); x *= 0.1 * magicEffect->mData.mBaseCost; - x *= 1 + duration; - x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; + x *= durationOffset + duration; + x += 0.05 * std::max(minArea, effect.mArea) * magicEffect->mData.mBaseCost; return x * fEffectCostMult; } - int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) + int calcSpellCost(const ESM::Spell& spell) + { + if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc)) + return spell.mData.mCost; + + float cost = getTotalCost(spell.mEffects); + + return std::round(cost); + } + + int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr& actor) { /* * Each point of enchant skill above/under 10 subtracts/adds @@ -59,7 +96,51 @@ namespace MWMechanics return static_cast((result < 1) ? 1 : result); } - float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) + int getEffectiveEnchantmentCastCost(const ESM::Enchantment& enchantment, const MWWorld::Ptr& actor) + { + float castCost; + if (enchantment.mData.mFlags & ESM::Enchantment::Autocalc) + castCost = getTotalCost(enchantment.mEffects, EffectCostMethod::GameEnchantment); + else + castCost = static_cast(enchantment.mData.mCost); + return getEffectiveEnchantmentCastCost(castCost, actor); + } + + int getEnchantmentCharge(const ESM::Enchantment& enchantment) + { + if (enchantment.mData.mFlags & ESM::Enchantment::Autocalc) + { + int charge + = static_cast(std::round(getTotalCost(enchantment.mEffects, EffectCostMethod::GameEnchantment))); + const auto& store = MWBase::Environment::get().getESMStore()->get(); + switch (enchantment.mData.mType) + { + case ESM::Enchantment::CastOnce: + { + static const int iMagicItemChargeOnce = store.find("iMagicItemChargeOnce")->mValue.getInteger(); + return charge * iMagicItemChargeOnce; + } + case ESM::Enchantment::WhenStrikes: + { + static const int iMagicItemChargeStrike = store.find("iMagicItemChargeStrike")->mValue.getInteger(); + return charge * iMagicItemChargeStrike; + } + case ESM::Enchantment::WhenUsed: + { + static const int iMagicItemChargeUse = store.find("iMagicItemChargeUse")->mValue.getInteger(); + return charge * iMagicItemChargeUse; + } + case ESM::Enchantment::ConstantEffect: + { + static const int iMagicItemChargeConst = store.find("iMagicItemChargeConst")->mValue.getInteger(); + return charge * iMagicItemChargeConst; + } + } + } + return enchantment.mData.mCharge; + } + + float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float y = std::numeric_limits::max(); @@ -68,7 +149,8 @@ namespace MWMechanics for (const ESM::ENAMstruct& effect : spell->mEffects.mList) { float x = static_cast(effect.mDuration); - const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + const auto magicEffect + = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) x = std::max(1.f, x); @@ -78,11 +160,14 @@ namespace MWMechanics x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; if (effect.mRange == ESM::RT_Target) x *= 1.5f; - static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fEffectCostMult")->mValue.getFloat(); + static const float fEffectCostMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fEffectCostMult") + ->mValue.getFloat(); x *= fEffectCostMult; - float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool)); + float s = 2.0f * actor.getClass().getSkill(actor, magicEffect->mData.mSchool); if (s - x < y) { y = s - x; @@ -97,12 +182,13 @@ namespace MWMechanics float actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); float actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); - float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck); + float castChance = (lowestSkill - calcSpellCost(*spell) + 0.2f * actorWillpower + 0.1f * actorLuck); return castChance; } - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) + float getSpellSuccessChance( + const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool, bool cap, bool checkMagicka) { // NB: Base chance is calculated here because the effective school pointer must be filled float baseChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool); @@ -111,7 +197,7 @@ namespace MWMechanics CreatureStats& stats = actor.getClass().getCreatureStats(actor); - if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() && !godmode) + if (stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Silence).getMagnitude() && !godmode) return 0; if (spell->mData.mType == ESM::Spell::ST_Power) @@ -123,92 +209,52 @@ namespace MWMechanics if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; - if (checkMagicka && spell->mData.mCost > 0 && stats.getMagicka().getCurrent() < spell->mData.mCost) + if (checkMagicka && calcSpellCost(*spell) > 0 && stats.getMagicka().getCurrent() < calcSpellCost(*spell)) return 0; if (spell->mData.mFlags & ESM::Spell::F_Always) return 100; - float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); + float castBonus = -stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Sound).getMagnitude(); float castChance = baseChance + castBonus; castChance *= stats.getFatigueTerm(); - return std::max(0.f, cap ? std::min(100.f, castChance) : castChance); + if (cap) + return std::clamp(castChance, 0.f, 100.f); + + return std::max(castChance, 0.f); } - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) + float getSpellSuccessChance( + const ESM::RefId& spellId, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool, bool cap, bool checkMagicka) { - if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId)) + if (const auto spell = MWBase::Environment::get().getESMStore()->get().search(spellId)) return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); return 0.f; } - int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) + ESM::RefId getSpellSchool(const ESM::RefId& spellId, const MWWorld::Ptr& actor) { - int school = 0; + ESM::RefId school; getSpellSuccessChance(spellId, actor, &school); return school; } - int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) + ESM::RefId getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) { - int school = 0; + ESM::RefId school; getSpellSuccessChance(spell, actor, &school); return school; } - bool spellIncreasesSkill(const ESM::Spell *spell) + bool spellIncreasesSkill(const ESM::Spell* spell) { return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always); } - bool spellIncreasesSkill(const std::string &spellId) + bool spellIncreasesSkill(const ESM::RefId& spellId) { - const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); + const auto spell = MWBase::Environment::get().getESMStore()->get().search(spellId); return spell && spellIncreasesSkill(spell); } - - bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer) - { - switch (effectId) - { - case ESM::MagicEffect::Levitate: - { - if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); - return false; - } - break; - } - case ESM::MagicEffect::Soultrap: - { - if (!target.getClass().isNpc() // no messagebox for NPCs - && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); - return true; // must still apply to get visual effect and have target regard it as attack - } - break; - } - case ESM::MagicEffect::WaterWalking: - { - if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target)) - return false; - - MWBase::World *world = MWBase::Environment::get().getWorld(); - - if (!world->isWaterWalkingCastableOnTarget(target)) - { - if (castByPlayer && caster == target) - MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}"); - return false; - } - break; - } - } - return true; - } } diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index 865a9126e..a332a231e 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -1,11 +1,12 @@ #ifndef MWMECHANICS_SPELLUTIL_H #define MWMECHANICS_SPELLUTIL_H -#include +#include namespace ESM { struct ENAMstruct; + struct Enchantment; struct MagicEffect; struct Spell; } @@ -17,11 +18,20 @@ namespace MWWorld namespace MWMechanics { - ESM::Skill::SkillEnum spellSchoolToSkill(int school); + enum class EffectCostMethod + { + GameSpell, + PlayerSpell, + GameEnchantment, + }; - float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr); + float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr, + const EffectCostMethod method = EffectCostMethod::GameSpell); + int calcSpellCost(const ESM::Spell& spell); - int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); + int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr& actor); + int getEffectiveEnchantmentCastCost(const ESM::Enchantment& enchantment, const MWWorld::Ptr& actor); + int getEnchantmentCharge(const ESM::Enchantment& enchantment); /** * @param spell spell to cast @@ -32,19 +42,18 @@ namespace MWMechanics * @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 calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool); - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); + float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool); + float getSpellSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, + ESM::RefId* effectiveSchool = nullptr, bool cap = true, bool checkMagicka = true); + float getSpellSuccessChance(const ESM::RefId& spellId, const MWWorld::Ptr& actor, + ESM::RefId* effectiveSchool = nullptr, bool cap = true, bool checkMagicka = true); - int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); - int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); + ESM::RefId getSpellSchool(const ESM::RefId& spellId, const MWWorld::Ptr& actor); + ESM::RefId getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// Get whether or not the given spell contributes to skill progress. bool spellIncreasesSkill(const ESM::Spell* spell); - bool spellIncreasesSkill(const std::string& spellId); - - /// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure. - bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); + bool spellIncreasesSkill(const ESM::RefId& spellId); } #endif diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index c87de2ccb..b603fc220 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -1,180 +1,72 @@ #include "stat.hpp" -#include +#include + +#include namespace MWMechanics { - template - Stat::Stat() : mBase (0), mModified (0), mCurrentModified (0) {} - template - Stat::Stat(T base) : mBase (base), mModified (base), mCurrentModified (base) {} - template - Stat::Stat(T base, T modified) : mBase (base), mModified (modified), mCurrentModified (modified) {} - - template - const T& Stat::getBase() const + template + Stat::Stat() + : mBase(0) + , mModifier(0) + { + } + template + Stat::Stat(T base, T modified) + : mBase(base) + , mModifier(modified) { - return mBase; } - template + template T Stat::getModified(bool capped) const { - if(!capped) - return mModified; - return std::max(static_cast(0), mModified); + if (capped) + return std::max({}, mModifier + mBase); + return mModifier + mBase; } - template - T Stat::getCurrentModified() const - { - return mCurrentModified; - } - - template - T Stat::getModifier() const - { - return mModified-mBase; - } - - template - T Stat::getCurrentModifier() const - { - return mCurrentModified - mModified; - } - - template - void Stat::set (const T& value) - { - T diff = value - mBase; - mBase = mModified = value; - mCurrentModified += diff; - } - - template - void Stat::setBase (const T& value) - { - T diff = value - mBase; - mBase = value; - mModified += diff; - mCurrentModified += diff; - } - - template - void Stat::setModified (T value, const T& min, const T& max) - { - T diff = value - mModified; - - if (mBase+diffmax) - { - value = max + (mModified - mBase); - diff = value - mModified; - } - - mModified = value; - mBase += diff; - mCurrentModified += diff; - } - - template - void Stat::setCurrentModified(T value) - { - mCurrentModified = value; - } - - template - void Stat::setModifier (const T& modifier) - { - mModified = mBase + modifier; - } - - template - void Stat::setCurrentModifier(const T& modifier) - { - mCurrentModified = mModified + modifier; - } - - template - void Stat::writeState (ESM::StatState& state) const + template + void Stat::writeState(ESM::StatState& state) const { state.mBase = mBase; - state.mMod = mCurrentModified; + state.mMod = mModifier; } - template - void Stat::readState (const ESM::StatState& state) + template + void Stat::readState(const ESM::StatState& state) { mBase = state.mBase; - mModified = state.mBase; - mCurrentModified = state.mMod; + mModifier = state.mMod; } - - template - DynamicStat::DynamicStat() : mStatic (0), mCurrent (0) {} - template - DynamicStat::DynamicStat(T base) : mStatic (base), mCurrent (base) {} - template - DynamicStat::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {} - template - DynamicStat::DynamicStat(const Stat &stat, T current) : mStatic(stat), mCurrent (current) {} - - - template - const T& DynamicStat::getBase() const + template + DynamicStat::DynamicStat() + : mStatic(0, 0) + , mCurrent(0) { - return mStatic.getBase(); } - template - T DynamicStat::getModified() const + template + DynamicStat::DynamicStat(T base) + : mStatic(base, 0) + , mCurrent(base) { - return mStatic.getModified(); } - template - T DynamicStat::getCurrentModified() const + template + DynamicStat::DynamicStat(T base, T modified, T current) + : mStatic(base, modified) + , mCurrent(current) + { + } + template + DynamicStat::DynamicStat(const Stat& stat, T current) + : mStatic(stat) + , mCurrent(current) { - return mStatic.getCurrentModified(); } - template - const T& DynamicStat::getCurrent() const - { - return mCurrent; - } - - template - void DynamicStat::set (const T& value) - { - mStatic.set (value); - mCurrent = value; - } - template - void DynamicStat::setBase (const T& value) - { - mStatic.setBase (value); - - if (mCurrent>getModified()) - mCurrent = getModified(); - } - template - void DynamicStat::setModified (T value, const T& min, const T& max) - { - mStatic.setModified (value, min, max); - - if (mCurrent>getModified()) - mCurrent = getModified(); - } - template - void DynamicStat::setCurrentModified(T value) - { - mStatic.setCurrentModified(value); - } - template - void DynamicStat::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified) + template + void DynamicStat::setCurrent(const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified) { if (value > mCurrent) { @@ -197,39 +89,37 @@ namespace MWMechanics mCurrent = 0; } } - template - void DynamicStat::setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero) + + template + T DynamicStat::getRatio(bool nanIsZero) const { - T diff = modifier - mStatic.getModifier(); - mStatic.setModifier (modifier); - setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); + T modified = getModified(); + if (modified == T{}) + { + if (nanIsZero) + return modified; + return { 1 }; + } + return getCurrent() / modified; } - template - void DynamicStat::setCurrentModifier(const T& modifier, bool allowCurrentDecreaseBelowZero) + template + void DynamicStat::writeState(ESM::StatState& state) const { - T diff = modifier - mStatic.getCurrentModifier(); - mStatic.setCurrentModifier(modifier); - - // The (modifier > 0) check here allows increase over modified only if the modifier is positive (a fortify effect is active). - setCurrent (getCurrent() + diff, allowCurrentDecreaseBelowZero, (modifier > 0)); - } - - template - void DynamicStat::writeState (ESM::StatState& state) const - { - mStatic.writeState (state); + mStatic.writeState(state); state.mCurrent = mCurrent; } - template - void DynamicStat::readState (const ESM::StatState& state) + template + void DynamicStat::readState(const ESM::StatState& state) { - mStatic.readState (state); + mStatic.readState(state); mCurrent = state.mCurrent; } - AttributeValue::AttributeValue() : - mBase(0.f), mModifier(0.f), mDamage(0.f) + AttributeValue::AttributeValue() + : mBase(0.f) + , mModifier(0.f) + , mDamage(0.f) { } @@ -246,28 +136,35 @@ namespace MWMechanics return mModifier; } - void AttributeValue::setBase(float base) + void AttributeValue::setBase(float base, bool clearModifier) { mBase = base; + if (clearModifier) + { + mModifier = 0.f; + mDamage = 0.f; + } } void AttributeValue::setModifier(float mod) { - mModifier = mod; + if (mod < 0) + { + mModifier = 0.f; + mDamage -= mod; + } + else + mModifier = mod; } void AttributeValue::damage(float damage) { - float threshold = mBase + mModifier; - - if (mDamage + damage > threshold) - mDamage = threshold; - else - mDamage += damage; + mDamage += damage; } void AttributeValue::restore(float amount) { - if (mDamage <= 0) return; + if (mDamage <= 0) + return; mDamage -= std::min(mDamage, amount); } @@ -277,22 +174,22 @@ namespace MWMechanics return mDamage; } - void AttributeValue::writeState (ESM::StatState& state) const + void AttributeValue::writeState(ESM::StatState& state) const { state.mBase = mBase; state.mMod = mModifier; state.mDamage = mDamage; } - void AttributeValue::readState (const ESM::StatState& state) + void AttributeValue::readState(const ESM::StatState& state) { mBase = state.mBase; mModifier = state.mMod; mDamage = state.mDamage; } - SkillValue::SkillValue() : - mProgress(0) + SkillValue::SkillValue() + : mProgress(0) { } @@ -305,15 +202,15 @@ namespace MWMechanics mProgress = progress; } - void SkillValue::writeState (ESM::StatState& state) const + void SkillValue::writeState(ESM::StatState& state) const { - AttributeValue::writeState (state); + AttributeValue::writeState(state); state.mProgress = mProgress; } - void SkillValue::readState (const ESM::StatState& state) + void SkillValue::readState(const ESM::StatState& state) { - AttributeValue::readState (state); + AttributeValue::readState(state); mProgress = state.mProgress; } } diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index fb9dca922..21cdad21a 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -1,123 +1,93 @@ #ifndef GAME_MWMECHANICS_STAT_H #define GAME_MWMECHANICS_STAT_H -#include -#include - namespace ESM { - template + template struct StatState; } namespace MWMechanics { - template + template class Stat { - T mBase; - T mModified; - T mCurrentModified; + T mBase; + T mModifier; - public: - typedef T Type; + public: + typedef T Type; - Stat(); - Stat(T base); - Stat(T base, T modified); + Stat(); + Stat(T base, T modified); - const T& getBase() const; + const T& getBase() const { return mBase; } - T getModified(bool capped = true) const; - T getCurrentModified() const; - T getModifier() const; - T getCurrentModifier() const; + T getModified(bool capped = true) const; + T getModifier() const { return mModifier; } - /// Set base and modified to \a value. - void set (const T& value); + void setBase(const T& value) { mBase = value; } - /// Set base and adjust modified accordingly. - void setBase (const T& value); + void setModifier(const T& modifier) { mModifier = modifier; } - /// Set modified value and adjust base accordingly. - void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); - - /// Set "current modified," used for drain and fortify. Unlike the regular modifier - /// this just adds and subtracts from the current value without changing the maximum. - void setCurrentModified(T value); - - void setModifier (const T& modifier); - void setCurrentModifier (const T& modifier); - - void writeState (ESM::StatState& state) const; - void readState (const ESM::StatState& state); + void writeState(ESM::StatState& state) const; + void readState(const ESM::StatState& state); }; - template - inline bool operator== (const Stat& left, const Stat& right) + template + inline bool operator==(const Stat& left, const Stat& right) { - return left.getBase()==right.getBase() && - left.getModified()==right.getModified(); + return left.getBase() == right.getBase() && left.getModifier() == right.getModifier(); } - template - inline bool operator!= (const Stat& left, const Stat& right) + template + inline bool operator!=(const Stat& left, const Stat& right) { - return !(left==right); + return !(left == right); } - template + template class DynamicStat { - Stat mStatic; - T mCurrent; + Stat mStatic; + T mCurrent; - public: - typedef T Type; + public: + typedef T Type; - DynamicStat(); - DynamicStat(T base); - DynamicStat(T base, T modified, T current); - DynamicStat(const Stat &stat, T current); + DynamicStat(); + DynamicStat(T base); + DynamicStat(T base, T modified, T current); + DynamicStat(const Stat& stat, T current); - const T& getBase() const; - T getModified() const; - T getCurrentModified() const; - const T& getCurrent() const; + const T& getBase() const { return mStatic.getBase(); } + T getModified(bool capped = true) const { return mStatic.getModified(capped); } + const T& getCurrent() const { return mCurrent; } + T getRatio(bool nanIsZero = true) const; - /// Set base, modified and current to \a value. - void set (const T& value); + /// Set base and adjust current accordingly. + void setBase(const T& value) { mStatic.setBase(value); } - /// Set base and adjust modified accordingly. - void setBase (const T& value); + void setCurrent(const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false); - /// Set modified value and adjust base accordingly. - void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); + T getModifier() const { return mStatic.getModifier(); } + void setModifier(T value) { mStatic.setModifier(value); } - /// Set "current modified," used for drain and fortify. Unlike the regular modifier - /// this just adds and subtracts from the current value without changing the maximum. - void setCurrentModified(T value); - - void setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false); - void setModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero=false); - void setCurrentModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero = false); - - void writeState (ESM::StatState& state) const; - void readState (const ESM::StatState& state); + void writeState(ESM::StatState& state) const; + void readState(const ESM::StatState& state); }; - template - inline bool operator== (const DynamicStat& left, const DynamicStat& right) + template + inline bool operator==(const DynamicStat& left, const DynamicStat& right) { - return left.getBase()==right.getBase() && - left.getModified()==right.getModified() && - left.getCurrent()==right.getCurrent(); + return left.getBase() == right.getBase() && left.getModifier() == right.getModifier() + && left.getCurrent() == right.getCurrent(); } - template - inline bool operator!= (const DynamicStat& left, const DynamicStat& right) + template + inline bool operator!=(const DynamicStat& left, const DynamicStat& right) { - return !(left==right); + return !(left == right); } class AttributeValue @@ -133,53 +103,52 @@ namespace MWMechanics float getBase() const; float getModifier() const; - void setBase(float base); + void setBase(float base, bool clearModifier = false); void setModifier(float mod); // Maximum attribute damage is limited to the modified value. - // Note: I think MW applies damage directly to mModified, since you can also - // "restore" drained attributes. We need to rewrite the magic effect system to support this. + // Note: MW applies damage directly to mModified, however it does track how much + // a damaged attribute that has been fortified beyond its base can be restored. + // Getting rid of mDamage would require calculating its value by ignoring active effects when restoring void damage(float damage); void restore(float amount); float getDamage() const; - void writeState (ESM::StatState& state) const; - void readState (const ESM::StatState& state); + void writeState(ESM::StatState& state) const; + void readState(const ESM::StatState& state); }; class SkillValue : public AttributeValue { float mProgress; + public: SkillValue(); float getProgress() const; void setProgress(float progress); - void writeState (ESM::StatState& state) const; - void readState (const ESM::StatState& state); + void writeState(ESM::StatState& state) const; + void readState(const ESM::StatState& state); }; - inline bool operator== (const AttributeValue& left, const AttributeValue& right) + inline bool operator==(const AttributeValue& left, const AttributeValue& right) { - return left.getBase() == right.getBase() - && left.getModifier() == right.getModifier() - && left.getDamage() == right.getDamage(); + return left.getBase() == right.getBase() && left.getModifier() == right.getModifier() + && left.getDamage() == right.getDamage(); } - inline bool operator!= (const AttributeValue& left, const AttributeValue& right) + inline bool operator!=(const AttributeValue& left, const AttributeValue& right) { return !(left == right); } - inline bool operator== (const SkillValue& left, const SkillValue& right) + inline bool operator==(const SkillValue& left, const SkillValue& right) { - return left.getBase() == right.getBase() - && left.getModifier() == right.getModifier() - && left.getDamage() == right.getDamage() - && left.getProgress() == right.getProgress(); + return left.getBase() == right.getBase() && left.getModifier() == right.getModifier() + && left.getDamage() == right.getDamage() && left.getProgress() == right.getProgress(); } - inline bool operator!= (const SkillValue& left, const SkillValue& right) + inline bool operator!=(const SkillValue& left, const SkillValue& right) { return !(left == right); } diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index eaf37fbd2..fe7d12e6d 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -1,7 +1,7 @@ #include "steering.hpp" #include -#include +#include #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" @@ -13,32 +13,32 @@ namespace MWMechanics { -bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians) -{ - MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); - float diff = Misc::normalizeAngle(targetAngleRadians - actor.getRefData().getPosition().rot[axis]); - float absDiff = std::abs(diff); + bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians) + { + MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); + float diff = Misc::normalizeAngle(targetAngleRadians - actor.getRefData().getPosition().rot[axis]); + float absDiff = std::abs(diff); - // The turning animation actually moves you slightly, so the angle will be wrong again. - // Use epsilon to prevent jerkiness. - if (absDiff < epsilonRadians) - return true; + // The turning animation actually moves you slightly, so the angle will be wrong again. + // Use epsilon to prevent jerkiness. + if (absDiff < epsilonRadians) + return true; - float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration(); - static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); - if (smoothMovement) - limit *= std::min(absDiff / osg::PI + 0.1, 0.5); + float limit + = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration(); + if (Settings::game().mSmoothMovement) + limit *= std::min(absDiff / osg::PI + 0.1, 0.5); - if (absDiff > limit) - diff = osg::sign(diff) * limit; + if (absDiff > limit) + diff = osg::sign(diff) * limit; - movement.mRotation[axis] = diff; - return false; -} + movement.mRotation[axis] = diff; + return false; + } -bool zTurn(const MWWorld::Ptr& actor, float targetAngleRadians, float epsilonRadians) -{ - return smoothTurn(actor, targetAngleRadians, 2, epsilonRadians); -} + bool zTurn(const MWWorld::Ptr& actor, float targetAngleRadians, float epsilonRadians) + { + return smoothTurn(actor, targetAngleRadians, 2, epsilonRadians); + } } diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index f305a6961..d1d120e09 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -7,27 +7,28 @@ namespace MWWorld { -class Ptr; + class Ptr; } namespace MWMechanics { -// Max rotating speed, radian/sec -inline float getAngularVelocity(const float actorSpeed) -{ - const float baseAngluarVelocity = 10; - const float baseSpeed = 200; - return baseAngluarVelocity * std::max(actorSpeed / baseSpeed, 1.0f); -} + // Max rotating speed, radian/sec + inline float getAngularVelocity(const float actorSpeed) + { + constexpr float degreesPerFrame = 15.f; + constexpr int framesPerSecond = 60; + const float baseAngularVelocity = osg::DegreesToRadians(degreesPerFrame * framesPerSecond); + const float baseSpeed = 200; + return baseAngularVelocity * std::max(actorSpeed / baseSpeed, 1.0f); + } -/// configure rotation settings for an actor to reach this target angle (eventually) -/// @return have we reached the target angle? -bool zTurn(const MWWorld::Ptr& actor, float targetAngleRadians, - float epsilonRadians = osg::DegreesToRadians(0.5)); + /// configure rotation settings for an actor to reach this target angle (eventually) + /// @return have we reached the target angle? + bool zTurn(const MWWorld::Ptr& actor, float targetAngleRadians, float epsilonRadians = osg::DegreesToRadians(0.5)); -bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, - float epsilonRadians = osg::DegreesToRadians(0.5)); + bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, + float epsilonRadians = osg::DegreesToRadians(0.5)); } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 6beab007c..667db15d9 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -1,6 +1,10 @@ #include "summoning.hpp" #include +#include +#include +#include +#include /* Start of tes3mp addition @@ -18,18 +22,17 @@ */ #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/manualref.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" -#include "creaturestats.hpp" #include "aifollow.hpp" +#include "creaturestats.hpp" namespace MWMechanics { @@ -37,70 +40,86 @@ namespace MWMechanics bool isSummoningEffect(int effectId) { return ((effectId >= ESM::MagicEffect::SummonScamp && effectId <= ESM::MagicEffect::SummonStormAtronach) - || (effectId == ESM::MagicEffect::SummonCenturionSphere) - || (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05)); + || (effectId == ESM::MagicEffect::SummonCenturionSphere) + || (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05)); } - std::string getSummonedCreature(int effectId) + static const std::map& getSummonMap() { - static const std::map summonMap - { - {ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"}, - {ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"}, - {ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"}, - {ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"}, - {ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"}, - {ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"}, - {ESM::MagicEffect::SummonDremora, "sMagicDremoraID"}, - {ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"}, - {ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"}, - {ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"}, - {ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"}, - {ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"}, - {ESM::MagicEffect::SummonHunger, "sMagicHungerID"}, - {ESM::MagicEffect::SummonScamp, "sMagicScampID"}, - {ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"}, - {ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"}, - {ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"}, - {ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"}, - {ESM::MagicEffect::SummonBear, "sMagicCreature02ID"}, - {ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"}, - {ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"}, - {ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"} + static std::map summonMap; + + if (summonMap.size() > 0) + return summonMap; + + const std::map summonMapToGameSetting{ + { ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID" }, + { ESM::MagicEffect::SummonBonelord, "sMagicBonelordID" }, + { ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID" }, + { ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID" }, + { ESM::MagicEffect::SummonClannfear, "sMagicClannfearID" }, + { ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID" }, + { ESM::MagicEffect::SummonDremora, "sMagicDremoraID" }, + { ESM::MagicEffect::SummonFabricant, "sMagicFabricantID" }, + { ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID" }, + { ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID" }, + { ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID" }, + { ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID" }, + { ESM::MagicEffect::SummonHunger, "sMagicHungerID" }, + { ESM::MagicEffect::SummonScamp, "sMagicScampID" }, + { ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID" }, + { ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID" }, + { ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID" }, + { ESM::MagicEffect::SummonWolf, "sMagicCreature01ID" }, + { ESM::MagicEffect::SummonBear, "sMagicCreature02ID" }, + { ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID" }, + { ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID" }, + { ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID" }, }; + for (const auto& it : summonMapToGameSetting) + { + summonMap[it.first] = ESM::RefId::stringRefId( + MWBase::Environment::get().getESMStore()->get().find(it.second)->mValue.getString()); + } + return summonMap; + } + + ESM::RefId getSummonedCreature(int effectId) + { + const auto& summonMap = getSummonMap(); auto it = summonMap.find(effectId); if (it != summonMap.end()) - return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->mValue.getString(); - return std::string(); - } - - UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) - : mActor(actor) - { - } - - void UpdateSummonedCreatures::visit(EffectKey key, int effectIndex, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) - { - if (isSummoningEffect(key.mId) && magnitude > 0) { - mActiveEffects.insert(ESM::SummonKey(key.mId, sourceId, effectIndex)); + return it->second; } + return ESM::RefId(); } - void UpdateSummonedCreatures::process(bool cleanup) + int summonCreature(int effectId, const MWWorld::Ptr& summoner) { - MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); - - for (std::set::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it) + const ESM::RefId& creatureID = getSummonedCreature(effectId); + int creatureActorId = -1; + if (!creatureID.empty()) { - bool found = creatureMap.find(*it) != creatureMap.end(); - if (!found) + try { - std::string creatureID = getSummonedCreature(it->mEffectId); - if (!creatureID.empty()) + auto world = MWBase::Environment::get().getWorld(); + MWWorld::ManualRef ref(world->getStore(), creatureID, 1); + + MWMechanics::CreatureStats& summonedCreatureStats + = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); + + // Make the summoned creature follow its master and help in fights + AiFollow package(summoner); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + creatureActorId = summonedCreatureStats.getActorId(); + + MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f); + + MWRender::Animation* anim = world->getAnimation(placed); + if (anim) { +<<<<<<< HEAD int creatureActorId = -1; /* @@ -165,60 +184,73 @@ namespace MWMechanics /* End of tes3mp change (major) */ +======= + const ESM::Static* fx + = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_Start")); + if (fx) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1, false); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } - } - - // Update summon effects - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) - { - bool found = mActiveEffects.find(it->first) != mActiveEffects.end(); - if (!found) + catch (std::exception& e) { - // Effect has ended - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); - creatureMap.erase(it++); - continue; + Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what(); + // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning + // log } - ++it; + + summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creatureActorId); } + return creatureActorId; + } + + void updateSummons(const MWWorld::Ptr& summoner, bool cleanup) + { + MWMechanics::CreatureStats& creatureStats = summoner.getClass().getCreatureStats(summoner); + auto& creatureMap = creatureStats.getSummonedCreatureMap(); std::vector graveyard = creatureStats.getSummonedCreatureGraveyard(); creatureStats.getSummonedCreatureGraveyard().clear(); for (const int creature : graveyard) - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, creature); + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, creature); if (!cleanup) return; - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + for (auto it = creatureMap.begin(); it != creatureMap.end();) { - if(it->second == -1) + if (it->second == -1) { // Keep the spell effect active if we failed to spawn anything it++; continue; } MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); - if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) + if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() + && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) { // Purge the magic effect so a new creature can be summoned if desired - purgeSummonEffect(mActor, *it); + auto summon = *it; creatureMap.erase(it++); + purgeSummonEffect(summoner, summon); } else ++it; } } - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) { auto& creatureStats = summoner.getClass().getCreatureStats(summoner); - creatureStats.getActiveSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId, summon.first.mEffectIndex); - creatureStats.getSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId); - if (summoner.getClass().hasInventoryStore(summoner)) - summoner.getClass().getInventoryStore(summoner).purgeEffect(summon.first.mEffectId, summon.first.mSourceId, false, summon.first.mEffectIndex); + creatureStats.getActiveSpells().purge( + [summon](const auto& spell, const auto& effect) { + return effect.mEffectId == summon.first && effect.mArg == summon.second; + }, + summoner); MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second); } diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index 3c3e18a96..a341ee6e9 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -1,42 +1,28 @@ #ifndef OPENMW_MECHANICS_SUMMONING_H #define OPENMW_MECHANICS_SUMMONING_H -#include - -#include "../mwworld/ptr.hpp" - -#include - -#include "magiceffects.hpp" +#include +#include +namespace ESM +{ + class RefId; +} +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { - class CreatureStats; - bool isSummoningEffect(int effectId); - std::string getSummonedCreature(int effectId); + ESM::RefId getSummonedCreature(int effectId); - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); - struct UpdateSummonedCreatures : public EffectSourceVisitor - { - UpdateSummonedCreatures(const MWWorld::Ptr& actor); - virtual ~UpdateSummonedCreatures() = default; - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override; - - /// To call after all effect sources have been visited - void process(bool cleanup); - - private: - MWWorld::Ptr mActor; - - std::set mActiveEffects; - }; + int summonCreature(int effectId, const MWWorld::Ptr& summoner); + void updateSummons(const MWWorld::Ptr& summoner, bool cleanup); } #endif diff --git a/apps/openmw/mwmechanics/tickableeffects.cpp b/apps/openmw/mwmechanics/tickableeffects.cpp deleted file mode 100644 index 73f08d9a0..000000000 --- a/apps/openmw/mwmechanics/tickableeffects.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "tickableeffects.hpp" - -#include - -/* - Start of tes3mp addition - - Include additional headers for multiplayer purposes -*/ -#include "../mwmp/Main.hpp" -#include "../mwmp/PlayerList.hpp" -#include "../mwmp/CellController.hpp" -/* - End of tes3mp addition -*/ - -#include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/cellstore.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" - -#include "actorutil.hpp" -#include "npcstats.hpp" - -namespace MWMechanics -{ - void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false) - { - DynamicStat stat = creatureStats.getDynamic(index); - stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero); - creatureStats.setDynamic(index, stat); - } - - bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate) - { - if (!ptr.getClass().hasInventoryStore(ptr)) - return false; - - MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); - MWWorld::ContainerStoreIterator item = inv.getSlot(slot); - - if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) - { - if (!item->getClass().hasItemHealth(*item)) - return false; - int charge = item->getClass().getItemHealth(*item); - if (charge == 0) - return false; - - // Store remainder of disintegrate amount (automatically subtracted if > 1) - item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); - - charge = item->getClass().getItemHealth(*item); - charge -= std::min(static_cast(disintegrate), charge); - item->getCellRef().setCharge(charge); - - if (charge == 0) - { - // Will unequip the broken item and try to find a replacement - if (ptr != getPlayer()) - inv.autoEquip(ptr); - else - inv.unequipItem(*item, ptr); - } - - return true; - } - - return false; - } - - bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) - { - if (magnitude == 0.f) - return false; - - bool receivedMagicDamage = false; - bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - switch (effectKey.mId) - { - case ESM::MagicEffect::DamageAttribute: - { - if (godmode) - break; - AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); - attr.damage(magnitude); - creatureStats.setAttribute(effectKey.mArg, attr); - break; - } - case ESM::MagicEffect::RestoreAttribute: - { - AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); - attr.restore(magnitude); - creatureStats.setAttribute(effectKey.mArg, attr); - break; - } - case ESM::MagicEffect::RestoreHealth: - case ESM::MagicEffect::RestoreMagicka: - case ESM::MagicEffect::RestoreFatigue: - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); - break; - case ESM::MagicEffect::DamageHealth: - if (godmode) - break; - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); - break; - - case ESM::MagicEffect::DamageMagicka: - case ESM::MagicEffect::DamageFatigue: - { - if (godmode) - break; - int index = effectKey.mId-ESM::MagicEffect::DamageHealth; - static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); - adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue); - break; - } - case ESM::MagicEffect::AbsorbHealth: - if (!godmode || magnitude <= 0) - { - if (magnitude > 0.f) - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - } - break; - - case ESM::MagicEffect::AbsorbMagicka: - case ESM::MagicEffect::AbsorbFatigue: - if (!godmode || magnitude <= 0) - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - break; - - case ESM::MagicEffect::DisintegrateArmor: - { - if (godmode) - break; - static const std::array priorities - { - MWWorld::InventoryStore::Slot_CarriedLeft, - MWWorld::InventoryStore::Slot_Cuirass, - MWWorld::InventoryStore::Slot_LeftPauldron, - MWWorld::InventoryStore::Slot_RightPauldron, - MWWorld::InventoryStore::Slot_LeftGauntlet, - MWWorld::InventoryStore::Slot_RightGauntlet, - MWWorld::InventoryStore::Slot_Helmet, - MWWorld::InventoryStore::Slot_Greaves, - MWWorld::InventoryStore::Slot_Boots - }; - for (const int priority : priorities) - { - if (disintegrateSlot(actor, priority, magnitude)) - break; - } - - break; - } - case ESM::MagicEffect::DisintegrateWeapon: - if (!godmode) - disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude); - break; - - case ESM::MagicEffect::SunDamage: - { - // isInCell shouldn't be needed, but updateActor called during game start - if (!actor.isInCell() || !actor.getCell()->isExterior() || godmode) - break; - float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); - float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); - float damageScale = 1.f - timeDiff / 7.f; - // When cloudy, the sun damage effect is halved - static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fMagicSunBlockedMult")->mValue.getFloat(); - - int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); - if (weather > 1) - damageScale *= fMagicSunBlockedMult; - - adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); - if (magnitude * damageScale > 0.f) - receivedMagicDamage = true; - - break; - } - - case ESM::MagicEffect::FireDamage: - case ESM::MagicEffect::ShockDamage: - case ESM::MagicEffect::FrostDamage: - case ESM::MagicEffect::Poison: - { - if (godmode) - break; - adjustDynamicStat(creatureStats, 0, -magnitude); - receivedMagicDamage = true; - break; - } - - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::RestoreSkill: - { - if (!actor.getClass().isNpc()) - break; - if (godmode && effectKey.mId == ESM::MagicEffect::DamageSkill) - break; - NpcStats &npcStats = actor.getClass().getNpcStats(actor); - SkillValue& skill = npcStats.getSkill(effectKey.mArg); - if (effectKey.mId == ESM::MagicEffect::RestoreSkill) - skill.restore(magnitude); - else - skill.damage(magnitude); - break; - } - - default: - return false; - } - - if (receivedMagicDamage && actor == getPlayer()) - MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); - return true; - } -} diff --git a/apps/openmw/mwmechanics/tickableeffects.hpp b/apps/openmw/mwmechanics/tickableeffects.hpp deleted file mode 100644 index ccd42ca19..000000000 --- a/apps/openmw/mwmechanics/tickableeffects.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef MWMECHANICS_TICKABLEEFFECTS_H -#define MWMECHANICS_TICKABLEEFFECTS_H - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - class CreatureStats; - struct EffectKey; - - /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed - /// Note: this function works in loop, so magic effects should not be removed here to avoid iterator invalidation. - /// @return Was the effect a tickable effect with a magnitude? - bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude); -} - -#endif diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index b824d7c45..9500897f2 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -18,30 +18,30 @@ namespace MWMechanics bool Trading::haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) { // accept if merchant offer is better than player offer - if ( playerOffer <= merchantOffer ) { + if (playerOffer <= merchantOffer) + { return true; } // reject if npc is a creature - if ( merchant.getTypeName() != typeid(ESM::NPC).name() ) { + if (merchant.getType() != ESM::NPC::sRecordId) + { return false; } - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); // Is the player buying? bool buying = (merchantOffer < 0); int a = std::abs(merchantOffer); int b = std::abs(playerOffer); - int d = (buying) - ? int(100 * (a - b) / a) - : int(100 * (b - a) / b); + int d = (buying) ? int(100 * (a - b) / a) : int(100 * (b - a) / b); int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); - const MWMechanics::CreatureStats &merchantStats = merchant.getClass().getCreatureStats(merchant); - const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); + const MWMechanics::CreatureStats& merchantStats = merchant.getClass().getCreatureStats(merchant); + const MWMechanics::CreatureStats& playerStats = player.getClass().getCreatureStats(player); float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); @@ -54,14 +54,15 @@ namespace MWMechanics float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d - + gmst.find("fBargainOfferBase")->mValue.getFloat() - + int(pcTerm - npcTerm); + + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); - int roll = Misc::Rng::rollDice(100) + 1; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::rollDice(100, prng) + 1; // reject if roll fails // (or if player tries to buy things and get money) - if ( roll > x || (merchantOffer < 0 && 0 < playerOffer) ) { + if (roll > x || (merchantOffer < 0 && 0 < playerOffer)) + { return false; } @@ -70,11 +71,13 @@ namespace MWMechanics int finalPrice = std::abs(playerOffer); int initialMerchantOffer = std::abs(merchantOffer); - if ( !buying && (finalPrice > initialMerchantOffer) ) { - skillGain = floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); + if (!buying && (finalPrice > initialMerchantOffer)) + { + skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); } - else if ( buying && (finalPrice < initialMerchantOffer) ) { - skillGain = floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); + else if (buying && (finalPrice < initialMerchantOffer)) + { + skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); } player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); diff --git a/apps/openmw/mwmechanics/typedaipackage.hpp b/apps/openmw/mwmechanics/typedaipackage.hpp index d2d424326..9cbcc95ae 100644 --- a/apps/openmw/mwmechanics/typedaipackage.hpp +++ b/apps/openmw/mwmechanics/typedaipackage.hpp @@ -8,20 +8,28 @@ namespace MWMechanics template struct TypedAiPackage : public AiPackage { - TypedAiPackage() : - AiPackage(T::getTypeId(), T::makeDefaultOptions()) {} + TypedAiPackage() + : AiPackage(T::getTypeId(), T::makeDefaultOptions()) + { + } - TypedAiPackage(const Options& options) : - AiPackage(T::getTypeId(), options) {} + TypedAiPackage(bool repeat) + : AiPackage(T::getTypeId(), T::makeDefaultOptions().withRepeat(repeat)) + { + } + + TypedAiPackage(const Options& options) + : AiPackage(T::getTypeId(), options) + { + } template - TypedAiPackage(Derived*) : - AiPackage(Derived::getTypeId(), Derived::makeDefaultOptions()) {} - - std::unique_ptr clone() const override + TypedAiPackage(Derived*) + : AiPackage(Derived::getTypeId(), Derived::makeDefaultOptions()) { - return std::make_unique(*static_cast(this)); } + + std::unique_ptr clone() const override { return std::make_unique(*static_cast(this)); } }; } diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 8480dc208..e0584afcd 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -1,6 +1,8 @@ #include "weaponpriority.hpp" -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -9,18 +11,18 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "combat.hpp" #include "aicombataction.hpp" +#include "combat.hpp" #include "spellpriority.hpp" #include "spellutil.hpp" #include "weapontype.hpp" namespace MWMechanics { - float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type, - float arrowRating, float boltRating) + float rateWeapon(const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type, + float arrowRating, float boltRating) { - if (enemy.isEmpty() || item.getTypeName() != typeid(ESM::Weapon).name()) + if (enemy.isEmpty() || item.getType() != ESM::Weapon::sRecordId) return 0.f; if (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) == 0) @@ -38,7 +40,7 @@ namespace MWMechanics if (type == -1 && weapclass == ESM::WeaponType::Ammo) return 0.f; - float rating=0.f; + float rating = 0.f; static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); float ratingMult = fAIMeleeWeaponMult; @@ -46,7 +48,7 @@ namespace MWMechanics { // Underwater ranged combat is impossible if (world->isUnderwater(MWWorld::ConstPtr(actor), 0.75f) - || world->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) + || world->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) return 0.f; // Use a higher rating multiplier if the actor is out of enemy's reach, use the normal mult otherwise @@ -106,30 +108,30 @@ namespace MWMechanics const ESM::Enchantment* enchantment = world->getStore().get().find(weapon->mEnchant); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { - int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + int castCost = getEffectiveEnchantmentCastCost(*enchantment, actor); float charge = item.getCellRef().getEnchantmentCharge(); - if (charge == -1 || charge >= castCost || weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) - rating += rateEffects(enchantment->mEffects, actor, enemy); + if (charge == -1 || charge >= castCost || weapclass == ESM::WeaponType::Thrown + || weapclass == ESM::WeaponType::Ammo) + rating += rateEffects(enchantment->mEffects, actor, enemy, false); } } int value = 50.f; if (actor.getClass().isNpc()) { - int skill = item.getClass().getEquipmentSkill(item); - if (skill != -1) - value = actor.getClass().getSkill(actor, skill); + ESM::RefId skill = item.getClass().getEquipmentSkill(item); + if (!skill.empty()) + value = actor.getClass().getSkill(actor, skill); } else { - MWWorld::LiveCellRef *ref = actor.get(); + MWWorld::LiveCellRef* ref = actor.get(); value = ref->mBase->mData.mCombat; } // Take hit chance in account, but do not allow rating become negative. - float chance = getHitChance(actor, enemy, value) / 100.f; - rating *= std::min(1.f, std::max(0.01f, chance)); + rating *= std::clamp(getHitChance(actor, enemy, value) / 100.f, 0.01f, 1.f); if (weapclass != ESM::WeaponType::Ammo) rating *= weapon->mData.mSpeed; @@ -137,7 +139,7 @@ namespace MWMechanics return rating * ratingMult; } - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType) + float rateAmmo(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, MWWorld::Ptr& bestAmmo, int ammoType) { float bestAmmoRating = 0.f; if (!actor.getClass().hasInventoryStore(actor)) @@ -158,15 +160,17 @@ namespace MWMechanics return bestAmmoRating; } - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType) + float rateAmmo(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int 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) + 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(); + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); static const float fAIMeleeArmorMult = gmst.find("fAIMeleeArmorMult")->mValue.getFloat(); @@ -197,7 +201,7 @@ namespace MWMechanics float thrustRating = esmWeap->mData.mThrust[1] * skillMult * fAIMeleeWeaponMult; return actor.getClass().getArmorRating(actor) * fAIMeleeArmorMult - + std::max(std::max(chopRating, slashRating), thrustRating); + + std::max(std::max(chopRating, slashRating), thrustRating); } } diff --git a/apps/openmw/mwmechanics/weaponpriority.hpp b/apps/openmw/mwmechanics/weaponpriority.hpp index 9dcef3e2e..44e9611b4 100644 --- a/apps/openmw/mwmechanics/weaponpriority.hpp +++ b/apps/openmw/mwmechanics/weaponpriority.hpp @@ -1,17 +1,21 @@ #ifndef OPENMW_WEAPON_PRIORITY_H #define OPENMW_WEAPON_PRIORITY_H -#include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class Ptr; +} 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 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, int ammoType); - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType); + float rateAmmo(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, MWWorld::Ptr& bestAmmo, int ammoType); + float rateAmmo(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int ammoType); - float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float vanillaRateWeaponAndAmmo( + const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } #endif diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp index 2f8e45f7f..0612ca1a2 100644 --- a/apps/openmw/mwmechanics/weapontype.cpp +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -1,33 +1,296 @@ #include "weapontype.hpp" +#include "creaturestats.hpp" +#include "drawstate.hpp" + #include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" + +#include namespace MWMechanics { - MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype) + template + struct Weapon { - MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); - CreatureStats &stats = actor.getClass().getCreatureStats(actor); - if(stats.getDrawState() == MWMechanics::DrawState_Spell) + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "", + /* long group */ "", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "1h", + /* long group */ "pickprobe", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Security, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "spell", + /* long group */ "spellcast", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "hh", + /* long group */ "handtohand", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "1s", + /* long group */ "shortbladeonehand", + /* sound ID */ "Item Weapon Shortblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 ShortBladeOneHand", + /* usage skill */ ESM::Skill::ShortBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "1h", + /* long group */ "weapononehand", + /* sound ID */ "Item Weapon Longblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeOneHand", + /* usage skill */ ESM::Skill::LongBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "1b", + /* long group */ "bluntonehand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntOneHand", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "1b", + /* long group */ "bluntonehand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeOneHand", + /* usage skill */ ESM::Skill::Axe, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "2c", + /* long group */ "weapontwohand", + /* sound ID */ "Item Weapon Longblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeTwoClose", + /* usage skill */ ESM::Skill::LongBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth | ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "2b", + /* long group */ "blunttwohand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 AxeTwoClose", + /* usage skill */ ESM::Skill::Axe, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth | ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "2b", + /* long group */ "blunttwohand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntTwoClose", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth | ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "2w", + /* long group */ "weapontwowide", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntTwoWide", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth | ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "2w", + /* long group */ "weapontwowide", + /* sound ID */ "Item Weapon Spear", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 SpearTwoWide", + /* usage skill */ ESM::Skill::Spear, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth | ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "bow", + /* long group */ "bowandarrow", + /* sound ID */ "Item Weapon Bow", + /* attach bone */ "Weapon Bone Left", + /* sheath bone */ "Bip01 MarksmanBow", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ranged, + /* ammo type */ ESM::Weapon::Arrow, + /* flags */ ESM::WeaponType::HasHealth | ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "crossbow", + /* long group */ "crossbow", + /* sound ID */ "Item Weapon Crossbow", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 MarksmanCrossbow", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ranged, + /* ammo type */ ESM::Weapon::Bolt, + /* flags */ ESM::WeaponType::HasHealth | ESM::WeaponType::TwoHanded }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "1t", + /* long group */ "throwweapon", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 MarksmanThrown", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Thrown, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "", + /* long group */ "", + /* sound ID */ "Item Ammo", + /* attach bone */ "Bip01 Arrow", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ammo, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 }; + }; + + template <> + struct Weapon + { + inline static const ESM::WeaponType sValue{ /* short group */ "", + /* long group */ "", + /* sound ID */ "Item Ammo", + /* attach bone */ "ArrowBone", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ammo, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 }; + }; + + MWWorld::ContainerStoreIterator getActiveWeapon(const MWWorld::Ptr& actor, int* weaptype) + { + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + CreatureStats& stats = actor.getClass().getCreatureStats(actor); + if (stats.getDrawState() == MWMechanics::DrawState::Spell) { *weaptype = ESM::Weapon::Spell; return inv.end(); } - if(stats.getDrawState() == MWMechanics::DrawState_Weapon) + if (stats.getDrawState() == MWMechanics::DrawState::Weapon) { MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end()) + if (weapon == inv.end()) *weaptype = ESM::Weapon::HandToHand; else { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) + auto type = weapon->getType(); + if (type == ESM::Weapon::sRecordId) { - const MWWorld::LiveCellRef *ref = weapon->get(); + const MWWorld::LiveCellRef* ref = weapon->get(); *weaptype = ref->mBase->mData.mType; } - else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId) *weaptype = ESM::Weapon::PickProbe; } @@ -39,13 +302,46 @@ namespace MWMechanics const ESM::WeaponType* getWeaponType(const int weaponType) { - std::map::const_iterator found = sWeaponTypeList.find(weaponType); - if (found == sWeaponTypeList.end()) + switch (static_cast(weaponType)) { - // Use one-handed short blades as fallback - return &sWeaponTypeList[0]; + case ESM::Weapon::PickProbe: + return &Weapon::sValue; + case ESM::Weapon::HandToHand: + return &Weapon::sValue; + case ESM::Weapon::Spell: + return &Weapon::sValue; + case ESM::Weapon::None: + return &Weapon::sValue; + case ESM::Weapon::ShortBladeOneHand: + return &Weapon::sValue; + case ESM::Weapon::LongBladeOneHand: + return &Weapon::sValue; + case ESM::Weapon::LongBladeTwoHand: + return &Weapon::sValue; + case ESM::Weapon::BluntOneHand: + return &Weapon::sValue; + case ESM::Weapon::BluntTwoClose: + return &Weapon::sValue; + case ESM::Weapon::BluntTwoWide: + return &Weapon::sValue; + case ESM::Weapon::SpearTwoWide: + return &Weapon::sValue; + case ESM::Weapon::AxeOneHand: + return &Weapon::sValue; + case ESM::Weapon::AxeTwoHand: + return &Weapon::sValue; + case ESM::Weapon::MarksmanBow: + return &Weapon::sValue; + case ESM::Weapon::MarksmanCrossbow: + return &Weapon::sValue; + case ESM::Weapon::MarksmanThrown: + return &Weapon::sValue; + case ESM::Weapon::Arrow: + return &Weapon::sValue; + case ESM::Weapon::Bolt: + return &Weapon::sValue; } - return &found->second; + return &Weapon::sValue; } } diff --git a/apps/openmw/mwmechanics/weapontype.hpp b/apps/openmw/mwmechanics/weapontype.hpp index 09fa73c06..db7b3013f 100644 --- a/apps/openmw/mwmechanics/weapontype.hpp +++ b/apps/openmw/mwmechanics/weapontype.hpp @@ -1,267 +1,24 @@ #ifndef GAME_MWMECHANICS_WEAPONTYPE_H #define GAME_MWMECHANICS_WEAPONTYPE_H -#include "../mwworld/inventorystore.hpp" +namespace ESM +{ + struct WeaponType; +} + +namespace MWWorld +{ + class Ptr; + + template + class ContainerStoreIteratorBase; + + using ContainerStoreIterator = ContainerStoreIteratorBase; +} namespace MWMechanics { - static std::map sWeaponTypeList = - { - { - ESM::Weapon::None, - { - /* short group */ "", - /* long group */ "", - /* sound ID */ "", - /* attach bone */ "", - /* sheath bone */ "", - /* usage skill */ ESM::Skill::HandToHand, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ 0 - } - }, - { - ESM::Weapon::PickProbe, - { - /* short group */ "1h", - /* long group */ "pickprobe", - /* sound ID */ "", - /* attach bone */ "", - /* sheath bone */ "", - /* usage skill */ ESM::Skill::Security, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ 0 - } - }, - { - ESM::Weapon::Spell, - { - /* short group */ "spell", - /* long group */ "spellcast", - /* sound ID */ "", - /* attach bone */ "", - /* sheath bone */ "", - /* usage skill */ ESM::Skill::HandToHand, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::HandToHand, - { - /* short group */ "hh", - /* long group */ "handtohand", - /* sound ID */ "", - /* attach bone */ "", - /* sheath bone */ "", - /* usage skill */ ESM::Skill::HandToHand, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::ShortBladeOneHand, - { - /* short group */ "1s", - /* long group */ "shortbladeonehand", - /* sound ID */ "Item Weapon Shortblade", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 ShortBladeOneHand", - /* usage skill */ ESM::Skill::ShortBlade, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth - } - }, - { - ESM::Weapon::LongBladeOneHand, - { - /* short group */ "1h", - /* long group */ "weapononehand", - /* sound ID */ "Item Weapon Longblade", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 LongBladeOneHand", - /* usage skill */ ESM::Skill::LongBlade, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth - } - }, - { - ESM::Weapon::BluntOneHand, - { - /* short group */ "1b", - /* long group */ "bluntonehand", - /* sound ID */ "Item Weapon Blunt", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 BluntOneHand", - /* usage skill */ ESM::Skill::BluntWeapon, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth - } - }, - { - ESM::Weapon::AxeOneHand, - { - /* short group */ "1b", - /* long group */ "bluntonehand", - /* sound ID */ "Item Weapon Blunt", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 LongBladeOneHand", - /* usage skill */ ESM::Skill::Axe, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth - } - }, - { - ESM::Weapon::LongBladeTwoHand, - { - /* short group */ "2c", - /* long group */ "weapontwohand", - /* sound ID */ "Item Weapon Longblade", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 LongBladeTwoClose", - /* usage skill */ ESM::Skill::LongBlade, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::AxeTwoHand, - { - /* short group */ "2b", - /* long group */ "blunttwohand", - /* sound ID */ "Item Weapon Blunt", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 AxeTwoClose", - /* usage skill */ ESM::Skill::Axe, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::BluntTwoClose, - { - /* short group */ "2b", - /* long group */ "blunttwohand", - /* sound ID */ "Item Weapon Blunt", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 BluntTwoClose", - /* usage skill */ ESM::Skill::BluntWeapon, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::BluntTwoWide, - { - /* short group */ "2w", - /* long group */ "weapontwowide", - /* sound ID */ "Item Weapon Blunt", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 BluntTwoWide", - /* usage skill */ ESM::Skill::BluntWeapon, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::SpearTwoWide, - { - /* short group */ "2w", - /* long group */ "weapontwowide", - /* sound ID */ "Item Weapon Spear", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 SpearTwoWide", - /* usage skill */ ESM::Skill::Spear, - /* weapon class*/ ESM::WeaponType::Melee, - /* ammo type */ ESM::Weapon::None, - /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::MarksmanBow, - { - /* short group */ "bow", - /* long group */ "bowandarrow", - /* sound ID */ "Item Weapon Bow", - /* attach bone */ "Weapon Bone Left", - /* sheath bone */ "Bip01 MarksmanBow", - /* usage skill */ ESM::Skill::Marksman, - /* weapon class*/ ESM::WeaponType::Ranged, - /* ammo type */ ESM::Weapon::Arrow, - /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::MarksmanCrossbow, - { - /* short group */ "crossbow", - /* long group */ "crossbow", - /* sound ID */ "Item Weapon Crossbow", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 MarksmanCrossbow", - /* usage skill */ ESM::Skill::Marksman, - /* weapon class*/ ESM::WeaponType::Ranged, - /* ammo type */ ESM::Weapon::Bolt, - /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded - } - }, - { - ESM::Weapon::MarksmanThrown, - { - /* short group */ "1t", - /* long group */ "throwweapon", - /* sound ID */ "Item Weapon Blunt", - /* attach bone */ "Weapon Bone", - /* sheath bone */ "Bip01 MarksmanThrown", - /* usage skill */ ESM::Skill::Marksman, - /* weapon class*/ ESM::WeaponType::Thrown, - /* ammo type */ ESM::Weapon::None, - /* flags */ 0 - } - }, - { - ESM::Weapon::Arrow, - { - /* short group */ "", - /* long group */ "", - /* sound ID */ "Item Ammo", - /* attach bone */ "Bip01 Arrow", - /* sheath bone */ "", - /* usage skill */ ESM::Skill::Marksman, - /* weapon class*/ ESM::WeaponType::Ammo, - /* ammo type */ ESM::Weapon::None, - /* flags */ 0 - } - }, - { - ESM::Weapon::Bolt, - { - /* short group */ "", - /* long group */ "", - /* sound ID */ "Item Ammo", - /* attach bone */ "ArrowBone", - /* sheath bone */ "", - /* usage skill */ ESM::Skill::Marksman, - /* weapon class*/ ESM::WeaponType::Ammo, - /* ammo type */ ESM::Weapon::None, - /* flags */ 0 - } - } - }; - - MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype); + MWWorld::ContainerStoreIterator getActiveWeapon(const MWWorld::Ptr& actor, int* weaptype); const ESM::WeaponType* getWeaponType(const int weaponType); } diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 565b728ed..0da9b71d5 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -1,5 +1,6 @@ #include "actor.hpp" +<<<<<<< HEAD /* Start of tes3mp addition @@ -15,22 +16,28 @@ #include #include +======= +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 -#include -#include #include #include +#include +#include +#include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" #include "collisiontype.hpp" #include "mtphysics.hpp" +#include "trace.h" #include namespace MWPhysics { +<<<<<<< HEAD Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) @@ -47,26 +54,98 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic // In this case we should autogenerate collision box based on mesh shape // (NPCs have bodyparts and use a different approach) if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f) +======= + Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, + bool canWaterWalk, DetourNavigator::CollisionShapeType collisionShapeType) + : PtrHolder(ptr, ptr.getRefData().getPosition().asVec3()) + , mStandingOnPtr(nullptr) + , mCanWaterWalk(canWaterWalk) + , mWalkingOnWater(false) + , mMeshTranslation(shape->mCollisionBox.mCenter) + , mOriginalHalfExtents(shape->mCollisionBox.mExtents) + , mStuckFrames(0) + , mLastStuckPosition{ 0, 0, 0 } + , mForce(0.f, 0.f, 0.f) + , mOnGround(ptr.getClass().getCreatureStats(ptr).getFallHeight() == 0) + , mOnSlope(false) + , mInternalCollisionMode(true) + , mExternalCollisionMode(true) + , mActive(false) + , mTaskScheduler(scheduler) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - if (shape->mCollisionShape) + // We can not create actor without collisions - he will fall through the ground. + // In this case we should autogenerate collision box based on mesh shape + // (NPCs have bodyparts and use a different approach) + if (!ptr.getClass().isNpc() && mOriginalHalfExtents.length2() == 0.f) { - btTransform transform; - transform.setIdentity(); - btVector3 min; - btVector3 max; + if (shape->mCollisionShape) + { + btTransform transform; + transform.setIdentity(); + btVector3 min; + btVector3 max; - shape->mCollisionShape->getAabb(transform, min, max); - mHalfExtents.x() = (max[0] - min[0])/2.f; - mHalfExtents.y() = (max[1] - min[1])/2.f; - mHalfExtents.z() = (max[2] - min[2])/2.f; + shape->mCollisionShape->getAabb(transform, min, max); + mOriginalHalfExtents.x() = (max[0] - min[0]) / 2.f; + mOriginalHalfExtents.y() = (max[1] - min[1]) / 2.f; + mOriginalHalfExtents.z() = (max[2] - min[2]) / 2.f; - mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z()); + mMeshTranslation = osg::Vec3f(0.f, 0.f, mOriginalHalfExtents.z()); + } + + if (mOriginalHalfExtents.length2() == 0.f) + Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" + << ptr.getCellRef().getRefId() << "\"."; } - if (mHalfExtents.length2() == 0.f) - Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; + const btVector3 halfExtents = Misc::Convert::toBullet(mOriginalHalfExtents); + if ((mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) + && std::fabs(mOriginalHalfExtents.x() - mOriginalHalfExtents.y()) < 2.2) + { + switch (collisionShapeType) + { + case DetourNavigator::CollisionShapeType::Aabb: + mShape = std::make_unique(halfExtents); + mRotationallyInvariant = true; + break; + case DetourNavigator::CollisionShapeType::RotatingBox: + mShape = std::make_unique(halfExtents); + mRotationallyInvariant = false; + break; + case DetourNavigator::CollisionShapeType::Cylinder: + mShape = std::make_unique(halfExtents); + mRotationallyInvariant = true; + break; + } + mCollisionShapeType = collisionShapeType; + } + else + { + mShape = std::make_unique(halfExtents); + mRotationallyInvariant = false; + mCollisionShapeType = DetourNavigator::CollisionShapeType::RotatingBox; + } + + mConvexShape = static_cast(mShape.get()); + mConvexShape->setMargin(0.001); // make sure bullet isn't using the huge default convex shape margin of 0.04 + + mCollisionObject = std::make_unique(); + mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); + mCollisionObject->setActivationState(DISABLE_DEACTIVATION); + mCollisionObject->setCollisionShape(mShape.get()); + mCollisionObject->setUserPointer(this); + + updateScaleUnsafe(); + + if (!mRotationallyInvariant) + mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); + + addCollisionMask(getCollisionMask()); + updateCollisionObjectPositionUnsafe(); } +<<<<<<< HEAD mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2; @@ -123,12 +202,14 @@ void Actor::enableCollisionMode(bool collision) void Actor::enableCollisionBody(bool collision) { if (mExternalCollisionMode != collision) +======= + Actor::~Actor() +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - mExternalCollisionMode = collision; - updateCollisionMask(); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } -} +<<<<<<< HEAD void Actor::addCollisionMask(int collisionMask) { mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask); @@ -306,37 +387,219 @@ void Actor::setWalkingOnWater(bool walkingOnWater) void Actor::setCanWaterWalk(bool waterWalk) { if (waterWalk != mCanWaterWalk) +======= + void Actor::enableCollisionMode(bool collision) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - mCanWaterWalk = waterWalk; - updateCollisionMask(); + mInternalCollisionMode = collision; } -} -MWWorld::Ptr Actor::getStandingOnPtr() const -{ - std::scoped_lock lock(mPositionMutex); - return mStandingOnPtr; -} + void Actor::enableCollisionBody(bool collision) + { + if (mExternalCollisionMode != collision) + { + mExternalCollisionMode = collision; + updateCollisionMask(); + } + } -void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr) -{ - std::scoped_lock lock(mPositionMutex); - mStandingOnPtr = ptr; -} + void Actor::addCollisionMask(int collisionMask) + { + mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask); + } -bool Actor::skipCollisions() -{ - return std::exchange(mSkipCollisions, false); -} + void Actor::updateCollisionMask() + { + mTaskScheduler->setCollisionFilterMask(mCollisionObject.get(), getCollisionMask()); + } -void Actor::setVelocity(osg::Vec3f velocity) -{ - mVelocity = velocity; -} + int Actor::getCollisionMask() const + { + int collisionMask = CollisionType_World | CollisionType_HeightMap; + if (mExternalCollisionMode) + collisionMask |= CollisionType_Actor | CollisionType_Projectile | CollisionType_Door; + if (mCanWaterWalk) + collisionMask |= CollisionType_Water; + return collisionMask; + } -osg::Vec3f Actor::velocity() -{ - return std::exchange(mVelocity, osg::Vec3f()); -} + void Actor::updatePosition() + { + std::scoped_lock lock(mPositionMutex); + const auto worldPosition = mPtr.getRefData().getPosition().asVec3(); + mPreviousPosition = worldPosition; + mPosition = worldPosition; + mSimulationPosition = worldPosition; + mPositionOffset = osg::Vec3f(); + mStandingOnPtr = nullptr; + mSkipSimulation = true; + } + + void Actor::setSimulationPosition(const osg::Vec3f& position) + { + if (!std::exchange(mSkipSimulation, false)) + mSimulationPosition = position; + } + + osg::Vec3f Actor::getScaledMeshTranslation() const + { + return mRotation * osg::componentMultiply(mMeshTranslation, mScale); + } + + void Actor::updateCollisionObjectPosition() + { + std::scoped_lock lock(mPositionMutex); + updateCollisionObjectPositionUnsafe(); + } + + void Actor::updateCollisionObjectPositionUnsafe() + { + mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); + osg::Vec3f newPosition = getScaledMeshTranslation() + mPosition; + + auto& trans = mCollisionObject->getWorldTransform(); + trans.setOrigin(Misc::Convert::toBullet(newPosition)); + trans.setRotation(Misc::Convert::toBullet(mRotation)); + mCollisionObject->setWorldTransform(trans); + } + + osg::Vec3f Actor::getCollisionObjectPosition() const + { + std::scoped_lock lock(mPositionMutex); + return getScaledMeshTranslation() + mPosition; + } + + bool Actor::setPosition(const osg::Vec3f& position) + { + std::scoped_lock lock(mPositionMutex); + const bool worldPositionChanged = mPositionOffset.length2() != 0; + applyOffsetChange(); + if (worldPositionChanged || mSkipSimulation) + return true; + mPreviousPosition = mPosition; + mPosition = position; + return mPreviousPosition != mPosition; + } + + void Actor::adjustPosition(const osg::Vec3f& offset) + { + std::scoped_lock lock(mPositionMutex); + mPositionOffset += offset; + } + + osg::Vec3f Actor::applyOffsetChange() + { + if (mPositionOffset.length2() != 0) + { + mPosition += mPositionOffset; + mPreviousPosition += mPositionOffset; + mSimulationPosition += mPositionOffset; + mPositionOffset = osg::Vec3f(); + } + return mPosition; + } + + void Actor::setRotation(osg::Quat quat) + { + std::scoped_lock lock(mPositionMutex); + mRotation = quat; + } + + bool Actor::isRotationallyInvariant() const + { + return mRotationallyInvariant; + } + + void Actor::updateScale() + { + std::scoped_lock lock(mPositionMutex); + updateScaleUnsafe(); + } + + void Actor::updateScaleUnsafe() + { + float scale = mPtr.getCellRef().getScale(); + osg::Vec3f scaleVec(scale, scale, scale); + + mPtr.getClass().adjustScale(mPtr, scaleVec, false); + mScale = scaleVec; + mHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec); + + scaleVec = osg::Vec3f(scale, scale, scale); + mPtr.getClass().adjustScale(mPtr, scaleVec, true); + mRenderingHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec); + } + + osg::Vec3f Actor::getHalfExtents() const + { + return mHalfExtents; + } + + osg::Vec3f Actor::getOriginalHalfExtents() const + { + return mOriginalHalfExtents; + } + + osg::Vec3f Actor::getRenderingHalfExtents() const + { + return mRenderingHalfExtents; + } + + void Actor::setInertialForce(const osg::Vec3f& force) + { + mForce = force; + } + + void Actor::setOnGround(bool grounded) + { + mOnGround = grounded; + } + + void Actor::setOnSlope(bool slope) + { + mOnSlope = slope; + } + + bool Actor::isWalkingOnWater() const + { + return mWalkingOnWater; + } + + void Actor::setWalkingOnWater(bool walkingOnWater) + { + mWalkingOnWater = walkingOnWater; + } + + void Actor::setCanWaterWalk(bool waterWalk) + { + if (waterWalk != mCanWaterWalk) + { + mCanWaterWalk = waterWalk; + updateCollisionMask(); + } + } + + MWWorld::Ptr Actor::getStandingOnPtr() const + { + std::scoped_lock lock(mPositionMutex); + return mStandingOnPtr; + } + + void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr) + { + std::scoped_lock lock(mPositionMutex); + mStandingOnPtr = ptr; + } + + bool Actor::canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const + { + const float halfZ = getHalfExtents().z(); + const osg::Vec3f actorPosition = getPosition(); + const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); + const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); + MWPhysics::ActorTracer tracer; + tracer.doTrace(getCollisionObject(), startingPosition, destinationPosition, world); + return (tracer.mFraction >= 1.0f); + } } diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 99f625394..c76fcf5d8 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -1,23 +1,23 @@ #ifndef OPENMW_MWPHYSICS_ACTOR_H #define OPENMW_MWPHYSICS_ACTOR_H -#include #include #include #include "ptrholder.hpp" -#include -#include +#include + #include +#include class btCollisionShape; -class btCollisionObject; +class btCollisionWorld; class btConvexShape; namespace Resource { - class BulletShape; + struct BulletShape; } namespace MWPhysics @@ -27,28 +27,35 @@ namespace MWPhysics class Actor final : public PtrHolder { public: +<<<<<<< HEAD Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk); +======= + Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, + bool canWaterWalk, DetourNavigator::CollisionShapeType collisionShapeType); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 ~Actor() override; + Actor(const Actor&) = delete; + + Actor& operator=(const Actor&) = delete; + /** * Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry. */ void enableCollisionMode(bool collision); - bool getCollisionMode() const - { - return mInternalCollisionMode.load(std::memory_order_acquire); - } + bool getCollisionMode() const { return mInternalCollisionMode; } btConvexShape* getConvexShape() const { return mConvexShape; } /** - * Enables or disables the *external* collision body. If disabled, other actors will not collide with this actor. + * Enables or disables the *external* collision body. If disabled, other actors will not collide with this + * actor. */ void enableCollisionBody(bool collision); void updateScale(); - void updateRotation(); + void setRotation(osg::Quat quat); /** * Return true if the collision shape looks the same no matter how its Z rotated. @@ -56,11 +63,10 @@ namespace MWPhysics bool isRotationallyInvariant() const; /** - * Used by the physics simulation to store the simulation result. Used in conjunction with mWorldPosition - * to account for e.g. scripted movements - */ + * Used by the physics simulation to store the simulation result. Used in conjunction with mWorldPosition + * to account for e.g. scripted movements + */ void setSimulationPosition(const osg::Vec3f& position); - osg::Vec3f getSimulationPosition() const; void updateCollisionObjectPosition(); @@ -76,67 +82,51 @@ namespace MWPhysics /** * Returns the position of the collision body - * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. + * @note The collision shape's origin is in its center, so the position returned can be described as center of + * the actor collision box in world space. */ osg::Vec3f getCollisionObjectPosition() const; /** - * Store the current position into mPreviousPosition, then move to this position. - * Returns true if the new position is different. - */ + * Store the current position into mPreviousPosition, then move to this position. + * Returns true if the new position is different. + */ bool setPosition(const osg::Vec3f& position); // force set actor position to be as in Ptr::RefData void updatePosition(); // register a position offset that will be applied during simulation. - void adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions); + void adjustPosition(const osg::Vec3f& offset); // apply position offset. Can't be called during simulation - void applyOffsetChange(); - - osg::Vec3f getPosition() const; - - osg::Vec3f getPreviousPosition() const; + osg::Vec3f applyOffsetChange(); /** * Returns the half extents of the collision body (scaled according to rendering scale) - * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, - * most likely to make environment collision testing easier. However in some cases (swimming level) we want the actual scale. + * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't + * applied to the collision shape, most likely to make environment collision testing easier. However in some + * cases (swimming level) we want the actual scale. */ osg::Vec3f getRenderingHalfExtents() const; /** * Sets the current amount of inertial force (incl. gravity) affecting this physic actor */ - void setInertialForce(const osg::Vec3f &force); + void setInertialForce(const osg::Vec3f& force); /** * Gets the current amount of inertial force (incl. gravity) affecting this physic actor */ - const osg::Vec3f &getInertialForce() const - { - return mForce; - } + const osg::Vec3f& getInertialForce() const { return mForce; } void setOnGround(bool grounded); - bool getOnGround() const - { - return mInternalCollisionMode.load(std::memory_order_acquire) && mOnGround.load(std::memory_order_acquire); - } + bool getOnGround() const { return mOnGround; } void setOnSlope(bool slope); - bool getOnSlope() const - { - return mInternalCollisionMode.load(std::memory_order_acquire) && mOnSlope.load(std::memory_order_acquire); - } - - btCollisionObject* getCollisionObject() const - { - return mCollisionObject.get(); - } + bool getOnSlope() const { return mOnSlope; } /// Sets whether this actor should be able to collide with the water surface void setCanWaterWalk(bool waterWalk); @@ -148,28 +138,19 @@ namespace MWPhysics MWWorld::Ptr getStandingOnPtr() const; void setStandingOnPtr(const MWWorld::Ptr& ptr); - unsigned int getStuckFrames() const - { - return mStuckFrames; - } - void setStuckFrames(unsigned int frames) - { - mStuckFrames = frames; - } + unsigned int getStuckFrames() const { return mStuckFrames; } + void setStuckFrames(unsigned int frames) { mStuckFrames = frames; } - const osg::Vec3f &getLastStuckPosition() const - { - return mLastStuckPosition; - } - void setLastStuckPosition(osg::Vec3f position) - { - mLastStuckPosition = position; - } + const osg::Vec3f& getLastStuckPosition() const { return mLastStuckPosition; } + void setLastStuckPosition(osg::Vec3f position) { mLastStuckPosition = position; } - bool skipCollisions(); + bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const; - void setVelocity(osg::Vec3f velocity); - osg::Vec3f velocity(); + bool isActive() const { return mActive; } + + void setActive(bool value) { mActive = value; } + + DetourNavigator::CollisionShapeType getCollisionShapeType() const { return mCollisionShapeType; } private: MWWorld::Ptr mStandingOnPtr; @@ -182,47 +163,50 @@ namespace MWPhysics osg::Vec3f getScaledMeshTranslation() const; bool mCanWaterWalk; - std::atomic mWalkingOnWater; + bool mWalkingOnWater; bool mRotationallyInvariant; + DetourNavigator::CollisionShapeType mCollisionShapeType; + std::unique_ptr mShape; btConvexShape* mConvexShape; - std::unique_ptr mCollisionObject; - osg::Vec3f mMeshTranslation; + osg::Vec3f mOriginalHalfExtents; osg::Vec3f mHalfExtents; + osg::Vec3f mRenderingHalfExtents; osg::Quat mRotation; osg::Vec3f mScale; - osg::Vec3f mRenderingScale; - osg::Vec3f mSimulationPosition; - osg::Vec3f mPosition; - osg::Vec3f mPreviousPosition; osg::Vec3f mPositionOffset; +<<<<<<< HEAD osg::Vec3f mVelocity; bool mWorldPositionChanged; bool mSkipCollisions; bool mSkipSimulation; +======= + bool mSkipSimulation = true; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mutable std::mutex mPositionMutex; unsigned int mStuckFrames; osg::Vec3f mLastStuckPosition; osg::Vec3f mForce; - std::atomic mOnGround; - std::atomic mOnSlope; - std::atomic mInternalCollisionMode; + bool mOnGround; + bool mOnSlope; + bool mInternalCollisionMode; bool mExternalCollisionMode; + bool mActive; PhysicsTaskScheduler* mTaskScheduler; - Actor(const Actor&); - Actor& operator=(const Actor&); + inline void updateScaleUnsafe(); + + inline void updateCollisionObjectPositionUnsafe(); }; } - #endif diff --git a/apps/openmw/mwphysics/actorconvexcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp index ef52e07c2..db077beb3 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -14,53 +14,58 @@ namespace MWPhysics public: bool overlapping = false; - btScalar addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* colObj0Wrap, - int partId0, - int index0, - const btCollisionObjectWrapper* colObj1Wrap, - int partId1, - int index1) override + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, + int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override { - if(cp.getDistance() <= 0.0f) + if (cp.getDistance() <= 0.0f) overlapping = true; return btScalar(1); } }; - ActorConvexCallback::ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) - : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), - mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world) + ActorConvexCallback::ActorConvexCallback( + const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, const btCollisionWorld* world) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + , mMe(me) + , mMotion(motion) + , mMinCollisionDot(minCollisionDot) + , mWorld(world) { } - btScalar ActorConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) + btScalar ActorConvexCallback::addSingleResult( + btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); // override data for actor-actor collisions - // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter of the distance between them - // For some reason this doesn't work as well as it should when using capsules, but it still helps a lot. - if(convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) + // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter + // of the distance between them For some reason this doesn't work as well as it should when using capsules, but + // it still helps a lot. + if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) { ActorOverlapTester isOverlapping; - // FIXME: This is absolutely terrible and bullet should feel terrible for not making contactPairTest const-correct. - ContactTestWrapper::contactPairTest(const_cast(mWorld), const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), isOverlapping); + // FIXME: This is absolutely terrible and bullet should feel terrible for not making contactPairTest + // const-correct. + ContactTestWrapper::contactPairTest(const_cast(mWorld), + const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), + isOverlapping); - if(isOverlapping.overlapping) + if (isOverlapping.overlapping) { auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin()); auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin()); osg::Vec3f motion = Misc::Convert::toOsg(mMotion); - osg::Vec3f normal = (originA-originB); + osg::Vec3f normal = (originA - originB); normal.z() = 0; normal.normalize(); - // only collide if horizontally moving towards the hit actor (note: the motion vector appears to be inverted) - // FIXME: This kinda screws with standing on actors that walk up slopes for some reason. Makes you fall through them. - // It happens in vanilla Morrowind too, but much less often. - // I tried hunting down why but couldn't figure it out. Possibly a stair stepping or ground ejection bug. - if(normal * motion > 0.0f) + // only collide if horizontally moving towards the hit actor (note: the motion vector appears to be + // inverted) + // FIXME: This kinda screws with standing on actors that walk up slopes for some reason. Makes you fall + // through them. It happens in vanilla Morrowind too, but much less often. I tried hunting down why but + // couldn't figure it out. Possibly a stair stepping or ground ejection bug. + if (normal * motion > 0.0f) { convexResult.m_hitFraction = 0.0f; convexResult.m_hitNormalLocal = Misc::Convert::toBullet(normal); @@ -72,15 +77,14 @@ namespace MWPhysics } } } - if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) + if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup + == CollisionType_Projectile) { auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) return btScalar(1); - auto* targetHolder = static_cast(mMe->getUserPointer()); - const MWWorld::Ptr target = targetHolder->getPtr(); - if (projectileHolder->isValidTarget(target)) - projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); + if (projectileHolder->isValidTarget(mMe)) + projectileHolder->hit(mMe, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); return btScalar(1); } @@ -89,8 +93,9 @@ namespace MWPhysics hitNormalWorld = convexResult.m_hitNormalLocal; else { - ///need to transform normal into worldspace - hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; + /// need to transform normal into worldspace + hitNormalWorld + = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal; } // dot product of the motion vector against the collision contact normal diff --git a/apps/openmw/mwphysics/actorconvexcallback.hpp b/apps/openmw/mwphysics/actorconvexcallback.hpp index 1c28ee6cc..4b9ab1a8a 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.hpp +++ b/apps/openmw/mwphysics/actorconvexcallback.hpp @@ -10,15 +10,16 @@ namespace MWPhysics class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); + ActorConvexCallback(const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, + const btCollisionWorld* world); - btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override; + btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) override; protected: - const btCollisionObject *mMe; + const btCollisionObject* mMe; const btVector3 mMotion; const btScalar mMinCollisionDot; - const btCollisionWorld * mWorld; + const btCollisionWorld* mWorld; }; } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 32d97d6c7..30a42bc3b 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -5,31 +5,29 @@ #include -#include "../mwworld/class.hpp" - -#include "ptrholder.hpp" +#include "collisiontype.hpp" namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to) - : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me), mTargets(std::move(targets)) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, + std::vector targets, const btVector3& from, const btVector3& to) + : btCollisionWorld::ClosestRayResultCallback(from, to) + , mMe(me) + , mTargets(std::move(targets)) { } - btScalar ClosestNotMeRayResultCallback::addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) + btScalar ClosestNotMeRayResultCallback::addSingleResult( + btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { - if (rayResult.m_collisionObject == mMe) + const auto* hitObject = rayResult.m_collisionObject; + if (hitObject == mMe) return 1.f; - if (!mTargets.empty()) + if (hitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) { - if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) - { - auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); - if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) - return 1.f; - } + if ((std::find(mTargets.begin(), mTargets.end(), hitObject) == mTargets.end())) + return 1.f; } return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index 1fa32ef68..e6f5c45d3 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -14,7 +14,8 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to); + ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, + const btVector3& from, const btVector3& to); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; diff --git a/apps/openmw/mwphysics/collisiontype.hpp b/apps/openmw/mwphysics/collisiontype.hpp index 0d6a32fc0..5dda2a6ae 100644 --- a/apps/openmw/mwphysics/collisiontype.hpp +++ b/apps/openmw/mwphysics/collisiontype.hpp @@ -4,14 +4,21 @@ namespace MWPhysics { -enum CollisionType { - CollisionType_World = 1<<0, - CollisionType_Door = 1<<1, - CollisionType_Actor = 1<<2, - CollisionType_HeightMap = 1<<3, - CollisionType_Projectile = 1<<4, - CollisionType_Water = 1<<5 -}; + enum CollisionType + { + CollisionType_World = 1 << 0, + CollisionType_Door = 1 << 1, + CollisionType_Actor = 1 << 2, + CollisionType_HeightMap = 1 << 3, + CollisionType_Projectile = 1 << 4, + CollisionType_Water = 1 << 5, + CollisionType_Default + = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor | CollisionType_Door, + CollisionType_AnyPhysical = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor + | CollisionType_Door | CollisionType_Projectile | CollisionType_Water, + CollisionType_CameraOnly = 1 << 6, + CollisionType_VisualOnly = 1 << 7 + }; } diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index eaeb308d5..0b3c71408 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -3,23 +3,23 @@ namespace MWPhysics { - static constexpr float sStepSizeUp = 34.0f; static constexpr float sStepSizeDown = 62.0f; static constexpr float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes static constexpr float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes - // whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance + // whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems + // but improves performance static constexpr bool sDoExtraStairHacks = true; static constexpr float sGroundOffset = 1.0f; - static constexpr float sMaxSlope = 49.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static constexpr int sMaxIterations = 8; // Allows for more precise movement solving without getting stuck or snagging too easily. - static constexpr float sCollisionMargin = 0.1f; - // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily - // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues + static constexpr float sCollisionMargin = 0.2f; + // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code + // to run unnecessarily Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some + // glitchy snagging issues static constexpr float sAllowedPenetration = 0.0f; } diff --git a/apps/openmw/mwphysics/contacttestresultcallback.cpp b/apps/openmw/mwphysics/contacttestresultcallback.cpp index 5829ee02f..dae0a65af 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.cpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.cpp @@ -13,16 +13,16 @@ namespace MWPhysics { } - btScalar ContactTestResultCallback::addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, - const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) + btScalar ContactTestResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, + int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) { const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; if (collisionObject == mTestedAgainst) collisionObject = col1Wrap->m_collisionObject; PtrHolder* holder = static_cast(collisionObject->getUserPointer()); if (holder) - mResult.emplace_back(ContactPoint{holder->getPtr(), Misc::Convert::toOsg(cp.m_positionWorldOnB), Misc::Convert::toOsg(cp.m_normalWorldOnB)}); + mResult.emplace_back(ContactPoint{ holder->getPtr(), Misc::Convert::toOsg(cp.m_positionWorldOnB), + Misc::Convert::toOsg(cp.m_normalWorldOnB) }); return 0.f; } diff --git a/apps/openmw/mwphysics/contacttestresultcallback.hpp b/apps/openmw/mwphysics/contacttestresultcallback.hpp index 3d1b3b8aa..ae900e020 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.hpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.hpp @@ -19,9 +19,8 @@ namespace MWPhysics public: ContactTestResultCallback(const btCollisionObject* testedAgainst); - btScalar addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, - const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) override; + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, + const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) override; std::vector mResult; }; diff --git a/apps/openmw/mwphysics/contacttestwrapper.cpp b/apps/openmw/mwphysics/contacttestwrapper.cpp index c11a7e292..d5da9e67e 100644 --- a/apps/openmw/mwphysics/contacttestwrapper.cpp +++ b/apps/openmw/mwphysics/contacttestwrapper.cpp @@ -6,13 +6,15 @@ namespace MWPhysics { // Concurrent calls to contactPairTest (and by extension contactTest) are forbidden. static std::mutex contactMutex; - void ContactTestWrapper::contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) + void ContactTestWrapper::contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, + btCollisionWorld::ContactResultCallback& resultCallback) { std::unique_lock lock(contactMutex); collisionWorld->contactTest(colObj, resultCallback); } - void ContactTestWrapper::contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback) + void ContactTestWrapper::contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, + btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback) { std::unique_lock lock(contactMutex); collisionWorld->contactPairTest(colObjA, colObjB, resultCallback); diff --git a/apps/openmw/mwphysics/contacttestwrapper.h b/apps/openmw/mwphysics/contacttestwrapper.h index b3b6edc59..6a53f9ae2 100644 --- a/apps/openmw/mwphysics/contacttestwrapper.h +++ b/apps/openmw/mwphysics/contacttestwrapper.h @@ -7,8 +7,10 @@ namespace MWPhysics { struct ContactTestWrapper { - static void contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback); - static void contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback); + static void contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, + btCollisionWorld::ContactResultCallback& resultCallback); + static void contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, + btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback); }; } #endif diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp index 7744af14b..766ca7979 100644 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp +++ b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp @@ -4,37 +4,36 @@ #include -#include "../mwworld/class.hpp" - -#include "ptrholder.hpp" +#include "collisiontype.hpp" namespace MWPhysics { - DeepestNotMeContactTestResultCallback::DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3 &origin) - : mMe(me), mTargets(targets), mOrigin(origin), mLeastDistSqr(std::numeric_limits::max()) + DeepestNotMeContactTestResultCallback::DeepestNotMeContactTestResultCallback( + const btCollisionObject* me, const std::vector& targets, const btVector3& origin) + : mMe(me) + , mTargets(targets) + , mOrigin(origin) + , mLeastDistSqr(std::numeric_limits::max()) { } btScalar DeepestNotMeContactTestResultCallback::addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, - const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) + const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, + int partId1, int index1) { const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; if (collisionObject != mMe) { - if (!mTargets.empty()) + if (collisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor + && !mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) - { - PtrHolder* holder = static_cast(collisionObject->getUserPointer()); - if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) - return 0.f; - } + return 0.f; } btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); - if(!mObject || distsqr < mLeastDistSqr) + if (!mObject || distsqr < mLeastDistSqr) { mObject = collisionObject; mLeastDistSqr = distsqr; diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp index 00cb5c01f..d22a79e64 100644 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp +++ b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp @@ -18,16 +18,16 @@ namespace MWPhysics btVector3 mOrigin; public: - const btCollisionObject *mObject{nullptr}; - btVector3 mContactPoint{0,0,0}; - btVector3 mContactNormal{0,0,0}; + const btCollisionObject* mObject{ nullptr }; + btVector3 mContactPoint{ 0, 0, 0 }; + btVector3 mContactNormal{ 0, 0, 0 }; btScalar mLeastDistSqr; - DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3 &origin); + DeepestNotMeContactTestResultCallback( + const btCollisionObject* me, const std::vector& targets, const btVector3& origin); - btScalar addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, - const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) override; + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, + const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) override; }; } diff --git a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp index 275325cf6..c1fa0ad1e 100644 --- a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp +++ b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp @@ -1,63 +1,64 @@ #ifndef OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H #define OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H -#include #include #include -#include +#include #include namespace MWPhysics { // https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection - bool testAabbAgainstSphere(const btVector3& aabbMin, const btVector3& aabbMax, - const btVector3& position, const btScalar radius) + bool testAabbAgainstSphere( + const btVector3& aabbMin, const btVector3& aabbMax, const btVector3& position, const btScalar radius) { - const btVector3 nearest( - std::max(aabbMin.x(), std::min(aabbMax.x(), position.x())), - std::max(aabbMin.y(), std::min(aabbMax.y(), position.y())), - std::max(aabbMin.z(), std::min(aabbMax.z(), position.z())) - ); + const btVector3 nearest(std::clamp(position.x(), aabbMin.x(), aabbMax.x()), + std::clamp(position.y(), aabbMin.y(), aabbMax.y()), std::clamp(position.z(), aabbMin.z(), aabbMax.z())); return nearest.distance(position) < radius; } + template class HasSphereCollisionCallback final : public btBroadphaseAabbCallback { public: - HasSphereCollisionCallback(const btVector3& position, const btScalar radius, btCollisionObject* object, - const int mask, const int group) - : mPosition(position), - mRadius(radius), - mCollisionObject(object), - mCollisionFilterMask(mask), - mCollisionFilterGroup(group) + HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask, const int group, + const Ignore& ignore, OnCollision* onCollision) + : mPosition(position) + , mRadius(radius) + , mIgnore(ignore) + , mCollisionFilterMask(mask) + , mCollisionFilterGroup(group) + , mOnCollision(onCollision) { } bool process(const btBroadphaseProxy* proxy) override { - if (mResult) + if (mResult && mOnCollision == nullptr) return false; const auto collisionObject = static_cast(proxy->m_clientObject); - if (collisionObject == mCollisionObject) + if (mIgnore(collisionObject) || !needsCollision(*proxy) + || !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius)) return true; - if (needsCollision(*proxy)) - mResult = testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius); + mResult = true; + if (mOnCollision != nullptr) + { + (*mOnCollision)(collisionObject); + return true; + } return !mResult; } - bool getResult() const - { - return mResult; - } + bool getResult() const { return mResult; } private: btVector3 mPosition; btScalar mRadius; - btCollisionObject* mCollisionObject; + Ignore mIgnore; int mCollisionFilterMask; int mCollisionFilterGroup; + OnCollision* mOnCollision; bool mResult = false; bool needsCollision(const btBroadphaseProxy& proxy) const diff --git a/apps/openmw/mwphysics/heightfield.cpp b/apps/openmw/mwphysics/heightfield.cpp index 0856d8bb5..7131ef738 100644 --- a/apps/openmw/mwphysics/heightfield.cpp +++ b/apps/openmw/mwphysics/heightfield.cpp @@ -1,10 +1,12 @@ #include "heightfield.hpp" #include "mtphysics.hpp" +#include + #include -#include #include +#include #include @@ -19,17 +21,17 @@ namespace { template - auto makeHeights(const T* heights, float sqrtVerts) + auto makeHeights(const T* heights, int verts) -> std::enable_if_t::value, std::vector> { return {}; } template - auto makeHeights(const T* heights, float sqrtVerts) + auto makeHeights(const T* heights, int verts) -> std::enable_if_t::value, std::vector> { - return std::vector(heights, heights + static_cast(sqrtVerts * sqrtVerts)); + return std::vector(heights, heights + static_cast(verts * verts)); } template @@ -50,31 +52,32 @@ namespace namespace MWPhysics { +<<<<<<< HEAD HeightField::HeightField() {} HeightField::HeightField(const HeightField&, const osg::CopyOp&) {} HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) +======= + HeightField::HeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, + const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 : mHoldObject(holdObject) #if BT_BULLET_VERSION < 310 - , mHeights(makeHeights(heights, sqrtVerts)) + , mHeights(makeHeights(heights, verts)) #endif , mTaskScheduler(scheduler) { #if BT_BULLET_VERSION < 310 mShape = std::make_unique( - sqrtVerts, sqrtVerts, - getHeights(heights, mHeights), - 1, - minH, maxH, 2, - PHY_FLOAT, false - ); + verts, verts, getHeights(heights, mHeights), 1, minH, maxH, 2, PHY_FLOAT, false); #else - mShape = std::make_unique( - sqrtVerts, sqrtVerts, heights, minH, maxH, 2, false); + mShape = std::make_unique(verts, verts, heights, minH, maxH, 2, false); #endif mShape->setUseDiamondSubdivision(true); - mShape->setLocalScaling(btVector3(triSize, triSize, 1)); + + const float scaling = static_cast(size) / static_cast(verts - 1); + mShape->setLocalScaling(btVector3(scaling, scaling, 1)); #if BT_BULLET_VERSION >= 289 // Accelerates some collision tests. @@ -85,15 +88,14 @@ namespace MWPhysics mShape->buildAccelerator(); #endif - btTransform transform(btQuaternion::getIdentity(), - btVector3((x+0.5f) * triSize * (sqrtVerts-1), - (y+0.5f) * triSize * (sqrtVerts-1), - (maxH+minH)*0.5f)); + const btTransform transform( + btQuaternion::getIdentity(), BulletHelpers::getHeightfieldShift(x, y, size, minH, maxH)); mCollisionObject = std::make_unique(); mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setWorldTransform(transform); - mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_HeightMap, CollisionType_Actor|CollisionType_Projectile); + mTaskScheduler->addCollisionObject( + mCollisionObject.get(), CollisionType_HeightMap, CollisionType_Actor | CollisionType_Projectile); } HeightField::~HeightField() diff --git a/apps/openmw/mwphysics/heightfield.hpp b/apps/openmw/mwphysics/heightfield.hpp index 007fd12f5..c2612ae7b 100644 --- a/apps/openmw/mwphysics/heightfield.hpp +++ b/apps/openmw/mwphysics/heightfield.hpp @@ -24,7 +24,8 @@ namespace MWPhysics class HeightField : public osg::Object { public: - HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); + HeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, + const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); ~HeightField(); META_Object(MWPhysics, HeightField) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fd0e090fc..8ba9f44c7 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -2,23 +2,23 @@ #include #include -#include +#include -#include +#include #include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/refdata.hpp" #include "actor.hpp" #include "collisiontype.hpp" #include "constants.hpp" #include "contacttestwrapper.h" #include "physicssystem.hpp" +#include "projectile.hpp" +#include "projectileconvexcallback.hpp" #include "stepper.hpp" #include "trace.h" @@ -26,7 +26,7 @@ namespace MWPhysics { - static bool isActor(const btCollisionObject *obj) + static bool isActor(const btCollisionObject* obj) { assert(obj); return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; @@ -35,17 +35,20 @@ namespace MWPhysics class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback { public: - ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me) + ContactCollectionCallback(const btCollisionObject* me, osg::Vec3f velocity) + : mMe(me) { m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; mVelocity = Misc::Convert::toBullet(velocity); } - btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override + btScalar addSingleResult(btManifoldPoint& contact, const btCollisionObjectWrapper* colObj0Wrap, int partId0, + int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override { if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) return 0.0; - // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, that would break detection when not moving) + // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, + // that would break detection when not moving) if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) return 0.0; auto delta = contact.m_normalWorldOnB * -contact.m_distance1; @@ -68,21 +71,22 @@ namespace MWPhysics btScalar mMaxX = 0.0; btScalar mMaxY = 0.0; btScalar mMaxZ = 0.0; - btVector3 mContactSum{0.0, 0.0, 0.0}; - btVector3 mNormal{0.0, 0.0, 0.0}; // points towards "me" - btVector3 mDelta{0.0, 0.0, 0.0}; // points towards "me" + btVector3 mContactSum{ 0.0, 0.0, 0.0 }; + btVector3 mNormal{ 0.0, 0.0, 0.0 }; // points towards "me" + btVector3 mDelta{ 0.0, 0.0, 0.0 }; // points towards "me" btScalar mDistance = 0.0; // negative or zero protected: btVector3 mVelocity; - const btCollisionObject * mMe; + const btCollisionObject* mMe; }; - osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) + osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, Actor* actor, + btCollisionWorld* collisionWorld, float maxHeight) { osg::Vec3f offset = actor->getCollisionObjectPosition() - ptr.getRefData().getPosition().asVec3(); ActorTracer tracer; - tracer.findGround(actor, position + offset, position + offset - osg::Vec3f(0,0,maxHeight), collisionWorld); + tracer.findGround(actor, position + offset, position + offset - osg::Vec3f(0, 0, maxHeight), collisionWorld); if (tracer.mFraction >= 1.0f) { actor->setOnGround(false); @@ -95,16 +99,17 @@ namespace MWPhysics // Required for some broken door destinations in Morrowind.esm, where the spawn point // intersects with other geometry if the actor's base is taken into account btVector3 from = Misc::Convert::toBullet(position); - btVector3 to = from - btVector3(0,0,maxHeight); + btVector3 to = from - btVector3(0, 0, maxHeight); btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); - resultCallback1.m_collisionFilterGroup = 0xff; - resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap; + resultCallback1.m_collisionFilterGroup = CollisionType_AnyPhysical; + resultCallback1.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap; collisionWorld->rayTest(from, to, resultCallback1); - if (resultCallback1.hasHit() && ((Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos + offset).length2() > 35*35 - || !isWalkableSlope(tracer.mPlaneNormal))) + if (resultCallback1.hasHit() + && ((Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos + offset).length2() > 35 * 35 + || !isWalkableSlope(tracer.mPlaneNormal))) { actor->setOnSlope(!isWalkableSlope(resultCallback1.m_hitNormalWorld)); return Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, sGroundOffset); @@ -112,118 +117,109 @@ namespace MWPhysics actor->setOnSlope(!isWalkableSlope(tracer.mPlaneNormal)); - return tracer.mEndPos-offset + osg::Vec3f(0.f, 0.f, sGroundOffset); + return tracer.mEndPos - offset + osg::Vec3f(0.f, 0.f, sGroundOffset); } - void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, - WorldFrameData& worldData) + void MovementSolver::move( + ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, const WorldFrameData& worldData) { - auto* physicActor = actor.mActorRaw; - const ESM::Position& refpos = actor.mRefpos; - // Early-out for totally static creatures - // (Not sure if gravity should still apply?) - { - const auto ptr = physicActor->getPtr(); - if (!ptr.getClass().isMobile(ptr)) - return; - } - // Reset per-frame data - physicActor->setWalkingOnWater(false); + actor.mWalkingOnWater = false; // Anything to collide with? - if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) + if (actor.mSkipCollisionDetection) { - actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * - osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)) - ) * actor.mMovement * time; + actor.mPosition += (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) + * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) + * actor.mMovement * time; return; } - const btCollisionObject *colobj = physicActor->getCollisionObject(); - // Adjust for collision mesh offset relative to actor's "location" - // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own) - // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation - // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() - osg::Vec3f halfExtents = physicActor->getHalfExtents(); - actor.mPosition.z() += halfExtents.z(); // vanilla-accurate + // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our + // own) for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of + // from internal hull translation if not for this hack, the "correct" collision hull position would be + // physicActor->getScaledMeshTranslation() + actor.mPosition.z() += actor.mHalfExtentsZ; // vanilla-accurate - static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); - float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); + float swimlevel = actor.mSwimLevel + actor.mHalfExtentsZ; ActorTracer tracer; - osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f velocity; - if (actor.mPosition.z() < swimlevel || actor.mFlying) - { - velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; - } - else - { - velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; - - if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope()) - || (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope())) - inertia = velocity; - else if (!physicActor->getOnGround() || physicActor->getOnSlope()) - velocity = velocity + inertia; - } - // Dead and paralyzed actors underwater will float to the surface, // if the CharacterController tells us to do so - if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel) - velocity = osg::Vec3f(0,0,1) * 25; - - if (actor.mWantJump) - actor.mDidJump = true; - - // Now that we have the effective movement vector, apply wind forces to it - if (worldData.mIsInStorm) + if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) { - osg::Vec3f stormDirection = worldData.mStormDirection; - float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length()))); - static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get().find("fStromWalkMult")->mValue.getFloat(); - velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); + velocity = osg::Vec3f(0, 0, 1) * 25; + } + else if (actor.mPosition.z() < swimlevel || actor.mFlying) + { + velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) + * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) + * actor.mMovement; + } + else + { + velocity = (osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; + + if ((velocity.z() > 0.f && actor.mIsOnGround && !actor.mIsOnSlope) + || (velocity.z() > 0.f && velocity.z() + actor.mInertia.z() <= -velocity.z() && actor.mIsOnSlope)) + actor.mInertia = velocity; + else if (!actor.mIsOnGround || actor.mIsOnSlope) + velocity = velocity + actor.mInertia; } - Stepper stepper(collisionWorld, colobj); + // Now that we have the effective movement vector, apply wind forces to it + if (worldData.mIsInStorm && velocity.length() > 0) + { + osg::Vec3f stormDirection = worldData.mStormDirection; + float angleDegrees = osg::RadiansToDegrees( + std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length()))); + static const float fStromWalkMult = MWBase::Environment::get() + .getESMStore() + ->get() + .find("fStromWalkMult") + ->mValue.getFloat(); + velocity *= 1.f - (fStromWalkMult * (angleDegrees / 180.f)); + } + + Stepper stepper(collisionWorld, actor.mCollisionObject); osg::Vec3f origVelocity = velocity; osg::Vec3f newPosition = actor.mPosition; /* * A loop to find newPosition using tracer, if successful different from the starting position. * nextpos is the local variable used to find potential newPosition, using velocity and remainingTime * The initial velocity was set earlier (see above). - */ + */ float remainingTime = time; - bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying; int numTimesSlid = 0; - osg::Vec3f lastSlideNormal(0,0,1); - osg::Vec3f lastSlideNormalFallback(0,0,1); + osg::Vec3f lastSlideNormal(0, 0, 1); + osg::Vec3f lastSlideNormalFallback(0, 0, 1); bool forceGroundTest = false; for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { osg::Vec3f nextpos = newPosition + velocity * remainingTime; + bool underwater = newPosition.z() < swimlevel; // If not able to fly, don't allow to swim up into the air - if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel) + if (!actor.mFlying && nextpos.z() > swimlevel && underwater) { - const osg::Vec3f down(0,0,-1); + const osg::Vec3f down(0, 0, -1); velocity = reject(velocity, down); // NOTE: remainingTime is unchanged before the loop continues continue; // velocity updated, calculate nextpos again } - if((newPosition - nextpos).length2() > 0.0001) + if ((newPosition - nextpos).length2() > 0.0001) { // trace to where character would go if there were no obstructions - tracer.doTrace(colobj, newPosition, nextpos, collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); // check for obstructions - if(tracer.mFraction >= 1.0f) + if (tracer.mFraction >= 1.0f) { newPosition = tracer.mEndPos; // ok to move, so set newPosition break; @@ -241,14 +237,14 @@ namespace MWPhysics break; } - if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel) - seenGround = true; + bool seenGround = !actor.mFlying && !underwater + && ((actor.mIsOnGround && !actor.mIsOnSlope) || isWalkableSlope(tracer.mPlaneNormal)); // We hit something. Check if we can step up. - float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); + float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ; osg::Vec3f oldPosition = newPosition; bool usedStepLogic = false; - if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject)) + if (hitHeight < Constants::sStepSizeUp && !isActor(tracer.mHitObject)) { // Try to step up onto it. // NOTE: this modifies newPosition and velocity on its own if successful @@ -256,23 +252,24 @@ namespace MWPhysics } if (usedStepLogic) { - // don't let pure water creatures move out of water after stepMove - const auto ptr = physicActor->getPtr(); - if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel) + if (actor.mIsAquatic && newPosition.z() + actor.mHalfExtentsZ > actor.mWaterlevel) newPosition = oldPosition; - else if(!actor.mFlying && actor.mPosition.z() >= swimlevel) + else if (!actor.mFlying && actor.mPosition.z() >= swimlevel) forceGroundTest = true; } else { // Can't step up, so slide against what we ran into - remainingTime *= (1.0f-tracer.mFraction); + remainingTime *= (1.0f - tracer.mFraction); auto planeNormal = tracer.mPlaneNormal; + // need to know the unadjusted normal to handle certain types of seams properly + const auto origPlaneNormal = planeNormal; // If we touched the ground this frame, and whatever we ran into is a wall of some sort, // pretend that its collision normal is pointing horizontally - // (fixes snagging on slightly downward-facing walls, and crawling up the bases of very steep walls because of the collision margin) + // (fixes snagging on slightly downward-facing walls, and crawling up the bases of very steep walls + // because of the collision margin) if (seenGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) { planeNormal.z() = 0; @@ -280,45 +277,49 @@ namespace MWPhysics } // Move up to what we ran into (with a bit of a collision margin) - if ((newPosition-tracer.mEndPos).length2() > sCollisionMargin*sCollisionMargin) + if ((newPosition - tracer.mEndPos).length2() > sCollisionMargin * sCollisionMargin) { auto direction = velocity; direction.normalize(); newPosition = tracer.mEndPos; - newPosition -= direction*sCollisionMargin; + newPosition -= direction * sCollisionMargin; } osg::Vec3f newVelocity = (velocity * planeNormal <= 0.0) ? reject(velocity, planeNormal) : velocity; bool usedSeamLogic = false; - // check for the current and previous collision planes forming an acute angle; slide along the seam if they do - if(numTimesSlid > 0) + // check for the current and previous collision planes forming an acute angle; slide along the seam if + // they do for this, we want to use the original plane normal, or else certain types of geometry will + // snag + if (numTimesSlid > 0) { - auto dotA = lastSlideNormal * planeNormal; - auto dotB = lastSlideNormalFallback * planeNormal; - if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide + auto dotA = lastSlideNormal * origPlaneNormal; + auto dotB = lastSlideNormalFallback * origPlaneNormal; + if (numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide dotB = 1.0; - if(dotA <= 0.0 || dotB <= 0.0) + if (dotA <= 0.0 || dotB <= 0.0) { osg::Vec3f bestNormal = lastSlideNormal; - // use previous-to-previous collision plane if it's acute with current plane but actual previous plane isn't - if(dotB < dotA) + // use previous-to-previous collision plane if it's acute with current plane but actual previous + // plane isn't + if (dotB < dotA) { bestNormal = lastSlideNormalFallback; lastSlideNormal = lastSlideNormalFallback; } - auto constraintVector = bestNormal ^ planeNormal; // cross product - if(constraintVector.length2() > 0) // only if it's not zero length + auto constraintVector = bestNormal ^ origPlaneNormal; // cross product + if (constraintVector.length2() > 0) // only if it's not zero length { constraintVector.normalize(); newVelocity = project(velocity, constraintVector); // version of surface rejection for acute crevices/seams - auto averageNormal = bestNormal + planeNormal; + auto averageNormal = bestNormal + origPlaneNormal; averageNormal.normalize(); - tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); - newPosition = (newPosition + tracer.mEndPos)/2.0; + tracer.doTrace(actor.mCollisionObject, newPosition, + newPosition + averageNormal * (sCollisionMargin * 2.0), collisionWorld); + newPosition = (newPosition + tracer.mEndPos) / 2.0; usedSeamLogic = true; } @@ -326,207 +327,246 @@ namespace MWPhysics } // otherwise just keep the normal vector rejection - // if this isn't the first iteration, or if the first iteration is also the last iteration, // move away from the collision plane slightly, if possible - // this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings - // this is different from the normal collision margin, because the normal collision margin is along the movement path, - // but this is along the collision normal - if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f)) + // this reduces getting stuck in some concave geometry, like the gaps above the railings in some + // ald'ruhn buildings this is different from the normal collision margin, because the normal collision + // margin is along the movement path, but this is along the collision normal + if (!usedSeamLogic) { - tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); - newPosition = (newPosition + tracer.mEndPos)/2.0; + tracer.doTrace(actor.mCollisionObject, newPosition, + newPosition + planeNormal * (sCollisionMargin * 2.0), collisionWorld); + newPosition = (newPosition + tracer.mEndPos) / 2.0; + } + + // short circuit if we went backwards, but only if it was mostly horizontal and we're on the ground + if (seenGround && newVelocity * origVelocity <= 0.0f) + { + auto perpendicular = newVelocity ^ origVelocity; + if (perpendicular.length2() > 0.0f) + { + perpendicular.normalize(); + if (std::abs(perpendicular.z()) > 0.7071f) + break; + } } // Do not allow sliding up steep slopes if there is gravity. - if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal)) + // The purpose of this is to prevent air control from letting you slide up tall, unwalkable slopes. + // For that purpose, it is not necessary to do it when trying to slide along acute seams/crevices (i.e. + // usedSeamLogic) and doing so would actually break air control in some situations where vanilla allows + // air control. Vanilla actually allows you to slide up slopes as long as you're in the "walking" + // animation, which can be true even in the air, so allowing this for seams isn't a compatibility break. + if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal) && !usedSeamLogic) newVelocity.z() = std::min(newVelocity.z(), velocity.z()); - if (newVelocity * origVelocity <= 0.0f) - break; - numTimesSlid += 1; lastSlideNormalFallback = lastSlideNormal; - lastSlideNormal = planeNormal; + lastSlideNormal = origPlaneNormal; velocity = newVelocity; } } bool isOnGround = false; bool isOnSlope = false; - if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel)) + if (forceGroundTest || (actor.mInertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; - auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0); - osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); - tracer.doTrace(colobj, from, to, collisionWorld); - if(tracer.mFraction < 1.0f) + auto dropDistance = 2 * sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); + osg::Vec3f to = newPosition - osg::Vec3f(0, 0, dropDistance); + tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld, actor.mIsOnGround); + if (tracer.mFraction < 1.0f) { if (!isActor(tracer.mHitObject)) { isOnGround = true; isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); + actor.mStandingOn = tracer.mHitObject; - const btCollisionObject* standingOn = tracer.mHitObject; - PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); - if (ptrHolder) - actor.mStandingOn = ptrHolder->getPtr(); - - if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) - physicActor->setWalkingOnWater(true); + if (actor.mStandingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) + actor.mWalkingOnWater = true; if (!actor.mFlying && !isOnSlope) { - if (tracer.mFraction*dropDistance > sGroundOffset) + if (tracer.mFraction * dropDistance > sGroundOffset) newPosition.z() = tracer.mEndPos.z() + sGroundOffset; else { newPosition.z() = tracer.mEndPos.z(); - tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld); - newPosition = (newPosition+tracer.mEndPos)/2.0; + tracer.doTrace(actor.mCollisionObject, newPosition, + newPosition + osg::Vec3f(0, 0, 2 * sGroundOffset), collisionWorld); + newPosition = (newPosition + tracer.mEndPos) / 2.0; } } } else { // Vanilla allows actors to float on top of other actors. Do not push them off. - if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) && tracer.mEndPos.z()+sGroundOffset <= newPosition.z()) + if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) + && tracer.mEndPos.z() + sGroundOffset <= newPosition.z()) newPosition.z() = tracer.mEndPos.z() + sGroundOffset; isOnGround = false; } } - // forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection - if(physicActor->getStuckFrames() > 0) + // forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things + // can/will break ground detection + if (actor.mStuckFrames > 0) { isOnGround = true; isOnSlope = false; } } - if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) - physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f)); + if ((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) + actor.mInertia = osg::Vec3f(0.f, 0.f, 0.f); else { - inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; - if (inertia.z() < 0) - inertia.z() *= actor.mSlowFall; - if (actor.mSlowFall < 1.f) { - inertia.x() *= actor.mSlowFall; - inertia.y() *= actor.mSlowFall; + actor.mInertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; + if (actor.mInertia.z() < 0) + actor.mInertia.z() *= actor.mSlowFall; + if (actor.mSlowFall < 1.f) + { + actor.mInertia.x() *= actor.mSlowFall; + actor.mInertia.y() *= actor.mSlowFall; } - physicActor->setInertialForce(inertia); } - physicActor->setOnGround(isOnGround); - physicActor->setOnSlope(isOnSlope); + actor.mIsOnGround = isOnGround; + actor.mIsOnSlope = isOnSlope; actor.mPosition = newPosition; // remove what was added earlier in compensating for doTrace not taking interior transformation into account - actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate + actor.mPosition.z() -= actor.mHalfExtentsZ; // vanilla-accurate + } + + void MovementSolver::move(ProjectileFrameData& projectile, float time, const btCollisionWorld* collisionWorld) + { + btVector3 btFrom = Misc::Convert::toBullet(projectile.mPosition); + btVector3 btTo = Misc::Convert::toBullet(projectile.mPosition + projectile.mMovement * time); + + if (btFrom == btTo) + return; + + ProjectileConvexCallback resultCallback( + projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile); + resultCallback.m_collisionFilterMask = CollisionType_AnyPhysical; + resultCallback.m_collisionFilterGroup = CollisionType_Projectile; + + const btQuaternion btrot = btQuaternion::getIdentity(); + btTransform from_(btrot, btFrom); + btTransform to_(btrot, btTo); + + const btCollisionShape* shape = projectile.mCollisionObject->getCollisionShape(); + assert(shape->isConvex()); + collisionWorld->convexSweepTest(static_cast(shape), from_, to_, resultCallback); + + projectile.mPosition + = Misc::Convert::toOsg(projectile.mProjectile->isActive() ? btTo : resultCallback.m_hitPointWorld); } btVector3 addMarginToDelta(btVector3 delta) { - if(delta.length2() == 0.0) + if (delta.length2() == 0.0) return delta; return delta + delta.normalized() * sCollisionMargin; } void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { - const auto& ptr = actor.mActorRaw->getPtr(); - if (!ptr.getClass().isMobile(ptr)) + if (actor.mSkipCollisionDetection) // noclipping/tcl return; - auto* physicActor = actor.mActorRaw; - if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl + if (actor.mMovement.length2() == 0) // no AI nor player attempted to move, current position is assumed correct return; - auto* collisionObject = physicActor->getCollisionObject(); auto tempPosition = actor.mPosition; - if(physicActor->getStuckFrames() >= 10) + if (actor.mStuckFrames >= 10) { - if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100) + if ((actor.mLastStuckPosition - actor.mPosition).length2() < 100) return; else { - physicActor->setStuckFrames(0); - physicActor->setLastStuckPosition({0, 0, 0}); + actor.mStuckFrames = 0; + actor.mLastStuckPosition = { 0, 0, 0 }; } } // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) - // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() - const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); + // if vanilla compatibility didn't matter, the "correct" collision hull position would be + // physicActor->getScaledMeshTranslation() + const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, actor.mHalfExtentsZ); // use a 3d approximation of the movement vector to better judge player intent - auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + auto velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) + * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) + * actor.mMovement; // try to pop outside of the world before doing anything else if we're inside of it - if (!physicActor->getOnGround() || physicActor->getOnSlope()) - velocity += physicActor->getInertialForce(); + if (!actor.mIsOnGround || actor.mIsOnSlope) + velocity += actor.mInertia; // because of the internal collision box offset hack, and the fact that we're moving the collision box manually, // we need to replicate part of the collision box's transform process from scratch osg::Vec3f refPosition = tempPosition + verticalHalfExtent; osg::Vec3f goodPosition = refPosition; - const btTransform oldTransform = collisionObject->getWorldTransform(); + const btTransform oldTransform = actor.mCollisionObject->getWorldTransform(); btTransform newTransform = oldTransform; - auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback - { + auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback { goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset)); newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); - collisionObject->setWorldTransform(newTransform); + actor.mCollisionObject->setWorldTransform(newTransform); - ContactCollectionCallback callback{collisionObject, velocity}; - ContactTestWrapper::contactTest(const_cast(collisionWorld), collisionObject, callback); + ContactCollectionCallback callback{ actor.mCollisionObject, velocity }; + ContactTestWrapper::contactTest( + const_cast(collisionWorld), actor.mCollisionObject, callback); return callback; }; // check whether we're inside the world with our collision box with manually-derived offset - auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); - if(contactCallback.mDistance < -sAllowedPenetration) + auto contactCallback = gatherContacts({ 0.0, 0.0, 0.0 }); + if (contactCallback.mDistance < -sAllowedPenetration) { - physicActor->setStuckFrames(physicActor->getStuckFrames() + 1); - physicActor->setLastStuckPosition(actor.mPosition); + ++actor.mStuckFrames; + actor.mLastStuckPosition = actor.mPosition; // we are; try moving it out of the world auto positionDelta = contactCallback.mContactSum; // limit rejection delta to the largest known individual rejections - if(std::abs(positionDelta.x()) > contactCallback.mMaxX) + if (std::abs(positionDelta.x()) > contactCallback.mMaxX) positionDelta *= contactCallback.mMaxX / std::abs(positionDelta.x()); - if(std::abs(positionDelta.y()) > contactCallback.mMaxY) + if (std::abs(positionDelta.y()) > contactCallback.mMaxY) positionDelta *= contactCallback.mMaxY / std::abs(positionDelta.y()); - if(std::abs(positionDelta.z()) > contactCallback.mMaxZ) + if (std::abs(positionDelta.z()) > contactCallback.mMaxZ) positionDelta *= contactCallback.mMaxZ / std::abs(positionDelta.z()); auto contactCallback2 = gatherContacts(positionDelta); - // successfully moved further out from contact (does not have to be in open space, just less inside of things) - if(contactCallback2.mDistance > contactCallback.mDistance) + // successfully moved further out from contact (does not have to be in open space, just less inside of + // things) + if (contactCallback2.mDistance > contactCallback.mDistance) tempPosition = goodPosition - verticalHalfExtent; // try again but only upwards (fixes some bad coc floors) else { // upwards-only offset - auto contactCallback3 = gatherContacts({0.0, 0.0, std::abs(positionDelta.z())}); + auto contactCallback3 = gatherContacts({ 0.0, 0.0, std::abs(positionDelta.z()) }); // success - if(contactCallback3.mDistance > contactCallback.mDistance) + if (contactCallback3.mDistance > contactCallback.mDistance) tempPosition = goodPosition - verticalHalfExtent; else // try again but fixed distance up { - auto contactCallback4 = gatherContacts({0.0, 0.0, 10.0}); + auto contactCallback4 = gatherContacts({ 0.0, 0.0, 10.0 }); // success - if(contactCallback4.mDistance > contactCallback.mDistance) + if (contactCallback4.mDistance > contactCallback.mDistance) tempPosition = goodPosition - verticalHalfExtent; } } } else { - physicActor->setStuckFrames(0); - physicActor->setLastStuckPosition({0, 0, 0}); + actor.mStuckFrames = 0; + actor.mLastStuckPosition = { 0, 0, 0 }; } - collisionObject->setWorldTransform(oldTransform); + actor.mCollisionObject->setWorldTransform(oldTransform); actor.mPosition = tempPosition; } } diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 90b10c275..e3d6657d2 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -3,8 +3,7 @@ #include -#include "constants.hpp" -#include "../mwworld/ptr.hpp" +#include class btCollisionWorld; @@ -16,33 +15,37 @@ namespace MWWorld namespace MWPhysics { /// Vector projection - static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) + static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f& v) { return v * (u * v); } /// Vector rejection - static inline osg::Vec3f reject(const osg::Vec3f& direction, const osg::Vec3f &planeNormal) + static inline osg::Vec3f reject(const osg::Vec3f& direction, const osg::Vec3f& planeNormal) { return direction - project(direction, planeNormal); } template - static bool isWalkableSlope(const Vec3 &normal) + static bool isWalkableSlope(const Vec3& normal) { - static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); + static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(Constants::sMaxSlope)); return (normal.z() > sMaxSlopeCos); } class Actor; struct ActorFrameData; + struct ProjectileFrameData; struct WorldFrameData; class MovementSolver { public: - static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight); - static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData); + static osg::Vec3f traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, Actor* actor, + btCollisionWorld* collisionWorld, float maxHeight); + static void move( + ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, const WorldFrameData& worldData); + static void move(ProjectileFrameData& projectile, float time, const btCollisionWorld* collisionWorld); static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld); }; } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 5a7f5f044..2eaebfe0f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -1,133 +1,309 @@ +#include "mtphysics.hpp" + +#include +#include +#include +#include +#include +#include +#include + #include #include +#include #include #include "components/debug/debuglog.hpp" -#include #include "components/misc/convert.hpp" #include "components/settings/settings.hpp" +#include + #include "../mwmechanics/actorutil.hpp" -#include "../mwmechanics/movement.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwrender/bulletdebugdraw.hpp" + #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "actor.hpp" #include "contacttestwrapper.h" #include "movementsolver.hpp" -#include "mtphysics.hpp" #include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" -namespace +namespace MWPhysics { - /// @brief A scoped lock that is either shared or exclusive depending on configuration - template - class MaybeSharedLock + namespace { + template + std::optional> makeExclusiveLock(Mutex& mutex, LockingPolicy lockingPolicy) + { + if (lockingPolicy == LockingPolicy::NoLocks) + return {}; + return std::unique_lock(mutex); + } + + /// @brief A scoped lock that is either exclusive or inexistent depending on configuration + template + class MaybeExclusiveLock + { + public: + /// @param mutex a mutex + /// @param threadCount decide wether the excluse lock will be taken + explicit MaybeExclusiveLock(Mutex& mutex, LockingPolicy lockingPolicy) + : mImpl(makeExclusiveLock(mutex, lockingPolicy)) + { + } + + private: + std::optional> mImpl; + }; + + template + std::optional> makeSharedLock(Mutex& mutex, LockingPolicy lockingPolicy) + { + if (lockingPolicy == LockingPolicy::NoLocks) + return {}; + return std::shared_lock(mutex); + } + + /// @brief A scoped lock that is either shared or inexistent depending on configuration + template + class MaybeSharedLock + { public: /// @param mutex a shared mutex - /// @param canBeSharedLock decide wether the lock will be shared or exclusive - MaybeSharedLock(Mutex& mutex, bool canBeSharedLock) : mMutex(mutex), mCanBeSharedLock(canBeSharedLock) + /// @param threadCount decide wether the shared lock will be taken + explicit MaybeSharedLock(Mutex& mutex, LockingPolicy lockingPolicy) + : mImpl(makeSharedLock(mutex, lockingPolicy)) { - if (mCanBeSharedLock) - mMutex.lock_shared(); - else - mMutex.lock(); } - ~MaybeSharedLock() - { - if (mCanBeSharedLock) - mMutex.unlock_shared(); - else - mMutex.unlock(); - } private: - Mutex& mMutex; - bool mCanBeSharedLock; - }; + std::optional> mImpl; + }; - void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed) - { - const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; - - const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mActorRaw->getOnGround()); - - if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1) - actorData.mNeedLand = true; - else if (heightDiff < 0) - actorData.mFallHeight += heightDiff; - } - - void handleJump(const MWWorld::Ptr &ptr) - { - const bool isPlayer = (ptr == MWMechanics::getPlayer()); - // Advance acrobatics and set flag for GetPCJumping - if (isPlayer) + template + std::variant, std::shared_lock> makeLock( + Mutex& mutex, LockingPolicy lockingPolicy) { - ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); - MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); + switch (lockingPolicy) + { + case LockingPolicy::NoLocks: + return std::monostate{}; + case LockingPolicy::ExclusiveLocksOnly: + return std::unique_lock(mutex); + case LockingPolicy::AllowSharedLocks: + return std::shared_lock(mutex); + }; + + throw std::runtime_error("Unsupported LockingPolicy: " + + std::to_string(static_cast>(lockingPolicy))); } - // Decrease fatigue - if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState()) + /// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration + template + class MaybeLock { - const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); - const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); - const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr)); - const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult; - MWMechanics::DynamicStat fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue(); - fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); - ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue); - } - ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; - } + public: + /// @param mutex a shared mutex + /// @param threadCount decide wether the lock will be shared, exclusive or inexistent + explicit MaybeLock(Mutex& mutex, LockingPolicy lockingPolicy) + : mImpl(makeLock(mutex, lockingPolicy)) + { + } - void updateMechanics(MWPhysics::ActorFrameData& actorData) + private: + std::variant, std::shared_lock> mImpl; + }; + } +} + +namespace +{ + bool isUnderWater(const MWPhysics::ActorFrameData& actorData) { - auto ptr = actorData.mActorRaw->getPtr(); - if (actorData.mDidJump) - handleJump(ptr); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - if (actorData.mNeedLand) - stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming)); - else if (actorData.mFallHeight < 0) - stats.addToFallHeight(-actorData.mFallHeight); + return actorData.mPosition.z() < actorData.mSwimLevel; } - osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) + osg::Vec3f interpolateMovements(const MWPhysics::PtrHolder& ptr, float timeAccum, float physicsDt) { const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); - return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); + return ptr.getPosition() * interpolationFactor + ptr.getPreviousPosition() * (1.f - interpolationFactor); } - namespace Config - { - /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading - int computeNumThreads(bool& threadSafeBullet) - { - int wantedThread = Settings::Manager::getInt("async num threads", "Physics"); + using LockedActorSimulation + = std::pair, std::reference_wrapper>; + using LockedProjectileSimulation + = std::pair, std::reference_wrapper>; - auto broad = std::make_unique(); - auto maxSupportedThreads = broad->m_rayTestStacks.size(); - threadSafeBullet = (maxSupportedThreads > 1); - if (!threadSafeBullet && wantedThread > 1) + namespace Visitors + { + template class Lock> + struct WithLockedPtr + { + const Impl& mImpl; + std::shared_mutex& mCollisionWorldMutex; + const MWPhysics::LockingPolicy mLockingPolicy; + + template + void operator()(MWPhysics::SimulationImpl& sim) const { - Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used"; - return 1; + auto locked = sim.lock(); + if (!locked.has_value()) + return; + auto&& [ptr, frameData] = *std::move(locked); + // Locked shared_ptr has to be destructed after releasing mCollisionWorldMutex to avoid + // possible deadlock. Ptr destructor also acquires mCollisionWorldMutex. + const std::pair arg(std::move(ptr), frameData); + const Lock lock(mCollisionWorldMutex, mLockingPolicy); + mImpl(arg); } - return std::max(0, wantedThread); - } + }; + + struct InitPosition + { + const btCollisionWorld* mCollisionWorld; + void operator()(MWPhysics::ActorSimulation& sim) const + { + auto locked = sim.lock(); + if (!locked.has_value()) + return; + auto& [actor, frameDataRef] = *locked; + auto& frameData = frameDataRef.get(); + frameData.mPosition = actor->applyOffsetChange(); + if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel + && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld)) + { + const auto offset = osg::Vec3f(0, 0, frameData.mWaterlevel - frameData.mPosition.z()); + MWBase::Environment::get().getWorld()->moveObjectBy(actor->getPtr(), offset, false); + frameData.mPosition = actor->applyOffsetChange(); + } + actor->updateCollisionObjectPosition(); + frameData.mOldHeight = frameData.mPosition.z(); + const auto rotation = actor->getPtr().getRefData().getPosition().asRotationVec3(); + frameData.mRotation = osg::Vec2f(rotation.x(), rotation.z()); + frameData.mInertia = actor->getInertialForce(); + frameData.mStuckFrames = actor->getStuckFrames(); + frameData.mLastStuckPosition = actor->getLastStuckPosition(); + } + void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const {} + }; + + struct PreStep + { + btCollisionWorld* mCollisionWorld; + void operator()(const LockedActorSimulation& sim) const + { + MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld); + } + void operator()(const LockedProjectileSimulation& /*sim*/) const {} + }; + + struct UpdatePosition + { + btCollisionWorld* mCollisionWorld; + void operator()(const LockedActorSimulation& sim) const + { + auto& [actor, frameDataRef] = sim; + auto& frameData = frameDataRef.get(); + if (actor->setPosition(frameData.mPosition)) + { + frameData.mPosition = actor->getPosition(); // account for potential position change made by script + actor->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); + } + } + void operator()(const LockedProjectileSimulation& sim) const + { + auto& [proj, frameDataRef] = sim; + auto& frameData = frameDataRef.get(); + proj->setPosition(frameData.mPosition); + proj->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(proj->getCollisionObject()); + } + }; + + struct Move + { + const float mPhysicsDt; + const btCollisionWorld* mCollisionWorld; + const MWPhysics::WorldFrameData& mWorldFrameData; + void operator()(const LockedActorSimulation& sim) const + { + MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData); + } + void operator()(const LockedProjectileSimulation& sim) const + { + if (sim.first->isActive()) + MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld); + } + }; + + struct Sync + { + const bool mAdvanceSimulation; + const float mTimeAccum; + const float mPhysicsDt; + const MWPhysics::PhysicsTaskScheduler* scheduler; + void operator()(MWPhysics::ActorSimulation& sim) const + { + auto locked = sim.lock(); + if (!locked.has_value()) + return; + auto& [actor, frameDataRef] = *locked; + auto& frameData = frameDataRef.get(); + auto ptr = actor->getPtr(); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const float heightDiff = frameData.mPosition.z() - frameData.mOldHeight; + const bool isStillOnGround = (mAdvanceSimulation && frameData.mWasOnGround && frameData.mIsOnGround); + + if (isStillOnGround || frameData.mFlying || isUnderWater(frameData) || frameData.mSlowFall < 1) + stats.land(ptr == MWMechanics::getPlayer() && (frameData.mFlying || isUnderWater(frameData))); + else if (heightDiff < 0) + stats.addToFallHeight(-heightDiff); + + actor->setSimulationPosition(::interpolateMovements(*actor, mTimeAccum, mPhysicsDt)); + actor->setLastStuckPosition(frameData.mLastStuckPosition); + actor->setStuckFrames(frameData.mStuckFrames); + if (mAdvanceSimulation) + { + MWWorld::Ptr standingOn; + auto* ptrHolder + = static_cast(scheduler->getUserPointer(frameData.mStandingOn)); + if (ptrHolder) + standingOn = ptrHolder->getPtr(); + actor->setStandingOnPtr(standingOn); + // the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the + // change + if (actor->getOnGround() == frameData.mWasOnGround) + actor->setOnGround(frameData.mIsOnGround); + actor->setOnSlope(frameData.mIsOnSlope); + actor->setWalkingOnWater(frameData.mWalkingOnWater); + actor->setInertialForce(frameData.mInertia); + } + } + void operator()(MWPhysics::ProjectileSimulation& sim) const + { + auto locked = sim.lock(); + if (!locked.has_value()) + return; + auto& [proj, frameData] = *locked; + proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt)); + } + }; } } namespace MWPhysics { +<<<<<<< HEAD PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer) : mDefaultPhysicsDt(physicsDt) , mPhysicsDt(physicsDt) @@ -153,18 +329,137 @@ namespace MWPhysics , mTimeBegin(0) , mTimeEnd(0) , mFrameStart(0) +======= + namespace +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - mNumThreads = Config::computeNumThreads(mThreadSafeBullet); + unsigned getMaxBulletSupportedThreads() + { + auto broad = std::make_unique(); + assert(BT_MAX_THREAD_COUNT > 0); + return std::min(broad->m_rayTestStacks.size(), BT_MAX_THREAD_COUNT - 1); + } + LockingPolicy detectLockingPolicy() + { + if (Settings::Manager::getInt("async num threads", "Physics") < 1) + return LockingPolicy::NoLocks; + if (getMaxBulletSupportedThreads() > 1) + return LockingPolicy::AllowSharedLocks; + Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used"; + return LockingPolicy::ExclusiveLocksOnly; + } + + unsigned getNumThreads(LockingPolicy lockingPolicy) + { + switch (lockingPolicy) + { + case LockingPolicy::NoLocks: + return 0; + case LockingPolicy::ExclusiveLocksOnly: + return 1; + case LockingPolicy::AllowSharedLocks: + return std::clamp( + Settings::Manager::getInt("async num threads", "Physics"), 0, getMaxBulletSupportedThreads()); + } + + throw std::runtime_error("Unsupported LockingPolicy: " + + std::to_string(static_cast>(lockingPolicy))); + } + } + + class PhysicsTaskScheduler::WorkersSync + { + public: + void waitForWorkers() + { + std::unique_lock lock(mWorkersDoneMutex); + if (mFrameCounter != mWorkersFrameCounter) + mWorkersDone.wait(lock); + } + + void wakeUpWorkers() + { + const std::lock_guard lock(mHasJobMutex); + ++mFrameCounter; + mHasJob.notify_all(); + } + + void stopWorkers() + { + const std::lock_guard lock(mHasJobMutex); + mShouldStop = true; + mHasJob.notify_all(); + } + + void workIsDone() + { + const std::lock_guard lock(mWorkersDoneMutex); + ++mWorkersFrameCounter; + mWorkersDone.notify_all(); + } + + template + void runWorker(F&& f) noexcept + { + std::size_t lastFrame = 0; + std::unique_lock lock(mHasJobMutex); + while (!mShouldStop) + { + mHasJob.wait(lock, [&] { return mShouldStop || mFrameCounter != lastFrame; }); + lastFrame = mFrameCounter; + lock.unlock(); + f(); + lock.lock(); + } + } + + private: + std::size_t mWorkersFrameCounter = 0; + std::condition_variable mWorkersDone; + std::mutex mWorkersDoneMutex; + std::condition_variable mHasJob; + bool mShouldStop = false; + std::size_t mFrameCounter = 0; + std::mutex mHasJobMutex; + }; + + PhysicsTaskScheduler::PhysicsTaskScheduler( + float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer) + : mDefaultPhysicsDt(physicsDt) + , mPhysicsDt(physicsDt) + , mTimeAccum(0.f) + , mCollisionWorld(collisionWorld) + , mDebugDrawer(debugDrawer) + , mLockingPolicy(detectLockingPolicy()) + , mNumThreads(getNumThreads(mLockingPolicy)) + , mNumJobs(0) + , mRemainingSteps(0) + , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) + , mAdvanceSimulation(false) + , mNextJob(0) + , mNextLOS(0) + , mFrameNumber(0) + , mTimer(osg::Timer::instance()) + , mPrevStepCount(1) + , mBudget(physicsDt) + , mAsyncBudget(0.0f) + , mBudgetCursor(0) + , mAsyncStartTime(0) + , mTimeBegin(0) + , mTimeEnd(0) + , mFrameStart(0) + , mWorkersSync(mNumThreads >= 1 ? std::make_unique() : nullptr) + { if (mNumThreads >= 1) { - for (int i = 0; i < mNumThreads; ++i) - mThreads.emplace_back([&] { worker(); } ); + Log(Debug::Info) << "Using " << mNumThreads << " async physics threads"; + for (unsigned i = 0; i < mNumThreads; ++i) + mThreads.emplace_back([&] { worker(); }); } else { - mLOSCacheExpiry = -1; - mDeferAabbUpdate = false; + mLOSCacheExpiry = 0; } mPreStepBarrier = std::make_unique(mNumThreads); @@ -177,12 +472,22 @@ namespace MWPhysics PhysicsTaskScheduler::~PhysicsTaskScheduler() { waitForWorkers(); +<<<<<<< HEAD std::unique_lock lock(mSimulationMutex); mQuit = true; mNumJobs = 0; mRemainingSteps = 0; mHasJob.notify_all(); lock.unlock(); +======= + { + MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy); + mNumJobs = 0; + mRemainingSteps = 0; + } + if (mWorkersSync != nullptr) + mWorkersSync->stopWorkers(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 for (auto& thread : mThreads) thread.join(); } @@ -194,11 +499,11 @@ namespace MWPhysics // adjust maximum step count based on whether we're likely physics bottlenecked or not // if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time - // if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget - // if it ends up lower than numSteps and also 1, we will run a single delta time physics step - // if we did not do this, and had a fixed step count limit, - // we would have an unnecessarily low render framerate if we were only physics bottlenecked, - // and we would be unnecessarily invoking true delta time if we were only render bottlenecked + // if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps + // that we expect to be within budget if it ends up lower than numSteps and also 1, we will run a single delta + // time physics step if we did not do this, and had a fixed step count limit, we would have an unnecessarily low + // render framerate if we were only physics bottlenecked, and we would be unnecessarily invoking true delta time + // if we were only render bottlenecked // get physics timing stats float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get()); @@ -211,7 +516,7 @@ namespace MWPhysics maxAllowedSteps = 1; // physics is fairly cheap; limit based on expense if (budgetMeasurement < 0.5) - maxAllowedSteps = std::ceil(1.0/budgetMeasurement); + maxAllowedSteps = std::ceil(1.0 / budgetMeasurement); // limit to a reasonable amount maxAllowedSteps = std::min(10, maxAllowedSteps); @@ -223,7 +528,7 @@ namespace MWPhysics // ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency // this causes interpolation to 100% use the most recent physics result when true delta time is happening // and we deliberately simulate up to exactly the timestamp that we want to render - actualDelta = timeAccum/float(numSteps+1); + actualDelta = timeAccum / float(numSteps + 1); // actually: if this results in a per-step delta less than the target physics steptime, clamp it // this might reintroduce some stutter, but only comes into play in obscure cases // (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun) @@ -233,59 +538,64 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - const std::vector& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, std::vector& simulations, + osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + { + assert(mSimulations != &simulations); + + waitForWorkers(); + prepareWork(timeAccum, simulations, frameStart, frameNumber, stats); + if (mWorkersSync != nullptr) + mWorkersSync->wakeUpWorkers(); + } + + void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector& simulations, + osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { waitForWorkers(); // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. +<<<<<<< HEAD std::unique_lock lock(mSimulationMutex); +======= + + MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 double timeStart = mTimer->tick(); - mMovedActors.clear(); - // start by finishing previous background computation if (mNumThreads != 0) { - for (auto& data : mActorsFrameData) - { - const auto actorActive = [&data](const auto& newFrameData) -> bool - { - const auto actor = data.mActor.lock(); - return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr(); - }; - // Only return actors that are still part of the scene - if (std::any_of(actorsData.begin(), actorsData.end(), actorActive)) - { - updateMechanics(data); + syncWithMainThread(); - // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values - if (mAdvanceSimulation) - data.mActorRaw->setStandingOnPtr(data.mStandingOn); - data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); - mMovedActors.emplace_back(data.mActorRaw->getPtr()); - } - } - if(mAdvanceSimulation) + if (mAdvanceSimulation) mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); updateStats(frameStart, frameNumber, stats); } auto [numSteps, newDelta] = calculateStepConfig(timeAccum); - timeAccum -= numSteps*newDelta; + timeAccum -= numSteps * newDelta; // init - for (auto& data : actorsData) - data.updatePosition(mCollisionWorld); + const Visitors::InitPosition vis{ mCollisionWorld }; + for (auto& sim : simulations) + { + std::visit(vis, sim); + } mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; mPhysicsDt = newDelta; - mActorsFrameData = std::move(actorsData); + mSimulations = &simulations; mAdvanceSimulation = (mRemainingSteps != 0); +<<<<<<< HEAD ++mFrameCounter; mNumJobs = mActorsFrameData.size(); +======= + mNumJobs = mSimulations->size(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mNextLOS.store(0, std::memory_order_relaxed); mNextJob.store(0, std::memory_order_release); @@ -297,58 +607,69 @@ namespace MWPhysics if (mNumThreads == 0) { - syncComputation(); - if(mAdvanceSimulation) + doSimulation(); + syncWithMainThread(); + if (mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor); - return mMovedActors; + return; } mAsyncStartTime = mTimer->tick(); +<<<<<<< HEAD mHasJob.notify_all(); lock.unlock(); +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); - return mMovedActors; } - const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) + void PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { waitForWorkers(); +<<<<<<< HEAD std::unique_lock lock(mSimulationMutex); +======= + MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); - mMovedActors.clear(); - mActorsFrameData.clear(); + if (mSimulations != nullptr) + { + mSimulations->clear(); + mSimulations = nullptr; + } for (const auto& [_, actor] : actors) { actor->updatePosition(); actor->updateCollisionObjectPosition(); - mMovedActors.emplace_back(actor->getPtr()); } - return mMovedActors; } - void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const + void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, + btCollisionWorld::RayResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback); } - void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const + void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, + const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback); } - void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) + void PhysicsTaskScheduler::contactTest( + btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy); ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeLock lock(mCollisionWorldMutex, mLockingPolicy); // target the collision object's world origin, this should be the center of the collision object btTransform rayTo; rayTo.setIdentity(); @@ -356,72 +677,72 @@ namespace MWPhysics btCollisionWorld::ClosestRayResultCallback cb(from.getOrigin(), rayTo.getOrigin()); - mCollisionWorld->rayTestSingle(from, rayTo, target, target->getCollisionShape(), target->getWorldTransform(), cb); + mCollisionWorld->rayTestSingle( + from, rayTo, target, target->getCollisionShape(), target->getWorldTransform(), cb); if (!cb.hasHit()) // didn't hit the target. this could happen if point is already inside the collision box return std::nullopt; - return {cb.m_hitPointWorld}; + return { cb.m_hitPointWorld }; } - void PhysicsTaskScheduler::aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback) + void PhysicsTaskScheduler::aabbTest( + const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); } void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy); obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max); } void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask) { - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy); collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask; } - void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) + void PhysicsTaskScheduler::addCollisionObject( + btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) { - std::unique_lock lock(mCollisionWorldMutex); + mCollisionObjects.insert(collisionObject); + MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask); } void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject) { - std::unique_lock lock(mCollisionWorldMutex); + mCollisionObjects.erase(collisionObject); + MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->removeCollisionObject(collisionObject); } - void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr, bool immediate) + void PhysicsTaskScheduler::updateSingleAabb(const std::shared_ptr& ptr, bool immediate) { - if (!mDeferAabbUpdate || immediate) + if (immediate || mNumThreads == 0) { updatePtrAabb(ptr); } else { - std::unique_lock lock(mUpdateAabbMutex); - mUpdateAabb.insert(std::move(ptr)); + MaybeExclusiveLock lock(mUpdateAabbMutex, mLockingPolicy); + mUpdateAabb.insert(ptr); } } - bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2) + bool PhysicsTaskScheduler::getLineOfSight( + const std::shared_ptr& actor1, const std::shared_ptr& actor2) { - std::unique_lock lock(mLOSCacheMutex); - - auto actorPtr1 = actor1.lock(); - auto actorPtr2 = actor2.lock(); - if (!actorPtr1 || !actorPtr2) - return false; + MaybeExclusiveLock lock(mLOSCacheMutex, mLockingPolicy); auto req = LOSRequest(actor1, actor2); auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req); if (result == mLOSCache.end()) { - req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get()); - if (mLOSCacheExpiry >= 0) - mLOSCache.push_back(req); + req.mResult = hasLineOfSight(actor1.get(), actor2.get()); + mLOSCache.push_back(req); return req.mResult; } result->mAge = 0; @@ -430,7 +751,7 @@ namespace MWPhysics void PhysicsTaskScheduler::refreshLOSCache() { - std::shared_lock lock(mLOSCacheMutex); + MaybeSharedLock lock(mLOSCacheMutex, mLockingPolicy); int job = 0; int numLOS = mLOSCache.size(); while ((job = mNextLOS.fetch_add(1, std::memory_order_relaxed)) < numLOS) @@ -444,42 +765,42 @@ namespace MWPhysics else req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get()); } - } void PhysicsTaskScheduler::updateAabbs() { - std::scoped_lock lock(mUpdateAabbMutex); - std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), - [this](const std::weak_ptr& ptr) { updatePtrAabb(ptr); }); + MaybeExclusiveLock lock(mUpdateAabbMutex, mLockingPolicy); + std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), [this](const std::weak_ptr& ptr) { + auto p = ptr.lock(); + if (p != nullptr) + updatePtrAabb(p); + }); mUpdateAabb.clear(); } - void PhysicsTaskScheduler::updatePtrAabb(const std::weak_ptr& ptr) + void PhysicsTaskScheduler::updatePtrAabb(const std::shared_ptr& ptr) { - if (const auto p = ptr.lock()) + MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy); + if (const auto actor = std::dynamic_pointer_cast(ptr)) { - std::scoped_lock lock(mCollisionWorldMutex); - if (const auto actor = std::dynamic_pointer_cast(p)) - { - actor->updateCollisionObjectPosition(); - mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); - } - else if (const auto object = std::dynamic_pointer_cast(p)) - { - object->commitPositionChange(); - mCollisionWorld->updateSingleAabb(object->getCollisionObject()); - } - else if (const auto projectile = std::dynamic_pointer_cast(p)) - { - projectile->commitPositionChange(); - mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); - } - }; + actor->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); + } + else if (const auto object = std::dynamic_pointer_cast(ptr)) + { + object->commitPositionChange(); + mCollisionWorld->updateSingleAabb(object->getCollisionObject()); + } + else if (const auto projectile = std::dynamic_pointer_cast(ptr)) + { + projectile->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); + } } void PhysicsTaskScheduler::worker() { +<<<<<<< HEAD std::size_t lastFrame = 0; std::shared_lock lock(mSimulationMutex); while (!mQuit) @@ -518,62 +839,56 @@ namespace MWPhysics mPostSimBarrier->wait([this] { afterPostSim(); }); } } +======= + mWorkersSync->runWorker([this] { + std::shared_lock lock(mSimulationMutex); + doSimulation(); + }); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } void PhysicsTaskScheduler::updateActorsPositions() { - for (auto& actorData : mActorsFrameData) - { - if(const auto actor = actorData.mActor.lock()) - { - if (actor->setPosition(actorData.mPosition)) - { - std::scoped_lock lock(mCollisionWorldMutex); - actorData.mPosition = actor->getPosition(); // account for potential position change made by script - actor->updateCollisionObjectPosition(); - mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); - } - } - } + const Visitors::UpdatePosition impl{ mCollisionWorld }; + const Visitors::WithLockedPtr vis{ impl, mCollisionWorldMutex, + mLockingPolicy }; + for (Simulation& sim : *mSimulations) + std::visit(vis, sim); } bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) { - btVector3 pos1 = Misc::Convert::toBullet(actor1->getCollisionObjectPosition() + osg::Vec3f(0,0,actor1->getHalfExtents().z() * 0.9)); // eye level - btVector3 pos2 = Misc::Convert::toBullet(actor2->getCollisionObjectPosition() + osg::Vec3f(0,0,actor2->getHalfExtents().z() * 0.9)); + btVector3 pos1 = Misc::Convert::toBullet( + actor1->getCollisionObjectPosition() + osg::Vec3f(0, 0, actor1->getHalfExtents().z() * 0.9)); // eye level + btVector3 pos2 = Misc::Convert::toBullet( + actor2->getCollisionObjectPosition() + osg::Vec3f(0, 0, actor2->getHalfExtents().z() * 0.9)); btCollisionWorld::ClosestRayResultCallback resultCallback(pos1, pos2); - resultCallback.m_collisionFilterGroup = 0xFF; - resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; + resultCallback.m_collisionFilterGroup = CollisionType_AnyPhysical; + resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Door; - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); + MaybeLock lockColWorld(mCollisionWorldMutex, mLockingPolicy); mCollisionWorld->rayTest(pos1, pos2, resultCallback); return !resultCallback.hasHit(); } - void PhysicsTaskScheduler::syncComputation() + void PhysicsTaskScheduler::doSimulation() { - while (mRemainingSteps--) + while (mRemainingSteps) { - for (auto& actorData : mActorsFrameData) - { - MovementSolver::unstuck(actorData, mCollisionWorld); - MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData); - } + mPreStepBarrier->wait([this] { afterPreStep(); }); + int job = 0; + const Visitors::Move impl{ mPhysicsDt, mCollisionWorld, *mWorldFrameData }; + const Visitors::WithLockedPtr vis{ impl, mCollisionWorldMutex, mLockingPolicy }; + while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) + std::visit(vis, (*mSimulations)[job]); - updateActorsPositions(); + mPostStepBarrier->wait([this] { afterPostStep(); }); } - for (auto& actorData : mActorsFrameData) - { - handleFall(actorData, mAdvanceSimulation); - actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); - updateMechanics(actorData); - mMovedActors.emplace_back(actorData.mActorRaw->getPtr()); - if (mAdvanceSimulation) - actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); - } + refreshLOSCache(); + mPostSimBarrier->wait([this] { afterPostSim(); }); } void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) @@ -593,22 +908,49 @@ namespace MWPhysics void PhysicsTaskScheduler::debugDraw() { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy); mDebugDrawer->step(); } + void* PhysicsTaskScheduler::getUserPointer(const btCollisionObject* object) const + { + auto it = mCollisionObjects.find(object); + if (it == mCollisionObjects.end()) + return nullptr; + return (*it)->getUserPointer(); + } + + void PhysicsTaskScheduler::releaseSharedStates() + { + waitForWorkers(); + std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex); + if (mSimulations != nullptr) + { + mSimulations->clear(); + mSimulations = nullptr; + } + mUpdateAabb.clear(); + } + void PhysicsTaskScheduler::afterPreStep() { - if (mDeferAabbUpdate) - updateAabbs(); + updateAabbs(); if (!mRemainingSteps) return; +<<<<<<< HEAD for (auto& data : mActorsFrameData) if (const auto actor = data.mActor.lock()) { std::unique_lock lock(mCollisionWorldMutex); MovementSolver::unstuck(data, mCollisionWorld); } +======= + const Visitors::PreStep impl{ mCollisionWorld }; + const Visitors::WithLockedPtr vis{ impl, mCollisionWorldMutex, + mLockingPolicy }; + for (auto& sim : *mSimulations) + std::visit(vis, sim); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } void PhysicsTaskScheduler::afterPostStep() @@ -623,18 +965,36 @@ namespace MWPhysics void PhysicsTaskScheduler::afterPostSim() { +<<<<<<< HEAD if (mLOSCacheExpiry >= 0) +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - std::unique_lock lock(mLOSCacheMutex); + MaybeExclusiveLock lock(mLOSCacheMutex, mLockingPolicy); mLOSCache.erase( - std::remove_if(mLOSCache.begin(), mLOSCache.end(), - [](const LOSRequest& req) { return req.mStale; }), - mLOSCache.end()); + std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), + mLOSCache.end()); } mTimeEnd = mTimer->tick(); +<<<<<<< HEAD std::unique_lock lock(mWorkersDoneMutex); ++mWorkersFrameCounter; mWorkersDone.notify_all(); +======= + if (mWorkersSync != nullptr) + mWorkersSync->workIsDone(); + } + + void PhysicsTaskScheduler::syncWithMainThread() + { + if (mSimulations == nullptr) + return; + const Visitors::Sync vis{ mAdvanceSimulation, mTimeAccum, mPhysicsDt, this }; + for (auto& sim : *mSimulations) + std::visit(vis, sim); + mSimulations->clear(); + mSimulations = nullptr; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } // Attempt to acquire unique lock on mSimulationMutex while not all worker @@ -645,10 +1005,15 @@ namespace MWPhysics // https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks void PhysicsTaskScheduler::waitForWorkers() { +<<<<<<< HEAD if (mNumThreads == 0) return; std::unique_lock lock(mWorkersDoneMutex); if (mFrameCounter != mWorkersFrameCounter) mWorkersDone.wait(lock); +======= + if (mWorkersSync != nullptr) + mWorkersSync->waitForWorkers(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index d2fdafd1b..dfb0ed6ee 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -6,14 +6,15 @@ #include #include #include +#include #include #include +#include "components/misc/budgetmeasurement.hpp" #include "physicssystem.hpp" #include "ptrholder.hpp" -#include "components/misc/budgetmeasurement.hpp" namespace Misc { @@ -27,35 +28,49 @@ namespace MWRender namespace MWPhysics { + enum class LockingPolicy + { + NoLocks, + ExclusiveLocksOnly, + AllowSharedLocks, + }; + class PhysicsTaskScheduler { - public: - PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer); - ~PhysicsTaskScheduler(); + public: + PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer); + ~PhysicsTaskScheduler(); - /// @brief move actors taking into account desired movements and collisions - /// @param numSteps how much simulation step to run - /// @param timeAccum accumulated time from previous run to interpolate movements - /// @param actorsData per actor data needed to compute new positions - /// @return new position of each actor - const std::vector& moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + /// @brief move actors taking into account desired movements and collisions + /// @param numSteps how much simulation step to run + /// @param timeAccum accumulated time from previous run to interpolate movements + /// @param actorsData per actor data needed to compute new positions + /// @return new position of each actor + void applyQueuedMovements(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + unsigned int frameNumber, osg::Stats& stats); - const std::vector& resetSimulation(const ActorMap& actors); + void resetSimulation(const ActorMap& actors); - // Thread safe wrappers - void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; - void convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const; - void contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback); - std::optional getHitPoint(const btTransform& from, btCollisionObject* target); - void aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback); - void getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max); - void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask); - void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); - void removeCollisionObject(btCollisionObject* collisionObject); - void updateSingleAabb(std::weak_ptr ptr, bool immediate=false); - bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); - void debugDraw(); + // Thread safe wrappers + void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, + btCollisionWorld::RayResultCallback& resultCallback) const; + void convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, + btCollisionWorld::ConvexResultCallback& resultCallback) const; + void contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback); + std::optional getHitPoint(const btTransform& from, btCollisionObject* target); + void aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback); + void getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max); + void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask); + void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); + void removeCollisionObject(btCollisionObject* collisionObject); + void updateSingleAabb(const std::shared_ptr& ptr, bool immediate = false); + bool getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2); + void debugDraw(); + void* getUserPointer(const btCollisionObject* object) const; + void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from + // ~PhysicsTaskScheduler() +<<<<<<< HEAD private: void syncComputation(); void worker(); @@ -91,12 +106,40 @@ namespace MWPhysics MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; std::set, std::owner_less>> mUpdateAabb; +======= + private: + class WorkersSync; - // TODO: use std::experimental::flex_barrier or std::barrier once it becomes a thing - std::unique_ptr mPreStepBarrier; - std::unique_ptr mPostStepBarrier; - std::unique_ptr mPostSimBarrier; + void doSimulation(); + void worker(); + void updateActorsPositions(); + bool hasLineOfSight(const Actor* actor1, const Actor* actor2); + void refreshLOSCache(); + void updateAabbs(); + void updatePtrAabb(const std::shared_ptr& ptr); + void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + std::tuple calculateStepConfig(float timeAccum) const; + void afterPreStep(); + void afterPostStep(); + void afterPostSim(); + void syncWithMainThread(); + void waitForWorkers(); + void prepareWork(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + unsigned int frameNumber, osg::Stats& stats); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 + std::unique_ptr mWorldFrameData; + std::vector* mSimulations = nullptr; + std::unordered_set mCollisionObjects; + float mDefaultPhysicsDt; + float mPhysicsDt; + float mTimeAccum; + btCollisionWorld* mCollisionWorld; + MWRender::DebugDrawer* mDebugDrawer; + std::vector mLOSCache; + std::set, std::owner_less>> mUpdateAabb; + +<<<<<<< HEAD int mNumThreads; int mNumJobs; int mRemainingSteps; @@ -119,18 +162,41 @@ namespace MWPhysics mutable std::shared_mutex mLOSCacheMutex; mutable std::mutex mUpdateAabbMutex; std::condition_variable_any mHasJob; +======= + // TODO: use std::experimental::flex_barrier or std::barrier once it becomes a thing + std::unique_ptr mPreStepBarrier; + std::unique_ptr mPostStepBarrier; + std::unique_ptr mPostSimBarrier; - unsigned int mFrameNumber; - const osg::Timer* mTimer; + LockingPolicy mLockingPolicy; + unsigned mNumThreads; + int mNumJobs; + int mRemainingSteps; + int mLOSCacheExpiry; + bool mAdvanceSimulation; + std::atomic mNextJob; + std::atomic mNextLOS; + std::vector mThreads; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - int mPrevStepCount; - Misc::BudgetMeasurement mBudget; - Misc::BudgetMeasurement mAsyncBudget; - unsigned int mBudgetCursor; - osg::Timer_t mAsyncStartTime; - osg::Timer_t mTimeBegin; - osg::Timer_t mTimeEnd; - osg::Timer_t mFrameStart; + mutable std::shared_mutex mSimulationMutex; + mutable std::shared_mutex mCollisionWorldMutex; + mutable std::shared_mutex mLOSCacheMutex; + mutable std::mutex mUpdateAabbMutex; + + unsigned int mFrameNumber; + const osg::Timer* mTimer; + + int mPrevStepCount; + Misc::BudgetMeasurement mBudget; + Misc::BudgetMeasurement mAsyncBudget; + unsigned int mBudgetCursor; + osg::Timer_t mAsyncStartTime; + osg::Timer_t mTimeBegin; + osg::Timer_t mTimeEnd; + osg::Timer_t mFrameStart; + + std::unique_ptr mWorkersSync; }; } diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 1e69136ab..120926179 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -1,30 +1,33 @@ #include "object.hpp" #include "mtphysics.hpp" +#include #include +#include #include #include #include -#include #include -#include #include namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) - : mShapeInstance(shapeInstance) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, + osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) + : PtrHolder(ptr, osg::Vec3f()) + , mShapeInstance(std::move(shapeInstance)) , mSolid(true) + , mScale(ptr.getCellRef().getScale(), ptr.getCellRef().getScale(), ptr.getCellRef().getScale()) + , mPosition(ptr.getRefData().getPosition().asVec3()) + , mRotation(rotation) , mTaskScheduler(scheduler) { - mPtr = ptr; - - mCollisionObject = std::make_unique(); - mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); - + mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(), + Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation)); mCollisionObject->setUserPointer(this); +<<<<<<< HEAD setScale(ptr.getCellRef().getScale()); setRotation(ptr.getRefData().getBaseNode()->getAttitude()); @@ -32,6 +35,11 @@ namespace MWPhysics commitPositionChange(); mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); +======= + mShapeInstance->setLocalScaling(mScale); + mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, + CollisionType_Actor | CollisionType_HeightMap | CollisionType_Projectile); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } Object::~Object() @@ -47,11 +55,15 @@ namespace MWPhysics void Object::setScale(float scale) { std::unique_lock lock(mPositionMutex); - mScale = { scale,scale,scale }; + mScale = { scale, scale, scale }; mScaleUpdatePending = true; } +<<<<<<< HEAD void Object::setRotation(const osg::Quat& quat) +======= + void Object::setRotation(osg::Quat quat) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { std::unique_lock lock(mPositionMutex); mRotation = quat; @@ -83,16 +95,6 @@ namespace MWPhysics } } - btCollisionObject* Object::getCollisionObject() - { - return mCollisionObject.get(); - } - - const btCollisionObject* Object::getCollisionObject() const - { - return mCollisionObject.get(); - } - btTransform Object::getTransform() const { std::unique_lock lock(mPositionMutex); @@ -114,7 +116,7 @@ namespace MWPhysics bool Object::isAnimated() const { - return !mShapeInstance->mAnimatedShapes.empty(); + return mShapeInstance->isAnimated(); } bool Object::animateCollisionShapes() @@ -122,9 +124,10 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - assert (mShapeInstance->getCollisionShape()->isCompound()); + assert(mShapeInstance->mCollisionShape->isCompound()); - btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); + btCompoundShape* compound = static_cast(mShapeInstance->mCollisionShape.get()); + bool result = false; for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes) { auto nodePathFound = mRecIndexToNodePath.find(recIndex); @@ -134,7 +137,8 @@ namespace MWPhysics mPtr.getRefData().getBaseNode()->accept(visitor); if (!visitor.mFound) { - Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId(); + Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " + << mPtr.getCellRef().getRefId(); // Remove nonexistent nodes from animated shapes map and early out mShapeInstance->mAnimatedShapes.erase(recIndex); @@ -151,15 +155,18 @@ namespace MWPhysics btTransform transform; transform.setOrigin(Misc::Convert::toBullet(matrix.getTrans()) * compound->getLocalScaling()); - for (int i=0; i<3; ++i) - for (int j=0; j<3; ++j) - transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + transform.getBasis()[i][j] = matrix(j, i); // NB column/row major difference // Note: we can not apply scaling here for now since we treat scaled shapes // as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now if (!(transform == compound->getChildTransform(shapeIndex))) + { compound->updateChildTransform(shapeIndex, transform); + result = true; + } } - return true; + return result; } } diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index c640318c7..b617daef6 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -7,7 +7,6 @@ #include #include -#include #include namespace Resource @@ -15,9 +14,12 @@ namespace Resource class BulletShapeInstance; } +<<<<<<< HEAD class btCollisionObject; class btVector3; +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 namespace MWPhysics { class PhysicsTaskScheduler; @@ -25,16 +27,19 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, + int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); +<<<<<<< HEAD void setRotation(const osg::Quat& quat); +======= + void setRotation(osg::Quat quat); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 void updatePosition(); void commitPositionChange(); - btCollisionObject* getCollisionObject(); - const btCollisionObject* getCollisionObject() const; btTransform getTransform() const; /// Return solid flag. Not used by the object itself, true by default. bool isSolid() const; @@ -45,15 +50,19 @@ namespace MWPhysics bool animateCollisionShapes(); private: - std::unique_ptr mCollisionObject; osg::ref_ptr mShapeInstance; std::map mRecIndexToNodePath; bool mSolid; btVector3 mScale; osg::Vec3f mPosition; osg::Quat mRotation; +<<<<<<< HEAD bool mScaleUpdatePending; bool mTransformUpdatePending; +======= + bool mScaleUpdatePending = false; + bool mTransformUpdatePending = false; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mutable std::mutex mPositionMutex; PhysicsTaskScheduler* mTaskScheduler; }; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8265fef9b..2df1ddeb0 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1,83 +1,101 @@ #include "physicssystem.hpp" -#include -#include +#include #include +#include + #include #include #include -#include -#include -#include -#include +#include #include #include #include -#include +#include +#include +#include #include +#include -#include -#include -#include #include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include -#include // FindRecIndexVisitor - -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" -#include "collisiontype.hpp" #include "actor.hpp" +#include "collisiontype.hpp" -#include "projectile.hpp" -#include "trace.h" -#include "object.hpp" -#include "heightfield.hpp" -#include "hasspherecollisioncallback.hpp" -#include "deepestnotmecontacttestresultcallback.hpp" #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" -#include "projectileconvexcallback.hpp" +#include "deepestnotmecontacttestresultcallback.hpp" +#include "hasspherecollisioncallback.hpp" +#include "heightfield.hpp" #include "movementsolver.hpp" #include "mtphysics.hpp" +#include "object.hpp" +#include "projectile.hpp" namespace { - bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world) + void handleJump(const MWWorld::Ptr& ptr) { - if (!physicActor) - return false; - const float halfZ = physicActor->getHalfExtents().z(); - const osg::Vec3f actorPosition = physicActor->getPosition(); - const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); - const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); - MWPhysics::ActorTracer tracer; - tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); - return (tracer.mFraction >= 1.0f); + if (!ptr.getClass().isActor()) + return; + if (ptr.getClass().getMovementSettings(ptr).mPosition[2] == 0) + return; + const bool isPlayer = (ptr == MWMechanics::getPlayer()); + // Advance acrobatics and set flag for GetPCJumping + if (isPlayer) + { + ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); + MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); + } + + // Decrease fatigue + if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState()) + { + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); + const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); + const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr)); + const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult; + MWMechanics::DynamicStat fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue); + } + ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; } + } namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) - : mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) + : mShapeManager(std::make_unique( + resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) @@ -93,21 +111,22 @@ namespace MWPhysics mDispatcher = std::make_unique(mCollisionConfiguration.get()); mBroadphase = std::make_unique(); - mCollisionWorld = std::make_unique(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); + mCollisionWorld + = std::make_unique(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get()); // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. - // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. + // Should a "static" object ever be moved, we have to update its AABB manually using + // DynamicsWorld::updateSingleAabb. mCollisionWorld->setForceUpdateAllAabbs(false); // Check if a user decided to override a physics system FPS - const char* env = getenv("OPENMW_PHYSICS_FPS"); - if (env) + if (const char* env = getenv("OPENMW_PHYSICS_FPS")) { - float physFramerate = std::atof(env); - if (physFramerate > 0) + if (const auto physFramerate = Misc::StringUtils::toNumeric(env); + physFramerate.has_value() && *physFramerate > 0) { - mPhysicsDt = 1.f / physFramerate; - Log(Debug::Warning) << "Warning: using custom physics framerate (" << physFramerate << " FPS)."; + mPhysicsDt = 1.f / *physFramerate; + Log(Debug::Warning) << "Warning: using custom physics framerate (" << *physFramerate << " FPS)."; } } @@ -122,18 +141,14 @@ namespace MWPhysics if (mWaterCollisionObject) mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); + mTaskScheduler->releaseSharedStates(); mHeightFields.clear(); mObjects.clear(); mActors.clear(); mProjectiles.clear(); } - void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) - { - mUnrefQueue = unrefQueue; - } - - Resource::BulletShapeManager *PhysicsSystem::getShapeManager() + Resource::BulletShapeManager* PhysicsSystem::getShapeManager() { return mShapeManager.get(); } @@ -147,26 +162,26 @@ namespace MWPhysics return mDebugDrawEnabled; } - void PhysicsSystem::markAsNonSolid(const MWWorld::ConstPtr &ptr) + void PhysicsSystem::markAsNonSolid(const MWWorld::ConstPtr& ptr) { - ObjectMap::iterator found = mObjects.find(ptr); + ObjectMap::iterator found = mObjects.find(ptr.mRef); if (found == mObjects.end()) return; found->second->setSolid(false); } - bool PhysicsSystem::isOnSolidGround (const MWWorld::Ptr& actor) const + bool PhysicsSystem::isOnSolidGround(const MWWorld::Ptr& actor) const { const Actor* physactor = getActor(actor); - if (!physactor || !physactor->getOnGround()) + if (!physactor || !physactor->getOnGround() || !physactor->getCollisionMode()) return false; const auto obj = physactor->getStandingOnPtr(); if (obj.isEmpty()) return true; // assume standing on terrain (which is a non-object, so not collision tracked) - ObjectMap::const_iterator foundObj = mObjects.find(obj); + ObjectMap::const_iterator foundObj = mObjects.find(obj.mRef); if (foundObj == mObjects.end()) return false; @@ -201,13 +216,12 @@ namespace MWPhysics */ std::pair PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor, - const osg::Vec3f &origin, - const osg::Quat &orient, - float queryDistance, std::vector& targets) + const osg::Vec3f& origin, const osg::Quat& orient, float queryDistance, std::vector& targets) { // First of all, try to hit where you aim to int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; - RayCastingResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, hitmask, CollisionType_Actor); + RayCastingResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, + targets, hitmask, CollisionType_Actor); if (result.mHit) { @@ -216,15 +230,16 @@ namespace MWPhysics } // Use cone shape as fallback - const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& store + = MWBase::Environment::get().getESMStore()->get(); - btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->mValue.getFloat()/2.0f), queryDistance); - shape.setLocalScaling(btVector3(1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->mValue.getFloat()/2.0f) / - shape.getRadius())); + btConeShape shape(osg::DegreesToRadians(store.find("fCombatAngleXY")->mValue.getFloat() / 2.0f), queryDistance); + shape.setLocalScaling(btVector3( + 1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->mValue.getFloat() / 2.0f) / shape.getRadius())); // The shape origin is its center, so we have to move it forward by half the length. The // real origin will be provided to getFilteredContact to find the closest. - osg::Vec3f center = origin + (orient * osg::Vec3f(0.0f, queryDistance*0.5f, 0.0f)); + osg::Vec3f center = origin + (orient * osg::Vec3f(0.0f, queryDistance * 0.5f, 0.0f)); btCollisionObject object; object.setCollisionShape(&shape); @@ -247,9 +262,11 @@ namespace MWPhysics } } - DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, Misc::Convert::toBullet(origin)); + DeepestNotMeContactTestResultCallback resultCallback( + me, targetCollisionObjects, Misc::Convert::toBullet(origin)); resultCallback.m_collisionFilterGroup = CollisionType_Actor; - resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; + resultCallback.m_collisionFilterMask + = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; mTaskScheduler->contactTest(&object, resultCallback); if (resultCallback.mObject) @@ -264,7 +281,7 @@ namespace MWPhysics return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); } - float PhysicsSystem::getHitDistance(const osg::Vec3f &point, const MWWorld::ConstPtr &target) const + float PhysicsSystem::getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const { btCollisionObject* targetCollisionObj = nullptr; const Actor* actor = getActor(target); @@ -285,7 +302,8 @@ namespace MWPhysics return 0.f; } - RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const + RayCastingResult PhysicsSystem::castRay(const osg::Vec3f& from, const osg::Vec3f& to, + const MWWorld::ConstPtr& ignore, const std::vector& targets, int mask, int group) const { if (from == to) { @@ -314,7 +332,7 @@ namespace MWPhysics if (!targets.empty()) { - for (MWWorld::Ptr& target : targets) + for (const MWWorld::Ptr& target : targets) { const Actor* actor = getActor(target); if (actor) @@ -340,17 +358,19 @@ namespace MWPhysics return result; } - RayCastingResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius) const + RayCastingResult PhysicsSystem::castSphere( + const osg::Vec3f& from, const osg::Vec3f& to, float radius, int mask, int group) const { - btCollisionWorld::ClosestConvexResultCallback callback(Misc::Convert::toBullet(from), Misc::Convert::toBullet(to)); - callback.m_collisionFilterGroup = 0xff; - callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; + btCollisionWorld::ClosestConvexResultCallback callback( + Misc::Convert::toBullet(from), Misc::Convert::toBullet(to)); + callback.m_collisionFilterGroup = group; + callback.m_collisionFilterMask = mask; btSphereShape shape(radius); const btQuaternion btrot = btQuaternion::getIdentity(); - btTransform from_ (btrot, Misc::Convert::toBullet(from)); - btTransform to_ (btrot, Misc::Convert::toBullet(to)); + btTransform from_(btrot, Misc::Convert::toBullet(from)); + btTransform to_(btrot, Misc::Convert::toBullet(to)); mTaskScheduler->convexSweepTest(&shape, from_, to_, callback); @@ -360,35 +380,38 @@ namespace MWPhysics { result.mHitPos = Misc::Convert::toOsg(callback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(callback.m_hitNormalWorld); + if (auto* ptrHolder = static_cast(callback.m_hitCollisionObject->getUserPointer())) + result.mHitObject = ptrHolder->getPtr(); } return result; } - bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const + bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const { - const auto getWeakPtr = [&](const MWWorld::ConstPtr &ptr) -> std::weak_ptr - { - const auto found = mActors.find(ptr); - if (found != mActors.end()) - return { found->second }; - return {}; - }; + if (actor1 == actor2) + return true; - return mTaskScheduler->getLineOfSight(getWeakPtr(actor1), getWeakPtr(actor2)); + const auto it1 = mActors.find(actor1.mRef); + const auto it2 = mActors.find(actor2.mRef); + if (it1 == mActors.end() || it2 == mActors.end()) + return false; + + return mTaskScheduler->getLineOfSight(it1->second, it2->second); } - bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) + bool PhysicsSystem::isOnGround(const MWWorld::Ptr& actor) { Actor* physactor = getActor(actor); - return physactor && physactor->getOnGround(); + return physactor && physactor->getOnGround() && physactor->getCollisionMode(); } - bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) + bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr& actor, const float waterlevel) { - return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get()); + const auto* physactor = getActor(actor); + return physactor && physactor->canMoveToWaterSurface(waterlevel, mCollisionWorld.get()); } - osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const + osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr& actor) const { const Actor* physactor = getActor(actor); if (physactor) @@ -397,7 +420,7 @@ namespace MWPhysics return osg::Vec3f(); } - osg::Vec3f PhysicsSystem::getOriginalHalfExtents(const MWWorld::ConstPtr &actor) const + osg::Vec3f PhysicsSystem::getOriginalHalfExtents(const MWWorld::ConstPtr& actor) const { if (const Actor* physactor = getActor(actor)) return physactor->getOriginalHalfExtents(); @@ -405,7 +428,7 @@ namespace MWPhysics return osg::Vec3f(); } - osg::Vec3f PhysicsSystem::getRenderingHalfExtents(const MWWorld::ConstPtr &actor) const + osg::Vec3f PhysicsSystem::getRenderingHalfExtents(const MWWorld::ConstPtr& actor) const { const Actor* physactor = getActor(actor); if (physactor) @@ -414,16 +437,17 @@ namespace MWPhysics return osg::Vec3f(); } - osg::BoundingBox PhysicsSystem::getBoundingBox(const MWWorld::ConstPtr &object) const + osg::BoundingBox PhysicsSystem::getBoundingBox(const MWWorld::ConstPtr& object) const { - const Object * physobject = getObject(object); - if (!physobject) return osg::BoundingBox(); + const Object* physobject = getObject(object); + if (!physobject) + return osg::BoundingBox(); btVector3 min, max; mTaskScheduler->getAabb(physobject->getCollisionObject(), min, max); return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max)); } - osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const + osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const { const Actor* physactor = getActor(actor); if (physactor) @@ -432,24 +456,26 @@ namespace MWPhysics return osg::Vec3f(); } - std::vector PhysicsSystem::getCollisionsPoints(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const + std::vector PhysicsSystem::getCollisionsPoints( + const MWWorld::ConstPtr& ptr, int collisionGroup, int collisionMask) const { btCollisionObject* me = nullptr; - auto found = mObjects.find(ptr); + auto found = mObjects.find(ptr.mRef); if (found != mObjects.end()) me = found->second->getCollisionObject(); else return {}; - ContactTestResultCallback resultCallback (me); + ContactTestResultCallback resultCallback(me); resultCallback.m_collisionFilterGroup = collisionGroup; resultCallback.m_collisionFilterMask = collisionMask; mTaskScheduler->contactTest(me, resultCallback); return resultCallback.mResult; } - std::vector PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const + std::vector PhysicsSystem::getCollisions( + const MWWorld::ConstPtr& ptr, int collisionGroup, int collisionMask) const { std::vector actors; for (auto& [actor, point, normal] : getCollisionsPoints(ptr, collisionGroup, collisionMask)) @@ -457,62 +483,86 @@ namespace MWPhysics return actors; } - osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight) + osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, float maxHeight) { - ActorMap::iterator found = mActors.find(ptr); - if (found == mActors.end()) + ActorMap::iterator found = mActors.find(ptr.mRef); + if (found == mActors.end()) return ptr.getRefData().getPosition().asVec3(); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); } - void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) + void PhysicsSystem::addHeightField( + const float* heights, int x, int y, int size, int verts, float minH, float maxH, const osg::Object* holdObject) { +<<<<<<< HEAD mHeightFields[std::make_pair(x,y)] = osg::ref_ptr(new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject, mTaskScheduler.get())); +======= + mHeightFields[std::make_pair(x, y)] + = std::make_unique(heights, x, y, size, verts, minH, maxH, holdObject, mTaskScheduler.get()); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - void PhysicsSystem::removeHeightField (int x, int y) + void PhysicsSystem::removeHeightField(int x, int y) { - HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y)); - if(heightfield != mHeightFields.end()) + HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x, y)); + if (heightfield != mHeightFields.end()) mHeightFields.erase(heightfield); } - const HeightField* PhysicsSystem::getHeightField(int x, int y) const + const HeightField* PhysicsSystem::getHeightField(ESM::ExteriorCellLocation cellIndex) const { - const auto heightField = mHeightFields.find(std::make_pair(x, y)); + if (ESM::isEsm4Ext(cellIndex.mWorldspace)) + return nullptr; + const auto heightField = mHeightFields.find(std::make_pair(cellIndex.mX, cellIndex.mY)); if (heightField == mHeightFields.end()) return nullptr; return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) + void PhysicsSystem::addObject( + const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) { - osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); - if (!shapeInstance || !shapeInstance->getCollisionShape()) + if (ptr.mRef->mData.mPhysicsPostponed) return; - auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); - mObjects.emplace(ptr, obj); + std::string animationMesh = mesh; + if (ptr.getClass().useAnim()) + animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + osg::ref_ptr shapeInstance = mShapeManager->getInstance(animationMesh); + if (!shapeInstance || !shapeInstance->mCollisionShape) + return; - if (obj->isAnimated()) - mAnimatedObjects.insert(obj.get()); - } + assert(!getObject(ptr)); - void PhysicsSystem::remove(const MWWorld::Ptr &ptr) - { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + // Override collision type based on shape content. + switch (shapeInstance->mVisualCollisionType) { - if (mUnrefQueue.get()) - mUnrefQueue->push(found->second->getShapeInstance()); - - mAnimatedObjects.erase(found->second.get()); - - mObjects.erase(found); + case Resource::VisualCollisionType::None: + break; + case Resource::VisualCollisionType::Default: + collisionType = CollisionType_VisualOnly; + break; + case Resource::VisualCollisionType::Camera: + collisionType = CollisionType_CameraOnly; + break; } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); + mObjects.emplace(ptr.mRef, obj); + + if (obj->isAnimated()) + mAnimatedObjects.emplace(obj.get(), false); + } + + void PhysicsSystem::remove(const MWWorld::Ptr& ptr) + { + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) + { + mAnimatedObjects.erase(foundObject->second.get()); + + mObjects.erase(foundObject); + } + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { mActors.erase(foundActor); } @@ -525,25 +575,12 @@ namespace MWPhysics mProjectiles.erase(foundProjectile); } - void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) + void PhysicsSystem::updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated) { - ObjectMap::iterator found = mObjects.find(old); - if (found != mObjects.end()) - { - auto obj = found->second; - obj->updatePtr(updated); - mObjects.erase(found); - mObjects.emplace(updated, std::move(obj)); - } - - ActorMap::iterator foundActor = mActors.find(old); - if (foundActor != mActors.end()) - { - auto actor = foundActor->second; - actor->updatePtr(updated); - mActors.erase(foundActor); - mActors.emplace(updated, std::move(actor)); - } + if (auto foundObject = mObjects.find(old.mRef); foundObject != mObjects.end()) + foundObject->second->updatePtr(updated); + else if (auto foundActor = mActors.find(old.mRef); foundActor != mActors.end()) + foundActor->second->updatePtr(updated); for (auto& [_, actor] : mActors) { @@ -556,28 +593,27 @@ namespace MWPhysics if (projectile->getCaster() == old) projectile->setCaster(updated); } - } - Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) + Actor* PhysicsSystem::getActor(const MWWorld::Ptr& ptr) { - ActorMap::iterator found = mActors.find(ptr); + ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) return found->second.get(); return nullptr; } - const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const + const Actor* PhysicsSystem::getActor(const MWWorld::ConstPtr& ptr) const { - ActorMap::const_iterator found = mActors.find(ptr); + ActorMap::const_iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) return found->second.get(); return nullptr; } - const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr &ptr) const + const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr& ptr) const { - ObjectMap::const_iterator found = mObjects.find(ptr); + ObjectMap::const_iterator found = mObjects.find(ptr.mRef); if (found != mObjects.end()) return found->second.get(); return nullptr; @@ -591,40 +627,26 @@ namespace MWPhysics return nullptr; } - void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) + void PhysicsSystem::updateScale(const MWWorld::Ptr& ptr) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { float scale = ptr.getCellRef().getScale(); - found->second->setScale(scale); - mTaskScheduler->updateSingleAabb(found->second); - return; + foundObject->second->setScale(scale); + mTaskScheduler->updateSingleAabb(foundObject->second); } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { foundActor->second->updateScale(); mTaskScheduler->updateSingleAabb(foundActor->second); - return; } } - void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const + void PhysicsSystem::updateRotation(const MWWorld::Ptr& ptr, osg::Quat rotate) { - const auto foundProjectile = mProjectiles.find(projectileId); - assert(foundProjectile != mProjectiles.end()); - auto* projectile = foundProjectile->second.get(); - - btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition()); - btVector3 btTo = Misc::Convert::toBullet(position); - - if (btFrom == btTo) - return; - - const auto casterPtr = projectile->getCaster(); - const auto* caster = [this,&casterPtr]() -> const btCollisionObject* + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { +<<<<<<< HEAD const Actor* actor = getActor(casterPtr); if (actor) return actor->getCollisionObject(); @@ -657,48 +679,52 @@ namespace MWPhysics found->second->setRotation(ptr.getRefData().getBaseNode()->getAttitude()); mTaskScheduler->updateSingleAabb(found->second); return; +======= + foundObject->second->setRotation(rotate); + mTaskScheduler->updateSingleAabb(foundObject->second); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { if (!foundActor->second->isRotationallyInvariant()) { - foundActor->second->updateRotation(); + foundActor->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(foundActor->second); } - return; } } - void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr) + void PhysicsSystem::updatePosition(const MWWorld::Ptr& ptr) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { +<<<<<<< HEAD found->second->updatePosition(); mTaskScheduler->updateSingleAabb(found->second); return; +======= + foundObject->second->updatePosition(); + mTaskScheduler->updateSingleAabb(foundObject->second); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { foundActor->second->updatePosition(); mTaskScheduler->updateSingleAabb(foundActor->second, true); - return; } } - void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) + void PhysicsSystem::addActor(const MWWorld::Ptr& ptr, const std::string& mesh) { - osg::ref_ptr shape = mShapeManager->getShape(mesh); + std::string animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + osg::ref_ptr shape = mShapeManager->getShape(animationMesh); // Try to get shape from basic model as fallback for creatures - if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.extents.length2() == 0) + if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.mExtents.length2() == 0) { - const std::string fallbackModel = ptr.getClass().getModel(ptr); - if (fallbackModel != mesh) + if (animationMesh != mesh) { - shape = mShapeManager->getShape(fallbackModel); + shape = mShapeManager->getShape(mesh); } } @@ -707,6 +733,7 @@ namespace MWPhysics // check if Actor should spawn above water const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); +<<<<<<< HEAD const bool canWaterWalk = effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); @@ -718,10 +745,22 @@ namespace MWPhysics } int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) +======= + const bool canWaterWalk = effects.getOrDefault(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; + + auto actor = std::make_shared( + ptr, shape, mTaskScheduler.get(), canWaterWalk, Settings::game().mActorCollisionShapeType); + + mActors.emplace(ptr.mRef, std::move(actor)); + } + + int PhysicsSystem::addProjectile( + const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); assert(shapeInstance); - float radius = computeRadius ? shapeInstance->mCollisionBox.extents.length() / 2.f : 1.f; + float radius = computeRadius ? shapeInstance->mCollisionBox.mExtents.length() / 2.f : 1.f; mProjectileId++; @@ -742,7 +781,7 @@ namespace MWPhysics bool PhysicsSystem::toggleCollisionMode() { - ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); + ActorMap::iterator found = mActors.find(MWMechanics::getPlayer().mRef); if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); @@ -755,9 +794,9 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) { - ActorMap::iterator found = mActors.find(ptr); + ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) found->second->setVelocity(velocity); } @@ -765,75 +804,127 @@ namespace MWPhysics void PhysicsSystem::clearQueuedMovement() { for (const auto& [_, actor] : mActors) - actor->setVelocity(osg::Vec3f()); - } - - const std::vector& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) - { - mTimeAccum += dt; - - if (skipSimulation) - return mTaskScheduler->resetSimulation(mActors); - - // modifies mTimeAccum - return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats); - } - - std::vector PhysicsSystem::prepareFrameData(bool willSimulate) - { - std::vector actorsFrameData; - actorsFrameData.reserve(mActors.size()); - const MWBase::World *world = MWBase::Environment::get().getWorld(); - for (const auto& [ptr, physicActor] : mActors) { + actor->setVelocity(osg::Vec3f()); + actor->setInertialForce(osg::Vec3f()); + } + } + + void PhysicsSystem::prepareSimulation(bool willSimulate, std::vector& simulations) + { + assert(simulations.empty()); + simulations.reserve(mActors.size() + mProjectiles.size()); + const MWBase::World* world = MWBase::Environment::get().getWorld(); + for (const auto& [ref, physicActor] : mActors) + { + if (!physicActor->isActive()) + continue; + + auto ptr = physicActor->getPtr(); + if (!ptr.getClass().isMobile(ptr)) + continue; float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = ptr.getCell(); - if(cell->getCell()->hasWater()) + const MWWorld::CellStore* cell = ptr.getCell(); + if (cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); - const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(physicActor->getPtr()).getMagicEffects(); + const auto& stats = ptr.getClass().getCreatureStats(ptr); + const MWMechanics::MagicEffects& effects = stats.getMagicEffects(); bool waterCollision = false; - if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) + if (cell->getCell()->hasWater() && effects.getOrDefault(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))) + if (physicActor->getCollisionMode() + || !world->isUnderwater(ptr.getCell(), ptr.getRefData().getPosition().asVec3())) waterCollision = true; } physicActor->setCanWaterWalk(waterCollision); // Slow fall reduces fall speed by a factor of (effect magnitude / 200) - const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); + const float slowFall + = 1.f - std::clamp(effects.getOrDefault(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f, 0.f, 1.f); + const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); + const bool inert = stats.isDead() + || (!godmode && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Paralyze).getModifier() > 0); - // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. - MWWorld::Ptr standingOn; - if (!willSimulate) - standingOn = physicActor->getStandingOnPtr(); + simulations.emplace_back(ActorSimulation{ + physicActor, ActorFrameData{ *physicActor, inert, waterCollision, slowFall, waterlevel } }); - actorsFrameData.emplace_back(physicActor, standingOn, waterCollision, slowFall, waterlevel); + // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. + if (willSimulate) + handleJump(ptr); + } + + for (const auto& [id, projectile] : mProjectiles) + { + simulations.emplace_back(ProjectileSimulation{ projectile, ProjectileFrameData{ *projectile } }); } - return actorsFrameData; } - void PhysicsSystem::stepSimulation() + void PhysicsSystem::stepSimulation( + float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { - for (Object* animatedObject : mAnimatedObjects) + for (auto& [animatedObject, changed] : mAnimatedObjects) + { if (animatedObject->animateCollisionShapes()) { - auto obj = mObjects.find(animatedObject->getPtr()); + auto obj = mObjects.find(animatedObject->getPtr().mRef); assert(obj != mObjects.end()); mTaskScheduler->updateSingleAabb(obj->second); + changed = true; } + else + { + changed = false; + } + } #ifndef BT_NO_PROFILE CProfileManager::Reset(); CProfileManager::Increment_Frame_Counter(); #endif + + mTimeAccum += dt; + + if (skipSimulation) + mTaskScheduler->resetSimulation(mActors); + else + { + std::vector& simulations = mSimulations[mSimulationsCounter++ % mSimulations.size()]; + prepareSimulation(mTimeAccum >= mPhysicsDt, simulations); + // modifies mTimeAccum + mTaskScheduler->applyQueuedMovements(mTimeAccum, simulations, frameStart, frameNumber, stats); + } + } + + void PhysicsSystem::moveActors() + { + auto* player = getActor(MWMechanics::getPlayer()); + const auto world = MWBase::Environment::get().getWorld(); + + // copy new ptr position in temporary vector. player is handled separately as its movement might change active + // cell. + mActorsPositions.clear(); + if (!mActors.empty()) + mActorsPositions.reserve(mActors.size() - 1); + for (const auto& [ptr, physicActor] : mActors) + { + if (physicActor.get() == player) + continue; + mActorsPositions.emplace_back(physicActor->getPtr(), physicActor->getSimulationPosition()); + } + + for (const auto& [ptr, pos] : mActorsPositions) + world->moveObject(ptr, pos, false, false); + + if (player != nullptr) + world->moveObject(player->getPtr(), player->getSimulationPosition(), false, false); } void PhysicsSystem::updateAnimatedCollisionShape(const MWWorld::Ptr& object) { - ObjectMap::iterator found = mObjects.find(object); + ObjectMap::iterator found = mObjects.find(object.mRef); if (found != mObjects.end()) if (found->second->animateCollisionShapes()) mTaskScheduler->updateSingleAabb(found->second); @@ -845,15 +936,15 @@ namespace MWPhysics mTaskScheduler->debugDraw(); } - bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const + bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const { - const auto physActor = mActors.find(actor); + const auto physActor = mActors.find(actor.mRef); if (physActor != mActors.end()) return physActor->second->getStandingOnPtr() == object; return false; } - void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector &out) const + void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector& out) const { for (const auto& [_, actor] : mActors) { @@ -862,13 +953,13 @@ namespace MWPhysics } } - bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const + bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const { std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end()); } - void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr &object, std::vector &out) const + void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const { std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); out.insert(out.end(), collisions.begin(), collisions.end()); @@ -915,25 +1006,43 @@ namespace MWPhysics return; } - mWaterCollisionObject.reset(new btCollisionObject()); - mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); + mWaterCollisionObject = std::make_unique(); + mWaterCollisionShape = std::make_unique(btVector3(0, 0, 1), mWaterHeight); mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); - mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, - CollisionType_Actor|CollisionType_Projectile); + mTaskScheduler->addCollisionObject( + mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor | CollisionType_Projectile); } - bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + std::span ignore, std::vector* occupyingActors) const { - btCollisionObject* object = nullptr; - const auto it = mActors.find(ignore); - if (it != mActors.end()) - object = it->second->getCollisionObject(); + std::vector ignoredObjects; + ignoredObjects.reserve(ignore.size()); + for (const auto& v : ignore) + if (const auto it = mActors.find(v.mRef); it != mActors.end()) + ignoredObjects.push_back(it->second->getCollisionObject()); + std::sort(ignoredObjects.begin(), ignoredObjects.end()); + ignoredObjects.erase(std::unique(ignoredObjects.begin(), ignoredObjects.end()), ignoredObjects.end()); + const auto ignoreFilter = [&](const btCollisionObject* v) { + return std::binary_search(ignoredObjects.begin(), ignoredObjects.end(), v); + }; const auto bulletPosition = Misc::Convert::toBullet(position); const auto aabbMin = bulletPosition - btVector3(radius, radius, radius); const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); const int mask = MWPhysics::CollisionType_Actor; - const int group = 0xff; - HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group); + const int group = MWPhysics::CollisionType_AnyPhysical; + if (occupyingActors == nullptr) + { + HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, + static_cast(nullptr)); + mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); + return callback.getResult(); + } + const auto onCollision = [&](const btCollisionObject* object) { + if (PtrHolder* holder = static_cast(object->getUserPointer())) + occupyingActors->push_back(holder->getPtr()); + }; + HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, &onCollision); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); } @@ -942,6 +1051,7 @@ namespace MWPhysics { stats.setAttribute(frameNumber, "Physics Actors", mActors.size()); stats.setAttribute(frameNumber, "Physics Objects", mObjects.size()); + stats.setAttribute(frameNumber, "Physics Projectiles", mProjectiles.size()); stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size()); } @@ -951,58 +1061,71 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, - bool waterCollision, float slowFall, float waterlevel) - : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mSkipCollisionDetection(actor->skipCollisions()), - mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(actor->velocity()), mPosition(), mRefpos() + ActorFrameData::ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel) + : mPosition() + , mStandingOn(nullptr) + , mIsOnGround(actor.getOnGround()) + , mIsOnSlope(actor.getOnSlope()) + , mWalkingOnWater(false) + , mInert(inert) + , mCollisionObject(actor.getCollisionObject()) + , mSwimLevel(waterlevel + - (actor.getRenderingHalfExtents().z() * 2 + * MWBase::Environment::get() + .getESMStore() + ->get() + .find("fSwimHeightScale") + ->mValue.getFloat())) + , mSlowFall(slowFall) + , mRotation() + , mMovement(actor.velocity()) + , mWaterlevel(waterlevel) + , mHalfExtentsZ(actor.getHalfExtents().z()) + , mOldHeight(0) + , mStuckFrames(0) + , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) + , mWasOnGround(actor.getOnGround()) + , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) + , mWaterCollision(waterCollision) + , mSkipCollisionDetection(!actor.getCollisionMode()) { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const auto ptr = actor->getPtr(); - mFlying = world->isFlying(ptr); - mSwimming = world->isSwimming(ptr); - mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; - auto& stats = ptr.getClass().getCreatureStats(ptr); - const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); - mFloatToSurface = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); - mWasOnGround = actor->getOnGround(); } - void ActorFrameData::updatePosition(btCollisionWorld* world) + ProjectileFrameData::ProjectileFrameData(Projectile& projectile) + : mPosition(projectile.getPosition()) + , mMovement(projectile.velocity()) + , mCaster(projectile.getCasterCollisionObject()) + , mCollisionObject(projectile.getCollisionObject()) + , mProjectile(&projectile) { - mActorRaw->applyOffsetChange(); - mPosition = mActorRaw->getPosition(); - if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) - { - mPosition.z() = mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false); - } - mOldHeight = mPosition.z(); - mRefpos = mActorRaw->getPtr().getRefData().getPosition(); } WorldFrameData::WorldFrameData() : mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm()) , mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection()) - {} + { + } LOSRequest::LOSRequest(const std::weak_ptr& a1, const std::weak_ptr& a2) - : mResult(false), mStale(false), mAge(0) + : mResult(false) + , mStale(false) + , mAge(0) { // we use raw actor pointer pair to uniquely identify request - // sort the pointer value in ascending order to not duplicate equivalent requests, eg. getLOS(A, B) and getLOS(B, A) + // sort the pointer value in ascending order to not duplicate equivalent requests, eg. getLOS(A, B) and + // getLOS(B, A) auto* raw1 = a1.lock().get(); auto* raw2 = a2.lock().get(); assert(raw1 != raw2); if (raw1 < raw2) { - mActors = {a1, a2}; - mRawActors = {raw1, raw2}; + mActors = { a1, a2 }; + mRawActors = { raw1, raw2 }; } else { - mActors = {a2, a1}; - mRawActors = {raw2, raw1}; + mActors = { a2, a1 }; + mRawActors = { raw2, raw1 }; } } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index a746dc01a..a4d899124 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -2,15 +2,21 @@ #define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H #include -#include +#include #include +#include +#include #include -#include +#include +#include +#include -#include #include -#include +#include #include +#include + +#include #include "../mwworld/ptr.hpp" @@ -35,11 +41,6 @@ namespace Resource class ResourceSystem; } -namespace SceneUtil -{ - class UnrefQueue; -} - class btCollisionWorld; class btBroadphaseInterface; class btDefaultCollisionConfiguration; @@ -56,7 +57,7 @@ namespace MWPhysics class PhysicsTaskScheduler; class Projectile; - using ActorMap = std::map>; + using ActorMap = std::unordered_map>; struct ContactPoint { @@ -78,27 +79,39 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); - void updatePosition(btCollisionWorld* world); - std::weak_ptr mActor; - Actor* mActorRaw; - MWWorld::Ptr mStandingOn; - bool mFlying; - bool mSwimming; - bool mWasOnGround; - bool mWantJump; - bool mDidJump; - bool mFloatToSurface; - bool mNeedLand; - bool mWaterCollision; - bool mSkipCollisionDetection; - float mWaterlevel; - float mSlowFall; - float mOldHeight; - float mFallHeight; - osg::Vec3f mMovement; + ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel); osg::Vec3f mPosition; - ESM::Position mRefpos; + osg::Vec3f mInertia; + const btCollisionObject* mStandingOn; + bool mIsOnGround; + bool mIsOnSlope; + bool mWalkingOnWater; + const bool mInert; + btCollisionObject* mCollisionObject; + const float mSwimLevel; + const float mSlowFall; + osg::Vec2f mRotation; + osg::Vec3f mMovement; + osg::Vec3f mLastStuckPosition; + const float mWaterlevel; + const float mHalfExtentsZ; + float mOldHeight; + unsigned int mStuckFrames; + const bool mFlying; + const bool mWasOnGround; + const bool mIsAquatic; + const bool mWaterCollision; + const bool mSkipCollisionDetection; + }; + + struct ProjectileFrameData + { + explicit ProjectileFrameData(Projectile& projectile); + osg::Vec3f mPosition; + osg::Vec3f mMovement; + const btCollisionObject* mCaster; + const btCollisionObject* mCollisionObject; + Projectile* mProjectile; }; struct WorldFrameData @@ -108,134 +121,183 @@ namespace MWPhysics osg::Vec3f mStormDirection; }; + template + class SimulationImpl + { + public: + explicit SimulationImpl(const std::weak_ptr& ptr, FrameData&& data) + : mPtr(ptr) + , mData(data) + { + } + + std::optional, std::reference_wrapper>> lock() + { + if (auto locked = mPtr.lock()) + return { { std::move(locked), std::ref(mData) } }; + return std::nullopt; + } + + private: + std::weak_ptr mPtr; + FrameData mData; + }; + + using ActorSimulation = SimulationImpl; + using ProjectileSimulation = SimulationImpl; + using Simulation = std::variant; + class PhysicsSystem : public RayCastingInterface { - public: - PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); - virtual ~PhysicsSystem (); + public: + PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); + virtual ~PhysicsSystem(); - void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); + Resource::BulletShapeManager* getShapeManager(); - Resource::BulletShapeManager* getShapeManager(); + void enableWater(float height); + void setWaterHeight(float height); + void disableWater(); - void enableWater(float height); - void setWaterHeight(float height); - void disableWater(); + void addObject(const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, + int collisionType = CollisionType_World); + void addActor(const MWWorld::Ptr& ptr, const std::string& mesh); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); - void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); + int addProjectile( + const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); + void setCaster(int projectileId, const MWWorld::Ptr& caster); + void removeProjectile(const int projectileId); +<<<<<<< HEAD int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); void setCaster(int projectileId, const MWWorld::Ptr& caster); void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); +======= + void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); + Actor* getActor(const MWWorld::Ptr& ptr); + const Actor* getActor(const MWWorld::ConstPtr& ptr) const; - Actor* getActor(const MWWorld::Ptr& ptr); - const Actor* getActor(const MWWorld::ConstPtr& ptr) const; + const Object* getObject(const MWWorld::ConstPtr& ptr) const; - const Object* getObject(const MWWorld::ConstPtr& ptr) const; + Projectile* getProjectile(int projectileId) const; - Projectile* getProjectile(int projectileId) const; + // Object or Actor + void remove(const MWWorld::Ptr& ptr); - // Object or Actor - void remove (const MWWorld::Ptr& ptr); + void updateScale(const MWWorld::Ptr& ptr); + void updateRotation(const MWWorld::Ptr& ptr, osg::Quat rotate); + void updatePosition(const MWWorld::Ptr& ptr); - void updateScale (const MWWorld::Ptr& ptr); - void updateRotation (const MWWorld::Ptr& ptr); - void updatePosition (const MWWorld::Ptr& ptr); + void addHeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, + const osg::Object* holdObject); - void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); + void removeHeightField(int x, int y); - void removeHeightField (int x, int y); + const HeightField* getHeightField(ESM::ExteriorCellLocation cellIndex) const; - const HeightField* getHeightField(int x, int y) const; + bool toggleCollisionMode(); - bool toggleCollisionMode(); + /// Determine new position based on all queued movements, then clear the list. + void stepSimulation( + float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - void stepSimulation(); - void debugDraw(); + /// Apply new positions to actors + void moveActors(); + void debugDraw(); - std::vector getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with - std::vector getCollisionsPoints(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; - osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight); + std::vector getCollisions(const MWWorld::ConstPtr& ptr, int collisionGroup, + int collisionMask) const; ///< get handles this object collides with + std::vector getCollisionsPoints( + const MWWorld::ConstPtr& ptr, int collisionGroup, int collisionMask) const; + osg::Vec3f traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, float maxHeight); - std::pair getHitContact(const MWWorld::ConstPtr& actor, - const osg::Vec3f &origin, - const osg::Quat &orientation, - float queryDistance, std::vector& targets); + std::pair getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f& origin, + const osg::Quat& orientation, float queryDistance, std::vector& targets); + /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the + /// target vector hits the collision shape and then calculates distance from the intersection point. + /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be + /// successful. \note Only Actor targets are supported at the moment. + float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; - /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the - /// target vector hits the collision shape and then calculates distance from the intersection point. - /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be successful. - /// \note Only Actor targets are supported at the moment. - float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; + /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all + /// other actors. + RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, + const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), + const std::vector& targets = std::vector(), int mask = CollisionType_Default, + int group = 0xff) const override; + using RayCastingInterface::castRay; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. - RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), - std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; + RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, + int mask = CollisionType_Default, int group = 0xff) const override; - RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; + /// Return true if actor1 can see actor2. + bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const override; - /// Return true if actor1 can see actor2. - bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const override; + bool isOnGround(const MWWorld::Ptr& actor); - bool isOnGround (const MWWorld::Ptr& actor); + bool canMoveToWaterSurface(const MWWorld::ConstPtr& actor, const float waterlevel); - bool canMoveToWaterSurface (const MWWorld::ConstPtr &actor, const float waterlevel); + /// Get physical half extents (scaled) of the given actor. + osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor) const; - /// Get physical half extents (scaled) of the given actor. - osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor) const; + /// Get physical half extents (not scaled) of the given actor. + osg::Vec3f getOriginalHalfExtents(const MWWorld::ConstPtr& actor) const; - /// Get physical half extents (not scaled) of the given actor. - osg::Vec3f getOriginalHalfExtents(const MWWorld::ConstPtr& actor) const; + /// @see MWPhysics::Actor::getRenderingHalfExtents + osg::Vec3f getRenderingHalfExtents(const MWWorld::ConstPtr& actor) const; - /// @see MWPhysics::Actor::getRenderingHalfExtents - osg::Vec3f getRenderingHalfExtents(const MWWorld::ConstPtr& actor) const; + /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the + /// collision bounds in world space. + /// @note The collision shape's origin is in its center, so the position returned can be described as center of + /// the actor collision box in world space. + osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; - /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space. - /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. - osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; + /// Get bounding box in world space of the given object. + osg::BoundingBox getBoundingBox(const MWWorld::ConstPtr& object) const; - /// Get bounding box in world space of the given object. - osg::BoundingBox getBoundingBox(const MWWorld::ConstPtr &object) const; + /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will + /// be overwritten. Valid until the next call to stepSimulation + void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity); - /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will - /// be overwritten. Valid until the next call to applyQueuedMovement. - void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); + /// Clear the queued movements list without applying. + void clearQueuedMovement(); - /// Apply all queued movements, then clear the list. - const std::vector& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + /// Return true if \a actor has been standing on \a object in this frame + /// This will trigger whenever the object is directly below the actor. + /// It doesn't matter if the actor is stationary or moving. + bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; - /// Clear the queued movements list without applying. - void clearQueuedMovement(); + /// Get the handle of all actors standing on \a object in this frame. + void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector& out) const; - /// Return true if \a actor has been standing on \a object in this frame - /// This will trigger whenever the object is directly below the actor. - /// It doesn't matter if the actor is stationary or moving. - bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; + /// Return true if \a actor has collided with \a object in this frame. + /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. + bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; - /// Get the handle of all actors standing on \a object in this frame. - void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector& out) const; + /// Get the handle of all actors colliding with \a object in this frame. + void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const; - /// Return true if \a actor has collided with \a object in this frame. - /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. - bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; + bool toggleDebugRendering(); - /// Get the handle of all actors colliding with \a object in this frame. - void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const; + /// Mark the given object as a 'non-solid' object. A non-solid object means that + /// \a isOnSolidGround will return false for actors standing on that object. + void markAsNonSolid(const MWWorld::ConstPtr& ptr); - bool toggleDebugRendering(); + bool isOnSolidGround(const MWWorld::Ptr& actor) const; - /// Mark the given object as a 'non-solid' object. A non-solid object means that - /// \a isOnSolidGround will return false for actors standing on that object. - void markAsNonSolid (const MWWorld::ConstPtr& ptr); + void updateAnimatedCollisionShape(const MWWorld::Ptr& object); - bool isOnSolidGround (const MWWorld::Ptr& actor) const; + template + void forEachAnimatedObject(Function&& function) const + { + std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); + } +<<<<<<< HEAD /* Start of tes3mp addition @@ -247,68 +309,70 @@ namespace MWPhysics */ void updateAnimatedCollisionShape(const MWWorld::Ptr& object); +======= + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + std::span ignore, std::vector* occupyingActors) const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - template - void forEachAnimatedObject(Function&& function) const - { - std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); - } + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + void reportCollision(const btVector3& position, const btVector3& normal); - bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const; + private: + void updateWater(); - void reportStats(unsigned int frameNumber, osg::Stats& stats) const; - void reportCollision(const btVector3& position, const btVector3& normal); + void prepareSimulation(bool willSimulate, std::vector& simulations); - private: + std::unique_ptr mBroadphase; + std::unique_ptr mCollisionConfiguration; + std::unique_ptr mDispatcher; + std::unique_ptr mCollisionWorld; + std::unique_ptr mTaskScheduler; - void updateWater(); + std::unique_ptr mShapeManager; + Resource::ResourceSystem* mResourceSystem; - std::vector prepareFrameData(bool willSimulate); + using ObjectMap = std::unordered_map>; + ObjectMap mObjects; - osg::ref_ptr mUnrefQueue; + std::map mAnimatedObjects; // stores pointers to elements in mObjects - std::unique_ptr mBroadphase; - std::unique_ptr mCollisionConfiguration; - std::unique_ptr mDispatcher; - std::unique_ptr mCollisionWorld; - std::unique_ptr mTaskScheduler; + ActorMap mActors; - std::unique_ptr mShapeManager; - Resource::ResourceSystem* mResourceSystem; + using ProjectileMap = std::map>; + ProjectileMap mProjectiles; - using ObjectMap = std::map>; - ObjectMap mObjects; + using HeightFieldMap = std::map, std::unique_ptr>; + HeightFieldMap mHeightFields; - std::set mAnimatedObjects; // stores pointers to elements in mObjects + bool mDebugDrawEnabled; - ActorMap mActors; + float mTimeAccum; - using ProjectileMap = std::map>; - ProjectileMap mProjectiles; + unsigned int mProjectileId; +<<<<<<< HEAD using HeightFieldMap = std::map, osg::ref_ptr>; HeightFieldMap mHeightFields; +======= + float mWaterHeight; + bool mWaterEnabled; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - bool mDebugDrawEnabled; + std::unique_ptr mWaterCollisionObject; + std::unique_ptr mWaterCollisionShape; - float mTimeAccum; + std::unique_ptr mDebugDrawer; - unsigned int mProjectileId; + osg::ref_ptr mParentNode; - float mWaterHeight; - bool mWaterEnabled; + float mPhysicsDt; - std::unique_ptr mWaterCollisionObject; - std::unique_ptr mWaterCollisionShape; + std::size_t mSimulationsCounter = 0; + std::array, 2> mSimulations; + std::vector> mActorsPositions; - std::unique_ptr mDebugDrawer; - - osg::ref_ptr mParentNode; - - float mPhysicsDt; - - PhysicsSystem (const PhysicsSystem&); - PhysicsSystem& operator= (const PhysicsSystem&); + PhysicsSystem(const PhysicsSystem&); + PhysicsSystem& operator=(const PhysicsSystem&); }; } diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 0c7c99db3..2a9b921c5 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -1,18 +1,22 @@ #include #include +<<<<<<< HEAD #include +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include -#include "../mwworld/class.hpp" - +#include "actor.hpp" #include "collisiontype.hpp" #include "mtphysics.hpp" +#include "object.hpp" #include "projectile.hpp" namespace MWPhysics { +<<<<<<< HEAD Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) : mHitWater(false) , mActive(true) @@ -54,9 +58,39 @@ void Projectile::commitPositionChange() trans.setOrigin(Misc::Convert::toBullet(mPosition)); mCollisionObject->setWorldTransform(trans); mTransformUpdatePending = false; - } -} +======= + Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, + PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) + : PtrHolder(MWWorld::Ptr(), position) + , mHitWater(false) + , mActive(true) + , mHitTarget(nullptr) + , mPhysics(physicssystem) + , mTaskScheduler(scheduler) + { + mShape = std::make_unique(radius); + mConvexShape = static_cast(mShape.get()); + mCollisionObject = std::make_unique(); + mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); + mCollisionObject->setActivationState(DISABLE_DEACTIVATION); + mCollisionObject->setCollisionShape(mShape.get()); + mCollisionObject->setUserPointer(this); + + mPosition = position; + mPreviousPosition = position; + mSimulationPosition = position; + setCaster(caster); + + const int collisionMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor + | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; + mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); + + updateCollisionObjectPosition(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 + } + +<<<<<<< HEAD void Projectile::setPosition(const osg::Vec3f &position) { std::scoped_lock lock(mMutex); @@ -110,14 +144,86 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const bool validTarget = false; for (const auto& targetActor : mValidTargets) +======= + Projectile::~Projectile() +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - if (targetActor == target) + if (!mActive) + mPhysics->reportCollision(mHitPosition, mHitNormal); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + } + + void Projectile::updateCollisionObjectPosition() + { + std::scoped_lock lock(mMutex); + auto& trans = mCollisionObject->getWorldTransform(); + trans.setOrigin(Misc::Convert::toBullet(mPosition)); + mCollisionObject->setWorldTransform(trans); + } + + void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal) + { + bool active = true; + if (!mActive.compare_exchange_strong(active, false, std::memory_order_relaxed) || !active) + return; + mHitTarget = target; + mHitPosition = pos; + mHitNormal = normal; + } + + MWWorld::Ptr Projectile::getTarget() const + { + assert(!mActive); + auto* target = static_cast(mHitTarget->getUserPointer()); + return target ? target->getPtr() : MWWorld::Ptr(); + } + + MWWorld::Ptr Projectile::getCaster() const + { + return mCaster; + } + + void Projectile::setCaster(const MWWorld::Ptr& caster) + { + mCaster = caster; + mCasterColObj = [this, &caster]() -> const btCollisionObject* { + const Actor* actor = mPhysics->getActor(caster); + if (actor) + return actor->getCollisionObject(); + const Object* object = mPhysics->getObject(caster); + if (object) + return object->getCollisionObject(); + return nullptr; + }(); + } + + void Projectile::setValidTargets(const std::vector& targets) + { + std::scoped_lock lock(mMutex); + mValidTargets.clear(); + for (const auto& ptr : targets) { - validTarget = true; - break; + const auto* physicActor = mPhysics->getActor(ptr); + if (physicActor) + mValidTargets.push_back(physicActor->getCollisionObject()); } } - return validTarget; -} +<<<<<<< HEAD +======= + bool Projectile::isValidTarget(const btCollisionObject* target) const + { + assert(target); + std::scoped_lock lock(mMutex); + if (mCasterColObj == target) + return false; + + if (mValidTargets.empty()) + return true; + + return std::any_of(mValidTargets.begin(), mValidTargets.end(), + [target](const btCollisionObject* actor) { return target == actor; }); + } + +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index c14b201d8..bb21d09e3 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -18,11 +18,6 @@ namespace osg class Vec3f; } -namespace Resource -{ - class BulletShape; -} - namespace MWPhysics { class PhysicsTaskScheduler; @@ -31,35 +26,27 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: +<<<<<<< HEAD Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); +======= + Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, + PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } - void commitPositionChange(); + void updateCollisionObjectPosition(); - void setPosition(const osg::Vec3f& position); - osg::Vec3f getPosition() const; + bool isActive() const { return mActive.load(std::memory_order_acquire); } - btCollisionObject* getCollisionObject() const - { - return mCollisionObject.get(); - } - - bool isActive() const - { - return mActive.load(std::memory_order_acquire); - } - - MWWorld::Ptr getTarget() const - { - assert(!mActive); - return mHitTarget; - } + MWWorld::Ptr getTarget() const; MWWorld::Ptr getCaster() const; - void setCaster(MWWorld::Ptr caster); + void setCaster(const MWWorld::Ptr& caster); + const btCollisionObject* getCasterCollisionObject() const { return mCasterColObj; } +<<<<<<< HEAD void setHitWater() { mHitWater = true; @@ -69,22 +56,31 @@ namespace MWPhysics { return mHitWater; } +======= + void setHitWater() { mHitWater = true; } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); + bool getHitWater() const { return mHitWater; } + + void hit(const btCollisionObject* target, btVector3 pos, btVector3 normal); void setValidTargets(const std::vector& targets); - bool isValidTarget(const MWWorld::Ptr& target) const; + bool isValidTarget(const btCollisionObject* target) const; +<<<<<<< HEAD btVector3 getHitPosition() const { return mHitPosition; } +======= + btVector3 getHitPosition() const { return mHitPosition; } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 private: - std::unique_ptr mShape; btConvexShape* mConvexShape; +<<<<<<< HEAD std::unique_ptr mCollisionObject; bool mTransformUpdatePending; bool mHitWater; @@ -92,15 +88,22 @@ namespace MWPhysics MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; osg::Vec3f mPosition; +======= + bool mHitWater; + std::atomic mActive; + MWWorld::Ptr mCaster; + const btCollisionObject* mCasterColObj; + const btCollisionObject* mHitTarget; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 btVector3 mHitPosition; btVector3 mHitNormal; - std::vector mValidTargets; + std::vector mValidTargets; mutable std::mutex mMutex; - PhysicsSystem *mPhysics; - PhysicsTaskScheduler *mTaskScheduler; + PhysicsSystem* mPhysics; + PhysicsTaskScheduler* mTaskScheduler; Projectile(const Projectile&); Projectile& operator=(const Projectile&); @@ -108,5 +111,4 @@ namespace MWPhysics } - #endif diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index 1e19937db..573705f15 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -1,51 +1,52 @@ -#include "../mwworld/class.hpp" +#include -#include "actor.hpp" #include "collisiontype.hpp" #include "projectile.hpp" #include "projectileconvexcallback.hpp" -#include "ptrholder.hpp" namespace MWPhysics { - ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj) + ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, + const btVector3& from, const btVector3& to, Projectile* proj) : btCollisionWorld::ClosestConvexResultCallback(from, to) - , mMe(me), mProjectile(proj) + , mCaster(caster) + , mMe(me) + , mProjectile(proj) { assert(mProjectile); } - btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) + btScalar ProjectileConvexCallback::addSingleResult( + btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) { + const auto* hitObject = result.m_hitCollisionObject; // don't hit the caster - if (result.m_hitCollisionObject == mMe) + if (hitObject == mCaster) return 1.f; // don't hit the projectile - if (result.m_hitCollisionObject == mProjectile->getCollisionObject()) + if (hitObject == mMe) return 1.f; btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); - switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup) + switch (hitObject->getBroadphaseHandle()->m_collisionFilterGroup) { case CollisionType_Actor: - { - auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getPtr())) - return 1.f; - mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal); - break; - } + { + if (!mProjectile->isValidTarget(hitObject)) + return 1.f; + break; + } case CollisionType_Projectile: - { - auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getCaster())) - return 1.f; - target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - break; - } + { + auto* target = static_cast(hitObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getCasterCollisionObject())) + return 1.f; + target->hit(mMe, m_hitPointWorld, m_hitNormalWorld); + break; + } case CollisionType_Water: +<<<<<<< HEAD { mProjectile->setHitWater(); mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); @@ -58,10 +59,16 @@ namespace MWPhysics mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld); break; } +======= + { + mProjectile->setHitWater(); + break; + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + mProjectile->hit(hitObject, m_hitPointWorld, m_hitNormalWorld); return result.m_hitFraction; } } - diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp index 96c84b1fe..3cd304bab 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.hpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -12,11 +12,13 @@ namespace MWPhysics class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj); + ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, + const btVector3& to, Projectile* proj); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; private: + const btCollisionObject* mCaster; const btCollisionObject* mMe; Projectile* mProjectile; }; diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 152a5d64f..fc8fd94c3 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -1,7 +1,13 @@ #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H +#include #include +#include + +#include + +#include #include "../mwworld/ptr.hpp" @@ -10,31 +16,47 @@ namespace MWPhysics class PtrHolder { public: - virtual ~PtrHolder() {} - - void updatePtr(const MWWorld::Ptr& updated) + explicit PtrHolder(const MWWorld::Ptr& ptr, const osg::Vec3f& position) + : mPtr(ptr) + , mSimulationPosition(position) + , mPosition(position) + , mPreviousPosition(position) { - std::scoped_lock lock(mMutex); - mPtr = updated; } - MWWorld::Ptr getPtr() + virtual ~PtrHolder() = default; + + void updatePtr(const MWWorld::Ptr& updated) { mPtr = updated; } + + MWWorld::Ptr getPtr() const { return mPtr; } + + btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } + + void setVelocity(osg::Vec3f velocity) { mVelocity = velocity; } + + osg::Vec3f velocity() { return std::exchange(mVelocity, osg::Vec3f()); } + + void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } + + osg::Vec3f getSimulationPosition() const { return mSimulationPosition; } + + void setPosition(const osg::Vec3f& position) { - std::scoped_lock lock(mMutex); - return mPtr; + mPreviousPosition = mPosition; + mPosition = position; } - MWWorld::ConstPtr getPtr() const - { - std::scoped_lock lock(mMutex); - return mPtr; - } + osg::Vec3d getPosition() const { return mPosition; } + + osg::Vec3d getPreviousPosition() const { return mPreviousPosition; } protected: MWWorld::Ptr mPtr; - - private: - mutable std::mutex mMutex; + std::unique_ptr mCollisionObject; + osg::Vec3f mVelocity; + osg::Vec3f mSimulationPosition; + osg::Vec3d mPosition; + osg::Vec3d mPreviousPosition; }; } diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 7c8375cb5..4a56e9bf3 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -9,8 +9,9 @@ namespace MWPhysics { - struct RayCastingResult + class RayCastingResult { + public: bool mHit; osg::Vec3f mHitPos; osg::Vec3f mHitNormal; @@ -19,22 +20,32 @@ namespace MWPhysics class RayCastingInterface { - public: - /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the - /// target vector hits the collision shape and then calculates distance from the intersection point. - /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be successful. - /// \note Only Actor targets are supported at the moment. - virtual float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const = 0; + public: + virtual ~RayCastingInterface() = default; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. - virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), - std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; + /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the + /// target vector hits the collision shape and then calculates distance from the intersection point. + /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be + /// successful. \note Only Actor targets are supported at the moment. + virtual float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const = 0; - virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; + /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all + /// other actors. + virtual RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, + const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), + const std::vector& targets = std::vector(), int mask = CollisionType_Default, + int group = 0xff) const = 0; - /// Return true if actor1 can see actor2. - virtual bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const = 0; + RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask) const + { + return castRay(from, to, MWWorld::ConstPtr(), std::vector(), mask); + } + + virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, + int mask = CollisionType_Default, int group = 0xff) const = 0; + + /// Return true if actor1 can see actor2. + virtual bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const = 0; }; } diff --git a/apps/openmw/mwphysics/stepper.cpp b/apps/openmw/mwphysics/stepper.cpp index 1f53c1ac5..5b7cde301 100644 --- a/apps/openmw/mwphysics/stepper.cpp +++ b/apps/openmw/mwphysics/stepper.cpp @@ -3,44 +3,49 @@ #include #include +#include + #include "collisiontype.hpp" #include "constants.hpp" #include "movementsolver.hpp" namespace MWPhysics { - static bool canStepDown(const ActorTracer &stepper) + static bool canStepDown(const ActorTracer& stepper) { if (!stepper.mHitObject) return false; - static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); + static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(Constants::sMaxSlope)); if (stepper.mPlaneNormal.z() <= sMaxSlopeCos) return false; return stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor; } - Stepper::Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj) + Stepper::Stepper(const btCollisionWorld* colWorld, const btCollisionObject* colObj) : mColWorld(colWorld) , mColObj(colObj) { } - bool Stepper::step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration) + bool Stepper::step( + osg::Vec3f& position, osg::Vec3f& velocity, float& remainingTime, const bool& onGround, bool firstIteration) { - if(velocity.x() == 0.0 && velocity.y() == 0.0) + if (velocity.x() == 0.0 && velocity.y() == 0.0) return false; - // Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the ground. - // This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and just prevent stepping on insane geometry. + // Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the + // ground. This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and + // just prevent stepping on insane geometry. - mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld); + mUpStepper.doTrace( + mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld, onGround); float upDistance = 0; - if(!mUpStepper.mHitObject) - upDistance = sStepSizeUp; - else if(mUpStepper.mFraction*sStepSizeUp > sCollisionMargin) - upDistance = mUpStepper.mFraction*sStepSizeUp - sCollisionMargin; + if (!mUpStepper.mHitObject) + upDistance = Constants::sStepSizeUp; + else if (mUpStepper.mFraction * Constants::sStepSizeUp > sCollisionMargin) + upDistance = mUpStepper.mFraction * Constants::sStepSizeUp - sCollisionMargin; else { return false; @@ -54,75 +59,79 @@ namespace MWPhysics auto normalMove = toMove; auto moveDistance = normalMove.normalize(); // attempt 1: normal movement - // attempt 2: fixed distance movement, only happens on the first movement solver iteration/bounce each frame to avoid a glitch - // attempt 3: further, less tall fixed distance movement, same as above - // If you're making a full conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems with vanilla Morrowind assets. + // attempt 2: fixed distance movement, only happens on the first movement solver iteration/bounce each frame to + // avoid a glitch attempt 3: further, less tall fixed distance movement, same as above If you're making a full + // conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems + // with vanilla Morrowind assets. int attempt = 0; float downStepSize = 0; - while(attempt < 3) + while (attempt < 3) { attempt++; - if(attempt == 1) + if (attempt == 1) tracerDest = tracerPos + toMove; else if (!sDoExtraStairHacks) // early out if we have extra hacks disabled { return false; } - else if(attempt == 2) + else if (attempt == 2) { moveDistance = sMinStep; - tracerDest = tracerPos + normalMove*sMinStep; + tracerDest = tracerPos + normalMove * sMinStep; } - else if(attempt == 3) + else if (attempt == 3) { - if(upDistance > sStepSizeUp) + if (upDistance > Constants::sStepSizeUp) { - upDistance = sStepSizeUp; + upDistance = Constants::sStepSizeUp; tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); } moveDistance = sMinStep2; - tracerDest = tracerPos + normalMove*sMinStep2; + tracerDest = tracerPos + normalMove * sMinStep2; } mTracer.doTrace(mColObj, tracerPos, tracerDest, mColWorld); - if(mTracer.mHitObject) + if (mTracer.mHitObject) { // map against what we hit, minus the safety margin moveDistance *= mTracer.mFraction; - if(moveDistance <= sCollisionMargin) // didn't move enough to accomplish anything + if (moveDistance <= sCollisionMargin) // didn't move enough to accomplish anything { return false; } moveDistance -= sCollisionMargin; - tracerDest = tracerPos + normalMove*moveDistance; + tracerDest = tracerPos + normalMove * moveDistance; // safely eject from what we hit by the safety margin - auto tempDest = tracerDest + mTracer.mPlaneNormal*sCollisionMargin*2; + auto tempDest = tracerDest + mTracer.mPlaneNormal * sCollisionMargin * 2; ActorTracer tempTracer; tempTracer.doTrace(mColObj, tracerDest, tempDest, mColWorld); - if(tempTracer.mFraction > 0.5f) // distance to any object is greater than sCollisionMargin (we checked sCollisionMargin*2 distance) + if (tempTracer.mFraction > 0.5f) // distance to any object is greater than sCollisionMargin (we checked + // sCollisionMargin*2 distance) { - auto effectiveFraction = tempTracer.mFraction*2.0f - 1.0f; - tracerDest += mTracer.mPlaneNormal*sCollisionMargin*effectiveFraction; + auto effectiveFraction = tempTracer.mFraction * 2.0f - 1.0f; + tracerDest += mTracer.mPlaneNormal * sCollisionMargin * effectiveFraction; } } - if(attempt > 2) // do not allow stepping down below original height for attempt 3 + if (attempt > 2) // do not allow stepping down below original height for attempt 3 downStepSize = upDistance; else downStepSize = moveDistance + upDistance + sStepSizeDown; - mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld); + mDownStepper.doTrace( + mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld, onGround); // can't step down onto air, non-walkable-slopes, or actors - // NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were intended to be valid at the bottoms of stairs - // (like the bottoms of the staircases in aldruhn's guild of mages) - // The old code worked around this by trying to do mTracer again with a fixed distance of sMinStep (10.0) but it caused all sorts of other problems. - // Switched back to cylinders to avoid that and similer problems. - if(canStepDown(mDownStepper)) + // NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were + // intended to be valid at the bottoms of stairs (like the bottoms of the staircases in aldruhn's guild of + // mages) The old code worked around this by trying to do mTracer again with a fixed distance of sMinStep + // (10.0) but it caused all sorts of other problems. Switched back to cylinders to avoid that and similer + // problems. + if (canStepDown(mDownStepper)) { break; } @@ -130,12 +139,12 @@ namespace MWPhysics { // do not try attempt 3 if we just tried attempt 2 and the horizontal distance was rather large // (forces actor to get snug against the defective ledge for attempt 3 to be tried) - if(attempt == 2 && moveDistance > upDistance-(mDownStepper.mFraction*downStepSize)) + if (attempt == 2 && moveDistance > upDistance - (mDownStepper.mFraction * downStepSize)) { return false; } // do next attempt if first iteration of movement solver and not out of attempts - if(firstIteration && attempt < 3) + if (firstIteration && attempt < 3) { continue; } @@ -146,18 +155,18 @@ namespace MWPhysics // note: can't downstep onto actors so no need to pick safety margin float downDistance = 0; - if(mDownStepper.mFraction*downStepSize > sCollisionMargin) - downDistance = mDownStepper.mFraction*downStepSize - sCollisionMargin; + if (mDownStepper.mFraction * downStepSize > sCollisionMargin) + downDistance = mDownStepper.mFraction * downStepSize - sCollisionMargin; - if(downDistance-sCollisionMargin-sGroundOffset > upDistance && !onGround) + if (downDistance - sCollisionMargin - sGroundOffset > upDistance && !onGround) return false; auto newpos = tracerDest + osg::Vec3f(0.0f, 0.0f, -downDistance); - if((position-newpos).length2() < sCollisionMargin*sCollisionMargin) + if ((position - newpos).length2() < sCollisionMargin * sCollisionMargin) return false; - if(mTracer.mHitObject) + if (mTracer.mHitObject) { auto planeNormal = mTracer.mPlaneNormal; if (onGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) @@ -171,7 +180,7 @@ namespace MWPhysics position = newpos; - remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance + remainingTime *= (1.0f - mTracer.mFraction); // remaining time is proportional to remaining distance return true; } } diff --git a/apps/openmw/mwphysics/stepper.hpp b/apps/openmw/mwphysics/stepper.hpp index 512493c52..d555ef540 100644 --- a/apps/openmw/mwphysics/stepper.hpp +++ b/apps/openmw/mwphysics/stepper.hpp @@ -16,15 +16,16 @@ namespace MWPhysics class Stepper { private: - const btCollisionWorld *mColWorld; - const btCollisionObject *mColObj; + const btCollisionWorld* mColWorld; + const btCollisionObject* mColObj; ActorTracer mTracer, mUpStepper, mDownStepper; public: - Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj); + Stepper(const btCollisionWorld* colWorld, const btCollisionObject* colObj); - bool step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration); + bool step(osg::Vec3f& position, osg::Vec3f& velocity, float& remainingTime, const bool& onGround, + bool firstIteration); }; } diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 049d026e8..9e60a7c62 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -5,82 +5,120 @@ #include #include -#include "collisiontype.hpp" #include "actor.hpp" #include "actorconvexcallback.hpp" +#include "collisiontype.hpp" namespace MWPhysics { -void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) -{ - const btVector3 btstart = Misc::Convert::toBullet(start); - const btVector3 btend = Misc::Convert::toBullet(end); - - const btTransform &trans = actor->getWorldTransform(); - btTransform from(trans); - btTransform to(trans); - from.setOrigin(btstart); - to.setOrigin(btend); - - const btVector3 motion = btstart-btend; - ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world); - // Inherit the actor's collision group and mask - newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; - newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; - - const btCollisionShape *shape = actor->getCollisionShape(); - assert(shape->isConvex()); - world->convexSweepTest(static_cast(shape), from, to, newTraceCallback); - - // Copy the hit data over to our trace results struct: - if(newTraceCallback.hasHit()) + ActorConvexCallback sweepHelper(const btCollisionObject* actor, const btVector3& from, const btVector3& to, + const btCollisionWorld* world, bool actorFilter) { - mFraction = newTraceCallback.m_closestHitFraction; - mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); - mEndPos = (end-start)*mFraction + start; - mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); - mHitObject = newTraceCallback.m_hitCollisionObject; + const btTransform& trans = actor->getWorldTransform(); + btTransform transFrom(trans); + btTransform transTo(trans); + transFrom.setOrigin(from); + transTo.setOrigin(to); + + const btCollisionShape* shape = actor->getCollisionShape(); + assert(shape->isConvex()); + + const btVector3 motion + = from - to; // FIXME: this is backwards; means ActorConvexCallback is doing dot product tests backwards too + ActorConvexCallback traceCallback(actor, motion, btScalar(0.0), world); + // Inherit the actor's collision group and mask + traceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; + traceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; + if (actorFilter) + traceCallback.m_collisionFilterMask &= ~CollisionType_Actor; + + world->convexSweepTest(static_cast(shape), transFrom, transTo, traceCallback); + return traceCallback; } - else + + void ActorTracer::doTrace(const btCollisionObject* actor, const osg::Vec3f& start, const osg::Vec3f& end, + const btCollisionWorld* world, bool attempt_short_trace) { - mEndPos = end; - mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); - mFraction = 1.0f; - mHitPoint = end; - mHitObject = nullptr; + const btVector3 btstart = Misc::Convert::toBullet(start); + btVector3 btend = Misc::Convert::toBullet(end); + + // Because Bullet's collision trace tests touch *all* geometry in its path, a lot of long collision tests + // will unnecessarily test against complex meshes that are dozens of units away. This wouldn't normally be + // a problem, but bullet isn't the fastest in the world when it comes to doing tests against triangle meshes. + // Therefore, we try out a short trace first, then only fall back to the full length trace if needed. + // This trace needs to be at least a couple units long, but there's no one particular ideal length. + // The length of 2.1 chosen here is a "works well in practice after testing a few random lengths" value. + // (Also, we only do this short test if the intended collision trace is long enough for it to make sense.) + const float fallback_length = 2.1f; + bool doing_short_trace = false; + // For some reason, typical scenes perform a little better if we increase the threshold length for the length + // test. (Multiplying by 2 in 'square distance' units gives us about 1.4x the threshold length. In benchmarks + // this was + // slightly better for the performance of normal scenes than 4.0, and just plain better than 1.0.) + if (attempt_short_trace && (btend - btstart).length2() > fallback_length * fallback_length * 2.0) + { + btend = btstart + (btend - btstart).normalized() * fallback_length; + doing_short_trace = true; + } + + const auto traceCallback = sweepHelper(actor, btstart, btend, world, false); + + // Copy the hit data over to our trace results struct: + if (traceCallback.hasHit()) + { + mFraction = traceCallback.m_closestHitFraction; + // ensure fraction is correct (covers intended distance traveled instead of actual distance traveled) + if (doing_short_trace && (end - start).length2() > 0.0) + mFraction *= (btend - btstart).length() / (end - start).length(); + mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld); + mEndPos = (end - start) * mFraction + start; + mHitPoint = Misc::Convert::toOsg(traceCallback.m_hitPointWorld); + mHitObject = traceCallback.m_hitCollisionObject; + } + else + { + if (doing_short_trace) + { + btend = Misc::Convert::toBullet(end); + const auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); + + if (newTraceCallback.hasHit()) + { + mFraction = newTraceCallback.m_closestHitFraction; + mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); + mEndPos = (end - start) * mFraction + start; + mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); + mHitObject = newTraceCallback.m_hitCollisionObject; + return; + } + } + // fallthrough + mEndPos = end; + mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); + mFraction = 1.0f; + mHitPoint = end; + mHitObject = nullptr; + } } -} -void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) -{ - const btVector3 btstart = Misc::Convert::toBullet(start); - const btVector3 btend = Misc::Convert::toBullet(end); - - const btTransform &trans = actor->getCollisionObject()->getWorldTransform(); - btTransform from(trans.getBasis(), btstart); - btTransform to(trans.getBasis(), btend); - - const btVector3 motion = btstart-btend; - ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); - // Inherit the actor's collision group and mask - newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; - newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; - newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor; - - world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback); - if(newTraceCallback.hasHit()) + void ActorTracer::findGround( + const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { - mFraction = newTraceCallback.m_closestHitFraction; - mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); - mEndPos = (end-start)*mFraction + start; + const auto traceCallback = sweepHelper( + actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true); + if (traceCallback.hasHit()) + { + mFraction = traceCallback.m_closestHitFraction; + mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld); + mEndPos = (end - start) * mFraction + start; + } + else + { + mEndPos = end; + mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); + mFraction = 1.0f; + } } - else - { - mEndPos = end; - mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); - mFraction = 1.0f; - } -} } diff --git a/apps/openmw/mwphysics/trace.h b/apps/openmw/mwphysics/trace.h index 0297c9e07..71475e793 100644 --- a/apps/openmw/mwphysics/trace.h +++ b/apps/openmw/mwphysics/trace.h @@ -6,7 +6,6 @@ class btCollisionObject; class btCollisionWorld; - namespace MWPhysics { class Actor; @@ -20,8 +19,10 @@ namespace MWPhysics float mFraction; - void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); - void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); + void doTrace(const btCollisionObject* actor, const osg::Vec3f& start, const osg::Vec3f& end, + const btCollisionWorld* world, bool attempt_short_trace = false); + void findGround( + const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); }; } diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 59bc32765..d15bf7543 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -1,572 +1,594 @@ #include "actoranimation.hpp" #include -#include #include +#include #include -#include -#include +#include +#include +#include #include #include +#include +#include #include #include #include -#include +#include -#include +#include #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/drawstate.hpp" #include "../mwmechanics/weapontype.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/ptr.hpp" #include "vismask.hpp" namespace MWRender { -ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) - : Animation(ptr, parentNode, resourceSystem) -{ - MWWorld::ContainerStore& store = mPtr.getClass().getContainerStore(mPtr); - - for (MWWorld::ConstContainerStoreIterator iter = store.cbegin(MWWorld::ContainerStore::Type_Light); - iter != store.cend(); ++iter) + ActorAnimation::ActorAnimation( + const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) + : Animation(ptr, parentNode, resourceSystem) { - const ESM::Light* light = iter->get()->mBase; - if (!(light->mData.mFlags & ESM::Light::Carry)) + MWWorld::ContainerStore& store = mPtr.getClass().getContainerStore(mPtr); + + for (MWWorld::ConstContainerStoreIterator iter = store.cbegin(MWWorld::ContainerStore::Type_Light); + iter != store.cend(); ++iter) { - addHiddenItemLight(*iter, light); - } - } - - // Make sure we cleaned object from effects, just in cast if we re-use node - removeEffects(); -} - -ActorAnimation::~ActorAnimation() -{ - for (ItemLightMap::iterator iter = mItemLights.begin(); iter != mItemLights.end(); ++iter) - { - mInsert->removeChild(iter->second); - } - - mScabbard.reset(); - mHolsteredShield.reset(); -} - -PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor) -{ - osg::Group* parent = getBoneByName(bonename); - if (!parent) - return nullptr; - - osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); - - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); - if (found == nodeMap.end()) - return PartHolderPtr(); - - if (enchantedGlow) - mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); - - return PartHolderPtr(new PartHolder(instance)); -} - -std::string ActorAnimation::getShieldMesh(MWWorld::ConstPtr shield) const -{ - std::string mesh = shield.getClass().getModel(shield); - const ESM::Armor *armor = shield.get()->mBase; - const std::vector& bodyparts = armor->mParts.mParts; - if (!bodyparts.empty()) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - - // Try to get shield model from bodyparts first, with ground model as fallback - for (const auto& part : bodyparts) - { - // Assume all creatures use the male mesh. - if (part.mPart != ESM::PRT_Shield || part.mMale.empty()) - continue; - const ESM::BodyPart *bodypart = partStore.search(part.mMale); - if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty()) + const ESM::Light* light = iter->get()->mBase; + if (!(light->mData.mFlags & ESM::Light::Carry)) { - mesh = "meshes\\" + bodypart->mModel; - break; + addHiddenItemLight(*iter, light); } } + + // Make sure we cleaned object from effects, just in cast if we re-use node + removeEffects(); } - if (mesh.empty()) - return mesh; - - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); - if(mResourceSystem->getVFS()->exists(holsteredName)) + ActorAnimation::~ActorAnimation() { - osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); - SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); - shieldTemplate->accept(findVisitor); - osg::ref_ptr sheathNode = findVisitor.mFoundNode; - if(!sheathNode) - return std::string(); + removeFromSceneImpl(); } - return mesh; -} - -bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const -{ - static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); - if (shieldSheathing) + PartHolderPtr ActorAnimation::attachMesh( + const std::string& model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor) { - const MWWorld::Class &cls = mPtr.getClass(); - MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); - if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell) + osg::Group* parent = getBoneByName(bonename); + if (!parent) + return nullptr; + + osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); + + const NodeMap& nodeMap = getNodeMap(); + NodeMap::const_iterator found = nodeMap.find(bonename); + if (found == nodeMap.end()) + return {}; + + if (enchantedGlow) + mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); + + return std::make_unique(instance); + } + + osg::ref_ptr ActorAnimation::attach( + const std::string& model, std::string_view bonename, std::string_view bonefilter, bool isLight) + { + osg::ref_ptr templateNode = mResourceSystem->getSceneManager()->getTemplate(model); + + const NodeMap& nodeMap = getNodeMap(); + auto found = nodeMap.find(bonename); + if (found == nodeMap.end()) + throw std::runtime_error("Can't find attachment node " + std::string{ bonename }); + if (isLight) { - SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); - mObjectRoot->accept(findVisitor); - if (findVisitor.mFoundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) - { - const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); - const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Armor).name() && !getShieldMesh(*shield).empty()) - { - if(stats.getDrawState() != MWMechanics::DrawState_Weapon) - return false; + osg::Quat rotation(osg::DegreesToRadians(-90.f), osg::Vec3f(1, 0, 0)); + return SceneUtil::attach( + templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation); + } + return SceneUtil::attach( + templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); + } - if (weapon != inv.end()) - { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) - { - const MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); - } - else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) - return true; - } + std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const + { + const ESM::Armor* armor = shield.get()->mBase; + const std::vector& bodyparts = armor->mParts.mParts; + // Try to recover the body part model, use ground model as a fallback otherwise. + if (!bodyparts.empty()) + { + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const MWWorld::Store& partStore = store.get(); + for (const auto& part : bodyparts) + { + if (part.mPart != ESM::PRT_Shield) + continue; + + const ESM::RefId* bodypartName = nullptr; + if (female && !part.mFemale.empty()) + bodypartName = &part.mFemale; + else if (!part.mMale.empty()) + bodypartName = &part.mMale; + + if (bodypartName && !bodypartName->empty()) + { + const ESM::BodyPart* bodypart = partStore.search(*bodypartName); + if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) + return std::string(); + if (!bodypart->mModel.empty()) + return Misc::ResourceHelpers::correctMeshPath( + bodypart->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); } } } + return shield.getClass().getModel(shield); } - return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); -} - -void ActorAnimation::updateHolsteredShield(bool showCarriedLeft) -{ - static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); - if (!shieldSheathing) - return; - - if (!mPtr.getClass().hasInventoryStore(mPtr)) - return; - - mHolsteredShield.reset(); - - if (showCarriedLeft) - return; - - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) - return; - - // Can not show holdstered shields with two-handed weapons at all - const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end()) - return; - - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) + std::string ActorAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const { - const MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - if (MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded) - return; - } + std::string mesh = getShieldMesh(shield, false); - std::string mesh = getShieldMesh(*shield); - if (mesh.empty()) - return; + if (mesh.empty()) + return mesh; - std::string boneName = "Bip01 AttachShield"; - osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); - bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); - - // If we have no dedicated sheath model, use basic shield model as fallback. - if (!mResourceSystem->getVFS()->exists(holsteredName)) - mHolsteredShield = attachMesh(mesh, boneName, isEnchanted, &glowColor); - else - mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted, &glowColor); - - if (!mHolsteredShield) - return; - - SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); - mHolsteredShield->getNode()->accept(findVisitor); - osg::Group* shieldNode = findVisitor.mFoundNode; - - // If mesh author declared an empty sheath node, use transformation from this node, but use the common shield mesh. - // This approach allows to tweak shield position without need to store the whole shield mesh in the _sh file. - if (shieldNode && !shieldNode->getNumChildren()) - { - osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, shieldNode); - if (isEnchanted) - SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor); - } - - if (mAlpha != 1.f) - mResourceSystem->getSceneManager()->recreateShaders(mHolsteredShield->getNode()); -} - -bool ActorAnimation::useShieldAnimations() const -{ - static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); - if (!shieldSheathing) - return false; - - const MWWorld::Class &cls = mPtr.getClass(); - if (!cls.hasInventoryStore(mPtr)) - return false; - - if (getTextKeyTime("shield: equip attach") < 0 || getTextKeyTime("shield: unequip detach") < 0) - return false; - - const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); - const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (weapon != inv.end() && shield != inv.end() && - shield->getTypeName() == typeid(ESM::Armor).name() && - !getShieldMesh(*shield).empty()) - { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) + std::string holsteredName = mesh; + holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + if (mResourceSystem->getVFS()->exists(holsteredName)) { - const MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); - } - else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) - return true; - } - - return false; -} - -osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) -{ - if (!mObjectRoot) - return nullptr; - - SceneUtil::FindByNameVisitor findVisitor (boneName); - mObjectRoot->accept(findVisitor); - - return findVisitor.mFoundNode; -} - -std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon) -{ - std::string boneName; - if(weapon.isEmpty()) - return boneName; - - const std::string &type = weapon.getClass().getTypeName(); - if(type == typeid(ESM::Weapon).name()) - { - const MWWorld::LiveCellRef *ref = weapon.get(); - int weaponType = ref->mBase->mData.mType; - return MWMechanics::getWeaponType(weaponType)->mSheathingBone; - } - - return boneName; -} - -void ActorAnimation::resetControllers(osg::Node* node) -{ - if (node == nullptr) - return; - - std::shared_ptr src; - src.reset(new NullAnimationTime); - SceneUtil::AssignControllerSourcesVisitor removeVisitor(src); - node->accept(removeVisitor); -} - -void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) -{ - static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); - if (!weaponSheathing) - return; - - if (!mPtr.getClass().hasInventoryStore(mPtr)) - return; - - mScabbard.reset(); - - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) - return; - - // Since throwing weapons stack themselves, do not show such weapon itself - int type = weapon->get()->mBase->mData.mType; - if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) - showHolsteredWeapons = false; - - std::string mesh = weapon->getClass().getModel(*weapon); - std::string scabbardName = mesh; - - std::string boneName = getHolsteredWeaponBoneName(*weapon); - if (mesh.empty() || boneName.empty()) - return; - - // If the scabbard is not found, use a weapon mesh as fallback. - // Note: it is unclear how to handle time for controllers attached to bodyparts, so disable them for now. - // We use the similar approach for other bodyparts. - scabbardName = scabbardName.replace(scabbardName.size()-4, 4, "_sh.nif"); - bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); - if(!mResourceSystem->getVFS()->exists(scabbardName)) - { - if (showHolsteredWeapons) - { - osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); - mScabbard = attachMesh(mesh, boneName, isEnchanted, &glowColor); - if (mScabbard) - resetControllers(mScabbard->getNode()); + osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); + SceneUtil::FindByNameVisitor findVisitor("Bip01 Sheath"); + shieldTemplate->accept(findVisitor); + osg::ref_ptr sheathNode = findVisitor.mFoundNode; + if (!sheathNode) + return std::string(); } - return; + return mesh; } - mScabbard = attachMesh(scabbardName, boneName); - if (mScabbard) - resetControllers(mScabbard->getNode()); - - osg::Group* weaponNode = getBoneByName("Bip01 Weapon"); - if (!weaponNode) - return; - - // When we draw weapon, hide the Weapon node from sheath model. - // Otherwise add the enchanted glow to it. - if (!showHolsteredWeapons) + bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const { - weaponNode->setNodeMask(0); - } - else - { - // If mesh author declared empty weapon node, use transformation from this node, but use the common weapon mesh. - // This approach allows to tweak weapon position without need to store the whole weapon mesh in the _sh file. - if (!weaponNode->getNumChildren()) + if (Settings::game().mShieldSheathing) { - osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode); - resetControllers(fallbackNode); - } - - if (isEnchanted) - { - osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); - mGlowUpdater = SceneUtil::addEnchantedGlow(weaponNode, mResourceSystem, glowColor); - } - } -} - -void ActorAnimation::updateQuiver() -{ - static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); - if (!weaponSheathing) - return; - - if (!mPtr.getClass().hasInventoryStore(mPtr)) - return; - - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) - return; - - std::string mesh = weapon->getClass().getModel(*weapon); - std::string boneName = getHolsteredWeaponBoneName(*weapon); - if (mesh.empty() || boneName.empty()) - return; - - osg::Group* ammoNode = getBoneByName("Bip01 Ammo"); - if (!ammoNode) - return; - - // Special case for throwing weapons - they do not use ammo, but they stack themselves - bool suitableAmmo = false; - MWWorld::ConstContainerStoreIterator ammo = weapon; - unsigned int ammoCount = 0; - int type = weapon->get()->mBase->mData.mType; - const auto& weaponType = MWMechanics::getWeaponType(type); - if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) - { - ammoCount = ammo->getRefData().getCount(); - osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone); - if (throwingWeaponNode && throwingWeaponNode->getNumChildren()) - ammoCount--; - - suitableAmmo = true; - } - else - { - ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - - ammoCount = ammo->getRefData().getCount(); - bool arrowAttached = isArrowAttached(); - if (arrowAttached) - ammoCount--; - - suitableAmmo = ammo->get()->mBase->mData.mType == weaponType->mAmmoType; - } - - if (!suitableAmmo) - return; - - // We should not show more ammo than equipped and more than quiver mesh has - ammoCount = std::min(ammoCount, ammoNode->getNumChildren()); - - // Remove existing ammo nodes - for (unsigned int i=0; igetNumChildren(); ++i) - { - osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); - if (!arrowNode->getNumChildren()) - continue; - - osg::ref_ptr arrowChildNode = arrowNode->getChild(0); - arrowNode->removeChild(arrowChildNode); - } - - // Add new ones - osg::Vec4f glowColor = ammo->getClass().getEnchantmentColor(*ammo); - std::string model = ammo->getClass().getModel(*ammo); - for (unsigned int i=0; i arrowNode = ammoNode->getChild(i)->asGroup(); - osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); - if (!ammo->getClass().getEnchantment(*ammo).empty()) - mGlowUpdater = SceneUtil::addEnchantedGlow(arrow, mResourceSystem, glowColor); - } -} - -void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) -{ - if (item.getTypeName() == typeid(ESM::Light).name()) - { - const ESM::Light* light = item.get()->mBase; - if (!(light->mData.mFlags & ESM::Light::Carry)) - { - addHiddenItemLight(item, light); - } - } - - if (!mPtr.getClass().hasInventoryStore(mPtr)) - return; - - // If the count of equipped ammo or throwing weapon was changed, we should update quiver - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) - return; - - MWWorld::ConstContainerStoreIterator ammo = inv.end(); - int type = weapon->get()->mBase->mData.mType; - if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) - ammo = weapon; - else - ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - - if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId()) - updateQuiver(); -} - -void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/) -{ - if (item.getTypeName() == typeid(ESM::Light).name()) - { - ItemLightMap::iterator iter = mItemLights.find(item); - if (iter != mItemLights.end()) - { - if (!item.getRefData().getCount()) + const MWWorld::Class& cls = mPtr.getClass(); + MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr); + if (cls.hasInventoryStore(mPtr) && stats.getDrawState() == MWMechanics::DrawState::Nothing) { - removeHiddenItemLight(item); + SceneUtil::FindByNameVisitor findVisitor("Bip01 AttachShield"); + mObjectRoot->accept(findVisitor); + if (findVisitor.mFoundNode) + { + const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); + const MWWorld::ConstContainerStoreIterator shield + = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId + && !getSheathedShieldMesh(*shield).empty()) + return false; + } + } + } + + return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); + } + + void ActorAnimation::updateHolsteredShield(bool showCarriedLeft) + { + if (!Settings::game().mShieldSheathing) + return; + + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return; + + mHolsteredShield.reset(); + + if (showCarriedLeft) + return; + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield == inv.end() || shield->getType() != ESM::Armor::sRecordId) + return; + + // Can not show holdstered shields with two-handed weapons at all + const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end()) + return; + + auto type = weapon->getType(); + if (type == ESM::Weapon::sRecordId) + { + const MWWorld::LiveCellRef* ref = weapon->get(); + ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; + if (MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded) + return; + } + + std::string mesh = getSheathedShieldMesh(*shield); + if (mesh.empty()) + return; + + std::string_view boneName = "Bip01 AttachShield"; + osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); + std::string holsteredName = mesh; + holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); + + // If we have no dedicated sheath model, use basic shield model as fallback. + if (!mResourceSystem->getVFS()->exists(holsteredName)) + mHolsteredShield = attachMesh(mesh, boneName, isEnchanted, &glowColor); + else + mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted, &glowColor); + + if (!mHolsteredShield) + return; + + SceneUtil::FindByNameVisitor findVisitor("Bip01 Sheath"); + mHolsteredShield->getNode()->accept(findVisitor); + osg::Group* shieldNode = findVisitor.mFoundNode; + + // If mesh author declared an empty sheath node, use transformation from this node, but use the common shield + // mesh. This approach allows to tweak shield position without need to store the whole shield mesh in the _sh + // file. + if (shieldNode && !shieldNode->getNumChildren()) + { + osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, shieldNode); + if (isEnchanted) + SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor); + } + } + + bool ActorAnimation::useShieldAnimations() const + { + if (!Settings::game().mShieldSheathing) + return false; + + const MWWorld::Class& cls = mPtr.getClass(); + if (!cls.hasInventoryStore(mPtr)) + return false; + + if (getTextKeyTime("shield: equip attach") < 0 || getTextKeyTime("shield: unequip detach") < 0) + return false; + + const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); + const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (weapon != inv.end() && shield != inv.end() && shield->getType() == ESM::Armor::sRecordId + && !getSheathedShieldMesh(*shield).empty()) + { + auto type = weapon->getType(); + if (type == ESM::Weapon::sRecordId) + { + const MWWorld::LiveCellRef* ref = weapon->get(); + ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; + return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); + } + else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId) + return true; + } + + return false; + } + + osg::Group* ActorAnimation::getBoneByName(std::string_view boneName) const + { + if (!mObjectRoot) + return nullptr; + + SceneUtil::FindByNameVisitor findVisitor(boneName); + mObjectRoot->accept(findVisitor); + + return findVisitor.mFoundNode; + } + + std::string_view ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon) + { + if (weapon.isEmpty()) + return {}; + + auto type = weapon.getClass().getType(); + if (type == ESM::Weapon::sRecordId) + { + const MWWorld::LiveCellRef* ref = weapon.get(); + int weaponType = ref->mBase->mData.mType; + return MWMechanics::getWeaponType(weaponType)->mSheathingBone; + } + + return {}; + } + + void ActorAnimation::resetControllers(osg::Node* node) + { + if (node == nullptr) + return; + + SceneUtil::ForceControllerSourcesVisitor removeVisitor(std::make_shared()); + node->accept(removeVisitor); + } + + void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) + { + if (!Settings::game().mWeaponSheathing) + return; + + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return; + + mScabbard.reset(); + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) + return; + + // Since throwing weapons stack themselves, do not show such weapon itself + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) + showHolsteredWeapons = false; + + std::string mesh = weapon->getClass().getModel(*weapon); + std::string scabbardName = mesh; + + std::string_view boneName = getHolsteredWeaponBoneName(*weapon); + if (mesh.empty() || boneName.empty()) + return; + + // If the scabbard is not found, use a weapon mesh as fallback. + // Note: it is unclear how to handle time for controllers attached to bodyparts, so disable them for now. + // We use the similar approach for other bodyparts. + scabbardName = scabbardName.replace(scabbardName.size() - 4, 4, "_sh.nif"); + bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); + if (!mResourceSystem->getVFS()->exists(scabbardName)) + { + if (showHolsteredWeapons) + { + osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); + mScabbard = attachMesh(mesh, boneName, isEnchanted, &glowColor); + if (mScabbard) + resetControllers(mScabbard->getNode()); + } + + return; + } + + mScabbard = attachMesh(scabbardName, boneName); + if (mScabbard) + resetControllers(mScabbard->getNode()); + + osg::Group* weaponNode = getBoneByName("Bip01 Weapon"); + if (!weaponNode) + return; + + // When we draw weapon, hide the Weapon node from sheath model. + // Otherwise add the enchanted glow to it. + if (!showHolsteredWeapons) + { + weaponNode->setNodeMask(0); + } + else + { + // If mesh author declared empty weapon node, use transformation from this node, but use the common weapon + // mesh. This approach allows to tweak weapon position without need to store the whole weapon mesh in the + // _sh file. + if (!weaponNode->getNumChildren()) + { + osg::ref_ptr fallbackNode + = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode); + resetControllers(fallbackNode); + } + + if (isEnchanted) + { + osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); + mGlowUpdater = SceneUtil::addEnchantedGlow(weaponNode, mResourceSystem, glowColor); } } } - if (!mPtr.getClass().hasInventoryStore(mPtr)) - return; - - // If the count of equipped ammo or throwing weapon was changed, we should update quiver - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) - return; - - MWWorld::ConstContainerStoreIterator ammo = inv.end(); - int type = weapon->get()->mBase->mData.mType; - if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) - ammo = weapon; - else - ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - - if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId()) - updateQuiver(); -} - -void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight) -{ - if (mItemLights.find(item) != mItemLights.end()) - return; - - bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); - - osg::Vec4f ambient(1,1,1,1); - osg::ref_ptr lightSource = SceneUtil::createLightSource(esmLight, Mask_Lighting, exterior, ambient); - - mInsert->addChild(lightSource); - - if (mLightListCallback && mPtr == MWMechanics::getPlayer()) - mLightListCallback->getIgnoredLightSources().insert(lightSource.get()); - - mItemLights.insert(std::make_pair(item, lightSource)); -} - -void ActorAnimation::removeHiddenItemLight(const MWWorld::ConstPtr& item) -{ - ItemLightMap::iterator iter = mItemLights.find(item); - if (iter == mItemLights.end()) - return; - - if (mLightListCallback && mPtr == MWMechanics::getPlayer()) + void ActorAnimation::updateQuiver() { - std::set::iterator ignoredIter = mLightListCallback->getIgnoredLightSources().find(iter->second.get()); - if (ignoredIter != mLightListCallback->getIgnoredLightSources().end()) - mLightListCallback->getIgnoredLightSources().erase(ignoredIter); + if (!Settings::game().mWeaponSheathing) + return; + + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return; + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) + return; + + std::string mesh = weapon->getClass().getModel(*weapon); + std::string_view boneName = getHolsteredWeaponBoneName(*weapon); + if (mesh.empty() || boneName.empty()) + return; + + osg::Group* ammoNode = getBoneByName("Bip01 Ammo"); + if (!ammoNode) + return; + + // Special case for throwing weapons - they do not use ammo, but they stack themselves + bool suitableAmmo = false; + MWWorld::ConstContainerStoreIterator ammo = weapon; + unsigned int ammoCount = 0; + int type = weapon->get()->mBase->mData.mType; + const auto& weaponType = MWMechanics::getWeaponType(type); + if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) + { + ammoCount = ammo->getRefData().getCount(); + osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone); + if (throwingWeaponNode && throwingWeaponNode->getNumChildren()) + ammoCount--; + + suitableAmmo = true; + } + else + { + ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + + ammoCount = ammo->getRefData().getCount(); + bool arrowAttached = isArrowAttached(); + if (arrowAttached) + ammoCount--; + + suitableAmmo = ammo->get()->mBase->mData.mType == weaponType->mAmmoType; + } + + if (!suitableAmmo) + return; + + // We should not show more ammo than equipped and more than quiver mesh has + ammoCount = std::min(ammoCount, ammoNode->getNumChildren()); + + // Remove existing ammo nodes + for (unsigned int i = 0; i < ammoNode->getNumChildren(); ++i) + { + osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); + if (!arrowNode->getNumChildren()) + continue; + + osg::ref_ptr arrowChildNode = arrowNode->getChild(0); + arrowNode->removeChild(arrowChildNode); + } + + // Add new ones + osg::Vec4f glowColor = ammo->getClass().getEnchantmentColor(*ammo); + std::string model = ammo->getClass().getModel(*ammo); + for (unsigned int i = 0; i < ammoCount; ++i) + { + osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); + osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); + if (!ammo->getClass().getEnchantment(*ammo).empty()) + mGlowUpdater = SceneUtil::addEnchantedGlow(arrow, mResourceSystem, glowColor); + } } - mInsert->removeChild(iter->second); - mItemLights.erase(iter); -} + void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) + { + if (item.getType() == ESM::Light::sRecordId) + { + const ESM::Light* light = item.get()->mBase; + if (!(light->mData.mFlags & ESM::Light::Carry)) + { + addHiddenItemLight(item, light); + } + } + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return; + + // If the count of equipped ammo or throwing weapon was changed, we should update quiver + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) + return; + + MWWorld::ConstContainerStoreIterator ammo = inv.end(); + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) + ammo = weapon; + else + ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + + if (ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId()) + updateQuiver(); + } + + void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/) + { + if (item.getType() == ESM::Light::sRecordId) + { + ItemLightMap::iterator iter = mItemLights.find(item); + if (iter != mItemLights.end()) + { + if (!item.getRefData().getCount()) + { + removeHiddenItemLight(item); + } + } + } + + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return; + + // If the count of equipped ammo or throwing weapon was changed, we should update quiver + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) + return; + + MWWorld::ConstContainerStoreIterator ammo = inv.end(); + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) + ammo = weapon; + else + ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + + if (ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId()) + updateQuiver(); + } + + void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight) + { + if (mItemLights.find(item) != mItemLights.end()) + return; + + bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); + + osg::Vec4f ambient(1, 1, 1, 1); + osg::ref_ptr lightSource + = SceneUtil::createLightSource(SceneUtil::LightCommon(*esmLight), Mask_Lighting, exterior, ambient); + + mInsert->addChild(lightSource); + + if (mLightListCallback && mPtr == MWMechanics::getPlayer()) + mLightListCallback->getIgnoredLightSources().insert(lightSource.get()); + + mItemLights.insert(std::make_pair(item, lightSource)); + } + + void ActorAnimation::removeHiddenItemLight(const MWWorld::ConstPtr& item) + { + ItemLightMap::iterator iter = mItemLights.find(item); + if (iter == mItemLights.end()) + return; + + if (mLightListCallback && mPtr == MWMechanics::getPlayer()) + { + std::set::iterator ignoredIter + = mLightListCallback->getIgnoredLightSources().find(iter->second.get()); + if (ignoredIter != mLightListCallback->getIgnoredLightSources().end()) + mLightListCallback->getIgnoredLightSources().erase(ignoredIter); + } + + mInsert->removeChild(iter->second); + mItemLights.erase(iter); + } + + void ActorAnimation::removeFromScene() + { + removeFromSceneImpl(); + Animation::removeFromScene(); + } + + void ActorAnimation::removeFromSceneImpl() + { + for (const auto& [k, v] : mItemLights) + mInsert->removeChild(v); + } } diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index e149e4414..b6586d4ea 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -28,10 +28,11 @@ namespace SceneUtil namespace MWRender { -class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener -{ + class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener + { public: - ActorAnimation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); + ActorAnimation( + const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); virtual ~ActorAnimation(); void itemAdded(const MWWorld::ConstPtr& item, int count) override; @@ -40,19 +41,25 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener bool useShieldAnimations() const override; bool updateCarriedLeftVisible(const int weaptype) const override; + void removeFromScene() override; + protected: - osg::Group* getBoneByName(const std::string& boneName); + osg::Group* getBoneByName(std::string_view boneName) const; virtual void updateHolsteredWeapon(bool showHolsteredWeapons); virtual void updateHolsteredShield(bool showCarriedLeft); virtual void updateQuiver(); - virtual std::string getShieldMesh(MWWorld::ConstPtr shield) const; - virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); - virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor); - virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename) + std::string getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const; + virtual std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const; + virtual std::string_view getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); + virtual PartHolderPtr attachMesh( + const std::string& model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor); + virtual PartHolderPtr attachMesh(const std::string& model, std::string_view bonename) { - osg::Vec4f stubColor = osg::Vec4f(0,0,0,0); + osg::Vec4f stubColor = osg::Vec4f(0, 0, 0, 0); return attachMesh(model, bonename, false, &stubColor); - }; + } + osg::ref_ptr attach( + const std::string& model, std::string_view bonename, std::string_view bonefilter, bool isLight); PartHolderPtr mScabbard; PartHolderPtr mHolsteredShield; @@ -61,10 +68,11 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight); void removeHiddenItemLight(const MWWorld::ConstPtr& item); void resetControllers(osg::Node* node); + void removeFromSceneImpl(); - typedef std::map > ItemLightMap; + typedef std::map> ItemLightMap; ItemLightMap mItemLights; -}; + }; } diff --git a/apps/openmw/mwrender/actorspaths.cpp b/apps/openmw/mwrender/actorspaths.cpp index 35b255355..68cf73fb5 100644 --- a/apps/openmw/mwrender/actorspaths.cpp +++ b/apps/openmw/mwrender/actorspaths.cpp @@ -1,10 +1,18 @@ #include "actorspaths.hpp" #include "vismask.hpp" +#include +#include +#include #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include + namespace MWRender { ActorsPaths::ActorsPaths(const osg::ref_ptr& root, bool enabled) @@ -30,42 +38,43 @@ namespace MWRender } void ActorsPaths::update(const MWWorld::ConstPtr& actor, const std::deque& path, - const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, - const DetourNavigator::Settings& settings) + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, + const DetourNavigator::Settings& settings) { if (!mEnabled) return; - const auto group = mGroups.find(actor); + const auto group = mGroups.find(actor.mRef); if (group != mGroups.end()) - mRootNode->removeChild(group->second); + mRootNode->removeChild(group->second.mNode); - const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings); + auto newGroup = SceneUtil::createAgentPathGroup(path, agentBounds, start, end, settings.mRecast); if (newGroup) { + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(newGroup, "debug"); newGroup->setNodeMask(Mask_Debug); mRootNode->addChild(newGroup); - mGroups[actor] = newGroup; + mGroups[actor.mRef] = Group{ actor.mCell, std::move(newGroup) }; } } void ActorsPaths::remove(const MWWorld::ConstPtr& actor) { - const auto group = mGroups.find(actor); + const auto group = mGroups.find(actor.mRef); if (group != mGroups.end()) { - mRootNode->removeChild(group->second); + mRootNode->removeChild(group->second.mNode); mGroups.erase(group); } } void ActorsPaths::removeCell(const MWWorld::CellStore* const store) { - for (auto it = mGroups.begin(); it != mGroups.end(); ) + for (auto it = mGroups.begin(); it != mGroups.end();) { - if (it->first.getCell() == store) + if (it->second.mCell == store) { - mRootNode->removeChild(it->second); + mRootNode->removeChild(it->second.mNode); it = mGroups.erase(it); } else @@ -75,25 +84,23 @@ namespace MWRender void ActorsPaths::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) { - const auto it = mGroups.find(old); + const auto it = mGroups.find(old.mRef); if (it == mGroups.end()) return; - auto group = std::move(it->second); - mGroups.erase(it); - mGroups.insert(std::make_pair(updated, std::move(group))); + it->second.mCell = updated.mCell; } void ActorsPaths::enable() { - std::for_each(mGroups.begin(), mGroups.end(), - [&] (const Groups::value_type& v) { mRootNode->addChild(v.second); }); + std::for_each( + mGroups.begin(), mGroups.end(), [&](const Groups::value_type& v) { mRootNode->addChild(v.second.mNode); }); mEnabled = true; } void ActorsPaths::disable() { std::for_each(mGroups.begin(), mGroups.end(), - [&] (const Groups::value_type& v) { mRootNode->removeChild(v.second); }); + [&](const Groups::value_type& v) { mRootNode->removeChild(v.second.mNode); }); mEnabled = false; } } diff --git a/apps/openmw/mwrender/actorspaths.hpp b/apps/openmw/mwrender/actorspaths.hpp index 1f61834d4..d18197b97 100644 --- a/apps/openmw/mwrender/actorspaths.hpp +++ b/apps/openmw/mwrender/actorspaths.hpp @@ -3,18 +3,23 @@ #include -#include - #include -#include #include +#include +#include namespace osg { class Group; } +namespace DetourNavigator +{ + struct Settings; + struct AgentBounds; +} + namespace MWRender { class ActorsPaths @@ -26,8 +31,8 @@ namespace MWRender bool toggle(); void update(const MWWorld::ConstPtr& actor, const std::deque& path, - const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, - const DetourNavigator::Settings& settings); + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, + const DetourNavigator::Settings& settings); void remove(const MWWorld::ConstPtr& actor); @@ -40,7 +45,13 @@ namespace MWRender void disable(); private: - using Groups = std::map>; + struct Group + { + const MWWorld::CellStore* mCell; + osg::ref_ptr mNode; + }; + + using Groups = std::map; osg::ref_ptr mRootNode; Groups mGroups; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b578ee25b..115f49a0b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -3,52 +3,76 @@ #include #include -#include #include +#include +#include #include -#include +#include #include -#include #include +#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include #include #include #include -#include -#include #include #include -#include #include +#include +#include #include +#include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority -#include "vismask.hpp" -#include "util.hpp" #include "rotatecontroller.hpp" +#include "util.hpp" +#include "vismask.hpp" namespace { + class MarkDrawablesVisitor : public osg::NodeVisitor + { + public: + MarkDrawablesVisitor(osg::Node::NodeMask mask) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mMask(mask) + { + } + + void apply(osg::Drawable& drawable) override { drawable.setNodeMask(mMask); } + + private: + osg::Node::NodeMask mMask = 0; + }; /// Removes all particle systems and related nodes in a subgraph. class RemoveParticlesVisitor : public osg::NodeVisitor @@ -56,9 +80,10 @@ namespace public: RemoveParticlesVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { } + { + } - void apply(osg::Node &node) override + void apply(osg::Node& node) override { if (dynamic_cast(&node)) mToRemove.emplace_back(&node); @@ -84,25 +109,26 @@ namespace } private: - std::vector > mToRemove; + std::vector> mToRemove; }; - class DayNightCallback : public osg::NodeCallback + class DayNightCallback : public SceneUtil::NodeCallback { public: - DayNightCallback() : mCurrentState(0) + DayNightCallback() + : mCurrentState(0) { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Switch* node, osg::NodeVisitor* nv) { unsigned int state = MWBase::Environment::get().getWorld()->getNightDayMode(); - const unsigned int newState = node->asGroup()->getNumChildren() > state ? state : 0; + const unsigned int newState = node->getNumChildren() > state ? state : 0; if (newState != mCurrentState) { mCurrentState = newState; - node->asSwitch()->setSingleChildOn(mCurrentState); + node->setSingleChildOn(mCurrentState); } traverse(node, nv); @@ -117,9 +143,10 @@ namespace public: AddSwitchCallbacksVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { } + { + } - void apply(osg::Switch &switchNode) override + void apply(osg::Switch& switchNode) override { if (switchNode.getName() == Constants::NightDayLabel) switchNode.addUpdateCallback(new DayNightCallback()); @@ -147,13 +174,20 @@ namespace } }; - float calcAnimVelocity(const SceneUtil::TextKeyMap& keys, SceneUtil::KeyframeController *nonaccumctrl, - const osg::Vec3f& accum, const std::string &groupname) + bool equalsParts(std::string_view value, std::string_view s1, std::string_view s2, std::string_view s3 = {}) + { + if (value.starts_with(s1)) + { + value = value.substr(s1.size()); + if (value.starts_with(s2)) + return value.substr(s2.size()) == s3; + } + return false; + } + + float calcAnimVelocity(const SceneUtil::TextKeyMap& keys, SceneUtil::KeyframeController* nonaccumctrl, + const osg::Vec3f& accum, std::string_view groupname) { - const std::string start = groupname+": start"; - const std::string loopstart = groupname+": loop start"; - const std::string loopstop = groupname+": loop stop"; - const std::string stop = groupname+": stop"; float starttime = std::numeric_limits::max(); float stoptime = 0.0f; @@ -164,9 +198,10 @@ namespace // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. auto keyiter = keys.rbegin(); - while(keyiter != keys.rend()) + while (keyiter != keys.rend()) { - if(keyiter->second == start || keyiter->second == loopstart) + if (equalsParts(keyiter->second, groupname, ": start") + || equalsParts(keyiter->second, groupname, ": loop start")) { starttime = keyiter->first; break; @@ -174,11 +209,11 @@ namespace ++keyiter; } keyiter = keys.rbegin(); - while(keyiter != keys.rend()) + while (keyiter != keys.rend()) { - if (keyiter->second == stop) + if (equalsParts(keyiter->second, groupname, ": stop")) stoptime = keyiter->first; - else if (keyiter->second == loopstop) + else if (equalsParts(keyiter->second, groupname, ": loop stop")) { stoptime = keyiter->first; break; @@ -186,43 +221,17 @@ namespace ++keyiter; } - if(stoptime > starttime) + if (stoptime > starttime) { osg::Vec3f startpos = osg::componentMultiply(nonaccumctrl->getTranslation(starttime), accum); osg::Vec3f endpos = osg::componentMultiply(nonaccumctrl->getTranslation(stoptime), accum); - return (startpos-endpos).length() / (stoptime - starttime); + return (startpos - endpos).length() / (stoptime - starttime); } return 0.0f; } - /// @brief Base class for visitors that remove nodes from a scene graph. - /// Subclasses need to fill the mToRemove vector. - /// To use, node->accept(removeVisitor); removeVisitor.remove(); - class RemoveVisitor : public osg::NodeVisitor - { - public: - RemoveVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { - } - - void remove() - { - for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) - { - if (!it->second->removeChild(it->first)) - Log(Debug::Error) << "Error removing " << it->first->getName(); - } - } - - protected: - // - typedef std::vector > RemoveVec; - std::vector > mToRemove; - }; - class GetExtendedBonesVisitor : public osg::NodeVisitor { public: @@ -242,10 +251,10 @@ namespace traverse(node); } - std::vector > mFoundBones; + std::vector> mFoundBones; }; - class RemoveFinishedCallbackVisitor : public RemoveVisitor + class RemoveFinishedCallbackVisitor : public SceneUtil::RemoveVisitor { public: bool mHasMagicEffects; @@ -256,12 +265,9 @@ namespace { } - void apply(osg::Node &node) override - { - traverse(node); - } + void apply(osg::Node& node) override { traverse(node); } - void apply(osg::Group &group) override + void apply(osg::Group& group) override { traverse(group); @@ -280,17 +286,12 @@ namespace } } - void apply(osg::MatrixTransform &node) override - { - traverse(node); - } + void apply(osg::MatrixTransform& node) override { traverse(node); } - void apply(osg::Geometry&) override - { - } + void apply(osg::Geometry&) override {} }; - class RemoveCallbackVisitor : public RemoveVisitor + class RemoveCallbackVisitor : public SceneUtil::RemoveVisitor { public: bool mHasMagicEffects; @@ -309,12 +310,9 @@ namespace { } - void apply(osg::Node &node) override - { - traverse(node); - } + void apply(osg::Node& node) override { traverse(node); } - void apply(osg::Group &group) override + void apply(osg::Group& group) override { traverse(group); @@ -333,14 +331,9 @@ namespace } } - void apply(osg::MatrixTransform &node) override - { - traverse(node); - } + void apply(osg::MatrixTransform& node) override { traverse(node); } - void apply(osg::Geometry&) override - { - } + void apply(osg::Geometry&) override {} private: int mEffectId; @@ -349,7 +342,6 @@ namespace class FindVfxCallbacksVisitor : public osg::NodeVisitor { public: - std::vector mCallbacks; FindVfxCallbacksVisitor() @@ -364,12 +356,9 @@ namespace { } - void apply(osg::Node &node) override - { - traverse(node); - } + void apply(osg::Node& node) override { traverse(node); } - void apply(osg::Group &group) override + void apply(osg::Group& group) override { osg::Callback* callback = group.getUpdateCallback(); if (callback) @@ -386,102 +375,26 @@ namespace traverse(group); } - void apply(osg::MatrixTransform &node) override - { - traverse(node); - } + void apply(osg::MatrixTransform& node) override { traverse(node); } - void apply(osg::Geometry&) override - { - } + void apply(osg::Geometry&) override {} private: int mEffectId; }; - // Removes all drawables from a graph. - class CleanObjectRootVisitor : public RemoveVisitor + osg::ref_ptr getVFXLightModelInstance() { - public: - void apply(osg::Drawable& drw) override + static osg::ref_ptr lightModel = nullptr; + + if (!lightModel) { - applyDrawable(drw); + lightModel = new osg::LightModel; + lightModel->setAmbientIntensity({ 1, 1, 1, 1 }); } - void apply(osg::Group& node) override - { - applyNode(node); - } - void apply(osg::MatrixTransform& node) override - { - applyNode(node); - } - void apply(osg::Node& node) override - { - applyNode(node); - } - - void applyNode(osg::Node& node) - { - if (node.getStateSet()) - node.setStateSet(nullptr); - - if (node.getNodeMask() == 0x1 && node.getNumParents() == 1) - mToRemove.emplace_back(&node, node.getParent(0)); - else - traverse(node); - } - void applyDrawable(osg::Node& node) - { - osg::NodePath::iterator parent = getNodePath().end()-2; - // We know that the parent is a Group because only Groups can have children. - osg::Group* parentGroup = static_cast(*parent); - - // Try to prune nodes that would be empty after the removal - if (parent != getNodePath().begin()) - { - // This could be extended to remove the parent's parent, and so on if they are empty as well. - // But for NIF files, there won't be a benefit since only TriShapes can be set to STATIC dataVariance. - osg::Group* parentParent = static_cast(*(parent - 1)); - if (parentGroup->getNumChildren() == 1 && parentGroup->getDataVariance() == osg::Object::STATIC) - { - mToRemove.emplace_back(parentGroup, parentParent); - return; - } - } - - mToRemove.emplace_back(&node, parentGroup); - } - }; - - class RemoveTriBipVisitor : public RemoveVisitor - { - public: - void apply(osg::Drawable& drw) override - { - applyImpl(drw); - } - - void apply(osg::Group& node) override - { - traverse(node); - } - void apply(osg::MatrixTransform& node) override - { - traverse(node); - } - - void applyImpl(osg::Node& node) - { - const std::string toFind = "tri bip"; - if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) - { - osg::Group* parent = static_cast(*(getNodePath().end()-2)); - // Not safe to remove in apply(), since the visitor is still iterating the child list - mToRemove.emplace_back(&node, parent); - } - } - }; + return lightModel; + } } namespace MWRender @@ -494,21 +407,13 @@ namespace MWRender { } - void setAlpha(const float alpha) - { - mAlpha = alpha; - } - - void setLightSource(const osg::ref_ptr& lightSource) - { - mLightSource = lightSource; - } + void setAlpha(const float alpha) { mAlpha = alpha; } protected: void setDefaults(osg::StateSet* stateset) override { - osg::BlendFunc* blendfunc (new osg::BlendFunc); - stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + osg::BlendFunc* blendfunc(new osg::BlendFunc); + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); @@ -516,30 +421,29 @@ namespace MWRender // FIXME: overriding diffuse/ambient/emissive colors osg::Material* material = new osg::Material; material->setColorMode(osg::Material::OFF); - material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,mAlpha)); - material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("colorMode", 0), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, mAlpha)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->addUniform( + new osg::Uniform("colorMode", 0), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); } void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override { - osg::Material* material = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + osg::Material* material + = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha); - if (mLightSource) - mLightSource->setActorFade(mAlpha); } private: float mAlpha; - osg::ref_ptr mLightSource; }; struct Animation::AnimSource { osg::ref_ptr mKeyframes; - typedef std::map > ControllerMap; + typedef std::map> ControllerMap; ControllerMap mControllerMap[Animation::sNumBlendMasks]; @@ -582,20 +486,18 @@ namespace MWRender } } - class ResetAccumRootCallback : public osg::NodeCallback + class ResetAccumRootCallback : public SceneUtil::NodeCallback { public: - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix mat = transform->getMatrix(); osg::Vec3f position = mat.getTrans(); position = osg::componentMultiply(mResetAxes, position); mat.setTrans(position); transform->setMatrix(mat); - traverse(node, nv); + traverse(transform, nv); } void setAccumulate(const osg::Vec3f& accumulate) @@ -610,7 +512,8 @@ namespace MWRender osg::Vec3f mResetAxes; }; - Animation::Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) + Animation::Animation( + const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : mInsert(parentNode) , mSkeleton(nullptr) , mNodeMapCreated(false) @@ -626,28 +529,15 @@ namespace MWRender , mHasMagicEffects(false) , mAlpha(1.f) { - for(size_t i = 0;i < sNumBlendMasks;i++) - mAnimationTimePtr[i].reset(new AnimationTime); + for (size_t i = 0; i < sNumBlendMasks; i++) + mAnimationTimePtr[i] = std::make_shared(); mLightListCallback = new SceneUtil::LightListCallback; } Animation::~Animation() { - Animation::setLightEffect(0.f); - - if (mObjectRoot) - mInsert->removeChild(mObjectRoot); - } - - MWWorld::ConstPtr Animation::getPtr() const - { - return mPtr; - } - - MWWorld::Ptr Animation::getPtr() - { - return mPtr; + removeFromSceneImpl(); } void Animation::setActive(int active) @@ -656,7 +546,7 @@ namespace MWRender mSkeleton->setActive(static_cast(active)); } - void Animation::updatePtr(const MWWorld::Ptr &ptr) + void Animation::updatePtr(const MWWorld::Ptr& ptr) { mPtr = ptr; } @@ -669,21 +559,22 @@ namespace MWRender mResetAccumRootCallback->setAccumulate(mAccumulate); } - size_t Animation::detectBlendMask(const osg::Node* node) const + // controllerName is used for Collada animated deforming models + size_t Animation::detectBlendMask(const osg::Node* node, const std::string& controllerName) const { - static const char sBlendMaskRoots[sNumBlendMasks][32] = { + static const std::string_view sBlendMaskRoots[sNumBlendMasks] = { "", /* Lower body / character root */ "Bip01 Spine1", /* Torso */ "Bip01 L Clavicle", /* Left arm */ "Bip01 R Clavicle", /* Right arm */ }; - while(node != mObjectRoot) + while (node != mObjectRoot) { - const std::string &name = node->getName(); - for(size_t i = 1;i < sNumBlendMasks;i++) + const std::string& name = node->getName(); + for (size_t i = 1; i < sNumBlendMasks; i++) { - if(name == sBlendMaskRoots[i]) + if (name == sBlendMaskRoots[i] || controllerName == sBlendMaskRoots[i]) return i; } @@ -695,104 +586,106 @@ namespace MWRender return 0; } - const SceneUtil::TextKeyMap &Animation::AnimSource::getTextKeys() const + const SceneUtil::TextKeyMap& Animation::AnimSource::getTextKeys() const { return mKeyframes->mTextKeys; } - void Animation::loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel) + void Animation::loadAllAnimationsInFolder(const std::string& model, const std::string& baseModel) { - const std::map& index = mResourceSystem->getVFS()->getIndex(); - std::string animationPath = model; if (animationPath.find("meshes") == 0) { animationPath.replace(0, 6, "animations"); } - animationPath.replace(animationPath.size()-3, 3, "/"); + animationPath.replace(animationPath.size() - 3, 3, "/"); - mResourceSystem->getVFS()->normalizeFilename(animationPath); - - std::map::const_iterator found = index.lower_bound(animationPath); - while (found != index.end()) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - const std::string& name = found->first; - if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".kf") == 0) - addSingleAnimSource(name, baseModel); - } - else - break; - ++found; + if (Misc::getFileExtension(name) == "kf") + addSingleAnimSource(name, baseModel); } } - void Animation::addAnimSource(const std::string &model, const std::string& baseModel) + void Animation::addAnimSource(std::string_view model, const std::string& baseModel) { - std::string kfname = model; - Misc::StringUtils::lowerCaseInPlace(kfname); + std::string kfname = Misc::StringUtils::lowerCase(model); - if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) - kfname.replace(kfname.size()-4, 4, ".kf"); + if (kfname.ends_with(".nif")) + kfname.replace(kfname.size() - 4, 4, ".kf"); addSingleAnimSource(kfname, baseModel); - static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); - if (useAdditionalSources) + if (Settings::game().mUseAdditionalAnimSources) loadAllAnimationsInFolder(kfname, baseModel); } - void Animation::addSingleAnimSource(const std::string &kfname, const std::string& baseModel) + void Animation::addSingleAnimSource(const std::string& kfname, const std::string& baseModel) { - if(!mResourceSystem->getVFS()->exists(kfname)) + if (!mResourceSystem->getVFS()->exists(kfname)) return; - std::shared_ptr animsrc; - animsrc.reset(new AnimSource); + auto animsrc = std::make_shared(); animsrc->mKeyframes = mResourceSystem->getKeyframeManager()->get(kfname); - if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) + if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() + || animsrc->mKeyframes->mKeyframeControllers.empty()) return; const NodeMap& nodeMap = getNodeMap(); - - for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); - it != animsrc->mKeyframes->mKeyframeControllers.end(); ++it) + const auto& controllerMap = animsrc->mKeyframes->mKeyframeControllers; + for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = controllerMap.begin(); + it != controllerMap.end(); ++it) { std::string bonename = Misc::StringUtils::lowerCase(it->first); NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) { - Log(Debug::Warning) << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << baseModel << " (referenced by " << kfname << ")"; + Log(Debug::Warning) << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << baseModel + << " (referenced by " << kfname << ")"; continue; } osg::Node* node = found->second; - size_t blendMask = detectBlendMask(node); + size_t blendMask = detectBlendMask(node, it->second->getName()); // clone the controller, because each Animation needs its own ControllerSource - osg::ref_ptr cloned = osg::clone(it->second.get(), osg::CopyOp::SHALLOW_COPY); + osg::ref_ptr cloned + = osg::clone(it->second.get(), osg::CopyOp::SHALLOW_COPY); cloned->setSource(mAnimationTimePtr[blendMask]); animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned)); } - mAnimSources.push_back(animsrc); + mAnimSources.push_back(std::move(animsrc)); SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]); mObjectRoot->accept(assignVisitor); + // Determine the movement accumulation bone if necessary if (!mAccumRoot) { - NodeMap::const_iterator found = nodeMap.find("bip01"); - if (found == nodeMap.end()) - found = nodeMap.find("root bone"); - - if (found != nodeMap.end()) - mAccumRoot = found->second; + // Priority matters! bip01 is preferred. + static const std::initializer_list accumRootNames = { "bip01", "root bone" }; + NodeMap::const_iterator found = nodeMap.end(); + for (const std::string_view& name : accumRootNames) + { + found = nodeMap.find(name); + if (found == nodeMap.end()) + continue; + for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = controllerMap.begin(); + it != controllerMap.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->first, name)) + { + mAccumRoot = found->second; + break; + } + } + if (mAccumRoot) + break; + } } } @@ -800,7 +693,7 @@ namespace MWRender { mStates.clear(); - for(size_t i = 0;i < sNumBlendMasks;i++) + for (size_t i = 0; i < sNumBlendMasks; i++) mAnimationTimePtr[i]->setTimePtr(std::shared_ptr()); mAccumCtrl = nullptr; @@ -810,12 +703,12 @@ namespace MWRender mAnimVelocities.clear(); } - bool Animation::hasAnimation(const std::string &anim) const + bool Animation::hasAnimation(std::string_view anim) const { AnimSourceList::const_iterator iter(mAnimSources.begin()); - for(;iter != mAnimSources.end();++iter) + for (; iter != mAnimSources.end(); ++iter) { - const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); + const SceneUtil::TextKeyMap& keys = (*iter)->getTextKeys(); if (keys.hasGroupStart(anim)) return true; } @@ -823,28 +716,28 @@ namespace MWRender return false; } - float Animation::getStartTime(const std::string &groupname) const + float Animation::getStartTime(const std::string& groupname) const { - for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) + for (AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { - const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); + const SceneUtil::TextKeyMap& keys = (*iter)->getTextKeys(); const auto found = keys.findGroupStart(groupname); - if(found != keys.end()) + if (found != keys.end()) return found->first; } return -1.f; } - float Animation::getTextKeyTime(const std::string &textKey) const + float Animation::getTextKeyTime(std::string_view textKey) const { - for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) + for (AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { - const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); + const SceneUtil::TextKeyMap& keys = (*iter)->getTextKeys(); - for(auto iterKey = keys.begin(); iterKey != keys.end(); ++iterKey) + for (auto iterKey = keys.begin(); iterKey != keys.end(); ++iterKey) { - if(iterKey->second.compare(0, textKey.size(), textKey) == 0) + if (iterKey->second.starts_with(textKey)) return iterKey->first; } } @@ -852,20 +745,17 @@ namespace MWRender return -1.f; } - void Animation::handleTextKey(AnimState &state, const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, - const SceneUtil::TextKeyMap& map) + void Animation::handleTextKey(AnimState& state, std::string_view groupname, + SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) { - const std::string &evt = key->second; + std::string_view evt = key->second; - size_t off = groupname.size()+2; - size_t len = evt.size() - off; - - if(evt.compare(0, groupname.size(), groupname) == 0 && - evt.compare(groupname.size(), 2, ": ") == 0) + if (evt.starts_with(groupname) && evt.substr(groupname.size()).starts_with(": ")) { - if(evt.compare(off, len, "loop start") == 0) + size_t off = groupname.size() + 2; + if (evt.substr(off) == "loop start") state.mLoopStartTime = key->first; - else if(evt.compare(off, len, "loop stop") == 0) + else if (evt.substr(off) == "loop stop") state.mLoopStopTime = key->first; } @@ -882,29 +772,30 @@ namespace MWRender } } - void Animation::play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult, - const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback) + void Animation::play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable, + float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, + bool loopfallback) { - if(!mObjectRoot || mAnimSources.empty()) + if (!mObjectRoot || mAnimSources.empty()) return; - if(groupname.empty()) + if (groupname.empty()) { resetActiveGroups(); return; } AnimStateMap::iterator stateiter = mStates.begin(); - while(stateiter != mStates.end()) + while (stateiter != mStates.end()) { - if(stateiter->second.mPriority == priority) + if (stateiter->second.mPriority == priority) mStates.erase(stateiter++); else ++stateiter; } stateiter = mStates.find(groupname); - if(stateiter != mStates.end()) + if (stateiter != mStates.end()) { stateiter->second.mPriority = priority; resetActiveGroups(); @@ -914,10 +805,10 @@ namespace MWRender /* Look in reverse; last-inserted source has priority. */ AnimState state; AnimSourceList::reverse_iterator iter(mAnimSources.rbegin()); - for(;iter != mAnimSources.rend();++iter) + for (; iter != mAnimSources.rend(); ++iter) { - const SceneUtil::TextKeyMap &textkeys = (*iter)->getTextKeys(); - if(reset(state, textkeys, groupname, start, stop, startpoint, loopfallback)) + const SceneUtil::TextKeyMap& textkeys = (*iter)->getTextKeys(); + if (reset(state, textkeys, groupname, start, stop, startpoint, loopfallback)) { state.mSource = *iter; state.mSpeedMult = speedmult; @@ -926,28 +817,28 @@ namespace MWRender state.mPriority = priority; state.mBlendMask = blendMask; state.mAutoDisable = autodisable; - mStates[groupname] = state; + mStates[std::string{ groupname }] = state; if (state.mPlaying) { auto textkey = textkeys.lowerBound(state.getTime()); - while(textkey != textkeys.end() && textkey->first <= state.getTime()) + while (textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, groupname, textkey, textkeys); ++textkey; } } - if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) + if (state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) { state.mLoopCount--; state.setTime(state.mLoopStartTime); state.mPlaying = true; - if(state.getTime() >= state.mLoopStopTime) + if (state.getTime() >= state.mLoopStopTime) break; auto textkey = textkeys.lowerBound(state.getTime()); - while(textkey != textkeys.end() && textkey->first <= state.getTime()) + while (textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, groupname, textkey, textkeys); ++textkey; @@ -961,44 +852,42 @@ namespace MWRender resetActiveGroups(); } - bool Animation::reset(AnimState &state, const SceneUtil::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback) + bool Animation::reset(AnimState& state, const SceneUtil::TextKeyMap& keys, std::string_view groupname, + std::string_view start, std::string_view stop, float startpoint, bool loopfallback) { // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two // separate walkforward keys, and the last one is supposed to be used. auto groupend = keys.rbegin(); - for(;groupend != keys.rend();++groupend) + for (; groupend != keys.rend(); ++groupend) { - if(groupend->second.compare(0, groupname.size(), groupname) == 0 && - groupend->second.compare(groupname.size(), 2, ": ") == 0) + if (groupend->second.starts_with(groupname) && groupend->second.compare(groupname.size(), 2, ": ") == 0) break; } - std::string starttag = groupname+": "+start; auto startkey = groupend; - while(startkey != keys.rend() && startkey->second != starttag) + while (startkey != keys.rend() && !equalsParts(startkey->second, groupname, ": ", start)) ++startkey; - if(startkey == keys.rend() && start == "loop start") + if (startkey == keys.rend() && start == "loop start") { - starttag = groupname+": start"; startkey = groupend; - while(startkey != keys.rend() && startkey->second != starttag) + while (startkey != keys.rend() && !equalsParts(startkey->second, groupname, ": start")) ++startkey; } - if(startkey == keys.rend()) + if (startkey == keys.rend()) return false; - const std::string stoptag = groupname+": "+stop; auto stopkey = groupend; - while(stopkey != keys.rend() - // We have to ignore extra garbage at the end. - // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". - // Why, just why? :( - && (stopkey->second.size() < stoptag.size() || stopkey->second.compare(0,stoptag.size(), stoptag) != 0)) + std::size_t checkLength = groupname.size() + 2 + stop.size(); + while (stopkey != keys.rend() + // We have to ignore extra garbage at the end. + // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". + // Why, just why? :( + && !equalsParts(std::string_view{ stopkey->second }.substr(0, checkLength), groupname, ": ", stop)) ++stopkey; - if(stopkey == keys.rend()) + if (stopkey == keys.rend()) return false; - if(startkey->first > stopkey->first) + if (startkey->first > stopkey->first) return false; state.mStartTime = startkey->first; @@ -1016,10 +905,9 @@ namespace MWRender state.setTime(state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint)); - // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation - // (see handleTextKey). But if startpoint is already past these keys, or start time is == stop time, we need to assign them now. - const std::string loopstarttag = groupname+": loop start"; - const std::string loopstoptag = groupname+": loop stop"; + // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the + // animation (see handleTextKey). But if startpoint is already past these keys, or start time is == stop time, + // we need to assign them now. auto key = groupend; for (; key != startkey && key != keys.rend(); ++key) @@ -1027,21 +915,21 @@ namespace MWRender if (key->first > state.getTime()) continue; - if (key->second == loopstarttag) + if (equalsParts(key->second, groupname, ": loop start")) state.mLoopStartTime = key->first; - else if (key->second == loopstoptag) + else if (equalsParts(key->second, groupname, ": loop stop")) state.mLoopStopTime = key->first; } return true; } - void Animation::setTextKeyListener(Animation::TextKeyListener *listener) + void Animation::setTextKeyListener(Animation::TextKeyListener* listener) { mTextKeyListener = listener; } - const Animation::NodeMap &Animation::getNodeMap() const + const Animation::NodeMap& Animation::getNodeMap() const { if (!mNodeMapCreated && mObjectRoot) { @@ -1068,33 +956,38 @@ namespace MWRender mAccumCtrl = nullptr; - for(size_t blendMask = 0;blendMask < sNumBlendMasks;blendMask++) + for (size_t blendMask = 0; blendMask < sNumBlendMasks; blendMask++) { AnimStateMap::const_iterator active = mStates.end(); AnimStateMap::const_iterator state = mStates.begin(); - for(;state != mStates.end();++state) + for (; state != mStates.end(); ++state) { - if(!(state->second.mBlendMask&(1<second.mBlendMask & (1 << blendMask))) continue; - if(active == mStates.end() || active->second.mPriority[(BoneGroup)blendMask] < state->second.mPriority[(BoneGroup)blendMask]) + if (active == mStates.end() + || active->second.mPriority[(BoneGroup)blendMask] < state->second.mPriority[(BoneGroup)blendMask]) active = state; } - mAnimationTimePtr[blendMask]->setTimePtr(active == mStates.end() ? std::shared_ptr() : active->second.mTime); + mAnimationTimePtr[blendMask]->setTimePtr( + active == mStates.end() ? std::shared_ptr() : active->second.mTime); // add external controllers for the AnimSource active in this blend mask if (active != mStates.end()) { std::shared_ptr animsrc = active->second.mSource; - for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) + for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); + it != animsrc->mControllerMap[blendMask].end(); ++it) { - osg::ref_ptr node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource + osg::ref_ptr node = getNodeMap().at( + it->first); // this should not throw, we already checked for the node existing in addAnimSource - node->addUpdateCallback(it->second); - mActiveControllers.emplace_back(node, it->second); + osg::Callback* callback = it->second->getAsCallback(); + node->addUpdateCallback(callback); + mActiveControllers.emplace_back(node, callback); if (blendMask == 0 && node == mAccumRoot) { @@ -1115,47 +1008,50 @@ namespace MWRender addControllers(); } - void Animation::adjustSpeedMult(const std::string &groupname, float speedmult) + void Animation::adjustSpeedMult(const std::string& groupname, float speedmult) { AnimStateMap::iterator state(mStates.find(groupname)); - if(state != mStates.end()) + if (state != mStates.end()) state->second.mSpeedMult = speedmult; } - bool Animation::isPlaying(const std::string &groupname) const + bool Animation::isPlaying(std::string_view groupname) const { AnimStateMap::const_iterator state(mStates.find(groupname)); - if(state != mStates.end()) + if (state != mStates.end()) return state->second.mPlaying; return false; } - bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult) const + bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); - if(iter == mStates.end()) + if (iter == mStates.end()) { - if(complete) *complete = 0.0f; - if(speedmult) *speedmult = 0.0f; + if (complete) + *complete = 0.0f; + if (speedmult) + *speedmult = 0.0f; return false; } - if(complete) + if (complete) { - if(iter->second.mStopTime > iter->second.mStartTime) - *complete = (iter->second.getTime() - iter->second.mStartTime) / - (iter->second.mStopTime - iter->second.mStartTime); + if (iter->second.mStopTime > iter->second.mStartTime) + *complete = (iter->second.getTime() - iter->second.mStartTime) + / (iter->second.mStopTime - iter->second.mStartTime); else *complete = (iter->second.mPlaying ? 0.0f : 1.0f); } - if(speedmult) *speedmult = iter->second.mSpeedMult; + if (speedmult) + *speedmult = iter->second.mSpeedMult; return true; } - float Animation::getCurrentTime(const std::string &groupname) const + float Animation::getCurrentTime(const std::string& groupname) const { AnimStateMap::const_iterator iter = mStates.find(groupname); - if(iter == mStates.end()) + if (iter == mStates.end()) return -1.f; return iter->second.getTime(); @@ -1164,21 +1060,21 @@ namespace MWRender size_t Animation::getCurrentLoopCount(const std::string& groupname) const { AnimStateMap::const_iterator iter = mStates.find(groupname); - if(iter == mStates.end()) + if (iter == mStates.end()) return 0; return iter->second.mLoopCount; } - void Animation::disable(const std::string &groupname) + void Animation::disable(std::string_view groupname) { AnimStateMap::iterator iter = mStates.find(groupname); - if(iter != mStates.end()) + if (iter != mStates.end()) mStates.erase(iter); resetActiveGroups(); } - float Animation::getVelocity(const std::string &groupname) const + float Animation::getVelocity(std::string_view groupname) const { if (!mAccumRoot) return 0.0f; @@ -1189,17 +1085,17 @@ namespace MWRender // Look in reverse; last-inserted source has priority. AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin()); - for(;animsrc != mAnimSources.rend();++animsrc) + for (; animsrc != mAnimSources.rend(); ++animsrc) { - const SceneUtil::TextKeyMap &keys = (*animsrc)->getTextKeys(); + const SceneUtil::TextKeyMap& keys = (*animsrc)->getTextKeys(); if (keys.hasGroupStart(groupname)) break; } - if(animsrc == mAnimSources.rend()) + if (animsrc == mAnimSources.rend()) return 0.0f; float velocity = 0.0f; - const SceneUtil::TextKeyMap &keys = (*animsrc)->getTextKeys(); + const SceneUtil::TextKeyMap& keys = (*animsrc)->getTextKeys(); const AnimSource::ControllerMap& ctrls = (*animsrc)->mControllerMap[0]; for (AnimSource::ControllerMap::const_iterator it = ctrls.begin(); it != ctrls.end(); ++it) @@ -1212,15 +1108,15 @@ namespace MWRender } // If there's no velocity, keep looking - if(!(velocity > 1.0f)) + if (!(velocity > 1.0f)) { AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin(); - while(*animiter != *animsrc) + while (*animiter != *animsrc) ++animiter; - while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend()) + while (!(velocity > 1.0f) && ++animiter != mAnimSources.rend()) { - const SceneUtil::TextKeyMap &keys2 = (*animiter)->getTextKeys(); + const SceneUtil::TextKeyMap& keys2 = (*animiter)->getTextKeys(); const AnimSource::ControllerMap& ctrls2 = (*animiter)->mControllerMap[0]; for (AnimSource::ControllerMap::const_iterator it = ctrls2.begin(); it != ctrls2.end(); ++it) @@ -1234,7 +1130,7 @@ namespace MWRender } } - mAnimVelocities.insert(std::make_pair(groupname, velocity)); + mAnimVelocities.emplace(groupname, velocity); return velocity; } @@ -1252,7 +1148,8 @@ namespace MWRender bool hasScriptedAnims = false; for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) && stateiter->second.mPlaying) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) + && stateiter->second.mPlaying) { hasScriptedAnims = true; break; @@ -1261,33 +1158,33 @@ namespace MWRender osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); - while(stateiter != mStates.end()) + while (stateiter != mStates.end()) { - AnimState &state = stateiter->second; + AnimState& state = stateiter->second; if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) { ++stateiter; continue; } - const SceneUtil::TextKeyMap &textkeys = state.mSource->getTextKeys(); + const SceneUtil::TextKeyMap& textkeys = state.mSource->getTextKeys(); auto textkey = textkeys.upperBound(state.getTime()); float timepassed = duration * state.mSpeedMult; - while(state.mPlaying) + while (state.mPlaying) { if (!state.shouldLoop()) { float targetTime = state.getTime() + timepassed; - if(textkey == textkeys.end() || textkey->first > targetTime) + if (textkey == textkeys.end() || textkey->first > targetTime) { - if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) updatePosition(state.getTime(), targetTime, movement); state.setTime(std::min(targetTime, state.mStopTime)); } else { - if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) updatePosition(state.getTime(), textkey->first, movement); state.setTime(textkey->first); } @@ -1295,34 +1192,34 @@ namespace MWRender state.mPlaying = (state.getTime() < state.mStopTime); timepassed = targetTime - state.getTime(); - while(textkey != textkeys.end() && textkey->first <= state.getTime()) + while (textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } } - if(state.shouldLoop()) + if (state.shouldLoop()) { state.mLoopCount--; state.setTime(state.mLoopStartTime); state.mPlaying = true; textkey = textkeys.lowerBound(state.getTime()); - while(textkey != textkeys.end() && textkey->first <= state.getTime()) + while (textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } - if(state.getTime() >= state.mLoopStopTime) + if (state.getTime() >= state.mLoopStopTime) break; } - if(timepassed <= 0.0f) + if (timepassed <= 0.0f) break; } - if(!state.mPlaying && state.mAutoDisable) + if (!state.mPlaying && state.mAutoDisable) { mStates.erase(stateiter++); @@ -1342,7 +1239,8 @@ namespace MWRender mRootController->setEnabled(enable); if (enable) { - mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0,0,1)) * osg::Quat(mBodyPitchRadians, osg::Vec3f(1,0,0))); + mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1)) + * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0))); yawOffset = mLegsYawRadians; } } @@ -1353,7 +1251,7 @@ namespace MWRender mSpineController->setEnabled(enable); if (enable) { - mSpineController->setRotate(osg::Quat(yaw, osg::Vec3f(0,0,1))); + mSpineController->setRotate(osg::Quat(yaw, osg::Vec3f(0, 0, 1))); yawOffset = mUpperBodyYawRadians; } } @@ -1363,7 +1261,8 @@ namespace MWRender bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(yaw) > epsilon); mHeadController->setEnabled(enable); if (enable) - mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(yaw, osg::Vec3f(0,0,1))); + mHeadController->setRotate( + osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1))); } // Scripted animations should not cause movement @@ -1373,23 +1272,25 @@ namespace MWRender return movement; } - void Animation::setLoopingEnabled(const std::string &groupname, bool enabled) + void Animation::setLoopingEnabled(std::string_view groupname, bool enabled) { AnimStateMap::iterator state(mStates.find(groupname)); - if(state != mStates.end()) + if (state != mStates.end()) state->second.mLoopingEnabled = enabled; } - void loadBonesFromFile(osg::ref_ptr& baseNode, const std::string &model, Resource::ResourceSystem* resourceSystem) + void loadBonesFromFile( + osg::ref_ptr& baseNode, const std::string& model, Resource::ResourceSystem* resourceSystem) { const osg::Node* node = resourceSystem->getSceneManager()->getTemplate(model).get(); - osg::ref_ptr sheathSkeleton (const_cast(node)); // const-trickery required because there is no const version of NodeVisitor + osg::ref_ptr sheathSkeleton( + const_cast(node)); // const-trickery required because there is no const version of NodeVisitor GetExtendedBonesVisitor getBonesVisitor; sheathSkeleton->accept(getBonesVisitor); for (auto& nodePair : getBonesVisitor.mFoundBones) { - SceneUtil::FindByNameVisitor findVisitor (nodePair.second->getName()); + SceneUtil::FindByNameVisitor findVisitor(nodePair.second->getName()); baseNode->accept(findVisitor); osg::Group* sheathParent = findVisitor.mFoundNode; @@ -1401,44 +1302,33 @@ namespace MWRender } } - void injectCustomBones(osg::ref_ptr& node, const std::string& model, Resource::ResourceSystem* resourceSystem) + void injectCustomBones( + osg::ref_ptr& node, const std::string& model, Resource::ResourceSystem* resourceSystem) { if (model.empty()) return; - const std::map& index = resourceSystem->getVFS()->getIndex(); - std::string animationPath = model; if (animationPath.find("meshes") == 0) { animationPath.replace(0, 6, "animations"); } - animationPath.replace(animationPath.size()-4, 4, "/"); + animationPath.replace(animationPath.size() - 4, 4, "/"); - resourceSystem->getVFS()->normalizeFilename(animationPath); - - std::map::const_iterator found = index.lower_bound(animationPath); - while (found != index.end()) + for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - const std::string& name = found->first; - if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".nif") == 0) - loadBonesFromFile(node, name, resourceSystem); - } - else - break; - ++found; + if (Misc::getFileExtension(name) == "nif") + loadBonesFromFile(node, name, resourceSystem); } } - osg::ref_ptr getModelInstance(Resource::ResourceSystem* resourceSystem, const std::string& model, bool baseonly, bool inject, const std::string& defaultSkeleton) + osg::ref_ptr getModelInstance(Resource::ResourceSystem* resourceSystem, const std::string& model, + bool baseonly, bool inject, const std::string& defaultSkeleton) { Resource::SceneManager* sceneMgr = resourceSystem->getSceneManager(); if (baseonly) { - typedef std::map > Cache; + typedef std::map> Cache; static Cache cache; Cache::iterator found = cache.find(model); if (found == cache.end()) @@ -1457,10 +1347,10 @@ namespace MWRender cache.insert(std::make_pair(model, created)); - return sceneMgr->createInstance(created); + return sceneMgr->getInstance(created); } else - return sceneMgr->createInstance(found->second); + return sceneMgr->getInstance(found->second); } else { @@ -1476,7 +1366,7 @@ namespace MWRender } } - void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) + void Animation::setObjectRoot(const std::string& model, bool forceskeleton, bool baseonly, bool isCreature) { osg::ref_ptr previousStateset; if (mObjectRoot) @@ -1497,16 +1387,15 @@ namespace MWRender mAccumRoot = nullptr; mAccumCtrl = nullptr; - static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); std::string defaultSkeleton; bool inject = false; - if (useAdditionalSources && mPtr.getClass().isActor()) + if (Settings::game().mUseAdditionalAnimSources && mPtr.getClass().isActor()) { if (isCreature) { - MWWorld::LiveCellRef *ref = mPtr.get(); - if(ref->mBase->mFlags & ESM::Creature::Bipedal) + MWWorld::LiveCellRef* ref = mPtr.get(); + if (ref->mBase->mFlags & ESM::Creature::Bipedal) { defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models"); inject = true; @@ -1515,27 +1404,30 @@ namespace MWRender else { inject = true; - MWWorld::LiveCellRef *ref = mPtr.get(); + MWWorld::LiveCellRef* ref = mPtr.get(); if (!ref->mBase->mModel.empty()) { - // If NPC has a custom animation model attached, we should inject bones from default skeleton for given race and gender as well - // Since it is a quite rare case, there should not be a noticable performance loss - // Note: consider that player and werewolves have no custom animation files attached for now - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Race *race = store.get().find(ref->mBase->mRace); + // If NPC has a custom animation model attached, we should inject bones from default skeleton for + // given race and gender as well Since it is a quite rare case, there should not be a noticable + // performance loss Note: consider that player and werewolves have no custom animation files + // attached for now + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::Race* race = store.get().find(ref->mBase->mRace); bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; bool isFemale = !ref->mBase->isMale(); defaultSkeleton = SceneUtil::getActorSkeleton(false, isFemale, isBeast, false); - defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); + defaultSkeleton + = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); } } } if (!forceskeleton) { - osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); + osg::ref_ptr created + = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1551,7 +1443,8 @@ namespace MWRender } else { - osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); + osg::ref_ptr created + = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { @@ -1595,9 +1488,9 @@ namespace MWRender return mObjectRoot.get(); } - void Animation::addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration) + void Animation::addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration) { - osg::Vec4f glowColor(1,1,1,1); + osg::Vec4f glowColor(1, 1, 1, 1); glowColor.x() = effect->mData.mRed / 255.f; glowColor.y() = effect->mData.mGreen / 255.f; glowColor.z() = effect->mData.mBlue / 255.f; @@ -1617,14 +1510,16 @@ namespace MWRender } } - void Animation::addExtraLight(osg::ref_ptr parent, const ESM::Light *esmLight) + void Animation::addExtraLight(osg::ref_ptr parent, const SceneUtil::LightCommon& esmLight) { bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); - mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior); + mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_Lighting, exterior); + mExtraLightSource->setActorFade(mAlpha); } - void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) + void Animation::addEffect( + const std::string& model, int effectId, bool loop, std::string_view bonename, std::string_view texture) { if (!mObjectRoot.get()) return; @@ -1633,7 +1528,8 @@ namespace MWRender FindVfxCallbacksVisitor visitor(effectId); mInsert->accept(visitor); - for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) + for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); + ++it) { UpdateVfxCallback* callback = *it; @@ -1648,41 +1544,55 @@ namespace MWRender parentNode = mInsert; else { - NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename)); + NodeMap::const_iterator found = getNodeMap().find(bonename); if (found == getNodeMap().end()) - throw std::runtime_error("Can't find bone " + bonename); + throw std::runtime_error("Can't find bone " + std::string{ bonename }); parentNode = found->second; } - osg::ref_ptr trans = new osg::PositionAttitudeTransform; + osg::ref_ptr trans = new SceneUtil::PositionAttitudeTransform; if (!mPtr.getClass().isNpc()) { - osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mPtr) * 2.f / Constants::UnitsPerFoot); - float scale = std::max({ bounds.x()/3.f, bounds.y()/3.f, bounds.z()/6.f }); - trans->setScale(osg::Vec3f(scale, scale, scale)); + osg::Vec3f bounds(MWBase::Environment::get().getWorld()->getHalfExtents(mPtr) * 2.f); + float scale = std::max({ bounds.x(), bounds.y(), bounds.z() / 2.f }) / 64.f; + if (scale > 1.f) + trans->setScale(osg::Vec3f(scale, scale, scale)); + float offset = 0.f; + if (bounds.z() < 128.f) + offset = bounds.z() - 128.f; + else if (bounds.z() < bounds.x() + bounds.y()) + offset = 128.f - bounds.z(); + if (MWBase::Environment::get().getWorld()->isFlying(mPtr)) + offset /= 20.f; + trans->setPosition(osg::Vec3f(0.f, 0.f, offset * scale)); } parentNode->addChild(trans); osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, trans); - node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + // Morrowind has a white ambient light attached to the root VFX node of the scenegraph + node->getOrCreateStateSet()->setAttributeAndModes( + getVFXLightModelInstance(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + if (mResourceSystem->getSceneManager()->getSupportsNormalsRT()) + node->getOrCreateStateSet()->setAttribute(new osg::ColorMaski(1, false, false, false, false)); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); - // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters - SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; - node->accept(disableFreezeOnCullVisitor); node->setNodeMask(Mask_Effect); + MarkDrawablesVisitor markVisitor(Mask_Effect); + node->accept(markVisitor); + params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; - params.mAnimTime = std::shared_ptr(new EffectAnimationTime); + params.mAnimTime = std::make_shared(); trans->addUpdateCallback(new UpdateVfxCallback(params)); - SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(params.mAnimTime)); + SceneUtil::AssignControllerSourcesVisitor assignVisitor( + std::shared_ptr(params.mAnimTime)); node->accept(assignVisitor); // Notify that this animation has attached magic effects @@ -1704,7 +1614,7 @@ namespace MWRender removeEffect(-1); } - void Animation::getLoopingEffects(std::vector &out) const + void Animation::getLoopingEffects(std::vector& out) const { if (!mHasMagicEffects) return; @@ -1712,7 +1622,8 @@ namespace MWRender FindVfxCallbacksVisitor visitor; mInsert->accept(visitor); - for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) + for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); + ++it) { UpdateVfxCallback* callback = *it; @@ -1741,18 +1652,17 @@ namespace MWRender for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) { if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Hit)) - || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Weapon)) - || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Knockdown)) - || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Death))) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Weapon)) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Knockdown)) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Death))) return false; } return true; } - const osg::Node* Animation::getNode(const std::string &name) const + const osg::Node* Animation::getNode(std::string_view name) const { - std::string lowerName = Misc::StringUtils::lowerCase(name); - NodeMap::const_iterator found = getNodeMap().find(lowerName); + NodeMap::const_iterator found = getNodeMap().find(name); if (found == getNodeMap().end()) return nullptr; else @@ -1771,7 +1681,6 @@ namespace MWRender if (mTransparencyUpdater == nullptr) { mTransparencyUpdater = new TransparencyUpdater(alpha); - mTransparencyUpdater->setLightSource(mExtraLightSource); mObjectRoot->addCullCallback(mTransparencyUpdater); } else @@ -1782,6 +1691,8 @@ namespace MWRender mObjectRoot->removeCullCallback(mTransparencyUpdater); mTransparencyUpdater = nullptr; } + if (mExtraLightSource) + mExtraLightSource->setActorFade(alpha); } void Animation::setLightEffect(float effect) @@ -1809,10 +1720,10 @@ namespace MWRender mGlowLight = nullptr; } - osg::ref_ptr light (new osg::Light); - light->setDiffuse(osg::Vec4f(0,0,0,0)); - light->setSpecular(osg::Vec4f(0,0,0,0)); - light->setAmbient(osg::Vec4f(1.5f,1.5f,1.5f,1.f)); + osg::ref_ptr light(new osg::Light); + light->setDiffuse(osg::Vec4f(0, 0, 0, 0)); + light->setSpecular(osg::Vec4f(0, 0, 0, 0)); + light->setAmbient(osg::Vec4f(1.5f, 1.5f, 1.5f, 1.f)); bool isExterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); SceneUtil::configureLight(light, radius, isExterior); @@ -1834,7 +1745,7 @@ namespace MWRender mRootController = addRotateController("bip01"); } - RotateController* Animation::addRotateController(std::string bone) + osg::ref_ptr Animation::addRotateController(std::string_view bone) { auto iter = getNodeMap().find(bone); if (iter == getNodeMap().end()) @@ -1857,7 +1768,7 @@ namespace MWRender if (!foundKeyframeCtrl) return nullptr; - RotateController* controller = new RotateController(mObjectRoot.get()); + osg::ref_ptr controller(new RotateController(mObjectRoot.get())); node->addUpdateCallback(controller); mActiveControllers.emplace_back(node, controller); return controller; @@ -1883,6 +1794,20 @@ namespace MWRender return mHeadYawRadians; } + void Animation::removeFromScene() + { + removeFromSceneImpl(); + } + + void Animation::removeFromSceneImpl() + { + if (mGlowLight != nullptr) + mInsert->removeChild(mGlowLight); + + if (mObjectRoot != nullptr) + mInsert->removeChild(mObjectRoot); + } + // ------------------------------------------------------ float Animation::AnimationTime::getValue(osg::NodeVisitor*) @@ -1914,7 +1839,8 @@ namespace MWRender // -------------------------------------------------------------------------------- - ObjectAnimation::ObjectAnimation(const MWWorld::Ptr &ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight) + ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string& model, + Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight) : Animation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) { if (!model.empty()) @@ -1924,10 +1850,13 @@ namespace MWRender addAnimSource(model, model); if (!ptr.getClass().getEnchantment(ptr).empty()) - mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); + mGlowUpdater = SceneUtil::addEnchantedGlow( + mObjectRoot, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); } - if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight) - addExtraLight(getOrCreateObjectRoot(), ptr.get()->mBase); + if (ptr.getType() == ESM::Light::sRecordId && allowLight) + addExtraLight(getOrCreateObjectRoot(), SceneUtil::LightCommon(*ptr.get()->mBase)); + if (ptr.getType() == ESM4::Light::sRecordId && allowLight) + addExtraLight(getOrCreateObjectRoot(), SceneUtil::LightCommon(*ptr.get()->mBase)); if (!allowLight && mObjectRoot) { @@ -1936,13 +1865,13 @@ namespace MWRender visitor.remove(); } - if (SceneUtil::hasUserDescription(mObjectRoot, Constants::NightDayLabel)) + if (Settings::game().mDayNightSwitches && SceneUtil::hasUserDescription(mObjectRoot, Constants::NightDayLabel)) { AddSwitchCallbacksVisitor visitor; mObjectRoot->accept(visitor); } - if (ptr.getRefData().getCustomData() != nullptr && canBeHarvested()) + if (ptr.getRefData().getCustomData() != nullptr && ObjectAnimation::canBeHarvested()) { const MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); if (!store.hasVisibleItems()) @@ -1955,7 +1884,7 @@ namespace MWRender bool ObjectAnimation::canBeHarvested() const { - if (mPtr.getTypeName() != typeid(ESM::Container).name()) + if (mPtr.getType() != ESM::Container::sRecordId) return false; const MWWorld::LiveCellRef* ref = mPtr.get(); @@ -1965,11 +1894,6 @@ namespace MWRender return SceneUtil::hasUserDescription(mObjectRoot, Constants::HerbalismLabel); } - Animation::AnimState::~AnimState() - { - - } - // ------------------------------ PartHolder::PartHolder(osg::ref_ptr node) @@ -1980,12 +1904,13 @@ namespace MWRender PartHolder::~PartHolder() { if (mNode.get() && !mNode->getNumParents()) - Log(Debug::Verbose) << "Part \"" << mNode->getName() << "\" has no parents" ; + Log(Debug::Verbose) << "Part \"" << mNode->getName() << "\" has no parents"; if (mNode.get() && mNode->getNumParents()) { if (mNode->getNumParents() > 1) - Log(Debug::Verbose) << "Part \"" << mNode->getName() << "\" has multiple (" << mNode->getNumParents() << ") parents"; + Log(Debug::Verbose) << "Part \"" << mNode->getName() << "\" has multiple (" << mNode->getNumParents() + << ") parents"; mNode->getParent(0)->removeChild(mNode); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 213a4f704..ac5376fb2 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -3,10 +3,13 @@ #include "../mwworld/ptr.hpp" +#include #include +#include #include #include +#include #include namespace ESM @@ -27,503 +30,513 @@ namespace SceneUtil class LightSource; class LightListCallback; class Skeleton; + struct LightCommon; } namespace MWRender { -class ResetAccumRootCallback; -class RotateController; -class TransparencyUpdater; + class ResetAccumRootCallback; + class RotateController; + class TransparencyUpdater; -class EffectAnimationTime : public SceneUtil::ControllerSource -{ -private: - float mTime; -public: - float getValue(osg::NodeVisitor* nv) override; - - void addTime(float duration); - void resetTime(float time); - float getTime() const; - - EffectAnimationTime() : mTime(0) { } -}; - -/// @brief Detaches the node from its parent when the object goes out of scope. -class PartHolder -{ -public: - PartHolder(osg::ref_ptr node); - - ~PartHolder(); - - osg::ref_ptr getNode() - { - return mNode; - } - -private: - osg::ref_ptr mNode; - - void operator= (const PartHolder&); - PartHolder(const PartHolder&); -}; -typedef std::shared_ptr PartHolderPtr; - -struct EffectParams -{ - std::string mModelName; // Just here so we don't add the same effect twice - std::shared_ptr mAnimTime; - float mMaxControllerLength; - int mEffectId; - bool mLoop; - std::string mBoneName; -}; - -class Animation : public osg::Referenced -{ -public: - enum BoneGroup { - BoneGroup_LowerBody = 0, - BoneGroup_Torso, - BoneGroup_LeftArm, - BoneGroup_RightArm - }; - - enum BlendMask { - BlendMask_LowerBody = 1<<0, - BlendMask_Torso = 1<<1, - BlendMask_LeftArm = 1<<2, - BlendMask_RightArm = 1<<3, - - BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm, - - BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody - }; - /* This is the number of *discrete* blend masks. */ - static constexpr size_t sNumBlendMasks = 4; - - /// Holds an animation priority value for each BoneGroup. - struct AnimPriority - { - /// Convenience constructor, initialises all priorities to the same value. - AnimPriority(int priority) - { - for (unsigned int i=0; i mTimePtr; + float mTime; public: - - void setTimePtr(std::shared_ptr time) - { mTimePtr = time; } - std::shared_ptr getTimePtr() const - { return mTimePtr; } - float getValue(osg::NodeVisitor* nv) override; + + void addTime(float duration); + void resetTime(float time); + float getTime() const; + + EffectAnimationTime() + : mTime(0) + { + } }; - class NullAnimationTime : public SceneUtil::ControllerSource + /// @brief Detaches the node from its parent when the object goes out of scope. + class PartHolder { public: - float getValue(osg::NodeVisitor *nv) override - { - return 0.f; - } + PartHolder(osg::ref_ptr node); + + ~PartHolder(); + + const osg::ref_ptr& getNode() const { return mNode; } + + private: + osg::ref_ptr mNode; + + void operator=(const PartHolder&); + PartHolder(const PartHolder&); }; + using PartHolderPtr = std::unique_ptr; - struct AnimSource; - - struct AnimState { - std::shared_ptr mSource; - float mStartTime; - float mLoopStartTime; - float mLoopStopTime; - float mStopTime; - - typedef std::shared_ptr TimePtr; - TimePtr mTime; - float mSpeedMult; - - bool mPlaying; - bool mLoopingEnabled; - size_t mLoopCount; - - AnimPriority mPriority; - int mBlendMask; - bool mAutoDisable; - - AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f), - mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopingEnabled(true), - mLoopCount(0), mPriority(0), mBlendMask(0), mAutoDisable(true) - { - } - ~AnimState(); - - float getTime() const - { - return *mTime; - } - void setTime(float time) - { - *mTime = time; - } - - bool shouldLoop() const - { - return getTime() >= mLoopStopTime && mLoopingEnabled && mLoopCount > 0; - } - }; - typedef std::map AnimStateMap; - AnimStateMap mStates; - - typedef std::vector > AnimSourceList; - AnimSourceList mAnimSources; - - osg::ref_ptr mInsert; - - osg::ref_ptr mObjectRoot; - SceneUtil::Skeleton* mSkeleton; - - // The node expected to accumulate movement during movement animations. - osg::ref_ptr mAccumRoot; - - // The controller animating that node. - osg::ref_ptr mAccumCtrl; - - // Used to reset the position of the accumulation root every frame - the movement should be applied to the physics system - osg::ref_ptr mResetAccumRootCallback; - - // Keep track of controllers that we added to our scene graph. - // We may need to rebuild these controllers when the active animation groups / sources change. - std::vector, osg::ref_ptr>> mActiveControllers; - - std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; - - // Stored in all lowercase for a case-insensitive lookup - typedef std::map > NodeMap; - mutable NodeMap mNodeMap; - mutable bool mNodeMapCreated; - - MWWorld::Ptr mPtr; - - Resource::ResourceSystem* mResourceSystem; - - osg::Vec3f mAccumulate; - - TextKeyListener* mTextKeyListener; - - osg::ref_ptr mHeadController; - osg::ref_ptr mSpineController; - osg::ref_ptr mRootController; - float mHeadYawRadians; - float mHeadPitchRadians; - float mUpperBodyYawRadians; - float mLegsYawRadians; - float mBodyPitchRadians; - - RotateController* addRotateController(std::string bone); - - bool mHasMagicEffects; - - osg::ref_ptr mGlowLight; - osg::ref_ptr mGlowUpdater; - osg::ref_ptr mTransparencyUpdater; - osg::ref_ptr mExtraLightSource; - - float mAlpha; - - mutable std::map mAnimVelocities; - - osg::ref_ptr mLightListCallback; - - const NodeMap& getNodeMap() const; - - /* Sets the appropriate animations on the bone groups based on priority. - */ - void resetActiveGroups(); - - size_t detectBlendMask(const osg::Node* node) const; - - /* Updates the position of the accum root node for the given time, and - * returns the wanted movement vector from the previous time. */ - void updatePosition(float oldtime, float newtime, osg::Vec3f& position); - - /* Resets the animation to the time of the specified start marker, without - * moving anything, and set the end time to the specified stop marker. If - * the marker is not found, or if the markers are the same, it returns - * false. - */ - bool reset(AnimState &state, const SceneUtil::TextKeyMap &keys, - const std::string &groupname, const std::string &start, const std::string &stop, - float startpoint, bool loopfallback); - - void handleTextKey(AnimState &state, const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, - const SceneUtil::TextKeyMap& map); - - /** Sets the root model of the object. - * - * Note that you must make sure all animation sources are cleared before resetting the object - * root. All nodes previously retrieved with getNode will also become invalidated. - * @param forceskeleton Wrap the object root in a Skeleton, even if it contains no skinned parts. Use this if you intend to add skinned parts manually. - * @param baseonly If true, then any meshes or particle systems in the model are ignored - * (useful for NPCs, where only the skeleton is needed for the root, and the actual NPC parts are then assembled from separate files). - */ - void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature); - - void loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel); - - /** 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, const std::string& baseModel); - void addSingleAnimSource(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); - - void clearAnimSources(); - - /** - * Provided to allow derived classes adding their own controllers. Note, the controllers must be added to mActiveControllers - * so they get cleaned up properly on the next controller rebuild. A controller rebuild may be necessary to ensure correct ordering. - */ - virtual void addControllers(); - -public: - - Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); - - /// Must be thread safe - virtual ~Animation(); - - MWWorld::ConstPtr getPtr() const; - - MWWorld::Ptr getPtr(); - - /// Set active flag on the object skeleton, if one exists. - /// @see SceneUtil::Skeleton::setActive - /// 0 = Inactive, 1 = Active in place, 2 = Active - void setActive(int active); - - osg::Group* getOrCreateObjectRoot(); - - osg::Group* getObjectRoot(); - - /** - * @brief Add an effect mesh attached to a bone or the insert scene node - * @param model - * @param effectId An ID for this effect by which you can identify it later. If this is not wanted, set to -1. - * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, - * you need to remove it manually using removeEffect when the effect should end. - * @param bonename Bone to attach to, or empty string to use the scene node instead - * @param texture override the texture specified in the model's materials - if empty, do not override - * @note Will not add an effect twice. - */ - void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = ""); - void removeEffect (int effectId); - void removeEffects (); - void getLoopingEffects (std::vector& out) const; - - // Add a spell casting glow to an object. From measuring video taken from the original engine, - // the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second. - void addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration = 1.5); - - virtual void updatePtr(const MWWorld::Ptr &ptr); - - bool hasAnimation(const std::string &anim) const; - - // Specifies the axis' to accumulate on. Non-accumulated axis will just - // move visually, but not affect the actual movement. Each x/y/z value - // should be on the scale of 0 to 1. - void setAccumulation(const osg::Vec3f& accum); - - /** Plays an animation. - * \param groupname Name of the animation group to play. - * \param priority Priority of the animation. The animation will play on - * bone groups that don't have another animation set of a - * higher priority. - * \param blendMask Bone groups to play the animation on. - * \param autodisable Automatically disable the animation when it stops - * playing. - * \param speedmult Speed multiplier for the animation. - * \param start Key marker from which to start. - * \param stop Key marker to stop at. - * \param startpoint How far in between the two markers to start. 0 starts - * at the start marker, 1 starts at the stop marker. - * \param loops How many times to loop the animation. This will use the - * "loop start" and "loop stop" markers if they exist, - * otherwise it may fall back to "start" and "stop", but only if - * the \a loopFallback parameter is true. - * \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use - * the "start" and "stop" keys for looping? - */ - void play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, - float speedmult, const std::string &start, const std::string &stop, - float startpoint, size_t loops, bool loopfallback=false); - - /** Adjust the speed multiplier of an already playing animation. - */ - void adjustSpeedMult (const std::string& groupname, float speedmult); - - /** Returns true if the named animation group is playing. */ - bool isPlaying(const std::string &groupname) const; - - /// Returns true if no important animations are currently playing on the upper body. - bool upperBodyReady() const; - - /** Gets info about the given animation group. - * \param groupname Animation group to check. - * \param complete Stores completion amount (0 = at start key, 0.5 = half way between start and stop keys), etc. - * \param speedmult Stores the animation speed multiplier - * \return True if the animation is active, false otherwise. - */ - bool getInfo(const std::string &groupname, float *complete=nullptr, float *speedmult=nullptr) const; - - /// Get the absolute position in the animation track of the first text key with the given group. - float getStartTime(const std::string &groupname) const; - - /// Get the absolute position in the animation track of the text key - float getTextKeyTime(const std::string &textKey) const; - - /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. - float getCurrentTime(const std::string& groupname) const; - - size_t getCurrentLoopCount(const std::string& groupname) const; - - /** Disables the specified animation group; - * \param groupname Animation group to disable. - */ - void disable(const std::string &groupname); - - /** Retrieves the velocity (in units per second) that the animation will move. */ - float getVelocity(const std::string &groupname) const; - - virtual osg::Vec3f runAnimation(float duration); - - void setLoopingEnabled(const std::string &groupname, bool enabled); - - /// This is typically called as part of runAnimation, but may be called manually if needed. - void updateEffects(); - - /// Return a node with the specified name, or nullptr if not existing. - /// @note The matching is case-insensitive. - const osg::Node* getNode(const std::string& name) const; - - virtual bool useShieldAnimations() const { return false; } - virtual void showWeapons(bool showWeapon) {} - virtual bool getCarriedLeftShown() const { return false; } - virtual void showCarriedLeft(bool show) {} - virtual void setWeaponGroup(const std::string& group, bool relativeDuration) {} - virtual void setVampire(bool vampire) {} - /// A value < 1 makes the animation translucent, 1.f = fully opaque - void setAlpha(float alpha); - virtual void setPitchFactor(float factor) {} - virtual void attachArrow() {} - virtual void detachArrow() {} - virtual void releaseArrow(float attackStrength) {} - virtual void enableHeadAnimation(bool enable) {} - // TODO: move outside of this class - /// Makes this object glow, by placing a Light in its center. - /// @param effect Controls the radius and intensity of the light. - virtual void setLightEffect(float effect); - - virtual void setHeadPitch(float pitchRadians); - virtual void setHeadYaw(float yawRadians); - virtual float getHeadPitch() const; - virtual float getHeadYaw() const; - - virtual void setUpperBodyYawRadians(float v) { mUpperBodyYawRadians = v; } - virtual void setLegsYawRadians(float v) { mLegsYawRadians = v; } - virtual float getUpperBodyYawRadians() const { return mUpperBodyYawRadians; } - virtual float getLegsYawRadians() const { return mLegsYawRadians; } - virtual void setBodyPitchRadians(float v) { mBodyPitchRadians = v; } - virtual float getBodyPitchRadians() const { return mBodyPitchRadians; } - - virtual void setAccurateAiming(bool enabled) {} - virtual bool canBeHarvested() const { return false; } - -private: - Animation(const Animation&); - void operator=(Animation&); -}; - -class ObjectAnimation : public Animation { -public: - ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight); - - bool canBeHarvested() const override; -}; - -class UpdateVfxCallback : public osg::NodeCallback -{ -public: - UpdateVfxCallback(EffectParams& params) - : mFinished(false) - , mParams(params) - , mStartingTime(0) + struct EffectParams { - } + std::string mModelName; // Just here so we don't add the same effect twice + std::shared_ptr mAnimTime; + float mMaxControllerLength; + int mEffectId; + bool mLoop; + std::string mBoneName; + }; - bool mFinished; - EffectParams mParams; + class Animation : public osg::Referenced + { + public: + enum BoneGroup + { + BoneGroup_LowerBody = 0, + BoneGroup_Torso, + BoneGroup_LeftArm, + BoneGroup_RightArm + }; - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + enum BlendMask + { + BlendMask_LowerBody = 1 << 0, + BlendMask_Torso = 1 << 1, + BlendMask_LeftArm = 1 << 2, + BlendMask_RightArm = 1 << 3, -private: - double mStartingTime; -}; + BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm, + + BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody + }; + /* This is the number of *discrete* blend masks. */ + static constexpr size_t sNumBlendMasks = 4; + + /// Holds an animation priority value for each BoneGroup. + struct AnimPriority + { + /// Convenience constructor, initialises all priorities to the same value. + AnimPriority(int priority) + { + for (unsigned int i = 0; i < sNumBlendMasks; ++i) + mPriority[i] = priority; + } + + bool operator==(const AnimPriority& other) const + { + for (unsigned int i = 0; i < sNumBlendMasks; ++i) + if (other.mPriority[i] != mPriority[i]) + return false; + return true; + } + + int& operator[](BoneGroup n) { return mPriority[n]; } + + const int& operator[](BoneGroup n) const { return mPriority[n]; } + + bool contains(int priority) const + { + for (unsigned int i = 0; i < sNumBlendMasks; ++i) + if (priority == mPriority[i]) + return true; + return false; + } + + int mPriority[sNumBlendMasks]; + }; + + class TextKeyListener + { + public: + virtual void handleTextKey( + std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) + = 0; + + virtual ~TextKeyListener() = default; + }; + + void setTextKeyListener(TextKeyListener* listener); + + virtual bool updateCarriedLeftVisible(const int weaptype) const { return false; } + + typedef std::unordered_map, Misc::StringUtils::CiHash, + Misc::StringUtils::CiEqual> + NodeMap; + + protected: + class AnimationTime : public SceneUtil::ControllerSource + { + private: + std::shared_ptr mTimePtr; + + public: + void setTimePtr(std::shared_ptr time) { mTimePtr = time; } + std::shared_ptr getTimePtr() const { return mTimePtr; } + + float getValue(osg::NodeVisitor* nv) override; + }; + + class NullAnimationTime : public SceneUtil::ControllerSource + { + public: + float getValue(osg::NodeVisitor* nv) override { return 0.f; } + }; + + struct AnimSource; + + struct AnimState + { + std::shared_ptr mSource; + float mStartTime; + float mLoopStartTime; + float mLoopStopTime; + float mStopTime; + + typedef std::shared_ptr TimePtr; + TimePtr mTime; + float mSpeedMult; + + bool mPlaying; + bool mLoopingEnabled; + size_t mLoopCount; + + AnimPriority mPriority; + int mBlendMask; + bool mAutoDisable; + + AnimState() + : mStartTime(0.0f) + , mLoopStartTime(0.0f) + , mLoopStopTime(0.0f) + , mStopTime(0.0f) + , mTime(new float) + , mSpeedMult(1.0f) + , mPlaying(false) + , mLoopingEnabled(true) + , mLoopCount(0) + , mPriority(0) + , mBlendMask(0) + , mAutoDisable(true) + { + } + ~AnimState() = default; + + float getTime() const { return *mTime; } + void setTime(float time) { *mTime = time; } + + bool shouldLoop() const { return getTime() >= mLoopStopTime && mLoopingEnabled && mLoopCount > 0; } + }; + typedef std::map> AnimStateMap; + AnimStateMap mStates; + + typedef std::vector> AnimSourceList; + AnimSourceList mAnimSources; + + osg::ref_ptr mInsert; + + osg::ref_ptr mObjectRoot; + SceneUtil::Skeleton* mSkeleton; + + // The node expected to accumulate movement during movement animations. + osg::ref_ptr mAccumRoot; + + // The controller animating that node. + osg::ref_ptr mAccumCtrl; + + // Used to reset the position of the accumulation root every frame - the movement should be applied to the + // physics system + osg::ref_ptr mResetAccumRootCallback; + + // Keep track of controllers that we added to our scene graph. + // We may need to rebuild these controllers when the active animation groups / sources change. + std::vector, osg::ref_ptr>> mActiveControllers; + + std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; + + mutable NodeMap mNodeMap; + mutable bool mNodeMapCreated; + + MWWorld::Ptr mPtr; + + Resource::ResourceSystem* mResourceSystem; + + osg::Vec3f mAccumulate; + + TextKeyListener* mTextKeyListener; + + osg::ref_ptr mHeadController; + osg::ref_ptr mSpineController; + osg::ref_ptr mRootController; + float mHeadYawRadians; + float mHeadPitchRadians; + float mUpperBodyYawRadians; + float mLegsYawRadians; + float mBodyPitchRadians; + + osg::ref_ptr addRotateController(std::string_view bone); + + bool mHasMagicEffects; + + osg::ref_ptr mGlowLight; + osg::ref_ptr mGlowUpdater; + osg::ref_ptr mTransparencyUpdater; + osg::ref_ptr mExtraLightSource; + + float mAlpha; + + mutable std::map> mAnimVelocities; + + osg::ref_ptr mLightListCallback; + + const NodeMap& getNodeMap() const; + + /* Sets the appropriate animations on the bone groups based on priority. + */ + void resetActiveGroups(); + + size_t detectBlendMask(const osg::Node* node, const std::string& controllerName) const; + + /* Updates the position of the accum root node for the given time, and + * returns the wanted movement vector from the previous time. */ + void updatePosition(float oldtime, float newtime, osg::Vec3f& position); + + /* Resets the animation to the time of the specified start marker, without + * moving anything, and set the end time to the specified stop marker. If + * the marker is not found, or if the markers are the same, it returns + * false. + */ + bool reset(AnimState& state, const SceneUtil::TextKeyMap& keys, std::string_view groupname, + std::string_view start, std::string_view stop, float startpoint, bool loopfallback); + + void handleTextKey(AnimState& state, std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key, + const SceneUtil::TextKeyMap& map); + + /** Sets the root model of the object. + * + * Note that you must make sure all animation sources are cleared before resetting the object + * root. All nodes previously retrieved with getNode will also become invalidated. + * @param forceskeleton Wrap the object root in a Skeleton, even if it contains no skinned parts. Use this if + * you intend to add skinned parts manually. + * @param baseonly If true, then any meshes or particle systems in the model are ignored + * (useful for NPCs, where only the skeleton is needed for the root, and the actual NPC parts are then + * assembled from separate files). + */ + void setObjectRoot(const std::string& model, bool forceskeleton, bool baseonly, bool isCreature); + + void loadAllAnimationsInFolder(const std::string& model, const std::string& baseModel); + + /** 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(std::string_view model, const std::string& baseModel); + void addSingleAnimSource(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 SceneUtil::LightCommon& light); + + void clearAnimSources(); + + /** + * Provided to allow derived classes adding their own controllers. Note, the controllers must be added to + * mActiveControllers so they get cleaned up properly on the next controller rebuild. A controller rebuild may + * be necessary to ensure correct ordering. + */ + virtual void addControllers(); + + void removeFromSceneImpl(); + + public: + Animation( + const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); + + /// Must be thread safe + virtual ~Animation(); + + MWWorld::ConstPtr getPtr() const { return mPtr; } + + MWWorld::Ptr getPtr() { return mPtr; } + + /// Set active flag on the object skeleton, if one exists. + /// @see SceneUtil::Skeleton::setActive + /// 0 = Inactive, 1 = Active in place, 2 = Active + void setActive(int active); + + osg::Group* getOrCreateObjectRoot(); + + osg::Group* getObjectRoot(); + + /** + * @brief Add an effect mesh attached to a bone or the insert scene node + * @param model + * @param effectId An ID for this effect by which you can identify it later. If this is not wanted, set to -1. + * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, + * you need to remove it manually using removeEffect when the effect should end. + * @param bonename Bone to attach to, or empty string to use the scene node instead + * @param texture override the texture specified in the model's materials - if empty, do not override + * @note Will not add an effect twice. + */ + void addEffect(const std::string& model, int effectId, bool loop = false, std::string_view bonename = {}, + std::string_view texture = {}); + void removeEffect(int effectId); + void removeEffects(); + void getLoopingEffects(std::vector& out) const; + + // Add a spell casting glow to an object. From measuring video taken from the original engine, + // the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second. + void addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration = 1.5); + + virtual void updatePtr(const MWWorld::Ptr& ptr); + + bool hasAnimation(std::string_view anim) const; + + // Specifies the axis' to accumulate on. Non-accumulated axis will just + // move visually, but not affect the actual movement. Each x/y/z value + // should be on the scale of 0 to 1. + void setAccumulation(const osg::Vec3f& accum); + + /** Plays an animation. + * \param groupname Name of the animation group to play. + * \param priority Priority of the animation. The animation will play on + * bone groups that don't have another animation set of a + * higher priority. + * \param blendMask Bone groups to play the animation on. + * \param autodisable Automatically disable the animation when it stops + * playing. + * \param speedmult Speed multiplier for the animation. + * \param start Key marker from which to start. + * \param stop Key marker to stop at. + * \param startpoint How far in between the two markers to start. 0 starts + * at the start marker, 1 starts at the stop marker. + * \param loops How many times to loop the animation. This will use the + * "loop start" and "loop stop" markers if they exist, + * otherwise it may fall back to "start" and "stop", but only if + * the \a loopFallback parameter is true. + * \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use + * the "start" and "stop" keys for looping? + */ + void play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable, + float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, + bool loopfallback = false); + + /** Adjust the speed multiplier of an already playing animation. + */ + void adjustSpeedMult(const std::string& groupname, float speedmult); + + /** Returns true if the named animation group is playing. */ + bool isPlaying(std::string_view groupname) const; + + /// Returns true if no important animations are currently playing on the upper body. + bool upperBodyReady() const; + + /** Gets info about the given animation group. + * \param groupname Animation group to check. + * \param complete Stores completion amount (0 = at start key, 0.5 = half way between start and stop keys), etc. + * \param speedmult Stores the animation speed multiplier + * \return True if the animation is active, false otherwise. + */ + bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr) const; + + /// Get the absolute position in the animation track of the first text key with the given group. + float getStartTime(const std::string& groupname) const; + + /// Get the absolute position in the animation track of the text key + float getTextKeyTime(std::string_view textKey) const; + + /// Get the current absolute position in the animation track for the animation that is currently playing from + /// the given group. + float getCurrentTime(const std::string& groupname) const; + + size_t getCurrentLoopCount(const std::string& groupname) const; + + /** Disables the specified animation group; + * \param groupname Animation group to disable. + */ + void disable(std::string_view groupname); + + /** Retrieves the velocity (in units per second) that the animation will move. */ + float getVelocity(std::string_view groupname) const; + + virtual osg::Vec3f runAnimation(float duration); + + void setLoopingEnabled(std::string_view groupname, bool enabled); + + /// This is typically called as part of runAnimation, but may be called manually if needed. + void updateEffects(); + + /// Return a node with the specified name, or nullptr if not existing. + /// @note The matching is case-insensitive. + const osg::Node* getNode(std::string_view name) const; + + virtual bool useShieldAnimations() const { return false; } + virtual bool getWeaponsShown() const { return false; } + virtual void showWeapons(bool showWeapon) {} + virtual bool getCarriedLeftShown() const { return false; } + virtual void showCarriedLeft(bool show) {} + virtual void setWeaponGroup(const std::string& group, bool relativeDuration) {} + virtual void setVampire(bool vampire) {} + /// A value < 1 makes the animation translucent, 1.f = fully opaque + void setAlpha(float alpha); + virtual void setPitchFactor(float factor) {} + virtual void attachArrow() {} + virtual void detachArrow() {} + virtual void releaseArrow(float attackStrength) {} + virtual void enableHeadAnimation(bool enable) {} + // TODO: move outside of this class + /// Makes this object glow, by placing a Light in its center. + /// @param effect Controls the radius and intensity of the light. + virtual void setLightEffect(float effect); + + virtual void setHeadPitch(float pitchRadians); + virtual void setHeadYaw(float yawRadians); + virtual float getHeadPitch() const; + virtual float getHeadYaw() const; + + virtual void setUpperBodyYawRadians(float v) { mUpperBodyYawRadians = v; } + virtual void setLegsYawRadians(float v) { mLegsYawRadians = v; } + virtual float getUpperBodyYawRadians() const { return mUpperBodyYawRadians; } + virtual float getLegsYawRadians() const { return mLegsYawRadians; } + virtual void setBodyPitchRadians(float v) { mBodyPitchRadians = v; } + virtual float getBodyPitchRadians() const { return mBodyPitchRadians; } + + virtual void setAccurateAiming(bool enabled) {} + virtual bool canBeHarvested() const { return false; } + + virtual void removeFromScene(); + + private: + Animation(const Animation&); + void operator=(Animation&); + }; + + class ObjectAnimation : public Animation + { + public: + ObjectAnimation(const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, + bool animated, bool allowLight); + + bool canBeHarvested() const override; + }; + + class UpdateVfxCallback : public SceneUtil::NodeCallback + { + public: + UpdateVfxCallback(EffectParams& params) + : mFinished(false) + , mParams(params) + , mStartingTime(0) + { + } + + bool mFinished; + EffectParams mParams; + + void operator()(osg::Node* node, osg::NodeVisitor* nv); + + private: + double mStartingTime; + }; } #endif diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index 207b0ee50..6b045ccda 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -4,146 +4,219 @@ #include #include +#include #include #include +#include #include +#include #include #include #include "bulletdebugdraw.hpp" #include "vismask.hpp" +#include +#include + +#include "../mwbase/environment.hpp" + namespace MWRender { -DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world, int debugMode) - : mParentNode(parentNode), - mWorld(world) -{ - setDebugMode(debugMode); -} - -void DebugDrawer::createGeometry() -{ - if (!mGeometry) + DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld* world, int debugMode) + : mParentNode(parentNode) + , mWorld(world) { - mGeometry = new osg::Geometry; - mGeometry->setNodeMask(Mask_Debug); - - mVertices = new osg::Vec3Array; - mColors = new osg::Vec4Array; - - mDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); - - mGeometry->setUseDisplayList(false); - mGeometry->setVertexArray(mVertices); - mGeometry->setColorArray(mColors); - mGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); - mGeometry->setDataVariance(osg::Object::DYNAMIC); - mGeometry->addPrimitiveSet(mDrawArrays); - - mParentNode->addChild(mGeometry); - - auto* stateSet = new osg::StateSet; - stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); - mShapesRoot = new osg::Group; - mShapesRoot->setStateSet(stateSet); - mShapesRoot->setDataVariance(osg::Object::DYNAMIC); - mShapesRoot->setNodeMask(Mask_Debug); - mParentNode->addChild(mShapesRoot); + DebugDrawer::setDebugMode(debugMode); } -} -void DebugDrawer::destroyGeometry() -{ - if (mGeometry) + void DebugDrawer::createGeometry() { - mParentNode->removeChild(mGeometry); - mParentNode->removeChild(mShapesRoot); - mGeometry = nullptr; - mVertices = nullptr; - mDrawArrays = nullptr; - } -} - -DebugDrawer::~DebugDrawer() -{ - destroyGeometry(); -} - -void DebugDrawer::step() -{ - if (mDebugOn) - { - mVertices->clear(); - mColors->clear(); - mShapesRoot->removeChildren(0, mShapesRoot->getNumChildren()); - mWorld->debugDrawWorld(); - showCollisions(); - mDrawArrays->setCount(mVertices->size()); - mVertices->dirty(); - mColors->dirty(); - mGeometry->dirtyBound(); - } -} - -void DebugDrawer::drawLine(const btVector3 &from, const btVector3 &to, const btVector3 &color) -{ - mVertices->push_back(Misc::Convert::toOsg(from)); - mVertices->push_back(Misc::Convert::toOsg(to)); - mColors->push_back({1,1,1,1}); - mColors->push_back({1,1,1,1}); -} - -void DebugDrawer::addCollision(const btVector3& orig, const btVector3& normal) -{ - mCollisionViews.emplace_back(orig, normal); -} - -void DebugDrawer::showCollisions() -{ - const auto now = std::chrono::steady_clock::now(); - for (auto& [from, to , created] : mCollisionViews) - { - if (now - created < std::chrono::seconds(2)) + if (!mLinesGeometry) { - mVertices->push_back(Misc::Convert::toOsg(from)); - mVertices->push_back(Misc::Convert::toOsg(to)); - mColors->push_back({1,0,0,1}); - mColors->push_back({1,0,0,1}); + mLinesGeometry = new osg::Geometry; + mTrisGeometry = new osg::Geometry; + mLinesGeometry->setNodeMask(Mask_Debug); + mTrisGeometry->setNodeMask(Mask_Debug); + + mLinesVertices = new osg::Vec3Array; + mTrisVertices = new osg::Vec3Array; + mLinesColors = new osg::Vec4Array; + + mLinesDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); + mTrisDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES); + + mLinesGeometry->setUseDisplayList(false); + mLinesGeometry->setVertexArray(mLinesVertices); + mLinesGeometry->setColorArray(mLinesColors); + mLinesGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); + mLinesGeometry->setDataVariance(osg::Object::DYNAMIC); + mLinesGeometry->addPrimitiveSet(mLinesDrawArrays); + + mTrisGeometry->setUseDisplayList(false); + mTrisGeometry->setVertexArray(mTrisVertices); + mTrisGeometry->setDataVariance(osg::Object::DYNAMIC); + mTrisGeometry->addPrimitiveSet(mTrisDrawArrays); + + mParentNode->addChild(mLinesGeometry); + mParentNode->addChild(mTrisGeometry); + + auto* stateSet = new osg::StateSet; + stateSet->setAttributeAndModes( + new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), + osg::StateAttribute::ON); + stateSet->setAttributeAndModes(new osg::PolygonOffset( + SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0, SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0)); + osg::ref_ptr material = new osg::Material; + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + stateSet->setAttribute(material); + mLinesGeometry->setStateSet(stateSet); + mTrisGeometry->setStateSet(stateSet); + mShapesRoot = new osg::Group; + mShapesRoot->setStateSet(stateSet); + mShapesRoot->setDataVariance(osg::Object::DYNAMIC); + mShapesRoot->setNodeMask(Mask_Debug); + mParentNode->addChild(mShapesRoot); + + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mLinesGeometry, "debug"); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mTrisGeometry, "debug"); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mShapesRoot, "debug"); } } - mCollisionViews.erase(std::remove_if(mCollisionViews.begin(), mCollisionViews.end(), + + void DebugDrawer::destroyGeometry() + { + if (mLinesGeometry) + { + mParentNode->removeChild(mLinesGeometry); + mParentNode->removeChild(mTrisGeometry); + mParentNode->removeChild(mShapesRoot); + mLinesGeometry = nullptr; + mLinesVertices = nullptr; + mLinesColors = nullptr; + mLinesDrawArrays = nullptr; + mTrisGeometry = nullptr; + mTrisVertices = nullptr; + mTrisDrawArrays = nullptr; + } + } + + DebugDrawer::~DebugDrawer() + { + destroyGeometry(); + } + + void DebugDrawer::step() + { + if (mDebugOn) + { + mLinesVertices->clear(); + mTrisVertices->clear(); + mLinesColors->clear(); + mShapesRoot->removeChildren(0, mShapesRoot->getNumChildren()); + mWorld->debugDrawWorld(); + showCollisions(); + mLinesDrawArrays->setCount(mLinesVertices->size()); + mTrisDrawArrays->setCount(mTrisVertices->size()); + mLinesVertices->dirty(); + mTrisVertices->dirty(); + mLinesColors->dirty(); + mLinesGeometry->dirtyBound(); + mTrisGeometry->dirtyBound(); + } + } + + void DebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& color) + { + mLinesVertices->push_back(Misc::Convert::toOsg(from)); + mLinesVertices->push_back(Misc::Convert::toOsg(to)); + mLinesColors->push_back({ 1, 1, 1, 1 }); + mLinesColors->push_back({ 1, 1, 1, 1 }); + +#if BT_BULLET_VERSION < 317 + size_t size = mLinesVertices->size(); + if (size >= 6 && (*mLinesVertices)[size - 1] == (*mLinesVertices)[size - 6] + && (*mLinesVertices)[size - 2] == (*mLinesVertices)[size - 3] + && (*mLinesVertices)[size - 4] == (*mLinesVertices)[size - 5]) + { + mTrisVertices->push_back(mLinesVertices->back()); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mTrisVertices->push_back(mLinesVertices->back()); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mTrisVertices->push_back(mLinesVertices->back()); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + } +#endif + } + + void DebugDrawer::drawTriangle( + const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar) + { + mTrisVertices->push_back(Misc::Convert::toOsg(v0)); + mTrisVertices->push_back(Misc::Convert::toOsg(v1)); + mTrisVertices->push_back(Misc::Convert::toOsg(v2)); + } + + void DebugDrawer::addCollision(const btVector3& orig, const btVector3& normal) + { + mCollisionViews.emplace_back(orig, normal); + } + + void DebugDrawer::showCollisions() + { + const auto now = std::chrono::steady_clock::now(); + for (auto& [from, to, created] : mCollisionViews) + { + if (now - created < std::chrono::seconds(2)) + { + mLinesVertices->push_back(Misc::Convert::toOsg(from)); + mLinesVertices->push_back(Misc::Convert::toOsg(to)); + mLinesColors->push_back({ 1, 0, 0, 1 }); + mLinesColors->push_back({ 1, 0, 0, 1 }); + } + } + mCollisionViews.erase( + std::remove_if(mCollisionViews.begin(), mCollisionViews.end(), [&now](const CollisionView& view) { return now - view.mCreated >= std::chrono::seconds(2); }), mCollisionViews.end()); -} + } -void DebugDrawer::drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) -{ - auto* geom = new osg::ShapeDrawable(new osg::Sphere(Misc::Convert::toOsg(transform.getOrigin()), radius)); - geom->setColor(osg::Vec4(1, 1, 1, 1)); - mShapesRoot->addChild(geom); -} + void DebugDrawer::drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) + { + auto* geom = new osg::ShapeDrawable(new osg::Sphere(Misc::Convert::toOsg(transform.getOrigin()), radius)); + geom->setColor(osg::Vec4(1, 1, 1, 1)); + mShapesRoot->addChild(geom); + } -void DebugDrawer::reportErrorWarning(const char *warningString) -{ - Log(Debug::Warning) << warningString; -} + void DebugDrawer::reportErrorWarning(const char* warningString) + { + Log(Debug::Warning) << warningString; + } -void DebugDrawer::setDebugMode(int isOn) -{ - mDebugOn = (isOn != 0); + void DebugDrawer::setDebugMode(int isOn) + { + mDebugOn = (isOn != 0); - if (!mDebugOn) - destroyGeometry(); - else - createGeometry(); -} + if (!mDebugOn) + destroyGeometry(); + else + createGeometry(); + } -int DebugDrawer::getDebugMode() const -{ - return mDebugOn; -} + int DebugDrawer::getDebugMode() const + { + return mDebugOn; + } } diff --git a/apps/openmw/mwrender/bulletdebugdraw.hpp b/apps/openmw/mwrender/bulletdebugdraw.hpp index b24640427..662fc9f14 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.hpp +++ b/apps/openmw/mwrender/bulletdebugdraw.hpp @@ -4,9 +4,9 @@ #include #include -#include #include #include +#include #include @@ -21,60 +21,71 @@ namespace osg namespace MWRender { -class DebugDrawer : public btIDebugDraw -{ -private: - struct CollisionView + class DebugDrawer : public btIDebugDraw { - btVector3 mOrig; - btVector3 mEnd; - std::chrono::time_point mCreated; - CollisionView(btVector3 orig, btVector3 normal) : mOrig(orig), mEnd(orig + normal * 20), mCreated(std::chrono::steady_clock::now()) {}; + private: + struct CollisionView + { + btVector3 mOrig; + btVector3 mEnd; + std::chrono::time_point mCreated; + CollisionView(btVector3 orig, btVector3 normal) + : mOrig(orig) + , mEnd(orig + normal * 20) + , mCreated(std::chrono::steady_clock::now()) + { + } + }; + std::vector mCollisionViews; + osg::ref_ptr mShapesRoot; + + protected: + osg::ref_ptr mParentNode; + btCollisionWorld* mWorld; + osg::ref_ptr mLinesGeometry; + osg::ref_ptr mTrisGeometry; + osg::ref_ptr mLinesVertices; + osg::ref_ptr mTrisVertices; + osg::ref_ptr mLinesColors; + osg::ref_ptr mLinesDrawArrays; + osg::ref_ptr mTrisDrawArrays; + + bool mDebugOn; + + void createGeometry(); + void destroyGeometry(); + + public: + DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld* world, int debugMode = 1); + ~DebugDrawer(); + + void step(); + + void drawLine(const btVector3& from, const btVector3& to, const btVector3& color) override; + + void drawTriangle( + const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar) override; + + void addCollision(const btVector3& orig, const btVector3& normal); + + void showCollisions(); + + void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, + const btVector3& color) override + { + } + void drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) override; + + void reportErrorWarning(const char* warningString) override; + + void draw3dText(const btVector3& location, const char* textString) override {} + + // 0 for off, anything else for on. + void setDebugMode(int isOn) override; + + // 0 for off, anything else for on. + int getDebugMode() const override; }; - std::vector mCollisionViews; - osg::ref_ptr mShapesRoot; - -protected: - osg::ref_ptr mParentNode; - btCollisionWorld *mWorld; - osg::ref_ptr mGeometry; - osg::ref_ptr mVertices; - osg::ref_ptr mColors; - osg::ref_ptr mDrawArrays; - - bool mDebugOn; - - void createGeometry(); - void destroyGeometry(); - -public: - - DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world, int debugMode = 1); - ~DebugDrawer(); - - void step(); - - void drawLine(const btVector3& from,const btVector3& to,const btVector3& color) override; - - void addCollision(const btVector3& orig, const btVector3& normal); - - void showCollisions(); - - void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) override {}; - void drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) override; - - void reportErrorWarning(const char* warningString) override; - - void draw3dText(const btVector3& location,const char* textString) override {} - - //0 for off, anything else for on. - void setDebugMode(int isOn) override; - - //0 for off, anything else for on. - int getDebugMode() const override; - -}; - } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 3e5d1d0b3..fbe5c6b4c 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -3,8 +3,8 @@ #include #include +#include #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -14,9 +14,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/refdata.hpp" -#include "../mwmechanics/drawstate.hpp" #include "../mwmechanics/movement.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwphysics/raycasting.hpp" @@ -25,73 +23,58 @@ namespace { -class UpdateRenderCameraCallback : public osg::NodeCallback -{ -public: - UpdateRenderCameraCallback(MWRender::Camera* cam) - : mCamera(cam) + class UpdateRenderCameraCallback : public SceneUtil::NodeCallback { - } + public: + UpdateRenderCameraCallback(MWRender::Camera* cam) + : mCamera(cam) + { + } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osg::Camera* cam = static_cast(node); + void operator()(osg::Camera* cam, osg::NodeVisitor* nv) + { + // traverse first to update animations, in case the camera is attached to an animated node + traverse(cam, nv); - // traverse first to update animations, in case the camera is attached to an animated node - traverse(node, nv); + mCamera->updateCamera(cam); + } - mCamera->updateCamera(cam); - } - -private: - MWRender::Camera* mCamera; -}; + private: + MWRender::Camera* mCamera; + }; } namespace MWRender { - Camera::Camera (osg::Camera* camera) - : mHeightScale(1.f), - mCamera(camera), - mAnimation(nullptr), - mFirstPersonView(true), - mMode(Mode::Normal), - mVanityAllowed(true), - mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")), - mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")), - mNearest(30.f), - mFurthest(800.f), - mIsNearest(false), - mHeight(124.f), - mBaseCameraDistance(Settings::Manager::getFloat("third person camera distance", "Camera")), - mPitch(0.f), - mYaw(0.f), - mRoll(0.f), - mVanityToggleQueued(false), - mVanityToggleQueuedValue(false), - mViewModeToggleQueued(false), - mCameraDistance(0.f), - mMaxNextCameraDistance(800.f), - mFocalPointCurrentOffset(osg::Vec2d()), - mFocalPointTargetOffset(osg::Vec2d()), - mFocalPointTransitionSpeedCoef(1.f), - mSkipFocalPointTransition(true), - mPreviousTransitionInfluence(0.f), - mSmoothedSpeed(0.f), - mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")), - mDynamicCameraDistanceEnabled(false), - mShowCrosshairInThirdPersonMode(false), - mHeadBobbingEnabled(Settings::Manager::getBool("head bobbing", "Camera")), - mHeadBobbingOffset(0.f), - mHeadBobbingWeight(0.f), - mTotalMovement(0.f), - mDeferredRotation(osg::Vec3f()), - mDeferredRotationDisabled(false) + Camera::Camera(osg::Camera* camera) + : mHeightScale(1.f) + , mCollisionType( + (MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor) + | MWPhysics::CollisionType_CameraOnly) + , mCamera(camera) + , mAnimation(nullptr) + , mFirstPersonView(true) + , mMode(Mode::FirstPerson) + , mVanityAllowed(true) + , mDeferredRotationAllowed(true) + , mProcessViewChange(false) + , mHeight(124.f) + , mPitch(0.f) + , mYaw(0.f) + , mRoll(0.f) + , mCameraDistance(0.f) + , mPreferredCameraDistance(0.f) + , mFocalPointCurrentOffset(osg::Vec2d()) + , mFocalPointTargetOffset(osg::Vec2d()) + , mFocalPointTransitionSpeedCoef(1.f) + , mSkipFocalPointTransition(true) + , mPreviousTransitionInfluence(0.f) + , mShowCrosshair(false) + , mDeferredRotation(osg::Vec3f()) + , mDeferredRotationDisabled(false) { - mCameraDistance = mBaseCameraDistance; - mUpdateCallback = new UpdateRenderCameraCallback(this); mCamera->addUpdateCallback(mUpdateCallback); } @@ -101,7 +84,7 @@ namespace MWRender mCamera->removeUpdateCallback(mUpdateCallback); } - osg::Vec3d Camera::getFocalPoint() const + osg::Vec3d Camera::calculateTrackedPosition() const { if (!mTrackingNode) return osg::Vec3d(); @@ -109,194 +92,156 @@ namespace MWRender if (nodepaths.empty()) return osg::Vec3d(); osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); - - osg::Vec3d position = worldMat.getTrans(); - if (isFirstPerson()) - position.z() += mHeadBobbingOffset; - else - { - position.z() += mHeight * mHeightScale; - - // We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling. - // Needed because character's head can be a bit higher than collision area. - position.z() -= 10.f; - - position += getFocalPointOffset() + mFocalPointAdjustment; - } - return position; + osg::Vec3d res = worldMat.getTrans(); + if (mMode != Mode::FirstPerson) + res.z() += mHeight * mHeightScale; + return res; } osg::Vec3d Camera::getFocalPointOffset() const { - osg::Vec3d offset(0, 0, 10.f); - offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw()); - offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw()); - offset.z() += mFocalPointCurrentOffset.y(); + osg::Vec3d offset; + offset.x() = mFocalPointCurrentOffset.x() * cos(mYaw); + offset.y() = mFocalPointCurrentOffset.x() * sin(mYaw); + offset.z() = mFocalPointCurrentOffset.y(); return offset; } - void Camera::getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const + void Camera::updateCamera(osg::Camera* cam) { - focal = getFocalPoint(); - osg::Vec3d offset(0,0,0); - if (!isFirstPerson()) + osg::Quat orient = osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) + * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); + osg::Vec3d forward = orient * osg::Vec3d(0, 1, 0); + osg::Vec3d up = orient * osg::Vec3d(0, 0, 1); + + osg::Vec3d pos = mPosition; + if (mMode == Mode::FirstPerson) { - osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); - offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); + // It is a hack. Camera position depends on neck animation. + // Animations are updated in OSG cull traversal and in order to avoid 1 frame delay we + // recalculate the position here. Note that it becomes different from mPosition that + // is used in other parts of the code. + // TODO: detach camera from OSG animation and get rid of this hack. + osg::Vec3d recalculatedTrackedPosition = calculateTrackedPosition(); + pos = calculateFirstPersonPosition(recalculatedTrackedPosition); } - camera = focal + offset; - } - - void Camera::updateCamera(osg::Camera *cam) - { - osg::Vec3d focal, position; - getPosition(focal, position); - - osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, osg::Vec3d(0, 0, 1)); - osg::Vec3d forward = orient * osg::Vec3d(0,1,0); - osg::Vec3d up = orient * osg::Vec3d(0,0,1); - - cam->setViewMatrixAsLookAt(position, position + forward, up); - } - - void Camera::updateHeadBobbing(float duration) { - static const float doubleStepLength = Settings::Manager::getFloat("head bobbing step", "Camera") * 2; - static const float stepHeight = Settings::Manager::getFloat("head bobbing height", "Camera"); - static const float maxRoll = osg::DegreesToRadians(Settings::Manager::getFloat("head bobbing roll", "Camera")); - - if (MWBase::Environment::get().getWorld()->isOnGround(mTrackingPtr)) - mHeadBobbingWeight = std::min(mHeadBobbingWeight + duration * 5, 1.f); - else - mHeadBobbingWeight = std::max(mHeadBobbingWeight - duration * 5, 0.f); - - float doubleStepState = mTotalMovement / doubleStepLength - std::floor(mTotalMovement / doubleStepLength); // from 0 to 1 during 2 steps - float stepState = std::abs(doubleStepState * 4 - 2) - 1; // from -1 to 1 on even steps and from 1 to -1 on odd steps - float effect = (1 - std::cos(stepState * osg::DegreesToRadians(30.f))) * 7.5f; // range from 0 to 1 - float coef = std::min(mSmoothedSpeed / 300.f, 1.f) * mHeadBobbingWeight; - mHeadBobbingOffset = (0.5f - effect) * coef * stepHeight; // range from -stepHeight/2 to stepHeight/2 - mRoll = osg::sign(stepState) * effect * coef * maxRoll; // range from -maxRoll to maxRoll - } - - void Camera::reset() - { - togglePreviewMode(false); - toggleVanityMode(false); - if (!mFirstPersonView) - toggleViewMode(); - } - - void Camera::rotateCamera(float pitch, float yaw, bool adjust) - { - if (adjust) - { - pitch += getPitch(); - yaw += getYaw(); - } - setYaw(yaw); - setPitch(pitch); + cam->setViewMatrixAsLookAt(pos, pos + forward, up); + mViewMatrix = cam->getViewMatrix(); + mProjectionMatrix = cam->getProjectionMatrix(); } void Camera::update(float duration, bool paused) { - if (mAnimation->upperBodyReady()) - { - // Now process the view changes we queued earlier - if (mVanityToggleQueued) - { - toggleVanityMode(mVanityToggleQueuedValue); - mVanityToggleQueued = false; - } - if (mViewModeToggleQueued) - { - togglePreviewMode(false); - toggleViewMode(); - mViewModeToggleQueued = false; - } - } + mLockPitch = mLockYaw = false; + if (mQueuedMode && mAnimation->upperBodyReady()) + setMode(*mQueuedMode); + if (mProcessViewChange) + processViewChange(); if (paused) return; // only show the crosshair in game mode - MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); - wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity - && (mFirstPersonView || mShowCrosshairInThirdPersonMode)); - - if(mMode == Mode::Vanity) - rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true); - - if (isFirstPerson() && mHeadBobbingEnabled) - updateHeadBobbing(duration); - else - mRoll = mHeadBobbingOffset = 0; + MWBase::WindowManager* wm = MWBase::Environment::get().getWindowManager(); + wm->showCrosshair(!wm->isGuiMode() && mShowCrosshair); updateFocalPointOffset(duration); updatePosition(); + } - float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); - mTotalMovement += speed * duration; - speed /= (1.f + speed / 500.f); - float maxDelta = 300.f * duration; - mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta); - - mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance); - updateStandingPreviewMode(); + osg::Vec3d Camera::calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const + { + osg::Vec3d res = trackedPosition; + osg::Vec2f horizontalOffset + = Misc::rotateVec2f(osg::Vec2f(mFirstPersonOffset.x(), mFirstPersonOffset.y()), mYaw); + res.x() += horizontalOffset.x(); + res.y() += horizontalOffset.y(); + res.z() += mFirstPersonOffset.z(); + return res; } void Camera::updatePosition() { - mFocalPointAdjustment = osg::Vec3d(); - if (isFirstPerson()) + mTrackedPosition = calculateTrackedPosition(); + if (mMode == Mode::Static) return; + if (mMode == Mode::FirstPerson) + { + mPosition = calculateFirstPersonPosition(mTrackedPosition); + mCameraDistance = 0; + return; + } - const float cameraObstacleLimit = 5.0f; - const float focalObstacleLimit = 10.f; + constexpr float cameraObstacleLimit = 5.0f; + constexpr float focalObstacleLimit = 10.f; const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); // Adjust focal point to prevent clipping. - osg::Vec3d focal = getFocalPoint(); osg::Vec3d focalOffset = getFocalPointOffset(); + osg::Vec3d focal = mTrackedPosition + focalOffset; + focalOffset.z() += 10.f; // Needed to avoid camera clipping through the ceiling because + // character's head can be a bit higher than the collision area. float offsetLen = focalOffset.length(); if (offsetLen > 0) { - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit); + MWPhysics::RayCastingResult result + = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, mCollisionType); if (result.mHit) { - double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; - mFocalPointAdjustment = focalOffset * std::max(-1.0, adjustmentCoef); + double adjustmentCoef + = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; + focal += focalOffset * std::max(-1.0, adjustmentCoef); } } - // Calculate camera distance. - mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection(); - if (mDynamicCameraDistanceEnabled) - mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance); - osg::Vec3d cameraPos; - getPosition(focal, cameraPos); - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit); + // Adjust camera distance. + mCameraDistance = mPreferredCameraDistance; + osg::Quat orient + = osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); + osg::Vec3d offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); + MWPhysics::RayCastingResult result + = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, mCollisionType); if (result.mHit) - mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); - } - - void Camera::updateStandingPreviewMode() - { - if (!mStandingPreviewAllowed) - return; - float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); - bool combat = mTrackingPtr.getClass().isActor() && - mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing; - bool standingStill = speed == 0 && !combat && !mFirstPersonView; - if (!standingStill && mMode == Mode::StandingPreview) { - mMode = Mode::Normal; - calculateDeferredRotation(); + mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); + offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); } - else if (standingStill && mMode == Mode::Normal) - mMode = Mode::StandingPreview; + + mPosition = focal + offset; } - void Camera::setFocalPointTargetOffset(osg::Vec2d v) + void Camera::setMode(Mode newMode, bool force) + { + if (mMode == newMode) + { + mQueuedMode = std::nullopt; + return; + } + Mode oldMode = mMode; + if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && mAnimation + && !mAnimation->upperBodyReady()) + { + // Changing the view will stop all playing animations, so if we are playing + // anything important, queue the view change for later + mQueuedMode = newMode; + return; + } + mMode = newMode; + mQueuedMode = std::nullopt; + if (newMode == Mode::FirstPerson) + mFirstPersonView = true; + else if (newMode == Mode::ThirdPerson) + mFirstPersonView = false; + calculateDeferredRotation(); + if (oldMode == Mode::FirstPerson || newMode == Mode::FirstPerson) + { + instantTransition(); + mProcessViewChange = true; + } + } + + void Camera::setFocalPointTargetOffset(const osg::Vec2d& v) { mFocalPointTargetOffset = v; mPreviousTransitionSpeed = mFocalPointTransitionSpeed; @@ -321,9 +266,10 @@ namespace MWRender if (mPreviousTransitionInfluence > 0) { mFocalPointCurrentOffset -= mPreviousExtraOffset; - mPreviousExtraOffset = mPreviousExtraOffset / mPreviousTransitionInfluence + mPreviousTransitionSpeed * duration; - mPreviousTransitionInfluence = - std::max(0.f, mPreviousTransitionInfluence - duration * mFocalPointTransitionSpeedCoef); + mPreviousExtraOffset + = mPreviousExtraOffset / mPreviousTransitionInfluence + mPreviousTransitionSpeed * duration; + mPreviousTransitionInfluence + = std::max(0.f, mPreviousTransitionInfluence - duration * mFocalPointTransitionSpeedCoef); mPreviousExtraOffset *= mPreviousTransitionInfluence; mFocalPointCurrentOffset += mPreviousExtraOffset; } @@ -331,8 +277,8 @@ namespace MWRender osg::Vec2d delta = mFocalPointTargetOffset - mFocalPointCurrentOffset; if (delta.length2() > 0) { - float coef = duration * (1.0 + 5.0 / delta.length()) * - mFocalPointTransitionSpeedCoef * (1.0f - mPreviousTransitionInfluence); + float coef = duration * (1.0 + 5.0 / delta.length()) * mFocalPointTransitionSpeedCoef + * (1.0f - mPreviousTransitionInfluence); mFocalPointCurrentOffset += delta * std::min(coef, 1.0f); } else @@ -346,146 +292,59 @@ namespace MWRender void Camera::toggleViewMode(bool force) { - // Changing the view will stop all playing animations, so if we are playing - // anything important, queue the view change for later - if (!mAnimation->upperBodyReady() && !force) - { - mViewModeToggleQueued = true; - return; - } - else - mViewModeToggleQueued = false; - - mFirstPersonView = !mFirstPersonView; - updateStandingPreviewMode(); - instantTransition(); - processViewChange(); - } - - void Camera::allowVanityMode(bool allow) - { - if (!allow && mMode == Mode::Vanity) - { - disableDeferredPreviewRotation(); - toggleVanityMode(false); - } - mVanityAllowed = allow; + setMode(mFirstPersonView ? Mode::ThirdPerson : Mode::FirstPerson, force); } bool Camera::toggleVanityMode(bool enable) { - // Changing the view will stop all playing animations, so if we are playing - // anything important, queue the view change for later - if (mFirstPersonView && !mAnimation->upperBodyReady()) - { - mVanityToggleQueued = true; - mVanityToggleQueuedValue = enable; - return false; - } - - if (!mVanityAllowed && enable) - return false; - - if ((mMode == Mode::Vanity) == enable) - return true; - mMode = enable ? Mode::Vanity : Mode::Normal; - if (!mDeferredRotationAllowed) - disableDeferredPreviewRotation(); if (!enable) - calculateDeferredRotation(); - - processViewChange(); - return true; - } - - void Camera::togglePreviewMode(bool enable) - { - if (mFirstPersonView && !mAnimation->upperBodyReady()) - return; - - if((mMode == Mode::Preview) == enable) - return; - - mMode = enable ? Mode::Preview : Mode::Normal; - if (mMode == Mode::Normal) - updateStandingPreviewMode(); - else if (mFirstPersonView) - instantTransition(); - if (mMode == Mode::Normal) - { - if (!mDeferredRotationAllowed) - disableDeferredPreviewRotation(); - calculateDeferredRotation(); - } - processViewChange(); + setMode(mFirstPersonView ? Mode::FirstPerson : Mode::ThirdPerson, false); + else if (mVanityAllowed) + setMode(Mode::Vanity, false); + return (mMode == Mode::Vanity) == enable; } void Camera::setSneakOffset(float offset) { - mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); + mAnimation->setFirstPersonOffset(osg::Vec3f(0, 0, -offset)); } - void Camera::setYaw(float angle) + void Camera::setYaw(float angle, bool force) { - mYaw = Misc::normalizeAngle(angle); + if (!mLockYaw || force) + mYaw = Misc::normalizeAngle(angle); + if (force) + mLockYaw = true; } - void Camera::setPitch(float angle) + void Camera::setPitch(float angle, bool force) { const float epsilon = 0.000001f; float limit = static_cast(osg::PI_2) - epsilon; - mPitch = osg::clampBetween(angle, -limit, limit); + if (!mLockPitch || force) + mPitch = std::clamp(angle, -limit, limit); + if (force) + mLockPitch = true; } - float Camera::getCameraDistance() const + void Camera::setStaticPosition(const osg::Vec3d& pos) { - if (isFirstPerson()) - return 0.f; - return mCameraDistance; + if (mMode != Mode::Static) + throw std::runtime_error("setStaticPosition can be used only if camera is in Static mode"); + mPosition = pos; } - void Camera::adjustCameraDistance(float delta) - { - if (!isFirstPerson()) - { - if(isNearest() && delta < 0.f && getMode() != Mode::Preview && getMode() != Mode::Vanity) - toggleViewMode(); - else - mBaseCameraDistance = std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance) + delta; - } - else if (delta > 0.f) - { - toggleViewMode(); - mBaseCameraDistance = 0; - } - - mIsNearest = mBaseCameraDistance <= mNearest; - mBaseCameraDistance = osg::clampBetween(mBaseCameraDistance, mNearest, mFurthest); - Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance); - } - - float Camera::getCameraDistanceCorrection() const - { - if (!mDynamicCameraDistanceEnabled) - return 0; - - float pitchCorrection = std::max(-getPitch(), 0.f) * 50.f; - - float smoothedSpeedSqr = mSmoothedSpeed * mSmoothedSpeed; - float speedCorrection = smoothedSpeedSqr / (smoothedSpeedSqr + 300.f*300.f) * mZoomOutWhenMoveCoef; - - return pitchCorrection + speedCorrection; - } - - void Camera::setAnimation(NpcAnimation *anim) + void Camera::setAnimation(NpcAnimation* anim) { mAnimation = anim; - processViewChange(); + mProcessViewChange = true; } void Camera::processViewChange() { - if(isFirstPerson()) + if (mTrackingPtr.isEmpty()) + return; + if (mMode == Mode::FirstPerson) { mAnimation->setViewMode(NpcAnimation::VM_FirstPerson); mTrackingNode = mAnimation->getNode("Camera"); @@ -503,12 +362,12 @@ namespace MWRender else mHeightScale = 1.f; } - rotateCamera(getPitch(), getYaw(), false); + mProcessViewChange = false; } void Camera::applyDeferredPreviewRotationToPlayer(float dt) { - if (isVanityOrPreviewModeEnabled() || mTrackingPtr.isEmpty()) + if (mMode != Mode::ThirdPerson || mTrackingPtr.isEmpty()) return; osg::Vec3f rot = mDeferredRotation; @@ -534,13 +393,15 @@ namespace MWRender float c = std::cos(mDeferredRotation.z()); float x = movement.mPosition[0]; float y = movement.mPosition[1]; - movement.mPosition[0] = x * c + y * s; + movement.mPosition[0] = x * c + y * s; movement.mPosition[1] = x * -s + y * c; } } void Camera::rotateCameraToTrackingPtr() { + if (mMode == Mode::Static || mTrackingPtr.isEmpty()) + return; setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x()); setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z()); } @@ -555,8 +416,13 @@ namespace MWRender void Camera::calculateDeferredRotation() { + if (mMode == Mode::Static) + { + mDeferredRotation = osg::Vec3f(); + return; + } MWWorld::Ptr ptr = mTrackingPtr; - if (isVanityOrPreviewModeEnabled() || ptr.isEmpty()) + if (mMode == Mode::Preview || mMode == Mode::Vanity || ptr.isEmpty()) return; if (mFirstPersonView) { @@ -566,6 +432,8 @@ namespace MWRender mDeferredRotation.x() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[0] - mPitch); mDeferredRotation.z() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[2] - mYaw); + if (!mDeferredRotationAllowed) + mDeferredRotationDisabled = true; } } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 9e2b608df..c6500160f 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -1,18 +1,20 @@ #ifndef GAME_MWRENDER_CAMERA_H #define GAME_MWRENDER_CAMERA_H +#include #include -#include +#include #include #include +#include #include "../mwworld/ptr.hpp" namespace osg { class Camera; - class NodeCallback; + class Callback; class Node; } @@ -24,38 +26,125 @@ namespace MWRender class Camera { public: - enum class Mode { Normal, Vanity, Preview, StandingPreview }; + enum class Mode : int + { + Static = 0, + FirstPerson = 1, + ThirdPerson = 2, + Vanity = 3, + Preview = 4 + }; + + Camera(osg::Camera* camera); + ~Camera(); + + /// Attach camera to object + void attachTo(const MWWorld::Ptr& ptr) { mTrackingPtr = ptr; } + MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; } + + void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; } + float getFocalPointTransitionSpeed() const { return mFocalPointTransitionSpeedCoef; } + void setFocalPointTargetOffset(const osg::Vec2d& v); + osg::Vec2d getFocalPointTargetOffset() const { return mFocalPointTargetOffset; } + void instantTransition(); + void showCrosshair(bool v) { mShowCrosshair = v; } + + /// Update the view matrix of \a cam + void updateCamera(osg::Camera* cam); + + /// Reset to defaults + void reset() { setMode(Mode::FirstPerson); } + + void rotateCameraToTrackingPtr(); + + float getPitch() const { return mPitch; } + float getYaw() const { return mYaw; } + float getRoll() const { return mRoll; } + + void setPitch(float angle, bool force = false); + void setYaw(float angle, bool force = false); + void setRoll(float angle) { mRoll = angle; } + + float getExtraPitch() const { return mExtraPitch; } + float getExtraYaw() const { return mExtraYaw; } + float getExtraRoll() const { return mExtraRoll; } + void setExtraPitch(float angle) { mExtraPitch = angle; } + void setExtraYaw(float angle) { mExtraYaw = angle; } + void setExtraRoll(float angle) { mExtraRoll = angle; } + + /// @param Force view mode switch, even if currently not allowed by the animation. + void toggleViewMode(bool force = false); + bool toggleVanityMode(bool enable); + + void applyDeferredPreviewRotationToPlayer(float dt); + void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; } + + /// \brief Lowers the camera for sneak. + void setSneakOffset(float offset); + + void processViewChange(); + + void update(float duration, bool paused = false); + + float getCameraDistance() const { return mCameraDistance; } + void setPreferredCameraDistance(float v) { mPreferredCameraDistance = v; } + + void setAnimation(NpcAnimation* anim); + + osg::Vec3d getTrackedPosition() const { return mTrackedPosition; } + const osg::Vec3d& getPosition() const { return mPosition; } + void setStaticPosition(const osg::Vec3d& pos); + + bool isVanityOrPreviewModeEnabled() const { return mMode == Mode::Vanity || mMode == Mode::Preview; } + Mode getMode() const { return mMode; } + std::optional getQueuedMode() const { return mQueuedMode; } + void setMode(Mode mode, bool force = true); + + void allowCharacterDeferredRotation(bool v) { mDeferredRotationAllowed = v; } + void calculateDeferredRotation(); + void setFirstPersonOffset(const osg::Vec3f& v) { mFirstPersonOffset = v; } + osg::Vec3f getFirstPersonOffset() const { return mFirstPersonOffset; } + + int getCollisionType() const { return mCollisionType; } + void setCollisionType(int collisionType) { mCollisionType = collisionType; } + + const osg::Matrixf& getViewMatrix() const { return mViewMatrix; } + const osg::Matrixf& getProjectionMatrix() const { return mProjectionMatrix; } private: MWWorld::Ptr mTrackingPtr; osg::ref_ptr mTrackingNode; + osg::Vec3d mTrackedPosition; float mHeightScale; + int mCollisionType; osg::ref_ptr mCamera; - NpcAnimation *mAnimation; + NpcAnimation* mAnimation; + // Always 'true' if mMode == `FirstPerson`. Also it is 'true' in `Vanity` or `Preview` modes if + // the camera should return to `FirstPerson` view after it. bool mFirstPersonView; + Mode mMode; + std::optional mQueuedMode; bool mVanityAllowed; - bool mStandingPreviewAllowed; bool mDeferredRotationAllowed; - float mNearest; - float mFurthest; - bool mIsNearest; + bool mProcessViewChange; - float mHeight, mBaseCameraDistance; + float mHeight; float mPitch, mYaw, mRoll; + float mExtraPitch = 0, mExtraYaw = 0, mExtraRoll = 0; + bool mLockPitch = false, mLockYaw = false; + osg::Vec3d mPosition; + osg::Matrixf mViewMatrix; + osg::Matrixf mProjectionMatrix; - bool mVanityToggleQueued; - bool mVanityToggleQueuedValue; - bool mViewModeToggleQueued; + float mCameraDistance, mPreferredCameraDistance; - float mCameraDistance; - float mMaxNextCameraDistance; + osg::Vec3f mFirstPersonOffset{ 0, 0, 0 }; - osg::Vec3d mFocalPointAdjustment; osg::Vec2d mFocalPointCurrentOffset; osg::Vec2d mFocalPointTargetOffset; float mFocalPointTransitionSpeedCoef; @@ -67,98 +156,19 @@ namespace MWRender osg::Vec2d mPreviousTransitionSpeed; osg::Vec2d mPreviousExtraOffset; - float mSmoothedSpeed; - float mZoomOutWhenMoveCoef; - bool mDynamicCameraDistanceEnabled; - bool mShowCrosshairInThirdPersonMode; - - bool mHeadBobbingEnabled; - float mHeadBobbingOffset; - float mHeadBobbingWeight; // Value from 0 to 1 for smooth enabling/disabling. - float mTotalMovement; // Needed for head bobbing. - void updateHeadBobbing(float duration); + bool mShowCrosshair; + osg::Vec3d calculateTrackedPosition() const; + osg::Vec3d calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const; + osg::Vec3d getFocalPointOffset() const; void updateFocalPointOffset(float duration); void updatePosition(); - float getCameraDistanceCorrection() const; - osg::ref_ptr mUpdateCallback; + osg::ref_ptr mUpdateCallback; // Used to rotate player to the direction of view after exiting preview or vanity mode. osg::Vec3f mDeferredRotation; bool mDeferredRotationDisabled; - void calculateDeferredRotation(); - void updateStandingPreviewMode(); - - public: - Camera(osg::Camera* camera); - ~Camera(); - - /// Attach camera to object - void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; } - MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; } - - void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; } - void setFocalPointTargetOffset(osg::Vec2d v); - void instantTransition(); - void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; } - void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; } - - /// Update the view matrix of \a cam - void updateCamera(osg::Camera* cam); - - /// Reset to defaults - void reset(); - - /// Set where the camera is looking at. Uses Morrowind (euler) angles - /// \param rot Rotation angles in radians - void rotateCamera(float pitch, float yaw, bool adjust); - void rotateCameraToTrackingPtr(); - - float getYaw() const { return mYaw; } - void setYaw(float angle); - - float getPitch() const { return mPitch; } - void setPitch(float angle); - - /// @param Force view mode switch, even if currently not allowed by the animation. - void toggleViewMode(bool force=false); - - bool toggleVanityMode(bool enable); - void allowVanityMode(bool allow); - - /// @note this may be ignored if an important animation is currently playing - void togglePreviewMode(bool enable); - - void applyDeferredPreviewRotationToPlayer(float dt); - void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; } - - /// \brief Lowers the camera for sneak. - void setSneakOffset(float offset); - - bool isFirstPerson() const { return mFirstPersonView && mMode == Mode::Normal; } - - void processViewChange(); - - void update(float duration, bool paused=false); - - /// Adds distDelta to the camera distance. Switches 3rd/1st person view if distance is less than limit. - void adjustCameraDistance(float distDelta); - - float getCameraDistance() const; - - void setAnimation(NpcAnimation *anim); - - osg::Vec3d getFocalPoint() const; - osg::Vec3d getFocalPointOffset() const; - - /// Stores focal and camera world positions in passed arguments - void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const; - - bool isVanityOrPreviewModeEnabled() const { return mMode != Mode::Normal; } - Mode getMode() const { return mMode; } - - bool isNearest() const { return mIsNearest; } }; } diff --git a/apps/openmw/mwrender/cell.hpp b/apps/openmw/mwrender/cell.hpp index 8fa3f9f0f..7fe7e08a5 100644 --- a/apps/openmw/mwrender/cell.hpp +++ b/apps/openmw/mwrender/cell.hpp @@ -7,28 +7,27 @@ namespace MWRender { class CellRender { - public: + public: + virtual ~CellRender() = default; - virtual ~CellRender() {}; + /// Make the cell visible. Load the cell if necessary. + virtual void show() = 0; - /// Make the cell visible. Load the cell if necessary. - virtual void show() = 0; + /// Remove the cell from rendering, but don't remove it from + /// memory. + virtual void hide() = 0; - /// Remove the cell from rendering, but don't remove it from - /// memory. - virtual void hide() = 0; + /// Destroy all rendering objects connected with this cell. + virtual void destroy() = 0; - /// Destroy all rendering objects connected with this cell. - virtual void destroy() = 0; + /// Make the reference with the given handle visible. + virtual void enable(const std::string& handle) = 0; - /// Make the reference with the given handle visible. - virtual void enable (const std::string& handle) = 0; + /// Make the reference with the given handle invisible. + virtual void disable(const std::string& handle) = 0; - /// Make the reference with the given handle invisible. - virtual void disable (const std::string& handle) = 0; - - /// Remove the reference with the given handle permanently from the scene. - virtual void deleteObject (const std::string& handle) = 0; + /// Remove the reference with the given handle permanently from the scene. + virtual void deleteObject(const std::string& handle) = 0; }; } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index e88c4cee3..38238b121 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -2,28 +2,31 @@ #include -#include -#include #include -#include -#include #include -#include +#include #include #include +#include +#include +#include +#include #include #include #include #include #include -#include #include +#include +#include #include +#include +#include #include #include +#include -#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -36,16 +39,17 @@ namespace MWRender { - class DrawOnceCallback : public osg::NodeCallback + class DrawOnceCallback : public SceneUtil::NodeCallback { public: - DrawOnceCallback () + DrawOnceCallback(osg::Node* subgraph) : mRendered(false) , mLastRenderedFrame(0) + , mSubgraph(subgraph) { } - void operator () (osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (!mRendered) { @@ -59,6 +63,10 @@ namespace MWRender nv->setFrameStamp(fs); + // Update keyframe controllers in the scene graph first... + // RTTNode does not continue update traversal, so manually continue the update traversal since we need + // it. + mSubgraph->accept(*nv); traverse(node, nv); nv->setFrameStamp(previousFramestamp); @@ -69,28 +77,23 @@ namespace MWRender } } - void redrawNextFrame() - { - mRendered = false; - } + void redrawNextFrame() { mRendered = false; } - unsigned int getLastRenderedFrame() const - { - return mLastRenderedFrame; - } + unsigned int getLastRenderedFrame() const { return mLastRenderedFrame; } private: bool mRendered; unsigned int mLastRenderedFrame; + osg::ref_ptr mSubgraph; }; - // Set up alpha blending mode to avoid issues caused by transparent objects writing onto the alpha value of the FBO // This makes the RTT have premultiplied alpha, though, so the source blend factor must be GL_ONE when it's applied class SetUpBlendVisitor : public osg::NodeVisitor { public: - SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mNoAlphaUniform(new osg::Uniform("noAlpha", false)) + SetUpBlendVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } @@ -99,9 +102,11 @@ namespace MWRender if (osg::ref_ptr stateset = node.getStateSet()) { osg::ref_ptr newStateSet; - if (stateset->getAttribute(osg::StateAttribute::BLENDFUNC) || stateset->getBinNumber() == osg::StateSet::TRANSPARENT_BIN) + if (stateset->getAttribute(osg::StateAttribute::BLENDFUNC) + || stateset->getBinNumber() == osg::StateSet::TRANSPARENT_BIN) { - osg::BlendFunc* blendFunc = static_cast(stateset->getAttribute(osg::StateAttribute::BLENDFUNC)); + osg::BlendFunc* blendFunc + = static_cast(stateset->getAttribute(osg::StateAttribute::BLENDFUNC)); if (blendFunc) { @@ -109,15 +114,19 @@ namespace MWRender node.setStateSet(newStateSet); osg::ref_ptr newBlendFunc = new osg::BlendFunc(*blendFunc); newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); - // I *think* (based on some by-hand maths) that the RGB and dest alpha factors are unchanged, and only dest determines source alpha factor - // This has the benefit of being idempotent if we assume nothing used glBlendFuncSeparate before we touched it + // I *think* (based on some by-hand maths) that the RGB and dest alpha factors are unchanged, + // and only dest determines source alpha factor This has the benefit of being idempotent if we + // assume nothing used glBlendFuncSeparate before we touched it if (blendFunc->getDestination() == osg::BlendFunc::ONE_MINUS_SRC_ALPHA) newBlendFunc->setSourceAlpha(osg::BlendFunc::ONE); else if (blendFunc->getDestination() == osg::BlendFunc::ONE) newBlendFunc->setSourceAlpha(osg::BlendFunc::ZERO); - // Other setups barely exist in the wild and aren't worth supporting as they're not equippable gear + // Other setups barely exist in the wild and aren't worth supporting as they're not equippable + // gear else - Log(Debug::Info) << "Unable to adjust blend mode for character preview. Source factor 0x" << std::hex << blendFunc->getSource() << ", destination factor 0x" << blendFunc->getDestination() << std::dec; + Log(Debug::Info) << "Unable to adjust blend mode for character preview. Source factor 0x" + << std::hex << blendFunc->getSource() << ", destination factor 0x" + << blendFunc->getDestination() << std::dec; } } if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON) @@ -129,17 +138,79 @@ namespace MWRender } // Disable noBlendAlphaEnv newStateSet->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); - newStateSet->addUniform(mNoAlphaUniform); + newStateSet->setDefine("FORCE_OPAQUE", "0", osg::StateAttribute::ON); } } traverse(node); } - private: - osg::ref_ptr mNoAlphaUniform; + }; + + class CharacterPreviewRTTNode : public SceneUtil::RTTNode + { + static constexpr float fovYDegrees = 12.3f; + static constexpr float znear = 4.0f; + static constexpr float zfar = 10000.f; + + public: + CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) + : RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, + StereoAwareness::Unaware_MultiViewShaders) + , mAspectRatio(static_cast(sizeX) / static_cast(sizeY)) + { + if (SceneUtil::AutoDepth::isReversed()) + mPerspectiveMatrix = static_cast( + SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar)); + else + mPerspectiveMatrix = osg::Matrixf::perspective(fovYDegrees, mAspectRatio, znear, zfar); + mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix)); + mViewMatrix = osg::Matrixf::identity(); + setColorBufferInternalFormat(GL_RGBA); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); + } + + void setDefaults(osg::Camera* camera) override + { + camera->setName("CharacterPreview"); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + camera->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar); + camera->setViewport(0, 0, width(), height()); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setCullMask(~(Mask_UpdateVisitor)); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + SceneUtil::setCameraClearDepth(camera); + + camera->setNodeMask(Mask_RenderToTexture); + camera->addChild(mGroup); + } + + void apply(osg::Camera* camera) override + { + if (mCameraStateset) + camera->setStateSet(mCameraStateset); + camera->setViewMatrix(mViewMatrix); + + if (shouldDoTextureArray()) + Stereo::setMultiviewMatrices(mGroup->getOrCreateStateSet(), { mPerspectiveMatrix, mPerspectiveMatrix }); + } + + void addChild(osg::Node* node) { mGroup->addChild(node); } + + void setCameraStateset(osg::StateSet* stateset) { mCameraStateset = stateset; } + + void setViewMatrix(const osg::Matrixf& viewMatrix) { mViewMatrix = viewMatrix; } + + osg::ref_ptr mGroup = new osg::Group; + osg::Matrixf mPerspectiveMatrix; + osg::Matrixf mViewMatrix; + osg::ref_ptr mCameraStateset; + float mAspectRatio; }; CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, - const MWWorld::Ptr& character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt) + const MWWorld::Ptr& character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt) : mParent(parent) , mResourceSystem(resourceSystem) , mPosition(position) @@ -149,42 +220,25 @@ namespace MWRender , mSizeX(sizeX) , mSizeY(sizeY) { - mTexture = new osg::Texture2D; - mTexture->setTextureSize(sizeX, sizeY); - mTexture->setInternalFormat(GL_RGBA); - mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mTexture->setUserValue("premultiplied alpha", true); + mTextureStateSet = new osg::StateSet; + mTextureStateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA)); - mCamera = new osg::Camera; - // hints that the camera is not relative to the master camera - mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - mCamera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); - mCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - const float fovYDegrees = 12.3f; - mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed - mCamera->setViewport(0, 0, sizeX, sizeY); - mCamera->setRenderOrder(osg::Camera::PRE_RENDER); - mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); - mCamera->setName("CharacterPreview"); - mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); - mCamera->setCullMask(~(Mask_UpdateVisitor)); - - mCamera->setNodeMask(Mask_RenderToTexture); + mRTTNode = new CharacterPreviewRTTNode(sizeX, sizeY); + mRTTNode->setNodeMask(Mask_RenderToTexture); bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP; osg::ref_ptr lightManager = new SceneUtil::LightManager(ffp); lightManager->setStartLight(1); osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); + stateset->setDefine("FORCE_OPAQUE", "1", osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - osg::ref_ptr defaultMat (new osg::Material); + osg::ref_ptr defaultMat(new osg::Material); defaultMat->setColorMode(osg::Material::OFF); - defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); + defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); @@ -192,10 +246,19 @@ namespace MWRender // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) - osg::ref_ptr fog (new osg::Fog); + osg::ref_ptr fog(new osg::Fog); fog->setStart(10000000); fog->setEnd(10000000); - stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + + // TODO: Clean up this mess of loose uniforms that shaders depend on. + // turn off sky blending + stateset->addUniform(new osg::Uniform("far", 10000000.0f)); + stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); + stateset->addUniform(new osg::Uniform("sky", 0)); + stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 })); + + stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); // Opaque stuff must have 1 as its fragment alpha as the FBO is translucent, so having blending off isn't enough osg::ref_ptr noBlendAlphaEnv = new osg::TexEnvCombine(); @@ -205,6 +268,8 @@ namespace MWRender noBlendAlphaEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); noBlendAlphaEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); osg::ref_ptr dummyTexture = new osg::Texture2D(); + dummyTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + dummyTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); dummyTexture->setInternalFormat(GL_DEPTH_COMPONENT); dummyTexture->setTextureSize(1, 1); // This might clash with a shadow map, so make sure it doesn't cast shadows @@ -212,7 +277,6 @@ namespace MWRender dummyTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("noAlpha", true)); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0)); @@ -230,19 +294,19 @@ namespace MWRender float positionX = -std::cos(azimuth) * std::sin(altitude); float positionY = std::sin(azimuth) * std::sin(altitude); float positionZ = std::cos(altitude); - light->setPosition(osg::Vec4(positionX,positionY,positionZ, 0.0)); - light->setDiffuse(osg::Vec4(diffuseR,diffuseG,diffuseB,1)); - osg::Vec4 ambientRGBA = osg::Vec4(ambientR,ambientG,ambientB,1); + light->setPosition(osg::Vec4(positionX, positionY, positionZ, 0.0)); + light->setDiffuse(osg::Vec4(diffuseR, diffuseG, diffuseB, 1)); + osg::Vec4 ambientRGBA = osg::Vec4(ambientR, ambientG, ambientB, 1); if (mResourceSystem->getSceneManager()->getForceShaders()) { // When using shaders, we now skip the ambient sun calculation as this is the only place it's used. // Using the scene ambient will give identical results. lightmodel->setAmbientIntensity(ambientRGBA); - light->setAmbient(osg::Vec4(0,0,0,1)); + light->setAmbient(osg::Vec4(0, 0, 0, 1)); } else light->setAmbient(ambientRGBA); - light->setSpecular(osg::Vec4(0,0,0,0)); + light->setSpecular(osg::Vec4(0, 0, 0, 0)); light->setLightNum(0); light->setConstantAttenuation(1.f); light->setLinearAttenuation(0.f); @@ -256,23 +320,22 @@ namespace MWRender lightManager->addChild(lightSource); - mCamera->addChild(lightManager); + mRTTNode->addChild(lightManager); mNode = new osg::PositionAttitudeTransform; lightManager->addChild(mNode); - mDrawOnceCallback = new DrawOnceCallback; - mCamera->addUpdateCallback(mDrawOnceCallback); + mDrawOnceCallback = new DrawOnceCallback(mRTTNode->mGroup); + mRTTNode->addUpdateCallback(mDrawOnceCallback); - mParent->addChild(mCamera); + mParent->addChild(mRTTNode); mCharacter.mCell = nullptr; } - CharacterPreview::~CharacterPreview () + CharacterPreview::~CharacterPreview() { - mCamera->removeChildren(0, mCamera->getNumChildren()); - mParent->removeChild(mCamera); + mParent->removeChild(mRTTNode); } int CharacterPreview::getTextureWidth() const @@ -287,7 +350,6 @@ namespace MWRender void CharacterPreview::setBlendMode() { - mResourceSystem->getSceneManager()->recreateShaders(mNode, "objects", true); SetUpBlendVisitor visitor; mNode->accept(visitor); } @@ -299,7 +361,7 @@ namespace MWRender osg::ref_ptr CharacterPreview::getTexture() { - return mTexture; + return static_cast(mRTTNode->getColorTexture(nullptr)); } void CharacterPreview::rebuild() @@ -307,7 +369,7 @@ namespace MWRender mAnimation = nullptr; mAnimation = new NpcAnimation(mCharacter, mNode, mResourceSystem, true, - (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); onSetup(); @@ -316,15 +378,15 @@ namespace MWRender void CharacterPreview::redraw() { - mCamera->setNodeMask(Mask_RenderToTexture); + mRTTNode->setNodeMask(Mask_RenderToTexture); mDrawOnceCallback->redrawNextFrame(); } // -------------------------------------------------------------------------------------------------- - - InventoryPreview::InventoryPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character) - : CharacterPreview(parent, resourceSystem, character, 512, 1024, osg::Vec3f(0, 700, 71), osg::Vec3f(0,0,71)) + InventoryPreview::InventoryPreview( + osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character) + : CharacterPreview(parent, resourceSystem, character, 512, 1024, osg::Vec3f(0, 700, 71), osg::Vec3f(0, 0, 71)) { } @@ -335,9 +397,9 @@ namespace MWRender // NB Camera::setViewport has threading issues osg::ref_ptr stateset = new osg::StateSet; - mViewport = new osg::Viewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); + mViewport = new osg::Viewport(0, mSizeY - sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); stateset->setAttributeAndModes(mViewport); - mCamera->setStateSet(stateset); + mRTTNode->setCameraStateset(stateset); redraw(); } @@ -350,16 +412,16 @@ namespace MWRender mAnimation->showWeapons(true); mAnimation->updateParts(); - MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); + MWWorld::InventoryStore& inv = mCharacter.getClass().getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); std::string groupname = "inventoryhandtohand"; bool showCarriedLeft = true; - if(iter != inv.end()) + if (iter != inv.end()) { groupname = "inventoryweapononehand"; - if(iter->getTypeName() == typeid(ESM::Weapon).name()) + if (iter->getType() == ESM::Weapon::sRecordId) { - MWWorld::LiveCellRef *ref = iter->get(); + MWWorld::LiveCellRef* ref = iter->get(); int type = ref->mBase->mData.mType; const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(type); showCarriedLeft = !(weaponInfo->mFlags & ESM::WeaponType::TwoHanded); @@ -372,16 +434,19 @@ namespace MWRender groupname = inventoryGroup; else { - static const std::string oneHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; - static const std::string twoHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; + static const std::string oneHandFallback + = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; + static const std::string twoHandFallback + = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones - if (weaponInfo->mFlags & ESM::WeaponType::TwoHanded && weaponInfo->mWeaponClass == ESM::WeaponType::Melee) + if (weaponInfo->mFlags & ESM::WeaponType::TwoHanded + && weaponInfo->mWeaponClass == ESM::WeaponType::Melee) groupname = twoHandFallback; else groupname = oneHandFallback; } - } + } } mAnimation->showCarriedLeft(showCarriedLeft); @@ -390,13 +455,13 @@ namespace MWRender mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && showCarriedLeft) + if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft) { - if(!mAnimation->getInfo("torch")) - mAnimation->play("torch", 2, Animation::BlendMask_LeftArm, false, - 1.0f, "start", "stop", 0.0f, ~0ul, true); + if (!mAnimation->getInfo("torch")) + mAnimation->play( + "torch", 2, Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); } - else if(mAnimation->getInfo("torch")) + else if (mAnimation->getInfo("torch")) mAnimation->disable("torch"); mAnimation->runAnimation(0.0f); @@ -406,7 +471,7 @@ namespace MWRender redraw(); } - int InventoryPreview::getSlotSelected (int posX, int posY) + int InventoryPreview::getSlotSelected(int posX, int posY) { if (!mViewport) return -1; @@ -416,18 +481,21 @@ namespace MWRender // precision issue - compiling with OSG_USE_FLOAT_MATRIX=0, Intersector::WINDOW works ok. // Using Intersector::PROJECTION results in better precision because the start/end points and the model matrices // don't go through as many transformations. - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::PROJECTION, projX, projY)); + osg::ref_ptr intersector( + new osgUtil::LineSegmentIntersector(osgUtil::Intersector::PROJECTION, projX, projY)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); osgUtil::IntersectionVisitor visitor(intersector); visitor.setTraversalMode(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN); - // Set the traversal number from the last draw, so that the frame switch used for RigGeometry double buffering works correctly + // Set the traversal number from the last draw, so that the frame switch used for RigGeometry double buffering + // works correctly visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame()); - osg::Node::NodeMask nodeMask = mCamera->getNodeMask(); - mCamera->setNodeMask(~0u); - mCamera->accept(visitor); - mCamera->setNodeMask(nodeMask); + auto* camera = mRTTNode->getCamera(nullptr); + osg::Node::NodeMask nodeMask = camera->getNodeMask(); + camera->setNodeMask(~0u); + camera->accept(visitor); + camera->setNodeMask(nodeMask); if (intersector->containsIntersections()) { @@ -437,7 +505,7 @@ namespace MWRender return -1; } - void InventoryPreview::updatePtr(const MWWorld::Ptr &ptr) + void InventoryPreview::updatePtr(const MWWorld::Ptr& ptr) { mCharacter = MWWorld::Ptr(ptr.getBase(), nullptr); } @@ -445,58 +513,55 @@ namespace MWRender void InventoryPreview::onSetup() { CharacterPreview::onSetup(); - osg::Vec3f scale (1.f, 1.f, 1.f); + osg::Vec3f scale(1.f, 1.f, 1.f); mCharacter.getClass().adjustScale(mCharacter, scale, true); mNode->setScale(scale); - mCamera->setViewMatrixAsLookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0,0,1)); + auto viewMatrix = osg::Matrixf::lookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0, 0, 1)); + mRTTNode->setViewMatrix(viewMatrix); } // -------------------------------------------------------------------------------------------------- RaceSelectionPreview::RaceSelectionPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem) - : CharacterPreview(parent, resourceSystem, MWMechanics::getPlayer(), - 512, 512, osg::Vec3f(0, 125, 8), osg::Vec3f(0,0,8)) - , mBase (*mCharacter.get()->mBase) + : CharacterPreview( + parent, resourceSystem, MWMechanics::getPlayer(), 512, 512, osg::Vec3f(0, 125, 8), osg::Vec3f(0, 0, 8)) + , mBase(*mCharacter.get()->mBase) , mRef(&mBase) , mPitchRadians(osg::DegreesToRadians(6.f)) { mCharacter = MWWorld::Ptr(&mRef, nullptr); } - RaceSelectionPreview::~RaceSelectionPreview() - { - } + RaceSelectionPreview::~RaceSelectionPreview() {} void RaceSelectionPreview::setAngle(float angleRadians) { - mNode->setAttitude(osg::Quat(mPitchRadians, osg::Vec3(1,0,0)) - * osg::Quat(angleRadians, osg::Vec3(0,0,1))); + mNode->setAttitude(osg::Quat(mPitchRadians, osg::Vec3(1, 0, 0)) * osg::Quat(angleRadians, osg::Vec3(0, 0, 1))); redraw(); } - void RaceSelectionPreview::setPrototype(const ESM::NPC &proto) + void RaceSelectionPreview::setPrototype(const ESM::NPC& proto) { mBase = proto; - mBase.mId = "player"; + mBase.mId = ESM::RefId::stringRefId("Player"); rebuild(); } - class UpdateCameraCallback : public osg::NodeCallback + class UpdateCameraCallback : public SceneUtil::NodeCallback { public: - UpdateCameraCallback(osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) + UpdateCameraCallback( + osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) : mNodeToFollow(nodeToFollow) , mPosOffset(posOffset) , mLookAtOffset(lookAtOffset) { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(CharacterPreviewRTTNode* node, osg::NodeVisitor* nv) { - osg::Camera* cam = static_cast(node); - // Update keyframe controllers in the scene graph first... traverse(node, nv); @@ -507,7 +572,9 @@ namespace MWRender osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3 headOffset = worldMat.getTrans(); - cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1)); + auto viewMatrix + = osg::Matrixf::lookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0, 0, 1)); + node->setViewMatrix(viewMatrix); } private: @@ -516,7 +583,7 @@ namespace MWRender osg::Vec3 mLookAtOffset; }; - void RaceSelectionPreview::onSetup () + void RaceSelectionPreview::onSetup() { CharacterPreview::onSetup(); mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); @@ -524,13 +591,13 @@ namespace MWRender // attach camera to follow the head node if (mUpdateCameraCallback) - mCamera->removeUpdateCallback(mUpdateCameraCallback); + mRTTNode->removeUpdateCallback(mUpdateCameraCallback); const osg::Node* head = mAnimation->getNode("Bip01 Head"); if (head) { mUpdateCameraCallback = new UpdateCameraCallback(head, mPosition, mLookAt); - mCamera->addUpdateCallback(mUpdateCameraCallback); + mRTTNode->addUpdateCallback(mUpdateCameraCallback); } else Log(Debug::Error) << "Error: Bip01 Head node not found"; diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 3eb968846..e2ab53bef 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -1,12 +1,12 @@ #ifndef MWRENDER_CHARACTERPREVIEW_H #define MWRENDER_CHARACTERPREVIEW_H -#include #include +#include #include -#include +#include #include @@ -18,6 +18,7 @@ namespace osg class Camera; class Group; class Viewport; + class StateSet; } namespace MWRender @@ -25,12 +26,13 @@ namespace MWRender class NpcAnimation; class DrawOnceCallback; + class CharacterPreviewRTTNode; class CharacterPreview { public: - CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character, int sizeX, int sizeY, - const osg::Vec3f& position, const osg::Vec3f& lookAt); + CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character, + int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt); virtual ~CharacterPreview(); int getTextureWidth() const; @@ -41,6 +43,8 @@ namespace MWRender void rebuild(); osg::ref_ptr getTexture(); + /// Get the osg::StateSet required to render the texture correctly, if any. + osg::StateSet* getTextureStateSet() { return mTextureStateSet; } private: CharacterPreview(const CharacterPreview&); @@ -53,9 +57,9 @@ namespace MWRender osg::ref_ptr mParent; Resource::ResourceSystem* mResourceSystem; - osg::ref_ptr mTexture; - osg::ref_ptr mCamera; + osg::ref_ptr mTextureStateSet; osg::ref_ptr mDrawOnceCallback; + osg::ref_ptr mRTTNode; osg::Vec3f mPosition; osg::Vec3f mLookAt; @@ -73,7 +77,6 @@ namespace MWRender class InventoryPreview : public CharacterPreview { public: - InventoryPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character); void updatePtr(const MWWorld::Ptr& ptr); @@ -93,11 +96,10 @@ namespace MWRender class RaceSelectionPreview : public CharacterPreview { - ESM::NPC mBase; - MWWorld::LiveCellRef mRef; + ESM::NPC mBase; + MWWorld::LiveCellRef mRef; protected: - bool renderHeadOnly() override { return true; } void onSetup() override; @@ -107,14 +109,11 @@ namespace MWRender void setAngle(float angleRadians); - const ESM::NPC &getPrototype() const { - return mBase; - } + const ESM::NPC& getPrototype() const { return mBase; } - void setPrototype(const ESM::NPC &proto); + void setPrototype(const ESM::NPC& proto); private: - osg::ref_ptr mUpdateCameraCallback; float mPitchRadians; diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 298711162..77faee723 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -2,290 +2,269 @@ #include -#include #include +#include #include -#include -#include -#include #include -#include +#include #include -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" namespace MWRender { -CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, - const std::string& model, Resource::ResourceSystem* resourceSystem) - : ActorAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) -{ - MWWorld::LiveCellRef *ref = mPtr.get(); - - if(!model.empty()) + CreatureAnimation::CreatureAnimation( + const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, bool animated) + : ActorAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) { - setObjectRoot(model, false, false, true); + MWWorld::LiveCellRef* ref = mPtr.get(); - if((ref->mBase->mFlags&ESM::Creature::Bipedal)) - addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); - addAnimSource(model, model); - } -} - - -CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem) - : ActorAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) - , mShowWeapons(false) - , mShowCarriedLeft(false) -{ - MWWorld::LiveCellRef *ref = mPtr.get(); - - if(!model.empty()) - { - setObjectRoot(model, true, false, true); - - if((ref->mBase->mFlags&ESM::Creature::Bipedal)) + if (!model.empty()) { - addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); + setObjectRoot(model, false, false, true); + + if ((ref->mBase->mFlags & ESM::Creature::Bipedal)) + addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); + + if (animated) + addAnimSource(model, model); } - addAnimSource(model, model); - - mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr); - - updateParts(); } - mWeaponAnimationTime = std::shared_ptr(new WeaponAnimationTime(this)); -} - -void CreatureWeaponAnimation::showWeapons(bool showWeapon) -{ - if (showWeapon != mShowWeapons) + CreatureWeaponAnimation::CreatureWeaponAnimation( + const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, bool animated) + : ActorAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) + , mShowWeapons(false) + , mShowCarriedLeft(false) { - mShowWeapons = showWeapon; - updateParts(); - } -} + MWWorld::LiveCellRef* ref = mPtr.get(); -void CreatureWeaponAnimation::showCarriedLeft(bool show) -{ - if (show != mShowCarriedLeft) - { - mShowCarriedLeft = show; - updateParts(); - } -} - -void CreatureWeaponAnimation::updateParts() -{ - mAmmunition.reset(); - mWeapon.reset(); - mShield.reset(); - - updateHolsteredWeapon(!mShowWeapons); - updateQuiver(); - updateHolsteredShield(mShowCarriedLeft); - - if (mShowWeapons) - updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight); - if (mShowCarriedLeft) - updatePart(mShield, MWWorld::InventoryStore::Slot_CarriedLeft); -} - -void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) -{ - if (!mObjectRoot) - return; - - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator it = inv.getSlot(slot); - - if (it == inv.end()) - { - scene.reset(); - return; - } - MWWorld::ConstPtr item = *it; - - std::string bonename; - std::string itemModel = item.getClass().getModel(item); - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - { - if(item.getTypeName() == typeid(ESM::Weapon).name()) + if (!model.empty()) { - int type = item.get()->mBase->mData.mType; - bonename = MWMechanics::getWeaponType(type)->mAttachBone; - if (bonename != "Weapon Bone") - { - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); - if (found == nodeMap.end()) - bonename = "Weapon Bone"; - } + setObjectRoot(model, true, false, true); + + if ((ref->mBase->mFlags & ESM::Creature::Bipedal)) + addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); + + if (animated) + addAnimSource(model, model); + + mPtr.getClass().getInventoryStore(mPtr).setInvListener(this); + + updateParts(); } - else - bonename = "Weapon Bone"; + + mWeaponAnimationTime = std::make_shared(this); } - else + + void CreatureWeaponAnimation::showWeapons(bool showWeapon) { - bonename = "Shield Bone"; - if (item.getTypeName() == typeid(ESM::Armor).name()) + if (showWeapon != mShowWeapons) { - // Shield body part model should be used if possible. - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - for (const auto& part : item.get()->mBase->mParts.mParts) + mShowWeapons = showWeapon; + updateParts(); + } + } + + void CreatureWeaponAnimation::showCarriedLeft(bool show) + { + if (show != mShowCarriedLeft) + { + mShowCarriedLeft = show; + updateParts(); + } + } + + void CreatureWeaponAnimation::updateParts() + { + mAmmunition.reset(); + mWeapon.reset(); + mShield.reset(); + + updateHolsteredWeapon(!mShowWeapons); + updateQuiver(); + updateHolsteredShield(mShowCarriedLeft); + + if (mShowWeapons) + updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight); + if (mShowCarriedLeft) + updatePart(mShield, MWWorld::InventoryStore::Slot_CarriedLeft); + } + + void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) + { + if (!mObjectRoot) + return; + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator it = inv.getSlot(slot); + + if (it == inv.end()) + { + scene.reset(); + return; + } + MWWorld::ConstPtr item = *it; + + std::string_view bonename; + std::string itemModel = item.getClass().getModel(item); + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + { + if (item.getType() == ESM::Weapon::sRecordId) { - // Assume all creatures use the male mesh. - if (part.mPart != ESM::PRT_Shield || part.mMale.empty()) - continue; - const ESM::BodyPart *bodypart = store.get().search(part.mMale); - if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty()) + int type = item.get()->mBase->mData.mType; + bonename = MWMechanics::getWeaponType(type)->mAttachBone; + if (bonename != "Weapon Bone") { - itemModel = "meshes\\" + bodypart->mModel; - break; + const NodeMap& nodeMap = getNodeMap(); + NodeMap::const_iterator found = nodeMap.find(bonename); + if (found == nodeMap.end()) + bonename = "Weapon Bone"; } } + else + bonename = "Weapon Bone"; } - } - - try - { - osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(itemModel); - - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); - if (found == nodeMap.end()) - throw std::runtime_error("Can't find attachment node " + bonename); - osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get()); - - scene.reset(new PartHolder(attached)); - - if (!item.getClass().getEnchantment(item).empty()) - mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, item.getClass().getEnchantmentColor(item)); - - // Crossbows start out with a bolt attached - // FIXME: code duplicated from NpcAnimation - if (slot == MWWorld::InventoryStore::Slot_CarriedRight && - item.getTypeName() == typeid(ESM::Weapon).name() && - item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + else { - const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow); - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo != inv.end() && ammo->get()->mBase->mData.mType == weaponInfo->mAmmoType) - attachArrow(); + bonename = "Shield Bone"; + if (item.getType() == ESM::Armor::sRecordId) + { + itemModel = getShieldMesh(item, false); + } + } + + try + { + osg::ref_ptr attached + = attach(itemModel, bonename, bonename, item.getType() == ESM::Light::sRecordId); + + scene = std::make_unique(attached); + + if (!item.getClass().getEnchantment(item).empty()) + mGlowUpdater + = SceneUtil::addEnchantedGlow(attached, mResourceSystem, item.getClass().getEnchantmentColor(item)); + + // Crossbows start out with a bolt attached + // FIXME: code duplicated from NpcAnimation + if (slot == MWWorld::InventoryStore::Slot_CarriedRight && item.getType() == ESM::Weapon::sRecordId + && item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow); + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == weaponInfo->mAmmoType) + attachArrow(); + else + mAmmunition.reset(); + } else mAmmunition.reset(); + + std::shared_ptr source; + + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + source = mWeaponAnimationTime; + else + source = std::make_shared(); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(source); + attached->accept(assignVisitor); + } + catch (std::exception& e) + { + Log(Debug::Error) << "Can not add creature part: " << e.what(); } - else - mAmmunition.reset(); - - std::shared_ptr source; - - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - source = mWeaponAnimationTime; - else - source.reset(new NullAnimationTime); - - SceneUtil::AssignControllerSourcesVisitor assignVisitor(source); - attached->accept(assignVisitor); } - catch (std::exception& e) + + bool CreatureWeaponAnimation::isArrowAttached() const { - Log(Debug::Error) << "Can not add creature part: " << e.what(); + return mAmmunition != nullptr; } -} -bool CreatureWeaponAnimation::isArrowAttached() const -{ - return mAmmunition != nullptr; -} - -void CreatureWeaponAnimation::detachArrow() -{ - WeaponAnimation::detachArrow(mPtr); - updateQuiver(); -} - -void CreatureWeaponAnimation::attachArrow() -{ - WeaponAnimation::attachArrow(mPtr); - - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty()) + void CreatureWeaponAnimation::detachArrow() { - osg::Group* bone = getArrowBone(); - if (bone != nullptr && bone->getNumChildren()) - SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); + WeaponAnimation::detachArrow(mPtr); + updateQuiver(); } - updateQuiver(); -} - -void CreatureWeaponAnimation::releaseArrow(float attackStrength) -{ - WeaponAnimation::releaseArrow(mPtr, attackStrength); - updateQuiver(); -} - -osg::Group *CreatureWeaponAnimation::getArrowBone() -{ - if (!mWeapon) - return nullptr; - - if (!mPtr.getClass().hasInventoryStore(mPtr)) - return nullptr; - - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) - return nullptr; - - int type = weapon->get()->mBase->mData.mType; - int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; - - // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh - osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone); - if (bone == nullptr) + void CreatureWeaponAnimation::attachArrow() { - SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); - mWeapon->getNode()->accept(findVisitor); - bone = findVisitor.mFoundNode; + WeaponAnimation::attachArrow(mPtr); + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty()) + { + osg::Group* bone = getArrowBone(); + if (bone != nullptr && bone->getNumChildren()) + SceneUtil::addEnchantedGlow( + bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); + } + + updateQuiver(); } - return bone; -} -osg::Node *CreatureWeaponAnimation::getWeaponNode() -{ - return mWeapon ? mWeapon->getNode().get() : nullptr; -} + void CreatureWeaponAnimation::releaseArrow(float attackStrength) + { + WeaponAnimation::releaseArrow(mPtr, attackStrength); + updateQuiver(); + } -Resource::ResourceSystem *CreatureWeaponAnimation::getResourceSystem() -{ - return mResourceSystem; -} + osg::Group* CreatureWeaponAnimation::getArrowBone() + { + if (!mWeapon) + return nullptr; -void CreatureWeaponAnimation::addControllers() -{ - Animation::addControllers(); - WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); -} + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return nullptr; -osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) -{ - osg::Vec3f ret = Animation::runAnimation(duration); + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) + return nullptr; - WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); + int type = weapon->get()->mBase->mData.mType; + int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; + if (ammoType == ESM::Weapon::None) + return nullptr; - return ret; -} + // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh + osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone); + if (bone == nullptr) + { + SceneUtil::FindByNameVisitor findVisitor("ArrowBone"); + mWeapon->getNode()->accept(findVisitor); + bone = findVisitor.mFoundNode; + } + return bone; + } + + osg::Node* CreatureWeaponAnimation::getWeaponNode() + { + return mWeapon ? mWeapon->getNode().get() : nullptr; + } + + Resource::ResourceSystem* CreatureWeaponAnimation::getResourceSystem() + { + return mResourceSystem; + } + + void CreatureWeaponAnimation::addControllers() + { + Animation::addControllers(); + WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); + } + + osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) + { + osg::Vec3f ret = Animation::runAnimation(duration); + + WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); + + return ret; + } } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 9169e4102..05235e519 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -1,9 +1,9 @@ #ifndef GAME_RENDER_CREATUREANIMATION_H #define GAME_RENDER_CREATUREANIMATION_H +#include "../mwworld/inventorystore.hpp" #include "actoranimation.hpp" #include "weaponanimation.hpp" -#include "../mwworld/inventorystore.hpp" namespace MWWorld { @@ -15,21 +15,26 @@ namespace MWRender class CreatureAnimation : public ActorAnimation { public: - CreatureAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem); + CreatureAnimation( + const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, bool animated); virtual ~CreatureAnimation() {} }; // For creatures with weapons and shields // Animation is already virtual anyway, so might as well make a separate class. // Most creatures don't need weapons/shields, so this will save some memory. - class CreatureWeaponAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener + class CreatureWeaponAnimation : public ActorAnimation, + public WeaponAnimation, + public MWWorld::InventoryStoreListener { public: - CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem); + CreatureWeaponAnimation( + const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, bool animated); virtual ~CreatureWeaponAnimation() {} void equipmentChanged() override { updateParts(); } + bool getWeaponsShown() const override { return mShowWeapons; } void showWeapons(bool showWeapon) override; bool getCarriedLeftShown() const override { return mShowCarriedLeft; } @@ -47,7 +52,10 @@ namespace MWRender osg::Node* getWeaponNode() override; Resource::ResourceSystem* getResourceSystem() override; void showWeapon(bool show) override { showWeapons(show); } - void setWeaponGroup(const std::string& group, bool relativeDuration) override { mWeaponAnimationTime->setGroup(group, relativeDuration); } + void setWeaponGroup(const std::string& group, bool relativeDuration) override + { + mWeaponAnimationTime->setGroup(group, relativeDuration); + } void addControllers() override; diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 3e785a769..3ae0c34c7 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -8,77 +8,79 @@ #include #include "animation.hpp" -#include "vismask.hpp" #include "util.hpp" +#include "vismask.hpp" + +#include namespace MWRender { -EffectManager::EffectManager(osg::ref_ptr parent, Resource::ResourceSystem* resourceSystem) - : mParentNode(parent) - , mResourceSystem(resourceSystem) -{ -} - -EffectManager::~EffectManager() -{ - clear(); -} - -void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) -{ - osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); - - node->setNodeMask(Mask_Effect); - - Effect effect; - effect.mAnimTime.reset(new EffectAnimationTime); - - SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; - node->accept(findMaxLengthVisitor); - effect.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); - - osg::ref_ptr trans = new osg::PositionAttitudeTransform; - trans->setPosition(worldPosition); - trans->setScale(osg::Vec3f(scale, scale, scale)); - trans->addChild(node); - - SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); - node->accept(assignVisitor); - - if (isMagicVFX) - overrideFirstRootTexture(textureOverride, mResourceSystem, node); - else - overrideTexture(textureOverride, mResourceSystem, node); - - mParentNode->addChild(trans); - - mEffects[trans] = effect; -} - -void EffectManager::update(float dt) -{ - for (EffectMap::iterator it = mEffects.begin(); it != mEffects.end(); ) + EffectManager::EffectManager(osg::ref_ptr parent, Resource::ResourceSystem* resourceSystem) + : mParentNode(parent) + , mResourceSystem(resourceSystem) { - it->second.mAnimTime->addTime(dt); + } - if (it->second.mAnimTime->getTime() >= it->second.mMaxControllerLength) - { - mParentNode->removeChild(it->first); - mEffects.erase(it++); - } + EffectManager::~EffectManager() + { + clear(); + } + + void EffectManager::addEffect(const std::string& model, std::string_view textureOverride, + const osg::Vec3f& worldPosition, float scale, bool isMagicVFX) + { + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); + + node->setNodeMask(Mask_Effect); + + Effect effect; + effect.mAnimTime = std::make_shared(); + + SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; + node->accept(findMaxLengthVisitor); + effect.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); + + osg::ref_ptr trans = new osg::PositionAttitudeTransform; + trans->setPosition(worldPosition); + trans->setScale(osg::Vec3f(scale, scale, scale)); + trans->addChild(node); + + effect.mTransform = trans; + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); + node->accept(assignVisitor); + + if (isMagicVFX) + overrideFirstRootTexture(textureOverride, mResourceSystem, node); else - ++it; - } -} + overrideTexture(textureOverride, mResourceSystem, node); -void EffectManager::clear() -{ - for (EffectMap::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + mParentNode->addChild(trans); + + mEffects.push_back(std::move(effect)); + } + + void EffectManager::update(float dt) { - mParentNode->removeChild(it->first); + mEffects.erase(std::remove_if(mEffects.begin(), mEffects.end(), + [dt, this](Effect& effect) { + effect.mAnimTime->addTime(dt); + const auto remove = effect.mAnimTime->getTime() >= effect.mMaxControllerLength; + if (remove) + mParentNode->removeChild(effect.mTransform); + return remove; + }), + mEffects.end()); + } + + void EffectManager::clear() + { + for (const auto& effect : mEffects) + { + mParentNode->removeChild(effect.mTransform); + } + mEffects.clear(); } - mEffects.clear(); -} } diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp index 5873c00dd..2477344fd 100644 --- a/apps/openmw/mwrender/effectmanager.hpp +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_MWRENDER_EFFECTMANAGER_H #define OPENMW_MWRENDER_EFFECTMANAGER_H -#include #include #include +#include #include @@ -29,10 +29,12 @@ namespace MWRender { public: EffectManager(osg::ref_ptr parent, Resource::ResourceSystem* resourceSystem); + EffectManager(const EffectManager&) = delete; ~EffectManager(); /// Add an effect. When it's finished playing, it will be removed automatically. - void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true); + void addEffect(const std::string& model, std::string_view textureOverride, const osg::Vec3f& worldPosition, + float scale, bool isMagicVFX = true); void update(float dt); @@ -44,16 +46,13 @@ namespace MWRender { float mMaxControllerLength; std::shared_ptr mAnimTime; + osg::ref_ptr mTransform; }; - typedef std::map, Effect> EffectMap; - EffectMap mEffects; + std::vector mEffects; osg::ref_ptr mParentNode; Resource::ResourceSystem* mResourceSystem; - - EffectManager(const EffectManager&); - void operator=(const EffectManager&); }; } diff --git a/apps/openmw/mwrender/fogmanager.cpp b/apps/openmw/mwrender/fogmanager.cpp index b15188292..ef8de1cb2 100644 --- a/apps/openmw/mwrender/fogmanager.cpp +++ b/apps/openmw/mwrender/fogmanager.cpp @@ -2,20 +2,14 @@ #include -#include +#include +#include +#include #include #include -#include +#include -namespace -{ - float DLLandFogStart; - float DLLandFogEnd; - float DLUnderwaterFogStart; - float DLUnderwaterFogEnd; - float DLInteriorFogStart; - float DLInteriorFogEnd; -} +#include namespace MWRender { @@ -25,44 +19,41 @@ namespace MWRender , mUnderwaterFogStart(0.f) , mUnderwaterFogEnd(std::numeric_limits::max()) , mFogColor(osg::Vec4f()) - , mDistantFog(Settings::Manager::getBool("use distant fog", "Fog")) , mUnderwaterColor(Fallback::Map::getColour("Water_UnderwaterColor")) , mUnderwaterWeight(Fallback::Map::getFloat("Water_UnderwaterColorWeight")) , mUnderwaterIndoorFog(Fallback::Map::getFloat("Water_UnderwaterIndoorFog")) { - DLLandFogStart = Settings::Manager::getFloat("distant land fog start", "Fog"); - DLLandFogEnd = Settings::Manager::getFloat("distant land fog end", "Fog"); - DLUnderwaterFogStart = Settings::Manager::getFloat("distant underwater fog start", "Fog"); - DLUnderwaterFogEnd = Settings::Manager::getFloat("distant underwater fog end", "Fog"); - DLInteriorFogStart = Settings::Manager::getFloat("distant interior fog start", "Fog"); - DLInteriorFogEnd = Settings::Manager::getFloat("distant interior fog end", "Fog"); } - void FogManager::configure(float viewDistance, const ESM::Cell *cell) + void FogManager::configure(float viewDistance, const MWWorld::Cell& cell) { - osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog); + osg::Vec4f color = SceneUtil::colourFromRGB(cell.getMood().mFogColor); - if (mDistantFog) + const float fogDensity = cell.getMood().mFogDensity; + if (Settings::fog().mUseDistantFog) { - float density = std::max(0.2f, cell->mAmbi.mFogDensity); - mLandFogStart = DLInteriorFogEnd * (1.0f - density) + DLInteriorFogStart*density; - mLandFogEnd = DLInteriorFogEnd; - mUnderwaterFogStart = DLUnderwaterFogStart; - mUnderwaterFogEnd = DLUnderwaterFogEnd; + float density = std::max(0.2f, fogDensity); + mLandFogStart = Settings::fog().mDistantInteriorFogEnd * (1.0f - density) + + Settings::fog().mDistantInteriorFogStart * density; + mLandFogEnd = Settings::fog().mDistantInteriorFogEnd; + mUnderwaterFogStart = Settings::fog().mDistantUnderwaterFogStart; + mUnderwaterFogEnd = Settings::fog().mDistantUnderwaterFogEnd; mFogColor = color; } else - configure(viewDistance, cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color); + configure(viewDistance, fogDensity, mUnderwaterIndoorFog, 1.0f, 0.0f, color); } - void FogManager::configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color) + void FogManager::configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, + const osg::Vec4f& color) { - if (mDistantFog) + if (Settings::fog().mUseDistantFog) { - mLandFogStart = dlFactor * (DLLandFogStart - dlOffset * DLLandFogEnd); - mLandFogEnd = dlFactor * (1.0f - dlOffset) * DLLandFogEnd; - mUnderwaterFogStart = DLUnderwaterFogStart; - mUnderwaterFogEnd = DLUnderwaterFogEnd; + mLandFogStart + = dlFactor * (Settings::fog().mDistantLandFogStart - dlOffset * Settings::fog().mDistantLandFogEnd); + mLandFogEnd = dlFactor * (1.0f - dlOffset) * Settings::fog().mDistantLandFogEnd; + mUnderwaterFogStart = Settings::fog().mDistantUnderwaterFogStart; + mUnderwaterFogEnd = Settings::fog().mDistantUnderwaterFogEnd; } else { @@ -96,7 +87,7 @@ namespace MWRender { if (isUnderwater) { - return mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight); + return mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f - mUnderwaterWeight); } return mFogColor; diff --git a/apps/openmw/mwrender/fogmanager.hpp b/apps/openmw/mwrender/fogmanager.hpp index c3efd06ab..a704f0efd 100644 --- a/apps/openmw/mwrender/fogmanager.hpp +++ b/apps/openmw/mwrender/fogmanager.hpp @@ -3,9 +3,9 @@ #include -namespace ESM +namespace MWWorld { - struct Cell; + class Cell; } namespace MWRender @@ -15,8 +15,9 @@ namespace MWRender public: FogManager(); - void configure(float viewDistance, const ESM::Cell *cell); - void configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color); + void configure(float viewDistance, const MWWorld::Cell& cell); + void configure(float viewDistance, float fogDepth, float underwaterFog, float dlFactor, float dlOffset, + const osg::Vec4f& color); osg::Vec4f getFogColor(bool isUnderwater) const; float getFogStart(bool isUnderwater) const; @@ -28,8 +29,6 @@ namespace MWRender float mUnderwaterFogStart; float mUnderwaterFogEnd; osg::Vec4f mFogColor; - bool mDistantFog; - osg::Vec4f mUnderwaterColor; float mUnderwaterWeight; float mUnderwaterIndoorFog; diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 46033f530..563d7fb86 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -1,22 +1,23 @@ #include "globalmap.hpp" -#include -#include -#include #include -#include +#include +#include #include +#include #include -#include #include +#include #include +#include +#include #include -#include +#include /* Start of tes3mp addition @@ -43,7 +44,8 @@ namespace // Create a screen-aligned quad with given texture coordinates. // Assumes a top-left origin of the sampled image. - osg::ref_ptr createTexturedQuad(float leftTexCoord, float topTexCoord, float rightTexCoord, float bottomTexCoord) + osg::ref_ptr createTexturedQuad( + float leftTexCoord, float topTexCoord, float rightTexCoord, float bottomTexCoord) { osg::ref_ptr geom = new osg::Geometry; @@ -56,10 +58,10 @@ namespace geom->setVertexArray(verts); osg::ref_ptr texcoords = new osg::Vec2Array; - texcoords->push_back(osg::Vec2f(leftTexCoord, 1.f-bottomTexCoord)); - texcoords->push_back(osg::Vec2f(leftTexCoord, 1.f-topTexCoord)); - texcoords->push_back(osg::Vec2f(rightTexCoord, 1.f-topTexCoord)); - texcoords->push_back(osg::Vec2f(rightTexCoord, 1.f-bottomTexCoord)); + texcoords->push_back(osg::Vec2f(leftTexCoord, 1.f - bottomTexCoord)); + texcoords->push_back(osg::Vec2f(leftTexCoord, 1.f - topTexCoord)); + texcoords->push_back(osg::Vec2f(rightTexCoord, 1.f - topTexCoord)); + texcoords->push_back(osg::Vec2f(rightTexCoord, 1.f - bottomTexCoord)); osg::ref_ptr colors = new osg::Vec4Array; colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); @@ -67,13 +69,12 @@ namespace geom->setTexCoordArray(0, texcoords, osg::Array::BIND_PER_VERTEX); - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); + geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4)); return geom; } - - class CameraUpdateGlobalCallback : public osg::NodeCallback + class CameraUpdateGlobalCallback : public SceneUtil::NodeCallback { public: CameraUpdateGlobalCallback(MWRender::GlobalMap* parent) @@ -82,14 +83,14 @@ namespace { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Camera* node, osg::NodeVisitor* nv) { if (mRendered) { - if (mParent->copyResult(static_cast(node), nv->getTraversalNumber())) + if (mParent->copyResult(node, nv->getTraversalNumber())) { node->setNodeMask(0); - mParent->markForRemoval(static_cast(node)); + mParent->markForRemoval(node); } return; } @@ -104,6 +105,27 @@ namespace MWRender::GlobalMap* mParent; }; + std::vector writePng(const osg::Image& overlayImage) + { + std::ostringstream ostream; + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!readerwriter) + { + Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found"; + return std::vector(); + } + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(overlayImage, ostream); + if (!result.success()) + { + Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " + << result.status(); + return std::vector(); + } + + std::string data = ostream.str(); + return std::vector(data.begin(), data.end()); + } } namespace MWRender @@ -123,8 +145,16 @@ namespace MWRender class CreateMapWorkItem : public SceneUtil::WorkItem { public: - CreateMapWorkItem(int width, int height, int minX, int minY, int maxX, int maxY, int cellSize, const MWWorld::Store& landStore) - : mWidth(width), mHeight(height), mMinX(minX), mMinY(minY), mMaxX(maxX), mMaxY(maxY), mCellSize(cellSize), mLandStore(landStore) + CreateMapWorkItem(int width, int height, int minX, int minY, int maxX, int maxY, int cellSize, + const MWWorld::Store& landStore) + : mWidth(width) + , mHeight(height) + , mMinX(minX) + , mMinY(minY) + , mMaxX(maxX) + , mMaxY(maxY) + , mCellSize(cellSize) + , mLandStore(landStore) { } @@ -142,19 +172,19 @@ namespace MWRender { for (int y = mMinY; y <= mMaxY; ++y) { - const ESM::Land* land = mLandStore.search (x,y); + const ESM::Land* land = mLandStore.search(x, y); - for (int cellY=0; cellY(float(cellX) / float(mCellSize) * 9); int vertexY = static_cast(float(cellY) / float(mCellSize) * 9); - int texelX = (x-mMinX) * mCellSize + cellX; - int texelY = (y-mMinY) * mCellSize + cellY; + int texelX = (x - mMinX) * mCellSize + cellX; + int texelY = (y - mMinY) * mCellSize + cellY; - unsigned char r,g,b; + unsigned char r, g, b; float y2 = 0; if (land && (land->mDataTypes & ESM::Land::DATA_WNAM)) @@ -190,10 +220,11 @@ namespace MWRender } data[texelY * mWidth * 3 + texelX * 3] = r; - data[texelY * mWidth * 3 + texelX * 3+1] = g; - data[texelY * mWidth * 3 + texelX * 3+2] = b; + data[texelY * mWidth * 3 + texelX * 3 + 1] = g; + data[texelY * mWidth * 3 + texelX * 3 + 2] = b; - alphaData[texelY * mWidth+ texelX] = (y2 < 0) ? static_cast(0) : static_cast(255); + alphaData[texelY * mWidth + texelX] + = (y2 < 0) ? static_cast(0) : static_cast(255); } } } @@ -243,13 +274,28 @@ namespace MWRender osg::ref_ptr mOverlayTexture; }; + struct GlobalMap::WritePng final : public SceneUtil::WorkItem + { + osg::ref_ptr mOverlayImage; + std::vector mImageData; + + explicit WritePng(osg::ref_ptr overlayImage) + : mOverlayImage(std::move(overlayImage)) + { + } + + void doWork() override { mImageData = writePng(*mOverlayImage); } + }; + GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue) : mRoot(root) , mWorkQueue(workQueue) , mWidth(0) , mHeight(0) - , mMinX(0), mMaxX(0) - , mMinY(0), mMaxY(0) + , mMinX(0) + , mMaxX(0) + , mMinY(0) + , mMaxY(0) { /* @@ -278,10 +324,9 @@ namespace MWRender mWorkItem->waitTillDone(); } - void GlobalMap::render () + void GlobalMap::render() { - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); // get the size of the world MWWorld::Store::iterator it = esmStore.get().extBegin(); @@ -297,32 +342,26 @@ namespace MWRender mMaxY = it->getGridY(); } - mWidth = mCellSize*(mMaxX-mMinX+1); - mHeight = mCellSize*(mMaxY-mMinY+1); + mWidth = mCellSize * (mMaxX - mMinX + 1); + mHeight = mCellSize * (mMaxY - mMinY + 1); - mWorkItem = new CreateMapWorkItem(mWidth, mHeight, mMinX, mMinY, mMaxX, mMaxY, mCellSize, esmStore.get()); + mWorkItem + = new CreateMapWorkItem(mWidth, mHeight, mMinX, mMinY, mMaxX, mMaxY, mCellSize, esmStore.get()); mWorkQueue->addWorkItem(mWorkItem); } void GlobalMap::worldPosToImageSpace(float x, float z, float& imageX, float& imageY) { - imageX = float(x / float(Constants::CellSizeInUnits) - mMinX) / (mMaxX - mMinX + 1); + imageX = (float(x / float(Constants::CellSizeInUnits) - mMinX) / (mMaxX - mMinX + 1)) * getWidth(); - imageY = 1.f-float(z / float(Constants::CellSizeInUnits) - mMinY) / (mMaxY - mMinY + 1); + imageY = (1.f - float(z / float(Constants::CellSizeInUnits) - mMinY) / (mMaxY - mMinY + 1)) * getHeight(); } - void GlobalMap::cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY) + void GlobalMap::requestOverlayTextureUpdate(int x, int y, int width, int height, + osg::ref_ptr texture, bool clear, bool cpuCopy, float srcLeft, float srcTop, float srcRight, + float srcBottom) { - imageX = float(x - mMinX) / (mMaxX - mMinX + 1); - - // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is - imageY = 1.f-float(y - mMinY + 1) / (mMaxY - mMinY + 1); - } - - void GlobalMap::requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, bool clear, bool cpuCopy, - float srcLeft, float srcTop, float srcRight, float srcBottom) - { - osg::ref_ptr camera (new osg::Camera); + osg::ref_ptr camera(new osg::Camera); camera->setNodeMask(Mask_RenderToTexture); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setViewMatrix(osg::Matrix::identity()); @@ -335,7 +374,7 @@ namespace MWRender if (clear) { camera->setClearMask(GL_COLOR_BUFFER_BIT); - camera->setClearColor(osg::Vec4(0,0,0,0)); + camera->setClearColor(osg::Vec4(0, 0, 0, 0)); } else camera->setClearMask(GL_NONE); @@ -351,7 +390,7 @@ namespace MWRender if (cpuCopy) { // Attach an image to copy the render back to the CPU when finished - osg::ref_ptr image (new osg::Image); + osg::ref_ptr image(new osg::Image); image->setPixelFormat(mOverlayImage->getPixelFormat()); image->setDataType(mOverlayImage->getDataType()); camera->attach(osg::Camera::COLOR_BUFFER, image); @@ -367,8 +406,8 @@ namespace MWRender if (texture) { osg::ref_ptr geom = createTexturedQuad(srcLeft, srcTop, srcRight, srcBottom); - osg::ref_ptr depth = new osg::Depth; - depth->setWriteMask(0); + osg::ref_ptr depth = new SceneUtil::AutoDepth; + depth->setWriteMask(false); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setAttribute(depth); stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); @@ -412,7 +451,8 @@ namespace MWRender return; int originX = (cellX - mMinX) * mCellSize; - int originY = (cellY - mMinY + 1) * mCellSize; // +1 because we want the top left corner of the cell, not the bottom left + int originY = (cellY - mMinY + 1) + * mCellSize; // +1 because we want the top left corner of the cell, not the bottom left if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY) return; @@ -453,23 +493,15 @@ namespace MWRender map.mBounds.mMinY = mMinY; map.mBounds.mMaxY = mMaxY; - std::ostringstream ostream; - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); - if (!readerwriter) + if (mWritePng != nullptr) { - Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found"; + mWritePng->waitTillDone(); + map.mImageData = std::move(mWritePng->mImageData); + mWritePng = nullptr; return; } - osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream); - if (!result.success()) - { - Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status(); - return; - } - - std::string data = ostream.str(); - map.mImageData = std::vector(data.begin(), data.end()); + map.mImageData = writePng(*mOverlayImage); } struct Box @@ -477,10 +509,13 @@ namespace MWRender int mLeft, mTop, mRight, mBottom; Box(int left, int top, int right, int bottom) - : mLeft(left), mTop(top), mRight(right), mBottom(bottom) + : mLeft(left) + , mTop(top) + , mRight(right) + , mBottom(bottom) { } - bool operator == (const Box& other) + bool operator==(const Box& other) const { return mLeft == other.mLeft && mTop == other.mTop && mRight == other.mRight && mBottom == other.mBottom; } @@ -492,13 +527,12 @@ namespace MWRender const ESM::GlobalMap::Bounds& bounds = map.mBounds; - if (bounds.mMaxX-bounds.mMinX < 0) + if (bounds.mMaxX - bounds.mMinX < 0) return; - if (bounds.mMaxY-bounds.mMinY < 0) + if (bounds.mMaxY - bounds.mMinY < 0) return; - if (bounds.mMinX > bounds.mMaxX - || bounds.mMinY > bounds.mMaxY) + if (bounds.mMinX > bounds.mMaxX || bounds.mMinY > bounds.mMaxY) throw std::runtime_error("invalid map bounds"); if (map.mImageData.empty()) @@ -524,8 +558,8 @@ namespace MWRender int imageWidth = image->s(); int imageHeight = image->t(); - int xLength = (bounds.mMaxX-bounds.mMinX+1); - int yLength = (bounds.mMaxY-bounds.mMinY+1); + int xLength = (bounds.mMaxX - bounds.mMinX + 1); + int yLength = (bounds.mMaxY - bounds.mMinY + 1); // Size of one cell in image space int cellImageSizeSrc = imageWidth / xLength; @@ -538,28 +572,23 @@ namespace MWRender int cellImageSizeDst = mCellSize; // Completely off-screen? -> no need to blit anything - if (bounds.mMaxX < mMinX - || bounds.mMaxY < mMinY - || bounds.mMinX > mMaxX - || bounds.mMinY > mMaxY) + if (bounds.mMaxX < mMinX || bounds.mMaxY < mMinY || bounds.mMinX > mMaxX || bounds.mMinY > mMaxY) return; int leftDiff = (mMinX - bounds.mMinX); int topDiff = (bounds.mMaxY - mMaxY); int rightDiff = (bounds.mMaxX - mMaxX); - int bottomDiff = (mMinY - bounds.mMinY); + int bottomDiff = (mMinY - bounds.mMinY); - Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), - std::max(0, topDiff * cellImageSizeSrc), - std::min(imageWidth, imageWidth - rightDiff * cellImageSizeSrc), - std::min(imageHeight, imageHeight - bottomDiff * cellImageSizeSrc)); + Box srcBox(std::max(0, leftDiff * cellImageSizeSrc), std::max(0, topDiff * cellImageSizeSrc), + std::min(imageWidth, imageWidth - rightDiff * cellImageSizeSrc), + std::min(imageHeight, imageHeight - bottomDiff * cellImageSizeSrc)); - Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), - std::max(0, -topDiff * cellImageSizeDst), - std::min(mWidth, mWidth + rightDiff * cellImageSizeDst), - std::min(mHeight, mHeight + bottomDiff * cellImageSizeDst)); + Box destBox(std::max(0, -leftDiff * cellImageSizeDst), std::max(0, -topDiff * cellImageSizeDst), + std::min(mWidth, mWidth + rightDiff * cellImageSizeDst), + std::min(mHeight, mHeight + bottomDiff * cellImageSizeDst)); - osg::ref_ptr texture (new osg::Texture2D); + osg::ref_ptr texture(new osg::Texture2D); texture->setImage(image); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); @@ -578,9 +607,10 @@ namespace MWRender // Dimensions don't match. This could mean a changed map region, or a changed map resolution. // In the latter case, we'll want filtering. // Create a RTT Camera and draw the image onto mOverlayImage in the next frame. - requestOverlayTextureUpdate(destBox.mLeft, destBox.mTop, destBox.mRight-destBox.mLeft, destBox.mBottom-destBox.mTop, texture, true, true, - srcBox.mLeft/float(imageWidth), srcBox.mTop/float(imageHeight), - srcBox.mRight/float(imageWidth), srcBox.mBottom/float(imageHeight)); + requestOverlayTextureUpdate(destBox.mLeft, destBox.mTop, destBox.mRight - destBox.mLeft, + destBox.mBottom - destBox.mTop, texture, true, true, srcBox.mLeft / float(imageWidth), + srcBox.mTop / float(imageHeight), srcBox.mRight / float(imageWidth), + srcBox.mBottom / float(imageHeight)); } } @@ -613,7 +643,7 @@ namespace MWRender } } - bool GlobalMap::copyResult(osg::Camera *camera, unsigned int frame) + bool GlobalMap::copyResult(osg::Camera* camera, unsigned int frame) { ImageDestMap::iterator it = mPendingImageDest.find(camera); if (it == mPendingImageDest.end()) @@ -621,7 +651,9 @@ namespace MWRender else { ImageDest& imageDest = it->second; - if (imageDest.mFrameDone == 0) imageDest.mFrameDone = frame+2; // wait an extra frame to ensure the draw thread has completed its frame. + if (imageDest.mFrameDone == 0) + imageDest.mFrameDone + = frame + 2; // wait an extra frame to ensure the draw thread has completed its frame. if (imageDest.mFrameDone > frame) { ++it; @@ -686,7 +718,7 @@ namespace MWRender } } - void GlobalMap::markForRemoval(osg::Camera *camera) + void GlobalMap::markForRemoval(osg::Camera* camera) { CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), camera); if (found == mActiveCameras.end()) @@ -706,12 +738,13 @@ namespace MWRender mCamerasPendingRemoval.clear(); } - void GlobalMap::removeCamera(osg::Camera *cam) + void GlobalMap::removeCamera(osg::Camera* cam) { cam->removeChildren(0, cam->getNumChildren()); mRoot->removeChild(cam); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -757,4 +790,14 @@ namespace MWRender /* End of tes3mp addition */ +======= + void GlobalMap::asyncWritePng() + { + if (mOverlayImage == nullptr) + return; + // Use deep copy to avoid any sychronization + mWritePng = new WritePng(new osg::Image(*mOverlayImage, osg::CopyOp::DEEP_COPY_ALL)); + mWorkQueue->addWorkItem(mWritePng, /*front=*/true); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index 09528d300..97c3b4542 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -1,9 +1,9 @@ #ifndef GAME_RENDER_GLOBALMAP_H #define GAME_RENDER_GLOBALMAP_H +#include #include #include -#include #include @@ -41,13 +41,9 @@ namespace MWRender int getWidth() const { return mWidth; } int getHeight() const { return mHeight; } - int getCellSize() const { return mCellSize; } - void worldPosToImageSpace(float x, float z, float& imageX, float& imageY); - void cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY); - - void exploreCell (int cellX, int cellY, osg::ref_ptr localMapTexture); + void exploreCell(int cellX, int cellY, osg::ref_ptr localMapTexture); /// Clears the overlay void clear(); @@ -79,28 +75,33 @@ namespace MWRender */ void markForRemoval(osg::Camera* camera); - void write (ESM::GlobalMap& map); - void read (ESM::GlobalMap& map); + void write(ESM::GlobalMap& map); + void read(ESM::GlobalMap& map); osg::ref_ptr getBaseTexture(); osg::ref_ptr getOverlayTexture(); void ensureLoaded(); + void asyncWritePng(); + private: + struct WritePng; + /** * Request rendering a 2d quad onto mOverlayTexture. * x, y, width and height are the destination coordinates (top-left coordinate origin) * @param cpuCopy copy the resulting render onto mOverlayImage as well? */ - void requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, bool clear, bool cpuCopy, - float srcLeft = 0.f, float srcTop = 0.f, float srcRight = 1.f, float srcBottom = 1.f); + void requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, + bool clear, bool cpuCopy, float srcLeft = 0.f, float srcTop = 0.f, float srcRight = 1.f, + float srcBottom = 1.f); int mCellSize; osg::ref_ptr mRoot; - typedef std::vector > CameraVector; + typedef std::vector> CameraVector; CameraVector mActiveCameras; CameraVector mCamerasPendingRemoval; @@ -108,7 +109,8 @@ namespace MWRender struct ImageDest { ImageDest() - : mX(0), mY(0) + : mX(0) + , mY(0) , mFrameDone(0) { } @@ -122,7 +124,7 @@ namespace MWRender ImageDestMap mPendingImageDest; - std::vector< std::pair > mExploredCells; + std::vector> mExploredCells; osg::ref_ptr mBaseTexture; osg::ref_ptr mAlphaTexture; @@ -136,6 +138,7 @@ namespace MWRender osg::ref_ptr mWorkQueue; osg::ref_ptr mWorkItem; + osg::ref_ptr mWritePng; int mWidth; int mHeight; @@ -146,4 +149,3 @@ namespace MWRender } #endif - diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 63df3e249..9e169b256 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -1,83 +1,36 @@ #include "groundcover.hpp" #include +#include +#include #include +#include #include -#include +#include +#include +#include #include +#include +#include +#include -#include "apps/openmw/mwworld/esmstore.hpp" -#include "apps/openmw/mwbase/environment.hpp" -#include "apps/openmw/mwbase/world.hpp" +#include "../mwworld/groundcoverstore.hpp" #include "vismask.hpp" namespace MWRender { - std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store) - { - switch (type) - { - case ESM::REC_STAT: - return store.get().searchStatic(id)->mModel; - default: - return std::string(); - } - } - - void GroundcoverUpdater::setWindSpeed(float windSpeed) - { - mWindSpeed = windSpeed; - } - - void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos) - { - mPlayerPos = playerPos; - } - - void GroundcoverUpdater::setDefaults(osg::StateSet *stateset) - { - osg::ref_ptr windUniform = new osg::Uniform("windSpeed", 0.0f); - stateset->addUniform(windUniform.get()); - - osg::ref_ptr playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)); - stateset->addUniform(playerPosUniform.get()); - } - - void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) - { - osg::ref_ptr windUniform = stateset->getUniform("windSpeed"); - if (windUniform != nullptr) - windUniform->set(mWindSpeed); - - osg::ref_ptr playerPosUniform = stateset->getUniform("playerPos"); - if (playerPosUniform != nullptr) - playerPosUniform->set(mPlayerPos); - } - class InstancingVisitor : public osg::NodeVisitor { public: InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mInstances(instances) - , mChunkPosition(chunkPosition) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mInstances(instances) + , mChunkPosition(chunkPosition) { } - void apply(osg::Node& node) override - { - osg::ref_ptr ss = node.getStateSet(); - if (ss != nullptr) - { - ss->removeAttribute(osg::StateAttribute::MATERIAL); - removeAlpha(ss); - } - - traverse(node); - } - void apply(osg::Geometry& geom) override { for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) @@ -110,32 +63,15 @@ namespace MWRender // Display lists do not support instancing in OSG 3.4 geom.setUseDisplayList(false); + geom.setUseVertexBufferObjects(true); geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); - - osg::ref_ptr ss = geom.getOrCreateStateSet(); - ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); - ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); - - ss->removeAttribute(osg::StateAttribute::MATERIAL); - removeAlpha(ss); - - traverse(geom); } + private: std::vector mInstances; osg::Vec3f mChunkPosition; - - void removeAlpha(osg::StateSet* stateset) - { - // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties - stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); - stateset->removeMode(GL_ALPHA_TEST); - stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); - stateset->removeMode(GL_BLEND); - stateset->setRenderBinToInherit(); - } }; class DensityCalculator @@ -148,10 +84,12 @@ namespace MWRender bool isInstanceEnabled() { - if (mDensity >= 1.f) return true; + if (mDensity >= 1.f) + return true; mCurrentGroundcover += mDensity; - if (mCurrentGroundcover < 1.f) return false; + if (mCurrentGroundcover < 1.f) + return false; mCurrentGroundcover -= 1.f; @@ -164,27 +102,51 @@ namespace MWRender float mDensity = 0.f; }; + class ViewDistanceCallback : public SceneUtil::NodeCallback + { + public: + ViewDistanceCallback(float dist, const osg::BoundingBox& box) + : mViewDistance(dist) + , mBox(box) + { + } + void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (Terrain::distance(mBox, nv->getEyePoint()) <= mViewDistance) + traverse(node, nv); + } + + private: + float mViewDistance; + osg::BoundingBox mBox; + }; + inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) { osg::Vec2f size = maxBound - minBound; - if (size.x() >=1 && size.y() >=1) return true; + if (size.x() >= 1 && size.y() >= 1) + return true; osg::Vec3f pos = ref.mPos.asVec3(); osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; - if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) - || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) + || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) + || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) return false; return true; } - osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { - ChunkId id = std::make_tuple(center, size, activeGrid); - + if (lod > getMaxLodLevel()) + return nullptr; + GroundcoverChunkId id = std::make_tuple(center, size); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) - return obj->asNode(); + return static_cast(obj.get()); else { InstanceMap instances; @@ -195,55 +157,84 @@ namespace MWRender } } - Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) - : GenericResourceManager(nullptr) - , mSceneManager(sceneManager) - , mDensity(density) + Groundcover::Groundcover( + Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::GroundcoverStore& store) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + , mDensity(density) + , mStateset(new osg::StateSet) + , mGroundcoverStore(store) { + setViewDistance(viewDistance); + // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); + mStateset->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + mStateset->setAttributeAndModes(new osg::BlendFunc, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); + mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); + + mProgramTemplate = mSceneManager->getShaderManager().getProgramTemplate() + ? Shader::ShaderManager::cloneProgram(mSceneManager->getShaderManager().getProgramTemplate()) + : osg::ref_ptr(new osg::Program); + mProgramTemplate->addBindAttribLocation("aOffset", 6); + mProgramTemplate->addBindAttribLocation("aRotation", 7); } + Groundcover::~Groundcover() {} + void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); - osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); + if (mDensity <= 0.f) + return; + + osg::Vec2f minBound = (center - osg::Vec2f(size / 2.f, size / 2.f)); + osg::Vec2f maxBound = (center + osg::Vec2f(size / 2.f, size / 2.f)); DensityCalculator calculator(mDensity); - std::vector esm; - osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + ESM::ReadersCache readers; + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size / 2.f), std::floor(center.y() - size / 2.f)); for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) { - const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); - if (!cell) continue; + ESM::Cell cell; + mGroundcoverStore.initCell(cell, cellX, cellY); + if (cell.mContextList.empty()) + continue; calculator.reset(); - for (size_t i=0; imContextList.size(); ++i) + std::map refs; + for (size_t i = 0; i < cell.mContextList.size(); ++i) { - unsigned int index = cell->mContextList.at(i).index; - if (esm.size() <= index) - esm.resize(index+1); - cell->restore(esm[index], i); + const std::size_t index = static_cast(cell.mContextList[i].index); + const ESM::ReadersCache::BusyItem reader = readers.get(index); + cell.restore(*reader, i); ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; bool deleted = false; - while(cell->getNextRef(esm[index], ref, deleted)) + while (cell.getNextRef(*reader, ref, deleted)) { - if (deleted) continue; - if (!ref.mRefNum.fromGroundcoverFile()) continue; + if (!deleted && refs.find(ref.mRefNum) == refs.end() && !calculator.isInstanceEnabled()) + deleted = true; + if (!deleted && !isInChunkBorders(ref, minBound, maxBound)) + deleted = true; - if (!calculator.isInstanceEnabled()) continue; - if (!isInChunkBorders(ref, minBound, maxBound)) continue; - - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - int type = store.findStatic(ref.mRefID); - std::string model = getGroundcoverModel(type, ref.mRefID, store); - if (model.empty()) continue; - model = "meshes/" + model; - - instances[model].emplace_back(std::move(ref), std::move(model)); + if (deleted) + { + refs.erase(ref.mRefNum); + continue; + } + refs[ref.mRefNum] = std::move(ref); } } + + for (auto& pair : refs) + { + ESM::CellRef& ref = pair.second; + const std::string& model = mGroundcoverStore.getGroundcoverModel(ref.mRefID); + if (!model.empty()) + instances[model].emplace_back(std::move(ref)); + } } } } @@ -251,31 +242,34 @@ namespace MWRender osg::ref_ptr Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center) { osg::ref_ptr group = new osg::Group; - osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0) * ESM::Land::REAL_SIZE; for (auto& pair : instances) { const osg::Node* temp = mSceneManager->getTemplate(pair.first); - osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); + osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_NODES + | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_USERDATA | osg::CopyOp::DEEP_COPY_ARRAYS + | osg::CopyOp::DEEP_COPY_PRIMITIVES)); // Keep link to original mesh to keep it in cache group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); - mSceneManager->reinstateRemovedState(node); - InstancingVisitor visitor(pair.second, worldCenter); node->accept(visitor); group->addChild(node); } - // Force a unified alpha handling instead of data from meshes - osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); - group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); - group->getBound(); + osg::ComputeBoundsVisitor cbv; + group->accept(cbv); + osg::BoundingBox box = cbv.getBoundingBox(); + group->addCullCallback(new ViewDistanceCallback(getViewDistance(), box)); + + group->setStateSet(mStateset); group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) - group->setCullCallback(new SceneUtil::LightListCallback); - mSceneManager->recreateShaders(group, "groundcover", false, true); - + group->addCullCallback(new SceneUtil::LightListCallback); + mSceneManager->recreateShaders(group, "groundcover", true, mProgramTemplate); + mSceneManager->shareState(group); + group->getBound(); return group; } @@ -284,7 +278,7 @@ namespace MWRender return Mask_Groundcover; } - void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const + void Groundcover::reportStats(unsigned int frameNumber, osg::Stats* stats) const { stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); } diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index cd80978be..df40d9d52 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -1,42 +1,33 @@ #ifndef OPENMW_MWRENDER_GROUNDCOVER_H #define OPENMW_MWRENDER_GROUNDCOVER_H -#include +#include #include -#include -#include +#include + +namespace MWWorld +{ + class ESMStore; + class GroundcoverStore; +} +namespace osg +{ + class Program; +} namespace MWRender { - class GroundcoverUpdater : public SceneUtil::StateSetUpdater + typedef std::tuple GroundcoverChunkId; // Center, Size + class Groundcover : public Resource::GenericResourceManager, + public Terrain::QuadTreeWorld::ChunkManager { public: - GroundcoverUpdater() - : mWindSpeed(0.f) - , mPlayerPos(osg::Vec3f()) - { - } + Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, + const MWWorld::GroundcoverStore& store); + ~Groundcover(); - void setWindSpeed(float windSpeed); - void setPlayerPos(osg::Vec3f playerPos); - - protected: - void setDefaults(osg::StateSet *stateset) override; - void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; - - private: - float mWindSpeed; - osg::Vec3f mPlayerPos; - }; - - typedef std::tuple ChunkId; // Center, Size, ActiveGrid - class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager - { - public: - Groundcover(Resource::SceneManager* sceneManager, float density); - ~Groundcover() = default; - - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, + bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; unsigned int getNodeMask() override; @@ -46,19 +37,20 @@ namespace MWRender { ESM::Position mPos; float mScale; - std::string mModel; - GroundcoverEntry(const ESM::CellRef& ref, const std::string& model) + GroundcoverEntry(const ESM::CellRef& ref) + : mPos(ref.mPos) + , mScale(ref.mScale) { - mPos = ref.mPos; - mScale = ref.mScale; - mModel = model; } }; private: Resource::SceneManager* mSceneManager; float mDensity; + osg::ref_ptr mStateset; + osg::ref_ptr mProgramTemplate; + const MWWorld::GroundcoverStore& mGroundcoverStore; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index 560c1ba72..23349ce94 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -11,33 +11,48 @@ namespace MWRender { -LandManager::LandManager(int loadFlags) - : GenericResourceManager >(nullptr) - , mLoadFlags(loadFlags) -{ - mCache = new CacheType; -} - -osg::ref_ptr LandManager::getLand(int x, int y) -{ - osg::ref_ptr obj = mCache->getRefFromObjectCache(std::make_pair(x,y)); - if (obj) - return static_cast(obj.get()); - else + LandManager::LandManager(int loadFlags) + : GenericResourceManager(nullptr) + , mLoadFlags(loadFlags) { - const ESM::Land* land = MWBase::Environment::get().getWorld()->getStore().get().search(x,y); - if (!land) - return nullptr; - osg::ref_ptr landObj (new ESMTerrain::LandObject(land, mLoadFlags)); - mCache->addEntryToObjectCache(std::make_pair(x,y), landObj.get()); - return landObj; + mCache = new CacheType; } -} -void LandManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const -{ - stats->setAttribute(frameNumber, "Land", mCache->getCacheSize()); -} + osg::ref_ptr LandManager::getLand(ESM::ExteriorCellLocation cellIndex) + { + osg::ref_ptr obj = mCache->getRefFromObjectCache(cellIndex); + if (obj) + return static_cast(obj.get()); + else + { + const auto world = MWBase::Environment::get().getWorld(); + if (!world) + return nullptr; + if (ESM::isEsm4Ext(cellIndex.mWorldspace)) + { + const ESM4::Land* land = world->getStore().get().search(cellIndex); + if (!land) + return nullptr; + osg::ref_ptr landObj(new ESMTerrain::LandObject(land, mLoadFlags)); + mCache->addEntryToObjectCache(cellIndex, landObj.get()); + return landObj; + } + else + { + const ESM::Land* land = world->getStore().get().search(cellIndex.mX, cellIndex.mY); + if (!land) + return nullptr; + osg::ref_ptr landObj(new ESMTerrain::LandObject(land, mLoadFlags)); + mCache->addEntryToObjectCache(cellIndex, landObj.get()); + return landObj; + } + } + } + + void LandManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const + { + stats->setAttribute(frameNumber, "Land", mCache->getCacheSize()); + } } diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index f3cc86085..d9c36ea5d 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -3,8 +3,9 @@ #include +#include +#include #include -#include namespace ESM { @@ -14,13 +15,13 @@ namespace ESM namespace MWRender { - class LandManager : public Resource::GenericResourceManager > + class LandManager : public Resource::GenericResourceManager { public: LandManager(int loadFlags); /// @note Will return nullptr if not found. - osg::ref_ptr getLand(int x, int y); + osg::ref_ptr getLand(ESM::ExteriorCellLocation cellIndex); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 686078879..937aa9116 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -1,27 +1,29 @@ #include "localmap.hpp" -#include +#include +#include #include #include -#include -#include #include #include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -33,40 +35,9 @@ namespace { - - class CameraLocalUpdateCallback : public osg::NodeCallback - { - public: - CameraLocalUpdateCallback(MWRender::LocalMap* parent) - : mRendered(false) - , mParent(parent) - { - } - - void operator()(osg::Node* node, osg::NodeVisitor*) override - { - if (mRendered) - node->setNodeMask(0); - - if (!mRendered) - { - mRendered = true; - mParent->markForRemoval(static_cast(node)); - } - - // Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's, - // so it has been updated already. - //traverse(node, nv); - } - - private: - bool mRendered; - MWRender::LocalMap* mParent; - }; - float square(float val) { - return val*val; + return val * val; } std::pair divideIntoSegments(const osg::BoundingBox& bounds, float mapSize) @@ -76,698 +47,744 @@ namespace osg::Vec2f length = max - min; const int segsX = static_cast(std::ceil(length.x() / mapSize)); const int segsY = static_cast(std::ceil(length.y() / mapSize)); - return {segsX, segsY}; + return { segsX, segsY }; } } namespace MWRender { - -LocalMap::LocalMap(osg::Group* root) - : mRoot(root) - , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) - , mMapWorldSize(Constants::CellSizeInUnits) - , mCellDistance(Constants::CellGridRadius) - , mAngle(0.f) - , mInterior(false) -{ - // Increase map resolution, if use UI scaling - float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - mMapResolution *= uiScale; - - SceneUtil::FindByNameVisitor find("Scene Root"); - mRoot->accept(find); - mSceneRoot = find.mFoundNode; - if (!mSceneRoot) - throw std::runtime_error("no scene root found"); -} - -LocalMap::~LocalMap() -{ - for (auto& camera : mActiveCameras) - removeCamera(camera); - for (auto& camera : mCamerasPendingRemoval) - removeCamera(camera); -} - -const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) -{ - return osg::Vec2f( std::cos(angle) * (point.x() - center.x()) - std::sin(angle) * (point.y() - center.y()) + center.x(), - std::sin(angle) * (point.x() - center.x()) + std::cos(angle) * (point.y() - center.y()) + center.y()); -} - -void LocalMap::clear() -{ - mSegments.clear(); -} - -void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) -{ - if (!mInterior) + class LocalMapRenderToTexture : public SceneUtil::RTTNode { - const MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; + public: + LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, float x, float y, + const osg::Vec3d& upVector, float zmin, float zmax); - if (segment.mFogOfWarImage && segment.mHasFogState) - { - std::unique_ptr fog (new ESM::FogState()); - fog->mFogTextures.emplace_back(); + void setDefaults(osg::Camera* camera) override; - segment.saveFogOfWar(fog->mFogTextures.back()); + osg::Node* mSceneRoot; + osg::Matrix mProjectionMatrix; + osg::Matrix mViewMatrix; + bool mActive; + }; - cell->setFog(fog.release()); - } + class CameraLocalUpdateCallback + : public SceneUtil::NodeCallback + { + public: + void operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv); + }; + + LocalMap::LocalMap(osg::Group* root) + : mRoot(root) + , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) + , mMapWorldSize(Constants::CellSizeInUnits) + , mCellDistance(Constants::CellGridRadius) + , mAngle(0.f) + , mInterior(false) + { + // Increase map resolution, if use UI scaling + float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); + mMapResolution *= uiScale; + + SceneUtil::FindByNameVisitor find("Scene Root"); + mRoot->accept(find); + mSceneRoot = find.mFoundNode; + if (!mSceneRoot) + throw std::runtime_error("no scene root found"); } - else + + LocalMap::~LocalMap() { - auto segments = divideIntoSegments(mBounds, mMapWorldSize); + for (auto& rtt : mLocalMapRTTs) + mRoot->removeChild(rtt); + } - std::unique_ptr fog (new ESM::FogState()); + const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) + { + return osg::Vec2f( + std::cos(angle) * (point.x() - center.x()) - std::sin(angle) * (point.y() - center.y()) + center.x(), + std::sin(angle) * (point.x() - center.x()) + std::cos(angle) * (point.y() - center.y()) + center.y()); + } - fog->mBounds.mMinX = mBounds.xMin(); - fog->mBounds.mMaxX = mBounds.xMax(); - fog->mBounds.mMinY = mBounds.yMin(); - fog->mBounds.mMaxY = mBounds.yMax(); - fog->mNorthMarkerAngle = mAngle; + void LocalMap::clear() + { + mExteriorSegments.clear(); + mInteriorSegments.clear(); + } - fog->mFogTextures.reserve(segments.first * segments.second); - - for (int x = 0; x < segments.first; ++x) + void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) + { + if (!mInterior) { - for (int y = 0; y < segments.second; ++y) - { - const MapSegment& segment = mSegments[std::make_pair(x,y)]; + const MapSegment& segment + = mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; + if (segment.mFogOfWarImage && segment.mHasFogState) + { + auto fog = std::make_unique(); fog->mFogTextures.emplace_back(); - // saving even if !segment.mHasFogState so we don't mess up the segmenting - // plus, older openmw versions can't deal with empty images segment.saveFogOfWar(fog->mFogTextures.back()); - fog->mFogTextures.back().mX = x; - fog->mFogTextures.back().mY = y; + cell->setFog(std::move(fog)); } } - - cell->setFog(fog.release()); - } -} - -osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) -{ - osg::ref_ptr camera (new osg::Camera); - camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); - camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); - camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - camera->setRenderOrder(osg::Camera::PRE_RENDER); - - camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); - camera->setNodeMask(Mask_RenderToTexture); - - // Disable small feature culling, it's not going to be reliable for this camera - osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING) & ~(osg::CullStack::SMALL_FEATURE_CULLING); - camera->setCullingMode(cullingMode); - - osg::ref_ptr stateset = new osg::StateSet; - stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); - - // assign large value to effectively turn off fog - // shaders don't respect glDisable(GL_FOG) - osg::ref_ptr fog (new osg::Fog); - fog->setStart(10000000); - fog->setEnd(10000000); - stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); - - osg::ref_ptr lightmodel = new osg::LightModel; - lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); - stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::ref_ptr light = new osg::Light; - light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f)); - light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f)); - light->setAmbient(osg::Vec4(0,0,0,1)); - light->setSpecular(osg::Vec4(0,0,0,0)); - light->setLightNum(0); - light->setConstantAttenuation(1.f); - light->setLinearAttenuation(0.f); - light->setQuadraticAttenuation(0.f); - - osg::ref_ptr lightSource = new osg::LightSource; - lightSource->setLight(light); - - lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); - - // override sun for local map - SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot.get()), light, stateset); - - camera->addChild(lightSource); - camera->setStateSet(stateset); - camera->setViewport(0, 0, mMapResolution, mMapResolution); - camera->setUpdateCallback(new CameraLocalUpdateCallback(this)); - - return camera; -} - -void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int y) -{ - osg::ref_ptr texture (new osg::Texture2D); - texture->setTextureSize(mMapResolution, mMapResolution); - texture->setInternalFormat(GL_RGB); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, texture); - - camera->addChild(mSceneRoot); - mRoot->addChild(camera); - mActiveCameras.push_back(camera); - - MapSegment& segment = mSegments[std::make_pair(x, y)]; - segment.mMapTexture = texture; -} - -bool needUpdate(std::set >& renderedGrid, std::set >& currentGrid, int cellX, int cellY) -{ - // if all the cells of the current grid are contained in the rendered grid then we can keep the old render - for (int dx=-1;dx<2;dx+=1) - { - for (int dy=-1;dy<2;dy+=1) + else { - bool haveInRenderedGrid = renderedGrid.find(std::make_pair(cellX+dx,cellY+dy)) != renderedGrid.end(); - bool haveInCurrentGrid = currentGrid.find(std::make_pair(cellX+dx,cellY+dy)) != currentGrid.end(); - if (haveInCurrentGrid && !haveInRenderedGrid) - return true; + auto segments = divideIntoSegments(mBounds, mMapWorldSize); + + auto fog = std::make_unique(); + + fog->mBounds.mMinX = mBounds.xMin(); + fog->mBounds.mMaxX = mBounds.xMax(); + fog->mBounds.mMinY = mBounds.yMin(); + fog->mBounds.mMaxY = mBounds.yMax(); + fog->mNorthMarkerAngle = mAngle; + + fog->mFogTextures.reserve(segments.first * segments.second); + + for (int x = 0; x < segments.first; ++x) + { + for (int y = 0; y < segments.second; ++y) + { + const MapSegment& segment = mInteriorSegments[std::make_pair(x, y)]; + + fog->mFogTextures.emplace_back(); + + // saving even if !segment.mHasFogState so we don't mess up the segmenting + // plus, older openmw versions can't deal with empty images + segment.saveFogOfWar(fog->mFogTextures.back()); + + fog->mFogTextures.back().mX = x; + fog->mFogTextures.back().mY = y; + } + } + + cell->setFog(std::move(fog)); } } - return false; -} -void LocalMap::requestMap(const MWWorld::CellStore* cell) -{ - if (cell->isExterior()) + void LocalMap::setupRenderToTexture( + int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax) { + mLocalMapRTTs.emplace_back( + new LocalMapRenderToTexture(mSceneRoot, mMapResolution, mMapWorldSize, left, top, upVector, zmin, zmax)); + + mRoot->addChild(mLocalMapRTTs.back()); + + MapSegment& segment = mInterior ? mInteriorSegments[std::make_pair(segment_x, segment_y)] + : mExteriorSegments[std::make_pair(segment_x, segment_y)]; + segment.mMapTexture = static_cast(mLocalMapRTTs.back()->getColorTexture(nullptr)); + } + + void LocalMap::requestMap(const MWWorld::CellStore* cell) + { + if (!cell->isExterior()) + { + requestInteriorMap(cell); + return; + } + int cellX = cell->getCell()->getGridX(); int cellY = cell->getCell()->getGridY(); - MapSegment& segment = mSegments[std::make_pair(cellX, cellY)]; - if (!needUpdate(segment.mGrid, mCurrentGrid, cellX, cellY)) + MapSegment& segment = mExteriorSegments[std::make_pair(cellX, cellY)]; + const std::uint8_t neighbourFlags = getExteriorNeighbourFlags(cellX, cellY); + if ((segment.mLastRenderNeighbourFlags & neighbourFlags) == neighbourFlags) return; + requestExteriorMap(cell, segment); + segment.mLastRenderNeighbourFlags = neighbourFlags; + } + + void LocalMap::addCell(MWWorld::CellStore* cell) + { + if (cell->isExterior()) + mExteriorSegments.emplace( + std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY()), MapSegment{}); + } + + void LocalMap::removeExteriorCell(int x, int y) + { + mExteriorSegments.erase({ x, y }); + } + + void LocalMap::removeCell(MWWorld::CellStore* cell) + { + saveFogOfWar(cell); + + if (cell->isExterior()) + mExteriorSegments.erase({ cell->getCell()->getGridX(), cell->getCell()->getGridY() }); else + mInteriorSegments.clear(); + } + + osg::ref_ptr LocalMap::getMapTexture(int x, int y) + { + auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); + SegmentMap::iterator found = segments.find(std::make_pair(x, y)); + if (found == segments.end()) + return osg::ref_ptr(); + else + return found->second.mMapTexture; + } + + osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) + { + auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); + SegmentMap::iterator found = segments.find(std::make_pair(x, y)); + if (found == segments.end()) + return osg::ref_ptr(); + else + return found->second.mFogOfWarTexture; + } + + void LocalMap::cleanupCameras() + { + auto it = mLocalMapRTTs.begin(); + while (it != mLocalMapRTTs.end()) { - segment.mGrid = mCurrentGrid; - requestExteriorMap(cell); + if (!(*it)->mActive) + { + mRoot->removeChild(*it); + it = mLocalMapRTTs.erase(it); + } + else + it++; } } - else - requestInteriorMap(cell); -} -void LocalMap::addCell(MWWorld::CellStore *cell) -{ - if (cell->isExterior()) - mCurrentGrid.emplace(cell->getCell()->getGridX(), cell->getCell()->getGridY()); -} - -void LocalMap::removeCell(MWWorld::CellStore *cell) -{ - saveFogOfWar(cell); - - if (cell->isExterior()) + void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell, MapSegment& segment) { - std::pair coords = std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY()); - mSegments.erase(coords); - mCurrentGrid.erase(coords); - } - else - mSegments.clear(); -} + mInterior = false; -osg::ref_ptr LocalMap::getMapTexture(int x, int y) -{ - SegmentMap::iterator found = mSegments.find(std::make_pair(x, y)); - if (found == mSegments.end()) - return osg::ref_ptr(); - else - return found->second.mMapTexture; -} + const int x = cell->getCell()->getGridX(); + const int y = cell->getCell()->getGridY(); -osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) -{ - SegmentMap::iterator found = mSegments.find(std::make_pair(x, y)); - if (found == mSegments.end()) - return osg::ref_ptr(); - else - return found->second.mFogOfWarTexture; -} + osg::BoundingSphere bound = mSceneRoot->getBound(); + float zmin = bound.center().z() - bound.radius(); + float zmax = bound.center().z() + bound.radius(); -void LocalMap::removeCamera(osg::Camera *cam) -{ - cam->removeChildren(0, cam->getNumChildren()); - mRoot->removeChild(cam); -} + setupRenderToTexture(x, y, x * mMapWorldSize + mMapWorldSize / 2.f, y * mMapWorldSize + mMapWorldSize / 2.f, + osg::Vec3d(0, 1, 0), zmin, zmax); -void LocalMap::markForRemoval(osg::Camera *cam) -{ - CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), cam); - if (found == mActiveCameras.end()) - { - Log(Debug::Error) << "Error: trying to remove an inactive camera"; - return; - } - mActiveCameras.erase(found); - mCamerasPendingRemoval.push_back(cam); -} + if (segment.mFogOfWarImage != nullptr) + return; -void LocalMap::cleanupCameras() -{ - if (mCamerasPendingRemoval.empty()) - return; - - for (auto& camera : mCamerasPendingRemoval) - removeCamera(camera); - - mCamerasPendingRemoval.clear(); -} - -void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) -{ - mInterior = false; - - int x = cell->getCell()->getGridX(); - int y = cell->getCell()->getGridY(); - - osg::BoundingSphere bound = mSceneRoot->getBound(); - float zmin = bound.center().z() - bound.radius(); - float zmax = bound.center().z() + bound.radius(); - - osg::ref_ptr camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, - osg::Vec3d(0,1,0), zmin, zmax); - setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); - - MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; - if (!segment.mFogOfWarImage) - { if (cell->getFog()) segment.loadFogOfWar(cell->getFog()->mFogTextures.back()); else segment.initFogOfWar(); } -} -void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) -{ - osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(Mask_Scene | Mask_Terrain | Mask_Object | Mask_Static); - mSceneRoot->accept(computeBoundsVisitor); - - osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); - - // If we're in an empty cell, bail out - // The operations in this function are only valid for finite bounds - if (!bounds.valid() || bounds.radius2() == 0.0) - return; - - mInterior = true; - - mBounds = bounds; - - // Get the cell's NorthMarker rotation. This is used to rotate the entire map. - osg::Vec2f north = MWBase::Environment::get().getWorld()->getNorthVector(cell); - - mAngle = std::atan2(north.x(), north.y()); - - // Rotate the cell and merge the rotated corners to the bounding box - osg::Vec2f origCenter(bounds.center().x(), bounds.center().y()); - osg::Vec3f origCorners[8]; - for (int i=0; i<8; ++i) - origCorners[i] = mBounds.corner(i); - - for (int i=0; i<8; ++i) + static osg::Vec2f getNorthVector(const MWWorld::CellStore* cell) { - osg::Vec3f corner = origCorners[i]; - osg::Vec2f corner2d (corner.x(), corner.y()); - corner2d = rotatePoint(corner2d, origCenter, mAngle); - mBounds.expandBy(osg::Vec3f(corner2d.x(), corner2d.y(), 0)); + MWWorld::ConstPtr northmarker = cell->searchConst(ESM::RefId::stringRefId("northmarker")); + + if (northmarker.isEmpty()) + return osg::Vec2f(0, 1); + + osg::Quat orient(-northmarker.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, 1)); + osg::Vec3f dir = orient * osg::Vec3f(0, 1, 0); + osg::Vec2f d(dir.x(), dir.y()); + return d; } - // Do NOT change padding! This will break older savegames. - // If the padding really needs to be changed, then it must be saved in the ESM::FogState and - // assume the old (500) value as default for older savegames. - const float padding = 500.0f; - - // Apply a little padding - mBounds.set(mBounds._min - osg::Vec3f(padding,padding,0.f), - mBounds._max + osg::Vec3f(padding,padding,0.f)); - - float zMin = mBounds.zMin(); - float zMax = mBounds.zMax(); - - // If there is fog state in the CellStore (e.g. when it came from a savegame) we need to do some checks - // to see if this state is still valid. - // Both the cell bounds and the NorthMarker rotation could be changed by the content files or exchanged models. - // If they changed by too much then parts of the interior might not be covered by the map anymore. - // The following code detects this, and discards the CellStore's fog state if it needs to. - std::vector> segmentMappings; - if (cell->getFog()) + void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) { - ESM::FogState* fog = cell->getFog(); + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(Mask_Scene | Mask_Terrain | Mask_Object | Mask_Static); + mSceneRoot->accept(computeBoundsVisitor); - if (std::abs(mAngle - fog->mNorthMarkerAngle) < osg::DegreesToRadians(5.f)) + osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); + + // If we're in an empty cell, bail out + // The operations in this function are only valid for finite bounds + if (!bounds.valid() || bounds.radius2() == 0.0) + return; + + mInterior = true; + + mBounds = bounds; + + // Get the cell's NorthMarker rotation. This is used to rotate the entire map. + osg::Vec2f north = getNorthVector(cell); + + mAngle = std::atan2(north.x(), north.y()); + + // Rotate the cell and merge the rotated corners to the bounding box + osg::Vec2f origCenter(bounds.center().x(), bounds.center().y()); + osg::Vec3f origCorners[8]; + for (int i = 0; i < 8; ++i) + origCorners[i] = mBounds.corner(i); + + for (int i = 0; i < 8; ++i) { - // Expand mBounds so the saved textures fit the same grid - int xOffset = 0; - int yOffset = 0; - if(fog->mBounds.mMinX < mBounds.xMin()) - { - mBounds.xMin() = fog->mBounds.mMinX; - } - else if(fog->mBounds.mMinX > mBounds.xMin()) - { - float diff = fog->mBounds.mMinX - mBounds.xMin(); - xOffset += diff / mMapWorldSize; - xOffset++; - mBounds.xMin() = fog->mBounds.mMinX - xOffset * mMapWorldSize; - } - if(fog->mBounds.mMinY < mBounds.yMin()) - { - mBounds.yMin() = fog->mBounds.mMinY; - } - else if(fog->mBounds.mMinY > mBounds.yMin()) - { - float diff = fog->mBounds.mMinY - mBounds.yMin(); - yOffset += diff / mMapWorldSize; - yOffset++; - mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize; - } - mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX); - mBounds.yMax() = std::max(mBounds.yMax(), fog->mBounds.mMaxY); - - if(xOffset != 0 || yOffset != 0) - Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset; - - const auto& textures = fog->mFogTextures; - segmentMappings.reserve(textures.size()); - osg::BoundingBox savedBounds{ - fog->mBounds.mMinX, fog->mBounds.mMinY, 0, - fog->mBounds.mMaxX, fog->mBounds.mMaxY, 0 - }; - auto segments = divideIntoSegments(savedBounds, mMapWorldSize); - for (int x = 0; x < segments.first; ++x) - for (int y = 0; y < segments.second; ++y) - segmentMappings.emplace_back(std::make_pair(x + xOffset, y + yOffset)); - - mAngle = fog->mNorthMarkerAngle; + osg::Vec3f corner = origCorners[i]; + osg::Vec2f corner2d(corner.x(), corner.y()); + corner2d = rotatePoint(corner2d, origCenter, mAngle); + mBounds.expandBy(osg::Vec3f(corner2d.x(), corner2d.y(), 0)); } - } - osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); + // Do NOT change padding! This will break older savegames. + // If the padding really needs to be changed, then it must be saved in the ESM::FogState and + // assume the old (500) value as default for older savegames. + const float padding = 500.0f; - osg::Vec2f center(mBounds.center().x(), mBounds.center().y()); - osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1)); + // Apply a little padding + mBounds.set(mBounds._min - osg::Vec3f(padding, padding, 0.f), mBounds._max + osg::Vec3f(padding, padding, 0.f)); - auto segments = divideIntoSegments(mBounds, mMapWorldSize); - for (int x = 0; x < segments.first; ++x) - { - for (int y = 0; y < segments.second; ++y) + float zMin = mBounds.zMin(); + float zMax = mBounds.zMax(); + + // If there is fog state in the CellStore (e.g. when it came from a savegame) we need to do some checks + // to see if this state is still valid. + // Both the cell bounds and the NorthMarker rotation could be changed by the content files or exchanged models. + // If they changed by too much then parts of the interior might not be covered by the map anymore. + // The following code detects this, and discards the CellStore's fog state if it needs to. + std::vector> segmentMappings; + if (cell->getFog()) { - osg::Vec2f start = min + osg::Vec2f(mMapWorldSize*x, mMapWorldSize*y); - osg::Vec2f newcenter = start + osg::Vec2f(mMapWorldSize/2.f, mMapWorldSize/2.f); + ESM::FogState* fog = cell->getFog(); - osg::Vec2f a = newcenter - center; - osg::Vec3f rotatedCenter = cameraOrient * (osg::Vec3f(a.x(), a.y(), 0)); - - osg::Vec2f pos = osg::Vec2f(rotatedCenter.x(), rotatedCenter.y()) + center; - - osg::ref_ptr camera = createOrthographicCamera(pos.x(), pos.y(), - mMapWorldSize, mMapWorldSize, - osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); - - setupRenderToTexture(camera, x, y); - - auto coords = std::make_pair(x,y); - MapSegment& segment = mSegments[coords]; - if (!segment.mFogOfWarImage) + if (std::abs(mAngle - fog->mNorthMarkerAngle) < osg::DegreesToRadians(5.f)) { - bool loaded = false; - for(size_t index{}; index < segmentMappings.size(); index++) + // Expand mBounds so the saved textures fit the same grid + int xOffset = 0; + int yOffset = 0; + if (fog->mBounds.mMinX < mBounds.xMin()) { - if(segmentMappings[index] == coords) - { - ESM::FogState* fog = cell->getFog(); - segment.loadFogOfWar(fog->mFogTextures[index]); - loaded = true; - break; - } + mBounds.xMin() = fog->mBounds.mMinX; } - if(!loaded) - segment.initFogOfWar(); + else if (fog->mBounds.mMinX > mBounds.xMin()) + { + float diff = fog->mBounds.mMinX - mBounds.xMin(); + xOffset += diff / mMapWorldSize; + xOffset++; + mBounds.xMin() = fog->mBounds.mMinX - xOffset * mMapWorldSize; + } + if (fog->mBounds.mMinY < mBounds.yMin()) + { + mBounds.yMin() = fog->mBounds.mMinY; + } + else if (fog->mBounds.mMinY > mBounds.yMin()) + { + float diff = fog->mBounds.mMinY - mBounds.yMin(); + yOffset += diff / mMapWorldSize; + yOffset++; + mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize; + } + if (fog->mBounds.mMaxX > mBounds.xMax()) + mBounds.xMax() = fog->mBounds.mMaxX; + if (fog->mBounds.mMaxY > mBounds.yMax()) + mBounds.yMax() = fog->mBounds.mMaxY; + + if (xOffset != 0 || yOffset != 0) + Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset; + + const auto& textures = fog->mFogTextures; + segmentMappings.reserve(textures.size()); + osg::BoundingBox savedBounds{ fog->mBounds.mMinX, fog->mBounds.mMinY, 0, fog->mBounds.mMaxX, + fog->mBounds.mMaxY, 0 }; + auto segments = divideIntoSegments(savedBounds, mMapWorldSize); + for (int x = 0; x < segments.first; ++x) + for (int y = 0; y < segments.second; ++y) + segmentMappings.emplace_back(std::make_pair(x + xOffset, y + yOffset)); + + mAngle = fog->mNorthMarkerAngle; } } - } -} -void LocalMap::worldToInteriorMapPosition (osg::Vec2f pos, float& nX, float& nY, int& x, int& y) -{ - pos = rotatePoint(pos, osg::Vec2f(mBounds.center().x(), mBounds.center().y()), mAngle); + osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); + osg::Vec2f center(mBounds.center().x(), mBounds.center().y()); + osg::Quat cameraOrient(mAngle, osg::Vec3d(0, 0, -1)); - x = static_cast(std::ceil((pos.x() - min.x()) / mMapWorldSize) - 1); - y = static_cast(std::ceil((pos.y() - min.y()) / mMapWorldSize) - 1); - - nX = (pos.x() - min.x() - mMapWorldSize*x)/mMapWorldSize; - nY = 1.0f-(pos.y() - min.y() - mMapWorldSize*y)/mMapWorldSize; -} - -osg::Vec2f LocalMap::interiorMapToWorldPosition (float nX, float nY, int x, int y) -{ - osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - osg::Vec2f pos (mMapWorldSize * (nX + x) + min.x(), - mMapWorldSize * (1.0f-nY + y) + min.y()); - - pos = rotatePoint(pos, osg::Vec2f(mBounds.center().x(), mBounds.center().y()), -mAngle); - return pos; -} - -bool LocalMap::isPositionExplored (float nX, float nY, int x, int y) -{ - const MapSegment& segment = mSegments[std::make_pair(x, y)]; - if (!segment.mFogOfWarImage) - return false; - - nX = std::max(0.f, std::min(1.f, nX)); - nY = std::max(0.f, std::min(1.f, nY)); - - int texU = static_cast((sFogOfWarResolution - 1) * nX); - int texV = static_cast((sFogOfWarResolution - 1) * nY); - - uint32_t clr = ((const uint32_t*)segment.mFogOfWarImage->data())[texV * sFogOfWarResolution + texU]; - uint8_t alpha = (clr >> 24); - return alpha < 200; -} - -osg::Group* LocalMap::getRoot() -{ - return mRoot; -} - -void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orientation, - float& u, float& v, int& x, int& y, osg::Vec3f& direction) -{ - // retrieve the x,y grid coordinates the player is in - osg::Vec2f pos(position.x(), position.y()); - - if (mInterior) - { - worldToInteriorMapPosition(pos, u,v, x,y); - - osg::Quat cameraOrient (mAngle, osg::Vec3(0,0,-1)); - direction = orientation * cameraOrient.inverse() * osg::Vec3f(0,1,0); - } - else - { - direction = orientation * osg::Vec3f(0,1,0); - - x = static_cast(std::ceil(pos.x() / mMapWorldSize) - 1); - y = static_cast(std::ceil(pos.y() / mMapWorldSize) - 1); - - // convert from world coordinates to texture UV coordinates - u = std::abs((pos.x() - (mMapWorldSize*x))/mMapWorldSize); - v = 1.0f-std::abs((pos.y() - (mMapWorldSize*y))/mMapWorldSize); - } - - // explore radius (squared) - const float exploreRadius = 0.17f * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1 - const float sqrExploreRadius = square(exploreRadius); - const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) - - // change the affected fog of war textures (in a 3x3 grid around the player) - for (int mx = -mCellDistance; mx<=mCellDistance; ++mx) - { - for (int my = -mCellDistance; my<=mCellDistance; ++my) + auto segments = divideIntoSegments(mBounds, mMapWorldSize); + for (int x = 0; x < segments.first; ++x) { - // is this texture affected at all? - bool affected = false; - if (mx == 0 && my == 0) // the player is always in the center of the 3x3 grid - affected = true; - else + for (int y = 0; y < segments.second; ++y) { - bool affectsX = (mx > 0)? (u + exploreRadiusUV > 1) : (u - exploreRadiusUV < 0); - bool affectsY = (my > 0)? (v + exploreRadiusUV > 1) : (v - exploreRadiusUV < 0); - affected = (affectsX && (my == 0)) || (affectsY && mx == 0) || (affectsX && affectsY); - } + osg::Vec2f start = min + osg::Vec2f(mMapWorldSize * x, mMapWorldSize * y); + osg::Vec2f newcenter = start + osg::Vec2f(mMapWorldSize / 2.f, mMapWorldSize / 2.f); - if (!affected) - continue; + osg::Vec2f a = newcenter - center; + osg::Vec3f rotatedCenter = cameraOrient * (osg::Vec3f(a.x(), a.y(), 0)); - int texX = x + mx; - int texY = y + my*-1; + osg::Vec2f pos = osg::Vec2f(rotatedCenter.x(), rotatedCenter.y()) + center; - MapSegment& segment = mSegments[std::make_pair(texX, texY)]; + setupRenderToTexture(x, y, pos.x(), pos.y(), osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); - if (!segment.mFogOfWarImage || !segment.mMapTexture) - continue; - - uint32_t* data = (uint32_t*)segment.mFogOfWarImage->data(); - bool changed = false; - for (int texV = 0; texV> 24); - - alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); - uint32_t val = (uint32_t) (alpha << 24); - if ( *data != val) + bool loaded = false; + for (size_t index{}; index < segmentMappings.size(); index++) { - *data = val; - changed = true; + if (segmentMappings[index] == coords) + { + ESM::FogState* fog = cell->getFog(); + segment.loadFogOfWar(fog->mFogTextures[index]); + loaded = true; + break; + } } - - ++data; + if (!loaded) + segment.initFogOfWar(); } } + } + } - if (changed) + void LocalMap::worldToInteriorMapPosition(osg::Vec2f pos, float& nX, float& nY, int& x, int& y) + { + pos = rotatePoint(pos, osg::Vec2f(mBounds.center().x(), mBounds.center().y()), mAngle); + + osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); + + x = static_cast(std::ceil((pos.x() - min.x()) / mMapWorldSize) - 1); + y = static_cast(std::ceil((pos.y() - min.y()) / mMapWorldSize) - 1); + + nX = (pos.x() - min.x() - mMapWorldSize * x) / mMapWorldSize; + nY = 1.0f - (pos.y() - min.y() - mMapWorldSize * y) / mMapWorldSize; + } + + osg::Vec2f LocalMap::interiorMapToWorldPosition(float nX, float nY, int x, int y) + { + osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); + osg::Vec2f pos(mMapWorldSize * (nX + x) + min.x(), mMapWorldSize * (1.0f - nY + y) + min.y()); + + pos = rotatePoint(pos, osg::Vec2f(mBounds.center().x(), mBounds.center().y()), -mAngle); + return pos; + } + + bool LocalMap::isPositionExplored(float nX, float nY, int x, int y) + { + auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); + const MapSegment& segment = segments[std::make_pair(x, y)]; + if (!segment.mFogOfWarImage) + return false; + + nX = std::clamp(nX, 0.f, 1.f); + nY = std::clamp(nY, 0.f, 1.f); + + int texU = static_cast((sFogOfWarResolution - 1) * nX); + int texV = static_cast((sFogOfWarResolution - 1) * nY); + + const std::uint32_t clr + = reinterpret_cast(segment.mFogOfWarImage->data())[texV * sFogOfWarResolution + texU]; + uint8_t alpha = (clr >> 24); + return alpha < 200; + } + + osg::Group* LocalMap::getRoot() + { + return mRoot; + } + + void LocalMap::updatePlayer(const osg::Vec3f& position, const osg::Quat& orientation, float& u, float& v, int& x, + int& y, osg::Vec3f& direction) + { + // retrieve the x,y grid coordinates the player is in + osg::Vec2f pos(position.x(), position.y()); + + if (mInterior) + { + worldToInteriorMapPosition(pos, u, v, x, y); + + osg::Quat cameraOrient(mAngle, osg::Vec3(0, 0, -1)); + direction = orientation * cameraOrient.inverse() * osg::Vec3f(0, 1, 0); + } + else + { + direction = orientation * osg::Vec3f(0, 1, 0); + + x = static_cast(std::ceil(pos.x() / mMapWorldSize) - 1); + y = static_cast(std::ceil(pos.y() / mMapWorldSize) - 1); + + // convert from world coordinates to texture UV coordinates + u = std::abs((pos.x() - (mMapWorldSize * x)) / mMapWorldSize); + v = 1.0f - std::abs((pos.y() - (mMapWorldSize * y)) / mMapWorldSize); + } + + // explore radius (squared) + const float exploreRadius = 0.17f * (sFogOfWarResolution - 1); // explore radius from 0 to sFogOfWarResolution-1 + const float sqrExploreRadius = square(exploreRadius); + const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) + + // change the affected fog of war textures (in a 3x3 grid around the player) + for (int mx = -mCellDistance; mx <= mCellDistance; ++mx) + { + for (int my = -mCellDistance; my <= mCellDistance; ++my) { - segment.mHasFogState = true; - segment.mFogOfWarImage->dirty(); + // is this texture affected at all? + bool affected = false; + if (mx == 0 && my == 0) // the player is always in the center of the 3x3 grid + affected = true; + else + { + bool affectsX = (mx > 0) ? (u + exploreRadiusUV > 1) : (u - exploreRadiusUV < 0); + bool affectsY = (my > 0) ? (v + exploreRadiusUV > 1) : (v - exploreRadiusUV < 0); + affected = (affectsX && (my == 0)) || (affectsY && mx == 0) || (affectsX && affectsY); + } + + if (!affected) + continue; + + int texX = x + mx; + int texY = y + my * -1; + + auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); + MapSegment& segment = segments[std::make_pair(texX, texY)]; + + if (!segment.mFogOfWarImage || !segment.mMapTexture) + continue; + + std::uint32_t* data = reinterpret_cast(segment.mFogOfWarImage->data()); + bool changed = false; + for (int texV = 0; texV < sFogOfWarResolution; ++texV) + { + for (int texU = 0; texU < sFogOfWarResolution; ++texU) + { + float sqrDist = square((texU + mx * (sFogOfWarResolution - 1)) - u * (sFogOfWarResolution - 1)) + + square((texV + my * (sFogOfWarResolution - 1)) - v * (sFogOfWarResolution - 1)); + + const std::uint8_t alpha = std::min( + *data >> 24, std::clamp(sqrDist / sqrExploreRadius, 0.f, 1.f) * 255); + std::uint32_t val = static_cast(alpha << 24); + if (*data != val) + { + *data = val; + changed = true; + } + + ++data; + } + } + + if (changed) + { + segment.mHasFogState = true; + segment.mFogOfWarImage->dirty(); + } } } } -} -LocalMap::MapSegment::MapSegment() - : mHasFogState(false) -{ -} - -LocalMap::MapSegment::~MapSegment() -{ - -} - -void LocalMap::MapSegment::createFogOfWarTexture() -{ - if (mFogOfWarTexture) - return; - mFogOfWarTexture = new osg::Texture2D; - // TODO: synchronize access? for now, the worst that could happen is the draw thread jumping a frame ahead. - //mFogOfWarTexture->setDataVariance(osg::Object::DYNAMIC); - mFogOfWarTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mFogOfWarTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mFogOfWarTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mFogOfWarTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mFogOfWarTexture->setUnRefImageDataAfterApply(false); -} - -void LocalMap::MapSegment::initFogOfWar() -{ - mFogOfWarImage = new osg::Image; - // Assign a PixelBufferObject for asynchronous transfer of data to the GPU - mFogOfWarImage->setPixelBufferObject(new osg::PixelBufferObject); - mFogOfWarImage->allocateImage(sFogOfWarResolution, sFogOfWarResolution, 1, GL_RGBA, GL_UNSIGNED_BYTE); - assert(mFogOfWarImage->isDataContiguous()); - std::vector data; - data.resize(sFogOfWarResolution*sFogOfWarResolution, 0xff000000); - - memcpy(mFogOfWarImage->data(), &data[0], data.size()*4); - - createFogOfWarTexture(); - mFogOfWarTexture->setImage(mFogOfWarImage); -} - -void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) -{ - const std::vector& data = esm.mImageData; - if (data.empty()) + std::uint8_t LocalMap::getExteriorNeighbourFlags(int cellX, int cellY) const { - initFogOfWar(); - return; + constexpr std::tuple flags[] = { + { NeighbourCellTopLeft, -1, -1 }, + { NeighbourCellTopCenter, 0, -1 }, + { NeighbourCellTopRight, 1, -1 }, + { NeighbourCellMiddleLeft, -1, 0 }, + { NeighbourCellMiddleRight, 1, 0 }, + { NeighbourCellBottomLeft, -1, 1 }, + { NeighbourCellBottomCenter, 0, 1 }, + { NeighbourCellBottomRight, 1, 1 }, + }; + std::uint8_t result = 0; + for (const auto& [flag, dx, dy] : flags) + if (mExteriorSegments.contains(std::pair(cellX + dx, cellY + dy))) + result |= flag; + return result; } - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); - if (!readerwriter) + void LocalMap::MapSegment::createFogOfWarTexture() { - Log(Debug::Error) << "Error: Unable to load fog, can't find a png ReaderWriter" ; - return; + if (mFogOfWarTexture) + return; + mFogOfWarTexture = new osg::Texture2D; + // TODO: synchronize access? for now, the worst that could happen is the draw thread jumping a frame ahead. + // mFogOfWarTexture->setDataVariance(osg::Object::DYNAMIC); + mFogOfWarTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mFogOfWarTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mFogOfWarTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mFogOfWarTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mFogOfWarTexture->setUnRefImageDataAfterApply(false); + mFogOfWarTexture->setImage(mFogOfWarImage); } - Files::IMemStream in(&data[0], data.size()); - - osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(in); - if (!result.success()) + void LocalMap::MapSegment::initFogOfWar() { - Log(Debug::Error) << "Error: Failed to read fog: " << result.message() << " code " << result.status(); - return; + mFogOfWarImage = new osg::Image; + // Assign a PixelBufferObject for asynchronous transfer of data to the GPU + mFogOfWarImage->setPixelBufferObject(new osg::PixelBufferObject); + mFogOfWarImage->allocateImage(sFogOfWarResolution, sFogOfWarResolution, 1, GL_RGBA, GL_UNSIGNED_BYTE); + assert(mFogOfWarImage->isDataContiguous()); + std::vector data; + data.resize(sFogOfWarResolution * sFogOfWarResolution, 0xff000000); + + memcpy(mFogOfWarImage->data(), data.data(), data.size() * 4); + + createFogOfWarTexture(); } - mFogOfWarImage = result.getImage(); - mFogOfWarImage->flipVertical(); - mFogOfWarImage->dirty(); - - createFogOfWarTexture(); - mFogOfWarTexture->setImage(mFogOfWarImage); - mHasFogState = true; -} - -void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const -{ - if (!mFogOfWarImage) - return; - - std::ostringstream ostream; - - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); - if (!readerwriter) + void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture& esm) { - Log(Debug::Error) << "Error: Unable to write fog, can't find a png ReaderWriter"; - return; + const std::vector& data = esm.mImageData; + if (data.empty()) + { + initFogOfWar(); + return; + } + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!readerwriter) + { + Log(Debug::Error) << "Error: Unable to load fog, can't find a png ReaderWriter"; + return; + } + + Files::IMemStream in(data.data(), data.size()); + + osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(in); + if (!result.success()) + { + Log(Debug::Error) << "Error: Failed to read fog: " << result.message() << " code " << result.status(); + return; + } + + mFogOfWarImage = result.getImage(); + mFogOfWarImage->flipVertical(); + mFogOfWarImage->dirty(); + + createFogOfWarTexture(); + mHasFogState = true; } - // extra flips are unfortunate, but required for compatibility with older versions - mFogOfWarImage->flipVertical(); - osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mFogOfWarImage, ostream); - if (!result.success()) + void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture& fog) const { - Log(Debug::Error) << "Error: Unable to write fog: " << result.message() << " code " << result.status(); - return; - } - mFogOfWarImage->flipVertical(); + if (!mFogOfWarImage) + return; - std::string data = ostream.str(); - fog.mImageData = std::vector(data.begin(), data.end()); -} + std::ostringstream ostream; + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!readerwriter) + { + Log(Debug::Error) << "Error: Unable to write fog, can't find a png ReaderWriter"; + return; + } + + // extra flips are unfortunate, but required for compatibility with older versions + mFogOfWarImage->flipVertical(); + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mFogOfWarImage, ostream); + if (!result.success()) + { + Log(Debug::Error) << "Error: Unable to write fog: " << result.message() << " code " << result.status(); + return; + } + mFogOfWarImage->flipVertical(); + + std::string data = ostream.str(); + fog.mImageData = std::vector(data.begin(), data.end()); + } + + LocalMapRenderToTexture::LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, float x, float y, + const osg::Vec3d& upVector, float zmin, float zmax) + : RTTNode(res, res, 0, false, 0, StereoAwareness::Unaware_MultiViewShaders) + , mSceneRoot(sceneRoot) + , mActive(true) + { + setNodeMask(Mask_RenderToTexture); + + if (SceneUtil::AutoDepth::isReversed()) + mProjectionMatrix = SceneUtil::getReversedZProjectionMatrixAsOrtho( + -mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10); + else + mProjectionMatrix.makeOrtho( + -mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10); + + mViewMatrix.makeLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); + + setUpdateCallback(new CameraLocalUpdateCallback); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); + } + + void LocalMapRenderToTexture::setDefaults(osg::Camera* camera) + { + // Disable small feature culling, it's not going to be reliable for this camera + osg::Camera::CullingMode cullingMode + = (osg::Camera::DEFAULT_CULLING | osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING); + camera->setCullingMode(cullingMode); + + SceneUtil::setCameraClearDepth(camera); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + + camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setCullMaskLeft(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setCullMaskRight(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setNodeMask(Mask_RenderToTexture); + camera->setProjectionMatrix(mProjectionMatrix); + camera->setViewMatrix(mViewMatrix); + + auto* stateset = camera->getOrCreateStateSet(); + + stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), + osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(mProjectionMatrix)), + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + if (Stereo::getMultiview()) + Stereo::setMultiviewMatrices(stateset, { mProjectionMatrix, mProjectionMatrix }); + + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog(new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + + // turn of sky blending + stateset->addUniform(new osg::Uniform("far", 10000000.0f)); + stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); + stateset->addUniform(new osg::Uniform("sky", 0)); + stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 })); + + osg::ref_ptr lightmodel = new osg::LightModel; + lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); + stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + osg::ref_ptr light = new osg::Light; + light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f)); + light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f)); + light->setAmbient(osg::Vec4(0, 0, 0, 1)); + light->setSpecular(osg::Vec4(0, 0, 0, 0)); + light->setLightNum(0); + light->setConstantAttenuation(1.f); + light->setLinearAttenuation(0.f); + light->setQuadraticAttenuation(0.f); + + osg::ref_ptr lightSource = new osg::LightSource; + lightSource->setLight(light); + + lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + + // override sun for local map + SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); + + camera->addChild(lightSource); + camera->addChild(mSceneRoot); + } + + void CameraLocalUpdateCallback::operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv) + { + if (!node->mActive) + node->setNodeMask(0); + + node->mActive = false; + + // Rtt-nodes do not forward update traversal to their cameras so we can traverse safely. + // Traverse in case there are nested callbacks. + traverse(node, nv); + } } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 83a975aed..9fd101c45 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -1,9 +1,10 @@ #ifndef GAME_RENDER_LOCALMAP_H #define GAME_RENDER_LOCALMAP_H +#include +#include #include #include -#include #include #include @@ -30,6 +31,8 @@ namespace osg namespace MWRender { + class LocalMapRenderToTexture; + /// /// \brief Local map rendering /// @@ -45,24 +48,19 @@ namespace MWRender void clear(); /** - * Request a map render for the given cell. Render textures will be immediately created and can be retrieved with the getMapTexture function. + * Request a map render for the given cell. Render textures will be immediately created and can be retrieved + * with the getMapTexture function. */ - void requestMap (const MWWorld::CellStore* cell); + void requestMap(const MWWorld::CellStore* cell); void addCell(MWWorld::CellStore* cell); + void removeExteriorCell(int x, int y); - void removeCell (MWWorld::CellStore* cell); + void removeCell(MWWorld::CellStore* cell); - osg::ref_ptr getMapTexture (int x, int y); + osg::ref_ptr getMapTexture(int x, int y); - osg::ref_ptr getFogOfWarTexture (int x, int y); - - void removeCamera(osg::Camera* cam); - - /** - * Indicates a camera has been queued for rendering and can be cleaned up in the next frame. For internal use only. - */ - void markForRemoval(osg::Camera* cam); + osg::ref_ptr getFogOfWarTexture(int x, int y); /** * Removes cameras that have already been rendered. Should be called every frame to ensure that @@ -72,12 +70,13 @@ namespace MWRender void cleanupCameras(); /** - * Set the position & direction of the player, and returns the position in map space through the reference parameters. + * Set the position & direction of the player, and returns the position in map space through the reference + * parameters. * @remarks This is used to draw a "fog of war" effect * to hide areas on the map the player has not discovered yet. */ - void updatePlayer (const osg::Vec3f& position, const osg::Quat& orientation, - float& u, float& v, int& x, int& y, osg::Vec3f& direction); + void updatePlayer(const osg::Vec3f& position, const osg::Quat& orientation, float& u, float& v, int& x, int& y, + osg::Vec3f& direction); /** * Save the fog of war for this cell to its CellStore. @@ -88,14 +87,14 @@ namespace MWRender /** * Get the interior map texture index and normalized position on this texture, given a world position */ - void worldToInteriorMapPosition (osg::Vec2f pos, float& nX, float& nY, int& x, int& y); + void worldToInteriorMapPosition(osg::Vec2f pos, float& nX, float& nY, int& x, int& y); - osg::Vec2f interiorMapToWorldPosition (float nX, float nY, int x, int y); + osg::Vec2f interiorMapToWorldPosition(float nX, float nY, int x, int y); /** * Check if a given position is explored by the player (i.e. not obscured by fog of war) */ - bool isPositionExplored (float nX, float nY, int x, int y); + bool isPositionExplored(float nX, float nY, int x, int y); osg::Group* getRoot(); @@ -103,36 +102,41 @@ namespace MWRender osg::ref_ptr mRoot; osg::ref_ptr mSceneRoot; - typedef std::vector< osg::ref_ptr > CameraVector; + typedef std::vector> RTTVector; + RTTVector mLocalMapRTTs; - CameraVector mActiveCameras; - - CameraVector mCamerasPendingRemoval; - - typedef std::set > Grid; + typedef std::set> Grid; Grid mCurrentGrid; + enum NeighbourCellFlag : std::uint8_t + { + NeighbourCellTopLeft = 1, + NeighbourCellTopCenter = 1 << 1, + NeighbourCellTopRight = 1 << 2, + NeighbourCellMiddleLeft = 1 << 3, + NeighbourCellMiddleRight = 1 << 4, + NeighbourCellBottomLeft = 1 << 5, + NeighbourCellBottomCenter = 1 << 6, + NeighbourCellBottomRight = 1 << 7, + }; + struct MapSegment { - MapSegment(); - ~MapSegment(); - void initFogOfWar(); void loadFogOfWar(const ESM::FogTexture& fog); void saveFogOfWar(ESM::FogTexture& fog) const; void createFogOfWarTexture(); + std::uint8_t mLastRenderNeighbourFlags = 0; + bool mHasFogState = false; osg::ref_ptr mMapTexture; osg::ref_ptr mFogOfWarTexture; osg::ref_ptr mFogOfWarImage; - - Grid mGrid; // the grid that was active at the time of rendering this segment - - bool mHasFogState; }; typedef std::map, MapSegment> SegmentMap; - SegmentMap mSegments; + SegmentMap mExteriorSegments; + SegmentMap mInteriorSegments; int mMapResolution; @@ -147,14 +151,16 @@ namespace MWRender float mAngle; const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle); - void requestExteriorMap(const MWWorld::CellStore* cell); + void requestExteriorMap(const MWWorld::CellStore* cell, MapSegment& segment); void requestInteriorMap(const MWWorld::CellStore* cell); - osg::ref_ptr createOrthographicCamera(float left, float top, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax); - void setupRenderToTexture(osg::ref_ptr camera, int x, int y); + void setupRenderToTexture( + int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax); bool mInterior; osg::BoundingBox mBounds; + + std::uint8_t getExteriorNeighbourFlags(int cellX, int cellY) const; }; } diff --git a/apps/openmw/mwrender/luminancecalculator.cpp b/apps/openmw/mwrender/luminancecalculator.cpp new file mode 100644 index 000000000..e0d275b17 --- /dev/null +++ b/apps/openmw/mwrender/luminancecalculator.cpp @@ -0,0 +1,153 @@ +#include "luminancecalculator.hpp" + +#include +#include +#include + +#include "pingpongcanvas.hpp" + +namespace MWRender +{ + LuminanceCalculator::LuminanceCalculator(Shader::ShaderManager& shaderManager) + { + const float hdrExposureTime + = std::max(Settings::Manager::getFloat("auto exposure speed", "Post Processing"), 0.0001f); + + Shader::ShaderManager::DefineMap defines = { + { "hdrExposureTime", std::to_string(hdrExposureTime) }, + }; + + auto vertex = shaderManager.getShader("fullscreen_tri.vert", {}); + auto luminanceFragment = shaderManager.getShader("luminance/luminance.frag", defines); + auto resolveFragment = shaderManager.getShader("luminance/resolve.frag", defines); + + mResolveProgram = shaderManager.getProgram(vertex, resolveFragment); + mLuminanceProgram = shaderManager.getProgram(vertex, luminanceFragment); + } + + void LuminanceCalculator::compile() + { + int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); + + for (auto& buffer : mBuffers) + { + buffer.mipmappedSceneLuminanceTex = new osg::Texture2D; + buffer.mipmappedSceneLuminanceTex->setInternalFormat(GL_R16F); + buffer.mipmappedSceneLuminanceTex->setSourceFormat(GL_RED); + buffer.mipmappedSceneLuminanceTex->setSourceType(GL_FLOAT); + buffer.mipmappedSceneLuminanceTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + buffer.mipmappedSceneLuminanceTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + buffer.mipmappedSceneLuminanceTex->setFilter( + osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST); + buffer.mipmappedSceneLuminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR); + buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight); + buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels); + + buffer.luminanceTex = new osg::Texture2D; + buffer.luminanceTex->setInternalFormat(GL_R16F); + buffer.luminanceTex->setSourceFormat(GL_RED); + buffer.luminanceTex->setSourceType(GL_FLOAT); + buffer.luminanceTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + buffer.luminanceTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + buffer.luminanceTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); + buffer.luminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); + buffer.luminanceTex->setTextureSize(1, 1); + + buffer.luminanceProxyTex = new osg::Texture2D(*buffer.luminanceTex); + buffer.luminanceProxyTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + buffer.luminanceProxyTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + buffer.resolveFbo = new osg::FrameBufferObject; + buffer.resolveFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.luminanceTex)); + + buffer.luminanceProxyFbo = new osg::FrameBufferObject; + buffer.luminanceProxyFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.luminanceProxyTex)); + + buffer.resolveSceneLumFbo = new osg::FrameBufferObject; + buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1)); + + buffer.sceneLumFbo = new osg::FrameBufferObject; + buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex)); + + buffer.sceneLumSS = new osg::StateSet; + buffer.sceneLumSS->setAttributeAndModes(mLuminanceProgram); + buffer.sceneLumSS->addUniform(new osg::Uniform("sceneTex", 0)); + buffer.sceneLumSS->addUniform(new osg::Uniform("scaling", mScale)); + + buffer.resolveSS = new osg::StateSet; + buffer.resolveSS->setAttributeAndModes(mResolveProgram); + buffer.resolveSS->setTextureAttributeAndModes(0, buffer.luminanceProxyTex); + buffer.resolveSS->addUniform(new osg::Uniform("luminanceSceneTex", 0)); + buffer.resolveSS->addUniform(new osg::Uniform("prevLuminanceSceneTex", 1)); + } + + mBuffers[0].resolveSS->setTextureAttributeAndModes(1, mBuffers[1].luminanceTex); + mBuffers[1].resolveSS->setTextureAttributeAndModes(1, mBuffers[0].luminanceTex); + + mCompiled = true; + } + + void LuminanceCalculator::draw(const PingPongCanvas& canvas, osg::RenderInfo& renderInfo, osg::State& state, + osg::GLExtensions* ext, size_t frameId) + { + if (!mEnabled) + return; + + bool dirty = !mCompiled; + + if (dirty) + compile(); + + auto& buffer = mBuffers[frameId]; + buffer.sceneLumFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + buffer.sceneLumSS->setTextureAttributeAndModes(0, canvas.getSceneTexture(frameId)); + buffer.sceneLumSS->getUniform("scaling")->set(mScale); + + state.apply(buffer.sceneLumSS); + canvas.drawGeometry(renderInfo); + + state.applyTextureAttribute(0, buffer.mipmappedSceneLuminanceTex); + ext->glGenerateMipmap(GL_TEXTURE_2D); + + buffer.resolveSceneLumFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); + buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + if (dirty) + { + // Use current frame data for previous frame to warm up calculations and prevent popin + mBuffers[(frameId + 1) % 2].resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + } + + buffer.resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.apply(buffer.resolveSS); + canvas.drawGeometry(renderInfo); + + ext->glBindFramebuffer( + GL_FRAMEBUFFER_EXT, state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0); + } + + osg::ref_ptr LuminanceCalculator::getLuminanceTexture(size_t frameId) const + { + return mBuffers[frameId].luminanceTex; + } + + void LuminanceCalculator::dirty(int w, int h) + { + constexpr int minSize = 64; + + mWidth = std::max(minSize, Misc::nextPowerOfTwo(w) / 2); + mHeight = std::max(minSize, Misc::nextPowerOfTwo(h) / 2); + + mScale = osg::Vec2f(w / static_cast(mWidth), h / static_cast(mHeight)); + + mCompiled = false; + } +} \ No newline at end of file diff --git a/apps/openmw/mwrender/luminancecalculator.hpp b/apps/openmw/mwrender/luminancecalculator.hpp new file mode 100644 index 000000000..71ea2f797 --- /dev/null +++ b/apps/openmw/mwrender/luminancecalculator.hpp @@ -0,0 +1,68 @@ +#ifndef OPENMW_MWRENDER_LUMINANCECALCULATOR_H +#define OPENMW_MWRENDER_LUMINANCECALCULATOR_H + +#include + +#include +#include +#include + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class PingPongCanvas; + + class LuminanceCalculator + { + + public: + LuminanceCalculator() = default; + + LuminanceCalculator(Shader::ShaderManager& shaderManager); + + void draw(const PingPongCanvas& canvas, osg::RenderInfo& renderInfo, osg::State& state, osg::GLExtensions* ext, + size_t frameId); + + bool isEnabled() const { return mEnabled; } + + void enable() { mEnabled = true; } + void disable() { mEnabled = false; } + + void dirty(int w, int h); + + osg::ref_ptr getLuminanceTexture(size_t frameId) const; + + private: + void compile(); + + struct Container + { + osg::ref_ptr sceneLumFbo; + osg::ref_ptr resolveSceneLumFbo; + osg::ref_ptr resolveFbo; + osg::ref_ptr luminanceProxyFbo; + osg::ref_ptr mipmappedSceneLuminanceTex; + osg::ref_ptr luminanceTex; + osg::ref_ptr luminanceProxyTex; + osg::ref_ptr sceneLumSS; + osg::ref_ptr resolveSS; + }; + + std::array mBuffers; + osg::ref_ptr mLuminanceProgram; + osg::ref_ptr mResolveProgram; + + bool mCompiled = false; + bool mEnabled = false; + + int mWidth = 1; + int mHeight = 1; + osg::Vec2f mScale = osg::Vec2f(1, 1); + }; +} + +#endif diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 791c41a1a..bb4a2aa23 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -1,17 +1,176 @@ #include "navmesh.hpp" #include "vismask.hpp" +#include +#include +#include +#include +#include +#include #include +#include #include +#include + +#include + +#include "../mwbase/environment.hpp" + +#include +#include namespace MWRender { - NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) + struct NavMesh::LessByTilePosition + { + bool operator()(const DetourNavigator::TilePosition& lhs, + const std::pair& rhs) const + { + return lhs < rhs.first; + } + + bool operator()(const std::pair& lhs, + const DetourNavigator::TilePosition& rhs) const + { + return lhs.first < rhs; + } + }; + + struct NavMesh::CreateNavMeshTileGroups final : SceneUtil::WorkItem + { + std::size_t mId; + DetourNavigator::Version mVersion; + const std::weak_ptr mNavMesh; + const osg::ref_ptr mGroupStateSet; + const osg::ref_ptr mDebugDrawStateSet; + const DetourNavigator::Settings mSettings; + std::map mTiles; + NavMeshMode mMode; + std::atomic_bool mAborted{ false }; + std::mutex mMutex; + bool mStarted = false; + std::vector> mUpdatedTiles; + std::vector mRemovedTiles; + + explicit CreateNavMeshTileGroups(std::size_t id, DetourNavigator::Version version, + std::weak_ptr navMesh, + const osg::ref_ptr& groupStateSet, const osg::ref_ptr& debugDrawStateSet, + const DetourNavigator::Settings& settings, const std::map& tiles, + NavMeshMode mode) + : mId(id) + , mVersion(version) + , mNavMesh(navMesh) + , mGroupStateSet(groupStateSet) + , mDebugDrawStateSet(debugDrawStateSet) + , mSettings(settings) + , mTiles(tiles) + , mMode(mode) + { + } + + void doWork() final + { + using DetourNavigator::TilePosition; + using DetourNavigator::Version; + + const std::lock_guard lock(mMutex); + mStarted = true; + + if (mAborted.load(std::memory_order_acquire)) + return; + + const auto navMeshPtr = mNavMesh.lock(); + if (navMeshPtr == nullptr) + return; + + std::vector> existingTiles; + unsigned minSalt = std::numeric_limits::max(); + unsigned maxSalt = 0; + + navMeshPtr->lockConst()->forEachUsedTile( + [&](const TilePosition& position, const Version& version, const dtMeshTile& meshTile) { + existingTiles.emplace_back(position, version); + minSalt = std::min(minSalt, meshTile.salt); + maxSalt = std::max(maxSalt, meshTile.salt); + }); + + if (mAborted.load(std::memory_order_acquire)) + return; + + std::sort(existingTiles.begin(), existingTiles.end()); + + std::vector removedTiles; + + for (const auto& [position, tile] : mTiles) + if (!std::binary_search(existingTiles.begin(), existingTiles.end(), position, LessByTilePosition{})) + removedTiles.push_back(position); + + std::vector> updatedTiles; + + const unsigned char flags = SceneUtil::NavMeshTileDrawFlagsOffMeshConnections + | SceneUtil::NavMeshTileDrawFlagsClosedList + | (mMode == NavMeshMode::UpdateFrequency ? SceneUtil::NavMeshTileDrawFlagsHeat : 0); + + for (const auto& [position, version] : existingTiles) + { + const auto it = mTiles.find(position); + if (it != mTiles.end() && it->second.mGroup != nullptr && it->second.mVersion == version + && mMode != NavMeshMode::UpdateFrequency) + continue; + + osg::ref_ptr group; + { + const auto navMesh = navMeshPtr->lockConst(); + const dtMeshTile* meshTile = DetourNavigator::getTile(navMesh->getImpl(), position); + if (meshTile == nullptr) + continue; + + if (mAborted.load(std::memory_order_acquire)) + return; + + group = SceneUtil::createNavMeshTileGroup(navMesh->getImpl(), *meshTile, mSettings, mGroupStateSet, + mDebugDrawStateSet, flags, minSalt, maxSalt); + } + if (group == nullptr) + { + removedTiles.push_back(position); + continue; + } + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); + group->setNodeMask(Mask_Debug); + updatedTiles.emplace_back(position, Tile{ version, std::move(group) }); + } + + if (mAborted.load(std::memory_order_acquire)) + return; + + mUpdatedTiles = std::move(updatedTiles); + mRemovedTiles = std::move(removedTiles); + } + + void abort() final { mAborted.store(true, std::memory_order_release); } + }; + + struct NavMesh::DeallocateCreateNavMeshTileGroups final : SceneUtil::WorkItem + { + osg::ref_ptr mWorkItem; + + explicit DeallocateCreateNavMeshTileGroups(osg::ref_ptr&& workItem) + : mWorkItem(std::move(workItem)) + { + } + }; + + NavMesh::NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, + bool enabled, NavMeshMode mode) : mRootNode(root) + , mWorkQueue(workQueue) + , mGroupStateSet(SceneUtil::makeNavMeshTileStateSet()) + , mDebugDrawStateSet(SceneUtil::DebugDraw::makeStateSet()) , mEnabled(enabled) - , mGeneration(0) - , mRevision(0) + , mMode(mode) + , mId(std::numeric_limits::max()) { } @@ -19,6 +178,8 @@ namespace MWRender { if (mEnabled) disable(); + for (const auto& workItem : mWorkItems) + workItem->abort(); } bool NavMesh::toggle() @@ -31,45 +192,129 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id, - const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) + void NavMesh::update(const std::shared_ptr& navMesh, std::size_t id, + const DetourNavigator::Settings& settings) { - if (!mEnabled || (mGroup && mId == id && mGeneration == generation && mRevision == revision)) + using DetourNavigator::TilePosition; + using DetourNavigator::Version; + + if (!mEnabled) return; - mId = id; - mGeneration = generation; - mRevision = revision; - if (mGroup) - mRootNode->removeChild(mGroup); - mGroup = SceneUtil::createNavMeshGroup(navMesh, settings); - if (mGroup) { - mGroup->setNodeMask(Mask_Debug); - mRootNode->addChild(mGroup); + std::pair lastest{ 0, Version{} }; + osg::ref_ptr latestCandidate; + for (auto it = mWorkItems.begin(); it != mWorkItems.end();) + { + if (!(*it)->isDone()) + { + ++it; + continue; + } + const std::pair order{ (*it)->mId, (*it)->mVersion }; + if (lastest < order) + { + lastest = order; + std::swap(latestCandidate, *it); + } + if (*it != nullptr) + mWorkQueue->addWorkItem(new DeallocateCreateNavMeshTileGroups(std::move(*it))); + it = mWorkItems.erase(it); + } + + if (latestCandidate != nullptr) + { + for (const TilePosition& position : latestCandidate->mRemovedTiles) + { + const auto it = mTiles.find(position); + if (it == mTiles.end()) + continue; + mRootNode->removeChild(it->second.mGroup); + mTiles.erase(it); + } + + for (auto& [position, tile] : latestCandidate->mUpdatedTiles) + { + const auto it = mTiles.find(position); + if (it == mTiles.end()) + { + mRootNode->addChild(tile.mGroup); + mTiles.emplace_hint(it, position, std::move(tile)); + } + else + { + mRootNode->replaceChild(it->second.mGroup, tile.mGroup); + std::swap(it->second, tile); + } + } + + mWorkQueue->addWorkItem(new DeallocateCreateNavMeshTileGroups(std::move(latestCandidate))); + } } + + const auto version = navMesh->lock()->getVersion(); + + if (!mTiles.empty() && mId == id && mVersion == version) + return; + + if (mId != id) + { + reset(); + mId = id; + } + + mVersion = version; + + for (auto& workItem : mWorkItems) + { + const std::unique_lock lock(workItem->mMutex, std::try_to_lock); + + if (!lock.owns_lock()) + continue; + + if (workItem->mStarted) + continue; + + workItem->mId = id; + workItem->mVersion = version; + workItem->mTiles = mTiles; + workItem->mMode = mMode; + + return; + } + + osg::ref_ptr workItem = new CreateNavMeshTileGroups( + id, version, navMesh, mGroupStateSet, mDebugDrawStateSet, settings, mTiles, mMode); + mWorkQueue->addWorkItem(workItem); + mWorkItems.push_back(std::move(workItem)); } void NavMesh::reset() { - if (mGroup) - { - mRootNode->removeChild(mGroup); - mGroup = nullptr; - } + for (auto& workItem : mWorkItems) + workItem->abort(); + mWorkItems.clear(); + for (auto& [position, tile] : mTiles) + mRootNode->removeChild(tile.mGroup); + mTiles.clear(); } void NavMesh::enable() { - if (mGroup) - mRootNode->addChild(mGroup); mEnabled = true; } void NavMesh::disable() { - if (mGroup) - mRootNode->removeChild(mGroup); + reset(); mEnabled = false; } + + void NavMesh::setMode(NavMeshMode value) + { + if (mMode == value) + return; + reset(); + mMode = value; + } } diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index d329b895d..4b4e50f79 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -1,14 +1,38 @@ #ifndef OPENMW_MWRENDER_NAVMESH_H #define OPENMW_MWRENDER_NAVMESH_H -#include +#include "navmeshmode.hpp" + +#include +#include +#include #include +#include +#include +#include +#include +#include + +class dtNavMesh; + namespace osg { class Group; class Geometry; + class StateSet; +} + +namespace DetourNavigator +{ + class NavMeshCacheItem; + struct Settings; +} + +namespace SceneUtil +{ + class WorkQueue; } namespace MWRender @@ -16,13 +40,14 @@ namespace MWRender class NavMesh { public: - NavMesh(const osg::ref_ptr& root, bool enabled); + explicit NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, + bool enabled, NavMeshMode mode); ~NavMesh(); bool toggle(); - void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation, - const std::size_t revision, const DetourNavigator::Settings& settings); + void update(const std::shared_ptr>& navMesh, + std::size_t id, const DetourNavigator::Settings& settings); void reset(); @@ -30,18 +55,31 @@ namespace MWRender void disable(); - bool isEnabled() const - { - return mEnabled; - } + bool isEnabled() const { return mEnabled; } + + void setMode(NavMeshMode value); private: + struct Tile + { + DetourNavigator::Version mVersion; + osg::ref_ptr mGroup; + }; + + struct LessByTilePosition; + struct CreateNavMeshTileGroups; + struct DeallocateCreateNavMeshTileGroups; + osg::ref_ptr mRootNode; + osg::ref_ptr mWorkQueue; + osg::ref_ptr mGroupStateSet; + osg::ref_ptr mDebugDrawStateSet; bool mEnabled; - std::size_t mId = std::numeric_limits::max(); - std::size_t mGeneration; - std::size_t mRevision; - osg::ref_ptr mGroup; + NavMeshMode mMode; + std::size_t mId; + DetourNavigator::Version mVersion; + std::map mTiles; + std::vector> mWorkItems; }; } diff --git a/apps/openmw/mwrender/navmeshmode.cpp b/apps/openmw/mwrender/navmeshmode.cpp new file mode 100644 index 000000000..d08e7cf69 --- /dev/null +++ b/apps/openmw/mwrender/navmeshmode.cpp @@ -0,0 +1,16 @@ +#include "navmeshmode.hpp" + +#include +#include + +namespace MWRender +{ + NavMeshMode parseNavMeshMode(std::string_view value) + { + if (value == "area type") + return NavMeshMode::AreaType; + if (value == "update frequency") + return NavMeshMode::UpdateFrequency; + throw std::logic_error("Unsupported navigation mesh rendering mode: " + std::string(value)); + } +} diff --git a/apps/openmw/mwrender/navmeshmode.hpp b/apps/openmw/mwrender/navmeshmode.hpp new file mode 100644 index 000000000..9401479e2 --- /dev/null +++ b/apps/openmw/mwrender/navmeshmode.hpp @@ -0,0 +1,17 @@ +#ifndef OPENMW_MWRENDER_NAVMESHMODE_H +#define OPENMW_MWRENDER_NAVMESHMODE_H + +#include + +namespace MWRender +{ + enum class NavMeshMode + { + AreaType, + UpdateFrequency, + }; + + NavMeshMode parseNavMeshMode(std::string_view value); +} + +#endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b538f0b7b..99f21bc9a 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1,11 +1,11 @@ #include "npcanimation.hpp" -#include -#include #include +#include +#include -#include #include +#include #include @@ -13,1321 +13,1294 @@ #include +#include +#include +#include #include #include #include -#include -#include -#include +#include #include - -#include +#include +#include +#include #include +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/world.hpp" -#include "camera.hpp" -#include "rotatecontroller.hpp" +#include "postprocessor.hpp" #include "renderbin.hpp" +#include "rotatecontroller.hpp" #include "vismask.hpp" namespace { -std::string getVampireHead(const std::string& race, bool female) -{ - static std::map , const ESM::BodyPart* > sVampireMapping; - - std::pair thisCombination = std::make_pair(race, int(female)); - - if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) + std::string getVampireHead(const ESM::RefId& race, bool female, const VFS::Manager& vfs) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - for (const ESM::BodyPart& bodypart : store.get()) + static std::map, const ESM::BodyPart*> sVampireMapping; + + std::pair thisCombination = std::make_pair(race, int(female)); + + if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) { - if (!bodypart.mData.mVampire) - continue; - if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) - continue; - if (bodypart.mData.mPart != ESM::BodyPart::MP_Head) - continue; - if (female != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) - continue; - if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) - continue; - sVampireMapping[thisCombination] = &bodypart; + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + for (const ESM::BodyPart& bodypart : store.get()) + { + if (!bodypart.mData.mVampire) + continue; + if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) + continue; + if (bodypart.mData.mPart != ESM::BodyPart::MP_Head) + continue; + if (female != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + continue; + if (!(bodypart.mRace == race)) + continue; + sVampireMapping[thisCombination] = &bodypart; + } } + + sVampireMapping.emplace(thisCombination, nullptr); + + const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; + if (!bodyPart) + return std::string(); + return Misc::ResourceHelpers::correctMeshPath(bodyPart->mModel, &vfs); } - sVampireMapping.emplace(thisCombination, nullptr); - - const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; - if (!bodyPart) - return std::string(); - return "meshes\\" + bodyPart->mModel; } -std::string getShieldBodypartMesh(const std::vector& bodyparts, bool female) -{ - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - for (const auto& part : bodyparts) - { - if (part.mPart != ESM::PRT_Shield) - continue; - - std::string bodypartName; - if (female && !part.mFemale.empty()) - bodypartName = part.mFemale; - else if (!part.mMale.empty()) - bodypartName = part.mMale; - - if (!bodypartName.empty()) - { - const ESM::BodyPart *bodypart = partStore.search(bodypartName); - if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) - return std::string(); - if (!bodypart->mModel.empty()) - return "meshes\\" + bodypart->mModel; - } - } - - return std::string(); -} - -} - - namespace MWRender { - -class HeadAnimationTime : public SceneUtil::ControllerSource -{ -private: - MWWorld::Ptr mReference; - float mTalkStart; - float mTalkStop; - float mBlinkStart; - float mBlinkStop; - - float mBlinkTimer; - - bool mEnabled; - - float mValue; -private: - void resetBlinkTimer(); -public: - HeadAnimationTime(const MWWorld::Ptr& reference); - - void updatePtr(const MWWorld::Ptr& updated); - - void update(float dt); - - void setEnabled(bool enabled); - - void setTalkStart(float value); - void setTalkStop(float value); - void setBlinkStart(float value); - void setBlinkStop(float value); - - float getValue(osg::NodeVisitor* nv) override; -}; - -// -------------------------------------------------------------------------------- - -/// Subclass RotateController to add a Z-offset for sneaking in first person mode. -/// @note We use inheritance instead of adding another controller, so that we do not have to compute the worldOrient twice. -/// @note Must be set on a MatrixTransform. -class NeckController : public RotateController -{ -public: - NeckController(osg::Node* relativeTo) - : RotateController(relativeTo) + class HeadAnimationTime : public SceneUtil::ControllerSource { - } + private: + MWWorld::Ptr mReference; + float mTalkStart; + float mTalkStop; + float mBlinkStart; + float mBlinkStop; - void setOffset(const osg::Vec3f& offset) - { - mOffset = offset; - } + float mBlinkTimer; - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix matrix = transform->getMatrix(); + bool mEnabled; - osg::Quat worldOrient = getWorldOrientation(node); - osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); + float mValue; - matrix.setRotate(orient); - matrix.setTrans(matrix.getTrans() + worldOrient.inverse() * mOffset); + private: + void resetBlinkTimer(); - transform->setMatrix(matrix); + public: + HeadAnimationTime(const MWWorld::Ptr& reference); - traverse(node,nv); - } + void updatePtr(const MWWorld::Ptr& updated); -private: - osg::Vec3f mOffset; -}; + void update(float dt); -// -------------------------------------------------------------------------------------------------------------- + void setEnabled(bool enabled); -HeadAnimationTime::HeadAnimationTime(const MWWorld::Ptr& reference) - : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mEnabled(true), mValue(0) -{ - resetBlinkTimer(); -} + void setTalkStart(float value); + void setTalkStop(float value); + void setBlinkStart(float value); + void setBlinkStop(float value); -void HeadAnimationTime::updatePtr(const MWWorld::Ptr &updated) -{ - mReference = updated; -} - -void HeadAnimationTime::setEnabled(bool enabled) -{ - mEnabled = enabled; -} - -void HeadAnimationTime::resetBlinkTimer() -{ - mBlinkTimer = -(2.0f + Misc::Rng::rollDice(6)); -} - -void HeadAnimationTime::update(float dt) -{ - if (!mEnabled) - return; - - if (!MWBase::Environment::get().getSoundManager()->sayActive(mReference)) - { - mBlinkTimer += dt; - - float duration = mBlinkStop - mBlinkStart; - - if (mBlinkTimer >= 0 && mBlinkTimer <= duration) - { - mValue = mBlinkStart + mBlinkTimer; - } - else - mValue = mBlinkStop; - - if (mBlinkTimer > duration) - resetBlinkTimer(); - } - else - { - // FIXME: would be nice to hold on to the SoundPtr so we don't have to retrieve it every frame - mValue = mTalkStart + - (mTalkStop - mTalkStart) * - std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*2); // Rescale a bit (most voices are not very loud) - } -} - -float HeadAnimationTime::getValue(osg::NodeVisitor*) -{ - return mValue; -} - -void HeadAnimationTime::setTalkStart(float value) -{ - mTalkStart = value; -} - -void HeadAnimationTime::setTalkStop(float value) -{ - mTalkStop = value; -} - -void HeadAnimationTime::setBlinkStart(float value) -{ - mBlinkStart = value; -} - -void HeadAnimationTime::setBlinkStop(float value) -{ - mBlinkStop = value; -} - -// ---------------------------------------------------- - -NpcAnimation::NpcType NpcAnimation::getNpcType() const -{ - const MWWorld::Class &cls = mPtr.getClass(); - // Dead vampires should typically stay vampires. - if (mNpcType == Type_Vampire && cls.getNpcStats(mPtr).isDead() && !cls.getNpcStats(mPtr).isWerewolf()) - return mNpcType; - return getNpcType(mPtr); -} - -NpcAnimation::NpcType NpcAnimation::getNpcType(const MWWorld::Ptr& ptr) -{ - const MWWorld::Class &cls = ptr.getClass(); - NpcAnimation::NpcType curType = Type_Normal; - if (cls.getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) - curType = Type_Vampire; - if (cls.getNpcStats(ptr).isWerewolf()) - curType = Type_Werewolf; - - return curType; -} - -static NpcAnimation::PartBoneMap createPartListMap() -{ - NpcAnimation::PartBoneMap result; - result.insert(std::make_pair(ESM::PRT_Head, "Head")); - result.insert(std::make_pair(ESM::PRT_Hair, "Head")); // note it uses "Head" as attach bone, but "Hair" as filter - result.insert(std::make_pair(ESM::PRT_Neck, "Neck")); - result.insert(std::make_pair(ESM::PRT_Cuirass, "Chest")); - result.insert(std::make_pair(ESM::PRT_Groin, "Groin")); - result.insert(std::make_pair(ESM::PRT_Skirt, "Groin")); - result.insert(std::make_pair(ESM::PRT_RHand, "Right Hand")); - result.insert(std::make_pair(ESM::PRT_LHand, "Left Hand")); - result.insert(std::make_pair(ESM::PRT_RWrist, "Right Wrist")); - result.insert(std::make_pair(ESM::PRT_LWrist, "Left Wrist")); - result.insert(std::make_pair(ESM::PRT_Shield, "Shield Bone")); - result.insert(std::make_pair(ESM::PRT_RForearm, "Right Forearm")); - result.insert(std::make_pair(ESM::PRT_LForearm, "Left Forearm")); - result.insert(std::make_pair(ESM::PRT_RUpperarm, "Right Upper Arm")); - result.insert(std::make_pair(ESM::PRT_LUpperarm, "Left Upper Arm")); - result.insert(std::make_pair(ESM::PRT_RFoot, "Right Foot")); - result.insert(std::make_pair(ESM::PRT_LFoot, "Left Foot")); - result.insert(std::make_pair(ESM::PRT_RAnkle, "Right Ankle")); - result.insert(std::make_pair(ESM::PRT_LAnkle, "Left Ankle")); - result.insert(std::make_pair(ESM::PRT_RKnee, "Right Knee")); - result.insert(std::make_pair(ESM::PRT_LKnee, "Left Knee")); - result.insert(std::make_pair(ESM::PRT_RLeg, "Right Upper Leg")); - result.insert(std::make_pair(ESM::PRT_LLeg, "Left Upper Leg")); - result.insert(std::make_pair(ESM::PRT_RPauldron, "Right Clavicle")); - result.insert(std::make_pair(ESM::PRT_LPauldron, "Left Clavicle")); - result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone")); // Fallback. The real node name depends on the current weapon type. - result.insert(std::make_pair(ESM::PRT_Tail, "Tail")); - return result; -} -const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); - -NpcAnimation::~NpcAnimation() -{ - mAmmunition.reset(); -} - -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, - bool disableSounds, ViewMode viewMode, float firstPersonFieldOfView) - : ActorAnimation(ptr, parentNode, resourceSystem), - mViewMode(viewMode), - mShowWeapons(false), - mShowCarriedLeft(true), - mNpcType(getNpcType(ptr)), - mFirstPersonFieldOfView(firstPersonFieldOfView), - mSoundsDisabled(disableSounds), - mAccurateAiming(false), - mAimingFactor(0.f) -{ - mNpc = mPtr.get()->mBase; - - mHeadAnimationTime = std::shared_ptr(new HeadAnimationTime(mPtr)); - mWeaponAnimationTime = std::shared_ptr(new WeaponAnimationTime(this)); - - for(size_t i = 0;i < ESM::PRT_Count;i++) - { - mPartslots[i] = -1; //each slot is empty - mPartPriorities[i] = 0; - } - - std::fill(mSounds.begin(), mSounds.end(), nullptr); - - updateNpcBase(); -} - -void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) -{ - assert(viewMode != VM_HeadOnly); - if(mViewMode == viewMode) - return; - - mViewMode = viewMode; - MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change - - mAmmunition.reset(); - rebuild(); - setRenderBin(); -} - -/// @brief A RenderBin callback to clear the depth buffer before rendering. -class DepthClearCallback : public osgUtil::RenderBin::DrawCallback -{ -public: - DepthClearCallback() - { - mDepth = new osg::Depth; - mDepth->setWriteMask(true); - } - - void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override - { - renderInfo.getState()->applyAttribute(mDepth); - - glClear(GL_DEPTH_BUFFER_BIT); - - bin->drawImplementation(renderInfo, previous); - } - - osg::ref_ptr mDepth; -}; - -/// Overrides Field of View to given value for rendering the subgraph. -/// Must be added as cull callback. -class OverrideFieldOfViewCallback : public osg::NodeCallback -{ -public: - OverrideFieldOfViewCallback(float fov) - : mFov(fov) - { - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osgUtil::CullVisitor* cv = static_cast(nv); - float fov, aspect, zNear, zFar; - if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar)) - { - fov = mFov; - osg::ref_ptr newProjectionMatrix = new osg::RefMatrix(); - newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar); - osg::ref_ptr invertedOldMatrix = cv->getProjectionMatrix(); - invertedOldMatrix = new osg::RefMatrix(osg::RefMatrix::inverse(*invertedOldMatrix)); - osg::ref_ptr viewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); - viewMatrix->postMult(*newProjectionMatrix); - viewMatrix->postMult(*invertedOldMatrix); - cv->pushModelViewMatrix(viewMatrix, osg::Transform::ReferenceFrame::ABSOLUTE_RF); - traverse(node, nv); - cv->popModelViewMatrix(); - } - else - traverse(node, nv); - } - -private: - float mFov; -}; - -void NpcAnimation::setRenderBin() -{ - if (mViewMode == VM_FirstPerson) - { - static bool prototypeAdded = false; - if (!prototypeAdded) - { - osg::ref_ptr depthClearBin (new osgUtil::RenderBin); - depthClearBin->setDrawCallback(new DepthClearCallback); - osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); - prototypeAdded = true; - } - mObjectRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); - } - else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) - stateset->setRenderBinToInherit(); -} - -void NpcAnimation::rebuild() -{ - mScabbard.reset(); - mHolsteredShield.reset(); - updateNpcBase(); - - MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr); -} - -int NpcAnimation::getSlot(const osg::NodePath &path) const -{ - for (int i=0; igetNode().get()) != path.end()) - { - return mPartslots[i]; - } - } - return -1; -} - -void NpcAnimation::updateNpcBase() -{ - clearAnimSources(); - for(size_t i = 0;i < ESM::PRT_Count;i++) - removeIndividualPart((ESM::PartReferenceType)i); - - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Race *race = store.get().find(mNpc->mRace); - NpcType curType = getNpcType(); - bool isWerewolf = (curType == Type_Werewolf); - bool isVampire = (curType == Type_Vampire); - bool isFemale = !mNpc->isMale(); - - mHeadModel.clear(); - mHairModel.clear(); - - std::string headName = isWerewolf ? "WerewolfHead" : mNpc->mHead; - std::string hairName = isWerewolf ? "WerewolfHair" : mNpc->mHair; - - if (!headName.empty()) - { - const ESM::BodyPart* bp = store.get().search(headName); - if (bp) - mHeadModel = "meshes\\" + bp->mModel; - else - Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'"; - } - - if (!hairName.empty()) - { - const ESM::BodyPart* bp = store.get().search(hairName); - if (bp) - mHairModel = "meshes\\" + bp->mModel; - else - Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'"; - } - - const std::string& vampireHead = getVampireHead(mNpc->mRace, isFemale); - if (!isWerewolf && isVampire && !vampireHead.empty()) - mHeadModel = vampireHead; - - bool is1stPerson = mViewMode == VM_FirstPerson; - bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - - std::string defaultSkeleton = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); - defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); - - std::string smodel = defaultSkeleton; - if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) - smodel = Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()); - - setObjectRoot(smodel, true, true, false); - - updateParts(); - - if(!is1stPerson) - { - const std::string base = Settings::Manager::getString("xbaseanim", "Models"); - if (smodel != base && !isWerewolf) - addAnimSource(base, smodel); - - if (smodel != defaultSkeleton && base != defaultSkeleton) - addAnimSource(defaultSkeleton, smodel); - - addAnimSource(smodel, smodel); - - if(!isWerewolf && Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) - addAnimSource("meshes\\xargonian_swimkna.nif", smodel); - } - else - { - const std::string base = Settings::Manager::getString("xbaseanim1st", "Models"); - if (smodel != base && !isWerewolf) - addAnimSource(base, smodel); - - addAnimSource(smodel, smodel); - - mObjectRoot->setNodeMask(Mask_FirstPerson); - mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); - } - - mWeaponAnimationTime->updateStartTime(); -} - -std::string NpcAnimation::getShieldMesh(MWWorld::ConstPtr shield) const -{ - std::string mesh = shield.getClass().getModel(shield); - const ESM::Armor *armor = shield.get()->mBase; - const std::vector& bodyparts = armor->mParts.mParts; - // Try to recover the body part model, use ground model as a fallback otherwise. - if (!bodyparts.empty()) - mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale()); - - if (mesh.empty()) - return std::string(); - - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); - if(mResourceSystem->getVFS()->exists(holsteredName)) - { - osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); - SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); - shieldTemplate->accept(findVisitor); - osg::ref_ptr sheathNode = findVisitor.mFoundNode; - if(!sheathNode) - return std::string(); - } - - return mesh; -} - -void NpcAnimation::updateParts() -{ - if (!mObjectRoot.get()) - return; - - NpcType curType = getNpcType(); - if (curType != mNpcType) - { - mNpcType = curType; - rebuild(); - return; - } - - static const struct { - int mSlot; - int mBasePriority; - } slotlist[] = { - // FIXME: Priority is based on the number of reserved slots. There should be a better way. - { MWWorld::InventoryStore::Slot_Robe, 11 }, - { MWWorld::InventoryStore::Slot_Skirt, 3 }, - { MWWorld::InventoryStore::Slot_Helmet, 0 }, - { MWWorld::InventoryStore::Slot_Cuirass, 0 }, - { MWWorld::InventoryStore::Slot_Greaves, 0 }, - { MWWorld::InventoryStore::Slot_LeftPauldron, 0 }, - { MWWorld::InventoryStore::Slot_RightPauldron, 0 }, - { MWWorld::InventoryStore::Slot_Boots, 0 }, - { MWWorld::InventoryStore::Slot_LeftGauntlet, 0 }, - { MWWorld::InventoryStore::Slot_RightGauntlet, 0 }, - { MWWorld::InventoryStore::Slot_Shirt, 0 }, - { MWWorld::InventoryStore::Slot_Pants, 0 }, - { MWWorld::InventoryStore::Slot_CarriedLeft, 0 }, - { MWWorld::InventoryStore::Slot_CarriedRight, 0 } + float getValue(osg::NodeVisitor* nv) override; }; - static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); - bool wasArrowAttached = isArrowAttached(); - mAmmunition.reset(); + // -------------------------------------------------------------------------------------------------------------- - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) + HeadAnimationTime::HeadAnimationTime(const MWWorld::Ptr& reference) + : mReference(reference) + , mTalkStart(0) + , mTalkStop(0) + , mBlinkStart(0) + , mBlinkStop(0) + , mEnabled(true) + , mValue(0) { - MWWorld::ConstContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot); + resetBlinkTimer(); + } - removePartGroup(slotlist[i].mSlot); + void HeadAnimationTime::updatePtr(const MWWorld::Ptr& updated) + { + mReference = updated; + } - if(store == inv.end()) - continue; + void HeadAnimationTime::setEnabled(bool enabled) + { + mEnabled = enabled; + } - if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet) - removeIndividualPart(ESM::PRT_Hair); + void HeadAnimationTime::resetBlinkTimer() + { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + mBlinkTimer = -(2.0f + Misc::Rng::rollDice(6, prng)); + } - int prio = 1; - bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); - osg::Vec4f glowColor = store->getClass().getEnchantmentColor(*store); - if(store->getTypeName() == typeid(ESM::Clothing).name()) + void HeadAnimationTime::update(float dt) + { + if (!mEnabled) + return; + + if (!MWBase::Environment::get().getSoundManager()->sayActive(mReference)) { - prio = ((slotlist[i].mBasePriority+1)<<1) + 0; - const ESM::Clothing *clothes = store->get()->mBase; - addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor); - } - else if(store->getTypeName() == typeid(ESM::Armor).name()) - { - prio = ((slotlist[i].mBasePriority+1)<<1) + 1; - const ESM::Armor *armor = store->get()->mBase; - addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor); - } + mBlinkTimer += dt; - if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe) - { - ESM::PartReferenceType parts[] = { - ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, - ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, - ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass - }; - size_t parts_size = sizeof(parts)/sizeof(parts[0]); - for(size_t p = 0;p < parts_size;++p) - reserveIndividualPart(parts[p], slotlist[i].mSlot, prio); - } - else if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Skirt) - { - reserveIndividualPart(ESM::PRT_Groin, slotlist[i].mSlot, prio); - reserveIndividualPart(ESM::PRT_RLeg, slotlist[i].mSlot, prio); - reserveIndividualPart(ESM::PRT_LLeg, slotlist[i].mSlot, prio); - } - } + float duration = mBlinkStop - mBlinkStart; - if(mViewMode != VM_FirstPerson) - { - if(mPartPriorities[ESM::PRT_Head] < 1 && !mHeadModel.empty()) - addOrReplaceIndividualPart(ESM::PRT_Head, -1,1, mHeadModel); - if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1 && !mHairModel.empty()) - addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1, mHairModel); - } - if(mViewMode == VM_HeadOnly) - return; - - if(mPartPriorities[ESM::PRT_Shield] < 1) - { - MWWorld::ConstContainerStoreIterator store = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - MWWorld::ConstPtr part; - if(store != inv.end() && (part=*store).getTypeName() == typeid(ESM::Light).name()) - { - const ESM::Light *light = part.get()->mBase; - addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, - 1, "meshes\\"+light->mModel); - if (mObjectParts[ESM::PRT_Shield]) - addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), light); - } - } - - showWeapons(mShowWeapons); - showCarriedLeft(mShowCarriedLeft); - - bool isWerewolf = (getNpcType() == Type_Werewolf); - std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace)); - - const std::vector &parts = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf); - for(int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) - { - if(mPartPriorities[part] < 1) - { - const ESM::BodyPart* bodypart = parts[part]; - if(bodypart) - addOrReplaceIndividualPart((ESM::PartReferenceType)part, -1, 1, - "meshes\\"+bodypart->mModel); - } - } - - if (wasArrowAttached) - attachArrow(); -} - - - -PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) -{ - osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model); - - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); - if (found == nodeMap.end()) - throw std::runtime_error("Can't find attachment node " + bonename); - - osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second); - if (enchantedGlow) - mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); - - return PartHolderPtr(new PartHolder(attached)); -} - -osg::Vec3f NpcAnimation::runAnimation(float timepassed) -{ - osg::Vec3f ret = Animation::runAnimation(timepassed); - - mHeadAnimationTime->update(timepassed); - - if (mFirstPersonNeckController) - { - if (mAccurateAiming) - mAimingFactor = 1.f; - else - mAimingFactor = std::max(0.f, mAimingFactor - timepassed * 0.5f); - - float rotateFactor = 0.75f + 0.25f * mAimingFactor; - - mFirstPersonNeckController->setRotate(osg::Quat(mPtr.getRefData().getPosition().rot[0] * rotateFactor, osg::Vec3f(-1,0,0))); - mFirstPersonNeckController->setOffset(mFirstPersonOffset); - } - - WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); - - return ret; -} - -void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type) -{ - mPartPriorities[type] = 0; - mPartslots[type] = -1; - - mObjectParts[type].reset(); - if (mSounds[type] != nullptr && !mSoundsDisabled) - { - MWBase::Environment::get().getSoundManager()->stopSound(mSounds[type]); - mSounds[type] = nullptr; - } -} - -void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority) -{ - if(priority > mPartPriorities[type]) - { - removeIndividualPart(type); - mPartPriorities[type] = priority; - mPartslots[type] = group; - } -} - -void NpcAnimation::removePartGroup(int group) -{ - for(int i = 0; i < ESM::PRT_Count; i++) - { - if(mPartslots[i] == group) - removeIndividualPart((ESM::PartReferenceType)i); - } -} - -bool NpcAnimation::isFirstPersonPart(const ESM::BodyPart* bodypart) -{ - return bodypart->mId.size() >= 3 && bodypart->mId.substr(bodypart->mId.size()-3, 3) == "1st"; -} - -bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart) -{ - return bodypart->mData.mFlags & ESM::BodyPart::BPF_Female; -} - -bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, osg::Vec4f* glowColor) -{ - if(priority <= mPartPriorities[type]) - return false; - - removeIndividualPart(type); - mPartslots[type] = group; - mPartPriorities[type] = priority; - try - { - std::string bonename = sPartList.at(type); - if (type == ESM::PRT_Weapon) - { - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()) + if (mBlinkTimer >= 0 && mBlinkTimer <= duration) { - int weaponType = weapon->get()->mBase->mData.mType; - const std::string weaponBonename = MWMechanics::getWeaponType(weaponType)->mAttachBone; + mValue = mBlinkStart + mBlinkTimer; + } + else + mValue = mBlinkStop; - if (weaponBonename != bonename) + if (mBlinkTimer > duration) + resetBlinkTimer(); + } + else + { + // FIXME: would be nice to hold on to the SoundPtr so we don't have to retrieve it every frame + mValue = mTalkStart + + (mTalkStop - mTalkStart) + * std::min(1.f, + MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference) + * 2); // Rescale a bit (most voices are not very loud) + } + } + + float HeadAnimationTime::getValue(osg::NodeVisitor*) + { + return mValue; + } + + void HeadAnimationTime::setTalkStart(float value) + { + mTalkStart = value; + } + + void HeadAnimationTime::setTalkStop(float value) + { + mTalkStop = value; + } + + void HeadAnimationTime::setBlinkStart(float value) + { + mBlinkStart = value; + } + + void HeadAnimationTime::setBlinkStop(float value) + { + mBlinkStop = value; + } + + // ---------------------------------------------------- + + NpcAnimation::NpcType NpcAnimation::getNpcType() const + { + const MWWorld::Class& cls = mPtr.getClass(); + // Dead vampires should typically stay vampires. + if (mNpcType == Type_Vampire && cls.getNpcStats(mPtr).isDead() && !cls.getNpcStats(mPtr).isWerewolf()) + return mNpcType; + return getNpcType(mPtr); + } + + NpcAnimation::NpcType NpcAnimation::getNpcType(const MWWorld::Ptr& ptr) + { + const MWWorld::Class& cls = ptr.getClass(); + NpcAnimation::NpcType curType = Type_Normal; + if (cls.getCreatureStats(ptr).getMagicEffects().getOrDefault(ESM::MagicEffect::Vampirism).getMagnitude() > 0) + curType = Type_Vampire; + if (cls.getNpcStats(ptr).isWerewolf()) + curType = Type_Werewolf; + + return curType; + } + + static const inline NpcAnimation::PartBoneMap createPartListMap() + { + return { { ESM::PRT_Head, "Head" }, + { ESM::PRT_Hair, "Head" }, // note it uses "Head" as attach bone, but "Hair" as filter + { ESM::PRT_Neck, "Neck" }, { ESM::PRT_Cuirass, "Chest" }, { ESM::PRT_Groin, "Groin" }, + { ESM::PRT_Skirt, "Groin" }, { ESM::PRT_RHand, "Right Hand" }, { ESM::PRT_LHand, "Left Hand" }, + { ESM::PRT_RWrist, "Right Wrist" }, { ESM::PRT_LWrist, "Left Wrist" }, { ESM::PRT_Shield, "Shield Bone" }, + { ESM::PRT_RForearm, "Right Forearm" }, { ESM::PRT_LForearm, "Left Forearm" }, + { ESM::PRT_RUpperarm, "Right Upper Arm" }, { ESM::PRT_LUpperarm, "Left Upper Arm" }, + { ESM::PRT_RFoot, "Right Foot" }, { ESM::PRT_LFoot, "Left Foot" }, { ESM::PRT_RAnkle, "Right Ankle" }, + { ESM::PRT_LAnkle, "Left Ankle" }, { ESM::PRT_RKnee, "Right Knee" }, { ESM::PRT_LKnee, "Left Knee" }, + { ESM::PRT_RLeg, "Right Upper Leg" }, { ESM::PRT_LLeg, "Left Upper Leg" }, + { ESM::PRT_RPauldron, "Right Clavicle" }, { ESM::PRT_LPauldron, "Left Clavicle" }, + { ESM::PRT_Weapon, "Weapon Bone" }, // Fallback. The real node name depends on the current weapon type. + { ESM::PRT_Tail, "Tail" } }; + } + const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); + + NpcAnimation::~NpcAnimation() + { + mAmmunition.reset(); + } + + NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, + Resource::ResourceSystem* resourceSystem, bool disableSounds, ViewMode viewMode, float firstPersonFieldOfView) + : ActorAnimation(ptr, parentNode, resourceSystem) + , mViewMode(viewMode) + , mShowWeapons(false) + , mShowCarriedLeft(true) + , mNpcType(getNpcType(ptr)) + , mFirstPersonFieldOfView(firstPersonFieldOfView) + , mSoundsDisabled(disableSounds) + , mAccurateAiming(false) + , mAimingFactor(0.f) + { + mNpc = mPtr.get()->mBase; + + mHeadAnimationTime = std::make_shared(mPtr); + mWeaponAnimationTime = std::make_shared(this); + + for (size_t i = 0; i < ESM::PRT_Count; i++) + { + mPartslots[i] = -1; // each slot is empty + mPartPriorities[i] = 0; + } + + std::fill(mSounds.begin(), mSounds.end(), nullptr); + + updateNpcBase(); + } + + void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) + { + assert(viewMode != VM_HeadOnly); + if (mViewMode == viewMode) + return; + // FIXME: sheathing state must be consistent if the third person skeleton doesn't have the necessary node, but + // third person skeleton is unavailable in first person view. This is a hack to avoid cosmetic issues. + bool viewChange = mViewMode == VM_FirstPerson || viewMode == VM_FirstPerson; + mViewMode = viewMode; + MWBase::Environment::get().getWorld()->scaleObject( + mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change + + mAmmunition.reset(); + rebuild(); + setRenderBin(); + + if (viewChange && Settings::game().mShieldSheathing) + { + int weaptype = ESM::Weapon::None; + MWMechanics::getActiveWeapon(mPtr, &weaptype); + showCarriedLeft(updateCarriedLeftVisible(weaptype)); + } + } + + /// @brief A RenderBin callback to clear the depth buffer before rendering. + /// Switches depth attachments to a proxy renderbuffer, reattaches original depth then redraws first person root. + /// This gives a complete depth buffer which can be used for postprocessing, buffer resolves as if depth was never + /// cleared. + class DepthClearCallback : public osgUtil::RenderBin::DrawCallback + { + public: + DepthClearCallback(Resource::ResourceSystem* resourceSystem) + { + mPassNormals = resourceSystem->getSceneManager()->getSupportsNormalsRT(); + mDepth = new SceneUtil::AutoDepth; + mDepth->setWriteMask(true); + + mStateSet = new osg::StateSet; + mStateSet->setAttributeAndModes(new osg::ColorMask(false, false, false, false), osg::StateAttribute::ON); + mStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + } + + void drawImplementation( + osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override + { + osg::State* state = renderInfo.getState(); + + PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); + + state->applyAttribute(mDepth); + + unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2; + + if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)) + { + postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); + if (mPassNormals) { - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(weaponBonename)); - if (found != nodeMap.end()) - bonename = weaponBonename; + state->get()->glColorMaski(1, true, true, true, true); + state->haveAppliedAttribute(osg::StateAttribute::COLORMASK); + } + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + // color accumulation pass + bin->drawImplementation(renderInfo, previous); + + auto primaryFBO = postProcessor->getPrimaryFbo(frameId); + + if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) + postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); + else + primaryFBO->apply(*state); + + // depth accumulation pass + osg::ref_ptr restore = bin->getStateSet(); + bin->setStateSet(mStateSet); + bin->drawImplementation(renderInfo, previous); + bin->setStateSet(restore); + + if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) + primaryFBO->apply(*state); + } + else + { + // fallback to standard depth clear when we are not rendering our main scene via an intermediate FBO + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + bin->drawImplementation(renderInfo, previous); + } + + state->checkGLErrors("after DepthClearCallback::drawImplementation"); + } + + bool mPassNormals; + osg::ref_ptr mDepth; + osg::ref_ptr mStateSet; + }; + + /// Overrides Field of View to given value for rendering the subgraph. + /// Must be added as cull callback. + class OverrideFieldOfViewCallback : public osg::NodeCallback + { + public: + OverrideFieldOfViewCallback(float fov) + : mFov(fov) + { + } + + void operator()(osg::Node* node, osg::NodeVisitor* nv) override + { + osgUtil::CullVisitor* cv = static_cast(nv); + float fov, aspect, zNear, zFar; + if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar) && std::abs(fov - mFov) > 0.001) + { + fov = mFov; + osg::ref_ptr newProjectionMatrix = new osg::RefMatrix(); + newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar); + osg::ref_ptr invertedOldMatrix = cv->getProjectionMatrix(); + invertedOldMatrix = new osg::RefMatrix(osg::RefMatrix::inverse(*invertedOldMatrix)); + osg::ref_ptr viewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + viewMatrix->postMult(*newProjectionMatrix); + viewMatrix->postMult(*invertedOldMatrix); + cv->pushModelViewMatrix(viewMatrix, osg::Transform::ReferenceFrame::ABSOLUTE_RF); + traverse(node, nv); + cv->popModelViewMatrix(); + } + else + traverse(node, nv); + } + + private: + float mFov; + }; + + void NpcAnimation::setRenderBin() + { + if (mViewMode == VM_FirstPerson) + { + static bool prototypeAdded = false; + if (!prototypeAdded) + { + osg::ref_ptr depthClearBin(new osgUtil::RenderBin); + depthClearBin->setDrawCallback(new DepthClearCallback(mResourceSystem)); + osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); + prototypeAdded = true; + } + mObjectRoot->getOrCreateStateSet()->setRenderBinDetails( + RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + } + else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) + stateset->setRenderBinToInherit(); + } + + void NpcAnimation::rebuild() + { + mScabbard.reset(); + mHolsteredShield.reset(); + updateNpcBase(); + + MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr); + } + + int NpcAnimation::getSlot(const osg::NodePath& path) const + { + for (int i = 0; i < ESM::PRT_Count; ++i) + { + const PartHolder* const part = mObjectParts[i].get(); + if (part == nullptr) + continue; + if (std::find(path.begin(), path.end(), part->getNode().get()) != path.end()) + { + return mPartslots[i]; + } + } + return -1; + } + + void NpcAnimation::updateNpcBase() + { + clearAnimSources(); + for (size_t i = 0; i < ESM::PRT_Count; i++) + removeIndividualPart((ESM::PartReferenceType)i); + + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const ESM::Race* race = store.get().find(mNpc->mRace); + NpcType curType = getNpcType(); + bool isWerewolf = (curType == Type_Werewolf); + bool isVampire = (curType == Type_Vampire); + bool isFemale = !mNpc->isMale(); + + mHeadModel.clear(); + mHairModel.clear(); + + const ESM::RefId& headName = isWerewolf ? ESM::RefId::stringRefId("WerewolfHead") : mNpc->mHead; + const ESM::RefId& hairName = isWerewolf ? ESM::RefId::stringRefId("WerewolfHair") : mNpc->mHair; + + if (!headName.empty()) + { + const ESM::BodyPart* bp = store.get().search(headName); + if (bp) + mHeadModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS()); + else + Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'"; + } + + if (!hairName.empty()) + { + const ESM::BodyPart* bp = store.get().search(hairName); + if (bp) + mHairModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS()); + else + Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'"; + } + + const std::string vampireHead = getVampireHead(mNpc->mRace, isFemale, *mResourceSystem->getVFS()); + if (!isWerewolf && isVampire && !vampireHead.empty()) + mHeadModel = vampireHead; + + bool is1stPerson = mViewMode == VM_FirstPerson; + bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; + + std::string defaultSkeleton = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); + defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); + + std::string smodel = defaultSkeleton; + if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) + smodel = Misc::ResourceHelpers::correctActorModelPath( + Misc::ResourceHelpers::correctMeshPath(mNpc->mModel, mResourceSystem->getVFS()), + mResourceSystem->getVFS()); + + setObjectRoot(smodel, true, true, false); + + updateParts(); + + if (!is1stPerson) + { + const std::string& base = Settings::Manager::getString("xbaseanim", "Models"); + if (smodel != base && !isWerewolf) + addAnimSource(base, smodel); + + if (smodel != defaultSkeleton && base != defaultSkeleton) + addAnimSource(defaultSkeleton, smodel); + + addAnimSource(smodel, smodel); + + if (!isWerewolf && mNpc->mRace.contains("argonian")) + addAnimSource("meshes\\xargonian_swimkna.nif", smodel); + } + else + { + const std::string& base = Settings::Manager::getString("xbaseanim1st", "Models"); + if (smodel != base && !isWerewolf) + addAnimSource(base, smodel); + + addAnimSource(smodel, smodel); + + mObjectRoot->setNodeMask(Mask_FirstPerson); + mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); + } + + mWeaponAnimationTime->updateStartTime(); + } + + std::string NpcAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const + { + std::string mesh = getShieldMesh(shield, !mNpc->isMale()); + + if (mesh.empty()) + return std::string(); + + std::string holsteredName = mesh; + holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + if (mResourceSystem->getVFS()->exists(holsteredName)) + { + osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); + SceneUtil::FindByNameVisitor findVisitor("Bip01 Sheath"); + shieldTemplate->accept(findVisitor); + osg::ref_ptr sheathNode = findVisitor.mFoundNode; + if (!sheathNode) + return std::string(); + } + + return mesh; + } + + void NpcAnimation::updateParts() + { + if (!mObjectRoot.get()) + return; + + NpcType curType = getNpcType(); + if (curType != mNpcType) + { + mNpcType = curType; + rebuild(); + return; + } + + static const struct + { + int mSlot; + int mBasePriority; + } slotlist[] = { // FIXME: Priority is based on the number of reserved slots. There should be a better way. + { MWWorld::InventoryStore::Slot_Robe, 11 }, { MWWorld::InventoryStore::Slot_Skirt, 3 }, + { MWWorld::InventoryStore::Slot_Helmet, 0 }, { MWWorld::InventoryStore::Slot_Cuirass, 0 }, + { MWWorld::InventoryStore::Slot_Greaves, 0 }, { MWWorld::InventoryStore::Slot_LeftPauldron, 0 }, + { MWWorld::InventoryStore::Slot_RightPauldron, 0 }, { MWWorld::InventoryStore::Slot_Boots, 0 }, + { MWWorld::InventoryStore::Slot_LeftGauntlet, 0 }, { MWWorld::InventoryStore::Slot_RightGauntlet, 0 }, + { MWWorld::InventoryStore::Slot_Shirt, 0 }, { MWWorld::InventoryStore::Slot_Pants, 0 }, + { MWWorld::InventoryStore::Slot_CarriedLeft, 0 }, { MWWorld::InventoryStore::Slot_CarriedRight, 0 } + }; + static const size_t slotlistsize = sizeof(slotlist) / sizeof(slotlist[0]); + + bool wasArrowAttached = isArrowAttached(); + mAmmunition.reset(); + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + for (size_t i = 0; i < slotlistsize && mViewMode != VM_HeadOnly; i++) + { + MWWorld::ConstContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot); + + removePartGroup(slotlist[i].mSlot); + + if (store == inv.end()) + continue; + + if (slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet) + removeIndividualPart(ESM::PRT_Hair); + + int prio = 1; + bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); + osg::Vec4f glowColor = store->getClass().getEnchantmentColor(*store); + if (store->getType() == ESM::Clothing::sRecordId) + { + prio = ((slotlist[i].mBasePriority + 1) << 1) + 0; + const ESM::Clothing* clothes = store->get()->mBase; + addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor); + } + else if (store->getType() == ESM::Armor::sRecordId) + { + prio = ((slotlist[i].mBasePriority + 1) << 1) + 1; + const ESM::Armor* armor = store->get()->mBase; + addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor); + } + + if (slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe) + { + ESM::PartReferenceType parts[] = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, + ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, ESM::PRT_RForearm, + ESM::PRT_LForearm, ESM::PRT_Cuirass }; + size_t parts_size = sizeof(parts) / sizeof(parts[0]); + for (size_t p = 0; p < parts_size; ++p) + reserveIndividualPart(parts[p], slotlist[i].mSlot, prio); + } + else if (slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Skirt) + { + reserveIndividualPart(ESM::PRT_Groin, slotlist[i].mSlot, prio); + reserveIndividualPart(ESM::PRT_RLeg, slotlist[i].mSlot, prio); + reserveIndividualPart(ESM::PRT_LLeg, slotlist[i].mSlot, prio); + } + } + + if (mViewMode != VM_FirstPerson) + { + if (mPartPriorities[ESM::PRT_Head] < 1 && !mHeadModel.empty()) + addOrReplaceIndividualPart(ESM::PRT_Head, -1, 1, mHeadModel); + if (mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1 && !mHairModel.empty()) + addOrReplaceIndividualPart(ESM::PRT_Hair, -1, 1, mHairModel); + } + if (mViewMode == VM_HeadOnly) + return; + + if (mPartPriorities[ESM::PRT_Shield] < 1) + { + MWWorld::ConstContainerStoreIterator store = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + MWWorld::ConstPtr part; + if (store != inv.end() && (part = *store).getType() == ESM::Light::sRecordId) + { + const ESM::Light* light = part.get()->mBase; + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, + Misc::ResourceHelpers::correctMeshPath(light->mModel, vfs), false, nullptr, true); + if (mObjectParts[ESM::PRT_Shield]) + addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), SceneUtil::LightCommon(*light)); + } + } + + showWeapons(mShowWeapons); + showCarriedLeft(mShowCarriedLeft); + + bool isWerewolf = (getNpcType() == Type_Werewolf); + ESM::RefId race = (isWerewolf ? ESM::RefId::stringRefId("werewolf") : mNpc->mRace); + + const std::vector& parts + = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf); + for (int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) + { + if (mPartPriorities[part] < 1) + { + const ESM::BodyPart* bodypart = parts[part]; + if (bodypart) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + addOrReplaceIndividualPart(static_cast(part), -1, 1, + Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs)); } } } - // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone - const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename; - mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor); - } - catch (std::exception& e) - { - Log(Debug::Error) << "Error adding NPC part: " << e.what(); - return false; + if (wasArrowAttached) + attachArrow(); } - if (!mSoundsDisabled) + PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, std::string_view bonename, + std::string_view bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight) { - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator csi = inv.getSlot(group < 0 ? MWWorld::InventoryStore::Slot_Helmet : group); - if (csi != inv.end()) + osg::ref_ptr attached = attach(model, bonename, bonefilter, isLight); + if (enchantedGlow) + mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); + + return std::make_unique(attached); + } + + osg::Vec3f NpcAnimation::runAnimation(float timepassed) + { + osg::Vec3f ret = Animation::runAnimation(timepassed); + + mHeadAnimationTime->update(timepassed); + + if (mFirstPersonNeckController) { - const auto soundId = csi->getClass().getSound(*csi); - if (!soundId.empty()) - { - mSounds[type] = MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, soundId, - 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop - ); - } + if (mAccurateAiming) + mAimingFactor = 1.f; + else + mAimingFactor = std::max(0.f, mAimingFactor - timepassed * 0.5f); + + float rotateFactor = 0.75f + 0.25f * mAimingFactor; + + mFirstPersonNeckController->setRotate( + osg::Quat(mPtr.getRefData().getPosition().rot[0] * rotateFactor, osg::Vec3f(-1, 0, 0))); + mFirstPersonNeckController->setOffset(mFirstPersonOffset); + } + + WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); + + return ret; + } + + void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type) + { + mPartPriorities[type] = 0; + mPartslots[type] = -1; + + mObjectParts[type].reset(); + if (mSounds[type] != nullptr && !mSoundsDisabled) + { + MWBase::Environment::get().getSoundManager()->stopSound(mSounds[type]); + mSounds[type] = nullptr; } } - osg::Node* node = mObjectParts[type]->getNode(); - if (node->getNumChildrenRequiringUpdateTraversal() > 0) + void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority) { - std::shared_ptr src; - if (type == ESM::PRT_Head) + if (priority > mPartPriorities[type]) { - src = mHeadAnimationTime; + removeIndividualPart(type); + mPartPriorities[type] = priority; + mPartslots[type] = group; + } + } - if (node->getUserDataContainer()) + void NpcAnimation::removePartGroup(int group) + { + for (int i = 0; i < ESM::PRT_Count; i++) + { + if (mPartslots[i] == group) + removeIndividualPart((ESM::PartReferenceType)i); + } + } + + bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart) + { + return bodypart->mData.mFlags & ESM::BodyPart::BPF_Female; + } + + bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, + const std::string& mesh, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight) + { + if (priority <= mPartPriorities[type]) + return false; + + removeIndividualPart(type); + mPartslots[type] = group; + mPartPriorities[type] = priority; + try + { + std::string_view bonename = sPartList.at(type); + if (type == ESM::PRT_Weapon) { - for (unsigned int i=0; igetUserDataContainer()->getNumUserObjects(); ++i) + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId) { - osg::Object* obj = node->getUserDataContainer()->getUserObject(i); - if (SceneUtil::TextKeyMapHolder* keys = dynamic_cast(obj)) - { - for (const auto &key : keys->mTextKeys) - { - if (Misc::StringUtils::ciEqual(key.second, "talk: start")) - mHeadAnimationTime->setTalkStart(key.first); - if (Misc::StringUtils::ciEqual(key.second, "talk: stop")) - mHeadAnimationTime->setTalkStop(key.first); - if (Misc::StringUtils::ciEqual(key.second, "blink: start")) - mHeadAnimationTime->setBlinkStart(key.first); - if (Misc::StringUtils::ciEqual(key.second, "blink: stop")) - mHeadAnimationTime->setBlinkStop(key.first); - } + int weaponType = weapon->get()->mBase->mData.mType; + const std::string& weaponBonename = MWMechanics::getWeaponType(weaponType)->mAttachBone; - break; + if (weaponBonename != bonename) + { + const NodeMap& nodeMap = getNodeMap(); + NodeMap::const_iterator found = nodeMap.find(weaponBonename); + if (found != nodeMap.end()) + bonename = weaponBonename; } } } + + // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the + // attachment bone + const std::string_view bonefilter = (type == ESM::PRT_Hair) ? std::string_view{ "hair" } : bonename; + mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor, isLight); } - else if (type == ESM::PRT_Weapon) - src = mWeaponAnimationTime; - else - src.reset(new NullAnimationTime); - - SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); - node->accept(assignVisitor); - } - - return true; -} - -void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow, osg::Vec4f* glowColor) -{ - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - - const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : ""; - for(const ESM::PartReference& part : parts) - { - const ESM::BodyPart *bodypart = nullptr; - if(!mNpc->isMale() && !part.mFemale.empty()) + catch (std::exception& e) { - bodypart = partStore.search(part.mFemale+ext); - if(!bodypart && mViewMode == VM_FirstPerson) + Log(Debug::Error) << "Error adding NPC part: " << e.what(); + return false; + } + + if (!mSoundsDisabled && group == MWWorld::InventoryStore::Slot_CarriedLeft) + { + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator csi = inv.getSlot(group); + if (csi != inv.end()) { - bodypart = partStore.search(part.mFemale); - if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || - bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || - bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || - bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) - bodypart = nullptr; + const auto soundId = csi->getClass().getSound(*csi); + if (!soundId.empty()) + { + mSounds[type] = MWBase::Environment::get().getSoundManager()->playSound3D( + mPtr, soundId, 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop); + } } - else if (!bodypart) - Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mFemale << "'"; } - if(!bodypart && !part.mMale.empty()) + + osg::Node* node = mObjectParts[type]->getNode(); + if (node->getNumChildrenRequiringUpdateTraversal() > 0) { - bodypart = partStore.search(part.mMale+ext); - if(!bodypart && mViewMode == VM_FirstPerson) + std::shared_ptr src; + if (type == ESM::PRT_Head) { - bodypart = partStore.search(part.mMale); - if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || - bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || - bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || - bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) - bodypart = nullptr; + src = mHeadAnimationTime; + + if (node->getUserDataContainer()) + { + for (unsigned int i = 0; i < node->getUserDataContainer()->getNumUserObjects(); ++i) + { + osg::Object* obj = node->getUserDataContainer()->getUserObject(i); + if (SceneUtil::TextKeyMapHolder* keys = dynamic_cast(obj)) + { + for (const auto& key : keys->mTextKeys) + { + if (Misc::StringUtils::ciEqual(key.second, "talk: start")) + mHeadAnimationTime->setTalkStart(key.first); + if (Misc::StringUtils::ciEqual(key.second, "talk: stop")) + mHeadAnimationTime->setTalkStop(key.first); + if (Misc::StringUtils::ciEqual(key.second, "blink: start")) + mHeadAnimationTime->setBlinkStart(key.first); + if (Misc::StringUtils::ciEqual(key.second, "blink: stop")) + mHeadAnimationTime->setBlinkStop(key.first); + } + + break; + } + } + } + SceneUtil::ForceControllerSourcesVisitor assignVisitor(src); + node->accept(assignVisitor); + } + else + { + if (type == ESM::PRT_Weapon) + src = mWeaponAnimationTime; + else + src = std::make_shared(); + SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); + node->accept(assignVisitor); } - else if (!bodypart) - Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mMale << "'"; } - if(bodypart) - addOrReplaceIndividualPart((ESM::PartReferenceType)part.mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor); - else - reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority); + return true; } -} -void NpcAnimation::addControllers() -{ - Animation::addControllers(); - - mFirstPersonNeckController = nullptr; - WeaponAnimation::deleteControllers(); - - if (mViewMode == VM_FirstPerson) + void NpcAnimation::addPartGroup(int group, int priority, const std::vector& parts, + bool enchantedGlow, osg::Vec4f* glowColor) { - NodeMap::iterator found = mNodeMap.find("bip01 neck"); - if (found != mNodeMap.end()) + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const MWWorld::Store& partStore = store.get(); + + const char* ext = (mViewMode == VM_FirstPerson) ? ".1st" : ""; + for (const ESM::PartReference& part : parts) { - osg::MatrixTransform* node = found->second.get(); - mFirstPersonNeckController = new NeckController(mObjectRoot.get()); - node->addUpdateCallback(mFirstPersonNeckController); - mActiveControllers.emplace_back(node, mFirstPersonNeckController); + const ESM::BodyPart* bodypart = nullptr; + if (!mNpc->isMale() && !part.mFemale.empty()) + { + bodypart = partStore.search(ESM::RefId::stringRefId(part.mFemale.getRefIdString() + ext)); + if (!bodypart && mViewMode == VM_FirstPerson) + { + bodypart = partStore.search(part.mFemale); + if (bodypart + && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand + || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist + || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm + || bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) + bodypart = nullptr; + } + else if (!bodypart) + Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mFemale << "'"; + } + if (!bodypart && !part.mMale.empty()) + { + bodypart = partStore.search(ESM::RefId::stringRefId(part.mMale.getRefIdString() + ext)); + if (!bodypart && mViewMode == VM_FirstPerson) + { + bodypart = partStore.search(part.mMale); + if (bodypart + && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand + || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist + || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm + || bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) + bodypart = nullptr; + } + else if (!bodypart) + Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mMale << "'"; + } + + if (bodypart) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + addOrReplaceIndividualPart(static_cast(part.mPart), group, priority, + Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs), enchantedGlow, glowColor); + } + else + reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority); } } - else if (mViewMode == VM_Normal) - { - WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); - } -} -void NpcAnimation::showWeapons(bool showWeapon) -{ - mShowWeapons = showWeapon; - mAmmunition.reset(); - if(showWeapon) + void NpcAnimation::addControllers() { + Animation::addControllers(); + + mFirstPersonNeckController = nullptr; + WeaponAnimation::deleteControllers(); + + if (mViewMode == VM_FirstPerson) + { + NodeMap::iterator found = mNodeMap.find("bip01 neck"); + if (found != mNodeMap.end()) + { + osg::MatrixTransform* node = found->second.get(); + mFirstPersonNeckController = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(mFirstPersonNeckController); + mActiveControllers.emplace_back(node, mFirstPersonNeckController); + } + } + else if (mViewMode == VM_Normal) + { + WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); + } + } + + void NpcAnimation::showWeapons(bool showWeapon) + { + mShowWeapons = showWeapon; + mAmmunition.reset(); + if (showWeapon) + { + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon != inv.end()) + { + osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); + std::string mesh = weapon->getClass().getModel(*weapon); + addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, + !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + + // Crossbows start out with a bolt attached + if (weapon->getType() == ESM::Weapon::sRecordId + && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + int ammotype = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow)->mAmmoType; + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype) + attachArrow(); + } + } + } + else + { + removeIndividualPart(ESM::PRT_Weapon); + // If we remove/hide weapon from player, we should reset attack animation as well + if (mPtr == MWMechanics::getPlayer()) + mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false); + } + + updateHolsteredWeapon(!mShowWeapons); + updateQuiver(); + } + + bool NpcAnimation::updateCarriedLeftVisible(const int weaptype) const + { + if (Settings::game().mShieldSheathing) + { + const MWWorld::Class& cls = mPtr.getClass(); + MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr); + if (stats.getDrawState() == MWMechanics::DrawState::Nothing) + { + SceneUtil::FindByNameVisitor findVisitor("Bip01 AttachShield"); + mObjectRoot->accept(findVisitor); + if (findVisitor.mFoundNode || mViewMode == VM_FirstPerson) + { + const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); + const MWWorld::ConstContainerStoreIterator shield + = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId + && !getSheathedShieldMesh(*shield).empty()) + return false; + } + } + } + + return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); + } + + void NpcAnimation::showCarriedLeft(bool show) + { + mShowCarriedLeft = show; + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (show && iter != inv.end()) + { + osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter); + std::string mesh = iter->getClass().getModel(*iter); + // For shields we must try to use the body part model + if (iter->getType() == ESM::Armor::sRecordId) + { + mesh = getShieldMesh(*iter, !mNpc->isMale()); + } + if (mesh.empty() + || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh, + !iter->getClass().getEnchantment(*iter).empty(), &glowColor, + iter->getType() == ESM::Light::sRecordId)) + { + if (mesh.empty()) + reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1); + if (iter->getType() == ESM::Light::sRecordId && mObjectParts[ESM::PRT_Shield]) + addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), + SceneUtil::LightCommon(*iter->get()->mBase)); + } + } + else + removeIndividualPart(ESM::PRT_Shield); + + updateHolsteredShield(mShowCarriedLeft); + } + + void NpcAnimation::attachArrow() + { + WeaponAnimation::attachArrow(mPtr); + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty()) + { + osg::Group* bone = getArrowBone(); + if (bone != nullptr && bone->getNumChildren()) + SceneUtil::addEnchantedGlow( + bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); + } + + updateQuiver(); + } + + void NpcAnimation::detachArrow() + { + WeaponAnimation::detachArrow(mPtr); + updateQuiver(); + } + + void NpcAnimation::releaseArrow(float attackStrength) + { + WeaponAnimation::releaseArrow(mPtr, attackStrength); + updateQuiver(); + } + + osg::Group* NpcAnimation::getArrowBone() + { + const PartHolder* const part = mObjectParts[ESM::PRT_Weapon].get(); + if (part == nullptr) + return nullptr; + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end()) - { - osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); - std::string mesh = weapon->getClass().getModel(*weapon); - addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, - mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) + return nullptr; - // Crossbows start out with a bolt attached - if (weapon->getTypeName() == typeid(ESM::Weapon).name() && - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + int type = weapon->get()->mBase->mData.mType; + int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; + if (ammoType == ESM::Weapon::None) + return nullptr; + + // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh + osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone); + if (bone == nullptr) + { + SceneUtil::FindByNameVisitor findVisitor("ArrowBone"); + part->getNode()->accept(findVisitor); + bone = findVisitor.mFoundNode; + } + return bone; + } + + osg::Node* NpcAnimation::getWeaponNode() + { + const PartHolder* const part = mObjectParts[ESM::PRT_Weapon].get(); + if (part == nullptr) + return nullptr; + return part->getNode(); + } + + Resource::ResourceSystem* NpcAnimation::getResourceSystem() + { + return mResourceSystem; + } + + void NpcAnimation::enableHeadAnimation(bool enable) + { + mHeadAnimationTime->setEnabled(enable); + } + + void NpcAnimation::setWeaponGroup(const std::string& group, bool relativeDuration) + { + mWeaponAnimationTime->setGroup(group, relativeDuration); + } + + void NpcAnimation::equipmentChanged() + { + if (Settings::game().mShieldSheathing) + { + int weaptype = ESM::Weapon::None; + MWMechanics::getActiveWeapon(mPtr, &weaptype); + showCarriedLeft(updateCarriedLeftVisible(weaptype)); + } + + updateParts(); + } + + void NpcAnimation::setVampire(bool vampire) + { + if (mNpcType == Type_Werewolf) // we can't have werewolf vampires, can we + return; + if ((mNpcType == Type_Vampire) != vampire) + { + if (mPtr == MWMechanics::getPlayer()) + MWBase::Environment::get().getWorld()->reattachPlayerCamera(); + else + rebuild(); + } + } + + void NpcAnimation::setFirstPersonOffset(const osg::Vec3f& offset) + { + mFirstPersonOffset = offset; + } + + void NpcAnimation::updatePtr(const MWWorld::Ptr& updated) + { + Animation::updatePtr(updated); + mHeadAnimationTime->updatePtr(updated); + } + + // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination + typedef std::map, std::vector> RaceMapping; + static RaceMapping sRaceMapping; + + const std::vector& NpcAnimation::getBodyParts( + const ESM::RefId& race, bool female, bool firstPerson, bool werewolf) + { + static const int Flag_FirstPerson = 1 << 1; + static const int Flag_Female = 1 << 0; + + int flags = (werewolf ? -1 : 0); + if (female) + flags |= Flag_Female; + if (firstPerson) + flags |= Flag_FirstPerson; + + RaceMapping::iterator found = sRaceMapping.find(std::make_pair(race, flags)); + if (found != sRaceMapping.end()) + return found->second; + else + { + std::vector& parts = sRaceMapping[std::make_pair(race, flags)]; + + typedef std::multimap BodyPartMapType; + static const BodyPartMapType sBodyPartMap = { { ESM::BodyPart::MP_Neck, ESM::PRT_Neck }, + { ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass }, { ESM::BodyPart::MP_Groin, ESM::PRT_Groin }, + { ESM::BodyPart::MP_Hand, ESM::PRT_RHand }, { ESM::BodyPart::MP_Hand, ESM::PRT_LHand }, + { ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist }, { ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist }, + { ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm }, { ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm }, + { ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm }, { ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm }, + { ESM::BodyPart::MP_Foot, ESM::PRT_RFoot }, { ESM::BodyPart::MP_Foot, ESM::PRT_LFoot }, + { ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle }, { ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle }, + { ESM::BodyPart::MP_Knee, ESM::PRT_RKnee }, { ESM::BodyPart::MP_Knee, ESM::PRT_LKnee }, + { ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg }, { ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg }, + { ESM::BodyPart::MP_Tail, ESM::PRT_Tail } }; + + parts.resize(ESM::PRT_Count, nullptr); + + if (werewolf) + return parts; + + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + + for (const ESM::BodyPart& bodypart : store.get()) { - int ammotype = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow)->mAmmoType; - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype) - attachArrow(); + if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) + continue; + if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) + continue; + + if (!(bodypart.mRace == race)) + continue; + + const bool partFirstPerson = ESM::isFirstPersonBodyPart(bodypart); + + bool isHand = bodypart.mData.mPart == ESM::BodyPart::MP_Hand + || bodypart.mData.mPart == ESM::BodyPart::MP_Wrist + || bodypart.mData.mPart == ESM::BodyPart::MP_Forearm + || bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm; + + bool isSameGender = isFemalePart(&bodypart) == female; + + /* A fallback for the arms if 1st person is missing: + 1. Try to use 3d person skin for same gender + 2. Try to use 1st person skin for male, if female == true + 3. Try to use 3d person skin for male, if female == true + + A fallback in another cases: allow to use male bodyparts, if female == true + */ + if (firstPerson && isHand && !partFirstPerson) + { + // Allow 3rd person skins as a fallback for the arms if 1st person is missing + BodyPartMapType::const_iterator bIt + = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while (bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + // If we have no fallback bodypart now and bodypart is for same gender (1) + if (!parts[bIt->second] && isSameGender) + parts[bIt->second] = &bodypart; + + // If we have fallback bodypart for other gender and found fallback for current gender (1) + else if (isSameGender && isFemalePart(parts[bIt->second]) != female) + parts[bIt->second] = &bodypart; + + // If we have no fallback bodypart and searching for female bodyparts (3) + else if (!parts[bIt->second] && female) + parts[bIt->second] = &bodypart; + + ++bIt; + } + + continue; + } + + // Don't allow to use podyparts for a different view + if (partFirstPerson != firstPerson) + continue; + + if (female && !isFemalePart(&bodypart)) + { + // Allow male parts as fallback for females if female parts are missing + BodyPartMapType::const_iterator bIt + = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while (bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + // If we have no fallback bodypart now + if (!parts[bIt->second]) + parts[bIt->second] = &bodypart; + + // If we have 3d person fallback bodypart for hand and 1st person fallback found (2) + else if (isHand && !ESM::isFirstPersonBodyPart(*parts[bIt->second]) && partFirstPerson) + parts[bIt->second] = &bodypart; + + ++bIt; + } + + continue; + } + + // Don't allow to use podyparts for another gender + if (female != isFemalePart(&bodypart)) + continue; + + // Use properly found bodypart, replacing fallbacks + BodyPartMapType::const_iterator bIt + = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while (bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + parts[bIt->second] = &bodypart; + ++bIt; + } } - } - } - else - { - removeIndividualPart(ESM::PRT_Weapon); - // If we remove/hide weapon from player, we should reset attack animation as well - if (mPtr == MWMechanics::getPlayer()) - MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); - } - - updateHolsteredWeapon(!mShowWeapons); - updateQuiver(); -} - -void NpcAnimation::showCarriedLeft(bool show) -{ - mShowCarriedLeft = show; - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(show && iter != inv.end()) - { - osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter); - std::string mesh = iter->getClass().getModel(*iter); - // For shields we must try to use the body part model - if (iter->getTypeName() == typeid(ESM::Armor).name()) - { - const ESM::Armor *armor = iter->get()->mBase; - const std::vector& bodyparts = armor->mParts.mParts; - if (!bodyparts.empty()) - mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale()); - } - if (mesh.empty() || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, - mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) - { - if (mesh.empty()) - reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1); - if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield]) - addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get()->mBase); - } - } - else - removeIndividualPart(ESM::PRT_Shield); - - updateHolsteredShield(mShowCarriedLeft); -} - -void NpcAnimation::attachArrow() -{ - WeaponAnimation::attachArrow(mPtr); - - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty()) - { - osg::Group* bone = getArrowBone(); - if (bone != nullptr && bone->getNumChildren()) - SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); - } - - updateQuiver(); -} - -void NpcAnimation::detachArrow() -{ - WeaponAnimation::detachArrow(mPtr); - updateQuiver(); -} - -void NpcAnimation::releaseArrow(float attackStrength) -{ - WeaponAnimation::releaseArrow(mPtr, attackStrength); - updateQuiver(); -} - -osg::Group* NpcAnimation::getArrowBone() -{ - PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; - if (!part) - return nullptr; - - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) - return nullptr; - - int type = weapon->get()->mBase->mData.mType; - int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; - - // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh - osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone); - if (bone == nullptr) - { - SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); - part->getNode()->accept(findVisitor); - bone = findVisitor.mFoundNode; - } - return bone; -} - -osg::Node* NpcAnimation::getWeaponNode() -{ - PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; - if (!part) - return nullptr; - return part->getNode(); -} - -Resource::ResourceSystem* NpcAnimation::getResourceSystem() -{ - return mResourceSystem; -} - -void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) -{ - // During first auto equip, we don't play any sounds. - // Basically we don't want sounds when the actor is first loaded, - // the items should appear as if they'd always been equipped. - if (isNew) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - } - - if (!magicEffect->mHit.empty()) - { - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; - // Don't play particle VFX unless the effect is new or it should be looping. - if (isNew || loop) - addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); - } -} - -void NpcAnimation::enableHeadAnimation(bool enable) -{ - mHeadAnimationTime->setEnabled(enable); -} - -void NpcAnimation::setWeaponGroup(const std::string &group, bool relativeDuration) -{ - mWeaponAnimationTime->setGroup(group, relativeDuration); -} - -void NpcAnimation::equipmentChanged() -{ - static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); - if (shieldSheathing) - { - int weaptype = ESM::Weapon::None; - MWMechanics::getActiveWeapon(mPtr, &weaptype); - showCarriedLeft(updateCarriedLeftVisible(weaptype)); - } - - updateParts(); -} - -void NpcAnimation::setVampire(bool vampire) -{ - if (mNpcType == Type_Werewolf) // we can't have werewolf vampires, can we - return; - if ((mNpcType == Type_Vampire) != vampire) - { - if (mPtr == MWMechanics::getPlayer()) - MWBase::Environment::get().getWorld()->reattachPlayerCamera(); - else - rebuild(); - } -} - -void NpcAnimation::setFirstPersonOffset(const osg::Vec3f &offset) -{ - mFirstPersonOffset = offset; -} - -void NpcAnimation::updatePtr(const MWWorld::Ptr &updated) -{ - Animation::updatePtr(updated); - mHeadAnimationTime->updatePtr(updated); -} - -// Remember body parts so we only have to search through the store once for each race/gender/viewmode combination -typedef std::map< std::pair,std::vector > RaceMapping; -static RaceMapping sRaceMapping; - -const std::vector& NpcAnimation::getBodyParts(const std::string &race, bool female, bool firstPerson, bool werewolf) -{ - static const int Flag_FirstPerson = 1<<1; - static const int Flag_Female = 1<<0; - - int flags = (werewolf ? -1 : 0); - if(female) - flags |= Flag_Female; - if(firstPerson) - flags |= Flag_FirstPerson; - - RaceMapping::iterator found = sRaceMapping.find(std::make_pair(race, flags)); - if (found != sRaceMapping.end()) - return found->second; - else - { - std::vector& parts = sRaceMapping[std::make_pair(race, flags)]; - - typedef std::multimap BodyPartMapType; - static const BodyPartMapType sBodyPartMap = - { - {ESM::BodyPart::MP_Neck, ESM::PRT_Neck}, - {ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass}, - {ESM::BodyPart::MP_Groin, ESM::PRT_Groin}, - {ESM::BodyPart::MP_Hand, ESM::PRT_RHand}, - {ESM::BodyPart::MP_Hand, ESM::PRT_LHand}, - {ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist}, - {ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist}, - {ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm}, - {ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm}, - {ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm}, - {ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm}, - {ESM::BodyPart::MP_Foot, ESM::PRT_RFoot}, - {ESM::BodyPart::MP_Foot, ESM::PRT_LFoot}, - {ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle}, - {ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle}, - {ESM::BodyPart::MP_Knee, ESM::PRT_RKnee}, - {ESM::BodyPart::MP_Knee, ESM::PRT_LKnee}, - {ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg}, - {ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg}, - {ESM::BodyPart::MP_Tail, ESM::PRT_Tail} - }; - - parts.resize(ESM::PRT_Count, nullptr); - - if (werewolf) return parts; - - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - - for(const ESM::BodyPart& bodypart : store.get()) - { - if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) - continue; - if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) - continue; - - if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) - continue; - - bool partFirstPerson = isFirstPersonPart(&bodypart); - - bool isHand = bodypart.mData.mPart == ESM::BodyPart::MP_Hand || - bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || - bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || - bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm; - - bool isSameGender = isFemalePart(&bodypart) == female; - - /* A fallback for the arms if 1st person is missing: - 1. Try to use 3d person skin for same gender - 2. Try to use 1st person skin for male, if female == true - 3. Try to use 3d person skin for male, if female == true - - A fallback in another cases: allow to use male bodyparts, if female == true - */ - if (firstPerson && isHand && !partFirstPerson) - { - // Allow 3rd person skins as a fallback for the arms if 1st person is missing - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - // If we have no fallback bodypart now and bodypart is for same gender (1) - if(!parts[bIt->second] && isSameGender) - parts[bIt->second] = &bodypart; - - // If we have fallback bodypart for other gender and found fallback for current gender (1) - else if(isSameGender && isFemalePart(parts[bIt->second]) != female) - parts[bIt->second] = &bodypart; - - // If we have no fallback bodypart and searching for female bodyparts (3) - else if(!parts[bIt->second] && female) - parts[bIt->second] = &bodypart; - - ++bIt; - } - - continue; - } - - // Don't allow to use podyparts for a different view - if (partFirstPerson != firstPerson) - continue; - - if (female && !isFemalePart(&bodypart)) - { - // Allow male parts as fallback for females if female parts are missing - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - // If we have no fallback bodypart now - if(!parts[bIt->second]) - parts[bIt->second] = &bodypart; - - // If we have 3d person fallback bodypart for hand and 1st person fallback found (2) - else if(isHand && !isFirstPersonPart(parts[bIt->second]) && partFirstPerson) - parts[bIt->second] = &bodypart; - - ++bIt; - } - - continue; - } - - // Don't allow to use podyparts for another gender - if (female != isFemalePart(&bodypart)) - continue; - - // Use properly found bodypart, replacing fallbacks - BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); - while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) - { - parts[bIt->second] = &bodypart; - ++bIt; - } } - return parts; } -} -void NpcAnimation::setAccurateAiming(bool enabled) -{ - mAccurateAiming = enabled; -} + void NpcAnimation::setAccurateAiming(bool enabled) + { + mAccurateAiming = enabled; + } -bool NpcAnimation::isArrowAttached() const -{ - return mAmmunition != nullptr; -} + bool NpcAnimation::isArrowAttached() const + { + return mAmmunition != nullptr; + } } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 7e55001da..a03ee28f3 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -24,155 +24,159 @@ namespace MWSound namespace MWRender { -class NeckController; -class HeadAnimationTime; + class RotateController; + class HeadAnimationTime; -class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener -{ -public: - void equipmentChanged() override; - void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) override; - -public: - typedef std::map PartBoneMap; - - enum ViewMode { - VM_Normal, - VM_FirstPerson, - VM_HeadOnly - }; - -private: - static const PartBoneMap sPartList; - - // Bounded Parts - PartHolderPtr mObjectParts[ESM::PRT_Count]; - std::array mSounds; - - const ESM::NPC *mNpc; - std::string mHeadModel; - std::string mHairModel; - ViewMode mViewMode; - bool mShowWeapons; - bool mShowCarriedLeft; - - enum NpcType + class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener { - Type_Normal, - Type_Werewolf, - Type_Vampire + public: + void equipmentChanged() override; + + public: + typedef std::map PartBoneMap; + + enum ViewMode + { + VM_Normal, + VM_FirstPerson, + VM_HeadOnly + }; + + private: + static const PartBoneMap sPartList; + + // Bounded Parts + PartHolderPtr mObjectParts[ESM::PRT_Count]; + std::array mSounds; + + const ESM::NPC* mNpc; + std::string mHeadModel; + std::string mHairModel; + ViewMode mViewMode; + bool mShowWeapons; + bool mShowCarriedLeft; + + enum NpcType + { + Type_Normal, + Type_Werewolf, + Type_Vampire + }; + NpcType mNpcType; + + int mPartslots[ESM::PRT_Count]; // Each part slot is taken by clothing, armor, or is empty + int mPartPriorities[ESM::PRT_Count]; + + osg::Vec3f mFirstPersonOffset; + // Field of view to use when rendering first person meshes + float mFirstPersonFieldOfView; + + std::shared_ptr mHeadAnimationTime; + std::shared_ptr mWeaponAnimationTime; + + bool mSoundsDisabled; + + bool mAccurateAiming; + float mAimingFactor; + + void updateNpcBase(); + + NpcType getNpcType() const; + + PartHolderPtr insertBoundedPart(const std::string& model, std::string_view bonename, + std::string_view bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight); + + void removeIndividualPart(ESM::PartReferenceType type); + void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority); + + bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string& mesh, + bool enchantedGlow = false, osg::Vec4f* glowColor = nullptr, bool isLight = false); + void removePartGroup(int group); + void addPartGroup(int group, int priority, const std::vector& parts, + bool enchantedGlow = false, osg::Vec4f* glowColor = nullptr); + + void setRenderBin(); + + osg::ref_ptr mFirstPersonNeckController; + + static bool isFemalePart(const ESM::BodyPart* bodypart); + static NpcType getNpcType(const MWWorld::Ptr& ptr); + + protected: + void addControllers() override; + bool isArrowAttached() const override; + std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const override; + + public: + /** + * @param ptr + * @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports + * one listener at a time, so you shouldn't do this if creating several NpcAnimations + * for the same Ptr, eg preview dolls for the player. + * Those need to be manually rendered anyway. + * @param disableSounds Same as \a disableListener but for playing items sounds + * @param viewMode + */ + NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, + Resource::ResourceSystem* resourceSystem, bool disableSounds = false, ViewMode viewMode = VM_Normal, + float firstPersonFieldOfView = 55.f); + virtual ~NpcAnimation(); + + void enableHeadAnimation(bool enable) override; + + /// 1: the first person meshes follow the camera's rotation completely + /// 0: the first person meshes follow the camera with a reduced factor, so you can look down at your own hands + void setAccurateAiming(bool enabled) override; + + void setWeaponGroup(const std::string& group, bool relativeDuration) override; + + osg::Vec3f runAnimation(float timepassed) override; + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + void setPitchFactor(float factor) override { mPitchFactor = factor; } + + bool getWeaponsShown() const override { return mShowWeapons; } + void showWeapons(bool showWeapon) override; + + bool updateCarriedLeftVisible(const int weaptype) const override; + bool getCarriedLeftShown() const override { return mShowCarriedLeft; } + void showCarriedLeft(bool show) override; + + void attachArrow() override; + void detachArrow() override; + void releaseArrow(float attackStrength) override; + + osg::Group* getArrowBone() override; + osg::Node* getWeaponNode() override; + Resource::ResourceSystem* getResourceSystem() override; + + // WeaponAnimation + void showWeapon(bool show) override { showWeapons(show); } + + void setViewMode(ViewMode viewMode); + + void updateParts(); + + /// Rebuilds the NPC, updating their root model, animation sources, and equipment. + void rebuild(); + + /// Get the inventory slot that the given node path leads into, or -1 if not found. + int getSlot(const osg::NodePath& path) const; + + void setVampire(bool vampire) override; + + /// Set a translation offset (in object root space) to apply to meshes when in first person mode. + void setFirstPersonOffset(const osg::Vec3f& offset); + + void updatePtr(const MWWorld::Ptr& updated) override; + + /// Get a list of body parts that may be used by an NPC of given race and gender. + /// @note This is a fixed size list, one list item for each ESM::PartReferenceType, may contain nullptr body + /// parts. + static const std::vector& getBodyParts( + const ESM::RefId& raceId, bool female, bool firstperson, bool werewolf); }; - NpcType mNpcType; - - int mPartslots[ESM::PRT_Count]; //Each part slot is taken by clothing, armor, or is empty - int mPartPriorities[ESM::PRT_Count]; - - osg::Vec3f mFirstPersonOffset; - // Field of view to use when rendering first person meshes - float mFirstPersonFieldOfView; - - std::shared_ptr mHeadAnimationTime; - std::shared_ptr mWeaponAnimationTime; - - bool mSoundsDisabled; - - bool mAccurateAiming; - float mAimingFactor; - - void updateNpcBase(); - - NpcType getNpcType() const; - - PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, - const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr); - - void removeIndividualPart(ESM::PartReferenceType type); - void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority); - - bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, - bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); - void removePartGroup(int group); - void addPartGroup(int group, int priority, const std::vector &parts, - bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); - - void setRenderBin(); - - osg::ref_ptr mFirstPersonNeckController; - - static bool isFirstPersonPart(const ESM::BodyPart* bodypart); - static bool isFemalePart(const ESM::BodyPart* bodypart); - static NpcType getNpcType(const MWWorld::Ptr& ptr); - -protected: - void addControllers() override; - bool isArrowAttached() const override; - std::string getShieldMesh(MWWorld::ConstPtr shield) const override; - -public: - /** - * @param ptr - * @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports - * one listener at a time, so you shouldn't do this if creating several NpcAnimations - * for the same Ptr, eg preview dolls for the player. - * Those need to be manually rendered anyway. - * @param disableSounds Same as \a disableListener but for playing items sounds - * @param viewMode - */ - NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, - bool disableSounds = false, ViewMode viewMode=VM_Normal, float firstPersonFieldOfView=55.f); - virtual ~NpcAnimation(); - - void enableHeadAnimation(bool enable) override; - - /// 1: the first person meshes follow the camera's rotation completely - /// 0: the first person meshes follow the camera with a reduced factor, so you can look down at your own hands - void setAccurateAiming(bool enabled) override; - - void setWeaponGroup(const std::string& group, bool relativeDuration) override; - - osg::Vec3f runAnimation(float timepassed) override; - - /// A relative factor (0-1) that decides if and how much the skeleton should be pitched - /// to indicate the facing orientation of the character. - void setPitchFactor(float factor) override { mPitchFactor = factor; } - - void showWeapons(bool showWeapon) override; - - bool getCarriedLeftShown() const override { return mShowCarriedLeft; } - void showCarriedLeft(bool show) override; - - void attachArrow() override; - void detachArrow() override; - void releaseArrow(float attackStrength) override; - - osg::Group* getArrowBone() override; - osg::Node* getWeaponNode() override; - Resource::ResourceSystem* getResourceSystem() override; - - // WeaponAnimation - void showWeapon(bool show) override { showWeapons(show); } - - void setViewMode(ViewMode viewMode); - - void updateParts(); - - /// Rebuilds the NPC, updating their root model, animation sources, and equipment. - void rebuild(); - - /// Get the inventory slot that the given node path leads into, or -1 if not found. - int getSlot(const osg::NodePath& path) const; - - void setVampire(bool vampire) override; - - /// Set a translation offset (in object root space) to apply to meshes when in first person mode. - void setFirstPersonOffset(const osg::Vec3f& offset); - - void updatePtr(const MWWorld::Ptr& updated) override; - - /// Get a list of body parts that may be used by an NPC of given race and gender. - /// @note This is a fixed size list, one list item for each ESM::PartReferenceType, may contain nullptr body parts. - static const std::vector& getBodyParts(const std::string& raceId, bool female, bool firstperson, bool werewolf); -}; } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 6ab7ac4ce..ecab3a975 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -1,37 +1,48 @@ #include "objectpaging.hpp" #include +#include -#include #include -#include -#include #include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include + +#include #include #include #include -#include +#include #include #include #include #include +#include #include #include #include +#include #include -#include -#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "vismask.hpp" +#include + namespace MWRender { @@ -39,37 +50,39 @@ namespace MWRender { switch (type) { - case ESM::REC_STAT: - case ESM::REC_ACTI: - case ESM::REC_DOOR: - return true; - case ESM::REC_CONT: - return !far; + case ESM::REC_STAT: + case ESM::REC_ACTI: + case ESM::REC_DOOR: + return true; + case ESM::REC_CONT: + return !far; - default: - return false; + default: + return false; } } - std::string getModel(int type, const std::string& id, const MWWorld::ESMStore& store) + std::string getModel(int type, const ESM::RefId& id, const MWWorld::ESMStore& store) { switch (type) { - case ESM::REC_STAT: - return store.get().searchStatic(id)->mModel; - case ESM::REC_ACTI: - return store.get().searchStatic(id)->mModel; - case ESM::REC_DOOR: - return store.get().searchStatic(id)->mModel; - case ESM::REC_CONT: - return store.get().searchStatic(id)->mModel; - default: - return std::string(); + case ESM::REC_STAT: + return store.get().searchStatic(id)->mModel; + case ESM::REC_ACTI: + return store.get().searchStatic(id)->mModel; + case ESM::REC_DOOR: + return store.get().searchStatic(id)->mModel; + case ESM::REC_CONT: + return store.get().searchStatic(id)->mModel; + default: + return {}; } } - osg::ref_ptr ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + osg::ref_ptr ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { + lod = static_cast(lodFlags >> (4 * 4)); if (activeGrid && !mActiveGrid) return nullptr; @@ -77,10 +90,10 @@ namespace MWRender osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) - return obj->asNode(); + return static_cast(obj.get()); else { - osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile); + osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile, lod); mCache->addEntryToObjectCache(id, node.get()); return node; } @@ -89,21 +102,43 @@ namespace MWRender class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback { public: - bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const override + bool isOperationPermissibleForObjectImplementation( + const SceneUtil::Optimizer* optimizer, const osg::Drawable* node, unsigned int option) const override { return true; } - bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const override + bool isOperationPermissibleForObjectImplementation( + const SceneUtil::Optimizer* optimizer, const osg::Node* node, unsigned int option) const override { return (node->getDataVariance() != osg::Object::DYNAMIC); } }; + namespace + { + using LODRange = osg::LOD::MinMaxPair; + + LODRange intersection(const LODRange& left, const LODRange& right) + { + return { std::max(left.first, right.first), std::min(left.second, right.second) }; + } + + bool empty(const LODRange& r) + { + return r.first >= r.second; + } + + LODRange operator/(const LODRange& r, float div) + { + return { r.first / div, r.second / div }; + } + } + class CopyOp : public osg::CopyOp { public: bool mOptimizeBillboards = true; - float mSqrDistance = 0.f; + LODRange mDistances = { 0.f, 0.f }; osg::Vec3f mViewVector; osg::Node::NodeMask mCopyMask = ~0u; mutable std::vector mNodePath; @@ -115,12 +150,12 @@ namespace MWRender attachTo->addChild(operator()(toCopy)); else { - for (unsigned int i=0; igetNumChildren(); ++i) + for (unsigned int i = 0; i < groupToCopy->getNumChildren(); ++i) attachTo->addChild(operator()(groupToCopy->getChild(i))); } } - osg::Node* operator() (const osg::Node* node) const override + osg::Node* operator()(const osg::Node* node) const override { if (!(node->getNodeMask() & mCopyMask)) return nullptr; @@ -136,18 +171,36 @@ namespace MWRender if (const osg::Switch* sw = node->asSwitch()) { osg::Group* n = new osg::Group; - for (unsigned int i=0; igetNumChildren(); ++i) + for (unsigned int i = 0; i < sw->getNumChildren(); ++i) if (sw->getValue(i)) n->addChild(operator()(sw->getChild(i))); n->setDataVariance(osg::Object::STATIC); return n; } if (const osg::LOD* lod = dynamic_cast(node)) + { + std::vector, LODRange>> children; + for (unsigned int i = 0; i < lod->getNumChildren(); ++i) + if (const auto r = intersection(lod->getRangeList()[i], mDistances); !empty(r)) + children.emplace_back(operator()(lod->getChild(i)), lod->getRangeList()[i]); + if (children.empty()) + return nullptr; + + if (children.size() == 1) + return children.front().first.release(); + else + { + osg::LOD* n = new osg::LOD; + for (const auto& [child, range] : children) + n->addChild(child, range.first, range.second); + n->setDataVariance(osg::Object::STATIC); + return n; + } + } + if (const osg::Sequence* sq = dynamic_cast(node)) { osg::Group* n = new osg::Group; - for (unsigned int i=0; igetNumChildren(); ++i) - if (lod->getMinRange(i) * lod->getMinRange(i) <= mSqrDistance && mSqrDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) - n->addChild(operator()(lod->getChild(i))); + n->addChild(operator()(sq->getChild(sq->getValue() != -1 ? sq->getValue() : 0))); n->setDataVariance(osg::Object::STATIC); return n; } @@ -165,9 +218,10 @@ namespace MWRender return cloned; } - void handleCallbacks(const osg::Node* node, osg::Node *cloned) const + void handleCallbacks(const osg::Node* node, osg::Node* cloned) const { - for (const osg::Callback* callback = node->getCullCallback(); callback != nullptr; callback = callback->getNestedCallback()) + for (const osg::Callback* callback = node->getCullCallback(); callback != nullptr; + callback = callback->getNestedCallback()) { if (callback->className() == std::string("BillboardCallback")) { @@ -182,7 +236,7 @@ namespace MWRender if (node->getCullCallback()->getNestedCallback()) { - osg::Callback *clonedCallback = osg::clone(callback, osg::CopyOp::SHALLOW_COPY); + osg::Callback* clonedCallback = osg::clone(callback, osg::CopyOp::SHALLOW_COPY); clonedCallback->setNestedCallback(nullptr); cloned->addCullCallback(clonedCallback); } @@ -193,9 +247,11 @@ namespace MWRender void handleBillboard(osg::Node* node) const { osg::Transform* transform = node->asTransform(); - if (!transform) return; + if (!transform) + return; osg::MatrixTransform* matrixTransform = transform->asMatrixTransform(); - if (!matrixTransform) return; + if (!matrixTransform) + return; osg::Matrix worldToLocal = osg::Matrix::identity(); for (auto pathNode : mNodePath) @@ -206,19 +262,20 @@ namespace MWRender osg::Matrix billboardMatrix; osg::Vec3f viewVector = -(mViewVector + worldToLocal.getTrans()); viewVector.normalize(); - osg::Vec3f right = viewVector ^ osg::Vec3f(0,0,1); + osg::Vec3f right = viewVector ^ osg::Vec3f(0, 0, 1); right.normalize(); osg::Vec3f up = right ^ viewVector; up.normalize(); - billboardMatrix.makeLookAt(osg::Vec3f(0,0,0), viewVector, up); + billboardMatrix.makeLookAt(osg::Vec3f(0, 0, 0), viewVector, up); billboardMatrix.invert(billboardMatrix); const osg::Matrix& oldMatrix = matrixTransform->getMatrix(); float mag[3]; // attempt to preserve scale - for (int i=0;i<3;++i) - mag[i] = std::sqrt(oldMatrix(0,i) * oldMatrix(0,i) + oldMatrix(1,i) * oldMatrix(1,i) + oldMatrix(2,i) * oldMatrix(2,i)); + for (int i = 0; i < 3; ++i) + mag[i] = std::sqrt(oldMatrix(0, i) * oldMatrix(0, i) + oldMatrix(1, i) * oldMatrix(1, i) + + oldMatrix(2, i) * oldMatrix(2, i)); osg::Matrix newMatrix; - worldToLocal.setTrans(0,0,0); + worldToLocal.setTrans(0, 0, 0); newMatrix *= worldToLocal; newMatrix.preMult(billboardMatrix); newMatrix.preMultScale(osg::Vec3f(mag[0], mag[1], mag[2])); @@ -226,7 +283,7 @@ namespace MWRender matrixTransform->setMatrix(newMatrix); } - osg::Drawable* operator() (const osg::Drawable* drawable) const override + osg::Drawable* operator()(const osg::Drawable* drawable) const override { if (!(drawable->getNodeMask() & mCopyMask)) return nullptr; @@ -234,6 +291,8 @@ namespace MWRender if (dynamic_cast(drawable)) return nullptr; + if (dynamic_cast(drawable)) + return nullptr; if (const SceneUtil::RigGeometry* rig = dynamic_cast(drawable)) return operator()(rig->getSourceGeometry()); if (const SceneUtil::MorphGeometry* morph = dynamic_cast(drawable)) @@ -250,29 +309,30 @@ namespace MWRender else return const_cast(drawable); } - osg::Callback* operator() (const osg::Callback* callback) const override - { - return nullptr; - } + osg::Callback* operator()(const osg::Callback* callback) const override { return nullptr; } }; class RefnumSet : public osg::Object { public: - RefnumSet(){} - RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {} + RefnumSet() {} + RefnumSet(const RefnumSet& copy, const osg::CopyOp&) + : mRefnums(copy.mRefnums) + { + } META_Object(MWRender, RefnumSet) - std::set mRefnums; + std::vector mRefnums; }; class AnalyzeVisitor : public osg::NodeVisitor { public: AnalyzeVisitor(osg::Node::NodeMask analyzeMask) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mCurrentStateSet(nullptr) - , mCurrentDistance(0.f) - , mAnalyzeMask(analyzeMask) {} + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mCurrentStateSet(nullptr) + { + setTraversalMask(analyzeMask); + } typedef std::unordered_map StateSetCounter; struct Result @@ -283,34 +343,33 @@ namespace MWRender void apply(osg::Node& node) override { - if (!(node.getNodeMask() & mAnalyzeMask)) - return; - if (node.getStateSet()) mCurrentStateSet = node.getStateSet(); if (osg::Switch* sw = node.asSwitch()) { - for (unsigned int i=0; igetNumChildren(); ++i) + for (unsigned int i = 0; i < sw->getNumChildren(); ++i) if (sw->getValue(i)) traverse(*sw->getChild(i)); return; } if (osg::LOD* lod = dynamic_cast(&node)) { - for (unsigned int i=0; igetNumChildren(); ++i) - if (lod->getMinRange(i) * lod->getMinRange(i) <= mCurrentDistance && mCurrentDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) + for (unsigned int i = 0; i < lod->getNumChildren(); ++i) + if (const auto r = intersection(lod->getRangeList()[i], mDistances); !empty(r)) traverse(*lod->getChild(i)); return; } + if (osg::Sequence* sq = dynamic_cast(&node)) + { + traverse(*sq->getChild(sq->getValue() != -1 ? sq->getValue() : 0)); + return; + } traverse(node); } void apply(osg::Geometry& geom) override { - if (!(geom.getNodeMask() & mAnalyzeMask)) - return; - if (osg::Array* array = geom.getVertexArray()) mResult.mNumVerts += array->getNumElements(); @@ -331,7 +390,8 @@ namespace MWRender } float getMergeBenefit(const Result& result) { - if (result.mStateSetCounter.empty()) return 1; + if (result.mStateSetCounter.empty()) + return 1; float mergeBenefit = 0; for (auto pair : result.mStateSetCounter) { @@ -344,27 +404,35 @@ namespace MWRender Result mResult; osg::StateSet* mCurrentStateSet; StateSetCounter mGlobalStateSetCounter; - float mCurrentDistance; - osg::Node::NodeMask mAnalyzeMask; + LODRange mDistances = { 0.f, 0.f }; }; class DebugVisitor : public osg::NodeVisitor { public: - DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {} + DebugVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } void apply(osg::Drawable& node) override { - osg::ref_ptr m (new osg::Material); - osg::Vec4f color(Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), 0.f); + osg::ref_ptr m(new osg::Material); + osg::Vec4f color( + Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), 0.f); color.normalize(); - m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f)); - m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f)); + m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f, 0.1f, 0.1f, 1.f)); + m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f, 0.1f, 0.1f, 1.f)); m->setColorMode(osg::Material::OFF); m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(color)); - osg::ref_ptr stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet; + osg::ref_ptr stateset + = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet; stateset->setAttribute(m); stateset->addUniform(new osg::Uniform("colorMode", 0)); stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); +<<<<<<< HEAD +======= + stateset->addUniform(new osg::Uniform("specStrength", 1.f)); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 node.setStateSet(stateset); } }; @@ -372,11 +440,15 @@ namespace MWRender class AddRefnumMarkerVisitor : public osg::NodeVisitor { public: - AddRefnumMarkerVisitor(const ESM::RefNum &refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mRefnum(refnum) {} - ESM::RefNum mRefnum; - void apply(osg::Geometry &node) override + AddRefnumMarkerVisitor(const ESM::RefNum& refnum) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mRefnum(refnum) { - osg::ref_ptr marker (new RefnumMarker); + } + ESM::RefNum mRefnum; + void apply(osg::Geometry& node) override + { + osg::ref_ptr marker(new RefnumMarker); marker->mRefnum = mRefnum; if (osg::Array* array = node.getVertexArray()) marker->mNumVertices = array->getNumElements(); @@ -385,54 +457,67 @@ namespace MWRender }; ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager) - : GenericResourceManager(nullptr) - , mSceneManager(sceneManager) - , mRefTrackerLocked(false) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + , mRefTrackerLocked(false) { mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); - mDebugBatches = Settings::Manager::getBool("object paging debug batches", "Terrain"); + mDebugBatches = Settings::Manager::getBool("debug chunks", "Terrain"); mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain"); } - osg::ref_ptr ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + osg::ref_ptr ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, + const osg::Vec3f& viewPoint, bool compile, unsigned char lod) { - osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size / 2.f), std::floor(center.y() - size / 2.f)); - osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0) * ESM::Land::REAL_SIZE; osg::Vec3f relativeViewPoint = viewPoint - worldCenter; std::map refs; - std::vector esm; - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + ESM::ReadersCache readers; + const auto& world = MWBase::Environment::get().getWorld(); + const auto& store = world->getStore(); for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) { const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); - if (!cell) continue; - for (size_t i=0; imContextList.size(); ++i) + if (!cell) + continue; + for (size_t i = 0; i < cell->mContextList.size(); ++i) { try { - unsigned int index = cell->mContextList[i].index; - if (esm.size()<=index) - esm.resize(index+1); - cell->restore(esm[index], i); + const std::size_t index = static_cast(cell->mContextList[i].index); + const ESM::ReadersCache::BusyItem reader = readers.get(index); + cell->restore(*reader, i); ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + ESM::MovedCellRef cMRef; bool deleted = false; - while(cell->getNextRef(esm[index], ref, deleted)) + bool moved = false; + while (ESM::Cell::getNextRef( + *reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { - if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + if (moved) + continue; + + if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) + != cell->mMovedRefs.end()) + continue; + int type = store.findStatic(ref.mRefID); - if (!typeFilter(type,size>=2)) continue; - if (deleted) { refs.erase(ref.mRefNum); continue; } - if (ref.mRefNum.fromGroundcoverFile()) continue; + if (!typeFilter(type, size >= 2)) + continue; + if (deleted) + { + refs.erase(ref.mRefNum); + continue; + } refs[ref.mRefNum] = std::move(ref); } } @@ -443,10 +528,14 @@ namespace MWRender } for (auto [ref, deleted] : cell->mLeasedRefs) { - if (deleted) { refs.erase(ref.mRefNum); continue; } - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + if (deleted) + { + refs.erase(ref.mRefNum); + continue; + } int type = store.findStatic(ref.mRefID); - if (!typeFilter(type,size>=2)) continue; + if (!typeFilter(type, size >= 2)) + continue; refs[ref.mRefNum] = std::move(ref); } } @@ -459,8 +548,8 @@ namespace MWRender refs.erase(ref); } - osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); - osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); + osg::Vec2f minBound = (center - osg::Vec2f(size / 2.f, size / 2.f)); + osg::Vec2f maxBound = (center + osg::Vec2f(size / 2.f, size / 2.f)); struct InstanceList { std::vector mInstances; @@ -477,8 +566,18 @@ namespace MWRender // Since ObjectPaging does not handle VisController, we can just ignore both types of nodes. constexpr auto copyMask = ~Mask_UpdateVisitor; + const auto smallestDistanceToChunk = (size > 1 / 8.f) ? (size * ESM::Land::REAL_SIZE) : 0.f; + const auto higherDistanceToChunk = [&] { + if (!activeGrid) + return smallestDistanceToChunk + 1; + return ((size < 1) ? 5 : 3) * ESM::Land::REAL_SIZE * size + 1; + }(); + AnalyzeVisitor analyzeVisitor(copyMask); +<<<<<<< HEAD analyzeVisitor.mCurrentDistance = (viewPoint - worldCenter).length2(); +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 float minSize = mMinSize; if (mMinSizeMergeFactor) minSize *= mMinSizeMergeFactor; @@ -490,8 +589,10 @@ namespace MWRender if (size < 1.f) { osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; - if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) - || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) + || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) + || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) continue; } @@ -500,38 +601,60 @@ namespace MWRender { std::lock_guard lock(mSizeCacheMutex); SizeCache::iterator found = mSizeCache.find(pair.first); - if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize) + if (found != mSizeCache.end() && found->second < dSqr * minSize * minSize) continue; } - if (ref.mRefID == "prisonmarker" || ref.mRefID == "divinemarker" || ref.mRefID == "templemarker" || ref.mRefID == "northmarker") - continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player + if (Misc::ResourceHelpers::isHiddenMarker(ref.mRefID)) + continue; int type = store.findStatic(ref.mRefID); std::string model = getModel(type, ref.mRefID, store); - if (model.empty()) continue; - model = "meshes/" + model; + if (model.empty()) + continue; + model = Misc::ResourceHelpers::correctMeshPath(model, mSceneManager->getVFS()); if (activeGrid && type != ESM::REC_STAT) { model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS()); std::string kfname = Misc::StringUtils::lowerCase(model); - if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) + if (kfname.size() > 4 && kfname.ends_with(".nif")) { - kfname.replace(kfname.size()-4, 4, ".kf"); + kfname.replace(kfname.size() - 4, 4, ".kf"); if (mSceneManager->getVFS()->exists(kfname)) continue; } } + if (!activeGrid) + { + std::lock_guard lock(mLODNameCacheMutex); + LODNameCacheKey key{ model, lod }; + LODNameCache::const_iterator found = mLODNameCache.lower_bound(key); + if (found != mLODNameCache.end() && found->first == key) + model = found->second; + else + model = mLODNameCache + .insert(found, + { key, + Misc::ResourceHelpers::getLODMeshName( + world->getESMVersions()[ref.mRefNum.mContentFile], model, + mSceneManager->getVFS(), lod) }) + ->second; + } + osg::ref_ptr cnode = mSceneManager->getTemplate(model, false); if (activeGrid) { - if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel)) + if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 + || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) + || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel) + || (cnode->getName() == "Collada visual scene group" + && dynamic_cast(cnode->getUpdateCallback()))) continue; else - refnumSet->mRefnums.insert(pair.first); + refnumSet->mRefnums.push_back(pair.first); } { @@ -540,8 +663,8 @@ namespace MWRender continue; } - float radius2 = cnode->getBound().radius2() * ref.mScale*ref.mScale; - if (radius2 < dSqr*minSize*minSize && !activeGrid) + float radius2 = cnode->getBound().radius2() * ref.mScale * ref.mScale; + if (radius2 < dSqr * minSize * minSize && !activeGrid) { std::lock_guard lock(mSizeCacheMutex); mSizeCache[pair.first] = radius2; @@ -551,7 +674,10 @@ namespace MWRender auto emplaced = nodes.emplace(cnode, InstanceList()); if (emplaced.second) { - const_cast(cnode.get())->accept(analyzeVisitor); // const-trickery required because there is no const version of NodeVisitor + analyzeVisitor.mDistances = LODRange{ smallestDistanceToChunk, higherDistanceToChunk } / ref.mScale; + const_cast(cnode.get()) + ->accept( + analyzeVisitor); // const-trickery required because there is no const version of NodeVisitor emplaced.first->second.mAnalyzeResult = analyzeVisitor.retrieveResult(); emplaced.first->second.mNeedCompile = compile && cnode->referenceCount() <= 3; } @@ -578,7 +704,7 @@ namespace MWRender float minSizeMerged = mMinSize; float factor2 = mergeBenefit > 0 ? std::min(1.f, mergeCost * mMinSizeCostMultiplier / mergeBenefit) : 1; - float minSizeMergeFactor2 = (1-factor2) * mMinSizeMergeFactor + factor2; + float minSizeMergeFactor2 = (1 - factor2) * mMinSizeMergeFactor + factor2; if (minSizeMergeFactor2 > 0) minSizeMerged *= minSizeMergeFactor2; @@ -588,22 +714,49 @@ namespace MWRender const ESM::CellRef& ref = *cref; osg::Vec3f pos = ref.mPos.asVec3(); - if (!activeGrid && minSizeMerged != minSize && cnode->getBound().radius2() * cref->mScale*cref->mScale < (viewPoint-pos).length2()*minSizeMerged*minSizeMerged) + if (!activeGrid && minSizeMerged != minSize + && cnode->getBound().radius2() * cref->mScale * cref->mScale + < (viewPoint - pos).length2() * minSizeMerged * minSizeMerged) continue; - osg::Matrixf matrix; - matrix.preMultTranslate(pos - worldCenter); - matrix.preMultRotate( osg::Quat(ref.mPos.rot[2], osg::Vec3f(0,0,-1)) * - osg::Quat(ref.mPos.rot[1], osg::Vec3f(0,-1,0)) * - osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1,0,0)) ); - matrix.preMultScale(osg::Vec3f(ref.mScale, ref.mScale, ref.mScale)); - osg::ref_ptr trans = new osg::MatrixTransform(matrix); - trans->setDataVariance(osg::Object::STATIC); + osg::Vec3f nodePos = pos - worldCenter; + osg::Quat nodeAttitude = osg::Quat(ref.mPos.rot[2], osg::Vec3f(0, 0, -1)) + * osg::Quat(ref.mPos.rot[1], osg::Vec3f(0, -1, 0)) + * osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1, 0, 0)); + osg::Vec3f nodeScale = osg::Vec3f(ref.mScale, ref.mScale, ref.mScale); - copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES); - copyop.mOptimizeBillboards = (size > 1/4.f); + osg::ref_ptr trans; + if (merge) + { + // Optimizer currently supports only MatrixTransforms. + osg::Matrixf matrix; + matrix.preMultTranslate(nodePos); + matrix.preMultRotate(nodeAttitude); + matrix.preMultScale(nodeScale); + trans = new osg::MatrixTransform(matrix); + trans->setDataVariance(osg::Object::STATIC); + } + else + { + trans = new SceneUtil::PositionAttitudeTransform; + SceneUtil::PositionAttitudeTransform* pat + = static_cast(trans.get()); + pat->setPosition(nodePos); + pat->setScale(nodeScale); + pat->setAttitude(nodeAttitude); + } + + // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is + // generally unsafe. In this specific case the operation is safe under the following two assumptions: + // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must + // outlive the cloned geometry regardless. (ensured by TemplateMultiRef) + // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing + // BufferObjects of the original geometry. (ensured by needvbo() in optimizer.cpp) + copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES + : osg::CopyOp::DEEP_COPY_NODES); + copyop.mOptimizeBillboards = (size > 1 / 4.f); copyop.mNodePath.push_back(trans); - copyop.mSqrDistance = (viewPoint - pos).length2(); + copyop.mDistances = LODRange{ smallestDistanceToChunk, higherDistanceToChunk } / ref.mScale; copyop.mViewVector = (viewPoint - worldCenter); copyop.copy(cnode, trans); copyop.mNodePath.pop_back(); @@ -617,7 +770,8 @@ namespace MWRender } else { - osg::ref_ptr marker = new RefnumMarker; marker->mRefnum = ref.mRefNum; + osg::ref_ptr marker = new RefnumMarker; + marker->mRefnum = ref.mRefNum; trans->getOrCreateUserDataContainer()->addUserObject(marker); } } @@ -628,7 +782,8 @@ namespace MWRender } if (numinstances > 0) { - // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache + // add a ref to the original template to help verify the safety of shallow cloning operations + // in addition, we hint to the cache that it's still being used and should be kept in cache templateRefs->addRef(cnode); if (pair.second.mNeedCompile) @@ -645,14 +800,15 @@ namespace MWRender if (mergeGroup->getNumChildren()) { SceneUtil::Optimizer optimizer; - if (size > 1/8.f) + if (size > 1 / 8.f) { optimizer.setViewPoint(relativeViewPoint); optimizer.setMergeAlphaBlending(true); } optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); - unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY; - mSceneManager->shareState(mergeGroup); + unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS + | SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES | SceneUtil::Optimizer::MERGE_GEOMETRY; + optimizer.optimize(mergeGroup, options); group->addChild(mergeGroup); @@ -682,6 +838,9 @@ namespace MWRender osg::UserDataContainer* udc = group->getOrCreateUserDataContainer(); if (activeGrid) { + std::sort(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()); + refnumSet->mRefnums.erase( + std::unique(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()), refnumSet->mRefnums.end()); udc->addUserObject(refnumSet); group->addCullCallback(new SceneUtil::LightListCallback); } @@ -704,21 +863,19 @@ namespace MWRender } bool intersects(ChunkId id, osg::Vec3f pos) { - if (mActiveGridOnly && !std::get<2>(id)) return false; + if (mActiveGridOnly && !std::get<2>(id)) + return false; pos /= ESM::Land::REAL_SIZE; clampToCell(pos); osg::Vec2f center = std::get<0>(id); - float halfSize = std::get<1>(id)/2; - return pos.x() >= center.x()-halfSize && pos.y() >= center.y()-halfSize && pos.x() <= center.x()+halfSize && pos.y() <= center.y()+halfSize; + float halfSize = std::get<1>(id) / 2; + return pos.x() >= center.x() - halfSize && pos.y() >= center.y() - halfSize + && pos.x() <= center.x() + halfSize && pos.y() <= center.y() + halfSize; } void clampToCell(osg::Vec3f& cellPos) { - osg::Vec2i min (mCell.x(), mCell.y()); - osg::Vec2i max (mCell.x()+1, mCell.y()+1); - if (cellPos.x() < min.x()) cellPos.x() = min.x(); - if (cellPos.x() > max.x()) cellPos.x() = max.x(); - if (cellPos.y() < min.y()) cellPos.y() = min.y(); - if (cellPos.y() > max.y()) cellPos.y() = max.y(); + cellPos.x() = std::clamp(cellPos.x(), mCell.x(), mCell.x() + 1); + cellPos.y() = std::clamp(cellPos.y(), mCell.y(), mCell.y() + 1); } osg::Vec3f mPosition; osg::Vec2i mCell; @@ -726,37 +883,45 @@ namespace MWRender bool mActiveGridOnly = false; }; - bool ObjectPaging::enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled) + bool ObjectPaging::enableObject( + int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled) { if (!typeFilter(type, false)) return false; { std::lock_guard lock(mRefTrackerMutex); - if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) return false; - if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) return false; - if (mRefTrackerLocked) return false; + if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) + return false; + if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) + return false; + if (mRefTrackerLocked) + return false; } ClearCacheFunctor ccf; ccf.mPosition = pos; ccf.mCell = cell; mCache->call(ccf); - if (ccf.mToClear.empty()) return false; + if (ccf.mToClear.empty()) + return false; for (const auto& chunk : ccf.mToClear) mCache->removeFromObjectCache(chunk); return true; } - bool ObjectPaging::blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell) + bool ObjectPaging::blacklistObject( + int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell) { if (!typeFilter(type, false)) return false; { std::lock_guard lock(mRefTrackerMutex); - if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false; - if (mRefTrackerLocked) return false; + if (!getWritableRefTracker().mBlacklist.insert(refnum).second) + return false; + if (mRefTrackerLocked) + return false; } ClearCacheFunctor ccf; @@ -764,13 +929,13 @@ namespace MWRender ccf.mCell = cell; ccf.mActiveGridOnly = true; mCache->call(ccf); - if (ccf.mToClear.empty()) return false; + if (ccf.mToClear.empty()) + return false; for (const auto& chunk : ccf.mToClear) mCache->removeFromObjectCache(chunk); return true; } - void ObjectPaging::clear() { std::lock_guard lock(mRefTrackerMutex); @@ -781,7 +946,8 @@ namespace MWRender bool ObjectPaging::unlockCache() { - if (!mRefTrackerLocked) return false; + if (!mRefTrackerLocked) + return false; { std::lock_guard lock(mRefTrackerMutex); mRefTrackerLocked = false; @@ -796,34 +962,43 @@ namespace MWRender struct GetRefnumsFunctor { - GetRefnumsFunctor(std::set& output) : mOutput(output) {} + GetRefnumsFunctor(std::vector& output) + : mOutput(output) + { + } void operator()(MWRender::ChunkId chunkId, osg::Object* obj) { - if (!std::get<2>(chunkId)) return; + if (!std::get<2>(chunkId)) + return; const osg::Vec2f& center = std::get<0>(chunkId); - bool activeGrid = (center.x() > mActiveGrid.x() || center.y() > mActiveGrid.y() || center.x() < mActiveGrid.z() || center.y() < mActiveGrid.w()); - if (!activeGrid) return; + bool activeGrid = (center.x() > mActiveGrid.x() || center.y() > mActiveGrid.y() + || center.x() < mActiveGrid.z() || center.y() < mActiveGrid.w()); + if (!activeGrid) + return; osg::UserDataContainer* udc = obj->getUserDataContainer(); if (udc && udc->getNumUserObjects()) { RefnumSet* refnums = dynamic_cast(udc->getUserObject(0)); - if (!refnums) return; - mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end()); + if (!refnums) + return; + mOutput.insert(mOutput.end(), refnums->mRefnums.begin(), refnums->mRefnums.end()); } } osg::Vec4i mActiveGrid; - std::set& mOutput; + std::vector& mOutput; }; - void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) + void ObjectPaging::getPagedRefnums(const osg::Vec4i& activeGrid, std::vector& out) { GetRefnumsFunctor grf(out); grf.mActiveGrid = activeGrid; mCache->call(grf); + std::sort(out.begin(), out.end()); + out.erase(std::unique(out.begin(), out.end()), out.end()); } - void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const + void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats* stats) const { stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); } diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index c24cdf4f8..ff84534c3 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_MWRENDER_OBJECTPAGING_H #define OPENMW_MWRENDER_OBJECTPAGING_H -#include +#include #include -#include +#include #include @@ -27,17 +27,20 @@ namespace MWRender ObjectPaging(Resource::SceneManager* sceneManager); ~ObjectPaging() = default; - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, + bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; - osg::ref_ptr createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile); + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, bool activeGrid, + const osg::Vec3f& viewPoint, bool compile, unsigned char lod); unsigned int getNodeMask() override; /// @return true if view needs rebuild - bool enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled); + bool enableObject( + int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled); /// @return true if view needs rebuild - bool blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell); + bool blacklistObject(int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell); void clear(); @@ -47,7 +50,7 @@ namespace MWRender void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; - void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); + void getPagedRefnums(const osg::Vec4i& activeGrid, std::vector& out); private: Resource::SceneManager* mSceneManager; @@ -63,7 +66,10 @@ namespace MWRender { std::set mDisabled; std::set mBlacklist; - bool operator==(const RefTracker&other) const { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } + bool operator==(const RefTracker& other) const + { + return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; + } }; RefTracker mRefTracker; RefTracker mRefTrackerNew; @@ -75,13 +81,25 @@ namespace MWRender std::mutex mSizeCacheMutex; typedef std::map SizeCache; SizeCache mSizeCache; + + std::mutex mLODNameCacheMutex; + typedef std::pair LODNameCacheKey; // Key: mesh name, lod level + typedef std::map LODNameCache; // Cache: key, mesh name to use + LODNameCache mLODNameCache; }; class RefnumMarker : public osg::Object { public: - RefnumMarker() : mNumVertices(0) { mRefnum.unset(); } - RefnumMarker(const RefnumMarker ©, osg::CopyOp co) : mRefnum(copy.mRefnum), mNumVertices(copy.mNumVertices) {} + RefnumMarker() + : mNumVertices(0) + { + } + RefnumMarker(const RefnumMarker& copy, osg::CopyOp co) + : mRefnum(copy.mRefnum) + , mNumVertices(copy.mNumVertices) + { + } META_Object(MWRender, RefnumMarker) ESM::RefNum mRefnum; diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index ec1c4397b..b8b7d6230 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -3,230 +3,240 @@ #include #include +#include +#include #include #include -#include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/ptr.hpp" #include "animation.hpp" -#include "npcanimation.hpp" #include "creatureanimation.hpp" +#include "npcanimation.hpp" #include "vismask.hpp" - namespace MWRender { -Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode, SceneUtil::UnrefQueue* unrefQueue) - : mRootNode(rootNode) - , mResourceSystem(resourceSystem) - , mUnrefQueue(unrefQueue) -{ -} - -Objects::~Objects() -{ - mObjects.clear(); - - for (CellMap::iterator iter = mCellSceneNodes.begin(); iter != mCellSceneNodes.end(); ++iter) - iter->second->getParent(0)->removeChild(iter->second); - mCellSceneNodes.clear(); -} - -void Objects::insertBegin(const MWWorld::Ptr& ptr) -{ - assert(mObjects.find(ptr) == mObjects.end()); - - osg::ref_ptr cellnode; - - CellMap::iterator found = mCellSceneNodes.find(ptr.getCell()); - if (found == mCellSceneNodes.end()) + Objects::Objects(Resource::ResourceSystem* resourceSystem, const osg::ref_ptr& rootNode, + SceneUtil::UnrefQueue& unrefQueue) + : mRootNode(rootNode) + , mResourceSystem(resourceSystem) + , mUnrefQueue(unrefQueue) { - cellnode = new osg::Group; - cellnode->setName("Cell Root"); - mRootNode->addChild(cellnode); - mCellSceneNodes[ptr.getCell()] = cellnode; } - else - cellnode = found->second; - osg::ref_ptr insert (new SceneUtil::PositionAttitudeTransform); - cellnode->addChild(insert); - - insert->getOrCreateUserDataContainer()->addUserObject(new PtrHolder(ptr)); - - const float *f = ptr.getRefData().getPosition().pos; - - insert->setPosition(osg::Vec3(f[0], f[1], f[2])); - - const float scale = ptr.getCellRef().getScale(); - osg::Vec3f scaleVec(scale, scale, scale); - ptr.getClass().adjustScale(ptr, scaleVec, true); - insert->setScale(scaleVec); - - ptr.getRefData().setBaseNode(insert); -} - -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool animated, bool allowLight) -{ - insertBegin(ptr); - ptr.getRefData().getBaseNode()->setNodeMask(Mask_Object); - - osg::ref_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); - - mObjects.insert(std::make_pair(ptr, anim)); -} - -void Objects::insertCreature(const MWWorld::Ptr &ptr, const std::string &mesh, bool weaponsShields) -{ - insertBegin(ptr); - ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); - - // CreatureAnimation - osg::ref_ptr anim; - - if (weaponsShields) - anim = new CreatureWeaponAnimation(ptr, mesh, mResourceSystem); - else - anim = new CreatureAnimation(ptr, mesh, mResourceSystem); - - if (mObjects.insert(std::make_pair(ptr, anim)).second) - ptr.getClass().getContainerStore(ptr).setContListener(static_cast(anim.get())); -} - -void Objects::insertNPC(const MWWorld::Ptr &ptr) -{ - insertBegin(ptr); - ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); - - osg::ref_ptr anim (new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); - - if (mObjects.insert(std::make_pair(ptr, anim)).second) + Objects::~Objects() { - ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get(), ptr); - ptr.getClass().getInventoryStore(ptr).setContListener(anim.get()); + mObjects.clear(); + + for (CellMap::iterator iter = mCellSceneNodes.begin(); iter != mCellSceneNodes.end(); ++iter) + iter->second->getParent(0)->removeChild(iter->second); + mCellSceneNodes.clear(); } -} -bool Objects::removeObject (const MWWorld::Ptr& ptr) -{ - if(!ptr.getRefData().getBaseNode()) - return true; - - PtrAnimationMap::iterator iter = mObjects.find(ptr); - if(iter != mObjects.end()) + void Objects::insertBegin(const MWWorld::Ptr& ptr) { - if (mUnrefQueue.get()) - mUnrefQueue->push(iter->second); + assert(mObjects.find(ptr.mRef) == mObjects.end()); - mObjects.erase(iter); + osg::ref_ptr cellnode; - if (ptr.getClass().isActor()) + CellMap::iterator found = mCellSceneNodes.find(ptr.getCell()); + if (found == mCellSceneNodes.end()) { - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr, ptr); - - ptr.getClass().getContainerStore(ptr).setContListener(nullptr); - } - - ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); - - ptr.getRefData().setBaseNode(nullptr); - return true; - } - return false; -} - - -void Objects::removeCell(const MWWorld::CellStore* store) -{ - for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) - { - MWWorld::Ptr ptr = iter->second->getPtr(); - if(ptr.getCell() == store) - { - if (mUnrefQueue.get()) - mUnrefQueue->push(iter->second); - - if (ptr.getClass().isNpc() && ptr.getRefData().getCustomData()) - { - MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); - invStore.setInvListener(nullptr, ptr); - invStore.setContListener(nullptr); - } - - mObjects.erase(iter++); + cellnode = new osg::Group; + cellnode->setName("Cell Root"); + mRootNode->addChild(cellnode); + mCellSceneNodes[ptr.getCell()] = cellnode; } else - ++iter; + cellnode = found->second; + + osg::ref_ptr insert(new SceneUtil::PositionAttitudeTransform); + cellnode->addChild(insert); + + insert->getOrCreateUserDataContainer()->addUserObject(new PtrHolder(ptr)); + + const float* f = ptr.getRefData().getPosition().pos; + + insert->setPosition(osg::Vec3(f[0], f[1], f[2])); + + const float scale = ptr.getCellRef().getScale(); + osg::Vec3f scaleVec(scale, scale, scale); + ptr.getClass().adjustScale(ptr, scaleVec, true); + insert->setScale(scaleVec); + + ptr.getRefData().setBaseNode(insert); } - CellMap::iterator cell = mCellSceneNodes.find(store); - if(cell != mCellSceneNodes.end()) + void Objects::insertModel(const MWWorld::Ptr& ptr, const std::string& mesh, bool allowLight) { - cell->second->getParent(0)->removeChild(cell->second); - if (mUnrefQueue.get()) - mUnrefQueue->push(cell->second); - mCellSceneNodes.erase(cell); - } -} - -void Objects::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) -{ - osg::Node* objectNode = cur.getRefData().getBaseNode(); - if (!objectNode) - return; - - MWWorld::CellStore *newCell = cur.getCell(); - - osg::Group* cellnode; - if(mCellSceneNodes.find(newCell) == mCellSceneNodes.end()) { - cellnode = new osg::Group; - mRootNode->addChild(cellnode); - mCellSceneNodes[newCell] = cellnode; - } else { - cellnode = mCellSceneNodes[newCell]; - } - - osg::UserDataContainer* userDataContainer = objectNode->getUserDataContainer(); - if (userDataContainer) - for (unsigned int i=0; igetNumUserObjects(); ++i) + insertBegin(ptr); + ptr.getRefData().getBaseNode()->setNodeMask(Mask_Object); + bool animated = ptr.getClass().useAnim(); + std::string animationMesh = mesh; + if (animated && !mesh.empty()) { - if (dynamic_cast(userDataContainer->getUserObject(i))) - userDataContainer->setUserObject(i, new PtrHolder(cur)); + animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + if (animationMesh == mesh && Misc::StringUtils::ciEndsWith(animationMesh, ".nif")) + animated = false; } - if (objectNode->getNumParents()) - objectNode->getParent(0)->removeChild(objectNode); - cellnode->addChild(objectNode); + osg::ref_ptr anim( + new ObjectAnimation(ptr, animationMesh, mResourceSystem, animated, allowLight)); - PtrAnimationMap::iterator iter = mObjects.find(old); - if(iter != mObjects.end()) - { - osg::ref_ptr anim = iter->second; - mObjects.erase(iter); - anim->updatePtr(cur); - mObjects[cur] = anim; + mObjects.emplace(ptr.mRef, std::move(anim)); } -} -Animation* Objects::getAnimation(const MWWorld::Ptr &ptr) -{ - PtrAnimationMap::const_iterator iter = mObjects.find(ptr); - if(iter != mObjects.end()) - return iter->second; + void Objects::insertCreature(const MWWorld::Ptr& ptr, const std::string& mesh, bool weaponsShields) + { + insertBegin(ptr); + ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); - return nullptr; -} + bool animated = true; + std::string animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + if (animationMesh == mesh && Misc::StringUtils::ciEndsWith(animationMesh, ".nif")) + animated = false; -const Animation* Objects::getAnimation(const MWWorld::ConstPtr &ptr) const -{ - PtrAnimationMap::const_iterator iter = mObjects.find(ptr); - if(iter != mObjects.end()) - return iter->second; + // CreatureAnimation + osg::ref_ptr anim; - return nullptr; -} + if (weaponsShields) + anim = new CreatureWeaponAnimation(ptr, animationMesh, mResourceSystem, animated); + else + anim = new CreatureAnimation(ptr, animationMesh, mResourceSystem, animated); + + if (mObjects.emplace(ptr.mRef, anim).second) + ptr.getClass().getContainerStore(ptr).setContListener(static_cast(anim.get())); + } + + void Objects::insertNPC(const MWWorld::Ptr& ptr) + { + insertBegin(ptr); + ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); + + osg::ref_ptr anim( + new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); + + if (mObjects.emplace(ptr.mRef, anim).second) + { + ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get()); + ptr.getClass().getInventoryStore(ptr).setContListener(anim.get()); + } + } + + bool Objects::removeObject(const MWWorld::Ptr& ptr) + { + if (!ptr.getRefData().getBaseNode()) + return true; + + const auto iter = mObjects.find(ptr.mRef); + if (iter != mObjects.end()) + { + iter->second->removeFromScene(); + mUnrefQueue.push(std::move(iter->second)); + mObjects.erase(iter); + + if (ptr.getClass().isActor()) + { + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr); + + ptr.getClass().getContainerStore(ptr).setContListener(nullptr); + } + + ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); + + ptr.getRefData().setBaseNode(nullptr); + return true; + } + return false; + } + + void Objects::removeCell(const MWWorld::CellStore* store) + { + for (PtrAnimationMap::iterator iter = mObjects.begin(); iter != mObjects.end();) + { + MWWorld::Ptr ptr = iter->second->getPtr(); + if (ptr.getCell() == store) + { + if (ptr.getClass().isActor() && ptr.getRefData().getCustomData()) + { + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr); + ptr.getClass().getContainerStore(ptr).setContListener(nullptr); + } + + iter->second->removeFromScene(); + mUnrefQueue.push(std::move(iter->second)); + iter = mObjects.erase(iter); + } + else + ++iter; + } + + CellMap::iterator cell = mCellSceneNodes.find(store); + if (cell != mCellSceneNodes.end()) + { + cell->second->getParent(0)->removeChild(cell->second); + mCellSceneNodes.erase(cell); + } + } + + void Objects::updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& cur) + { + osg::ref_ptr objectNode = cur.getRefData().getBaseNode(); + if (!objectNode) + return; + + MWWorld::CellStore* newCell = cur.getCell(); + + osg::Group* cellnode; + if (mCellSceneNodes.find(newCell) == mCellSceneNodes.end()) + { + cellnode = new osg::Group; + mRootNode->addChild(cellnode); + mCellSceneNodes[newCell] = cellnode; + } + else + { + cellnode = mCellSceneNodes[newCell]; + } + + osg::UserDataContainer* userDataContainer = objectNode->getUserDataContainer(); + if (userDataContainer) + for (unsigned int i = 0; i < userDataContainer->getNumUserObjects(); ++i) + { + if (dynamic_cast(userDataContainer->getUserObject(i))) + userDataContainer->setUserObject(i, new PtrHolder(cur)); + } + + if (objectNode->getNumParents()) + objectNode->getParent(0)->removeChild(objectNode); + cellnode->addChild(objectNode); + + PtrAnimationMap::iterator iter = mObjects.find(old.mRef); + if (iter != mObjects.end()) + iter->second->updatePtr(cur); + } + + Animation* Objects::getAnimation(const MWWorld::Ptr& ptr) + { + PtrAnimationMap::const_iterator iter = mObjects.find(ptr.mRef); + if (iter != mObjects.end()) + return iter->second; + + return nullptr; + } + + const Animation* Objects::getAnimation(const MWWorld::ConstPtr& ptr) const + { + PtrAnimationMap::const_iterator iter = mObjects.find(ptr.mRef); + if (iter != mObjects.end()) + return iter->second; + + return nullptr; + } } diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 98ebb95d9..b4359fdb9 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -4,8 +4,8 @@ #include #include -#include #include +#include #include "../mwworld/ptr.hpp" @@ -29,72 +29,69 @@ namespace SceneUtil class UnrefQueue; } -namespace MWRender{ - -class Animation; - -class PtrHolder : public osg::Object +namespace MWRender { -public: - PtrHolder(const MWWorld::Ptr& ptr) - : mPtr(ptr) + + class Animation; + + class PtrHolder : public osg::Object { - } + public: + PtrHolder(const MWWorld::Ptr& ptr) + : mPtr(ptr) + { + } - PtrHolder() + PtrHolder() {} + + PtrHolder(const PtrHolder& copy, const osg::CopyOp& copyop) + : mPtr(copy.mPtr) + { + } + + META_Object(MWRender, PtrHolder) + + MWWorld::Ptr mPtr; + }; + + class Objects { - } + using PtrAnimationMap = std::map>; - PtrHolder(const PtrHolder& copy, const osg::CopyOp& copyop) - : mPtr(copy.mPtr) - { - } + typedef std::map> CellMap; + CellMap mCellSceneNodes; + PtrAnimationMap mObjects; + osg::ref_ptr mRootNode; + Resource::ResourceSystem* mResourceSystem; + SceneUtil::UnrefQueue& mUnrefQueue; - META_Object(MWRender, PtrHolder) + void insertBegin(const MWWorld::Ptr& ptr); - MWWorld::Ptr mPtr; -}; + public: + Objects(Resource::ResourceSystem* resourceSystem, const osg::ref_ptr& rootNode, + SceneUtil::UnrefQueue& unrefQueue); + ~Objects(); -class Objects{ - typedef std::map > PtrAnimationMap; + /// @param allowLight If false, no lights will be created, and particles systems will be removed. + void insertModel(const MWWorld::Ptr& ptr, const std::string& model, bool allowLight = true); - typedef std::map > CellMap; - CellMap mCellSceneNodes; - PtrAnimationMap mObjects; + void insertNPC(const MWWorld::Ptr& ptr); + void insertCreature(const MWWorld::Ptr& ptr, const std::string& model, bool weaponsShields); - osg::ref_ptr mRootNode; + Animation* getAnimation(const MWWorld::Ptr& ptr); + const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const; - Resource::ResourceSystem* mResourceSystem; + bool removeObject(const MWWorld::Ptr& ptr); + ///< \return found? - osg::ref_ptr mUnrefQueue; + void removeCell(const MWWorld::CellStore* store); - void insertBegin(const MWWorld::Ptr& ptr); + /// Updates containing cell for object rendering data + void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& cur); -public: - Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode, SceneUtil::UnrefQueue* unrefQueue); - ~Objects(); - - /// @param animated Attempt to load separate keyframes from a .kf file matching the model file? - /// @param allowLight If false, no lights will be created, and particles systems will be removed. - void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool animated=false, bool allowLight=true); - - void insertNPC(const MWWorld::Ptr& ptr); - void insertCreature (const MWWorld::Ptr& ptr, const std::string& model, bool weaponsShields); - - Animation* getAnimation(const MWWorld::Ptr &ptr); - const Animation* getAnimation(const MWWorld::ConstPtr &ptr) const; - - bool removeObject (const MWWorld::Ptr& ptr); - ///< \return found? - - void removeCell(const MWWorld::CellStore* store); - - /// Updates containing cell for object rendering data - void updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); - -private: - void operator = (const Objects&); - Objects(const Objects&); -}; + private: + void operator=(const Objects&); + Objects(const Objects&); + }; } #endif diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index c20e81bb2..7e4d85bcc 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -3,150 +3,156 @@ #include #include -#include #include +#include -#include -#include +#include #include +#include +#include +#include -#include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone +#include "../mwmechanics/pathfinding.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/pathfinding.hpp" #include "vismask.hpp" namespace MWRender { -Pathgrid::Pathgrid(osg::ref_ptr root) - : mPathgridEnabled(false) - , mRootNode(root) - , mPathGridRoot(nullptr) - , mInteriorPathgridNode(nullptr) -{ -} - -Pathgrid::~Pathgrid() -{ - if (mPathgridEnabled) + Pathgrid::Pathgrid(osg::ref_ptr root) + : mPathgridEnabled(false) + , mRootNode(root) + , mPathGridRoot(nullptr) + , mInteriorPathgridNode(nullptr) { - togglePathgrid(); } -} - -bool Pathgrid::toggleRenderMode (int mode){ - switch (mode) + Pathgrid::~Pathgrid() { - case Render_Pathgrid: + if (mPathgridEnabled) + { togglePathgrid(); - return mPathgridEnabled; - default: - return false; - } - - return false; -} - -void Pathgrid::addCell(const MWWorld::CellStore *store) -{ - mActiveCells.push_back(store); - if (mPathgridEnabled) - enableCellPathgrid(store); -} - -void Pathgrid::removeCell(const MWWorld::CellStore *store) -{ - mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end()); - if (mPathgridEnabled) - disableCellPathgrid(store); -} - -void Pathgrid::togglePathgrid() -{ - mPathgridEnabled = !mPathgridEnabled; - if (mPathgridEnabled) - { - // add path grid meshes to already loaded cells - mPathGridRoot = new osg::Group; - mPathGridRoot->setNodeMask(Mask_Debug); - mRootNode->addChild(mPathGridRoot); - - for(const MWWorld::CellStore* cell : mActiveCells) - { - enableCellPathgrid(cell); } } - else + + bool Pathgrid::toggleRenderMode(int mode) { - // remove path grid meshes from already loaded cells - for(const MWWorld::CellStore* cell : mActiveCells) + switch (mode) { - disableCellPathgrid(cell); + case Render_Pathgrid: + togglePathgrid(); + return mPathgridEnabled; + default: + return false; } - if (mPathGridRoot) + return false; + } + + void Pathgrid::addCell(const MWWorld::CellStore* store) + { + mActiveCells.push_back(store); + if (mPathgridEnabled) + enableCellPathgrid(store); + } + + void Pathgrid::removeCell(const MWWorld::CellStore* store) + { + mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end()); + if (mPathgridEnabled) + disableCellPathgrid(store); + } + + void Pathgrid::togglePathgrid() + { + mPathgridEnabled = !mPathgridEnabled; + if (mPathgridEnabled) { - mRootNode->removeChild(mPathGridRoot); - mPathGridRoot = nullptr; + // add path grid meshes to already loaded cells + mPathGridRoot = new osg::Group; + mPathGridRoot->setNodeMask(Mask_Debug); + mRootNode->addChild(mPathGridRoot); + + for (const MWWorld::CellStore* cell : mActiveCells) + { + enableCellPathgrid(cell); + } + } + else + { + // remove path grid meshes from already loaded cells + for (const MWWorld::CellStore* cell : mActiveCells) + { + disableCellPathgrid(cell); + } + + if (mPathGridRoot) + { + mRootNode->removeChild(mPathGridRoot); + mPathGridRoot = nullptr; + } } } -} -void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) -{ - MWBase::World* world = MWBase::Environment::get().getWorld(); - const ESM::Pathgrid *pathgrid = - world->getStore().get().search(*store->getCell()); - if (!pathgrid) return; - - osg::Vec3f cellPathGridPos(0, 0, 0); - Misc::CoordinateConverter(store->getCell()).toWorld(cellPathGridPos); - - osg::ref_ptr cellPathGrid = new osg::PositionAttitudeTransform; - cellPathGrid->setPosition(cellPathGridPos); - - osg::ref_ptr geometry = SceneUtil::createPathgridGeometry(*pathgrid); - - cellPathGrid->addChild(geometry); - - mPathGridRoot->addChild(cellPathGrid); - - if (store->getCell()->isExterior()) + void Pathgrid::enableCellPathgrid(const MWWorld::CellStore* store) { - mExteriorPathgridNodes[std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())] = cellPathGrid; - } - else - { - assert(mInteriorPathgridNode == nullptr); - mInteriorPathgridNode = cellPathGrid; - } -} + MWBase::World* world = MWBase::Environment::get().getWorld(); -void Pathgrid::disableCellPathgrid(const MWWorld::CellStore *store) -{ - if (store->getCell()->isExterior()) - { - ExteriorPathgridNodes::iterator it = - mExteriorPathgridNodes.find(std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())); - if (it != mExteriorPathgridNodes.end()) + const ESM::Pathgrid* pathgrid = world->getStore().get().search(*store->getCell()); + if (!pathgrid) + return; + + osg::Vec3f cellPathGridPos(0, 0, 0); + Misc::CoordinateConverter(*store->getCell()).toWorld(cellPathGridPos); + + osg::ref_ptr cellPathGrid = new osg::PositionAttitudeTransform; + cellPathGrid->setPosition(cellPathGridPos); + + osg::ref_ptr geometry = SceneUtil::createPathgridGeometry(*pathgrid); + + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(geometry, "debug"); + + cellPathGrid->addChild(geometry); + + mPathGridRoot->addChild(cellPathGrid); + + if (store->getCell()->isExterior()) { - mPathGridRoot->removeChild(it->second); - mExteriorPathgridNodes.erase(it); + mExteriorPathgridNodes[std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())] + = cellPathGrid; + } + else + { + assert(mInteriorPathgridNode == nullptr); + mInteriorPathgridNode = cellPathGrid; } } - else + + void Pathgrid::disableCellPathgrid(const MWWorld::CellStore* store) { - if (mInteriorPathgridNode) + if (store->getCell()->isExterior()) { - mPathGridRoot->removeChild(mInteriorPathgridNode); - mInteriorPathgridNode = nullptr; + ExteriorPathgridNodes::iterator it = mExteriorPathgridNodes.find( + std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())); + if (it != mExteriorPathgridNodes.end()) + { + mPathGridRoot->removeChild(it->second); + mExteriorPathgridNodes.erase(it); + } + } + else + { + if (mInteriorPathgridNode) + { + mPathGridRoot->removeChild(mInteriorPathgridNode); + mInteriorPathgridNode = nullptr; + } } } -} } diff --git a/apps/openmw/mwrender/pathgrid.hpp b/apps/openmw/mwrender/pathgrid.hpp index 77f1a7f33..ef815d396 100644 --- a/apps/openmw/mwrender/pathgrid.hpp +++ b/apps/openmw/mwrender/pathgrid.hpp @@ -3,8 +3,8 @@ #include -#include #include +#include #include @@ -33,30 +33,29 @@ namespace MWRender void togglePathgrid(); - typedef std::vector CellList; + typedef std::vector CellList; CellList mActiveCells; osg::ref_ptr mRootNode; osg::ref_ptr mPathGridRoot; - typedef std::map, osg::ref_ptr > ExteriorPathgridNodes; + typedef std::map, osg::ref_ptr> ExteriorPathgridNodes; ExteriorPathgridNodes mExteriorPathgridNodes; osg::ref_ptr mInteriorPathgridNode; - void enableCellPathgrid(const MWWorld::CellStore *store); - void disableCellPathgrid(const MWWorld::CellStore *store); + void enableCellPathgrid(const MWWorld::CellStore* store); + void disableCellPathgrid(const MWWorld::CellStore* store); public: Pathgrid(osg::ref_ptr root); ~Pathgrid(); - bool toggleRenderMode (int mode); + bool toggleRenderMode(int mode); void addCell(const MWWorld::CellStore* store); void removeCell(const MWWorld::CellStore* store); }; - } #endif diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp new file mode 100644 index 000000000..6a56f7e5f --- /dev/null +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -0,0 +1,326 @@ +#include "pingpongcanvas.hpp" + +#include +#include +#include + +#include + +#include "postprocessor.hpp" + +namespace MWRender +{ + PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager) + : mFallbackStateSet(new osg::StateSet) + , mMultiviewResolveStateSet(new osg::StateSet) + { + setUseDisplayList(false); + setUseVertexBufferObjects(true); + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-1, -1, 0)); + verts->push_back(osg::Vec3f(-1, 3, 0)); + verts->push_back(osg::Vec3f(3, -1, 0)); + + setVertexArray(verts); + + addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); + + mLuminanceCalculator = LuminanceCalculator(shaderManager); + mLuminanceCalculator.disable(); + + Shader::ShaderManager::DefineMap defines; + Stereo::shaderStereoDefines(defines); + + mFallbackProgram = shaderManager.getProgram("fullscreen_tri"); + + mFallbackStateSet->setAttributeAndModes(mFallbackProgram); + mFallbackStateSet->addUniform(new osg::Uniform("lastShader", 0)); + mFallbackStateSet->addUniform(new osg::Uniform("scaling", osg::Vec2f(1, 1))); + + mMultiviewResolveProgram = shaderManager.getProgram("multiview_resolve"); + mMultiviewResolveStateSet->setAttributeAndModes(mMultiviewResolveProgram); + mMultiviewResolveStateSet->addUniform(new osg::Uniform("lastShader", 0)); + } + + void PingPongCanvas::setCurrentFrameData(size_t frameId, fx::DispatchArray&& data) + { + mBufferData[frameId].data = std::move(data); + } + + void PingPongCanvas::setMask(size_t frameId, bool underwater, bool exterior) + { + mBufferData[frameId].mask = 0; + + mBufferData[frameId].mask + |= underwater ? fx::Technique::Flag_Disable_Underwater : fx::Technique::Flag_Disable_Abovewater; + mBufferData[frameId].mask + |= exterior ? fx::Technique::Flag_Disable_Exteriors : fx::Technique::Flag_Disable_Interiors; + } + + void PingPongCanvas::drawGeometry(osg::RenderInfo& renderInfo) const + { + osg::Geometry::drawImplementation(renderInfo); + } + + static void attachCloneOfTemplate( + osg::FrameBufferObject* fbo, osg::Camera::BufferComponent component, osg::Texture* tex) + { + osg::ref_ptr clone = static_cast(tex->clone(osg::CopyOp::SHALLOW_COPY)); + fbo->setAttachment(component, Stereo::createMultiviewCompatibleAttachment(clone)); + } + + void PingPongCanvas::drawImplementation(osg::RenderInfo& renderInfo) const + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions* ext = state.get(); + + size_t frameId = state.getFrameStamp()->getFrameNumber() % 2; + + auto& bufferData = mBufferData[frameId]; + + const auto& data = bufferData.data; + + std::vector filtered; + + filtered.reserve(data.size()); + + for (size_t i = 0; i < data.size(); ++i) + { + const auto& node = data[i]; + + if (bufferData.mask & node.mFlags) + continue; + + filtered.push_back(i); + } + + auto* resolveViewport = state.getCurrentViewport(); + + if (filtered.empty() || !bufferData.postprocessing) + { + state.pushStateSet(mFallbackStateSet); + state.apply(); + + if (Stereo::getMultiview()) + { + state.pushStateSet(mMultiviewResolveStateSet); + state.apply(); + } + + state.applyTextureAttribute(0, bufferData.sceneTex); + resolveViewport->apply(state); + + drawGeometry(renderInfo); + state.popStateSet(); + + if (Stereo::getMultiview()) + { + state.popStateSet(); + } + + return; + } + + const unsigned int handle = mFbos[0] ? mFbos[0]->getHandle(state.getContextID()) : 0; + + if (handle == 0 || bufferData.dirty) + { + for (auto& fbo : mFbos) + { + fbo = new osg::FrameBufferObject; + attachCloneOfTemplate( + fbo, osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, bufferData.sceneTexLDR); + fbo->apply(state); + glClearColor(0.5, 0.5, 0.5, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + + if (Stereo::getMultiview()) + { + mMultiviewResolveFramebuffer = new osg::FrameBufferObject(); + attachCloneOfTemplate(mMultiviewResolveFramebuffer, + osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, bufferData.sceneTexLDR); + mMultiviewResolveFramebuffer->apply(state); + glClearColor(0.5, 0.5, 0.5, 1); + glClear(GL_COLOR_BUFFER_BIT); + + mMultiviewResolveStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, + (osg::Texture*)mMultiviewResolveFramebuffer->getAttachment(osg::Camera::COLOR_BUFFER0) + .getTexture()); + } + + mLuminanceCalculator.dirty(bufferData.sceneTex->getTextureWidth(), bufferData.sceneTex->getTextureHeight()); + + if (Stereo::getStereo()) + mRenderViewport = new osg::Viewport( + 0, 0, bufferData.sceneTex->getTextureWidth(), bufferData.sceneTex->getTextureHeight()); + else + mRenderViewport = nullptr; + + bufferData.dirty = false; + } + + constexpr std::array, 3> buffers + = { { { GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT2_EXT }, + { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT }, + { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT } } }; + + (bufferData.hdr) ? mLuminanceCalculator.enable() : mLuminanceCalculator.disable(); + + // A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly + // supported, so that's what we use for now. + mLuminanceCalculator.draw(*this, renderInfo, state, ext, frameId); + + auto buffer = buffers[0]; + + int lastDraw = 0; + int lastShader = 0; + + unsigned int lastApplied = handle; + + const unsigned int cid = state.getContextID(); + + const osg::ref_ptr& destinationFbo + = bufferData.destination ? bufferData.destination : nullptr; + unsigned int destinationHandle = destinationFbo ? destinationFbo->getHandle(cid) : 0; + + auto bindDestinationFbo = [&]() { + if (destinationFbo) + { + destinationFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + lastApplied = destinationHandle; + } + else if (Stereo::getMultiview()) + { + mMultiviewResolveFramebuffer->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + lastApplied = mMultiviewResolveFramebuffer->getHandle(cid); + } + else + { + ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0); + + lastApplied = 0; + } + }; + + for (const size_t& index : filtered) + { + const auto& node = data[index]; + + node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, bufferData.depthTex); + + if (bufferData.hdr) + node.mRootStateSet->setTextureAttribute( + PostProcessor::TextureUnits::Unit_EyeAdaptation, mLuminanceCalculator.getLuminanceTexture(frameId)); + + if (bufferData.normalsTex) + node.mRootStateSet->setTextureAttribute( + PostProcessor::TextureUnits::Unit_Normals, bufferData.normalsTex); + + state.pushStateSet(node.mRootStateSet); + state.apply(); + + for (size_t passIndex = 0; passIndex < node.mPasses.size(); ++passIndex) + { + if (mRenderViewport) + mRenderViewport->apply(state); + const auto& pass = node.mPasses[passIndex]; + + bool lastPass = passIndex == node.mPasses.size() - 1; + + // VR-TODO: This won't actually work for tex2darrays + if (lastShader == 0) + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, bufferData.sceneTex); + else + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, + (osg::Texture*)mFbos[lastShader - GL_COLOR_ATTACHMENT0_EXT] + ->getAttachment(osg::Camera::COLOR_BUFFER0) + .getTexture()); + + if (lastDraw == 0) + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, bufferData.sceneTex); + else + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, + (osg::Texture*)mFbos[lastDraw - GL_COLOR_ATTACHMENT0_EXT] + ->getAttachment(osg::Camera::COLOR_BUFFER0) + .getTexture()); + + if (pass.mRenderTarget) + { + pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + if (pass.mRenderTexture->getNumMipmapLevels() > 0) + { + state.setActiveTextureUnit(0); + state.applyTextureAttribute(0, + pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) + .getTexture()); + ext->glGenerateMipmap(GL_TEXTURE_2D); + } + + lastApplied = pass.mRenderTarget->getHandle(state.getContextID()); + ; + } + else if (pass.mResolve && index == filtered.back()) + { + bindDestinationFbo(); + if (!destinationFbo && !Stereo::getMultiview()) + { + resolveViewport->apply(state); + } + } + else if (lastPass) + { + lastDraw = buffer[0]; + lastShader = buffer[0]; + mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + buffer = buffers[lastShader - GL_COLOR_ATTACHMENT0_EXT]; + + lastApplied = mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->getHandle(cid); + } + else + { + mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + lastDraw = buffer[0]; + std::swap(buffer[0], buffer[1]); + + lastApplied = mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->getHandle(cid); + } + + state.pushStateSet(pass.mStateSet); + state.apply(); + + if (!state.getLastAppliedProgramObject()) + mFallbackProgram->apply(state); + + drawGeometry(renderInfo); + + state.popStateSet(); + state.apply(); + } + + state.popStateSet(); + } + + if (Stereo::getMultiview()) + { + ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0); + lastApplied = 0; + + resolveViewport->apply(state); + state.pushStateSet(mMultiviewResolveStateSet); + state.apply(); + + drawGeometry(renderInfo); + + state.popStateSet(); + state.apply(); + } + + if (lastApplied != destinationHandle) + { + bindDestinationFbo(); + } + } +} diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp new file mode 100644 index 000000000..a5557a6d6 --- /dev/null +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -0,0 +1,100 @@ +#ifndef OPENMW_MWRENDER_PINGPONGCANVAS_H +#define OPENMW_MWRENDER_PINGPONGCANVAS_H + +#include +#include + +#include +#include +#include + +#include + +#include "luminancecalculator.hpp" + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class PingPongCanvas : public osg::Geometry + { + public: + PingPongCanvas(Shader::ShaderManager& shaderManager); + + void drawImplementation(osg::RenderInfo& renderInfo) const override; + + void dirty(size_t frameId) { mBufferData[frameId].dirty = true; } + + const fx::DispatchArray& getCurrentFrameData(size_t frame) { return mBufferData[frame % 2].data; } + + // Sets current frame pass data and stores copy of dispatch array to apply to next frame data + void setCurrentFrameData(size_t frameId, fx::DispatchArray&& data); + + void setMask(size_t frameId, bool underwater, bool exterior); + + void setSceneTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].sceneTex = tex; } + + void setLDRSceneTexture(size_t frameId, osg::ref_ptr tex) + { + mBufferData[frameId].sceneTexLDR = tex; + } + + void setDepthTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].depthTex = tex; } + + void setNormalsTexture(size_t frameId, osg::ref_ptr tex) + { + mBufferData[frameId].normalsTex = tex; + } + + void setHDR(size_t frameId, bool hdr) { mBufferData[frameId].hdr = hdr; } + + void setPostProcessing(size_t frameId, bool postprocessing) + { + mBufferData[frameId].postprocessing = postprocessing; + } + + const osg::ref_ptr& getSceneTexture(size_t frameId) const + { + return mBufferData[frameId].sceneTex; + } + + void drawGeometry(osg::RenderInfo& renderInfo) const; + + private: + void copyNewFrameData(size_t frameId) const; + + mutable LuminanceCalculator mLuminanceCalculator; + + osg::ref_ptr mFallbackProgram; + osg::ref_ptr mMultiviewResolveProgram; + osg::ref_ptr mFallbackStateSet; + osg::ref_ptr mMultiviewResolveStateSet; + mutable osg::ref_ptr mMultiviewResolveFramebuffer; + + struct BufferData + { + bool dirty = false; + bool hdr = false; + bool postprocessing = true; + + fx::DispatchArray data; + fx::FlagsType mask; + + osg::ref_ptr destination; + + osg::ref_ptr sceneTex; + osg::ref_ptr depthTex; + osg::ref_ptr sceneTexLDR; + osg::ref_ptr normalsTex; + }; + + mutable std::array mBufferData; + mutable std::array, 3> mFbos; + mutable osg::ref_ptr mRenderViewport; + }; +} + +#endif diff --git a/apps/openmw/mwrender/pingpongcull.cpp b/apps/openmw/mwrender/pingpongcull.cpp new file mode 100644 index 000000000..8dfff5a60 --- /dev/null +++ b/apps/openmw/mwrender/pingpongcull.cpp @@ -0,0 +1,93 @@ +#include "pingpongcull.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include "postprocessor.hpp" + +namespace MWRender +{ + + PingPongCull::PingPongCull(PostProcessor* pp) + : mViewportStateset(nullptr) + , mPostProcessor(pp) + { + if (Stereo::getStereo()) + { + mViewportStateset = new osg::StateSet(); + mViewport = new osg::Viewport(0, 0, pp->renderWidth(), pp->renderHeight()); + mViewportStateset->setAttribute(mViewport); + } + } + + PingPongCull::~PingPongCull() + { + // Instantiate osg::ref_ptr<> destructor + } + + void PingPongCull::operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); + size_t frame = cv->getTraversalNumber(); + size_t frameId = frame % 2; + + MWRender::PostProcessor* postProcessor + = dynamic_cast(cv->getCurrentCamera()->getUserData()); + if (!postProcessor) + throw std::runtime_error("PingPongCull: failed to get a PostProcessor!"); + + if (Stereo::getStereo()) + { + auto& sm = Stereo::Manager::instance(); + auto view = sm.getEye(cv); + int index = view == Stereo::Eye::Right ? 1 : 0; + auto projectionMatrix = sm.computeEyeProjection(index, true); + postProcessor->getStateUpdater()->setProjectionMatrix(projectionMatrix); + } + + postProcessor->getStateUpdater()->setViewMatrix(cv->getCurrentCamera()->getViewMatrix()); + postProcessor->getStateUpdater()->setPrevViewMatrix(mLastViewMatrix[0]); + mLastViewMatrix[0] = cv->getCurrentCamera()->getViewMatrix(); + + postProcessor->getStateUpdater()->setEyePos(cv->getEyePoint()); + postProcessor->getStateUpdater()->setEyeVec(cv->getLookVectorLocal()); + + if (!postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) + { + renderStage->setMultisampleResolveFramebufferObject(nullptr); + renderStage->setFrameBufferObject(nullptr); + } + else if (!postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)) + { + renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); + } + else + { + renderStage->setMultisampleResolveFramebufferObject( + postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); + renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)); + + // The MultiView patch has a bug where it does not update resolve layers if the resolve framebuffer is + // changed. So we do blit manually in this case + if (Stereo::getMultiview() && !renderStage->getDrawCallback()) + Stereo::setMultiviewMSAAResolveCallback(renderStage); + } + + if (mViewportStateset) + { + mViewport->setViewport(0, 0, mPostProcessor->renderWidth(), mPostProcessor->renderHeight()); + renderStage->setViewport(mViewport); + cv->pushStateSet(mViewportStateset.get()); + traverse(node, cv); + cv->popStateSet(); + } + else + traverse(node, cv); + } +} diff --git a/apps/openmw/mwrender/pingpongcull.hpp b/apps/openmw/mwrender/pingpongcull.hpp new file mode 100644 index 000000000..8886ede9e --- /dev/null +++ b/apps/openmw/mwrender/pingpongcull.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_MWRENDER_PINGPONGCULL_H +#define OPENMW_MWRENDER_PINGPONGCULL_H + +#include + +#include + +#include "postprocessor.hpp" + +namespace osg +{ + class StateSet; + class Viewport; +} + +namespace MWRender +{ + class PostProcessor; + class PingPongCull : public SceneUtil::NodeCallback + { + public: + PingPongCull(PostProcessor* pp); + ~PingPongCull(); + + void operator()(osg::Node* node, osgUtil::CullVisitor* nv); + + private: + std::array mLastViewMatrix; + osg::ref_ptr mViewportStateset; + osg::ref_ptr mViewport; + PostProcessor* mPostProcessor; + }; +} + +#endif diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp new file mode 100644 index 000000000..d64e9651b --- /dev/null +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -0,0 +1,902 @@ +#include "postprocessor.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwgui/postprocessorhud.hpp" + +#include "pingpongcull.hpp" +#include "renderingmanager.hpp" +#include "sky.hpp" +#include "transparentpass.hpp" +#include "vismask.hpp" + +namespace +{ + struct ResizedCallback : osg::GraphicsContext::ResizedCallback + { + ResizedCallback(MWRender::PostProcessor* postProcessor) + : mPostProcessor(postProcessor) + { + } + + void resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height) override + { + gc->resizedImplementation(x, y, width, height); + + mPostProcessor->setRenderTargetSize(width, height); + mPostProcessor->resize(); + } + + MWRender::PostProcessor* mPostProcessor; + }; + + class HUDCullCallback : public SceneUtil::NodeCallback + { + public: + void operator()(osg::Camera* camera, osgUtil::CullVisitor* cv) + { + osg::ref_ptr stateset = new osg::StateSet; + auto& sm = Stereo::Manager::instance(); + auto* fullViewport = camera->getViewport(); + if (sm.getEye(cv) == Stereo::Eye::Left) + stateset->setAttributeAndModes( + new osg::Viewport(0, 0, fullViewport->width() / 2, fullViewport->height())); + if (sm.getEye(cv) == Stereo::Eye::Right) + stateset->setAttributeAndModes( + new osg::Viewport(fullViewport->width() / 2, 0, fullViewport->width() / 2, fullViewport->height())); + + cv->pushStateSet(stateset); + traverse(camera, cv); + cv->popStateSet(); + } + }; + + enum class Usage + { + RENDER_BUFFER, + TEXTURE, + }; + + static osg::FrameBufferAttachment createFrameBufferAttachmentFromTemplate( + Usage usage, int width, int height, osg::Texture* template_, int samples) + { + if (usage == Usage::RENDER_BUFFER && !Stereo::getMultiview()) + { + osg::ref_ptr attachment + = new osg::RenderBuffer(width, height, template_->getInternalFormat(), samples); + return osg::FrameBufferAttachment(attachment); + } + + auto texture = Stereo::createMultiviewCompatibleTexture(width, height, samples); + texture->setSourceFormat(template_->getSourceFormat()); + texture->setSourceType(template_->getSourceType()); + texture->setInternalFormat(template_->getInternalFormat()); + texture->setFilter(osg::Texture2D::MIN_FILTER, template_->getFilter(osg::Texture2D::MIN_FILTER)); + texture->setFilter(osg::Texture2D::MAG_FILTER, template_->getFilter(osg::Texture2D::MAG_FILTER)); + texture->setWrap(osg::Texture::WRAP_S, template_->getWrap(osg::Texture2D::WRAP_S)); + texture->setWrap(osg::Texture::WRAP_T, template_->getWrap(osg::Texture2D::WRAP_T)); + + return Stereo::createMultiviewCompatibleAttachment(texture); + } +} + +namespace MWRender +{ + PostProcessor::PostProcessor( + RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs) + : osg::Group() + , mEnableLiveReload(false) + , mRootNode(rootNode) + , mSamples(Settings::Manager::getInt("antialiasing", "Video")) + , mDirty(false) + , mDirtyFrameId(0) + , mRendering(rendering) + , mViewer(viewer) + , mVFS(vfs) + , mTriggerShaderReload(false) + , mReload(false) + , mEnabled(false) + , mUsePostProcessing(false) + , mSoftParticles(false) + , mDisableDepthPasses(false) + , mLastFrameNumber(0) + , mLastSimulationTime(0.f) + , mExteriorFlag(false) + , mUnderwater(false) + , mHDR(false) + , mNormals(false) + , mPrevNormals(false) + , mNormalsSupported(false) + , mPassLights(false) + , mPrevPassLights(false) + { + mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders"); + mUsePostProcessing = Settings::Manager::getBool("enabled", "Post Processing"); + + osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); + osg::GLExtensions* ext = gc->getState()->get(); + + mWidth = gc->getTraits()->width; + mHeight = gc->getTraits()->height; + + if (!ext->glDisablei && ext->glDisableIndexedEXT) + ext->glDisablei = ext->glDisableIndexedEXT; + +#ifdef ANDROID + ext->glDisablei = nullptr; +#endif + + if (ext->glDisablei) + mNormalsSupported = true; + else + Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders."; + + if (mSoftParticles) + { + for (int i = 0; i < 2; ++i) + { + if (Stereo::getMultiview()) + mTextures[i][Tex_OpaqueDepth] = new osg::Texture2DArray; + else + mTextures[i][Tex_OpaqueDepth] = new osg::Texture2D; + mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + } + } + + mGLSLVersion = ext->glslLanguageVersion * 100; + mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; + mStateUpdater = new fx::StateUpdater(mUBO); + + if (!Stereo::getStereo() && !SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !mUsePostProcessing) + return; + + enable(mUsePostProcessing); + } + + PostProcessor::~PostProcessor() + { + if (auto* bin = osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")) + bin->setDrawCallback(nullptr); + } + + void PostProcessor::resize() + { + mHUDCamera->resize(mWidth, mHeight); + mViewer->getCamera()->resize(mWidth, mHeight); + if (Stereo::getStereo()) + Stereo::Manager::instance().screenResolutionChanged(); + + auto width = renderWidth(); + auto height = renderHeight(); + for (auto& technique : mTechniques) + { + for (auto& [name, rt] : technique->getRenderTargetsMap()) + { + const auto [w, h] = rt.mSize.get(width, height); + rt.mTarget->setTextureSize(w, h); + } + } + + size_t frameId = frame() % 2; + + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); + + mRendering.updateProjectionMatrix(); + mRendering.setScreenRes(width, height); + + dirtyTechniques(); + + mPingPongCanvas->dirty(frameId); + + mDirty = true; + mDirtyFrameId = !frameId; + } + + void PostProcessor::populateTechniqueFiles() + { + for (const auto& name : mVFS->getRecursiveDirectoryIterator(fx::Technique::sSubdir)) + { + std::filesystem::path path = Files::pathFromUnicodeString(name); + std::string fileExt = Misc::StringUtils::lowerCase(Files::pathToUnicodeString(path.extension())); + if (!path.parent_path().has_parent_path() && fileExt == fx::Technique::sExt) + { + const auto absolutePath = mVFS->getAbsoluteFileName(path); + mTechniqueFileMap[Files::pathToUnicodeString(absolutePath.stem())] = absolutePath; + } + } + } + + void PostProcessor::enable(bool usePostProcessing) + { + mReload = true; + mEnabled = true; + bool postPass = Settings::Manager::getBool("transparent postpass", "Post Processing"); + mUsePostProcessing = usePostProcessing; + + mDisableDepthPasses = !mSoftParticles && !postPass; + +#ifdef ANDROID + mDisableDepthPasses = true; +#endif + + if (!mDisableDepthPasses) + { + mTransparentDepthPostPass = new TransparentDepthBinCallback( + mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), postPass); + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); + } + + if (mUsePostProcessing && mTechniqueFileMap.empty()) + { + populateTechniqueFiles(); + } + + createTexturesAndCamera(frame() % 2); + + removeChild(mHUDCamera); + removeChild(mRootNode); + + addChild(mHUDCamera); + addChild(mRootNode); + + mViewer->setSceneData(this); + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); + mViewer->getCamera()->setUserData(this); + + setCullCallback(mStateUpdater); + mHUDCamera->setCullCallback(new HUDCullCallback); + } + + void PostProcessor::disable() + { + if (!mSoftParticles) + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(nullptr); + + if (!SceneUtil::AutoDepth::isReversed() && !mSoftParticles) + { + removeChild(mHUDCamera); + setCullCallback(nullptr); + + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); + mViewer->getCamera()->getGraphicsContext()->setResizedCallback(nullptr); + mViewer->getCamera()->setUserData(nullptr); + + mEnabled = false; + } + + mUsePostProcessing = false; + mRendering.getSkyManager()->setSunglare(true); + } + + void PostProcessor::traverse(osg::NodeVisitor& nv) + { + if (!mEnabled) + { + osg::Group::traverse(nv); + return; + } + + size_t frameId = nv.getTraversalNumber() % 2; + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + cull(frameId, static_cast(&nv)); + else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + update(frameId); + + osg::Group::traverse(nv); + } + + void PostProcessor::cull(size_t frameId, osgUtil::CullVisitor* cv) + { + const auto& fbo = getFbo(FBO_Intercept, frameId); + if (fbo) + { + osgUtil::RenderStage* rs = cv->getRenderStage(); + if (rs && rs->getMultisampleResolveFramebufferObject()) + rs->setMultisampleResolveFramebufferObject(fbo); + } + + mPingPongCanvas->setPostProcessing(frameId, mUsePostProcessing); + mPingPongCanvas->setNormalsTexture(frameId, mNormals ? getTexture(Tex_Normal, frameId) : nullptr); + mPingPongCanvas->setMask(frameId, mUnderwater, mExteriorFlag); + mPingPongCanvas->setHDR(frameId, getHDR()); + + mPingPongCanvas->setSceneTexture(frameId, getTexture(Tex_Scene, frameId)); + if (mDisableDepthPasses) + mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_Depth, frameId)); + else + mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_OpaqueDepth, frameId)); + + mPingPongCanvas->setLDRSceneTexture(frameId, getTexture(Tex_Scene_LDR, frameId)); + + if (mTransparentDepthPostPass) + { + mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; + mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; + mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; + } + + size_t frame = cv->getTraversalNumber(); + + mStateUpdater->setResolution(osg::Vec2f(cv->getViewport()->width(), cv->getViewport()->height())); + + // per-frame data + if (frame != mLastFrameNumber) + { + mLastFrameNumber = frame; + auto stamp = cv->getFrameStamp(); + + mStateUpdater->setSimulationTime(static_cast(stamp->getSimulationTime())); + mStateUpdater->setDeltaSimulationTime(static_cast(stamp->getSimulationTime() - mLastSimulationTime)); + mLastSimulationTime = stamp->getSimulationTime(); + + for (const auto& dispatchNode : mPingPongCanvas->getCurrentFrameData(frame)) + { + for (auto& uniform : dispatchNode.mHandle->getUniformMap()) + { + if (uniform->getType().has_value() && !uniform->mSamplerType) + if (auto* u = dispatchNode.mRootStateSet->getUniform(uniform->mName)) + uniform->setUniform(u); + } + } + } + } + + void PostProcessor::updateLiveReload() + { + if (!mEnableLiveReload && !mTriggerShaderReload) + return; + + mTriggerShaderReload = false; // Done only once + + for (auto& technique : mTechniques) + { + if (technique->getStatus() == fx::Technique::Status::File_Not_exists) + continue; + + const auto lastWriteTime = std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]); + const bool isDirty = technique->setLastModificationTime(lastWriteTime); + + if (!isDirty) + continue; + + // TODO: Temporary workaround to avoid conflicts with external programs saving the file, especially + // problematic on Windows. + // If we move to a file watcher using native APIs this should be removed. + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + if (technique->compile()) + Log(Debug::Info) << "Reloaded technique : " << mTechniqueFileMap[technique->getName()]; + + mReload = technique->isValid(); + } + } + + void PostProcessor::reloadIfRequired() + { + if (!mReload) + return; + + mReload = false; + + loadChain(); + resize(); + } + + void PostProcessor::update(size_t frameId) + { + while (!mQueuedTemplates.empty()) + { + mTemplates.push_back(std::move(mQueuedTemplates.back())); + + mQueuedTemplates.pop_back(); + } + + updateLiveReload(); + + reloadIfRequired(); + + if (mDirty && mDirtyFrameId == frameId) + { + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); + mDirty = false; + + mPingPongCanvas->setCurrentFrameData(frameId, fx::DispatchArray(mTemplateData)); + } + + if ((mNormalsSupported && mNormals != mPrevNormals) || (mPassLights != mPrevPassLights)) + { + mPrevNormals = mNormals; + mPrevPassLights = mPassLights; + + mViewer->stopThreading(); + + auto& shaderManager = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager(); + auto defines = shaderManager.getGlobalDefines(); + defines["disableNormals"] = mNormals ? "0" : "1"; + shaderManager.setGlobalDefines(defines); + + mRendering.getLightRoot()->setCollectPPLights(mPassLights); + mStateUpdater->bindPointLights(mPassLights ? mRendering.getLightRoot()->getPPLightsBuffer() : nullptr); + mStateUpdater->reset(); + + mViewer->startThreading(); + + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); + + mDirty = true; + mDirtyFrameId = !frameId; + } + } + + void PostProcessor::createObjectsForFrame(size_t frameId) + { + auto& fbos = mFbos[frameId]; + auto& textures = mTextures[frameId]; + auto width = renderWidth(); + auto height = renderHeight(); + + for (auto& tex : textures) + { + if (!tex) + continue; + + Stereo::setMultiviewCompatibleTextureSize(tex, width, height); + tex->dirtyTextureObject(); + } + + fbos[FBO_Primary] = new osg::FrameBufferObject; + fbos[FBO_Primary]->setAttachment( + osg::Camera::COLOR_BUFFER0, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Scene])); + if (mNormals && mNormalsSupported) + fbos[FBO_Primary]->setAttachment( + osg::Camera::COLOR_BUFFER1, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); + fbos[FBO_Primary]->setAttachment( + osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Depth])); + + fbos[FBO_FirstPerson] = new osg::FrameBufferObject; + + auto fpDepthRb = createFrameBufferAttachmentFromTemplate( + Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, + osg::FrameBufferAttachment(fpDepthRb)); + + if (mSamples > 1) + { + fbos[FBO_Multisample] = new osg::FrameBufferObject; + auto colorRB = createFrameBufferAttachmentFromTemplate( + Usage::RENDER_BUFFER, width, height, textures[Tex_Scene], mSamples); + if (mNormals && mNormalsSupported) + { + auto normalRB = createFrameBufferAttachmentFromTemplate( + Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples); + fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); + } + auto depthRB = createFrameBufferAttachmentFromTemplate( + Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); + fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, colorRB); + fbos[FBO_Multisample]->setAttachment( + osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, depthRB); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, colorRB); + + fbos[FBO_Intercept] = new osg::FrameBufferObject; + fbos[FBO_Intercept]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_Scene])); + fbos[FBO_Intercept]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); + } + else + { + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_Scene])); + if (mNormals && mNormalsSupported) + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); + } + + if (textures[Tex_OpaqueDepth]) + { + fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; + fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth])); + } + +#ifdef __APPLE__ + if (textures[Tex_OpaqueDepth]) + fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, + osg::FrameBufferAttachment(new osg::RenderBuffer(textures[Tex_OpaqueDepth]->getTextureWidth(), + textures[Tex_OpaqueDepth]->getTextureHeight(), textures[Tex_Scene]->getInternalFormat()))); +#endif + } + + void PostProcessor::dirtyTechniques() + { + if (!isEnabled()) + return; + + size_t frameId = frame() % 2; + + mDirty = true; + mDirtyFrameId = !frameId; + + mTemplateData = {}; + + bool sunglare = true; + mHDR = false; + mNormals = false; + mPassLights = false; + + for (const auto& technique : mTechniques) + { + if (!technique->isValid()) + continue; + + if (technique->getGLSLVersion() > mGLSLVersion) + { + Log(Debug::Warning) << "Technique " << technique->getName() << " requires GLSL version " + << technique->getGLSLVersion() << " which is unsupported by your hardware."; + continue; + } + + fx::DispatchNode node; + + node.mFlags = technique->getFlags(); + + if (technique->getHDR()) + mHDR = true; + + if (technique->getNormals()) + mNormals = true; + + if (technique->getLights()) + mPassLights = true; + + if (node.mFlags & fx::Technique::Flag_Disable_SunGlare) + sunglare = false; + + // required default samplers available to every shader pass + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", Unit_LastShader)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastPass", Unit_LastPass)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth)); + + if (mNormals) + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals)); + + if (technique->getHDR()) + node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation)); + + int texUnit = Unit_NextFree; + + // user-defined samplers + for (const osg::Texture* texture : technique->getTextures()) + { + if (const auto* tex1D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture1D(*tex1D)); + else if (const auto* tex2D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture2D(*tex2D)); + else if (const auto* tex3D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture3D(*tex3D)); + + node.mRootStateSet->addUniform(new osg::Uniform(texture->getName().c_str(), texUnit++)); + } + + // user-defined uniforms + for (auto& uniform : technique->getUniformMap()) + { + if (uniform->mSamplerType) + continue; + + if (auto type = uniform->getType()) + uniform->setUniform(node.mRootStateSet->getOrCreateUniform( + uniform->mName.c_str(), *type, uniform->getNumElements())); + } + + std::unordered_map renderTargetCache; + + for (const auto& pass : technique->getPasses()) + { + int subTexUnit = texUnit; + fx::DispatchNode::SubPass subPass; + + pass->prepareStateSet(subPass.mStateSet, technique->getName()); + + node.mHandle = technique; + + if (!pass->getTarget().empty()) + { + const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()]; + + const auto [w, h] = rt.mSize.get(renderWidth(), renderHeight()); + + subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget); + renderTargetCache[rt.mTarget] = subPass.mRenderTexture; + subPass.mRenderTexture->setTextureSize(w, h); + subPass.mRenderTexture->setName(std::string(pass->getTarget())); + + if (rt.mMipMap) + subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + + subPass.mRenderTarget = new osg::FrameBufferObject; + subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(subPass.mRenderTexture)); + subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); + } + + for (const auto& whitelist : pass->getRenderTargets()) + { + auto it = technique->getRenderTargetsMap().find(whitelist); + if (it != technique->getRenderTargetsMap().end() && renderTargetCache[it->second.mTarget]) + { + subPass.mStateSet->setTextureAttribute(subTexUnit, renderTargetCache[it->second.mTarget]); + subPass.mStateSet->addUniform(new osg::Uniform(std::string(it->first).c_str(), subTexUnit++)); + } + } + + node.mPasses.emplace_back(std::move(subPass)); + } + + node.compile(); + + mTemplateData.emplace_back(std::move(node)); + } + + mPingPongCanvas->setCurrentFrameData(frameId, fx::DispatchArray(mTemplateData)); + + if (auto hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) + hud->updateTechniques(); + + mRendering.getSkyManager()->setSunglare(sunglare); + } + + PostProcessor::Status PostProcessor::enableTechnique( + std::shared_ptr technique, std::optional location) + { + if (!isEnabled()) + { + Log(Debug::Warning) << "PostProcessing disabled, cannot load technique '" << technique->getName() << "'"; + return Status_Error; + } + + if (!technique || technique->getLocked() || (location.has_value() && location.value() < 0)) + return Status_Error; + + disableTechnique(technique, false); + + int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); + + mTechniques.insert(mTechniques.begin() + pos, technique); + dirtyTechniques(); + + return Status_Toggled; + } + + PostProcessor::Status PostProcessor::disableTechnique(std::shared_ptr technique, bool dirty) + { + if (technique->getLocked()) + return Status_Error; + + auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); + if (it == std::end(mTechniques)) + return Status_Unchanged; + + mTechniques.erase(it); + if (dirty) + dirtyTechniques(); + + return Status_Toggled; + } + + bool PostProcessor::isTechniqueEnabled(const std::shared_ptr& technique) const + { + if (auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); it == mTechniques.end()) + return false; + + return technique->isValid(); + } + + void PostProcessor::createTexturesAndCamera(size_t frameId) + { + auto& textures = mTextures[frameId]; + + auto width = renderWidth(); + auto height = renderHeight(); + + for (auto& texture : textures) + { + if (!texture) + { + if (Stereo::getMultiview()) + texture = new osg::Texture2DArray; + else + texture = new osg::Texture2D; + } + Stereo::setMultiviewCompatibleTextureSize(texture, width, height); + texture->setSourceFormat(GL_RGBA); + texture->setSourceType(GL_UNSIGNED_BYTE); + texture->setInternalFormat(GL_RGBA); + texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setResizeNonPowerOfTwoHint(false); + } + + textures[Tex_Normal]->setSourceFormat(GL_RGB); + textures[Tex_Normal]->setInternalFormat(GL_RGB); + + auto setupDepth = [](osg::Texture* tex) { + tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); + tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); + tex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); + }; + + setupDepth(textures[Tex_Depth]); + + if (mDisableDepthPasses) + { + textures[Tex_OpaqueDepth] = nullptr; + } + else + { + setupDepth(textures[Tex_OpaqueDepth]); + textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); + } + + if (mHUDCamera) + return; + + mHUDCamera = new osg::Camera; + mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); + mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); + mHUDCamera->setClearMask(0); + mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); + mHUDCamera->setAllowEventFocus(false); + mHUDCamera->setViewport(0, 0, mWidth, mHeight); + + mViewer->getCamera()->removeCullCallback(mPingPongCull); + mPingPongCull = new PingPongCull(this); + mViewer->getCamera()->addCullCallback(mPingPongCull); + + mPingPongCanvas = new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()); + mHUDCamera->addChild(mPingPongCanvas); + mHUDCamera->setNodeMask(Mask_RenderToTexture); + + mHUDCamera->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + mHUDCamera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } + + std::shared_ptr PostProcessor::loadTechnique(const std::string& name, bool loadNextFrame) + { + if (!isEnabled()) + { + Log(Debug::Warning) << "PostProcessing disabled, cannot load technique '" << name << "'"; + return nullptr; + } + + for (const auto& technique : mTemplates) + if (Misc::StringUtils::ciEqual(technique->getName(), name)) + return technique; + + for (const auto& technique : mQueuedTemplates) + if (Misc::StringUtils::ciEqual(technique->getName(), name)) + return technique; + + auto technique = std::make_shared(*mVFS, *mRendering.getResourceSystem()->getImageManager(), + name, renderWidth(), renderHeight(), mUBO, mNormalsSupported); + + technique->compile(); + + if (technique->getStatus() != fx::Technique::Status::File_Not_exists) + technique->setLastModificationTime( + std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()])); + + if (loadNextFrame) + { + mQueuedTemplates.push_back(technique); + return technique; + } + + mTemplates.push_back(std::move(technique)); + + return mTemplates.back(); + } + + void PostProcessor::loadChain() + { + if (!isEnabled()) + return; + + mTechniques.clear(); + + std::vector techniqueStrings = Settings::Manager::getStringArray("chain", "Post Processing"); + + for (auto& techniqueName : techniqueStrings) + { + if (techniqueName.empty()) + continue; + + mTechniques.push_back(loadTechnique(techniqueName)); + } + + dirtyTechniques(); + } + + void PostProcessor::saveChain() + { + std::vector chain; + + for (const auto& technique : mTechniques) + { + if (!technique || technique->getDynamic()) + continue; + chain.push_back(technique->getName()); + } + + Settings::Manager::setStringArray("chain", "Post Processing", chain); + } + + void PostProcessor::toggleMode() + { + for (auto& technique : mTemplates) + technique->compile(); + + dirtyTechniques(); + } + + void PostProcessor::disableDynamicShaders() + { + for (auto& technique : mTechniques) + if (technique->getDynamic()) + disableTechnique(technique); + } + + int PostProcessor::renderWidth() const + { + if (Stereo::getStereo()) + return Stereo::Manager::instance().eyeResolution().x(); + return mWidth; + } + + int PostProcessor::renderHeight() const + { + if (Stereo::getStereo()) + return Stereo::Manager::instance().eyeResolution().y(); + return mHeight; + } + + void PostProcessor::triggerShaderReload() + { + mTriggerShaderReload = true; + } +} diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp new file mode 100644 index 000000000..fa230ec2e --- /dev/null +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -0,0 +1,279 @@ +#ifndef OPENMW_MWRENDER_POSTPROCESSOR_H +#define OPENMW_MWRENDER_POSTPROCESSOR_H + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "pingpongcanvas.hpp" +#include "transparentpass.hpp" + +#include + +namespace osgViewer +{ + class Viewer; +} + +namespace Stereo +{ + class MultiviewFramebuffer; +} + +namespace VFS +{ + class Manager; +} + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class RenderingManager; + class PingPongCull; + class PingPongCanvas; + class TransparentDepthBinCallback; + + class PostProcessor : public osg::Group + { + public: + using FBOArray = std::array, 5>; + using TextureArray = std::array, 5>; + using TechniqueList = std::vector>; + + enum TextureIndex + { + Tex_Scene, + Tex_Scene_LDR, + Tex_Depth, + Tex_OpaqueDepth, + Tex_Normal + }; + + enum FBOIndex + { + FBO_Primary, + FBO_Multisample, + FBO_FirstPerson, + FBO_OpaqueDepth, + FBO_Intercept + }; + + enum TextureUnits + { + Unit_LastShader = 0, + Unit_LastPass, + Unit_Depth, + Unit_EyeAdaptation, + Unit_Normals, + Unit_NextFree + }; + + enum Status + { + Status_Error, + Status_Toggled, + Status_Unchanged + }; + + PostProcessor( + RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs); + + ~PostProcessor(); + + void traverse(osg::NodeVisitor& nv) override; + + osg::ref_ptr getFbo(FBOIndex index, unsigned int frameId) + { + return mFbos[frameId][index]; + } + + osg::ref_ptr getTexture(TextureIndex index, unsigned int frameId) + { + return mTextures[frameId][index]; + } + + osg::ref_ptr getPrimaryFbo(unsigned int frameId) + { + return mFbos[frameId][FBO_Multisample] ? mFbos[frameId][FBO_Multisample] : mFbos[frameId][FBO_Primary]; + } + + osg::ref_ptr getStateUpdater() { return mStateUpdater; } + + const TechniqueList& getTechniques() { return mTechniques; } + + const TechniqueList& getTemplates() const { return mTemplates; } + + osg::ref_ptr getCanvas() { return mPingPongCanvas; } + + const auto& getTechniqueMap() const { return mTechniqueFileMap; } + + void resize(); + + Status enableTechnique(std::shared_ptr technique, std::optional location = std::nullopt); + + Status disableTechnique(std::shared_ptr technique, bool dirty = true); + + bool getSupportsNormalsRT() const { return mNormalsSupported; } + + template + void setUniform(std::shared_ptr technique, const std::string& name, const T& value) + { + if (!isEnabled()) + return; + + auto it = technique->findUniform(name); + + if (it == technique->getUniformMap().end()) + return; + + if ((*it)->mStatic) + { + Log(Debug::Warning) << "Attempting to set a configration variable [" << name << "] as a uniform"; + return; + } + + (*it)->setValue(value); + } + + std::optional getUniformSize(std::shared_ptr technique, const std::string& name) + { + auto it = technique->findUniform(name); + + if (it == technique->getUniformMap().end()) + return std::nullopt; + + return (*it)->getNumElements(); + } + + bool isTechniqueEnabled(const std::shared_ptr& technique) const; + + void setExteriorFlag(bool exterior) { mExteriorFlag = exterior; } + + void setUnderwaterFlag(bool underwater) { mUnderwater = underwater; } + + void toggleMode(); + + std::shared_ptr loadTechnique(const std::string& name, bool loadNextFrame = false); + + bool isEnabled() const { return mUsePostProcessing && mEnabled; } + + bool softParticlesEnabled() const { return mSoftParticles; } + + bool getHDR() const { return mHDR; } + + void disable(); + + void enable(bool usePostProcessing = true); + + void setRenderTargetSize(int width, int height) + { + mWidth = width; + mHeight = height; + } + + void disableDynamicShaders(); + + int renderWidth() const; + int renderHeight() const; + + void triggerShaderReload(); + + bool mEnableLiveReload; + + void loadChain(); + void saveChain(); + + private: + void populateTechniqueFiles(); + + size_t frame() const { return mViewer->getFrameStamp()->getFrameNumber(); } + + void createObjectsForFrame(size_t frameId); + + void createTexturesAndCamera(size_t frameId); + + void reloadMainPass(fx::Technique& technique); + + void dirtyTechniques(); + + void update(size_t frameId); + + void reloadIfRequired(); + + void updateLiveReload(); + + void cull(size_t frameId, osgUtil::CullVisitor* cv); + + osg::ref_ptr mRootNode; + osg::ref_ptr mHUDCamera; + + std::array mTextures; + std::array mFbos; + + TechniqueList mTechniques; + TechniqueList mTemplates; + TechniqueList mQueuedTemplates; + + std::unordered_map mTechniqueFileMap; + + int mSamples; + + bool mDirty; + size_t mDirtyFrameId; + + RenderingManager& mRendering; + osgViewer::Viewer* mViewer; + const VFS::Manager* mVFS; + + bool mTriggerShaderReload; + bool mReload; + bool mEnabled; + bool mUsePostProcessing; + bool mSoftParticles; + bool mDisableDepthPasses; + + size_t mLastFrameNumber; + float mLastSimulationTime; + + bool mExteriorFlag; + bool mUnderwater; + bool mHDR; + bool mNormals; + bool mPrevNormals; + bool mNormalsSupported; + bool mPassLights; + bool mPrevPassLights; + bool mUBO; + int mGLSLVersion; + + osg::ref_ptr mStateUpdater; + osg::ref_ptr mPingPongCull; + osg::ref_ptr mPingPongCanvas; + osg::ref_ptr mTransparentDepthPostPass; + + int mWidth; + int mHeight; + + fx::DispatchArray mTemplateData; + }; +} + +#endif diff --git a/apps/openmw/mwrender/precipitationocclusion.cpp b/apps/openmw/mwrender/precipitationocclusion.cpp new file mode 100644 index 000000000..ad3e6d8f0 --- /dev/null +++ b/apps/openmw/mwrender/precipitationocclusion.cpp @@ -0,0 +1,172 @@ +#include "precipitationocclusion.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "vismask.hpp" + +namespace +{ + class PrecipitationOcclusionUpdater : public SceneUtil::StateSetUpdater + { + public: + PrecipitationOcclusionUpdater(osg::ref_ptr depthTexture) + : mDepthTexture(depthTexture) + { + } + + private: + void setDefaults(osg::StateSet* stateset) override + { + stateset->setTextureAttributeAndModes(3, mDepthTexture); + stateset->addUniform(new osg::Uniform("orthoDepthMap", 3)); + stateset->addUniform(new osg::Uniform("depthSpaceMatrix", mDepthSpaceMatrix)); + } + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + osg::Camera* camera = nv->asCullVisitor()->getCurrentCamera(); + stateset->getUniform("depthSpaceMatrix")->set(camera->getViewMatrix() * camera->getProjectionMatrix()); + } + + osg::Matrixf mDepthSpaceMatrix; + osg::ref_ptr mDepthTexture; + }; + + class DepthCameraUpdater : public SceneUtil::StateSetUpdater + { + public: + DepthCameraUpdater() + : mDummyTexture(new osg::Texture2D) + { + mDummyTexture->setInternalFormat(GL_RGB); + mDummyTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mDummyTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mDummyTexture->setTextureSize(1, 1); + + Shader::ShaderManager& shaderMgr + = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager(); + mProgram = shaderMgr.getProgram("depthclipped"); + } + + private: + void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf())); + stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(0, mDummyTexture); + stateset->setRenderBinDetails( + osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); + } + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + osg::Camera* camera = nv->asCullVisitor()->getCurrentCamera(); + stateset->getUniform("projectionMatrix")->set(camera->getProjectionMatrix()); + } + + osg::Matrixf mProjectionMatrix; + osg::ref_ptr mDummyTexture; + osg::ref_ptr mProgram; + }; +} + +namespace MWRender +{ + PrecipitationOccluder::PrecipitationOccluder( + osg::Group* skyNode, osg::Group* sceneNode, osg::Group* rootNode, osg::Camera* camera) + : mSkyNode(skyNode) + , mSceneNode(sceneNode) + , mRootNode(rootNode) + , mSceneCamera(camera) + { + constexpr int rttSize = 256; + + mDepthTexture = new osg::Texture2D; + mDepthTexture->setTextureSize(rttSize, rttSize); + mDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); + mDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24); + mDepthTexture->setSourceType(GL_UNSIGNED_INT); + mDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); + mDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); + mDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mDepthTexture->setBorderColor( + SceneUtil::AutoDepth::isReversed() ? osg::Vec4(0, 0, 0, 0) : osg::Vec4(1, 1, 1, 1)); + + mCamera = new osg::Camera; + mCamera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + mCamera->setRenderOrder(osg::Camera::PRE_RENDER); + mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + mCamera->setNodeMask(Mask_RenderToTexture); + mCamera->setCullMask(Mask_Scene | Mask_Object | Mask_Static); + mCamera->setViewport(0, 0, rttSize, rttSize); + mCamera->attach(osg::Camera::DEPTH_BUFFER, mDepthTexture); + mCamera->addChild(mSceneNode); + mCamera->setSmallFeatureCullingPixelSize( + Settings::Manager::getFloat("weather particle occlusion small feature culling pixel size", "Shaders")); + + SceneUtil::setCameraClearDepth(mCamera); + } + + void PrecipitationOccluder::update() + { + const osg::Vec3 pos = mSceneCamera->getInverseViewMatrix().getTrans(); + + const float zmin = pos.z() - mRange.z() - Constants::CellSizeInUnits; + const float zmax = pos.z() + mRange.z() + Constants::CellSizeInUnits; + const float near = 0; + const float far = zmax - zmin; + + const float left = -mRange.x() / 2; + const float right = -left; + const float top = mRange.y() / 2; + const float bottom = -top; + + if (SceneUtil::AutoDepth::isReversed()) + { + mCamera->setProjectionMatrix( + SceneUtil::getReversedZProjectionMatrixAsOrtho(left, right, bottom, top, near, far)); + } + else + { + mCamera->setProjectionMatrix(osg::Matrixf::ortho(left, right, bottom, top, near, far)); + } + + mCamera->setViewMatrixAsLookAt( + osg::Vec3(pos.x(), pos.y(), zmax), osg::Vec3(pos.x(), pos.y(), zmin), osg::Vec3(0, 1, 0)); + } + + void PrecipitationOccluder::enable() + { + mSkyCullCallback = new PrecipitationOcclusionUpdater(mDepthTexture); + mSkyNode->addCullCallback(mSkyCullCallback); + mCamera->setCullCallback(new DepthCameraUpdater); + + mRootNode->removeChild(mCamera); + mRootNode->addChild(mCamera); + } + + void PrecipitationOccluder::disable() + { + mSkyNode->removeCullCallback(mSkyCullCallback); + mCamera->setCullCallback(nullptr); + mSkyCullCallback = nullptr; + + mRootNode->removeChild(mCamera); + } + + void PrecipitationOccluder::updateRange(const osg::Vec3f range) + { + const osg::Vec3f margin = { -50, -50, 0 }; + mRange = range - margin; + } +} diff --git a/apps/openmw/mwrender/precipitationocclusion.hpp b/apps/openmw/mwrender/precipitationocclusion.hpp new file mode 100644 index 000000000..26114ed42 --- /dev/null +++ b/apps/openmw/mwrender/precipitationocclusion.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_MWRENDER_PRECIPITATIONOCCLUSION_H +#define OPENMW_MWRENDER_PRECIPITATIONOCCLUSION_H + +#include +#include + +namespace MWRender +{ + class PrecipitationOccluder + { + public: + PrecipitationOccluder(osg::Group* skyNode, osg::Group* sceneNode, osg::Group* rootNode, osg::Camera* camera); + + void update(); + + void enable(); + + void disable(); + + void updateRange(const osg::Vec3f range); + + private: + osg::Group* mSkyNode; + osg::Group* mSceneNode; + osg::Group* mRootNode; + osg::ref_ptr mSkyCullCallback; + osg::ref_ptr mCamera; + osg::ref_ptr mSceneCamera; + osg::ref_ptr mDepthTexture; + osg::Vec3f mRange; + }; +} + +#endif diff --git a/apps/openmw/mwrender/recastmesh.cpp b/apps/openmw/mwrender/recastmesh.cpp index 7c7c6b8eb..8edbc8568 100644 --- a/apps/openmw/mwrender/recastmesh.cpp +++ b/apps/openmw/mwrender/recastmesh.cpp @@ -1,11 +1,19 @@ #include "recastmesh.hpp" +#include + +#include +#include +#include +#include #include #include #include "vismask.hpp" +#include "../mwbase/environment.hpp" + namespace MWRender { RecastMesh::RecastMesh(const osg::ref_ptr& root, bool enabled) @@ -45,17 +53,15 @@ namespace MWRender continue; } - if (it->second.mGeneration != tile->second->getGeneration() - || it->second.mRevision != tile->second->getRevision()) + if (it->second.mVersion != tile->second->getVersion()) { - const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings); + const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings.mRecast); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mRootNode->removeChild(it->second.mValue); mRootNode->addChild(group); it->second.mValue = group; - it->second.mGeneration = tile->second->getGeneration(); - it->second.mRevision = tile->second->getRevision(); - continue; + it->second.mVersion = tile->second->getVersion(); } ++it; @@ -65,28 +71,29 @@ namespace MWRender { if (mGroups.count(tile.first)) continue; - const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings); + const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings.mRecast); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); - mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group}); + mGroups.emplace(tile.first, Group{ tile.second->getVersion(), group }); mRootNode->addChild(group); } } void RecastMesh::reset() { - std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->removeChild(v.second.mValue); }); + std::for_each(mGroups.begin(), mGroups.end(), [&](const auto& v) { mRootNode->removeChild(v.second.mValue); }); mGroups.clear(); } void RecastMesh::enable() { - std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->addChild(v.second.mValue); }); + std::for_each(mGroups.begin(), mGroups.end(), [&](const auto& v) { mRootNode->addChild(v.second.mValue); }); mEnabled = true; } void RecastMesh::disable() { - std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->removeChild(v.second.mValue); }); + std::for_each(mGroups.begin(), mGroups.end(), [&](const auto& v) { mRootNode->removeChild(v.second.mValue); }); mEnabled = false; } } diff --git a/apps/openmw/mwrender/recastmesh.hpp b/apps/openmw/mwrender/recastmesh.hpp index 729438dbe..2a45d67c6 100644 --- a/apps/openmw/mwrender/recastmesh.hpp +++ b/apps/openmw/mwrender/recastmesh.hpp @@ -1,7 +1,8 @@ #ifndef OPENMW_MWRENDER_RECASTMESH_H #define OPENMW_MWRENDER_RECASTMESH_H -#include +#include +#include #include @@ -13,6 +14,11 @@ namespace osg class Geometry; } +namespace DetourNavigator +{ + struct Settings; +} + namespace MWRender { class RecastMesh @@ -31,16 +37,12 @@ namespace MWRender void disable(); - bool isEnabled() const - { - return mEnabled; - } + bool isEnabled() const { return mEnabled; } private: struct Group { - std::size_t mGeneration; - std::size_t mRevision; + DetourNavigator::Version mVersion; osg::ref_ptr mValue; }; diff --git a/apps/openmw/mwrender/renderinginterface.hpp b/apps/openmw/mwrender/renderinginterface.hpp index 63039b612..0fe7d2506 100644 --- a/apps/openmw/mwrender/renderinginterface.hpp +++ b/apps/openmw/mwrender/renderinginterface.hpp @@ -5,12 +5,12 @@ namespace MWRender { class Objects; class Actors; - + class RenderingInterface { public: virtual MWRender::Objects& getObjects() = 0; - virtual ~RenderingInterface(){} + virtual ~RenderingInterface() {} }; } #endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e497fdecd..21cad8a51 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1,16 +1,17 @@ #include "renderingmanager.hpp" -#include #include +#include +#include +#include +#include +#include #include #include -#include #include #include -#include #include -#include #include @@ -20,57 +21,188 @@ #include -#include +#include +#include + #include -#include #include +#include #include #include -#include +#include -#include +#include +#include #include -#include #include -#include -#include -#include +#include #include +#include +#include +#include +#include + +#include -#include #include +#include -#include +#include +#include +#include #include +#include #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" -#include "../mwgui/loadingscreen.hpp" -#include "../mwbase/windowmanager.hpp" +#include "../mwworld/groundcoverstore.hpp" + +#include "../mwgui/postprocessorhud.hpp" + #include "../mwmechanics/actorutil.hpp" -#include "sky.hpp" -#include "effectmanager.hpp" -#include "npcanimation.hpp" -#include "vismask.hpp" -#include "pathgrid.hpp" -#include "camera.hpp" -#include "viewovershoulder.hpp" -#include "water.hpp" -#include "terrainstorage.hpp" -#include "navmesh.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + #include "actorspaths.hpp" -#include "recastmesh.hpp" +#include "camera.hpp" +#include "effectmanager.hpp" #include "fogmanager.hpp" -#include "objectpaging.hpp" -#include "screenshotmanager.hpp" #include "groundcover.hpp" +#include "navmesh.hpp" +#include "npcanimation.hpp" +#include "objectpaging.hpp" +#include "pathgrid.hpp" +#include "postprocessor.hpp" +#include "recastmesh.hpp" +#include "screenshotmanager.hpp" +#include "sky.hpp" +#include "terrainstorage.hpp" +#include "vismask.hpp" +#include "water.hpp" namespace MWRender { + class PerViewUniformStateUpdater final : public SceneUtil::StateSetUpdater + { + public: + PerViewUniformStateUpdater(Resource::SceneManager* sceneManager) + : mSceneManager(sceneManager) + { + mOpaqueTextureUnit = mSceneManager->getShaderManager().reserveGlobalTextureUnits( + Shader::ShaderManager::Slot::OpaqueDepthTexture); + } + + void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); + if (mSkyRTT) + stateset->addUniform(new osg::Uniform("sky", mSkyTextureUnit)); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + stateset->getUniform("projectionMatrix")->set(mProjectionMatrix); + if (mSkyRTT && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + { + osg::Texture* skyTexture = mSkyRTT->getColorTexture(static_cast(nv)); + stateset->setTextureAttribute( + mSkyTextureUnit, skyTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + + stateset->setTextureAttribute(mOpaqueTextureUnit, + mSceneManager->getOpaqueDepthTex(nv->getTraversalNumber()), osg::StateAttribute::ON); + } + + void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + stateset->getUniform("projectionMatrix")->set(getEyeProjectionMatrix(0)); + } + + void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + stateset->getUniform("projectionMatrix")->set(getEyeProjectionMatrix(1)); + } + + void setProjectionMatrix(const osg::Matrixf& projectionMatrix) { mProjectionMatrix = projectionMatrix; } + + const osg::Matrixf& getProjectionMatrix() const { return mProjectionMatrix; } + + void enableSkyRTT(int skyTextureUnit, SceneUtil::RTTNode* skyRTT) + { + mSkyTextureUnit = skyTextureUnit; + mSkyRTT = skyRTT; + } + + private: + osg::Matrixf getEyeProjectionMatrix(int view) + { + return Stereo::Manager::instance().computeEyeProjection(view, SceneUtil::AutoDepth::isReversed()); + } + + osg::Matrixf mProjectionMatrix; + int mSkyTextureUnit = -1; + SceneUtil::RTTNode* mSkyRTT = nullptr; + + Resource::SceneManager* mSceneManager; + int mOpaqueTextureUnit = -1; + }; + + class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater + { + public: + SharedUniformStateUpdater() + : mNear(0.f) + , mFar(0.f) + , mWindSpeed(0.f) + , mSkyBlendingStartCoef(Settings::fog().mSkyBlendingStart) + { + } + + void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("near", 0.f)); + stateset->addUniform(new osg::Uniform("far", 0.f)); + stateset->addUniform(new osg::Uniform("skyBlendingStart", 0.f)); + stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{})); + stateset->addUniform(new osg::Uniform("isReflection", false)); + stateset->addUniform(new osg::Uniform("windSpeed", 0.0f)); + stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f))); + stateset->addUniform(new osg::Uniform("useTreeAnim", false)); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + stateset->getUniform("near")->set(mNear); + stateset->getUniform("far")->set(mFar); + stateset->getUniform("skyBlendingStart")->set(mFar * mSkyBlendingStartCoef); + stateset->getUniform("screenRes")->set(mScreenRes); + stateset->getUniform("windSpeed")->set(mWindSpeed); + stateset->getUniform("playerPos")->set(mPlayerPos); + } + + void setNear(float near) { mNear = near; } + + void setFar(float far) { mFar = far; } + + void setScreenRes(float width, float height) { mScreenRes = osg::Vec2f(width, height); } + + void setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; } + + void setPlayerPos(osg::Vec3f playerPos) { mPlayerPos = playerPos; } + + private: + float mNear; + float mFar; + float mWindSpeed; + float mSkyBlendingStartCoef; + osg::Vec3f mPlayerPos; + osg::Vec2f mScreenRes; + }; class StateUpdater : public SceneUtil::StateSetUpdater { @@ -82,7 +214,7 @@ namespace MWRender { } - void setDefaults(osg::StateSet *stateset) override + void setDefaults(osg::StateSet* stateset) override { osg::LightModel* lightModel = new osg::LightModel; stateset->setAttribute(lightModel, osg::StateAttribute::ON); @@ -101,7 +233,8 @@ namespace MWRender void apply(osg::StateSet* stateset, osg::NodeVisitor*) override { - osg::LightModel* lightModel = static_cast(stateset->getAttribute(osg::StateAttribute::LIGHTMODEL)); + osg::LightModel* lightModel + = static_cast(stateset->getAttribute(osg::StateAttribute::LIGHTMODEL)); lightModel->setAmbientIntensity(mAmbientColor); osg::Fog* fog = static_cast(stateset->getAttribute(osg::StateAttribute::FOG)); fog->setColor(mFogColor); @@ -109,25 +242,13 @@ namespace MWRender fog->setEnd(mFogEnd); } - void setAmbientColor(const osg::Vec4f& col) - { - mAmbientColor = col; - } + void setAmbientColor(const osg::Vec4f& col) { mAmbientColor = col; } - void setFogColor(const osg::Vec4f& col) - { - mFogColor = col; - } + void setFogColor(const osg::Vec4f& col) { mFogColor = col; } - void setFogStart(float start) - { - mFogStart = start; - } + void setFogStart(float start) { mFogStart = start; } - void setFogEnd(float end) - { - mFogEnd = end; - } + void setFogEnd(float end) { mFogEnd = end; } void setWireframe(bool wireframe) { @@ -138,10 +259,7 @@ namespace MWRender } } - bool getWireframe() const - { - return mWireframe; - } + bool getWireframe() const { return mWireframe; } private: osg::Vec4f mAmbientColor; @@ -164,7 +282,7 @@ namespace MWRender try { for (std::vector::const_iterator it = mModels.begin(); it != mModels.end(); ++it) - mResourceSystem->getSceneManager()->cacheInstance(*it); + mResourceSystem->getSceneManager()->getTemplate(*it); for (std::vector::const_iterator it = mTextures.begin(); it != mTextures.end(); ++it) mResourceSystem->getImageManager()->getImage(*it); for (std::vector::const_iterator it = mKeyframes.begin(); it != mKeyframes.end(); ++it) @@ -185,45 +303,68 @@ namespace MWRender }; RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, - Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& resourcePath, DetourNavigator::Navigator& navigator) - : mViewer(viewer) + Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, + DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore, + SceneUtil::UnrefQueue& unrefQueue) + : mSkyBlending(Settings::fog().mSkyBlending) + , mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) - , mUnrefQueue(new SceneUtil::UnrefQueue) , mNavigator(navigator) , mMinimumAmbientLuminance(0.f) , mNightEyeFactor(0.f) + // TODO: Near clip should not need to be bounded like this, but too small values break OSG shadow calculations + // CPU-side. See issue: #6072 + , mNearClip(Settings::camera().mNearClip) + , mViewDistance(Settings::camera().mViewingDistance) , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) + , mFieldOfView(Settings::camera().mFieldOfView) + , mFirstPersonFieldOfView(Settings::camera().mFirstPersonFieldOfView) + , mGroundCoverStore(groundcoverStore) { - auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); + bool reverseZ = SceneUtil::AutoDepth::isReversed(); + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString( + Settings::Manager::getString("lighting method", "Shaders")); resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); - // Shadows and radial fog have problems with fixed-function mode - bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") - || Settings::Manager::getBool("force shaders", "Shaders") - || Settings::Manager::getBool("enable shadows", "Shadows") - || lightingMethod != SceneUtil::LightingMethod::FFP; + // Shadows and radial fog have problems with fixed-function mode. + bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog + || Settings::Manager::getBool("soft particles", "Shaders") + || Settings::Manager::getBool("force shaders", "Shaders") + || Settings::Manager::getBool("enable shadows", "Shadows") + || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ || mSkyBlending || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); + // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); - resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); - resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); - resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::Manager::getString("normal height map pattern", "Shaders")); - resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); - resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); - resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); - resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); + resourceSystem->getSceneManager()->setAutoUseNormalMaps( + Settings::Manager::getBool("auto use object normal maps", "Shaders")); + resourceSystem->getSceneManager()->setNormalMapPattern( + Settings::Manager::getString("normal map pattern", "Shaders")); + resourceSystem->getSceneManager()->setNormalHeightMapPattern( + Settings::Manager::getString("normal height map pattern", "Shaders")); + resourceSystem->getSceneManager()->setAutoUseSpecularMaps( + Settings::Manager::getBool("auto use object specular maps", "Shaders")); + resourceSystem->getSceneManager()->setSpecularMapPattern( + Settings::Manager::getString("specular map pattern", "Shaders")); + resourceSystem->getSceneManager()->setApplyLightingToEnvMaps( + Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); + resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage( + Settings::Manager::getBool("antialias alpha test", "Shaders") + && Settings::Manager::getInt("antialiasing", "Video") > 1); + resourceSystem->getSceneManager()->setAdjustCoverageForAlphaTest( + Settings::Manager::getBool("adjust coverage for alpha test", "Shaders")); - // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this depends on support for various OpenGL extensions. - osg::ref_ptr sceneRoot = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); - resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); + // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this + // depends on support for various OpenGL extensions. + osg::ref_ptr sceneRoot + = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setSupportedLightingMethods(sceneRoot->getSupportedLightingMethods()); - mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); + mMinimumAmbientLuminance + = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; @@ -236,27 +377,38 @@ namespace MWRender shadowCastingTraversalMask |= Mask_Actor; if (Settings::Manager::getBool("player shadows", "Shadows")) shadowCastingTraversalMask |= Mask_Player; - if (Settings::Manager::getBool("terrain shadows", "Shadows")) - shadowCastingTraversalMask |= Mask_Terrain; int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; if (Settings::Manager::getBool("object shadows", "Shadows")) - shadowCastingTraversalMask |= (Mask_Object|Mask_Static); + shadowCastingTraversalMask |= (Mask_Object | Mask_Static); + if (Settings::Manager::getBool("terrain shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Terrain; - mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); + mShadowManager = std::make_unique(sceneRoot, mRootNode, shadowCastingTraversalMask, + indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static, + mResourceSystem->getSceneManager()->getShaderManager()); Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); - Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); + Shader::ShaderManager::DefineMap globalDefines + = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); for (auto itr = shadowDefines.begin(); itr != shadowDefines.end(); itr++) globalDefines[itr->first] = itr->second; globalDefines["forcePPL"] = Settings::Manager::getBool("force per pixel lighting", "Shaders") ? "1" : "0"; globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; - globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; - globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + globalDefines["preLightEnv"] + = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; + const bool exponentialFog = Settings::fog().mExponentialFog; + globalDefines["radialFog"] = (exponentialFog || Settings::fog().mRadialFog) ? "1" : "0"; + globalDefines["exponentialFog"] = exponentialFog ? "1" : "0"; + globalDefines["skyBlending"] = mSkyBlending ? "1" : "0"; + globalDefines["refraction_enabled"] = "0"; globalDefines["useGPUShader4"] = "0"; + globalDefines["useOVR_multiview"] = "0"; + globalDefines["numViews"] = "1"; + globalDefines["disableNormals"] = "1"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; @@ -265,103 +417,77 @@ namespace MWRender float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); - globalDefines["groundcoverStompMode"] = std::to_string(std::clamp(Settings::Manager::getInt("stomp mode", "Groundcover"), 0, 2)); - globalDefines["groundcoverStompIntensity"] = std::to_string(std::clamp(Settings::Manager::getInt("stomp intensity", "Groundcover"), 0, 2)); + globalDefines["groundcoverStompMode"] + = std::to_string(std::clamp(Settings::Manager::getInt("stomp mode", "Groundcover"), 0, 2)); + globalDefines["groundcoverStompIntensity"] + = std::to_string(std::clamp(Settings::Manager::getInt("stomp intensity", "Groundcover"), 0, 2)); + + globalDefines["reverseZ"] = reverseZ ? "1" : "0"; // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); - mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); - mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); - mRecastMesh.reset(new RecastMesh(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator"))); - mPathgrid.reset(new Pathgrid(mRootNode)); + mNavMesh = std::make_unique(mRootNode, mWorkQueue, + Settings::Manager::getBool("enable nav mesh render", "Navigator"), + parseNavMeshMode(Settings::Manager::getString("nav mesh render mode", "Navigator"))); + mActorsPaths = std::make_unique( + mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")); + mRecastMesh = std::make_unique( + mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator")); + mPathgrid = std::make_unique(mRootNode); - mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); + mObjects = std::make_unique(mResourceSystem, sceneRoot, unrefQueue); if (getenv("OPENMW_DONT_PRECOMPILE") == nullptr) { mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); - mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); + mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::cells().mTargetFramerate); } + mDebugDraw + = std::make_unique(mResourceSystem->getSceneManager()->getShaderManager(), mRootNode); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); - mEffectManager.reset(new EffectManager(sceneRoot, mResourceSystem)); + mEffectManager = std::make_unique(sceneRoot, mResourceSystem); - const std::string normalMapPattern = Settings::Manager::getString("normal map pattern", "Shaders"); - const std::string heightMapPattern = Settings::Manager::getString("normal height map pattern", "Shaders"); - const std::string specularMapPattern = Settings::Manager::getString("terrain specular map pattern", "Shaders"); + const std::string& normalMapPattern = Settings::Manager::getString("normal map pattern", "Shaders"); + const std::string& heightMapPattern = Settings::Manager::getString("normal height map pattern", "Shaders"); + const std::string& specularMapPattern = Settings::Manager::getString("terrain specular map pattern", "Shaders"); const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); - mTerrainStorage.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps)); - const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); + mTerrainStorage = std::make_unique(mResourceSystem, normalMapPattern, heightMapPattern, + useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); - if (Settings::Manager::getBool("distant terrain", "Terrain")) - { - const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); - int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); - compMapPower = std::max(-3, compMapPower); - float compMapLevel = pow(2, compMapPower); - const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); - float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); - maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); - mTerrain.reset(new Terrain::QuadTreeWorld( - sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, - compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); - if (Settings::Manager::getBool("object paging", "Terrain")) - { - mObjectPaging.reset(new ObjectPaging(mResourceSystem->getSceneManager())); - static_cast(mTerrain.get())->addChunkManager(mObjectPaging.get()); - mResourceSystem->addResourceManager(mObjectPaging.get()); - } - } - else - mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug)); + WorldspaceChunkMgr& chunkMgr = getWorldspaceChunkMgr(ESM::Cell::sDefaultWorldspaceId); + mTerrain = chunkMgr.mTerrain.get(); + mGroundcover = chunkMgr.mGroundcover.get(); + mObjectPaging = chunkMgr.mObjectPaging.get(); - mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); - mTerrain->setWorkQueue(mWorkQueue.get()); + mStateUpdater = new StateUpdater; + sceneRoot->addUpdateCallback(mStateUpdater); - if (Settings::Manager::getBool("enabled", "Groundcover")) - { - osg::ref_ptr groundcoverRoot = new osg::Group; - groundcoverRoot->setNodeMask(Mask_Groundcover); - groundcoverRoot->setName("Groundcover Root"); - sceneRoot->addChild(groundcoverRoot); + mSharedUniformStateUpdater = new SharedUniformStateUpdater(); + rootNode->addUpdateCallback(mSharedUniformStateUpdater); - mGroundcoverUpdater = new GroundcoverUpdater; - groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); + mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(mResourceSystem->getSceneManager()); + rootNode->addCullCallback(mPerViewUniformStateUpdater); - float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover"); - if (chunkSize >= 1.0f) - chunkSize = 1.0f; - else if (chunkSize >= 0.5f) - chunkSize = 0.5f; - else if (chunkSize >= 0.25f) - chunkSize = 0.25f; - else if (chunkSize != 0.125f) - chunkSize = 0.125f; + mPostProcessor = new PostProcessor(*this, viewer, mRootNode, resourceSystem->getVFS()); + resourceSystem->getSceneManager()->setOpaqueDepthTex( + mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 0), + mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 1)); + resourceSystem->getSceneManager()->setSoftParticles(mPostProcessor->softParticlesEnabled()); + resourceSystem->getSceneManager()->setSupportsNormalsRT(mPostProcessor->getSupportsNormalsRT()); - float density = Settings::Manager::getFloat("density", "Groundcover"); - density = std::clamp(density, 0.f, 1.f); - - mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize)); - mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); - static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); - mResourceSystem->addResourceManager(mGroundcover.get()); - - // Groundcover it is handled in the same way indifferently from if it is from active grid or from distant cell. - // Use a stub grid to avoid splitting between chunks for active grid and chunks for distant cells. - mGroundcoverWorld->setActiveGrid(osg::Vec4i(0, 0, 0, 0)); - } // water goes after terrain for correct waterculling order - mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); + mWater = std::make_unique( + sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation()); - mCamera.reset(new Camera(mViewer->getCamera())); - if (Settings::Manager::getBool("view over shoulder", "Camera")) - mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get())); + mCamera = std::make_unique(mViewer->getCamera()); - mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); + mScreenshotManager + = std::make_unique(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get()); mViewer->setLightingMode(osgViewer::View::NO_LIGHT); @@ -369,9 +495,9 @@ namespace MWRender source->setNodeMask(Mask_Lighting); mSunLight = new osg::Light; source->setLight(mSunLight); - mSunLight->setDiffuse(osg::Vec4f(0,0,0,1)); - mSunLight->setAmbient(osg::Vec4f(0,0,0,1)); - mSunLight->setSpecular(osg::Vec4f(0,0,0,0)); + mSunLight->setDiffuse(osg::Vec4f(0, 0, 0, 1)); + mSunLight->setAmbient(osg::Vec4f(0, 0, 0, 1)); + mSunLight->setSpecular(osg::Vec4f(0, 0, 0, 0)); mSunLight->setConstantAttenuation(1.f); sceneRoot->setSunlight(mSunLight); sceneRoot->addChild(source); @@ -379,63 +505,70 @@ namespace MWRender sceneRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); sceneRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); sceneRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); - osg::ref_ptr defaultMat (new osg::Material); + osg::ref_ptr defaultMat(new osg::Material); defaultMat->setColorMode(osg::Material::OFF); - defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); + defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); - mFog.reset(new FogManager()); + mFog = std::make_unique(); - mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); - mSky->setCamera(mViewer->getCamera()); + mSky = std::make_unique( + sceneRoot, mRootNode, mViewer->getCamera(), resourceSystem->getSceneManager(), mSkyBlending); + if (mSkyBlending) + { + int skyTextureUnit = mResourceSystem->getSceneManager()->getShaderManager().reserveGlobalTextureUnits( + Shader::ShaderManager::Slot::SkyTexture); + mPerViewUniformStateUpdater->enableSkyRTT(skyTextureUnit, mSky->getSkyRTT()); + } source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); - mStateUpdater = new StateUpdater; - sceneRoot->addUpdateCallback(mStateUpdater); + osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING | osg::Camera::FAR_PLANE_CULLING; - osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING; - - if (!Settings::Manager::getBool("small feature culling", "Camera")) + if (!Settings::camera().mSmallFeatureCulling) cullingMode &= ~(osg::CullStack::SMALL_FEATURE_CULLING); else { - mViewer->getCamera()->setSmallFeatureCullingPixelSize(Settings::Manager::getFloat("small feature culling pixel size", "Camera")); + mViewer->getCamera()->setSmallFeatureCullingPixelSize(Settings::camera().mSmallFeatureCullingPixelSize); cullingMode |= osg::CullStack::SMALL_FEATURE_CULLING; } - mViewer->getCamera()->setCullingMode( cullingMode ); - mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mViewer->getCamera()->setCullingMode(cullingMode); + mViewer->getCamera()->setName(Constants::SceneCamera); - mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); + auto mask = ~(Mask_UpdateVisitor | Mask_SimpleWater); + MWBase::Environment::get().getWindowManager()->setCullMask(mask); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); - Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); + Nif::Reader::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); + Nif::Reader::setWriteNifDebugLog(Settings::Manager::getBool("write nif debug log", "Models")); - mNearClip = Settings::Manager::getFloat("near clip", "Camera"); - mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - float fov = Settings::Manager::getFloat("field of view", "Camera"); - mFieldOfView = std::min(std::max(1.f, fov), 179.f); - float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera"); - mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); - // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); - // The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and breaks things. + // The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and + // breaks things. mRootNode->getOrCreateStateSet()->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); - mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); - mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); + if (reverseZ) + { + osg::ref_ptr clipcontrol + = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::ZERO_TO_ONE); + mRootNode->getOrCreateStateSet()->setAttributeAndModes(new SceneUtil::AutoDepth, osg::StateAttribute::ON); + mRootNode->getOrCreateStateSet()->setAttributeAndModes(clipcontrol, osg::StateAttribute::ON); + } + + SceneUtil::setCameraClearDepth(mViewer->getCamera()); + updateProjectionMatrix(); + + mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } RenderingManager::~RenderingManager() @@ -464,19 +597,14 @@ namespace MWRender return mWorkQueue.get(); } - SceneUtil::UnrefQueue* RenderingManager::getUnrefQueue() - { - return mUnrefQueue.get(); - } - Terrain::World* RenderingManager::getTerrain() { - return mTerrain.get(); + return mTerrain; } void RenderingManager::preloadCommonAssets() { - osg::ref_ptr workItem (new PreloadCommonAssetsWorkItem(mResourceSystem)); + osg::ref_ptr workItem(new PreloadCommonAssetsWorkItem(mResourceSystem)); mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); mWater->listAssetsToPreload(workItem->mTextures); @@ -492,7 +620,7 @@ namespace MWRender workItem->mTextures.emplace_back("textures/_land_default.dds"); - mWorkQueue->addWorkItem(workItem); + mWorkQueue->addWorkItem(std::move(workItem)); } double RenderingManager::getReferenceTime() const @@ -500,7 +628,7 @@ namespace MWRender return mViewer->getFrameStamp()->getReferenceTime(); } - osg::Group* RenderingManager::getLightRoot() + SceneUtil::LightManager* RenderingManager::getLightRoot() { return mSceneRoot.get(); } @@ -514,7 +642,7 @@ namespace MWRender } } - void RenderingManager::setAmbientColour(const osg::Vec4f &colour) + void RenderingManager::setAmbientColour(const osg::Vec4f& colour) { mAmbientColor = colour; updateAmbient(); @@ -540,13 +668,14 @@ namespace MWRender mSky->setMoonColour(red); } - void RenderingManager::configureAmbient(const ESM::Cell *cell) + void RenderingManager::configureAmbient(const MWWorld::Cell& cell) { + bool isInterior = !cell.isExterior() && !cell.isQuasiExterior(); bool needsAdjusting = false; if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) - needsAdjusting = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); + needsAdjusting = isInterior; - auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient); + osg::Vec4f ambient = SceneUtil::colourFromRGB(cell.getMood().mAmbiantColor); if (needsAdjusting) { @@ -555,43 +684,52 @@ namespace MWRender constexpr float pB = 0.0722; // we already work in linear RGB so no conversions are needed for the luminosity function - float relativeLuminance = pR*ambient.r() + pG*ambient.g() + pB*ambient.b(); + float relativeLuminance = pR * ambient.r() + pG * ambient.g() + pB * ambient.b(); if (relativeLuminance < mMinimumAmbientLuminance) { - // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can - float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; + // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data + // as least we can if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f) - ambient = osg::Vec4(mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a()); + ambient = osg::Vec4( + mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a()); else - ambient *= targetBrightnessIncreaseFactor; + ambient *= mMinimumAmbientLuminance / relativeLuminance; } } setAmbientColour(ambient); - osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); - mSunLight->setDiffuse(diffuse); - mSunLight->setSpecular(diffuse); - mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f)); + osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell.getMood().mDirectionalColor); + + setSunColour(diffuse, diffuse, 1.f); + + const osg::Vec4f interiorSunPos = osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f); + mPostProcessor->getStateUpdater()->setSunPos(interiorSunPos, false); + mSunLight->setPosition(interiorSunPos); } - void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular) + void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis) { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(specular); + + mPostProcessor->getStateUpdater()->setSunColor(diffuse); + mPostProcessor->getStateUpdater()->setSunVis(sunVis); } - void RenderingManager::setSunDirection(const osg::Vec3f &direction) + void RenderingManager::setSunDirection(const osg::Vec3f& direction) { osg::Vec3 position = direction * -1; // need to wrap this in a StateUpdater? mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); mSky->setSunDirection(position); + + mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); } - void RenderingManager::addCell(const MWWorld::CellStore *store) + void RenderingManager::addCell(const MWWorld::CellStore* store) { mPathgrid->addCell(store); @@ -599,12 +737,11 @@ namespace MWRender if (store->getCell()->isExterior()) { + enableTerrain(true, store->getCell()->getWorldSpace()); mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); - if (mGroundcoverWorld) - mGroundcoverWorld->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } } - void RenderingManager::removeCell(const MWWorld::CellStore *store) + void RenderingManager::removeCell(const MWWorld::CellStore* store) { mPathgrid->removeCell(store); mActorsPaths->removeCell(store); @@ -612,21 +749,29 @@ namespace MWRender if (store->getCell()->isExterior()) { - mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); - if (mGroundcoverWorld) - mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + getWorldspaceChunkMgr(store->getCell()->getWorldSpace()) + .mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } mWater->removeCell(store); } - void RenderingManager::enableTerrain(bool enable) + void RenderingManager::enableTerrain(bool enable, ESM::RefId worldspace) { if (!enable) mWater->setCullCallback(nullptr); + else + { + WorldspaceChunkMgr& newChunks = getWorldspaceChunkMgr(worldspace); + if (newChunks.mTerrain.get() != mTerrain) + { + mTerrain->enable(false); + mTerrain = newChunks.mTerrain.get(); + mGroundcover = newChunks.mGroundcover.get(); + mObjectPaging = newChunks.mObjectPaging.get(); + } + } mTerrain->enable(enable); - if (mGroundcoverWorld) - mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) @@ -636,6 +781,7 @@ namespace MWRender mShadowManager->enableOutdoorMode(); else mShadowManager->enableIndoorMode(); + mPostProcessor->getStateUpdater()->setIsInterior(!enabled); } bool RenderingManager::toggleBorders() @@ -661,14 +807,15 @@ namespace MWRender } else if (mode == Render_Scene) { - unsigned int mask = mViewer->getCamera()->getCullMask(); - bool enabled = mask&Mask_Scene; - enabled = !enabled; + const auto wm = MWBase::Environment::get().getWindowManager(); + unsigned int mask = wm->getCullMask(); + bool enabled = !(mask & sToggleWorldMask); if (enabled) - mask |= Mask_Scene; + mask |= sToggleWorldMask; else - mask &= ~Mask_Scene; - mViewer->getCamera()->setCullMask(mask); + mask &= ~sToggleWorldMask; + mWater->showWorld(enabled); + wm->setCullMask(mask); return enabled; } else if (mode == Render_NavMesh) @@ -686,12 +833,13 @@ namespace MWRender return false; } - void RenderingManager::configureFog(const ESM::Cell *cell) + void RenderingManager::configureFog(const MWWorld::Cell& cell) { mFog->configure(mViewDistance, cell); } - void RenderingManager::configureFog(float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f &color) + void RenderingManager::configureFog( + float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f& color) { mFog->configure(mViewDistance, fogDepth, underwaterFog, dlFactor, dlOffset, color); } @@ -705,48 +853,64 @@ namespace MWRender { reportStats(); - mUnrefQueue->flush(mWorkQueue.get()); + mResourceSystem->getSceneManager()->getShaderManager().update(*mViewer); float rainIntensity = mSky->getPrecipitationAlpha(); mWater->setRainIntensity(rainIntensity); + mWater->update(dt, paused); if (!paused) { mEffectManager->update(dt); mSky->update(dt); - mWater->update(dt); - if (mGroundcoverUpdater) - { - const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); - osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); - float windSpeed = mSky->getBaseWindSpeed(); - mGroundcoverUpdater->setWindSpeed(windSpeed); - mGroundcoverUpdater->setPlayerPos(playerPos); - } + float windSpeed = mSky->getBaseWindSpeed(); + mSharedUniformStateUpdater->setWindSpeed(windSpeed); + mSharedUniformStateUpdater->setPlayerPos(playerPos); } updateNavMesh(); updateRecastMesh(); - if (mViewOverShoulderController) - mViewOverShoulderController->update(); + if (mUpdateProjectionMatrix) + { + mUpdateProjectionMatrix = false; + updateProjectionMatrix(); + } mCamera->update(dt, paused); - osg::Vec3d focal, cameraPos; - mCamera->getPosition(focal, cameraPos); - mCurrentCameraPos = cameraPos; + bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); - bool isUnderwater = mWater->isUnderwater(cameraPos); - mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater)); - mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater)); - setFogColor(mFog->getFogColor(isUnderwater)); + float fogStart = mFog->getFogStart(isUnderwater); + float fogEnd = mFog->getFogEnd(isUnderwater); + osg::Vec4f fogColor = mFog->getFogColor(isUnderwater); + + mStateUpdater->setFogStart(fogStart); + mStateUpdater->setFogEnd(fogEnd); + setFogColor(fogColor); + + auto world = MWBase::Environment::get().getWorld(); + const auto& stateUpdater = mPostProcessor->getStateUpdater(); + + stateUpdater->setFogRange(fogStart, fogEnd); + stateUpdater->setNearFar(mNearClip, mViewDistance); + stateUpdater->setIsUnderwater(isUnderwater); + stateUpdater->setFogColor(fogColor); + stateUpdater->setGameHour(world->getTimeStamp().getHour()); + stateUpdater->setWeatherId(world->getCurrentWeather()); + stateUpdater->setNextWeatherId(world->getNextWeather()); + stateUpdater->setWeatherTransition(world->getWeatherTransition()); + stateUpdater->setWindSpeed(world->getWindSpeed()); + stateUpdater->setSkyColor(mSky->getSkyColor()); + mPostProcessor->setUnderwaterFlag(isUnderwater); } - void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr) + void RenderingManager::updatePlayerPtr(const MWWorld::Ptr& ptr) { - if(mPlayerAnimation.get()) + if (mPlayerAnimation.get()) { setupPlayer(ptr); mPlayerAnimation->updatePtr(ptr); @@ -754,15 +918,14 @@ namespace MWRender mCamera->attachTo(ptr); } - void RenderingManager::removePlayer(const MWWorld::Ptr &player) + void RenderingManager::removePlayer(const MWWorld::Ptr& player) { mWater->removeEmitter(player); } - void RenderingManager::rotateObject(const MWWorld::Ptr &ptr, const osg::Quat& rot) + void RenderingManager::rotateObject(const MWWorld::Ptr& ptr, const osg::Quat& rot) { - if(ptr == mCamera->getTrackingPtr() && - !mCamera->isVanityOrPreviewModeEnabled()) + if (ptr == mCamera->getTrackingPtr() && !mCamera->isVanityOrPreviewModeEnabled()) { mCamera->rotateCameraToTrackingPtr(); } @@ -770,12 +933,12 @@ namespace MWRender ptr.getRefData().getBaseNode()->setAttitude(rot); } - void RenderingManager::moveObject(const MWWorld::Ptr &ptr, const osg::Vec3f &pos) + void RenderingManager::moveObject(const MWWorld::Ptr& ptr, const osg::Vec3f& pos) { ptr.getRefData().getBaseNode()->setPosition(pos); } - void RenderingManager::scaleObject(const MWWorld::Ptr &ptr, const osg::Vec3f &scale) + void RenderingManager::scaleObject(const MWWorld::Ptr& ptr, const osg::Vec3f& scale) { ptr.getRefData().getBaseNode()->setScale(scale); @@ -783,7 +946,7 @@ namespace MWRender mCamera->processViewChange(); } - void RenderingManager::removeObject(const MWWorld::Ptr &ptr) + void RenderingManager::removeObject(const MWWorld::Ptr& ptr) { mActorsPaths->remove(ptr); mObjects->removeObject(ptr); @@ -794,6 +957,8 @@ namespace MWRender { mWater->setEnabled(enabled); mSky->setWaterEnabled(enabled); + + mPostProcessor->getStateUpdater()->setIsWaterEnabled(enabled); } void RenderingManager::setWaterHeight(float height) @@ -801,6 +966,8 @@ namespace MWRender mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water)); mWater->setHeight(height); mSky->setWaterHeight(height); + + mPostProcessor->getStateUpdater()->setWaterHeight(height); } void RenderingManager::screenshot(osg::Image* image, int w, int h) @@ -816,24 +983,18 @@ namespace MWRender return false; } - unsigned int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); - - if (mCamera->isFirstPerson()) - mPlayerAnimation->getObjectRoot()->setNodeMask(0); - mScreenshotManager->screenshot360(image); - mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); - return true; } - osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb) + osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox& worldbb) { - if (!worldbb.valid()) return osg::Vec4f(); + if (!worldbb.valid()) + return osg::Vec4f(); osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix(); float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; - for (int i=0; i<8; ++i) + for (int i = 0; i < 8; ++i) { osg::Vec3f corner = worldbb.corner(i); corner = corner * viewProj; @@ -842,26 +1003,25 @@ namespace MWRender float y = (corner.y() - 1.f) * (-0.5f); if (x < min_x) - min_x = x; + min_x = x; if (x > max_x) - max_x = x; + max_x = x; if (y < min_y) - min_y = y; + min_y = y; if (y > max_y) - max_y = y; + max_y = y; } return osg::Vec4f(min_x, min_y, max_x, max_y); } - RenderingManager::RayResult getIntersectionResult (osgUtil::LineSegmentIntersector* intersector) + RenderingManager::RayResult getIntersectionResult(osgUtil::LineSegmentIntersector* intersector) { RenderingManager::RayResult result; result.mHit = false; - result.mHitRefnum.unset(); result.mRatio = 0; if (intersector->containsIntersections()) { @@ -874,12 +1034,13 @@ namespace MWRender PtrHolder* ptrHolder = nullptr; std::vector refnumMarkers; - for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) + for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); + ++it) { osg::UserDataContainer* userDataContainer = (*it)->getUserDataContainer(); if (!userDataContainer) continue; - for (unsigned int i=0; igetNumUserObjects(); ++i) + for (unsigned int i = 0; i < userDataContainer->getNumUserObjects(); ++i) { if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) ptrHolder = p; @@ -892,10 +1053,12 @@ namespace MWRender result.mHitObject = ptrHolder->mPtr; unsigned int vertexCounter = 0; - for (unsigned int i=0; imNumVertices || (intersectionIndex >= vertexCounter && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices)) + if (!refnumMarkers[i]->mNumVertices + || (intersectionIndex >= vertexCounter + && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices)) { result.mHitRefnum = refnumMarkers[i]->mRefnum; break; @@ -905,10 +1068,10 @@ namespace MWRender } return result; - } - osg::ref_ptr RenderingManager::getIntersectionVisitor(osgUtil::Intersector *intersector, bool ignorePlayer, bool ignoreActors) + osg::ref_ptr RenderingManager::getIntersectionVisitor( + osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors) { if (!mIntersectionVisitor) mIntersectionVisitor = new osgUtil::IntersectionVisitor; @@ -918,20 +1081,22 @@ namespace MWRender mIntersectionVisitor->setIntersector(intersector); unsigned int mask = ~0u; - mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater|Mask_Groundcover); + mask &= ~(Mask_RenderToTexture | Mask_Sky | Mask_Debug | Mask_Effect | Mask_Water | Mask_SimpleWater + | Mask_Groundcover); if (ignorePlayer) mask &= ~(Mask_Player); if (ignoreActors) - mask &= ~(Mask_Actor|Mask_Player); + mask &= ~(Mask_Actor | Mask_Player); mIntersectionVisitor->setTraversalMask(mask); return mIntersectionVisitor; } - RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) + RenderingManager::RayResult RenderingManager::castRay( + const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) { - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, - origin, dest)); + osg::ref_ptr intersector( + new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, origin, dest)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); @@ -939,12 +1104,13 @@ namespace MWRender return getIntersectionResult(intersector); } - RenderingManager::RayResult RenderingManager::castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors) + RenderingManager::RayResult RenderingManager::castCameraToViewportRay( + const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors) { - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::PROJECTION, - nX * 2.f - 1.f, nY * (-2.f) + 1.f)); + osg::ref_ptr intersector(new osgUtil::LineSegmentIntersector( + osgUtil::LineSegmentIntersector::PROJECTION, nX * 2.f - 1.f, nY * (-2.f) + 1.f)); - osg::Vec3d dist (0.f, 0.f, -maxDistance); + osg::Vec3d dist(0.f, 0.f, -maxDistance); dist = dist * mViewer->getCamera()->getProjectionMatrix(); @@ -958,13 +1124,14 @@ namespace MWRender return getIntersectionResult(intersector); } - void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) + void RenderingManager::updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated) { mObjects->updatePtr(old, updated); mActorsPaths->updatePtr(old, updated); } - void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) + void RenderingManager::spawnEffect(const std::string& model, std::string_view texture, + const osg::Vec3f& worldPosition, float scale, bool isMagicVFX) { mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX); } @@ -984,7 +1151,7 @@ namespace MWRender mObjectPaging->clear(); } - MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) + MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr& ptr) { if (mPlayerAnimation.get() && ptr == mPlayerAnimation->getPtr()) return mPlayerAnimation.get(); @@ -992,7 +1159,7 @@ namespace MWRender return mObjects->getAnimation(ptr); } - const MWRender::Animation* RenderingManager::getAnimation(const MWWorld::ConstPtr &ptr) const + const MWRender::Animation* RenderingManager::getAnimation(const MWWorld::ConstPtr& ptr) const { if (mPlayerAnimation.get() && ptr == mPlayerAnimation->getPtr()) return mPlayerAnimation.get(); @@ -1000,7 +1167,12 @@ namespace MWRender return mObjects->getAnimation(ptr); } - void RenderingManager::setupPlayer(const MWWorld::Ptr &player) + PostProcessor* RenderingManager::getPostProcessor() + { + return mPostProcessor; + } + + void RenderingManager::setupPlayer(const MWWorld::Ptr& player) { if (!mPlayerNode) { @@ -1019,26 +1191,26 @@ namespace MWRender mWater->addEmitter(player); } - void RenderingManager::renderPlayer(const MWWorld::Ptr &player) + void RenderingManager::renderPlayer(const MWWorld::Ptr& player) { - mPlayerAnimation = new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, NpcAnimation::VM_Normal, - mFirstPersonFieldOfView); + mPlayerAnimation = new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, + NpcAnimation::VM_Normal, mFirstPersonFieldOfView); mCamera->setAnimation(mPlayerAnimation.get()); mCamera->attachTo(player); } - void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) + void RenderingManager::rebuildPtr(const MWWorld::Ptr& ptr) { - NpcAnimation *anim = nullptr; - if(ptr == mPlayerAnimation->getPtr()) + NpcAnimation* anim = nullptr; + if (ptr == mPlayerAnimation->getPtr()) anim = mPlayerAnimation.get(); else anim = dynamic_cast(mObjects->getAnimation(ptr)); - if(anim) + if (anim) { anim->rebuild(); - if(mCamera->getTrackingPtr() == ptr) + if (mCamera->getTrackingPtr() == ptr) { mCamera->attachTo(ptr); mCamera->setAnimation(anim); @@ -1046,43 +1218,74 @@ namespace MWRender } } - void RenderingManager::addWaterRippleEmitter(const MWWorld::Ptr &ptr) + void RenderingManager::addWaterRippleEmitter(const MWWorld::Ptr& ptr) { mWater->addEmitter(ptr); } - void RenderingManager::removeWaterRippleEmitter(const MWWorld::Ptr &ptr) + void RenderingManager::removeWaterRippleEmitter(const MWWorld::Ptr& ptr) { mWater->removeEmitter(ptr); } - void RenderingManager::emitWaterRipple(const osg::Vec3f &pos) + void RenderingManager::emitWaterRipple(const osg::Vec3f& pos) { mWater->emitRipple(pos); } void RenderingManager::updateProjectionMatrix() { - double aspect = mViewer->getCamera()->getViewport()->aspectRatio(); + if (mNearClip < 0.0f) + throw std::runtime_error("Near clip is less than zero"); + if (mViewDistance < mNearClip) + throw std::runtime_error("Viewing distance is less than near clip"); + + double width = Settings::Manager::getInt("resolution x", "Video"); + double height = Settings::Manager::getInt("resolution y", "Video"); + + double aspect = (height == 0.0) ? 1.0 : width / height; float fov = mFieldOfView; if (mFieldOfViewOverridden) fov = mFieldOfViewOverride; + mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); - mUniformNear->set(mNearClip); - mUniformFar->set(mViewDistance); - - // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. - // Limit FOV here just for sure, otherwise viewing distance can be too high. - fov = std::min(mFieldOfView, 140.f); - float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); - mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); - - if (mGroundcoverWorld) + if (SceneUtil::AutoDepth::isReversed()) { - float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); - mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + mPerViewUniformStateUpdater->setProjectionMatrix( + SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); } + else + mPerViewUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix()); + + mSharedUniformStateUpdater->setNear(mNearClip); + mSharedUniformStateUpdater->setFar(mViewDistance); + if (Stereo::getStereo()) + { + auto res = Stereo::Manager::instance().eyeResolution(); + mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); + Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); + } + else if (!mPostProcessor->isEnabled()) + { + mSharedUniformStateUpdater->setScreenRes(width, height); + } + + // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may + // disappear. Limit FOV here just for sure, otherwise viewing distance can be too high. + float distanceMult = std::cos(osg::DegreesToRadians(std::min(fov, 140.f)) / 2.f); + mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f / distanceMult : 1.f)); + + if (mPostProcessor) + { + mPostProcessor->getStateUpdater()->setProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); + mPostProcessor->getStateUpdater()->setFov(fov); + } + } + + void RenderingManager::setScreenRes(int width, int height) + { + mSharedUniformStateUpdater->setScreenRes(width, height); } void RenderingManager::updateTextureFiltering() @@ -1093,10 +1296,10 @@ namespace MWRender Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), - Settings::Manager::getInt("anisotropy", "General") - ); + Settings::Manager::getInt("anisotropy", "General")); mTerrain->updateTextureFiltering(); + mWater->processChangedSettings({}); mViewer->startThreading(); } @@ -1108,47 +1311,103 @@ namespace MWRender if (mNightEyeFactor > 0.f) color += osg::Vec4f(0.7, 0.7, 0.7, 0.0) * mNightEyeFactor; + mPostProcessor->getStateUpdater()->setAmbientColor(color); mStateUpdater->setAmbientColor(color); } - void RenderingManager::setFogColor(const osg::Vec4f &color) + void RenderingManager::setFogColor(const osg::Vec4f& color) { mViewer->getCamera()->setClearColor(color); mStateUpdater->setFogColor(color); } + RenderingManager::WorldspaceChunkMgr& RenderingManager::getWorldspaceChunkMgr(ESM::RefId worldspace) + { + auto existingChunkMgr = mWorldspaceChunks.find(worldspace); + if (existingChunkMgr != mWorldspaceChunks.end()) + return existingChunkMgr->second; + RenderingManager::WorldspaceChunkMgr newChunkMgr; + + const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); + bool groundcover = Settings::Manager::getBool("enabled", "Groundcover"); + bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + if (distantTerrain || groundcover) + { + const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); + int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); + compMapPower = std::max(-3, compMapPower); + float compMapLevel = pow(2, compMapPower); + const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); + float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); + maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); + bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain"); + auto quadTreeWorld = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, + mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, + lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace); + if (Settings::Manager::getBool("object paging", "Terrain")) + { + newChunkMgr.mObjectPaging = std::make_unique(mResourceSystem->getSceneManager()); + quadTreeWorld->addChunkManager(newChunkMgr.mObjectPaging.get()); + mResourceSystem->addResourceManager(newChunkMgr.mObjectPaging.get()); + } + if (groundcover) + { + float groundcoverDistance + = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); + float density = Settings::Manager::getFloat("density", "Groundcover"); + density = std::clamp(density, 0.f, 1.f); + + newChunkMgr.mGroundcover = std::make_unique( + mResourceSystem->getSceneManager(), density, groundcoverDistance, mGroundCoverStore); + quadTreeWorld->addChunkManager(newChunkMgr.mGroundcover.get()); + mResourceSystem->addResourceManager(newChunkMgr.mGroundcover.get()); + } + newChunkMgr.mTerrain = std::move(quadTreeWorld); + } + else + newChunkMgr.mTerrain = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, + mTerrainStorage.get(), Mask_Terrain, worldspace, Mask_PreCompile, Mask_Debug); + + newChunkMgr.mTerrain->setTargetFrameRate(Settings::cells().mTargetFramerate); + float distanceMult = std::cos(osg::DegreesToRadians(std::min(mFieldOfView, 140.f)) / 2.f); + newChunkMgr.mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f / distanceMult : 1.f)); + + return mWorldspaceChunks.emplace(worldspace, std::move(newChunkMgr)).first->second; + } + void RenderingManager::reportStats() const { osg::Stats* stats = mViewer->getViewerStats(); unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); if (stats->collectStats("resource")) { - stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getNumItems()); - mTerrain->reportStats(frameNumber, stats); } } - void RenderingManager::processChangedSettings(const Settings::CategorySettingVector &changed) + void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& changed) { + // Only perform a projection matrix update once if a relevant setting is changed. + bool updateProjection = false; + for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { if (it->first == "Camera" && it->second == "field of view") { - mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); - updateProjectionMatrix(); + mFieldOfView = Settings::camera().mFieldOfView; + updateProjection = true; + } + else if (it->first == "Video" && (it->second == "resolution x" || it->second == "resolution y")) + { + updateProjection = true; } else if (it->first == "Camera" && it->second == "viewing distance") { - mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - if(!Settings::Manager::getBool("use distant fog", "Fog")) - mStateUpdater->setFogEnd(mViewDistance); - updateProjectionMatrix(); + setViewDistance(Settings::camera().mViewingDistance); } - else if (it->first == "General" && (it->second == "texture filter" || - it->second == "texture mipmap" || - it->second == "anisotropy")) + else if (it->first == "General" + && (it->second == "texture filter" || it->second == "texture mipmap" || it->second == "anisotropy")) { updateTextureFiltering(); } @@ -1158,16 +1417,16 @@ namespace MWRender } else if (it->first == "Shaders" && it->second == "minimum interior brightness") { - mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); + mMinimumAmbientLuminance + = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); if (MWMechanics::getPlayer().isInCell()) - configureAmbient(MWMechanics::getPlayer().getCell()->getCell()); + configureAmbient(*MWMechanics::getPlayer().getCell()->getCell()); } - else if (it->first == "Shaders" && (it->second == "light bounds multiplier" || - it->second == "maximum light distance" || - it->second == "light fade start" || - it->second == "max lights")) + else if (it->first == "Shaders" + && (it->second == "light bounds multiplier" || it->second == "maximum light distance" + || it->second == "light fade start" || it->second == "max lights")) { - auto* lightManager = static_cast(getLightRoot()); + auto* lightManager = getLightRoot(); lightManager->processChangedSettings(changed); if (it->second == "max lights" && !lightManager->usingFFP()) @@ -1181,26 +1440,46 @@ namespace MWRender defines[name] = key; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); - mSceneRoot->removeUpdateCallback(mStateUpdater); - mStateUpdater = new StateUpdater; - mSceneRoot->addUpdateCallback(mStateUpdater); - mStateUpdater->setFogEnd(mViewDistance); - updateAmbient(); + mStateUpdater->reset(); mViewer->startThreading(); } } + else if (it->first == "Post Processing" && it->second == "enabled") + { + if (Settings::Manager::getBool("enabled", "Post Processing")) + mPostProcessor->enable(); + else + { + mPostProcessor->disable(); + if (auto* hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) + hud->setVisible(false); + } + } + } + + if (updateProjection) + { + updateProjectionMatrix(); } } - float RenderingManager::getNearClipDistance() const + void RenderingManager::setViewDistance(float distance, bool delay) { - return mNearClip; + mViewDistance = distance; + + if (delay) + { + mUpdateProjectionMatrix = true; + return; + } + + updateProjectionMatrix(); } - float RenderingManager::getTerrainHeightAt(const osg::Vec3f &pos) + float RenderingManager::getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace) { - return mTerrain->getHeightAt(pos); + return getWorldspaceChunkMgr(worldspace).mTerrain->getHeightAt(pos); } void RenderingManager::overrideFieldOfView(float val) @@ -1213,6 +1492,17 @@ namespace MWRender } } + void RenderingManager::setFieldOfView(float val) + { + mFieldOfView = val; + mUpdateProjectionMatrix = true; + } + + float RenderingManager::getFieldOfView() const + { + return mFieldOfViewOverridden ? mFieldOfViewOverridden : mFieldOfView; + } + osg::Vec3f RenderingManager::getHalfExtents(const MWWorld::ConstPtr& object) const { osg::Vec3f halfExtents(0, 0, 0); @@ -1222,7 +1512,7 @@ namespace MWRender osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(modelName); osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem | MWRender::Mask_Effect)); const_cast(node.get())->accept(computeBoundsVisitor); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); @@ -1236,6 +1526,49 @@ namespace MWRender return halfExtents; } + osg::BoundingBox RenderingManager::getCullSafeBoundingBox(const MWWorld::Ptr& ptr) const + { + const std::string model = ptr.getClass().getModel(ptr); + if (model.empty()) + return {}; + + osg::ref_ptr rootNode = new SceneUtil::PositionAttitudeTransform; + // Hack even used by osg internally, osg's NodeVisitor won't accept const qualified nodes + rootNode->addChild(const_cast(mResourceSystem->getSceneManager()->getTemplate(model).get())); + + const SceneUtil::PositionAttitudeTransform* baseNode = ptr.getRefData().getBaseNode(); + if (baseNode) + { + rootNode->setPosition(baseNode->getPosition()); + rootNode->setAttitude(baseNode->getAttitude()); + rootNode->setScale(baseNode->getScale()); + } + else + { + rootNode->setPosition(ptr.getRefData().getPosition().asVec3()); + osg::Vec3f rot = ptr.getRefData().getPosition().asRotationVec3(); + rootNode->setAttitude(osg::Quat(rot[2], osg::Vec3f(0, 0, -1)) * osg::Quat(rot[1], osg::Vec3f(0, -1, 0)) + * osg::Quat(rot[0], osg::Vec3f(-1, 0, 0))); + const float refScale = ptr.getCellRef().getScale(); + rootNode->setScale({ refScale, refScale, refScale }); + } + + SceneUtil::CullSafeBoundsVisitor computeBounds; + computeBounds.setTraversalMask(~(MWRender::Mask_ParticleSystem | MWRender::Mask_Effect)); + rootNode->accept(computeBounds); + + const osg::Vec3f& scale = rootNode->getScale(); + + computeBounds.mBoundingBox.xMin() *= scale.x(); + computeBounds.mBoundingBox.xMax() *= scale.x(); + computeBounds.mBoundingBox.yMin() *= scale.y(); + computeBounds.mBoundingBox.yMax() *= scale.y(); + computeBounds.mBoundingBox.zMin() *= scale.z(); + computeBounds.mBoundingBox.zMax() *= scale.z(); + + return computeBounds.mBoundingBox; + } + void RenderingManager::resetFieldOfView() { if (mFieldOfViewOverridden == true) @@ -1245,7 +1578,8 @@ namespace MWRender updateProjectionMatrix(); } } - void RenderingManager::exportSceneGraph(const MWWorld::Ptr &ptr, const std::string &filename, const std::string &format) + void RenderingManager::exportSceneGraph( + const MWWorld::Ptr& ptr, const std::filesystem::path& filename, const std::string& format) { osg::Node* node = mViewer->getSceneData(); if (!ptr.isEmpty()) @@ -1254,15 +1588,15 @@ namespace MWRender SceneUtil::writeScene(node, filename, format); } - LandManager *RenderingManager::getLandManager() const + LandManager* RenderingManager::getLandManager() const { return mTerrainStorage->getLandManager(); } void RenderingManager::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, - const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const { - mActorsPaths->update(actor, path, halfExtents, start, end, mNavigator.getSettings()); + mActorsPaths->update(actor, path, agentBounds, start, end, mNavigator.getSettings()); } void RenderingManager::removeActorPath(const MWWorld::ConstPtr& actor) const @@ -1293,9 +1627,7 @@ namespace MWRender { try { - const auto locked = it->second->lockConst(); - mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getGeneration(), - locked->getNavMeshRevision(), mNavigator.getSettings()); + mNavMesh->update(it->second, mNavMeshNumber, mNavigator.getSettings()); } catch (const std::exception& e) { @@ -1312,7 +1644,7 @@ namespace MWRender mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings()); } - void RenderingManager::setActiveGrid(const osg::Vec4i &grid) + void RenderingManager::setActiveGrid(const osg::Vec4i& grid) { mTerrain->setActiveGrid(grid); } @@ -1320,20 +1652,23 @@ namespace MWRender { if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) return false; - if (mObjectPaging->enableObject(type, ptr.getCellRef().getRefNum(), ptr.getCellRef().getPosition().asVec3(), osg::Vec2i(ptr.getCell()->getCell()->getGridX(), ptr.getCell()->getCell()->getGridY()), enabled)) + if (mObjectPaging->enableObject(type, ptr.getCellRef().getRefNum(), ptr.getCellRef().getPosition().asVec3(), + osg::Vec2i(ptr.getCell()->getCell()->getGridX(), ptr.getCell()->getCell()->getGridY()), enabled)) { mTerrain->rebuildViews(); return true; } return false; } - void RenderingManager::pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr) + void RenderingManager::pagingBlacklistObject(int type, const MWWorld::ConstPtr& ptr) { if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) return; - const ESM::RefNum & refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile()) return; - if (mObjectPaging->blacklistObject(type, refnum, ptr.getCellRef().getPosition().asVec3(), osg::Vec2i(ptr.getCell()->getCell()->getGridX(), ptr.getCell()->getCell()->getGridY()))) + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile()) + return; + if (mObjectPaging->blacklistObject(type, refnum, ptr.getCellRef().getPosition().asVec3(), + osg::Vec2i(ptr.getCell()->getCell()->getGridX(), ptr.getCell()->getCell()->getGridY()))) mTerrain->rebuildViews(); } bool RenderingManager::pagingUnlockCache() @@ -1345,9 +1680,14 @@ namespace MWRender } return false; } - void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) + void RenderingManager::getPagedRefnums(const osg::Vec4i& activeGrid, std::vector& out) { if (mObjectPaging) mObjectPaging->getPagedRefnums(activeGrid, out); } + + void RenderingManager::setNavMeshMode(NavMeshMode value) + { + mNavMesh->setMode(value); + } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index a0a74bd5c..0e2ece7de 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -1,21 +1,22 @@ #ifndef OPENMW_MWRENDER_RENDERINGMANAGER_H #define OPENMW_MWRENDER_RENDERINGMANAGER_H -#include -#include #include +#include +#include #include #include +#include "navmeshmode.hpp" #include "objects.hpp" - #include "renderinginterface.hpp" #include "rendermode.hpp" #include #include +#include namespace osg { @@ -42,7 +43,8 @@ namespace osgViewer namespace ESM { struct Cell; - struct RefNum; + struct FormId; + using RefNum = FormId; } namespace Terrain @@ -59,6 +61,7 @@ namespace SceneUtil { class ShadowManager; class WorkQueue; + class LightManager; class UnrefQueue; } @@ -66,12 +69,25 @@ namespace DetourNavigator { struct Navigator; struct Settings; + struct AgentBounds; +} + +namespace MWWorld +{ + class GroundcoverStore; + class Cell; +} + +namespace Debug +{ + struct DebugDrawer; } namespace MWRender { - class GroundcoverUpdater; class StateUpdater; + class SharedUniformStateUpdater; + class PerViewUniformStateUpdater; class EffectManager; class ScreenshotManager; @@ -80,7 +96,6 @@ namespace MWRender class NpcAnimation; class Pathgrid; class Camera; - class ViewOverShoulderController; class Water; class TerrainStorage; class LandManager; @@ -89,13 +104,15 @@ namespace MWRender class RecastMesh; class ObjectPaging; class Groundcover; + class PostProcessor; class RenderingManager : public MWRender::RenderingInterface { public: RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, - Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& resourcePath, DetourNavigator::Navigator& navigator); + Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, + DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore, + SceneUtil::UnrefQueue& unrefQueue); ~RenderingManager(); osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); @@ -105,17 +122,13 @@ namespace MWRender Resource::ResourceSystem* getResourceSystem(); SceneUtil::WorkQueue* getWorkQueue(); - SceneUtil::UnrefQueue* getUnrefQueue(); Terrain::World* getTerrain(); - osg::Uniform* mUniformNear; - osg::Uniform* mUniformFar; - void preloadCommonAssets(); double getReferenceTime() const; - osg::Group* getLightRoot(); + SceneUtil::LightManager* getLightRoot(); void setNightEyeFactor(float factor); @@ -127,16 +140,18 @@ namespace MWRender void skySetMoonColour(bool red); void setSunDirection(const osg::Vec3f& direction); - void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular); + void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis); + void setNight(bool isNight) { mNight = isNight; } - void configureAmbient(const ESM::Cell* cell); - void configureFog(const ESM::Cell* cell); - void configureFog(float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f& colour); + void configureAmbient(const MWWorld::Cell& cell); + void configureFog(const MWWorld::Cell& cell); + void configureFog( + float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f& colour); void addCell(const MWWorld::CellStore* store); void removeCell(const MWWorld::CellStore* store); - void enableTerrain(bool enable); + void enableTerrain(bool enable, ESM::RefId worldspace); void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated); @@ -163,14 +178,17 @@ namespace MWRender float mRatio; }; - RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors=false); + RayResult castRay( + const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors = false); - /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen coordinates, - /// where (0,0) is the top left corner. - RayResult castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors=false); + /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen + /// coordinates, where (0,0) is the top left corner. + RayResult castCameraToViewportRay( + const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors = false); - /// Get the bounding box of the given object in screen coordinates as (minX, minY, maxX, maxY), with (0,0) being the top left corner. - osg::Vec4f getScreenBounds(const osg::BoundingBox &worldbb); + /// Get the bounding box of the given object in screen coordinates as (minX, minY, maxX, maxY), with (0,0) being + /// the top left corner. + osg::Vec4f getScreenBounds(const osg::BoundingBox& worldbb); void setSkyEnabled(bool enabled); @@ -178,7 +196,8 @@ namespace MWRender SkyManager* getSkyManager(); - void spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale = 1.f, bool isMagicVFX = true); + void spawnEffect(const std::string& model, std::string_view texture, const osg::Vec3f& worldPosition, + float scale = 1.f, bool isMagicVFX = true); /// Clear all savegame-specific data void clear(); @@ -191,11 +210,13 @@ namespace MWRender Animation* getAnimation(const MWWorld::Ptr& ptr); const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const; + PostProcessor* getPostProcessor(); + void addWaterRippleEmitter(const MWWorld::Ptr& ptr); void removeWaterRippleEmitter(const MWWorld::Ptr& ptr); void emitWaterRipple(const osg::Vec3f& pos); - void updatePlayerPtr(const MWWorld::Ptr &ptr); + void updatePlayerPtr(const MWWorld::Ptr& ptr); void removePlayer(const MWWorld::Ptr& player); void setupPlayer(const MWWorld::Ptr& player); @@ -205,67 +226,91 @@ namespace MWRender void processChangedSettings(const Settings::CategorySettingVector& settings); - float getNearClipDistance() const; + float getNearClipDistance() const { return mNearClip; } + float getViewDistance() const { return mViewDistance; } - float getTerrainHeightAt(const osg::Vec3f& pos); + void setViewDistance(float distance, bool delay = false); + + float getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace); // camera stuff Camera* getCamera() { return mCamera.get(); } - const osg::Vec3f& getCameraPosition() const { return mCurrentCameraPos; } /// temporarily override the field of view with given value. void overrideFieldOfView(float val); + void setFieldOfView(float val); + float getFieldOfView() const; /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file. void resetFieldOfView(); osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& object) const; - void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format); + // Return local bounding box. Safe to be called in parallel with cull thread. + osg::BoundingBox getCullSafeBoundingBox(const MWWorld::Ptr& ptr) const; + + void exportSceneGraph( + const MWWorld::Ptr& ptr, const std::filesystem::path& filename, const std::string& format); + + Debug::DebugDrawer& getDebugDrawer() const { return *mDebugDraw; } LandManager* getLandManager() const; bool toggleBorders(); void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, - const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const; + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const; void removeActorPath(const MWWorld::ConstPtr& actor) const; void setNavMeshNumber(const std::size_t value); - void setActiveGrid(const osg::Vec4i &grid); + void setActiveGrid(const osg::Vec4i& grid); bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled); - void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr); + void pagingBlacklistObject(int type, const MWWorld::ConstPtr& ptr); bool pagingUnlockCache(); - void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); + void getPagedRefnums(const osg::Vec4i& activeGrid, std::vector& out); + + void updateProjectionMatrix(); + + void setScreenRes(int width, int height); + + void setNavMeshMode(NavMeshMode value); private: - void updateProjectionMatrix(); void updateTextureFiltering(); void updateAmbient(); void setFogColor(const osg::Vec4f& color); void updateThirdPersonViewMode(); + struct WorldspaceChunkMgr + { + std::unique_ptr mTerrain; + std::unique_ptr mObjectPaging; + std::unique_ptr mGroundcover; + }; + + WorldspaceChunkMgr& getWorldspaceChunkMgr(ESM::RefId worldspace); + void reportStats() const; void updateNavMesh(); void updateRecastMesh(); - osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); + const bool mSkyBlending; + + osg::ref_ptr getIntersectionVisitor( + osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; osg::ref_ptr mViewer; osg::ref_ptr mRootNode; - osg::ref_ptr mSceneRoot; + osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; - osg::ref_ptr mGroundcoverUpdater; - osg::ref_ptr mWorkQueue; - osg::ref_ptr mUnrefQueue; osg::ref_ptr mSunLight; @@ -277,23 +322,25 @@ namespace MWRender std::unique_ptr mPathgrid; std::unique_ptr mObjects; std::unique_ptr mWater; - std::unique_ptr mTerrain; - std::unique_ptr mGroundcoverWorld; + std::unordered_map mWorldspaceChunks; + Terrain::World* mTerrain; std::unique_ptr mTerrainStorage; - std::unique_ptr mObjectPaging; - std::unique_ptr mGroundcover; + ObjectPaging* mObjectPaging; + Groundcover* mGroundcover; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; std::unique_ptr mEffectManager; std::unique_ptr mShadowManager; + osg::ref_ptr mPostProcessor; osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; - std::unique_ptr mViewOverShoulderController; - osg::Vec3f mCurrentCameraPos; + std::unique_ptr mDebugDraw; osg::ref_ptr mStateUpdater; + osg::ref_ptr mSharedUniformStateUpdater; + osg::ref_ptr mPerViewUniformStateUpdater; osg::Vec4f mAmbientColor; float mMinimumAmbientLuminance; @@ -305,8 +352,11 @@ namespace MWRender float mFieldOfViewOverride; float mFieldOfView; float mFirstPersonFieldOfView; + bool mUpdateProjectionMatrix = false; + bool mNight = false; + const MWWorld::GroundcoverStore& mGroundCoverStore; - void operator = (const RenderingManager&); + void operator=(const RenderingManager&); RenderingManager(const RenderingManager&); }; diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp new file mode 100644 index 000000000..9d6218490 --- /dev/null +++ b/apps/openmw/mwrender/ripples.cpp @@ -0,0 +1,306 @@ +#include "ripples.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwworld/ptr.hpp" + +#include "../mwmechanics/actorutil.hpp" + +#include "vismask.hpp" + +namespace MWRender +{ + RipplesSurface::RipplesSurface(Resource::ResourceSystem* resourceSystem) + : osg::Geometry() + , mResourceSystem(resourceSystem) + { + setUseDisplayList(false); + setUseVertexBufferObjects(true); + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-1, -1, 0)); + verts->push_back(osg::Vec3f(-1, 3, 0)); + verts->push_back(osg::Vec3f(3, -1, 0)); + + setVertexArray(verts); + + setCullingActive(false); + + addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); + +#ifdef __APPLE__ + // we can not trust Apple :) + mUseCompute = false; +#else + constexpr float minimumGLVersionRequiredForCompute = 4.4; + osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); + mUseCompute = exts->glVersion >= minimumGLVersionRequiredForCompute + && exts->glslLanguageVersion >= minimumGLVersionRequiredForCompute; +#endif + + if (mUseCompute) + Log(Debug::Info) << "Initialized compute shader pipeline for water ripples"; + else + Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples"; + + for (size_t i = 0; i < mState.size(); ++i) + { + osg::ref_ptr stateset = new osg::StateSet; + // bindings are set in the compute shader + if (!mUseCompute) + stateset->addUniform(new osg::Uniform("imageIn", 0)); + + stateset->addUniform(new osg::Uniform("offset", osg::Vec2f())); + stateset->addUniform(new osg::Uniform("positionCount", 0)); + stateset->addUniform(new osg::Uniform(osg::Uniform::Type::FLOAT_VEC3, "positions", 100)); + stateset->setAttributeAndModes(new osg::Viewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize)); + mState[i].mStateset = stateset; + } + + for (size_t i = 0; i < mTextures.size(); ++i) + { + osg::ref_ptr texture = new osg::Texture2D; + texture->setSourceFormat(GL_RGBA); + texture->setSourceType(GL_HALF_FLOAT); + texture->setInternalFormat(GL_RGBA16F); + texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); + texture->setBorderColor(osg::Vec4(0, 0, 0, 0)); + texture->setTextureSize(mRTTSize, mRTTSize); + + mTextures[i] = texture; + + mFBOs[i] = new osg::FrameBufferObject; + mFBOs[i]->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mTextures[i])); + } + + if (mUseCompute) + setupComputePipeline(); + else + setupFragmentPipeline(); + + setCullCallback(new osg::NodeCallback); + setUpdateCallback(new osg::NodeCallback); + } + + void RipplesSurface::setupFragmentPipeline() + { + auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); + + Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(mRTTSize) + ".0" } }; + + osg::ref_ptr vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); + + mProgramBlobber = shaderManager.getProgram( + vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT)); + mProgramSimulation = shaderManager.getProgram( + vertex, shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT)); + } + + void RipplesSurface::setupComputePipeline() + { + auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); + + mProgramBlobber = shaderManager.getProgram( + nullptr, shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE)); + mProgramSimulation = shaderManager.getProgram( + nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE)); + } + + void RipplesSurface::traverse(osg::NodeVisitor& nv) + { + if (!nv.getFrameStamp()) + return; + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + { + size_t frameId = nv.getFrameStamp()->getFrameNumber() % 2; + + const ESM::Position& player = MWMechanics::getPlayer().getRefData().getPosition(); + + mCurrentPlayerPos = osg::Vec2f( + std::floor(player.pos[0] / mWorldScaleFactor), std::floor(player.pos[1] / mWorldScaleFactor)); + osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; + mLastPlayerPos = mCurrentPlayerPos; + mState[frameId].mPaused = mPaused; + mState[frameId].mOffset = offset; + mState[frameId].mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); + mState[frameId].mStateset->getUniform("offset")->set(offset); + + auto* positions = mState[frameId].mStateset->getUniform("positions"); + + for (size_t i = 0; i < mPositionCount; ++i) + { + osg::Vec3f pos = mPositions[i] + - osg::Vec3f( + mCurrentPlayerPos.x() * mWorldScaleFactor, mCurrentPlayerPos.y() * mWorldScaleFactor, 0.0) + + osg::Vec3f(mRTTSize * mWorldScaleFactor / 2, mRTTSize * mWorldScaleFactor / 2, 0.0); + pos /= mWorldScaleFactor; + positions->setElement(i, pos); + } + positions->dirty(); + + mPositionCount = 0; + } + osg::Geometry::traverse(nv); + } + + void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions& ext = *state.get(); + size_t contextID = state.getContextID(); + + size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; + const State& frameState = mState[currentFrame]; + if (frameState.mPaused) + { + return; + } + + auto bindImage = [contextID, &state, &ext](osg::Texture2D* texture, GLuint index, GLenum access) { + osg::Texture::TextureObject* to = texture->getTextureObject(contextID); + if (!to || texture->isDirty(contextID)) + { + state.applyTextureAttribute(index, texture); + to = texture->getTextureObject(contextID); + } + ext.glBindImageTexture(index, to->id(), 0, GL_FALSE, 0, access, GL_RGBA16F); + }; + + // Run simulation at a fixed rate independent on current FPS + // FIXME: when we skip frames we need to preserve positions. this doesn't work now + size_t ticks = 1; + + // float referenceTime = state.getFrameStamp()->getReferenceTime(); + // float frameTime = (mLastFrameTime != 0.0) ? referenceTime - mLastFrameTime : 0.0; + // frameTime = std::min(frameTime, 0.5f); + + // mLastFrameTime = referenceTime; + + // constexpr float rate = 60.0; + // constexpr float waveStep = 1.0 / rate; + + // mRemainingWaveTime += frameTime; + // ticks = mRemainingWaveTime / waveStep; + // mRemainingWaveTime -= ticks * waveStep; + + // PASS: Blot in all ripple spawners + mProgramBlobber->apply(state); + state.apply(frameState.mStateset); + + for (size_t i = 0; i < ticks; i++) + { + if (mUseCompute) + { + bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); + bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); + + ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1); + ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); + } + else + { + mFBOs[1]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.applyTextureAttribute(0, mTextures[0]); + osg::Geometry::drawImplementation(renderInfo); + } + } + + // PASS: Wave simulation + mProgramSimulation->apply(state); + state.apply(frameState.mStateset); + + for (size_t i = 0; i < ticks; i++) + { + if (mUseCompute) + { + bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); + bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); + + ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1); + ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); + } + else + { + mFBOs[0]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.applyTextureAttribute(0, mTextures[1]); + osg::Geometry::drawImplementation(renderInfo); + } + } + } + + osg::Texture* RipplesSurface::getColorTexture() const + { + return mTextures[0]; + } + + void RipplesSurface::emit(const osg::Vec3f pos, float sizeInCellUnits) + { + // Emitted positions are reset every frame, don't bother wrapping around when out of buffer space + if (mPositionCount >= mPositions.size()) + { + return; + } + + mPositions[mPositionCount] = osg::Vec3f(pos.x(), pos.y(), sizeInCellUnits); + + mPositionCount++; + } + + void RipplesSurface::releaseGLObjects(osg::State* state) const + { + for (const auto& tex : mTextures) + tex->releaseGLObjects(state); + for (const auto& fbo : mFBOs) + fbo->releaseGLObjects(state); + + if (mProgramBlobber) + mProgramBlobber->releaseGLObjects(state); + if (mProgramSimulation) + mProgramSimulation->releaseGLObjects(state); + } + + Ripples::Ripples(Resource::ResourceSystem* resourceSystem) + : osg::Camera() + , mRipples(new RipplesSurface(resourceSystem)) + { + getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + setRenderOrder(osg::Camera::PRE_RENDER); + setReferenceFrame(osg::Camera::ABSOLUTE_RF); + setNodeMask(Mask_RenderToTexture); + setClearMask(GL_NONE); + setViewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize); + addChild(mRipples); + setCullingActive(false); + setImplicitBufferAttachmentMask(0, 0); + } + + osg::Texture* Ripples::getColorTexture() const + { + return mRipples->getColorTexture(); + } + + void Ripples::emit(const osg::Vec3f pos, float sizeInCellUnits) + { + mRipples->emit(pos, sizeInCellUnits); + } + + void Ripples::setPaused(bool paused) + { + mRipples->setPaused(paused); + } +} diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp new file mode 100644 index 000000000..8e7f9f0c0 --- /dev/null +++ b/apps/openmw/mwrender/ripples.hpp @@ -0,0 +1,103 @@ +#ifndef OPENMW_MWRENDER_RIPPLES_H +#define OPENMW_MWRENDER_RIPPLES_H + +#include + +#include +#include + +#include +#include + +namespace Resource +{ + class ResourceSystem; +} + +namespace osg +{ + class Camera; + class Geometry; + class Program; + class Texture; + class StateSet; + class NodeVisitor; + class Texture; + class Texture2D; + class FrameBufferObject; +} + +namespace MWRender +{ + class RipplesSurface : public osg::Geometry + { + public: + RipplesSurface(Resource::ResourceSystem* resourceSystem); + + osg::Texture* getColorTexture() const; + + void emit(const osg::Vec3f pos, float sizeInCellUnits); + + void drawImplementation(osg::RenderInfo& renderInfo) const override; + + void setPaused(bool paused) { mPaused = paused; } + + void traverse(osg::NodeVisitor& nv) override; + + void releaseGLObjects(osg::State* state) const override; + + static constexpr size_t mRTTSize = 1024; + // e.g. texel to cell unit ratio + static constexpr float mWorldScaleFactor = 2.5; + + Resource::ResourceSystem* mResourceSystem; + + struct State + { + osg::Vec2f mOffset; + osg::ref_ptr mStateset; + bool mPaused = true; + }; + + size_t mPositionCount = 0; + std::array mPositions; + + std::array mState; + + private: + void setupFragmentPipeline(); + void setupComputePipeline(); + + osg::Vec2f mCurrentPlayerPos; + osg::Vec2f mLastPlayerPos; + + std::array, 2> mTextures; + std::array, 2> mFBOs; + + osg::ref_ptr mProgramBlobber; + osg::ref_ptr mProgramSimulation; + + bool mPaused = false; + bool mUseCompute = false; + + // Read/written in draw thread only + mutable float mRemainingWaveTime = 0; + mutable double mLastFrameTime = 0; + }; + + class Ripples : public osg::Camera + { + public: + Ripples(Resource::ResourceSystem* resourceSystem); + + osg::Texture* getColorTexture() const; + + void emit(const osg::Vec3f pos, float sizeInCellUnits); + + void setPaused(bool paused); + + osg::ref_ptr mRipples; + }; +} + +#endif diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 6788f53f4..abfb7ba9c 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -1,72 +1,76 @@ #include "ripplesimulation.hpp" #include +#include -#include -#include -#include #include +#include +#include #include +#include #include #include +#include #include #include #include #include #include -#include +#include #include "vismask.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" namespace { - void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem,osg::Node* node) + void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem, osg::Node* node) { int rippleFrameCount = Fallback::Map::getInt("Water_RippleFrameCount"); if (rippleFrameCount <= 0) return; - const std::string& tex = Fallback::Map::getString("Water_RippleTexture"); + std::string_view tex = Fallback::Map::getString("Water_RippleTexture"); - std::vector > textures; - for (int i=0; i> textures; + for (int i = 0; i < rippleFrameCount; ++i) { std::ostringstream texname; texname << "textures/water/" << tex << std::setw(2) << std::setfill('0') << i << ".dds"; - osg::ref_ptr tex2 (new osg::Texture2D(resourceSystem->getImageManager()->getImage(texname.str()))); + osg::ref_ptr tex2( + new osg::Texture2D(resourceSystem->getImageManager()->getImage(texname.str()))); tex2->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); tex2->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); resourceSystem->getSceneManager()->applyFilterSettings(tex2); textures.push_back(tex2); } - osg::ref_ptr controller (new NifOsg::FlipController(0, 0.3f/rippleFrameCount, textures)); - controller->setSource(std::shared_ptr(new SceneUtil::FrameTimeSource)); + osg::ref_ptr controller( + new NifOsg::FlipController(0, 0.3f / rippleFrameCount, textures)); + controller->setSource(std::make_shared()); node->addUpdateCallback(controller); - osg::ref_ptr stateset (new osg::StateSet); + osg::ref_ptr stateset(new osg::StateSet); stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); - osg::ref_ptr depth (new osg::Depth); + osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); - osg::ref_ptr polygonOffset (new osg::PolygonOffset); - polygonOffset->setUnits(-1); - polygonOffset->setFactor(-1); + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1 : -1); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 1 : -1); stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - osg::ref_ptr mat (new osg::Material); + osg::ref_ptr mat(new osg::Material); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); @@ -81,143 +85,164 @@ namespace namespace MWRender { -RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* resourceSystem) - : mParent(parent) -{ - mParticleSystem = new osgParticle::ParticleSystem; - - mParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); - mParticleSystem->setAlignVectorX(osg::Vec3f(1,0,0)); - mParticleSystem->setAlignVectorY(osg::Vec3f(0,1,0)); - - osgParticle::Particle& particleTemplate = mParticleSystem->getDefaultParticleTemplate(); - particleTemplate.setSizeRange(osgParticle::rangef(15, 180)); - particleTemplate.setColorRange(osgParticle::rangev4(osg::Vec4f(1,1,1,0.7), osg::Vec4f(1,1,1,0.7))); - particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 0.f)); - particleTemplate.setAngularVelocity(osg::Vec3f(0,0,Fallback::Map::getFloat("Water_RippleRotSpeed"))); - particleTemplate.setLifeTime(Fallback::Map::getFloat("Water_RippleLifetime")); - - osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); - updater->addParticleSystem(mParticleSystem); - - mParticleNode = new osg::PositionAttitudeTransform; - mParticleNode->setName("Ripple Root"); - mParticleNode->addChild(updater); - mParticleNode->addChild(mParticleSystem); - mParticleNode->setNodeMask(Mask_Water); - - createWaterRippleStateSet(resourceSystem, mParticleNode); - - resourceSystem->getSceneManager()->recreateShaders(mParticleNode); - - mParent->addChild(mParticleNode); -} - -RippleSimulation::~RippleSimulation() -{ - mParent->removeChild(mParticleNode); -} - -void RippleSimulation::update(float dt) -{ - const MWBase::World* world = MWBase::Environment::get().getWorld(); - for (Emitter& emitter : mEmitters) + RippleSimulation::RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem) + : mParent(parent) { - MWWorld::ConstPtr& ptr = emitter.mPtr; - if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr()) + mParticleSystem = new osgParticle::ParticleSystem; + + mParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); + mParticleSystem->setAlignVectorX(osg::Vec3f(1, 0, 0)); + mParticleSystem->setAlignVectorY(osg::Vec3f(0, 1, 0)); + + osgParticle::Particle& particleTemplate = mParticleSystem->getDefaultParticleTemplate(); + particleTemplate.setSizeRange(osgParticle::rangef(15, 180)); + particleTemplate.setColorRange(osgParticle::rangev4(osg::Vec4f(1, 1, 1, 0.7), osg::Vec4f(1, 1, 1, 0.7))); + particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 0.f)); + particleTemplate.setAngularVelocity(osg::Vec3f(0, 0, Fallback::Map::getFloat("Water_RippleRotSpeed"))); + particleTemplate.setLifeTime(Fallback::Map::getFloat("Water_RippleLifetime")); + + osg::ref_ptr updater(new osgParticle::ParticleSystemUpdater); + updater->addParticleSystem(mParticleSystem); + + mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->setName("Ripple Root"); + mParticleNode->addChild(updater); + mParticleNode->addChild(mParticleSystem); + mParticleNode->setNodeMask(Mask_Water); + + createWaterRippleStateSet(resourceSystem, mParticleNode); + + resourceSystem->getSceneManager()->recreateShaders(mParticleNode); + + mParent->addChild(mParticleNode); + } + + RippleSimulation::~RippleSimulation() + { + mParent->removeChild(mParticleNode); + } + + void RippleSimulation::update(float dt) + { + const MWBase::World* world = MWBase::Environment::get().getWorld(); + for (Emitter& emitter : mEmitters) { - // fetch a new ptr (to handle cell change etc) - // for non-player actors this is done in updateObjectCell - ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - } + emitter.mTimer -= dt; + MWWorld::ConstPtr& ptr = emitter.mPtr; + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + // fetch a new ptr (to handle cell change etc) + // for non-player actors this is done in updateObjectCell + ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + } - osg::Vec3f currentPos (ptr.getRefData().getPosition().asVec3()); + osg::Vec3f currentPos(ptr.getRefData().getPosition().asVec3()); - bool shouldEmit = (world->isUnderwater(ptr.getCell(), currentPos) && !world->isSubmerged(ptr)) || world->isWalkingOnWater(ptr); - if (shouldEmit && (currentPos - emitter.mLastEmitPosition).length() > 10) - { - emitter.mLastEmitPosition = currentPos; + bool shouldEmit = (world->isUnderwater(ptr.getCell(), currentPos) && !world->isSubmerged(ptr)) + || world->isWalkingOnWater(ptr); - currentPos.z() = mParticleNode->getPosition().z(); + if (!shouldEmit) + { + emitter.mTimer = 0.f; + } + else if (mRipples) + { + // Ripple simulation needs to continously apply impulses to keep simulation alive. + // Adding a timer delay will introduce many smaller ripples around actor instead of a smooth wake + currentPos.z() = mParticleNode->getPosition().z(); + emitRipple(currentPos); + } + else if (emitter.mTimer <= 0.f || (currentPos - emitter.mLastEmitPosition).length() > 10) + { + emitter.mLastEmitPosition = currentPos; + emitter.mTimer = 1.5f; - if (mParticleSystem->numParticles()-mParticleSystem->numDeadParticles() > 500) - continue; // TODO: remove the oldest particle to make room? + currentPos.z() = mParticleNode->getPosition().z(); - emitRipple(currentPos); + if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > 500) + continue; // TODO: remove the oldest particle to make room? + + emitRipple(currentPos); + } } } -} - -void RippleSimulation::addEmitter(const MWWorld::ConstPtr& ptr, float scale, float force) -{ - Emitter newEmitter; - newEmitter.mPtr = ptr; - newEmitter.mScale = scale; - newEmitter.mForce = force; - newEmitter.mLastEmitPosition = osg::Vec3f(0,0,0); - mEmitters.push_back (newEmitter); -} - -void RippleSimulation::removeEmitter (const MWWorld::ConstPtr& ptr) -{ - for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) + void RippleSimulation::addEmitter(const MWWorld::ConstPtr& ptr, float scale, float force) { - if (it->mPtr == ptr) + Emitter newEmitter; + newEmitter.mPtr = ptr; + newEmitter.mScale = scale; + newEmitter.mForce = force; + newEmitter.mLastEmitPosition = osg::Vec3f(0, 0, 0); + newEmitter.mTimer = 0.f; + mEmitters.push_back(newEmitter); + } + + void RippleSimulation::removeEmitter(const MWWorld::ConstPtr& ptr) + { + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) { - mEmitters.erase(it); - return; + if (it->mPtr == ptr) + { + mEmitters.erase(it); + return; + } } } -} -void RippleSimulation::updateEmitterPtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr) -{ - for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) + void RippleSimulation::updateEmitterPtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr) { - if (it->mPtr == old) + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) { - it->mPtr = ptr; - return; + if (it->mPtr == old) + { + it->mPtr = ptr; + return; + } } } -} -void RippleSimulation::removeCell(const MWWorld::CellStore *store) -{ - for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end();) + void RippleSimulation::removeCell(const MWWorld::CellStore* store) { - if ((it->mPtr.isInCell() && it->mPtr.getCell() == store) && it->mPtr != MWMechanics::getPlayer()) + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end();) { - it = mEmitters.erase(it); + if ((it->mPtr.isInCell() && it->mPtr.getCell() == store) && it->mPtr != MWMechanics::getPlayer()) + { + it = mEmitters.erase(it); + } + else + ++it; } - else - ++it; } -} -void RippleSimulation::emitRipple(const osg::Vec3f &pos) -{ - if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20) + void RippleSimulation::emitRipple(const osg::Vec3f& pos) { - osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex()); - osgParticle::Particle* p = mParticleSystem->createParticle(nullptr); - p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); - p->setAngle(osg::Vec3f(0,0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); + if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20) + { + if (mRipples) + { + constexpr float particleRippleSizeInUnits = 12.f; + mRipples->emit(osg::Vec3f(pos.x(), pos.y(), 0.f), particleRippleSizeInUnits); + } + else + { + osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex()); + osgParticle::Particle* p = mParticleSystem->createParticle(nullptr); + p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); + p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); + } + } } -} - -void RippleSimulation::setWaterHeight(float height) -{ - mParticleNode->setPosition(osg::Vec3f(0,0,height)); -} - -void RippleSimulation::clear() -{ - for (int i=0; inumParticles(); ++i) - mParticleSystem->destroyParticle(i); -} + void RippleSimulation::setWaterHeight(float height) + { + mParticleNode->setPosition(osg::Vec3f(0, 0, height)); + } + void RippleSimulation::clear() + { + for (int i = 0; i < mParticleSystem->numParticles(); ++i) + mParticleSystem->destroyParticle(i); + } } diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 186a578ba..1f09797bf 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -5,6 +5,8 @@ #include "../mwworld/ptr.hpp" +#include "ripples.hpp" + namespace osg { class Group; @@ -35,6 +37,7 @@ namespace MWRender osg::Vec3f mLastEmitPosition; float mScale; float mForce; + float mTimer; }; class RippleSimulation @@ -47,9 +50,9 @@ namespace MWRender void update(float dt); /// adds an emitter, position will be tracked automatically - void addEmitter (const MWWorld::ConstPtr& ptr, float scale = 1.f, float force = 1.f); - void removeEmitter (const MWWorld::ConstPtr& ptr); - void updateEmitterPtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr); + void addEmitter(const MWWorld::ConstPtr& ptr, float scale = 1.f, float force = 1.f); + void removeEmitter(const MWWorld::ConstPtr& ptr); + void updateEmitterPtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr); void removeCell(const MWWorld::CellStore* store); void emitRipple(const osg::Vec3f& pos); @@ -60,6 +63,8 @@ namespace MWRender /// Remove all active ripples void clear(); + void setRipples(Ripples* ripples) { mRipples = ripples; } + private: osg::ref_ptr mParent; @@ -67,6 +72,8 @@ namespace MWRender osg::ref_ptr mParticleNode; std::vector mEmitters; + + Ripples* mRipples = nullptr; }; } diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 534cc7490..d7f8bb902 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -5,53 +5,58 @@ namespace MWRender { -RotateController::RotateController(osg::Node *relativeTo) - : mEnabled(true) - , mRelativeTo(relativeTo) -{ - -} - -void RotateController::setEnabled(bool enabled) -{ - mEnabled = enabled; -} - -void RotateController::setRotate(const osg::Quat &rotate) -{ - mRotate = rotate; -} - -void RotateController::operator()(osg::Node *node, osg::NodeVisitor *nv) -{ - if (!mEnabled) + RotateController::RotateController(osg::Node* relativeTo) + : mEnabled(true) + , mRelativeTo(relativeTo) { + } + + void RotateController::setEnabled(bool enabled) + { + mEnabled = enabled; + } + + void RotateController::setRotate(const osg::Quat& rotate) + { + mRotate = rotate; + } + + void RotateController::setOffset(const osg::Vec3f& offset) + { + mOffset = offset; + } + + void RotateController::operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv) + { + if (!mEnabled) + { + traverse(node, nv); + return; + } + osg::Matrix matrix = node->getMatrix(); + osg::Quat worldOrient = getWorldOrientation(node); + osg::Quat worldOrientInverse = worldOrient.inverse(); + + osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); + matrix.setRotate(orient); + matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); + + node->setMatrix(matrix); + traverse(node, nv); - return; } - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix matrix = transform->getMatrix(); - osg::Quat worldOrient = getWorldOrientation(node); - osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); - matrix.setRotate(orient); - - transform->setMatrix(matrix); - - traverse(node,nv); -} - -osg::Quat RotateController::getWorldOrientation(osg::Node *node) -{ - // this could be optimized later, we just need the world orientation, not the full matrix - osg::NodePathList nodepaths = node->getParentalNodePaths(mRelativeTo); - osg::Quat worldOrient; - if (!nodepaths.empty()) + osg::Quat RotateController::getWorldOrientation(osg::Node* node) { - osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); - worldOrient = worldMat.getRotate(); + // this could be optimized later, we just need the world orientation, not the full matrix + osg::NodePathList nodepaths = node->getParentalNodePaths(mRelativeTo); + osg::Quat worldOrient; + if (!nodepaths.empty()) + { + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); + worldOrient = worldMat.getRotate(); + } + return worldOrient; } - return worldOrient; -} } diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp index 9d4080ac6..87bf0adfe 100644 --- a/apps/openmw/mwrender/rotatecontroller.hpp +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -1,35 +1,39 @@ #ifndef OPENMW_MWRENDER_ROTATECONTROLLER_H #define OPENMW_MWRENDER_ROTATECONTROLLER_H -#include +#include #include +namespace osg +{ + class MatrixTransform; +} + namespace MWRender { -/// Applies a rotation in \a relativeTo's space. -/// @note Assumes that the node being rotated has its "original" orientation set every frame by a different controller. -/// The rotation is then applied on top of that orientation. -/// @note Must be set on a MatrixTransform. -class RotateController : public osg::NodeCallback -{ -public: - RotateController(osg::Node* relativeTo); + /// Applies a rotation in \a relativeTo's space. + /// @note Assumes that the node being rotated has its "original" orientation set every frame by a different + /// controller. The rotation is then applied on top of that orientation. + class RotateController : public SceneUtil::NodeCallback + { + public: + RotateController(osg::Node* relativeTo); - void setEnabled(bool enabled); + void setEnabled(bool enabled); + void setOffset(const osg::Vec3f& offset); + void setRotate(const osg::Quat& rotate); - void setRotate(const osg::Quat& rotate); + void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; - -protected: - osg::Quat getWorldOrientation(osg::Node* node); - - bool mEnabled; - osg::Quat mRotate; - osg::Node* mRelativeTo; -}; + protected: + osg::Quat getWorldOrientation(osg::Node* node); + bool mEnabled; + osg::Vec3f mOffset; + osg::Quat mRotate; + osg::Node* mRelativeTo; + }; } diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index f474a5a9f..4179b2807 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -8,17 +8,21 @@ #include #include -#include +#include +#include #include #include +#include +#include #include +#include +#include -#include - -#include "../mwgui/loadingscreen.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwgui/loadingscreen.hpp" +#include "postprocessor.hpp" #include "util.hpp" #include "vismask.hpp" #include "water.hpp" @@ -37,11 +41,12 @@ namespace MWRender { public: NotifyDrawCompletedCallback() - : mDone(false), mFrame(0) + : mDone(false) + , mFrame(0) { } - void operator () (osg::RenderInfo& renderInfo) const override + void operator()(osg::RenderInfo& renderInfo) const override { std::lock_guard lock(mMutex); if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame && !mDone) @@ -76,28 +81,57 @@ namespace MWRender { public: ReadImageFromFramebufferCallback(osg::Image* image, int width, int height) - : mWidth(width), mHeight(height), mImage(image) + : mWidth(width) + , mHeight(height) + , mImage(image) { } - void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override + void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* /*drawable*/) const override { int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); - double imageaspect = (double)mWidth/(double)mHeight; + if (Stereo::getStereo()) + { + auto eyeRes = Stereo::Manager::instance().eyeResolution(); + screenW = eyeRes.x(); + screenH = eyeRes.y(); + } + double imageaspect = (double)mWidth / (double)mHeight; int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); - int width = screenW - leftPadding*2; - int height = screenH - topPadding*2; + int width = screenW - leftPadding * 2; + int height = screenH - topPadding * 2; + + // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer. Also ensure + // that the readbuffer is set correctly with rendeirng to FBO. glReadPixel() cannot read from multisampled + // targets + PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); + osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); + + if (ext) + { + size_t frameId = renderInfo.getState()->getFrameStamp()->getFrameNumber() % 2; + osg::FrameBufferObject* fbo = nullptr; + + if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) + fbo = postProcessor->getFbo(PostProcessor::FBO_Primary, frameId); + + if (fbo) + fbo->apply(*renderInfo.getState(), osg::FrameBufferObject::READ_FRAMEBUFFER); + } + mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); mImage->scaleImage(mWidth, mHeight, 1); } + private: int mWidth; int mHeight; osg::ref_ptr mImage; }; - ScreenshotManager::ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water) + ScreenshotManager::ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, + osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water) : mViewer(viewer) , mRootNode(rootNode) , mSceneRoot(sceneRoot) @@ -107,9 +141,7 @@ namespace MWRender { } - ScreenshotManager::~ScreenshotManager() - { - } + ScreenshotManager::~ScreenshotManager() {} void ScreenshotManager::screenshot(osg::Image* image, int w, int h) { @@ -117,10 +149,12 @@ namespace MWRender osg::ref_ptr tempDrw = new osg::Drawable; tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); tempDrw->setCullingActive(false); - tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera + tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", + osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera camera->addChild(tempDrw); traversalsAndWait(mViewer->getFrameStamp()->getFrameNumber()); - // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed + // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the + // screenshot is completed mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); camera->removeChild(tempDrw); } @@ -132,17 +166,17 @@ namespace MWRender Screenshot360Type screenshotMapping = Spherical; const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); - std::vector settingArgs; + std::vector settingArgs; Misc::StringUtils::split(settingStr, settingArgs); if (settingArgs.size() > 0) { - std::string typeStrings[4] = {"spherical", "cylindrical", "planet", "cubemap"}; + std::string_view typeStrings[4] = { "spherical", "cylindrical", "planet", "cubemap" }; bool found = false; for (int i = 0; i < 4; ++i) { - if (settingArgs[0].compare(typeStrings[i]) == 0) + if (settingArgs[0] == typeStrings[i]) { screenshotMapping = static_cast(i); found = true; @@ -161,50 +195,47 @@ namespace MWRender int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; if (settingArgs.size() > 1) - screenshotW = std::min(10000, std::atoi(settingArgs[1].c_str())); + { + screenshotW = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[1], 0)); + } if (settingArgs.size() > 2) - screenshotH = std::min(10000, std::atoi(settingArgs[2].c_str())); + { + screenshotH = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[2], 0)); + } if (settingArgs.size() > 3) - cubeSize = std::min(5000, std::atoi(settingArgs[3].c_str())); + { + cubeSize = std::min(5000, Misc::StringUtils::toNumeric(settingArgs[3], 0)); + } bool rawCubemap = screenshotMapping == RawCubemap; if (rawCubemap) - screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row + screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row else if (screenshotMapping == Planet) - screenshotH = screenshotW; // use square resolution for planet mapping + screenshotH = screenshotW; // use square resolution for planet mapping std::vector> images; + images.reserve(6); for (int i = 0; i < 6; ++i) images.push_back(new osg::Image); - osg::Vec3 directions[6] = { - rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), - osg::Vec3(0,0,-1), - osg::Vec3(-1,0,0), - rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), - osg::Vec3(0,1,0), - osg::Vec3(0,-1,0)}; + osg::Vec3 directions[6] + = { rawCubemap ? osg::Vec3(1, 0, 0) : osg::Vec3(0, 0, 1), osg::Vec3(0, 0, -1), osg::Vec3(-1, 0, 0), + rawCubemap ? osg::Vec3(0, 0, 1) : osg::Vec3(1, 0, 0), osg::Vec3(0, 1, 0), osg::Vec3(0, -1, 0) }; - double rotations[] = { - -osg::PI / 2.0, - osg::PI / 2.0, - osg::PI, - 0, - osg::PI / 2.0, - osg::PI / 2.0 }; + double rotations[] = { -osg::PI / 2.0, osg::PI / 2.0, osg::PI, 0, osg::PI / 2.0, osg::PI / 2.0 }; for (int i = 0; i < 6; ++i) // for each cubemap side { - osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1), directions[i]); + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0, 0, -1), directions[i]); if (!rawCubemap) - transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); + transform *= osg::Matrixd::rotate(rotations[i], osg::Vec3(0, 0, -1)); - osg::Image *sideImage = images[i].get(); + osg::Image* sideImage = images[i].get(); makeCubemapScreenshot(sideImage, cubeSize, cubeSize, transform); if (!rawCubemap) @@ -213,20 +244,22 @@ namespace MWRender if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images { - image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); + image->allocateImage( + cubeSize * 6, cubeSize, images[0]->r(), images[0]->getPixelFormat(), images[0]->getDataType()); for (int i = 0; i < 6; ++i) - osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); + osg::copyImage(images[i].get(), 0, 0, 0, images[i]->s(), images[i]->t(), images[i]->r(), image, + i * cubeSize, 0, 0); return true; } // run on GPU now: - osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + osg::ref_ptr cubeTexture(new osg::TextureCubeMap); cubeTexture->setResizeNonPowerOfTwoHint(false); - cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); - cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); + cubeTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST); + cubeTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST); cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); @@ -235,27 +268,17 @@ namespace MWRender cubeTexture->setImage(i, images[i].get()); osg::ref_ptr screenshotCamera(new osg::Camera); - osg::ref_ptr quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0))); + osg::ref_ptr quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0, 0, 0), 2.0))); - std::map defineMap; + osg::ref_ptr stateset = quad->getOrCreateStateSet(); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); - osg::ref_ptr fragmentShader(shaderMgr.getShader("s360_fragment.glsl", defineMap,osg::Shader::FRAGMENT)); - osg::ref_ptr vertexShader(shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); - osg::ref_ptr stateset = new osg::StateSet; - - osg::ref_ptr program(new osg::Program); - program->addShader(fragmentShader); - program->addShader(vertexShader); - stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap", 0)); stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); - quad->setStateSet(stateset); - quad->setUpdateCallback(nullptr); - screenshotCamera->addChild(quad); renderCameraToImage(screenshotCamera, image, screenshotW, screenshotH); @@ -275,23 +298,26 @@ namespace MWRender mDrawCompleteCallback->waitTillDone(); } - void ScreenshotManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) + void ScreenshotManager::renderCameraToImage(osg::Camera* camera, osg::Image* image, int w, int h) { camera->setNodeMask(Mask_RenderToTexture); camera->attach(osg::Camera::COLOR_BUFFER, image); camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); - + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setViewport(0, 0, w, h); - osg::ref_ptr texture (new osg::Texture2D); + SceneUtil::setCameraClearDepth(camera); + + osg::ref_ptr texture(new osg::Texture2D); texture->setInternalFormat(GL_RGB); - texture->setTextureSize(w,h); + texture->setTextureSize(w, h); texture->setResizeNonPowerOfTwoHint(false); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::COLOR_BUFFER,texture); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + camera->attach(osg::Camera::COLOR_BUFFER, texture); image->setDataType(GL_UNSIGNED_BYTE); image->setPixelFormat(texture->getInternalFormat()); @@ -305,32 +331,38 @@ namespace MWRender MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); - // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the + // screenshot is completed mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); camera->removeChildren(0, camera->getNumChildren()); mRootNode->removeChild(camera); } - void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) + void ScreenshotManager::makeCubemapScreenshot(osg::Image* image, int w, int h, const osg::Matrixd& cameraTransform) { - osg::ref_ptr rttCamera (new osg::Camera); - float nearClip = Settings::Manager::getFloat("near clip", "Camera"); - float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); + osg::ref_ptr rttCamera(new osg::Camera); + const float nearClip = Settings::camera().mNearClip; + const float viewDistance = Settings::camera().mViewingDistance; // each cubemap side sees 90 degrees - rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); + if (SceneUtil::AutoDepth::isReversed()) + rttCamera->setProjectionMatrix( + SceneUtil::getReversedZProjectionMatrixAsPerspectiveInf(90.0, w / float(h), nearClip)); + else + rttCamera->setProjectionMatrixAsPerspective(90.0, w / float(h), nearClip, viewDistance); rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->addChild(mSceneRoot); - rttCamera->addChild(mWater->getReflectionCamera()); - rttCamera->addChild(mWater->getRefractionCamera()); + rttCamera->addChild(mWater->getReflectionNode()); + rttCamera->addChild(mWater->getRefractionNode()); - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); + rttCamera->setCullMask( + MWBase::Environment::get().getWindowManager()->getCullMask() & ~(Mask_GUI | Mask_FirstPerson)); - rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - renderCameraToImage(rttCamera.get(),image,w,h); + renderCameraToImage(rttCamera.get(), image, w, h); } } diff --git a/apps/openmw/mwrender/screenshotmanager.hpp b/apps/openmw/mwrender/screenshotmanager.hpp index 373fe3be8..72e5b9163 100644 --- a/apps/openmw/mwrender/screenshotmanager.hpp +++ b/apps/openmw/mwrender/screenshotmanager.hpp @@ -21,7 +21,8 @@ namespace MWRender class ScreenshotManager { public: - ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water); + ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, + osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water); ~ScreenshotManager(); void screenshot(osg::Image* image, int w, int h); @@ -36,8 +37,9 @@ namespace MWRender Water* mWater; void traversalsAndWait(unsigned int frame); - void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); - void makeCubemapScreenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); + void renderCameraToImage(osg::Camera* camera, osg::Image* image, int w, int h); + void makeCubemapScreenshot( + osg::Image* image, int w, int h, const osg::Matrixd& cameraTransform = osg::Matrixd()); }; } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 8bac90604..5b8a25da1 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1,1309 +1,172 @@ #include "sky.hpp" -#include - -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include #include -#include -#include -#include -#include - -#include #include +#include +#include -#include +#include -#include +#include +#include +#include +#include +#include -#include #include +#include #include -#include -#include -#include -#include -#include -#include - -#include +#include +#include #include +#include "../mwworld/weather.hpp" + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "vismask.hpp" #include "renderbin.hpp" +#include "skyutil.hpp" +#include "vismask.hpp" namespace { - osg::ref_ptr createAlphaTrackingUnlitMaterial() - { - osg::ref_ptr mat = new osg::Material; - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mat->setColorMode(osg::Material::DIFFUSE); - return mat; - } - - osg::ref_ptr createUnlitMaterial() - { - osg::ref_ptr mat = new osg::Material; - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mat->setColorMode(osg::Material::OFF); - return mat; - } - - osg::ref_ptr createTexturedQuad(int numUvSets=1) - { - osg::ref_ptr geom = new osg::Geometry; - - osg::ref_ptr verts = new osg::Vec3Array; - verts->push_back(osg::Vec3f(-0.5, -0.5, 0)); - verts->push_back(osg::Vec3f(-0.5, 0.5, 0)); - verts->push_back(osg::Vec3f(0.5, 0.5, 0)); - verts->push_back(osg::Vec3f(0.5, -0.5, 0)); - - geom->setVertexArray(verts); - - osg::ref_ptr texcoords = new osg::Vec2Array; - texcoords->push_back(osg::Vec2f(0, 0)); - texcoords->push_back(osg::Vec2f(0, 1)); - texcoords->push_back(osg::Vec2f(1, 1)); - texcoords->push_back(osg::Vec2f(1, 0)); - - osg::ref_ptr colors = new osg::Vec4Array; - colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); - geom->setColorArray(colors, osg::Array::BIND_OVERALL); - - for (int i=0; isetTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); - - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); - - return geom; - } - -} - -namespace MWRender -{ - -class AtmosphereUpdater : public SceneUtil::StateSetUpdater -{ -public: - void setEmissionColor(const osg::Vec4f& emissionColor) - { - mEmissionColor = emissionColor; - } - -protected: - void setDefaults(osg::StateSet* stateset) override - { - stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override - { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); - } - -private: - osg::Vec4f mEmissionColor; -}; - -class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater -{ -public: - AtmosphereNightUpdater(Resource::ImageManager* imageManager) - { - // we just need a texture, its contents don't really matter - mTexture = new osg::Texture2D(imageManager->getWarningImage()); - } - - void setFade(const float fade) - { - mColor.a() = fade; - } - -protected: - void setDefaults(osg::StateSet* stateset) override - { - osg::ref_ptr texEnv (new osg::TexEnvCombine); - texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - - stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override - { - osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnv->setConstantColor(mColor); - } - - osg::ref_ptr mTexture; - - osg::Vec4f mColor; -}; - -class CloudUpdater : public SceneUtil::StateSetUpdater -{ -public: - CloudUpdater() - : mAnimationTimer(0.f) - , mOpacity(0.f) - { - } - - void setAnimationTimer(float timer) - { - mAnimationTimer = timer; - } - - void setTexture(osg::ref_ptr texture) - { - mTexture = texture; - } - void setEmissionColor(const osg::Vec4f& emissionColor) - { - mEmissionColor = emissionColor; - } - void setOpacity(float opacity) - { - mOpacity = opacity; - } - -protected: - void setDefaults(osg::StateSet *stateset) override - { - osg::ref_ptr texmat (new osg::TexMat); - stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); - stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already - osg::ref_ptr texEnvCombine (new osg::TexEnvCombine); - texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1)); - texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); - - stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON); - - stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override - { - osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); - texMat->setMatrix(osg::Matrix::translate(osg::Vec3f(0, -mAnimationTimer, 0.f))); - - stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); - - osg::TexEnvCombine* texEnvCombine = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); - } - -private: - float mAnimationTimer; - osg::ref_ptr mTexture; - osg::Vec4f mEmissionColor; - float mOpacity; -}; - -/// Transform that removes the eyepoint of the modelview matrix, -/// i.e. its children are positioned relative to the camera. -class CameraRelativeTransform : public osg::Transform -{ -public: - CameraRelativeTransform() - { - // Culling works in node-local space, not in camera space, so we can't cull this node correctly - // That's not a problem though, children of this node can be culled just fine - // Just make sure you do not place a CameraRelativeTransform deep in the scene graph - setCullingActive(false); - - addCullCallback(new CullCallback); - } - - CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) - : osg::Transform(copy, copyop) - { - } - - META_Node(MWRender, CameraRelativeTransform) - - const osg::Vec3f& getLastViewPoint() const - { - return mViewPoint; - } - - bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const override - { - if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) - { - mViewPoint = static_cast(nv)->getViewPoint(); - } - - if (_referenceFrame==RELATIVE_RF) - { - matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); - return false; - } - else // absolute - { - matrix.makeIdentity(); - return true; - } - } - - osg::BoundingSphere computeBound() const override - { - return osg::BoundingSphere(osg::Vec3f(0,0,0), 0); - } - - class CullCallback : public osg::NodeCallback + class WrapAroundOperator : public osgParticle::Operator { public: - void operator() (osg::Node* node, osg::NodeVisitor* nv) override + WrapAroundOperator(osg::Camera* camera, const osg::Vec3& wrapRange) + : osgParticle::Operator() + , mCamera(camera) + , mWrapRange(wrapRange) + , mHalfWrapRange(mWrapRange / 2.0) { - osgUtil::CullVisitor* cv = static_cast(nv); + mPreviousCameraPosition = getCameraPosition(); + } - // XXX have to remove unwanted culling plane of the water reflection camera + osg::Object* cloneType() const override { return nullptr; } - // Remove all planes that aren't from the standard frustum - unsigned int numPlanes = 4; - if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) - ++numPlanes; - if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) - ++numPlanes; + osg::Object* clone(const osg::CopyOp& op) const override { return nullptr; } - unsigned int mask = 0x1; - unsigned int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); - for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) + void operate(osgParticle::Particle* P, double dt) override {} + + void operateParticles(osgParticle::ParticleSystem* ps, double dt) override + { + osg::Vec3 position = getCameraPosition(); + osg::Vec3 positionDifference = position - mPreviousCameraPosition; + + osg::Matrix toWorld, toLocal; + + std::vector worldMatrices = ps->getWorldMatrices(); + + if (!worldMatrices.empty()) { - if (i >= numPlanes) + toWorld = worldMatrices[0]; + toLocal.invert(toWorld); + } + + for (int i = 0; i < ps->numParticles(); ++i) + { + osgParticle::Particle* p = ps->getParticle(i); + p->setPosition(toWorld.preMult(p->getPosition())); + p->setPosition(p->getPosition() - positionDifference); + + for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions { - // turn off this culling plane - resultMask &= (~mask); + osg::Vec3 pos = p->getPosition(); + + if (pos[j] < -mHalfWrapRange[j]) + pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j], mWrapRange[j]); + else if (pos[j] > mHalfWrapRange[j]) + pos[j] = fmod(pos[j] + mHalfWrapRange[j], mWrapRange[j]) - mHalfWrapRange[j]; + + p->setPosition(pos); } - mask <<= 1; + p->setPosition(toLocal.preMult(p->getPosition())); } - cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); - cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); - - cv->getProjectionCullingStack().back().pushCurrentMask(); - cv->getCurrentCullingSet().pushCurrentMask(); - - traverse(node, nv); - - cv->getProjectionCullingStack().back().popCurrentMask(); - cv->getCurrentCullingSet().popCurrentMask(); - } - }; -private: - // viewPoint for the current frame - mutable osg::Vec3f mViewPoint; -}; - -class ModVertexAlphaVisitor : public osg::NodeVisitor -{ -public: - ModVertexAlphaVisitor(int meshType) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mMeshType(meshType) - { - } - - void apply(osg::Drawable& drw) override - { - osg::Geometry* geom = drw.asGeometry(); - if (!geom) - return; - - osg::ref_ptr colors = new osg::Vec4Array(geom->getVertexArray()->getNumElements()); - for (unsigned int i=0; isize(); ++i) - { - float alpha = 1.f; - if (mMeshType == 0) alpha = (i%2) ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row - else if (mMeshType == 1) - { - if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row - else if (i>= 33 && i <= 48) alpha = 0.25098; // second row - else alpha = 1.f; - } - else if (mMeshType == 2) - { - if (geom->getColorArray()) - { - osg::Vec4Array* origColors = static_cast(geom->getColorArray()); - alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; - } - else - alpha = 1.f; - } - - (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); - } - - geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); - } - -private: - int mMeshType; -}; - -/// @brief Hides the node subgraph if the eye point is below water. -/// @note Must be added as cull callback. -/// @note Meant to be used on a node that is child of a CameraRelativeTransform. -/// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. -class UnderwaterSwitchCallback : public osg::NodeCallback -{ -public: - UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) - : mCameraRelativeTransform(cameraRelativeTransform) - , mEnabled(true) - , mWaterLevel(0.f) - { - } - - bool isUnderwater() - { - osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); - return mEnabled && viewPoint.z() < mWaterLevel; - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - if (isUnderwater()) - return; - - traverse(node, nv); - } - - void setEnabled(bool enabled) - { - mEnabled = enabled; - } - void setWaterLevel(float waterLevel) - { - mWaterLevel = waterLevel; - } - -private: - osg::ref_ptr mCameraRelativeTransform; - bool mEnabled; - float mWaterLevel; -}; - -/// A base class for the sun and moons. -class CelestialBody -{ -public: - CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0u) - : mVisibleMask(visibleMask) - { - mGeom = createTexturedQuad(numUvSets); - mTransform = new osg::PositionAttitudeTransform; - mTransform->setNodeMask(mVisibleMask); - mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); - mTransform->addChild(mGeom); - - parentNode->addChild(mTransform); - } - - virtual ~CelestialBody() {} - - virtual void adjustTransparency(const float ratio) = 0; - - void setVisible(bool visible) - { - mTransform->setNodeMask(visible ? mVisibleMask : 0); - } - -protected: - unsigned int mVisibleMask; - static const float mDistance; - osg::ref_ptr mTransform; - osg::ref_ptr mGeom; -}; - -const float CelestialBody::mDistance = 1000.0f; - -class Sun : public CelestialBody -{ -public: - Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) - : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) - , mUpdater(new Updater) - { - mTransform->addUpdateCallback(mUpdater); - - osg::ref_ptr sunTex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds"))); - sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - - osg::ref_ptr queryNode (new osg::Group); - // Need to render after the world geometry so we can correctly test for occlusions - osg::StateSet* stateset = queryNode->getOrCreateStateSet(); - stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); - stateset->setNestRenderBins(false); - // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun - osg::ref_ptr alphaFunc (new osg::AlphaFunc); - alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); - // Disable writing to the color buffer. We are using this geometry for visibility tests only. - osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); - stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON); - - mTransform->addChild(queryNode); - - mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); - mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); - - createSunFlash(imageManager); - createSunGlare(); - } - - ~Sun() - { - mTransform->removeUpdateCallback(mUpdater); - destroySunFlash(); - destroySunGlare(); - } - - void setColor(const osg::Vec4f& color) - { - mUpdater->mColor.r() = color.r(); - mUpdater->mColor.g() = color.g(); - mUpdater->mColor.b() = color.b(); - } - - void adjustTransparency(const float ratio) override - { - mUpdater->mColor.a() = ratio; - if (mSunGlareCallback) - mSunGlareCallback->setGlareView(ratio); - if (mSunFlashCallback) - mSunFlashCallback->setGlareView(ratio); - } - - void setDirection(const osg::Vec3f& direction) - { - osg::Vec3f normalizedDirection = direction / direction.length(); - mTransform->setPosition(normalizedDirection * mDistance); - - osg::Quat quat; - quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); - mTransform->setAttitude(quat); - } - - void setGlareTimeOfDayFade(float val) - { - if (mSunGlareCallback) - mSunGlareCallback->setTimeOfDayFade(val); - } - -private: - class DummyComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback - { - public: - osg::BoundingSphere computeBound(const osg::Node& node) const override { return osg::BoundingSphere(); } - }; - - /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. - osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible) - { - osg::ref_ptr oqn = new osg::OcclusionQueryNode; - oqn->setQueriesEnabled(true); - -#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) - // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced - osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); -#else - osg::ref_ptr queryGeom = oqn->getQueryGeometry(); -#endif - - // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, - // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry - // is only called once. - // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. - queryGeom->setDataVariance(osg::Object::STATIC); - - // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, - // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to - // circumvent this. - queryGeom->setVertexArray(mGeom->getVertexArray()); - queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); - queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); - queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); - - // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. - oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); - // Still need a proper bounding sphere. - oqn->setInitialBound(queryGeom->getBound()); - -#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) - oqn->setQueryGeometry(queryGeom.release()); -#endif - - osg::StateSet* queryStateSet = new osg::StateSet; - if (queryVisible) - { - osg::ref_ptr depth (new osg::Depth); - depth->setFunction(osg::Depth::LEQUAL); - // This is a trick to make fragments written by the query always use the maximum depth value, - // without having to retrieve the current far clipping distance. - // We want the sun glare to be "infinitely" far away. - depth->setZNear(1.0); - depth->setZFar(1.0); - depth->setWriteMask(false); - queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); - } - else - { - queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - } - oqn->setQueryStateSet(queryStateSet); - - parent->addChild(oqn); - - return oqn; - } - - void createSunFlash(Resource::ImageManager& imageManager) - { - osg::ref_ptr tex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds"))); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - osg::ref_ptr transform (new osg::PositionAttitudeTransform); - const float scale = 2.6f; - transform->setScale(osg::Vec3f(scale,scale,scale)); - - mTransform->addChild(transform); - - osg::ref_ptr geom = createTexturedQuad(); - transform->addChild(geom); - - osg::StateSet* stateset = geom->getOrCreateStateSet(); - - stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); - stateset->setNestRenderBins(false); - - mSunFlashNode = transform; - - mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); - mSunFlashNode->addCullCallback(mSunFlashCallback); - } - void destroySunFlash() - { - if (mSunFlashNode) - { - mSunFlashNode->removeCullCallback(mSunFlashCallback); - mSunFlashCallback = nullptr; - } - } - - void createSunGlare() - { - osg::ref_ptr camera (new osg::Camera); - camera->setProjectionMatrix(osg::Matrix::identity()); - camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? - camera->setViewMatrix(osg::Matrix::identity()); - camera->setClearMask(0); - camera->setRenderOrder(osg::Camera::NESTED_RENDER); - camera->setAllowEventFocus(false); - - osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); - - camera->addChild(geom); - - osg::StateSet* stateset = geom->getOrCreateStateSet(); - - stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); - stateset->setNestRenderBins(false); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - - // set up additive blending - osg::ref_ptr blendFunc (new osg::BlendFunc); - blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); - blendFunc->setDestination(osg::BlendFunc::ONE); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - - mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); - mSunGlareNode = camera; - - mSunGlareNode->addCullCallback(mSunGlareCallback); - - mTransform->addChild(camera); - } - void destroySunGlare() - { - if (mSunGlareNode) - { - mSunGlareNode->removeCullCallback(mSunGlareCallback); - mSunGlareCallback = nullptr; - } - } - - class Updater : public SceneUtil::StateSetUpdater - { - public: - osg::Vec4f mColor; - - Updater() - : mColor(1.f, 1.f, 1.f, 1.f) - { - } - - void setDefaults(osg::StateSet* stateset) override - { - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor*) override - { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); - } - }; - - class OcclusionCallback : public osg::NodeCallback - { - public: - OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) - : mOcclusionQueryVisiblePixels(oqnVisible) - , mOcclusionQueryTotalPixels(oqnTotal) - { + mPreviousCameraPosition = position; } protected: - float getVisibleRatio (osg::Camera* camera) - { - int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); - int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); + osg::Camera* mCamera; + osg::Vec3 mPreviousCameraPosition; + osg::Vec3 mWrapRange; + osg::Vec3 mHalfWrapRange; - float visibleRatio = 0.f; - if (total > 0) - visibleRatio = static_cast(visible) / static_cast(total); - - float dt = MWBase::Environment::get().getFrameDuration(); - - float lastRatio = mLastRatio[osg::observer_ptr(camera)]; - - float change = dt*10; - - if (visibleRatio > lastRatio) - visibleRatio = std::min(visibleRatio, lastRatio + change); - else - visibleRatio = std::max(visibleRatio, lastRatio - change); - - mLastRatio[osg::observer_ptr(camera)] = visibleRatio; - - return visibleRatio; - } - - private: - osg::ref_ptr mOcclusionQueryVisiblePixels; - osg::ref_ptr mOcclusionQueryTotalPixels; - - std::map, float> mLastRatio; + osg::Vec3 getCameraPosition() { return mCamera->getInverseViewMatrix().getTrans(); } }; - /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. - class SunFlashCallback : public OcclusionCallback + class WeatherAlphaOperator : public osgParticle::Operator { public: - SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) - : OcclusionCallback(oqnVisible, oqnTotal) - , mGlareView(1.f) + WeatherAlphaOperator(float& alpha, bool rain) + : mAlpha(alpha) + , mIsRain(rain) { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + osg::Object* cloneType() const override { return nullptr; } + + osg::Object* clone(const osg::CopyOp& op) const override { return nullptr; } + + void operate(osgParticle::Particle* particle, double dt) override { - osgUtil::CullVisitor* cv = static_cast(nv); - - float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); - - osg::ref_ptr stateset; - - if (visibleRatio > 0.f) - { - const float fadeThreshold = 0.1; - if (visibleRatio < fadeThreshold) - { - float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; - osg::ref_ptr mat (createUnlitMaterial()); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); - stateset = new osg::StateSet; - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - const float threshold = 0.6; - visibleRatio = visibleRatio * (1.f - threshold) + threshold; - } - - float scale = visibleRatio; - - if (scale == 0.f) - { - // no traverse - return; - } - else - { - osg::Matrix modelView = *cv->getModelViewMatrix(); - - modelView.preMultScale(osg::Vec3f(visibleRatio, visibleRatio, visibleRatio)); - - if (stateset) - cv->pushStateSet(stateset); - - cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); - - traverse(node, nv); - - cv->popModelViewMatrix(); - - if (stateset) - cv->popStateSet(); - } - } - - void setGlareView(float value) - { - mGlareView = value; + constexpr float rainThreshold = 0.6f; // Rain_Threshold? + float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; + particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); } private: - float mGlareView; + float& mAlpha; + bool mIsRain; }; - - /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. - /// Must be attached as a cull callback to the node above the glare node. - class SunGlareCallback : public OcclusionCallback + // Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute. + class AlphaFader : public SceneUtil::StateSetUpdater { public: - SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, - osg::ref_ptr sunTransform) - : OcclusionCallback(oqnVisible, oqnTotal) - , mSunTransform(sunTransform) - , mTimeOfDayFade(1.f) - , mGlareView(1.f) - { - mColor = Fallback::Map::getColour("Weather_Sun_Glare_Fader_Color"); - mSunGlareFaderMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Max"); - mSunGlareFaderAngleMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Angle_Max"); - - // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, - // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, - // so the resulting color looks more orange than red. - mColor *= 2; - for (int i=0; i<3; ++i) - mColor[i] = std::min(1.f, mColor[i]); - } - - void operator ()(osg::Node* node, osg::NodeVisitor* nv) override - { - osgUtil::CullVisitor* cv = static_cast(nv); - - float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); - float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); - - const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); - - float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); - float fade = value * mSunGlareFaderMax; - - fade *= mTimeOfDayFade * mGlareView * visibleRatio; - - if (fade == 0.f) - { - // no traverse - return; - } - else - { - osg::ref_ptr stateset (new osg::StateSet); - - osg::ref_ptr mat (createUnlitMaterial()); - - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); - - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - - cv->pushStateSet(stateset); - traverse(node, nv); - cv->popStateSet(); - } - } - - void setTimeOfDayFade(float val) - { - mTimeOfDayFade = val; - } - - void setGlareView(float glareView) - { - mGlareView = glareView; - } - - private: - float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const - { - osg::Vec3d eye, center, up; - viewMatrix.getLookAt(eye, center, up); - - osg::Vec3d forward = center - eye; - osg::Vec3d sun = mSunTransform->getPosition(); - - forward.normalize(); - sun.normalize(); - float angleRadians = std::acos(forward * sun); - return angleRadians; - } - - osg::ref_ptr mSunTransform; - float mTimeOfDayFade; - float mGlareView; - osg::Vec4f mColor; - float mSunGlareFaderMax; - float mSunGlareFaderAngleMax; - }; - - osg::ref_ptr mUpdater; - osg::ref_ptr mSunFlashCallback; - osg::ref_ptr mSunFlashNode; - osg::ref_ptr mSunGlareCallback; - osg::ref_ptr mSunGlareNode; - osg::ref_ptr mOcclusionQueryVisiblePixels; - osg::ref_ptr mOcclusionQueryTotalPixels; -}; - -class Moon : public CelestialBody -{ -public: - enum Type - { - Type_Masser = 0, - Type_Secunda - }; - - Moon(osg::Group* parentNode, Resource::ImageManager& imageManager, float scaleFactor, Type type) - : CelestialBody(parentNode, scaleFactor, 2) - , mType(type) - , mPhase(MoonState::Phase::Unspecified) - , mUpdater(new Updater(imageManager)) - { - setPhase(MoonState::Phase::Full); - setVisible(true); - - mGeom->addUpdateCallback(mUpdater); - } - - ~Moon() - { - mGeom->removeUpdateCallback(mUpdater); - } - - void adjustTransparency(const float ratio) override - { - mUpdater->mTransparency *= ratio; - } - - void setState(const MoonState& state) - { - float radsX = ((state.mRotationFromHorizon) * static_cast(osg::PI)) / 180.0f; - float radsZ = ((state.mRotationFromNorth) * static_cast(osg::PI)) / 180.0f; - - osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); - osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); - - osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); - mTransform->setPosition(direction * mDistance); - - // The moon quad is initially oriented facing down, so we need to offset its X-axis - // rotation to rotate it to face the camera when sitting at the horizon. - osg::Quat attX((-static_cast(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); - mTransform->setAttitude(attX * rotZ); - - setPhase(state.mPhase); - mUpdater->mTransparency = state.mMoonAlpha; - mUpdater->mShadowBlend = state.mShadowBlend; - } - - void setAtmosphereColor(const osg::Vec4f& color) - { - mUpdater->mAtmosphereColor = color; - } - - void setColor(const osg::Vec4f& color) - { - mUpdater->mMoonColor = color; - } - - unsigned int getPhaseInt() const - { - if (mPhase == MoonState::Phase::New) return 0; - else if (mPhase == MoonState::Phase::WaxingCrescent) return 1; - else if (mPhase == MoonState::Phase::WaningCrescent) return 1; - else if (mPhase == MoonState::Phase::FirstQuarter) return 2; - else if (mPhase == MoonState::Phase::ThirdQuarter) return 2; - else if (mPhase == MoonState::Phase::WaxingGibbous) return 3; - else if (mPhase == MoonState::Phase::WaningGibbous) return 3; - else if (mPhase == MoonState::Phase::Full) return 4; - return 0; - } - -private: - struct Updater : public SceneUtil::StateSetUpdater - { - Resource::ImageManager& mImageManager; - osg::ref_ptr mPhaseTex; - osg::ref_ptr mCircleTex; - float mTransparency; - float mShadowBlend; - osg::Vec4f mAtmosphereColor; - osg::Vec4f mMoonColor; - - Updater(Resource::ImageManager& imageManager) - : mImageManager(imageManager) - , mPhaseTex() - , mCircleTex() - , mTransparency(1.0f) - , mShadowBlend(1.0f) - , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) - , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) + /// @param alpha the variable alpha value is recovered from + AlphaFader(const float& alpha) + : mAlpha(alpha) { } void setDefaults(osg::StateSet* stateset) override { - stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); - osg::ref_ptr texEnv = new osg::TexEnvCombine; - texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); - texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor - stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); - - stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); - osg::ref_ptr texEnv2 = new osg::TexEnvCombine; - texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); - texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); - texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency - stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); - - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); } - void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { - osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); - texEnv->setConstantColor(mMoonColor * mShadowBlend); - - osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, mAlpha)); } - void setTextures(const std::string& phaseTex, const std::string& circleTex) - { - mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); - mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); - mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - reset(); - } + protected: + const float& mAlpha; }; - Type mType; - MoonState::Phase mPhase; - osg::ref_ptr mUpdater; - - void setPhase(const MoonState::Phase& phase) - { - if(mPhase == phase) - return; - - mPhase = phase; - - std::string textureName = "textures/tx_"; - - if (mType == Moon::Type_Secunda) - textureName += "secunda_"; - else - textureName += "masser_"; - - if (phase == MoonState::Phase::New) textureName += "new"; - else if(phase == MoonState::Phase::WaxingCrescent) textureName += "one_wax"; - else if(phase == MoonState::Phase::FirstQuarter) textureName += "half_wax"; - else if(phase == MoonState::Phase::WaxingGibbous) textureName += "three_wax"; - else if(phase == MoonState::Phase::WaningCrescent) textureName += "one_wan"; - else if(phase == MoonState::Phase::ThirdQuarter) textureName += "half_wan"; - else if(phase == MoonState::Phase::WaningGibbous) textureName += "three_wan"; - else if(phase == MoonState::Phase::Full) textureName += "full"; - - textureName += ".dds"; - - if (mType == Moon::Type_Secunda) - mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); - else - mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); - } -}; - -SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) - : mSceneManager(sceneManager) - , mCamera(nullptr) - , mAtmosphereNightRoll(0.f) - , mCreated(false) - , mIsStorm(false) - , mDay(0) - , mMonth(0) - , mCloudAnimationTimer(0.f) - , mRainTimer(0.f) - , mStormDirection(0,1,0) - , mClouds() - , mNextClouds() - , mCloudBlendFactor(0.0f) - , mCloudSpeed(0.0f) - , mStarsOpacity(0.0f) - , mRemainingTransitionTime(0.0f) - , mRainEnabled(false) - , mRainSpeed(0) - , mRainDiameter(0) - , mRainMinHeight(0) - , mRainMaxHeight(0) - , mRainEntranceSpeed(1) - , mRainMaxRaindrops(0) - , mWindSpeed(0.f) - , mBaseWindSpeed(0.f) - , mEnabled(true) - , mSunEnabled(true) - , mPrecipitationAlpha(0.f) -{ - osg::ref_ptr skyroot (new CameraRelativeTransform); - skyroot->setName("Sky Root"); - // Assign empty program to specify we don't want shaders - // The shaders generated by the SceneManager can't handle everything we need - skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); - - skyroot->setNodeMask(Mask_Sky); - parentNode->addChild(skyroot); - - mRootNode = skyroot; - - mEarlyRenderBinRoot = new osg::Group; - // render before the world is rendered - mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); - // Prevent unwanted clipping by water reflection camera's clipping plane - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); - mRootNode->addChild(mEarlyRenderBinRoot); - - mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); -} - -void SkyManager::create() -{ - assert(!mCreated); - - mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); - ModVertexAlphaVisitor modAtmosphere(0); - mAtmosphereDay->accept(modAtmosphere); - - mAtmosphereUpdater = new AtmosphereUpdater; - mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); - - mAtmosphereNightNode = new osg::PositionAttitudeTransform; - mAtmosphereNightNode->setNodeMask(0); - mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); - - osg::ref_ptr atmosphereNight; - if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) - atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); - else - atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); - atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - ModVertexAlphaVisitor modStars(2); - atmosphereNight->accept(modStars); - mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager()); - atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - - mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); - - mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); - mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); - - mCloudNode = new osg::PositionAttitudeTransform; - mEarlyRenderBinRoot->addChild(mCloudNode); - mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); - ModVertexAlphaVisitor modClouds(1); - mCloudMesh->accept(modClouds); - mCloudUpdater = new CloudUpdater; - mCloudUpdater->setOpacity(1.f); - mCloudMesh->addUpdateCallback(mCloudUpdater); - - mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); - mCloudMesh2->accept(modClouds); - mCloudUpdater2 = new CloudUpdater; - mCloudUpdater2->setOpacity(0.f); - mCloudMesh2->addUpdateCallback(mCloudUpdater2); - mCloudMesh2->setNodeMask(0); - - osg::ref_ptr depth = new osg::Depth; - depth->setWriteMask(false); - mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); - - mMoonScriptColor = Fallback::Map::getColour("Moons_Script_Color"); - - mCreated = true; -} - -class RainCounter : public osgParticle::ConstantRateCounter -{ -public: - int numParticlesToCreate(double dt) const override - { - // 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: - RainShooter() - : mAngle(0.f) - { - } - - void shoot(osgParticle::Particle* particle) const override - { - particle->setVelocity(mVelocity); - particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); - } - - void setVelocity(const osg::Vec3f& velocity) - { - mVelocity = velocity; - } - - void setAngle(float angle) - { - mAngle = angle; - } - - osg::Object* cloneType() const override - { - return new RainShooter; - } - osg::Object* clone(const osg::CopyOp &) const override - { - return new RainShooter(*this); - } - -private: - osg::Vec3f mVelocity; - float mAngle; -}; - -// Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute. -class AlphaFader : public SceneUtil::StateSetUpdater -{ -public: - /// @param alpha the variable alpha value is recovered from - AlphaFader(float& alpha) - : mAlpha(alpha) - { - } - - void setDefaults(osg::StateSet* stateset) override - { - // need to create a deep copy of StateAttributes we will modify - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override - { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mAlpha)); - } - // Helper for adding AlphaFaders to a subgraph class SetupVisitor : public osg::NodeVisitor { public: - SetupVisitor(float &alpha) + SetupVisitor(const float& alpha) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAlpha(alpha) { } - void apply(osg::Node &node) override + void apply(osg::Node& node) override { if (osg::StateSet* stateset = node.getStateSet()) { @@ -1321,7 +184,7 @@ public: callback = callback->getNestedCallback(); } - osg::ref_ptr alphaFader (new AlphaFader(mAlpha)); + osg::ref_ptr alphaFader = new AlphaFader(mAlpha); if (composite) composite->addController(alphaFader); @@ -1334,613 +197,759 @@ public: } private: - float &mAlpha; + const float& mAlpha; }; -protected: - float &mAlpha; -}; - -void SkyManager::setCamera(osg::Camera *camera) -{ - mCamera = camera; -} - -class WrapAroundOperator : public osgParticle::Operator -{ -public: - WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange): osgParticle::Operator() + class SkyRTT : public SceneUtil::RTTNode { - mCamera = camera; - mWrapRange = wrapRange; - mHalfWrapRange = mWrapRange / 2.0; - mPreviousCameraPosition = getCameraPosition(); - } - - osg::Object *cloneType() const override - { - return nullptr; - } - - osg::Object *clone(const osg::CopyOp &op) const override - { - return nullptr; - } - - void operate(osgParticle::Particle *P, double dt) override - { - } - - void operateParticles(osgParticle::ParticleSystem *ps, double dt) override - { - osg::Vec3 position = getCameraPosition(); - osg::Vec3 positionDifference = position - mPreviousCameraPosition; - - osg::Matrix toWorld, toLocal; - - std::vector worldMatrices = ps->getWorldMatrices(); - - if (!worldMatrices.empty()) + public: + SkyRTT(osg::Vec2f size, osg::Group* earlyRenderBinRoot) + : RTTNode(static_cast(size.x()), static_cast(size.y()), 0, false, 1, StereoAwareness::Aware) + , mEarlyRenderBinRoot(earlyRenderBinRoot) { - toWorld = worldMatrices[0]; - toLocal.invert(toWorld); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); } - for (int i = 0; i < ps->numParticles(); ++i) + void setDefaults(osg::Camera* camera) override { - osgParticle::Particle *p = ps->getParticle(i); - p->setPosition(toWorld.preMult(p->getPosition())); - p->setPosition(p->getPosition() - positionDifference); - - for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions - { - osg::Vec3 pos = p->getPosition(); - - if (pos[j] < -mHalfWrapRange[j]) - pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j],mWrapRange[j]); - else if (pos[j] > mHalfWrapRange[j]) - pos[j] = fmod(pos[j] + mHalfWrapRange[j],mWrapRange[j]) - mHalfWrapRange[j]; - - p->setPosition(pos); - } - - p->setPosition(toLocal.preMult(p->getPosition())); + camera->setReferenceFrame(osg::Camera::RELATIVE_RF); + camera->setName("SkyCamera"); + camera->setNodeMask(MWRender::Mask_RenderToTexture); + camera->setCullMask(MWRender::Mask_Sky); + camera->addChild(mEarlyRenderBinRoot); + SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); } - mPreviousCameraPosition = position; - } + private: + osg::ref_ptr mEarlyRenderBinRoot; + }; -protected: - osg::Camera *mCamera; - osg::Vec3 mPreviousCameraPosition; - osg::Vec3 mWrapRange; - osg::Vec3 mHalfWrapRange; +} - osg::Vec3 getCameraPosition() +namespace MWRender +{ + SkyManager::SkyManager(osg::Group* parentNode, osg::Group* rootNode, osg::Camera* camera, + Resource::SceneManager* sceneManager, bool enableSkyRTT) + : mSceneManager(sceneManager) + , mCamera(camera) + , mAtmosphereNightRoll(0.f) + , mCreated(false) + , mIsStorm(false) + , mDay(0) + , mMonth(0) + , mCloudAnimationTimer(0.f) + , mRainTimer(0.f) + , mStormParticleDirection(MWWorld::Weather::defaultDirection()) + , mStormDirection(MWWorld::Weather::defaultDirection()) + , mClouds() + , mNextClouds() + , mCloudBlendFactor(0.f) + , mCloudSpeed(0.f) + , mStarsOpacity(0.f) + , mRemainingTransitionTime(0.f) + , mRainEnabled(false) + , mRainSpeed(0.f) + , mRainDiameter(0.f) + , mRainMinHeight(0.f) + , mRainMaxHeight(0.f) + , mRainEntranceSpeed(1.f) + , mRainMaxRaindrops(0) + , mWindSpeed(0.f) + , mBaseWindSpeed(0.f) + , mEnabled(true) + , mSunEnabled(true) + , mSunglareEnabled(true) + , mPrecipitationAlpha(0.f) + , mDirtyParticlesEffect(false) { - return mCamera->getInverseViewMatrix().getTrans(); - } -}; + osg::ref_ptr skyroot = new CameraRelativeTransform; + skyroot->setName("Sky Root"); + // Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline + if (!mSceneManager->getForceShaders()) + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), + osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); + SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); + parentNode->addChild(skyroot); -class WeatherAlphaOperator : public osgParticle::Operator -{ -public: - WeatherAlphaOperator(float& alpha, bool rain) - : mAlpha(alpha) - , mIsRain(rain) - { - } + mEarlyRenderBinRoot = new osg::Group; + // render before the world is rendered + mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); + // Prevent unwanted clipping by water reflection camera's clipping plane + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); - osg::Object *cloneType() const override - { - return nullptr; - } - - osg::Object *clone(const osg::CopyOp &op) const override - { - return nullptr; - } - - void operate(osgParticle::Particle *particle, double dt) override - { - constexpr float rainThreshold = 0.6f; // Rain_Threshold? - const float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; - particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); - } - -private: - float &mAlpha; - bool mIsRain; -}; - -void SkyManager::createRain() -{ - if (mRainNode) - return; - - mRainNode = new osg::Group; - - mRainParticleSystem = new NifOsg::ParticleSystem; - osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); - - mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); - mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); - mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1)); - - osg::ref_ptr stateset (mRainParticleSystem->getOrCreateStateSet()); - - osg::ref_ptr raindropTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds"))); - raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); - stateset->setNestRenderBins(false); - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - - osg::ref_ptr mat (new osg::Material); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - - osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); - particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); - particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); - particleTemplate.setLifeTime(1); - - osg::ref_ptr emitter (new osgParticle::ModularEmitter); - emitter->setParticleSystem(mRainParticleSystem); - - osg::ref_ptr placer (new osgParticle::BoxPlacer); - placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); - placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); - placer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); - emitter->setPlacer(placer); - mPlacer = placer; - - // FIXME: vanilla engine does not use a particle system to handle rain, it uses a NIF-file with 20 raindrops in it. - // It spawns the (maxRaindrops-getParticleSystem()->numParticles())*dt/rainEntranceSpeed batches every frame (near 1-2). - // Since the rain is a regular geometry, it produces water ripples, also in theory it can be removed if collides with something. - osg::ref_ptr counter (new RainCounter); - counter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); - emitter->setCounter(counter); - mCounter = counter; - - osg::ref_ptr shooter (new RainShooter); - mRainShooter = shooter; - emitter->setShooter(shooter); - - osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); - updater->addParticleSystem(mRainParticleSystem); - - osg::ref_ptr program (new osgParticle::ModularProgram); - program->addOperator(new WrapAroundOperator(mCamera,rainRange)); - program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true)); - program->setParticleSystem(mRainParticleSystem); - mRainNode->addChild(program); - - mRainNode->addChild(emitter); - mRainNode->addChild(mRainParticleSystem); - mRainNode->addChild(updater); - - // Note: if we ever switch to regular geometry rain, it'll need to use an AlphaFader. - mRainNode->addCullCallback(mUnderwaterSwitch); - mRainNode->setNodeMask(Mask_WeatherParticles); - - mRootNode->addChild(mRainNode); -} - -void SkyManager::destroyRain() -{ - if (!mRainNode) - return; - - mRootNode->removeChild(mRainNode); - mRainNode = nullptr; - mPlacer = nullptr; - mCounter = nullptr; - mRainParticleSystem = nullptr; - mRainShooter = nullptr; -} - -SkyManager::~SkyManager() -{ - if (mRootNode) - { - mRootNode->getParent(0)->removeChild(mRootNode); - mRootNode = nullptr; - } -} - -int SkyManager::getMasserPhase() const -{ - if (!mCreated) return 0; - return mMasser->getPhaseInt(); -} - -int SkyManager::getSecundaPhase() const -{ - if (!mCreated) return 0; - return mSecunda->getPhaseInt(); -} - -bool SkyManager::isEnabled() -{ - return mEnabled; -} - -bool SkyManager::hasRain() const -{ - return mRainNode != nullptr; -} - -float SkyManager::getPrecipitationAlpha() const -{ - if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) - return mPrecipitationAlpha; - - return 0.f; -} - -void SkyManager::update(float duration) -{ - if (!mEnabled) - return; - - switchUnderwaterRain(); - - if (mIsStorm) - { - osg::Quat quat; - quat.makeRotate(osg::Vec3f(0,1,0), mStormDirection); - - mCloudNode->setAttitude(quat); - if (mParticleNode) + if (enableSkyRTT) { + mSkyRTT = new SkyRTT(Settings::fog().mSkyRttResolution, mEarlyRenderBinRoot); + skyroot->addChild(mSkyRTT); + mRootNode = new osg::Group; + skyroot->addChild(mRootNode); + } + else + mRootNode = skyroot; + + mRootNode->setNodeMask(Mask_Sky); + mRootNode->addChild(mEarlyRenderBinRoot); + mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); + + mPrecipitationOcclusion = Settings::Manager::getBool("weather particle occlusion", "Shaders"); + mPrecipitationOccluder = std::make_unique(skyroot, parentNode, rootNode, camera); + } + + void SkyManager::create() + { + assert(!mCreated); + + bool forceShaders = mSceneManager->getForceShaders(); + + mAtmosphereDay + = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); + ModVertexAlphaVisitor modAtmosphere(ModVertexAlphaVisitor::Atmosphere); + mAtmosphereDay->accept(modAtmosphere); + + mAtmosphereUpdater = new AtmosphereUpdater; + mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); + + mAtmosphereNightNode = new osg::PositionAttitudeTransform; + mAtmosphereNightNode->setNodeMask(0); + mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); + + osg::ref_ptr atmosphereNight; + if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) + atmosphereNight = mSceneManager->getInstance( + Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); + else + atmosphereNight = mSceneManager->getInstance( + Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); + atmosphereNight->getOrCreateStateSet()->setAttributeAndModes( + createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + ModVertexAlphaVisitor modStars(ModVertexAlphaVisitor::Stars); + atmosphereNight->accept(modStars); + mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager(), forceShaders); + atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); + + mSun = std::make_unique(mEarlyRenderBinRoot, *mSceneManager); + mSun->setSunglare(mSunglareEnabled); + mMasser = std::make_unique( + mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Masser_Size") / 125, Moon::Type_Masser); + mSecunda = std::make_unique(mEarlyRenderBinRoot, *mSceneManager, + Fallback::Map::getFloat("Moons_Secunda_Size") / 125, Moon::Type_Secunda); + + mCloudNode = new osg::Group; + mEarlyRenderBinRoot->addChild(mCloudNode); + + mCloudMesh = new osg::PositionAttitudeTransform; + osg::ref_ptr cloudMeshChild + = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudMesh); + mCloudUpdater = new CloudUpdater(forceShaders); + mCloudUpdater->setOpacity(1.f); + cloudMeshChild->addUpdateCallback(mCloudUpdater); + mCloudMesh->addChild(cloudMeshChild); + + mNextCloudMesh = new osg::PositionAttitudeTransform; + osg::ref_ptr nextCloudMeshChild + = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mNextCloudMesh); + mNextCloudUpdater = new CloudUpdater(forceShaders); + mNextCloudUpdater->setOpacity(0.f); + nextCloudMeshChild->addUpdateCallback(mNextCloudUpdater); + mNextCloudMesh->setNodeMask(0); + mNextCloudMesh->addChild(nextCloudMeshChild); + + mCloudNode->addChild(mCloudMesh); + mCloudNode->addChild(mNextCloudMesh); + + ModVertexAlphaVisitor modClouds(ModVertexAlphaVisitor::Clouds); + mCloudMesh->accept(modClouds); + mNextCloudMesh->accept(modClouds); + + if (mSceneManager->getForceShaders()) + { + Shader::ShaderManager::DefineMap defines = {}; + Stereo::shaderStereoDefines(defines); + auto program = mSceneManager->getShaderManager().getProgram("sky", defines); + mEarlyRenderBinRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", -1)); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes( + program, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + + osg::ref_ptr depth = new SceneUtil::AutoDepth; + depth->setWriteMask(false); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); + + mMoonScriptColor = Fallback::Map::getColour("Moons_Script_Color"); + + mCreated = true; + } + + void SkyManager::createRain() + { + if (mRainNode) + return; + + mRainNode = new osg::Group; + + mRainParticleSystem = new NifOsg::ParticleSystem; + osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight + mRainMaxHeight) / 2.f); + + mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); + mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1, 0, 0)); + mRainParticleSystem->setAlignVectorY(osg::Vec3f(0, 0, 1)); + + osg::ref_ptr stateset = mRainParticleSystem->getOrCreateStateSet(); + + osg::ref_ptr raindropTex + = new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds")); + raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + stateset->setTextureAttributeAndModes(0, raindropTex); + stateset->setNestRenderBins(false); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + + osg::ref_ptr mat = new osg::Material; + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1)); + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + stateset->setAttributeAndModes(mat); + + osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); + particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); + particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); + particleTemplate.setLifeTime(1); + + osg::ref_ptr emitter = new osgParticle::ModularEmitter; + emitter->setParticleSystem(mRainParticleSystem); + + osg::ref_ptr placer = new osgParticle::BoxPlacer; + placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); + placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); + placer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); + emitter->setPlacer(placer); + mPlacer = placer; + + // FIXME: vanilla engine does not use a particle system to handle rain, it uses a NIF-file with 20 raindrops in + // it. It spawns the (maxRaindrops-getParticleSystem()->numParticles())*dt/rainEntranceSpeed batches every frame + // (near 1-2). Since the rain is a regular geometry, it produces water ripples, also in theory it can be removed + // if collides with something. + osg::ref_ptr counter = new RainCounter; + counter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops / mRainEntranceSpeed * 20); + emitter->setCounter(counter); + mCounter = counter; + + osg::ref_ptr shooter = new RainShooter; + mRainShooter = shooter; + emitter->setShooter(shooter); + + osg::ref_ptr updater = new osgParticle::ParticleSystemUpdater; + updater->addParticleSystem(mRainParticleSystem); + + osg::ref_ptr program = new osgParticle::ModularProgram; + program->addOperator(new WrapAroundOperator(mCamera, rainRange)); + program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true)); + program->setParticleSystem(mRainParticleSystem); + mRainNode->addChild(program); + + mRainNode->addChild(emitter); + mRainNode->addChild(mRainParticleSystem); + mRainNode->addChild(updater); + + // Note: if we ever switch to regular geometry rain, it'll need to use an AlphaFader. + mRainNode->addCullCallback(mUnderwaterSwitch); + mRainNode->setNodeMask(Mask_WeatherParticles); + + mRainParticleSystem->setUserValue("simpleLighting", true); + mRainParticleSystem->setUserValue("particleOcclusion", true); + mSceneManager->recreateShaders(mRainNode); + + mRootNode->addChild(mRainNode); + if (mPrecipitationOcclusion) + mPrecipitationOccluder->enable(); + } + + void SkyManager::destroyRain() + { + if (!mRainNode) + return; + + mRootNode->removeChild(mRainNode); + mRainNode = nullptr; + mPlacer = nullptr; + mCounter = nullptr; + mRainParticleSystem = nullptr; + mRainShooter = nullptr; + mPrecipitationOccluder->disable(); + } + + SkyManager::~SkyManager() + { + if (mRootNode) + { + mRootNode->getParent(0)->removeChild(mRootNode); + mRootNode = nullptr; + } + } + + int SkyManager::getMasserPhase() const + { + if (!mCreated) + return 0; + return mMasser->getPhaseInt(); + } + + int SkyManager::getSecundaPhase() const + { + if (!mCreated) + return 0; + return mSecunda->getPhaseInt(); + } + + bool SkyManager::isEnabled() + { + return mEnabled; + } + + bool SkyManager::hasRain() const + { + return mRainNode != nullptr; + } + + float SkyManager::getPrecipitationAlpha() const + { + if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) + return mPrecipitationAlpha; + + return 0.f; + } + + void SkyManager::update(float duration) + { + if (!mEnabled) + return; + + switchUnderwaterRain(); + + if (mIsStorm && mParticleNode) + { + osg::Quat quat; + quat.makeRotate(MWWorld::Weather::defaultDirection(), mStormParticleDirection); // Morrowind deliberately rotates the blizzard mesh, so so should we. - if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models")) - quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection); + if (mCurrentParticleEffect == "meshes\\blizzard.nif") + quat.makeRotate(osg::Vec3f(-1, 0, 0), mStormParticleDirection); mParticleNode->setAttitude(quat); } - } - else - mCloudNode->setAttitude(osg::Quat()); - // UV Scroll the clouds - mCloudAnimationTimer += duration * mCloudSpeed * 0.003; - mCloudUpdater->setAnimationTimer(mCloudAnimationTimer); - mCloudUpdater2->setAnimationTimer(mCloudAnimationTimer); + // UV Scroll the clouds + mCloudAnimationTimer += duration * mCloudSpeed * 0.003; + mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer); + mCloudUpdater->setTextureCoord(mCloudAnimationTimer); - // rotate the stars by 360 degrees every 4 days - mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f); - if (mAtmosphereNightNode->getNodeMask() != 0) - mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0,0,1))); -} + // morrowind rotates each cloud mesh independently + osg::Quat rotation; + rotation.makeRotate(MWWorld::Weather::defaultDirection(), mStormDirection); + mCloudMesh->setAttitude(rotation); -void SkyManager::setEnabled(bool enabled) -{ - if (enabled && !mCreated) - create(); - - mRootNode->setNodeMask(enabled ? Mask_Sky : 0u); - - mEnabled = enabled; -} - -void SkyManager::setMoonColour (bool red) -{ - if (!mCreated) return; - mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1,1,1,1)); -} - -void SkyManager::updateRainParameters() -{ - if (mRainShooter) - { - float angle = -std::atan(mWindSpeed/50.f); - mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed*std::sin(angle), -mRainSpeed/std::cos(angle))); - mRainShooter->setAngle(angle); - - osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); - - mPlacer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); - mPlacer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); - mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); - - mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); - } -} - -void SkyManager::switchUnderwaterRain() -{ - if (!mRainParticleSystem) - return; - - bool freeze = mUnderwaterSwitch->isUnderwater(); - mRainParticleSystem->setFrozen(freeze); -} - -void SkyManager::setWeather(const WeatherResult& weather) -{ - if (!mCreated) return; - - mRainEntranceSpeed = weather.mRainEntranceSpeed; - mRainMaxRaindrops = weather.mRainMaxRaindrops; - mRainDiameter = weather.mRainDiameter; - mRainMinHeight = weather.mRainMinHeight; - mRainMaxHeight = weather.mRainMaxHeight; - mRainSpeed = weather.mRainSpeed; - mWindSpeed = weather.mWindSpeed; - mBaseWindSpeed = weather.mBaseWindSpeed; - - if (mRainEffect != weather.mRainEffect) - { - mRainEffect = weather.mRainEffect; - if (!mRainEffect.empty()) + if (mNextCloudMesh->getNodeMask()) { - createRain(); + rotation.makeRotate(MWWorld::Weather::defaultDirection(), mNextStormDirection); + mNextCloudMesh->setAttitude(rotation); } - else + + // rotate the stars by 360 degrees every 4 days + mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor() * duration + * osg::DegreesToRadians(360.f) / (3600 * 96.f); + if (mAtmosphereNightNode->getNodeMask() != 0) + mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1))); + mPrecipitationOccluder->update(); + } + + void SkyManager::setEnabled(bool enabled) + { + if (enabled && !mCreated) + create(); + + const osg::Node::NodeMask mask = enabled ? Mask_Sky : 0u; + + mEarlyRenderBinRoot->setNodeMask(mask); + mRootNode->setNodeMask(mask); + + if (!enabled && mParticleNode && mParticleEffect) { - destroyRain(); + mCurrentParticleEffect.clear(); + mDirtyParticlesEffect = true; + } + + mEnabled = enabled; + } + + void SkyManager::setMoonColour(bool red) + { + if (!mCreated) + return; + mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1, 1, 1, 1)); + } + + void SkyManager::updateRainParameters() + { + if (mRainShooter) + { + float angle = -std::atan(mWindSpeed / 50.f); + mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed * std::sin(angle), -mRainSpeed / std::cos(angle))); + mRainShooter->setAngle(angle); + + osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight + mRainMaxHeight) / 2.f); + + mPlacer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); + mPlacer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); + mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); + + mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops / mRainEntranceSpeed * 20); + mPrecipitationOccluder->updateRange(rainRange); } } - updateRainParameters(); - - mIsStorm = weather.mIsStorm; - - if (mCurrentParticleEffect != weather.mParticleEffect) + void SkyManager::switchUnderwaterRain() { - mCurrentParticleEffect = weather.mParticleEffect; + if (!mRainParticleSystem) + return; - // cleanup old particles - if (mParticleEffect) - { - mParticleNode->removeChild(mParticleEffect); - mParticleEffect = nullptr; - } + bool freeze = mUnderwaterSwitch->isUnderwater(); + mRainParticleSystem->setFrozen(freeze); + } - if (mCurrentParticleEffect.empty()) + void SkyManager::setWeather(const WeatherResult& weather) + { + if (!mCreated) + return; + + mRainEntranceSpeed = weather.mRainEntranceSpeed; + mRainMaxRaindrops = weather.mRainMaxRaindrops; + mRainDiameter = weather.mRainDiameter; + mRainMinHeight = weather.mRainMinHeight; + mRainMaxHeight = weather.mRainMaxHeight; + mRainSpeed = weather.mRainSpeed; + mWindSpeed = weather.mWindSpeed; + mBaseWindSpeed = weather.mBaseWindSpeed; + + if (mRainEffect != weather.mRainEffect) { - if (mParticleNode) + mRainEffect = weather.mRainEffect; + if (!mRainEffect.empty()) { - mRootNode->removeChild(mParticleNode); - mParticleNode = nullptr; + createRain(); + } + else + { + destroyRain(); } } - else + + updateRainParameters(); + + mIsStorm = weather.mIsStorm; + + if (mIsStorm) + mStormDirection = weather.mStormDirection; + + if (mDirtyParticlesEffect || (mCurrentParticleEffect != weather.mParticleEffect)) { - if (!mParticleNode) + mDirtyParticlesEffect = false; + mCurrentParticleEffect = weather.mParticleEffect; + + // cleanup old particles + if (mParticleEffect) { - mParticleNode = new osg::PositionAttitudeTransform; - mParticleNode->addCullCallback(mUnderwaterSwitch); - mParticleNode->setNodeMask(Mask_WeatherParticles); - mRootNode->addChild(mParticleNode); + mParticleNode->removeChild(mParticleEffect); + mParticleEffect = nullptr; } - mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); - - SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(new SceneUtil::FrameTimeSource)); - mParticleEffect->accept(assignVisitor); - - AlphaFader::SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); - - mParticleEffect->accept(alphaFaderSetupVisitor); - - SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; - mParticleEffect->accept(disableFreezeOnCullVisitor); - - SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); - mParticleEffect->accept(findPSVisitor); - - for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) + if (mCurrentParticleEffect.empty()) { - osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); - - osg::ref_ptr program (new osgParticle::ModularProgram); - if (!mIsStorm) - program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); - program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); - program->setParticleSystem(ps); - mParticleNode->addChild(program); + if (mParticleNode) + { + mRootNode->removeChild(mParticleNode); + mParticleNode = nullptr; + } + if (mRainEffect.empty()) + { + mPrecipitationOccluder->disable(); + } + } + else + { + if (!mParticleNode) + { + mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->addCullCallback(mUnderwaterSwitch); + mParticleNode->setNodeMask(Mask_WeatherParticles); + mRootNode->addChild(mParticleNode); + } + + mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::make_shared()); + mParticleEffect->accept(assignVisitor); + + SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); + mParticleEffect->accept(alphaFaderSetupVisitor); + + SceneUtil::FindByClassVisitor findPSVisitor("ParticleSystem"); + mParticleEffect->accept(findPSVisitor); + + const osg::Vec3 defaultWrapRange = osg::Vec3(1024, 1024, 800); + const bool occlusionEnabledForEffect + = !mRainEffect.empty() || mCurrentParticleEffect == "meshes\\snow.nif"; + + for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) + { + osgParticle::ParticleSystem* ps + = static_cast(findPSVisitor.mFoundNodes[i]); + + osg::ref_ptr program = new osgParticle::ModularProgram; + if (!mIsStorm) + program->addOperator(new WrapAroundOperator(mCamera, defaultWrapRange)); + program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); + program->setParticleSystem(ps); + mParticleNode->addChild(program); + + for (int particleIndex = 0; particleIndex < ps->numParticles(); ++particleIndex) + { + ps->getParticle(particleIndex) + ->setAlphaRange(osgParticle::rangef(mPrecipitationAlpha, mPrecipitationAlpha)); + ps->getParticle(particleIndex)->update(0, true); + } + + ps->setUserValue("simpleLighting", true); + + if (occlusionEnabledForEffect) + ps->setUserValue("particleOcclusion", true); + } + + mSceneManager->recreateShaders(mParticleNode); + + if (mPrecipitationOcclusion && occlusionEnabledForEffect) + { + mPrecipitationOccluder->enable(); + mPrecipitationOccluder->updateRange(defaultWrapRange); + } } } - } - if (mClouds != weather.mCloudTexture) - { - mClouds = weather.mCloudTexture; - - std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); - - osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); - cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - - mCloudUpdater->setTexture(cloudTex); - } - - if (mNextClouds != weather.mNextCloudTexture) - { - mNextClouds = weather.mNextCloudTexture; - - if (!mNextClouds.empty()) + if (mClouds != weather.mCloudTexture) { - std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); + mClouds = weather.mCloudTexture; - osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); + std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); + + osg::ref_ptr cloudTex + = new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mCloudUpdater2->setTexture(cloudTex); + mCloudUpdater->setTexture(cloudTex); } + + if (mStormDirection != weather.mStormDirection) + mStormDirection = weather.mStormDirection; + + if (mNextStormDirection != weather.mNextStormDirection) + mNextStormDirection = weather.mNextStormDirection; + + if (mNextClouds != weather.mNextCloudTexture) + { + mNextClouds = weather.mNextCloudTexture; + + if (!mNextClouds.empty()) + { + std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); + + osg::ref_ptr cloudTex + = new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)); + cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + + mNextCloudUpdater->setTexture(cloudTex); + mNextStormDirection = weather.mStormDirection; + } + } + + if (mCloudBlendFactor != weather.mCloudBlendFactor) + { + mCloudBlendFactor = std::clamp(weather.mCloudBlendFactor, 0.f, 1.f); + + mCloudUpdater->setOpacity(1.f - mCloudBlendFactor); + mNextCloudUpdater->setOpacity(mCloudBlendFactor); + mNextCloudMesh->setNodeMask(mCloudBlendFactor > 0.f ? ~0u : 0); + } + + if (mCloudColour != weather.mFogColor) + { + osg::Vec4f clr(weather.mFogColor); + clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); + + mCloudUpdater->setEmissionColor(clr); + mNextCloudUpdater->setEmissionColor(clr); + + mCloudColour = weather.mFogColor; + } + + if (mSkyColour != weather.mSkyColor) + { + mSkyColour = weather.mSkyColor; + + mAtmosphereUpdater->setEmissionColor(mSkyColour); + mMasser->setAtmosphereColor(mSkyColour); + mSecunda->setAtmosphereColor(mSkyColour); + } + + if (mFogColour != weather.mFogColor) + { + mFogColour = weather.mFogColor; + } + + mCloudSpeed = weather.mCloudSpeed; + + mMasser->adjustTransparency(weather.mGlareView); + mSecunda->adjustTransparency(weather.mGlareView); + + mSun->setColor(weather.mSunDiscColor); + mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); + + float nextStarsOpacity = weather.mNightFade * weather.mGlareView; + + if (weather.mNight && mStarsOpacity != nextStarsOpacity) + { + mStarsOpacity = nextStarsOpacity; + + mAtmosphereNightUpdater->setFade(mStarsOpacity); + } + + mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0u : 0); + mPrecipitationAlpha = weather.mPrecipitationAlpha; } - if (mCloudBlendFactor != weather.mCloudBlendFactor) + float SkyManager::getBaseWindSpeed() const { - mCloudBlendFactor = weather.mCloudBlendFactor; + if (!mCreated) + return 0.f; - mCloudUpdater->setOpacity((1.f-mCloudBlendFactor)); - mCloudUpdater2->setOpacity(mCloudBlendFactor); - mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? ~0u : 0); + return mBaseWindSpeed; } - if (mCloudColour != weather.mFogColor) + void SkyManager::setSunglare(bool enabled) { - osg::Vec4f clr (weather.mFogColor); - clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); + mSunglareEnabled = enabled; - mCloudUpdater->setEmissionColor(clr); - mCloudUpdater2->setEmissionColor(clr); - - mCloudColour = weather.mFogColor; + if (mSun) + mSun->setSunglare(mSunglareEnabled); } - if (mSkyColour != weather.mSkyColor) + void SkyManager::sunEnable() { - mSkyColour = weather.mSkyColor; + if (!mCreated) + return; - mAtmosphereUpdater->setEmissionColor(mSkyColour); - mMasser->setAtmosphereColor(mSkyColour); - mSecunda->setAtmosphereColor(mSkyColour); + mSun->setVisible(true); } - if (mFogColour != weather.mFogColor) + void SkyManager::sunDisable() { - mFogColour = weather.mFogColor; + if (!mCreated) + return; + + mSun->setVisible(false); } - mCloudSpeed = weather.mCloudSpeed; - - mMasser->adjustTransparency(weather.mGlareView); - mSecunda->adjustTransparency(weather.mGlareView); - - mSun->setColor(weather.mSunDiscColor); - mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); - - float nextStarsOpacity = weather.mNightFade * weather.mGlareView; - - if (weather.mNight && mStarsOpacity != nextStarsOpacity) + void SkyManager::setStormParticleDirection(const osg::Vec3f& direction) { - mStarsOpacity = nextStarsOpacity; - - mAtmosphereNightUpdater->setFade(mStarsOpacity); + mStormParticleDirection = direction; } - mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0u : 0); - - mPrecipitationAlpha = weather.mPrecipitationAlpha; -} - -float SkyManager::getBaseWindSpeed() const -{ - if (!mCreated) return 0.f; - - return mBaseWindSpeed; -} - -void SkyManager::sunEnable() -{ - if (!mCreated) return; - - mSun->setVisible(true); -} - -void SkyManager::sunDisable() -{ - if (!mCreated) return; - - mSun->setVisible(false); -} - -void SkyManager::setStormDirection(const osg::Vec3f &direction) -{ - mStormDirection = direction; -} - -void SkyManager::setSunDirection(const osg::Vec3f& direction) -{ - if (!mCreated) return; - - mSun->setDirection(direction); -} - -void SkyManager::setMasserState(const MoonState& state) -{ - if(!mCreated) return; - - mMasser->setState(state); -} - -void SkyManager::setSecundaState(const MoonState& state) -{ - if(!mCreated) return; - - mSecunda->setState(state); -} - -void SkyManager::setDate(int day, int month) -{ - mDay = day; - mMonth = month; -} - -void SkyManager::setGlareTimeOfDayFade(float val) -{ - mSun->setGlareTimeOfDayFade(val); -} - -void SkyManager::setWaterHeight(float height) -{ - mUnderwaterSwitch->setWaterLevel(height); -} - -void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) -{ - models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); - if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) - models.emplace_back(Settings::Manager::getString("skynight02", "Models")); - models.emplace_back(Settings::Manager::getString("skynight01", "Models")); - models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); - - models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); - models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); - models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); - models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); - - textures.emplace_back("textures/tx_mooncircle_full_s.dds"); - textures.emplace_back("textures/tx_mooncircle_full_m.dds"); - - textures.emplace_back("textures/tx_masser_new.dds"); - textures.emplace_back("textures/tx_masser_one_wax.dds"); - textures.emplace_back("textures/tx_masser_half_wax.dds"); - textures.emplace_back("textures/tx_masser_three_wax.dds"); - textures.emplace_back("textures/tx_masser_one_wan.dds"); - textures.emplace_back("textures/tx_masser_half_wan.dds"); - textures.emplace_back("textures/tx_masser_three_wan.dds"); - textures.emplace_back("textures/tx_masser_full.dds"); - - textures.emplace_back("textures/tx_secunda_new.dds"); - textures.emplace_back("textures/tx_secunda_one_wax.dds"); - textures.emplace_back("textures/tx_secunda_half_wax.dds"); - textures.emplace_back("textures/tx_secunda_three_wax.dds"); - textures.emplace_back("textures/tx_secunda_one_wan.dds"); - textures.emplace_back("textures/tx_secunda_half_wan.dds"); - textures.emplace_back("textures/tx_secunda_three_wan.dds"); - textures.emplace_back("textures/tx_secunda_full.dds"); - - textures.emplace_back("textures/tx_sun_05.dds"); - textures.emplace_back("textures/tx_sun_flash_grey_05.dds"); - - textures.emplace_back("textures/tx_raindrop_01.dds"); -} - -void SkyManager::setWaterEnabled(bool enabled) -{ - mUnderwaterSwitch->setEnabled(enabled); -} + void SkyManager::setSunDirection(const osg::Vec3f& direction) + { + if (!mCreated) + return; + mSun->setDirection(direction); + } + + void SkyManager::setMasserState(const MoonState& state) + { + if (!mCreated) + return; + + mMasser->setState(state); + } + + void SkyManager::setSecundaState(const MoonState& state) + { + if (!mCreated) + return; + + mSecunda->setState(state); + } + + void SkyManager::setDate(int day, int month) + { + mDay = day; + mMonth = month; + } + + void SkyManager::setGlareTimeOfDayFade(float val) + { + mSun->setGlareTimeOfDayFade(val); + } + + void SkyManager::setWaterHeight(float height) + { + mUnderwaterSwitch->setWaterLevel(height); + } + + void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) + { + models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); + if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) + models.emplace_back(Settings::Manager::getString("skynight02", "Models")); + models.emplace_back(Settings::Manager::getString("skynight01", "Models")); + models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); + + models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); + models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); + models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); + models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); + + textures.emplace_back("textures/tx_mooncircle_full_s.dds"); + textures.emplace_back("textures/tx_mooncircle_full_m.dds"); + + textures.emplace_back("textures/tx_masser_new.dds"); + textures.emplace_back("textures/tx_masser_one_wax.dds"); + textures.emplace_back("textures/tx_masser_half_wax.dds"); + textures.emplace_back("textures/tx_masser_three_wax.dds"); + textures.emplace_back("textures/tx_masser_one_wan.dds"); + textures.emplace_back("textures/tx_masser_half_wan.dds"); + textures.emplace_back("textures/tx_masser_three_wan.dds"); + textures.emplace_back("textures/tx_masser_full.dds"); + + textures.emplace_back("textures/tx_secunda_new.dds"); + textures.emplace_back("textures/tx_secunda_one_wax.dds"); + textures.emplace_back("textures/tx_secunda_half_wax.dds"); + textures.emplace_back("textures/tx_secunda_three_wax.dds"); + textures.emplace_back("textures/tx_secunda_one_wan.dds"); + textures.emplace_back("textures/tx_secunda_half_wan.dds"); + textures.emplace_back("textures/tx_secunda_three_wan.dds"); + textures.emplace_back("textures/tx_secunda_full.dds"); + + textures.emplace_back("textures/tx_sun_05.dds"); + textures.emplace_back("textures/tx_sun_flash_grey_05.dds"); + + textures.emplace_back("textures/tx_raindrop_01.dds"); + } + + void SkyManager::setWaterEnabled(bool enabled) + { + mUnderwaterSwitch->setEnabled(enabled); + } } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index f8c501dda..75c6a10a5 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -1,17 +1,15 @@ #ifndef OPENMW_MWRENDER_SKY_H #define OPENMW_MWRENDER_SKY_H -#include #include +#include #include -#include #include +#include -namespace osg -{ - class Camera; -} +#include "precipitationocclusion.hpp" +#include "skyutil.hpp" namespace osg { @@ -19,6 +17,7 @@ namespace osg class Node; class Material; class PositionAttitudeTransform; + class Camera; } namespace osgParticle @@ -32,109 +31,31 @@ namespace Resource class SceneManager; } +namespace SceneUtil +{ + class RTTNode; +} + namespace MWRender { - class AtmosphereUpdater; - class AtmosphereNightUpdater; - class CloudUpdater; - class Sun; - class Moon; - class RainCounter; - class RainShooter; - class RainFader; - class AlphaFader; - class UnderwaterSwitchCallback; - - struct WeatherResult - { - std::string mCloudTexture; - std::string mNextCloudTexture; - float mCloudBlendFactor; - - osg::Vec4f mFogColor; - - osg::Vec4f mAmbientColor; - - osg::Vec4f mSkyColor; - - // sun light color - osg::Vec4f mSunColor; - - // alpha is the sun transparency - osg::Vec4f mSunDiscColor; - - float mFogDepth; - - float mDLFogFactor; - float mDLFogOffset; - - float mWindSpeed; - float mBaseWindSpeed; - float mCurrentWindSpeed; - float mNextWindSpeed; - - float mCloudSpeed; - - float mGlareView; - - bool mNight; // use night skybox - float mNightFade; // fading factor for night skybox - - bool mIsStorm; - - std::string mAmbientLoopSoundID; - float mAmbientSoundVolume; - - std::string mParticleEffect; - std::string mRainEffect; - float mPrecipitationAlpha; - - float mRainDiameter; - float mRainMinHeight; - float mRainMaxHeight; - float mRainSpeed; - float mRainEntranceSpeed; - int mRainMaxRaindrops; - }; - - struct MoonState - { - enum class Phase - { - Full = 0, - WaningGibbous, - ThirdQuarter, - WaningCrescent, - New, - WaxingCrescent, - FirstQuarter, - WaxingGibbous, - Unspecified - }; - - float mRotationFromHorizon; - float mRotationFromNorth; - Phase mPhase; - float mShadowBlend; - float mMoonAlpha; - }; - - ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to be rendered + ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to + /// be rendered /// relative to the camera (e.g. weather particle effects) class SkyManager { public: - SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager); + SkyManager(osg::Group* parentNode, osg::Group* rootNode, osg::Camera* camera, + Resource::SceneManager* sceneManager, bool enableSkyRTT); ~SkyManager(); void update(float duration); void setEnabled(bool enabled); - void setHour (double hour); + void setHour(double hour); ///< will be called even when sky is disabled. - void setDate (int day, int month); + void setDate(int day, int month); ///< will be called even when sky is disabled. int getMasserPhase() const; @@ -145,7 +66,7 @@ namespace MWRender ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - void setMoonColour (bool red); + void setMoonColour(bool red); ///< change Secunda colour to red void setWeather(const WeatherResult& weather); @@ -162,7 +83,7 @@ namespace MWRender void setRainSpeed(float speed); - void setStormDirection(const osg::Vec3f& direction); + void setStormParticleDirection(const osg::Vec3f& direction); void setSunDirection(const osg::Vec3f& direction); @@ -179,10 +100,14 @@ namespace MWRender void listAssetsToPreload(std::vector& models, std::vector& textures); - void setCamera(osg::Camera *camera); - float getBaseWindSpeed() const; + void setSunglare(bool enabled); + + SceneUtil::RTTNode* getSkyRTT() { return mSkyRTT.get(); } + + osg::Vec4f getSkyColor() const { return mSkyColour; } + private: void create(); ///< no need to call this, automatically done on first enable() @@ -194,7 +119,7 @@ namespace MWRender Resource::SceneManager* mSceneManager; - osg::Camera *mCamera; + osg::Camera* mCamera; osg::ref_ptr mRootNode; osg::ref_ptr mEarlyRenderBinRoot; @@ -203,12 +128,12 @@ namespace MWRender osg::ref_ptr mParticleEffect; osg::ref_ptr mUnderwaterSwitch; - osg::ref_ptr mCloudNode; + osg::ref_ptr mCloudNode; osg::ref_ptr mCloudUpdater; - osg::ref_ptr mCloudUpdater2; - osg::ref_ptr mCloudMesh; - osg::ref_ptr mCloudMesh2; + osg::ref_ptr mNextCloudUpdater; + osg::ref_ptr mCloudMesh; + osg::ref_ptr mNextCloudMesh; osg::ref_ptr mAtmosphereDay; @@ -228,6 +153,9 @@ namespace MWRender osg::ref_ptr mCounter; osg::ref_ptr mRainShooter; + bool mPrecipitationOcclusion = false; + std::unique_ptr mPrecipitationOccluder; + bool mCreated; bool mIsStorm; @@ -239,7 +167,10 @@ namespace MWRender float mRainTimer; + // particle system rotation is independent of cloud rotation internally + osg::Vec3f mStormParticleDirection; osg::Vec3f mStormDirection; + osg::Vec3f mNextStormDirection; // remember some settings so we don't have to apply them again if they didn't change std::string mClouds; @@ -268,11 +199,15 @@ namespace MWRender bool mEnabled; bool mSunEnabled; + bool mSunglareEnabled; float mPrecipitationAlpha; + bool mDirtyParticlesEffect; osg::Vec4f mMoonScriptColor; + + osg::ref_ptr mSkyRTT; }; } -#endif // GAME_RENDER_SKY_H +#endif diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp new file mode 100644 index 000000000..2a21b950e --- /dev/null +++ b/apps/openmw/mwrender/skyutil.cpp @@ -0,0 +1,1210 @@ +#include "skyutil.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" + +#include "renderbin.hpp" +#include "vismask.hpp" + +namespace +{ + enum class Pass + { + Atmosphere, + Atmosphere_Night, + Clouds, + Moon, + Sun, + Sunflash_Query, + Sunglare, + }; + + osg::ref_ptr createTexturedQuad(int numUvSets = 1, float scale = 1.f) + { + osg::ref_ptr geom = new osg::Geometry; + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-0.5 * scale, -0.5 * scale, 0)); + verts->push_back(osg::Vec3f(-0.5 * scale, 0.5 * scale, 0)); + verts->push_back(osg::Vec3f(0.5 * scale, 0.5 * scale, 0)); + verts->push_back(osg::Vec3f(0.5 * scale, -0.5 * scale, 0)); + + geom->setVertexArray(verts); + + osg::ref_ptr texcoords = new osg::Vec2Array; + texcoords->push_back(osg::Vec2f(0, 1)); + texcoords->push_back(osg::Vec2f(0, 0)); + texcoords->push_back(osg::Vec2f(1, 0)); + texcoords->push_back(osg::Vec2f(1, 1)); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); + geom->setColorArray(colors, osg::Array::BIND_OVERALL); + + for (int i = 0; i < numUvSets; ++i) + geom->setTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); + + geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4)); + + return geom; + } + + struct DummyComputeBoundCallback : osg::Node::ComputeBoundingSphereCallback + { + osg::BoundingSphere computeBound(const osg::Node& node) const override { return osg::BoundingSphere(); } + }; +} + +namespace MWRender +{ + osg::ref_ptr createUnlitMaterial(osg::Material::ColorMode colorMode) + { + osg::ref_ptr mat = new osg::Material; + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mat->setColorMode(colorMode); + return mat; + } + + osg::ref_ptr createAlphaTrackingUnlitMaterial() + { + return createUnlitMaterial(osg::Material::DIFFUSE); + } + + class SunUpdater : public SceneUtil::StateSetUpdater + { + public: + osg::Vec4f mColor; + + SunUpdater() + : mColor(1.f, 1.f, 1.f, 1.f) + { + } + + void setDefaults(osg::StateSet* stateset) override { stateset->setAttributeAndModes(createUnlitMaterial()); } + + void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, mColor.a())); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); + } + }; + + OcclusionCallback::OcclusionCallback( + osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : mOcclusionQueryVisiblePixels(oqnVisible) + , mOcclusionQueryTotalPixels(oqnTotal) + { + } + + float OcclusionCallback::getVisibleRatio(osg::Camera* camera) + { + int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); + int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); + + float visibleRatio = 0.f; + if (total > 0) + visibleRatio = static_cast(visible) / static_cast(total); + + float dt = MWBase::Environment::get().getFrameDuration(); + + float lastRatio = mLastRatio[osg::observer_ptr(camera)]; + + float change = dt * 10; + + if (visibleRatio > lastRatio) + visibleRatio = std::min(visibleRatio, lastRatio + change); + else + visibleRatio = std::max(visibleRatio, lastRatio - change); + + mLastRatio[osg::observer_ptr(camera)] = visibleRatio; + + return visibleRatio; + } + + /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a + /// cull callback. + class SunFlashCallback : public OcclusionCallback, + public SceneUtil::NodeCallback + { + public: + SunFlashCallback( + osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : OcclusionCallback(oqnVisible, oqnTotal) + , mGlareView(1.f) + { + } + + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + osg::ref_ptr stateset; + + if (visibleRatio > 0.f) + { + const float fadeThreshold = 0.1; + if (visibleRatio < fadeThreshold) + { + float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; + osg::ref_ptr mat(createUnlitMaterial()); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, fade * mGlareView)); + stateset = new osg::StateSet; + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + else if (visibleRatio < 1.f) + { + const float threshold = 0.6; + visibleRatio = visibleRatio * (1.f - threshold) + threshold; + } + } + + float scale = visibleRatio; + + if (scale == 0.f) + { + // no traverse + return; + } + else if (scale == 1.f) + traverse(node, cv); + else + { + osg::Matrix modelView = *cv->getModelViewMatrix(); + + modelView.preMultScale(osg::Vec3f(scale, scale, scale)); + + if (stateset) + cv->pushStateSet(stateset); + + cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); + + traverse(node, cv); + + cv->popModelViewMatrix(); + + if (stateset) + cv->popStateSet(); + } + } + + void setGlareView(float value) { mGlareView = value; } + + private: + float mGlareView; + }; + + /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between + /// sun and camera. Must be attached as a cull callback to the node above the glare node. + class SunGlareCallback : public OcclusionCallback, + public SceneUtil::NodeCallback + { + public: + SunGlareCallback(osg::ref_ptr oqnVisible, + osg::ref_ptr oqnTotal, osg::ref_ptr sunTransform) + : OcclusionCallback(oqnVisible, oqnTotal) + , mSunTransform(sunTransform) + , mTimeOfDayFade(1.f) + , mGlareView(1.f) + { + mColor = Fallback::Map::getColour("Weather_Sun_Glare_Fader_Color"); + mSunGlareFaderMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Max"); + mSunGlareFaderAngleMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Angle_Max"); + + // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which + // multiplies the result by two, then finally gets clamped by the fixed function pipeline. With the default + // INI settings, only the red component gets clamped, so the resulting color looks more orange than red. + mColor *= 2; + for (int i = 0; i < 3; ++i) + mColor[i] = std::min(1.f, mColor[i]); + } + + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); + + float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); + float fade = value * mSunGlareFaderMax; + + fade *= mTimeOfDayFade * mGlareView * visibleRatio; + + if (fade == 0.f) + { + // no traverse + return; + } + else + { + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr mat = createUnlitMaterial(); + + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, fade)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); + + stateset->setAttributeAndModes(mat); + + cv->pushStateSet(stateset); + traverse(node, cv); + cv->popStateSet(); + } + } + + void setTimeOfDayFade(float val) { mTimeOfDayFade = val; } + + void setGlareView(float glareView) { mGlareView = glareView; } + + private: + float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const + { + osg::Vec3d eye, center, up; + viewMatrix.getLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Vec3d sun = mSunTransform->getPosition(); + + forward.normalize(); + sun.normalize(); + float angleRadians = std::acos(forward * sun); + return angleRadians; + } + + osg::ref_ptr mSunTransform; + float mTimeOfDayFade; + float mGlareView; + osg::Vec4f mColor; + float mSunGlareFaderMax; + float mSunGlareFaderAngleMax; + }; + + struct MoonUpdater : SceneUtil::StateSetUpdater + { + Resource::ImageManager& mImageManager; + osg::ref_ptr mPhaseTex; + osg::ref_ptr mCircleTex; + float mTransparency; + float mShadowBlend; + osg::Vec4f mAtmosphereColor; + osg::Vec4f mMoonColor; + bool mForceShaders; + + MoonUpdater(Resource::ImageManager& imageManager, bool forceShaders) + : mImageManager(imageManager) + , mPhaseTex() + , mCircleTex() + , mTransparency(1.0f) + , mShadowBlend(1.0f) + , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) + , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) + , mForceShaders(forceShaders) + { + } + + void setDefaults(osg::StateSet* stateset) override + { + if (mForceShaders) + { + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon))); + stateset->setTextureAttributeAndModes(0, mPhaseTex); + stateset->setTextureAttributeAndModes(1, mCircleTex); + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("moonBlend", osg::Vec4f{})); + stateset->addUniform(new osg::Uniform("atmosphereFade", osg::Vec4f{})); + stateset->addUniform(new osg::Uniform("diffuseMap", 0)); + stateset->addUniform(new osg::Uniform("maskMap", 1)); + stateset->setAttributeAndModes( + createUnlitMaterial(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + else + { + stateset->setTextureAttributeAndModes(0, mPhaseTex); + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor + stateset->setTextureAttributeAndModes(0, texEnv); + + stateset->setTextureAttributeAndModes(1, mCircleTex); + osg::ref_ptr texEnv2 = new osg::TexEnvCombine; + texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); + texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); + texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency + stateset->setTextureAttributeAndModes(1, texEnv2); + stateset->setAttributeAndModes( + createUnlitMaterial(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + { + if (mForceShaders) + { + stateset->setTextureAttribute(0, mPhaseTex, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->setTextureAttribute(1, mCircleTex, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + if (auto* uMoonBlend = stateset->getUniform("moonBlend")) + uMoonBlend->set(mMoonColor * mShadowBlend); + if (auto* uAtmosphereFade = stateset->getUniform("atmosphereFade")) + uAtmosphereFade->set( + osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + } + else + { + osg::TexEnvCombine* texEnv + = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(mMoonColor * mShadowBlend); + + osg::TexEnvCombine* texEnv2 + = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv2->setConstantColor( + osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + } + } + + void setTextures(const std::string& phaseTex, const std::string& circleTex) + { + mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); + mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); + mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + reset(); + } + }; + + class CameraRelativeTransformCullCallback + : public SceneUtil::NodeCallback + { + public: + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + // XXX have to remove unwanted culling plane of the water reflection camera + + // Remove all planes that aren't from the standard frustum + unsigned int numPlanes = 4; + if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) + ++numPlanes; + if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) + ++numPlanes; + + unsigned int mask = 0x1; + unsigned int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); + for (unsigned int i = 0; i < cv->getProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) + { + if (i >= numPlanes) + { + // turn off this culling plane + resultMask &= (~mask); + } + + mask <<= 1; + } + + cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); + cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); + + cv->getProjectionCullingStack().back().pushCurrentMask(); + cv->getCurrentCullingSet().pushCurrentMask(); + + traverse(node, cv); + + cv->getProjectionCullingStack().back().popCurrentMask(); + cv->getCurrentCullingSet().popCurrentMask(); + } + }; + + void AtmosphereUpdater::setEmissionColor(const osg::Vec4f& emissionColor) + { + mEmissionColor = emissionColor; + } + + void AtmosphereUpdater::setDefaults(osg::StateSet* stateset) + { + stateset->setAttributeAndModes( + createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere))); + } + + void AtmosphereUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); + } + + AtmosphereNightUpdater::AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders) + : mColor(osg::Vec4f(0, 0, 0, 0)) + , mTexture(new osg::Texture2D(imageManager->getWarningImage())) + , mForceShaders(forceShaders) + { + mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + } + + void AtmosphereNightUpdater::setFade(float fade) + { + mColor.a() = fade; + } + + void AtmosphereNightUpdater::setDefaults(osg::StateSet* stateset) + { + if (mForceShaders) + { + stateset->addUniform(new osg::Uniform("opacity", 0.f)); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night))); + } + else + { + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + + stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + } + + void AtmosphereNightUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + if (mForceShaders) + { + stateset->getUniform("opacity")->set(mColor.a()); + } + else + { + osg::TexEnvCombine* texEnv + = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(mColor); + } + } + + CloudUpdater::CloudUpdater(bool forceShaders) + : mOpacity(0.f) + , mForceShaders(forceShaders) + { + } + + void CloudUpdater::setTexture(osg::ref_ptr texture) + { + mTexture = texture; + } + + void CloudUpdater::setEmissionColor(const osg::Vec4f& emissionColor) + { + mEmissionColor = emissionColor; + } + + void CloudUpdater::setOpacity(float opacity) + { + mOpacity = opacity; + } + + void CloudUpdater::setTextureCoord(float timer) + { + mTexMat = osg::Matrixf::translate(osg::Vec3f(0.f, -timer, 0.f)); + } + + void CloudUpdater::setDefaults(osg::StateSet* stateset) + { + stateset->setAttribute( + createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + osg::ref_ptr texmat = new osg::TexMat; + stateset->setTextureAttributeAndModes(0, texmat); + + if (mForceShaders) + { + stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + stateset->addUniform(new osg::Uniform("opacity", 1.f)); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds))); + } + else + { + stateset->setTextureAttributeAndModes(1, texmat); + // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already + osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; + texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnvCombine->setConstantColor(osg::Vec4f(1, 1, 1, 1)); + texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); + + stateset->setTextureAttributeAndModes(1, texEnvCombine); + + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + } + + void CloudUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) + { + stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); + + osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); + texMat->setMatrix(mTexMat); + + if (mForceShaders) + { + stateset->getUniform("opacity")->set(mOpacity); + } + else + { + stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv + = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(osg::Vec4f(1, 1, 1, mOpacity)); + } + } + + class SkyStereoStatesetUpdater : public SceneUtil::StateSetUpdater + { + public: + SkyStereoStatesetUpdater() {} + + protected: + void setDefaults(osg::StateSet* stateset) override + { + if (!Stereo::getMultiview()) + stateset->addUniform( + new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrix"), osg::StateAttribute::OVERRIDE); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override + { + if (Stereo::getMultiview()) + { + std::array projectionMatrices; + auto& sm = Stereo::Manager::instance(); + + for (int view : { 0, 1 }) + { + auto projectionMatrix = sm.computeEyeProjection(view, SceneUtil::AutoDepth::isReversed()); + auto viewOffsetMatrix = sm.computeEyeViewOffset(view); + for (int col : { 0, 1, 2 }) + viewOffsetMatrix(3, col) = 0; + + projectionMatrices[view] = viewOffsetMatrix * projectionMatrix; + } + + Stereo::setMultiviewMatrices(stateset, projectionMatrices); + } + } + void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* /*cv*/) override + { + auto& sm = Stereo::Manager::instance(); + auto* projectionMatrixUniform = stateset->getUniform("projectionMatrix"); + auto projectionMatrix = sm.computeEyeProjection(0, SceneUtil::AutoDepth::isReversed()); + projectionMatrixUniform->set(projectionMatrix); + } + void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* /*cv*/) override + { + auto& sm = Stereo::Manager::instance(); + auto* projectionMatrixUniform = stateset->getUniform("projectionMatrix"); + auto projectionMatrix = sm.computeEyeProjection(1, SceneUtil::AutoDepth::isReversed()); + projectionMatrixUniform->set(projectionMatrix); + } + + private: + }; + + CameraRelativeTransform::CameraRelativeTransform() + { + // Culling works in node-local space, not in camera space, so we can't cull this node correctly + // That's not a problem though, children of this node can be culled just fine + // Just make sure you do not place a CameraRelativeTransform deep in the scene graph + setCullingActive(false); + + addCullCallback(new CameraRelativeTransformCullCallback); + if (Stereo::getStereo()) + addCullCallback(new SkyStereoStatesetUpdater); + } + + CameraRelativeTransform::CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) + : osg::Transform(copy, copyop) + { + } + + const osg::Vec3f& CameraRelativeTransform::getLastViewPoint() const + { + return mViewPoint; + } + + bool CameraRelativeTransform::computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const + { + if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + { + mViewPoint = static_cast(nv)->getViewPoint(); + } + + if (_referenceFrame == RELATIVE_RF) + { + matrix.setTrans(osg::Vec3f(0.f, 0.f, 0.f)); + return false; + } + else // absolute + { + matrix.makeIdentity(); + return true; + } + } + + osg::BoundingSphere CameraRelativeTransform::computeBound() const + { + return osg::BoundingSphere(); + } + + UnderwaterSwitchCallback::UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) + : mCameraRelativeTransform(cameraRelativeTransform) + , mEnabled(true) + , mWaterLevel(0.f) + { + } + + bool UnderwaterSwitchCallback::isUnderwater() + { + osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); + return mEnabled && viewPoint.z() < mWaterLevel; + } + + void UnderwaterSwitchCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (isUnderwater()) + return; + + traverse(node, nv); + } + + void UnderwaterSwitchCallback::setEnabled(bool enabled) + { + mEnabled = enabled; + } + void UnderwaterSwitchCallback::setWaterLevel(float waterLevel) + { + mWaterLevel = waterLevel; + } + + const float CelestialBody::mDistance = 1000.0f; + + CelestialBody::CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask) + : mVisibleMask(visibleMask) + { + mGeom = createTexturedQuad(numUvSets); + mGeom->getOrCreateStateSet(); + mTransform = new osg::PositionAttitudeTransform; + mTransform->setNodeMask(mVisibleMask); + mTransform->setScale(osg::Vec3f(450, 450, 450) * scaleFactor); + mTransform->addChild(mGeom); + + parentNode->addChild(mTransform); + } + + void CelestialBody::setVisible(bool visible) + { + mTransform->setNodeMask(visible ? mVisibleMask : 0); + } + + Sun::Sun(osg::Group* parentNode, Resource::SceneManager& sceneManager) + : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) + , mUpdater(new SunUpdater) + { + mTransform->addUpdateCallback(mUpdater); + + Resource::ImageManager& imageManager = *sceneManager.getImageManager(); + + osg::ref_ptr sunTex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds")); + sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + sunTex->setName("diffuseMap"); + + mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex); + mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); + + osg::ref_ptr queryNode = new osg::Group; + // Need to render after the world geometry so we can correctly test for occlusions + osg::StateSet* stateset = queryNode->getOrCreateStateSet(); + stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); + stateset->setNestRenderBins(false); + // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to + // match the circular shape of the sun + if (!sceneManager.getForceShaders()) + { + osg::ref_ptr alphaFunc = new osg::AlphaFunc; + alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); + stateset->setAttributeAndModes(alphaFunc); + } + stateset->setTextureAttributeAndModes(0, sunTex); + stateset->setAttributeAndModes(createUnlitMaterial()); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query))); + + // Disable writing to the color buffer. We are using this geometry for visibility tests only. + osg::ref_ptr colormask = new osg::ColorMask(0, 0, 0, 0); + stateset->setAttributeAndModes(colormask); + if (sceneManager.getSupportsNormalsRT()) + stateset->setAttributeAndModes(new osg::ColorMaski(1, false, false, false, false)); + mTransform->addChild(queryNode); + + mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); + mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); + + createSunFlash(imageManager); + createSunGlare(); + } + + Sun::~Sun() + { + mTransform->removeUpdateCallback(mUpdater); + destroySunFlash(); + destroySunGlare(); + } + + void Sun::setColor(const osg::Vec4f& color) + { + mUpdater->mColor.r() = color.r(); + mUpdater->mColor.g() = color.g(); + mUpdater->mColor.b() = color.b(); + } + + void Sun::adjustTransparency(const float ratio) + { + mUpdater->mColor.a() = ratio; + if (mSunGlareCallback) + mSunGlareCallback->setGlareView(ratio); + if (mSunFlashCallback) + mSunFlashCallback->setGlareView(ratio); + } + + void Sun::setDirection(const osg::Vec3f& direction) + { + osg::Vec3f normalizedDirection = direction / direction.length(); + mTransform->setPosition(normalizedDirection * mDistance); + + osg::Quat quat; + quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); + mTransform->setAttitude(quat); + } + + void Sun::setGlareTimeOfDayFade(float val) + { + if (mSunGlareCallback) + mSunGlareCallback->setTimeOfDayFade(val); + } + + void Sun::setSunglare(bool enabled) + { + mSunGlareNode->setNodeMask(enabled ? ~0u : 0); + mSunFlashNode->setNodeMask(enabled ? ~0u : 0); + } + + osg::ref_ptr Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible) + { + osg::ref_ptr oqn = new osg::OcclusionQueryNode; + oqn->setQueriesEnabled(true); + + // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced + osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); + + // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is + // rendered after all the other geometry, so that would be pretty bad). STATIC should be safe, since our node's + // local bounds are static, thus computeBounds() which modifies the queryGeometry is only called once. Note the + // debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. + queryGeom->setDataVariance(osg::Object::STATIC); + + // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't + // originally intended to allow this, normally it would automatically adjust the query geometry to match the sub + // graph's bounding box. The below hack is needed to circumvent this. + queryGeom->setVertexArray(mGeom->getVertexArray()); + queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); + queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); + queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); + + // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. + oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); + // Still need a proper bounding sphere. + oqn->setInitialBound(queryGeom->getBound()); + + oqn->setQueryGeometry(queryGeom.release()); + + osg::StateSet* queryStateSet = new osg::StateSet; + if (queryVisible) + { + osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LEQUAL); + // This is a trick to make fragments written by the query always use the maximum depth value, + // without having to retrieve the current far clipping distance. + // We want the sun glare to be "infinitely" far away. + double far = SceneUtil::AutoDepth::isReversed() ? 0.0 : 1.0; + depth->setFunction(osg::Depth::LEQUAL); + depth->setZNear(far); + depth->setZFar(far); + depth->setWriteMask(false); + queryStateSet->setAttributeAndModes(depth); + } + else + { + queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } + oqn->setQueryStateSet(queryStateSet); + + parent->addChild(oqn); + + return oqn; + } + + void Sun::createSunFlash(Resource::ImageManager& imageManager) + { + osg::ref_ptr tex + = new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds")); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + tex->setName("diffuseMap"); + + osg::ref_ptr group(new osg::Group); + + mTransform->addChild(group); + + const float scale = 2.6f; + osg::ref_ptr geom = createTexturedQuad(1, scale); + group->addChild(geom); + + osg::StateSet* stateset = geom->getOrCreateStateSet(); + + stateset->setTextureAttributeAndModes(0, tex); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); + + mSunFlashNode = group; + + mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); + mSunFlashNode->addCullCallback(mSunFlashCallback); + } + + void Sun::destroySunFlash() + { + if (mSunFlashNode) + { + mSunFlashNode->removeCullCallback(mSunFlashCallback); + mSunFlashCallback = nullptr; + } + } + + void Sun::createSunGlare() + { + osg::ref_ptr camera = new osg::Camera; + camera->setProjectionMatrix(osg::Matrix::identity()); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? + camera->setViewMatrix(osg::Matrix::identity()); + camera->setClearMask(0); + camera->setRenderOrder(osg::Camera::NESTED_RENDER); + camera->setAllowEventFocus(false); + camera->getOrCreateStateSet()->addUniform( + new osg::Uniform("projectionMatrix", static_cast(camera->getProjectionMatrix()))); + SceneUtil::setCameraClearDepth(camera); + + osg::ref_ptr geom + = osg::createTexturedQuadGeometry(osg::Vec3f(-1, -1, 0), osg::Vec3f(2, 0, 0), osg::Vec3f(0, 2, 0)); + camera->addChild(geom); + + osg::StateSet* stateset = geom->getOrCreateStateSet(); + + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare))); + + // set up additive blending + osg::ref_ptr blendFunc = new osg::BlendFunc; + blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); + blendFunc->setDestination(osg::BlendFunc::ONE); + stateset->setAttributeAndModes(blendFunc); + + mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); + mSunGlareNode = camera; + + mSunGlareNode->addCullCallback(mSunGlareCallback); + + mTransform->addChild(camera); + } + + void Sun::destroySunGlare() + { + if (mSunGlareNode) + { + mSunGlareNode->removeCullCallback(mSunGlareCallback); + mSunGlareCallback = nullptr; + } + } + + Moon::Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type) + : CelestialBody(parentNode, scaleFactor, 2) + , mType(type) + , mPhase(MoonState::Phase::Unspecified) + , mUpdater(new MoonUpdater(*sceneManager.getImageManager(), sceneManager.getForceShaders())) + { + setPhase(MoonState::Phase::Full); + setVisible(true); + + mGeom->addUpdateCallback(mUpdater); + } + + Moon::~Moon() + { + mGeom->removeUpdateCallback(mUpdater); + } + + void Moon::adjustTransparency(const float ratio) + { + mUpdater->mTransparency *= ratio; + } + + void Moon::setState(const MoonState state) + { + float radsX = ((state.mRotationFromHorizon) * static_cast(osg::PI)) / 180.0f; + float radsZ = ((state.mRotationFromNorth) * static_cast(osg::PI)) / 180.0f; + + osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); + + osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); + mTransform->setPosition(direction * mDistance); + + // The moon quad is initially oriented facing down, so we need to offset its X-axis + // rotation to rotate it to face the camera when sitting at the horizon. + osg::Quat attX((-static_cast(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + mTransform->setAttitude(attX * rotZ); + + setPhase(state.mPhase); + mUpdater->mTransparency = state.mMoonAlpha; + mUpdater->mShadowBlend = state.mShadowBlend; + } + + void Moon::setAtmosphereColor(const osg::Vec4f& color) + { + mUpdater->mAtmosphereColor = color; + } + + void Moon::setColor(const osg::Vec4f& color) + { + mUpdater->mMoonColor = color; + } + + unsigned int Moon::getPhaseInt() const + { + switch (mPhase) + { + case MoonState::Phase::New: + return 0; + case MoonState::Phase::WaxingCrescent: + return 1; + case MoonState::Phase::WaningCrescent: + return 1; + case MoonState::Phase::FirstQuarter: + return 2; + case MoonState::Phase::ThirdQuarter: + return 2; + case MoonState::Phase::WaxingGibbous: + return 3; + case MoonState::Phase::WaningGibbous: + return 3; + case MoonState::Phase::Full: + return 4; + default: + return 0; + } + } + + void Moon::setPhase(const MoonState::Phase& phase) + { + if (mPhase == phase) + return; + + mPhase = phase; + + std::string textureName = "textures/tx_"; + + if (mType == Moon::Type_Secunda) + textureName += "secunda_"; + else + textureName += "masser_"; + + switch (mPhase) + { + case MoonState::Phase::New: + textureName += "new"; + break; + case MoonState::Phase::WaxingCrescent: + textureName += "one_wax"; + break; + case MoonState::Phase::FirstQuarter: + textureName += "half_wax"; + break; + case MoonState::Phase::WaxingGibbous: + textureName += "three_wax"; + break; + case MoonState::Phase::WaningCrescent: + textureName += "one_wan"; + break; + case MoonState::Phase::ThirdQuarter: + textureName += "half_wan"; + break; + case MoonState::Phase::WaningGibbous: + textureName += "three_wan"; + break; + case MoonState::Phase::Full: + textureName += "full"; + break; + default: + break; + } + + textureName += ".dds"; + + if (mType == Moon::Type_Secunda) + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); + else + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); + } + + int RainCounter::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); + } + + RainShooter::RainShooter() + : mAngle(0.f) + { + } + + void RainShooter::shoot(osgParticle::Particle* particle) const + { + particle->setVelocity(mVelocity); + particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); + } + + void RainShooter::setVelocity(const osg::Vec3f& velocity) + { + mVelocity = velocity; + } + + void RainShooter::setAngle(float angle) + { + mAngle = angle; + } + + osg::Object* RainShooter::cloneType() const + { + return new RainShooter; + } + + osg::Object* RainShooter::clone(const osg::CopyOp&) const + { + return new RainShooter(*this); + } + + ModVertexAlphaVisitor::ModVertexAlphaVisitor(ModVertexAlphaVisitor::MeshType type) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mType(type) + { + } + + void ModVertexAlphaVisitor::apply(osg::Geometry& geometry) + { + osg::ref_ptr colors = new osg::Vec4Array(geometry.getVertexArray()->getNumElements()); + for (unsigned int i = 0; i < colors->size(); ++i) + { + float alpha = 1.f; + + switch (mType) + { + case ModVertexAlphaVisitor::Atmosphere: + { + // this is a cylinder, so every second vertex belongs to the bottom-most row + alpha = (i % 2) ? 0.f : 1.f; + break; + } + case ModVertexAlphaVisitor::Clouds: + { + if (i >= 49 && i <= 64) + alpha = 0.f; // bottom-most row + else if (i >= 33 && i <= 48) + alpha = 0.25098; // second row + else + alpha = 1.f; + break; + } + case ModVertexAlphaVisitor::Stars: + { + if (geometry.getColorArray()) + { + osg::Vec4Array* origColors = static_cast(geometry.getColorArray()); + alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; + } + else + alpha = 1.f; + break; + } + } + + (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); + } + + geometry.setColorArray(colors, osg::Array::BIND_PER_VERTEX); + } +} diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp new file mode 100644 index 000000000..101872459 --- /dev/null +++ b/apps/openmw/mwrender/skyutil.hpp @@ -0,0 +1,348 @@ +#ifndef OPENMW_MWRENDER_SKYUTIL_H +#define OPENMW_MWRENDER_SKYUTIL_H + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Resource +{ + class ImageManager; + class SceneManager; +} + +namespace MWRender +{ + struct MoonUpdater; + class SunUpdater; + class SunFlashCallback; + class SunGlareCallback; + + struct WeatherResult + { + std::string mCloudTexture; + std::string mNextCloudTexture; + float mCloudBlendFactor; + + osg::Vec4f mFogColor; + + osg::Vec4f mAmbientColor; + + osg::Vec4f mSkyColor; + + // sun light color + osg::Vec4f mSunColor; + + // alpha is the sun transparency + osg::Vec4f mSunDiscColor; + + float mFogDepth; + + float mDLFogFactor; + float mDLFogOffset; + + float mWindSpeed; + float mBaseWindSpeed; + float mCurrentWindSpeed; + float mNextWindSpeed; + + float mCloudSpeed; + + float mGlareView; + + bool mNight; // use night skybox + float mNightFade; // fading factor for night skybox + + bool mIsStorm; + + ESM::RefId mAmbientLoopSoundID; + float mAmbientSoundVolume; + + std::string mParticleEffect; + std::string mRainEffect; + float mPrecipitationAlpha; + + float mRainDiameter; + float mRainMinHeight; + float mRainMaxHeight; + float mRainSpeed; + float mRainEntranceSpeed; + int mRainMaxRaindrops; + + osg::Vec3f mStormDirection; + osg::Vec3f mNextStormDirection; + }; + + struct MoonState + { + enum class Phase + { + Full, + WaningGibbous, + ThirdQuarter, + WaningCrescent, + New, + WaxingCrescent, + FirstQuarter, + WaxingGibbous, + Unspecified + }; + + float mRotationFromHorizon; + float mRotationFromNorth; + Phase mPhase; + float mShadowBlend; + float mMoonAlpha; + }; + + osg::ref_ptr createAlphaTrackingUnlitMaterial(); + osg::ref_ptr createUnlitMaterial(osg::Material::ColorMode colorMode = osg::Material::OFF); + + class OcclusionCallback + { + public: + OcclusionCallback( + osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal); + + protected: + float getVisibleRatio(osg::Camera* camera); + + private: + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; + + std::map, float> mLastRatio; + }; + + class AtmosphereUpdater : public SceneUtil::StateSetUpdater + { + public: + void setEmissionColor(const osg::Vec4f& emissionColor); + + protected: + void setDefaults(osg::StateSet* stateset) override; + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override; + + private: + osg::Vec4f mEmissionColor; + }; + + class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater + { + public: + AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders); + + void setFade(float fade); + + protected: + void setDefaults(osg::StateSet* stateset) override; + + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override; + + private: + osg::Vec4f mColor; + osg::ref_ptr mTexture; + bool mForceShaders; + }; + + class CloudUpdater : public SceneUtil::StateSetUpdater + { + public: + CloudUpdater(bool forceShaders); + + void setTexture(osg::ref_ptr texture); + + void setEmissionColor(const osg::Vec4f& emissionColor); + void setOpacity(float opacity); + void setTextureCoord(float timer); + + protected: + void setDefaults(osg::StateSet* stateset) override; + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; + + private: + osg::ref_ptr mTexture; + osg::Vec4f mEmissionColor; + float mOpacity; + bool mForceShaders; + osg::Matrixf mTexMat; + }; + + /// Transform that removes the eyepoint of the modelview matrix, + /// i.e. its children are positioned relative to the camera. + class CameraRelativeTransform : public osg::Transform + { + public: + CameraRelativeTransform(); + + CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop); + + META_Node(MWRender, CameraRelativeTransform) + + const osg::Vec3f& getLastViewPoint() const; + + bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const override; + + osg::BoundingSphere computeBound() const override; + + private: + // viewPoint for the current frame + mutable osg::Vec3f mViewPoint; + }; + + /// @brief Hides the node subgraph if the eye point is below water. + /// @note Must be added as cull callback. + /// @note Meant to be used on a node that is child of a CameraRelativeTransform. + /// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we + /// are in camera-relative space. + class UnderwaterSwitchCallback : public SceneUtil::NodeCallback + { + public: + UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform); + bool isUnderwater(); + + void operator()(osg::Node* node, osg::NodeVisitor* nv); + void setEnabled(bool enabled); + void setWaterLevel(float waterLevel); + + private: + osg::ref_ptr mCameraRelativeTransform; + bool mEnabled; + float mWaterLevel; + }; + + /// A base class for the sun and moons. + class CelestialBody + { + public: + CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask = ~0u); + + virtual ~CelestialBody() = default; + + virtual void adjustTransparency(const float ratio) = 0; + + void setVisible(bool visible); + + protected: + unsigned int mVisibleMask; + static const float mDistance; + osg::ref_ptr mTransform; + osg::ref_ptr mGeom; + }; + + class Sun : public CelestialBody + { + public: + Sun(osg::Group* parentNode, Resource::SceneManager& sceneManager); + + ~Sun(); + + void setColor(const osg::Vec4f& color); + void adjustTransparency(const float ratio) override; + + void setDirection(const osg::Vec3f& direction); + void setGlareTimeOfDayFade(float val); + void setSunglare(bool enabled); + + private: + /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of + /// pixels. + osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible); + + void createSunFlash(Resource::ImageManager& imageManager); + void destroySunFlash(); + + void createSunGlare(); + void destroySunGlare(); + + osg::ref_ptr mUpdater; + osg::ref_ptr mSunFlashNode; + osg::ref_ptr mSunGlareNode; + osg::ref_ptr mSunFlashCallback; + osg::ref_ptr mSunGlareCallback; + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; + }; + + class Moon : public CelestialBody + { + public: + enum Type + { + Type_Masser = 0, + Type_Secunda + }; + + Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type); + + ~Moon(); + + void adjustTransparency(const float ratio) override; + void setState(const MoonState state); + void setAtmosphereColor(const osg::Vec4f& color); + void setColor(const osg::Vec4f& color); + + unsigned int getPhaseInt() const; + + private: + Type mType; + MoonState::Phase mPhase; + osg::ref_ptr mUpdater; + + void setPhase(const MoonState::Phase& phase); + }; + + class RainCounter : public osgParticle::ConstantRateCounter + { + public: + int numParticlesToCreate(double dt) const override; + }; + + class RainShooter : public osgParticle::Shooter + { + public: + RainShooter(); + + osg::Object* cloneType() const override; + + osg::Object* clone(const osg::CopyOp&) const override; + + void shoot(osgParticle::Particle* particle) const override; + + void setVelocity(const osg::Vec3f& velocity); + void setAngle(float angle); + + private: + osg::Vec3f mVelocity; + float mAngle; + }; + + class ModVertexAlphaVisitor : public osg::NodeVisitor + { + public: + enum MeshType + { + Atmosphere, + Stars, + Clouds + }; + + ModVertexAlphaVisitor(MeshType type); + + void apply(osg::Geometry& geometry) override; + + private: + MeshType mType; + }; +} + +#endif diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 528ce70ea..97ae99cd4 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,7 +1,7 @@ #include "terrainstorage.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "landmanager.hpp" @@ -9,9 +9,13 @@ namespace MWRender { - TerrainStorage::TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) - : ESMTerrain::Storage(resourceSystem->getVFS(), normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps) - , mLandManager(new LandManager(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX)) + TerrainStorage::TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern, + const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, + bool autoUseSpecularMaps) + : ESMTerrain::Storage(resourceSystem->getVFS(), normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, + specularMapPattern, autoUseSpecularMaps) + , mLandManager(new LandManager( + ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VTEX)) , mResourceSystem(resourceSystem) { mResourceSystem->addResourceManager(mLandManager.get()); @@ -22,56 +26,79 @@ namespace MWRender mResourceSystem->removeResourceManager(mLandManager.get()); } - bool TerrainStorage::hasData(int cellX, int cellY) + bool TerrainStorage::hasData(ESM::ExteriorCellLocation cellLocation) { - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); - const ESM::Land* land = esmStore.get().search(cellX, cellY); - return land != nullptr; + if (ESM::isEsm4Ext(cellLocation.mWorldspace)) + { + return esmStore.get().search(cellLocation) != nullptr; + } + else + { + return esmStore.get().search(cellLocation.mX, cellLocation.mY) != nullptr; + } } - void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) + static void BoundUnion(float& minX, float& maxX, float& minY, float& maxY, float x, float y) { - minX = 0, minY = 0, maxX = 0, maxY = 0; + if (x < minX) + minX = x; + if (x > maxX) + maxX = x; + if (y < minY) + minY = y; + if (y > maxY) + maxY = y; + } - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) + { + minX = 0; + minY = 0; + maxX = 0; + maxY = 0; - MWWorld::Store::iterator it = esmStore.get().begin(); - for (; it != esmStore.get().end(); ++it) + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + + if (ESM::isEsm4Ext(worldspace)) { - if (it->mX < minX) - minX = static_cast(it->mX); - if (it->mX > maxX) - maxX = static_cast(it->mX); - if (it->mY < minY) - minY = static_cast(it->mY); - if (it->mY > maxY) - maxY = static_cast(it->mY); + const auto& lands = esmStore.get().getLands(); + for (const auto& [landPos, _] : lands) + { + if (landPos.mWorldspace == worldspace) + { + BoundUnion(minX, maxX, minY, maxY, static_cast(landPos.mX), static_cast(landPos.mY)); + } + } + } + else + { + MWWorld::Store::iterator it = esmStore.get().begin(); + for (; it != esmStore.get().end(); ++it) + { + BoundUnion(minX, maxX, minY, maxY, static_cast(it->mX), static_cast(it->mY)); + } } - // since grid coords are at cell origin, we need to add 1 cell maxX += 1; maxY += 1; } - LandManager *TerrainStorage::getLandManager() const + LandManager* TerrainStorage::getLandManager() const { return mLandManager.get(); } - osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) + osg::ref_ptr TerrainStorage::getLand(ESM::ExteriorCellLocation cellLocation) { - return mLandManager->getLand(cellX, cellY); + return mLandManager->getLand(cellLocation); } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); return esmStore.get().search(index, plugin); } - } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 90bf42b84..2526c779c 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include @@ -16,27 +16,27 @@ namespace MWRender class TerrainStorage : public ESMTerrain::Storage { public: - - TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); + TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", + const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, + const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); ~TerrainStorage(); - osg::ref_ptr getLand (int cellX, int cellY) override; + osg::ref_ptr getLand(ESM::ExteriorCellLocation cellLocation) override; const ESM::LandTexture* getLandTexture(int index, short plugin) override; - bool hasData(int cellX, int cellY) override; + bool hasData(ESM::ExteriorCellLocation cellLocation) override; /// Get bounds of the whole terrain in cell units - void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; + void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) override; LandManager* getLandManager() const; private: - std::unique_ptr mLandManager; + std::unique_ptr mLandManager; - Resource::ResourceSystem* mResourceSystem; + Resource::ResourceSystem* mResourceSystem; }; } - #endif diff --git a/apps/openmw/mwrender/transparentpass.cpp b/apps/openmw/mwrender/transparentpass.cpp new file mode 100644 index 000000000..ce53b1c21 --- /dev/null +++ b/apps/openmw/mwrender/transparentpass.cpp @@ -0,0 +1,143 @@ +#include "transparentpass.hpp" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "vismask.hpp" + +namespace MWRender +{ + TransparentDepthBinCallback::TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass) + : mStateSet(new osg::StateSet) + , mPostPass(postPass) + { + osg::ref_ptr image = new osg::Image; + image->allocateImage(1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE); + image->setColor(osg::Vec4(1, 1, 1, 1), 0, 0); + + osg::ref_ptr dummyTexture = new osg::Texture2D(image); + dummyTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + dummyTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + constexpr osg::StateAttribute::OverrideValue modeOff = osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE; + constexpr osg::StateAttribute::OverrideValue modeOn = osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE; + + mStateSet->setTextureAttributeAndModes(0, dummyTexture); + + Shader::ShaderManager::DefineMap defines; + Stereo::shaderStereoDefines(defines); + + mStateSet->setAttributeAndModes(new osg::BlendFunc, modeOff); + mStateSet->setAttributeAndModes(shaderManager.getProgram("depthclipped", defines), modeOn); + mStateSet->setAttributeAndModes(new SceneUtil::AutoDepth, modeOn); + + for (unsigned int unit = 1; unit < 8; ++unit) + mStateSet->setTextureMode(unit, GL_TEXTURE_2D, modeOff); + } + + void TransparentDepthBinCallback::drawImplementation( + osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions* ext = state.get(); + + bool validFbo = false; + unsigned int frameId = state.getFrameStamp()->getFrameNumber() % 2; + + const auto& fbo = mFbo[frameId]; + const auto& msaaFbo = mMsaaFbo[frameId]; + const auto& opaqueFbo = mOpaqueFbo[frameId]; + + if (bin->getStage()->getMultisampleResolveFramebufferObject() + && bin->getStage()->getMultisampleResolveFramebufferObject() == fbo) + validFbo = true; + else if (bin->getStage()->getFrameBufferObject() + && (bin->getStage()->getFrameBufferObject() == fbo || bin->getStage()->getFrameBufferObject() == msaaFbo)) + validFbo = true; + + if (!validFbo) + { + bin->drawImplementation(renderInfo, previous); + return; + } + + const osg::Texture* tex + = opaqueFbo->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER) + .getTexture(); + + if (Stereo::getMultiview()) + { + if (!mMultiviewResolve[frameId]) + { + mMultiviewResolve[frameId] = std::make_unique( + msaaFbo ? msaaFbo : fbo, opaqueFbo, GL_DEPTH_BUFFER_BIT); + } + else + { + mMultiviewResolve[frameId]->setResolveFbo(opaqueFbo); + mMultiviewResolve[frameId]->setMsaaFbo(msaaFbo ? msaaFbo : fbo); + } + mMultiviewResolve[frameId]->resolveImplementation(state); + } + else + { + opaqueFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + ext->glBlitFramebuffer(0, 0, tex->getTextureWidth(), tex->getTextureHeight(), 0, 0, tex->getTextureWidth(), + tex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); + } + + msaaFbo ? msaaFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER) + : fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + // draws scene into primary attachments + bin->drawImplementation(renderInfo, previous); + + if (!mPostPass) + return; + + opaqueFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + // draw transparent post-pass to populate a postprocess friendly depth texture with alpha-clipped geometry + + unsigned int numToPop = previous ? osgUtil::StateGraph::numToPop(previous->_parent) : 0; + if (numToPop > 1) + numToPop--; + unsigned int insertStateSetPosition = state.getStateSetStackSize() - numToPop; + + state.insertStateSet(insertStateSetPosition, mStateSet); + for (auto rit = bin->getRenderLeafList().begin(); rit != bin->getRenderLeafList().end(); rit++) + { + osgUtil::RenderLeaf* rl = *rit; + const osg::StateSet* ss = rl->_parent->getStateSet(); + + if (rl->_drawable->getNodeMask() == Mask_ParticleSystem || rl->_drawable->getNodeMask() == Mask_Effect) + continue; + + if (ss->getAttribute(osg::StateAttribute::MATERIAL)) + { + const osg::Material* mat + = static_cast(ss->getAttribute(osg::StateAttribute::MATERIAL)); + if (mat->getDiffuse(osg::Material::FRONT).a() < 0.5) + continue; + } + + rl->render(renderInfo, previous); + previous = rl; + } + state.removeStateSet(insertStateSetPosition); + + msaaFbo ? msaaFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER) + : fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.checkGLErrors("after TransparentDepthBinCallback::drawImplementation"); + } +} diff --git a/apps/openmw/mwrender/transparentpass.hpp b/apps/openmw/mwrender/transparentpass.hpp new file mode 100644 index 000000000..9d727cf08 --- /dev/null +++ b/apps/openmw/mwrender/transparentpass.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_MWRENDER_TRANSPARENTPASS_H +#define OPENMW_MWRENDER_TRANSPARENTPASS_H + +#include +#include + +#include +#include + +#include + +namespace Shader +{ + class ShaderManager; +} + +namespace Stereo +{ + class MultiviewFramebufferResolve; +} + +namespace MWRender +{ + class TransparentDepthBinCallback : public osgUtil::RenderBin::DrawCallback + { + public: + TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass); + + void drawImplementation( + osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override; + + std::array, 2> mFbo; + std::array, 2> mMsaaFbo; + std::array, 2> mOpaqueFbo; + + std::array, 2> mMultiviewResolve; + + private: + osg::ref_ptr mStateSet; + bool mPostPass; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index e3fc48040..647c1db33 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -3,18 +3,18 @@ #include #include -#include -#include #include +#include +#include #include namespace MWRender { -class TextureOverrideVisitor : public osg::NodeVisitor + class TextureOverrideVisitor : public osg::NodeVisitor { public: - TextureOverrideVisitor(const std::string& texture, Resource::ResourceSystem* resourcesystem) + TextureOverrideVisitor(std::string_view texture, Resource::ResourceSystem* resourcesystem) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mTexture(texture) , mResourcesystem(resourcesystem) @@ -27,41 +27,44 @@ class TextureOverrideVisitor : public osg::NodeVisitor osg::ref_ptr nodePtr(&node); if (node.getUserValue("overrideFx", index)) { - if (index == 1) + if (index == 1) overrideTexture(mTexture, mResourcesystem, nodePtr); } traverse(node); } - std::string mTexture; + std::string_view mTexture; Resource::ResourceSystem* mResourcesystem; -}; + }; -void overrideFirstRootTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node) -{ - TextureOverrideVisitor overrideVisitor(texture, resourceSystem); - node->accept(overrideVisitor); -} + void overrideFirstRootTexture( + std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node) + { + TextureOverrideVisitor overrideVisitor(texture, resourceSystem); + node->accept(overrideVisitor); + } -void overrideTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node) -{ - if (texture.empty()) - return; - std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture, resourceSystem->getVFS()); - // Not sure if wrap settings should be pulled from the overridden texture? - osg::ref_ptr tex = new osg::Texture2D(resourceSystem->getImageManager()->getImage(correctedTexture)); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - tex->setName("diffuseMap"); + void overrideTexture( + std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node) + { + if (texture.empty()) + return; + std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture, resourceSystem->getVFS()); + // Not sure if wrap settings should be pulled from the overridden texture? + osg::ref_ptr tex + = new osg::Texture2D(resourceSystem->getImageManager()->getImage(correctedTexture)); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + tex->setName("diffuseMap"); - osg::ref_ptr stateset; - if (node->getStateSet()) - stateset = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); - else - stateset = new osg::StateSet; + osg::ref_ptr stateset; + if (node->getStateSet()) + stateset = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + else + stateset = new osg::StateSet; - stateset->setTextureAttribute(0, tex, osg::StateAttribute::OVERRIDE); + stateset->setTextureAttribute(0, tex, osg::StateAttribute::OVERRIDE); - node->setStateSet(stateset); -} + node->setStateSet(stateset); + } } diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index a89baa22b..457b23f94 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -17,11 +17,14 @@ namespace Resource namespace MWRender { - // Overrides the texture of nodes in the mesh that had the same NiTexturingProperty as the first NiTexturingProperty of the .NIF file's root node, - // if it had a NiTexturingProperty. Used for applying "particle textures" to magic effects. - void overrideFirstRootTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node); + // Overrides the texture of nodes in the mesh that had the same NiTexturingProperty as the first NiTexturingProperty + // of the .NIF file's root node, if it had a NiTexturingProperty. Used for applying "particle textures" to magic + // effects. + void overrideFirstRootTexture( + std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); - void overrideTexture(const std::string& texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); + void overrideTexture( + std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); // Node callback to entirely skip the traversal. class NoTraverseCallback : public osg::NodeCallback diff --git a/apps/openmw/mwrender/viewovershoulder.cpp b/apps/openmw/mwrender/viewovershoulder.cpp deleted file mode 100644 index 799e34c99..000000000 --- a/apps/openmw/mwrender/viewovershoulder.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "viewovershoulder.hpp" - -#include - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/refdata.hpp" - -#include "../mwmechanics/drawstate.hpp" - -namespace MWRender -{ - - ViewOverShoulderController::ViewOverShoulderController(Camera* camera) : - mCamera(camera), mMode(Mode::RightShoulder), - mAutoSwitchShoulder(Settings::Manager::getBool("auto switch shoulder", "Camera")), - mOverShoulderHorizontalOffset(30.f), mOverShoulderVerticalOffset(-10.f) - { - osg::Vec2f offset = Settings::Manager::getVector2("view over shoulder offset", "Camera"); - mOverShoulderHorizontalOffset = std::abs(offset.x()); - mOverShoulderVerticalOffset = offset.y(); - mDefaultShoulderIsRight = offset.x() >= 0; - - mCamera->enableDynamicCameraDistance(true); - mCamera->enableCrosshairInThirdPersonMode(true); - mCamera->setFocalPointTargetOffset(offset); - } - - void ViewOverShoulderController::update() - { - if (mCamera->isFirstPerson()) - return; - - Mode oldMode = mMode; - auto ptr = mCamera->getTrackingPtr(); - bool combat = ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing; - if (combat && !mCamera->isVanityOrPreviewModeEnabled()) - mMode = Mode::Combat; - else if (MWBase::Environment::get().getWorld()->isSwimming(ptr)) - mMode = Mode::Swimming; - else if (oldMode == Mode::Combat || oldMode == Mode::Swimming) - mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; - if (mAutoSwitchShoulder && (mMode == Mode::LeftShoulder || mMode == Mode::RightShoulder)) - trySwitchShoulder(); - - if (oldMode == mMode) - return; - - if (mCamera->getMode() == Camera::Mode::Vanity) - // Player doesn't touch controls for a long time. Transition should be very slow. - mCamera->setFocalPointTransitionSpeed(0.2f); - else if ((oldMode == Mode::Combat || mMode == Mode::Combat) && mCamera->getMode() == Camera::Mode::Normal) - // Transition to/from combat mode and we are not it preview mode. Should be fast. - mCamera->setFocalPointTransitionSpeed(5.f); - else - mCamera->setFocalPointTransitionSpeed(1.f); // Default transition speed. - - switch (mMode) - { - case Mode::RightShoulder: - mCamera->setFocalPointTargetOffset({mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); - break; - case Mode::LeftShoulder: - mCamera->setFocalPointTargetOffset({-mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); - break; - case Mode::Combat: - case Mode::Swimming: - default: - mCamera->setFocalPointTargetOffset({0, 15}); - } - } - - void ViewOverShoulderController::trySwitchShoulder() - { - if (mCamera->getMode() != Camera::Mode::Normal) - return; - - const float limitToSwitch = 120; // switch to other shoulder if wall is closer than this limit - const float limitToSwitchBack = 300; // switch back to default shoulder if there is no walls at this distance - - auto orient = osg::Quat(mCamera->getYaw(), osg::Vec3d(0,0,1)); - osg::Vec3d playerPos = mCamera->getFocalPoint() - mCamera->getFocalPointOffset(); - - MWBase::World* world = MWBase::Environment::get().getWorld(); - osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0); - float rayRight = world->getDistToNearestRayHit( - playerPos + sideOffset, orient * osg::Vec3d(1, 0, 0), limitToSwitchBack + 1); - float rayLeft = world->getDistToNearestRayHit( - playerPos - sideOffset, orient * osg::Vec3d(-1, 0, 0), limitToSwitchBack + 1); - float rayRightForward = world->getDistToNearestRayHit( - playerPos + sideOffset, orient * osg::Vec3d(1, 3, 0), limitToSwitchBack + 1); - float rayLeftForward = world->getDistToNearestRayHit( - playerPos - sideOffset, orient * osg::Vec3d(-1, 3, 0), limitToSwitchBack + 1); - float distRight = std::min(rayRight, rayRightForward); - float distLeft = std::min(rayLeft, rayLeftForward); - - if (distLeft < limitToSwitch && distRight > limitToSwitchBack) - mMode = Mode::RightShoulder; - else if (distRight < limitToSwitch && distLeft > limitToSwitchBack) - mMode = Mode::LeftShoulder; - else if (distRight > limitToSwitchBack && distLeft > limitToSwitchBack) - mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; - } - -} diff --git a/apps/openmw/mwrender/viewovershoulder.hpp b/apps/openmw/mwrender/viewovershoulder.hpp deleted file mode 100644 index 80ac30865..000000000 --- a/apps/openmw/mwrender/viewovershoulder.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef VIEWOVERSHOULDER_H -#define VIEWOVERSHOULDER_H - -#include "camera.hpp" - -namespace MWRender -{ - - class ViewOverShoulderController - { - public: - ViewOverShoulderController(Camera* camera); - - void update(); - - private: - void trySwitchShoulder(); - enum class Mode { RightShoulder, LeftShoulder, Combat, Swimming }; - - Camera* mCamera; - Mode mMode; - bool mAutoSwitchShoulder; - float mOverShoulderHorizontalOffset; - float mOverShoulderVerticalOffset; - bool mDefaultShoulderIsRight; - }; - -} - -#endif // VIEWOVERSHOULDER_H diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index 87ca9415f..1b25dfc36 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -24,40 +24,44 @@ namespace MWRender Mask_UpdateVisitor = 0x1, // reserved for separating UpdateVisitors from CullVisitors // child of Scene - Mask_Effect = (1<<1), - Mask_Debug = (1<<2), - Mask_Actor = (1<<3), - Mask_Player = (1<<4), - Mask_Sky = (1<<5), - Mask_Water = (1<<6), // choose Water or SimpleWater depending on detail required - Mask_SimpleWater = (1<<7), - Mask_Terrain = (1<<8), - Mask_FirstPerson = (1<<9), - Mask_Object = (1<<10), - Mask_Static = (1<<11), + Mask_Effect = (1 << 1), + Mask_Debug = (1 << 2), + Mask_Actor = (1 << 3), + Mask_Player = (1 << 4), + Mask_Sky = (1 << 5), + Mask_Water = (1 << 6), // choose Water or SimpleWater depending on detail required + Mask_SimpleWater = (1 << 7), + Mask_Terrain = (1 << 8), + Mask_FirstPerson = (1 << 9), + Mask_Object = (1 << 10), + Mask_Static = (1 << 11), // child of Sky - Mask_Sun = (1<<12), - Mask_WeatherParticles = (1<<13), + Mask_Sun = (1 << 12), + Mask_WeatherParticles = (1 << 13), // top level masks - Mask_Scene = (1<<14), - Mask_GUI = (1<<15), + Mask_Scene = (1 << 14), + Mask_GUI = (1 << 15), // Set on a ParticleSystem Drawable - Mask_ParticleSystem = (1<<16), + Mask_ParticleSystem = (1 << 16), // Set on cameras within the main scene graph - Mask_RenderToTexture = (1<<17), + Mask_RenderToTexture = (1 << 17), - Mask_PreCompile = (1<<18), + Mask_PreCompile = (1 << 18), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<19), + Mask_Lighting = (1 << 19), - Mask_Groundcover = (1<<20), + Mask_Groundcover = (1 << 20), }; + // Defines masks to remove when using ToggleWorld command + constexpr static unsigned int sToggleWorldMask + = Mask_Debug | Mask_Actor | Mask_Terrain | Mask_Object | Mask_Static | Mask_Groundcover; + } #endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c5fd1a363..a96ff2966 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -1,834 +1,889 @@ #include "water.hpp" -#include +#include -#include +#include #include -#include +#include +#include #include +#include #include #include -#include -#include +#include -#include - -#include -#include - -#include #include +#include -#include - -#include #include +#include #include +#include +#include #include -#include #include -#include #include +#include #include #include -#include +#include #include #include "../mwworld/cellstore.hpp" -#include "vismask.hpp" -#include "ripplesimulation.hpp" #include "renderbin.hpp" -#include "util.hpp" +#include "ripples.hpp" +#include "ripplesimulation.hpp" +#include "vismask.hpp" namespace MWRender { -// -------------------------------------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------- -/// @brief Allows to cull and clip meshes that are below a plane. Useful for reflection & refraction camera effects. -/// Also handles flipping of the plane when the eye point goes below it. -/// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); -class ClipCullNode : public osg::Group -{ - class PlaneCullCallback : public osg::NodeCallback + /// @brief Allows to cull and clip meshes that are below a plane. Useful for reflection & refraction camera effects. + /// Also handles flipping of the plane when the eye point goes below it. + /// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); + class ClipCullNode : public osg::Group { - public: - /// @param cullPlane The culling plane (in world space). - PlaneCullCallback(const osg::Plane* cullPlane) - : osg::NodeCallback() - , mCullPlane(cullPlane) + class PlaneCullCallback : public SceneUtil::NodeCallback { - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osgUtil::CullVisitor* cv = static_cast(nv); - - osg::Polytope::PlaneList origPlaneList = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); - - osg::Plane plane = *mCullPlane; - plane.transform(*cv->getCurrentRenderStage()->getInitialViewMatrix()); - - osg::Vec3d eyePoint = cv->getEyePoint(); - if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) - plane.flip(); - - cv->getProjectionCullingStack().back().getFrustum().add(plane); - - traverse(node, nv); - - // undo - cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); - } - - private: - const osg::Plane* mCullPlane; - }; - - class FlipCallback : public osg::NodeCallback - { - public: - FlipCallback(const osg::Plane* cullPlane) - : mCullPlane(cullPlane) - { - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osgUtil::CullVisitor* cv = static_cast(nv); - osg::Vec3d eyePoint = cv->getEyePoint(); - - osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); - - // apply the height of the plane - // we can't apply this height in the addClipPlane() since the "flip the below graph" function would otherwise flip the height as well - modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * ((*mCullPlane)[3] * -1)); - - // flip the below graph if the eye point is above the plane - if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) + public: + /// @param cullPlane The culling plane (in world space). + PlaneCullCallback(const osg::Plane* cullPlane) + : mCullPlane(cullPlane) { - modelViewMatrix->preMultScale(osg::Vec3(1,1,-1)); } - // move the plane back along its normal a little bit to prevent bleeding at the water shore - const float clipFudge = -5; - modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + osg::Polytope::PlaneList origPlaneList + = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); - cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); - traverse(node, nv); - cv->popModelViewMatrix(); + osg::Plane plane = *mCullPlane; + plane.transform(*cv->getCurrentRenderStage()->getInitialViewMatrix()); + + osg::Vec3d eyePoint = cv->getEyePoint(); + if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0, 0, eyePoint.z()), 0)) > 0) + plane.flip(); + + cv->getProjectionCullingStack().back().getFrustum().add(plane); + + traverse(node, cv); + + // undo + cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); + } + + private: + const osg::Plane* mCullPlane; + }; + + class FlipCallback : public SceneUtil::NodeCallback + { + public: + FlipCallback(const osg::Plane* cullPlane) + : mCullPlane(cullPlane) + { + } + + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + osg::Vec3d eyePoint = cv->getEyePoint(); + + osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + + // apply the height of the plane + // we can't apply this height in the addClipPlane() since the "flip the below graph" function would + // otherwise flip the height as well + modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * ((*mCullPlane)[3] * -1)); + + // flip the below graph if the eye point is above the plane + if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0, 0, eyePoint.z()), 0)) > 0) + { + modelViewMatrix->preMultScale(osg::Vec3(1, 1, -1)); + } + + // move the plane back along its normal a little bit to prevent bleeding at the water shore + const float clipFudge = -5; + modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); + + cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); + traverse(node, cv); + cv->popModelViewMatrix(); + } + + private: + const osg::Plane* mCullPlane; + }; + + public: + ClipCullNode() + { + addCullCallback(new PlaneCullCallback(&mPlane)); + + mClipNodeTransform = new osg::Group; + mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); + osg::Group::addChild(mClipNodeTransform); + + mClipNode = new osg::ClipNode; + + mClipNodeTransform->addChild(mClipNode); + } + + void setPlane(const osg::Plane& plane) + { + if (plane == mPlane) + return; + mPlane = plane; + + mClipNode->getClipPlaneList().clear(); + mClipNode->addClipPlane( + new osg::ClipPlane(0, osg::Plane(mPlane.getNormal(), 0))); // mPlane.d() applied in FlipCallback + mClipNode->setStateSetModes(*getOrCreateStateSet(), osg::StateAttribute::ON); + mClipNode->setCullingActive(false); } private: - const osg::Plane* mCullPlane; + osg::ref_ptr mClipNodeTransform; + osg::ref_ptr mClipNode; + + osg::Plane mPlane; }; -public: - ClipCullNode() + /// This callback on the Camera has the effect of a RELATIVE_RF_INHERIT_VIEWPOINT transform mode (which does not + /// exist in OSG). We want to keep the View Point of the parent camera so we will not have to recreate LODs. + class InheritViewPointCallback + : public SceneUtil::NodeCallback { - addCullCallback (new PlaneCullCallback(&mPlane)); - - mClipNodeTransform = new osg::Group; - mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); - osg::Group::addChild(mClipNodeTransform); - - mClipNode = new osg::ClipNode; - - mClipNodeTransform->addChild(mClipNode); - } - - void setPlane (const osg::Plane& plane) - { - if (plane == mPlane) - return; - mPlane = plane; - - mClipNode->getClipPlaneList().clear(); - mClipNode->addClipPlane(new osg::ClipPlane(0, osg::Plane(mPlane.getNormal(), 0))); // mPlane.d() applied in FlipCallback - mClipNode->setStateSetModes(*getOrCreateStateSet(), osg::StateAttribute::ON); - mClipNode->setCullingActive(false); - } - -private: - osg::ref_ptr mClipNodeTransform; - osg::ref_ptr mClipNode; - - osg::Plane mPlane; -}; - -/// This callback on the Camera has the effect of a RELATIVE_RF_INHERIT_VIEWPOINT transform mode (which does not exist in OSG). -/// We want to keep the View Point of the parent camera so we will not have to recreate LODs. -class InheritViewPointCallback : public osg::NodeCallback -{ -public: + public: InheritViewPointCallback() {} - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osgUtil::CullVisitor* cv = static_cast(nv); - osg::ref_ptr modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); - cv->popModelViewMatrix(); - cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT); - traverse(node, nv); - } -}; - -/// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. -/// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). -/// Must be added as a Cull callback. -class FudgeCallback : public osg::NodeCallback -{ -public: - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osgUtil::CullVisitor* cv = static_cast(nv); - - const float fudge = 0.2; - if (std::abs(cv->getEyeLocal().z()) < fudge) + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - float diff = fudge - cv->getEyeLocal().z(); - osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); - - if (cv->getEyeLocal().z() > 0) - modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,-diff)); - else - modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,diff)); - - cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); - traverse(node, nv); + osg::ref_ptr modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); cv->popModelViewMatrix(); + cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT); + traverse(node, cv); + } + }; + + /// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. + /// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely + /// close to the mesh (seen on NVIDIA at least). Must be added as a Cull callback. + class FudgeCallback : public SceneUtil::NodeCallback + { + public: + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + const float fudge = 0.2; + if (std::abs(cv->getEyeLocal().z()) < fudge) + { + float diff = fudge - cv->getEyeLocal().z(); + osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + + if (cv->getEyeLocal().z() > 0) + modelViewMatrix->preMultTranslate(osg::Vec3f(0, 0, -diff)); + else + modelViewMatrix->preMultTranslate(osg::Vec3f(0, 0, diff)); + + cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); + traverse(node, cv); + cv->popModelViewMatrix(); + } + else + traverse(node, cv); + } + }; + + class RainIntensityUpdater : public SceneUtil::StateSetUpdater + { + public: + RainIntensityUpdater() + : mRainIntensity(0.f) + { + } + + void setRainIntensity(float rainIntensity) { mRainIntensity = rainIntensity; } + + protected: + void setDefaults(osg::StateSet* stateset) override + { + osg::ref_ptr rainIntensityUniform = new osg::Uniform("rainIntensity", 0.0f); + stateset->addUniform(rainIntensityUniform.get()); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override + { + osg::ref_ptr rainIntensityUniform = stateset->getUniform("rainIntensity"); + if (rainIntensityUniform != nullptr) + rainIntensityUniform->set(mRainIntensity); + } + + private: + float mRainIntensity; + }; + + class Refraction : public SceneUtil::RTTNode + { + public: + Refraction(uint32_t rttSize) + : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) + , mNodeMask(Refraction::sDefaultCullMask) + { + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); + mClipCullNode = new ClipCullNode; + } + + void setDefaults(osg::Camera* camera) override + { + camera->setReferenceFrame(osg::Camera::RELATIVE_RF); + camera->setSmallFeatureCullingPixelSize( + Settings::Manager::getInt("small feature culling pixel size", "Water")); + camera->setName("RefractionCamera"); + camera->addCullCallback(new InheritViewPointCallback); + camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + + // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog(new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + camera->getOrCreateStateSet()->setAttributeAndModes( + fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + + camera->addChild(mClipCullNode); + camera->setNodeMask(Mask_RenderToTexture); + + if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709 + SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + } + + void apply(osg::Camera* camera) override + { + camera->setViewMatrix(mViewMatrix); + camera->setCullMask(mNodeMask); + } + + void setScene(osg::Node* scene) + { + if (mScene) + mClipCullNode->removeChild(mScene); + mScene = scene; + mClipCullNode->addChild(scene); + } + + void setWaterLevel(float waterLevel) + { + const float refractionScale + = std::clamp(Settings::Manager::getFloat("refraction scale", "Water"), 0.f, 1.f); + + mViewMatrix = 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))); + } + + void showWorld(bool show) + { + if (show) + mNodeMask = Refraction::sDefaultCullMask; + else + mNodeMask = Refraction::sDefaultCullMask & ~sToggleWorldMask; + } + + private: + osg::ref_ptr mClipCullNode; + osg::ref_ptr mScene; + osg::Matrix mViewMatrix{ osg::Matrix::identity() }; + + unsigned int mNodeMask; + + static constexpr unsigned int sDefaultCullMask = Mask_Effect | Mask_Scene | Mask_Object | Mask_Static + | Mask_Terrain | Mask_Actor | Mask_ParticleSystem | Mask_Sky | Mask_Sun | Mask_Player | Mask_Lighting + | Mask_Groundcover; + }; + + class Reflection : public SceneUtil::RTTNode + { + public: + Reflection(uint32_t rttSize, bool isInterior) + : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) + { + setInterior(isInterior); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); + mClipCullNode = new ClipCullNode; + } + + void setDefaults(osg::Camera* camera) override + { + camera->setReferenceFrame(osg::Camera::RELATIVE_RF); + camera->setSmallFeatureCullingPixelSize( + Settings::Manager::getInt("small feature culling pixel size", "Water")); + camera->setName("ReflectionCamera"); + camera->addCullCallback(new InheritViewPointCallback); + + // Inform the shader that we're in a reflection + camera->getOrCreateStateSet()->addUniform(new osg::Uniform("isReflection", true)); + + // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. + osg::ref_ptr frontFace(new osg::FrontFace); + frontFace->setMode(osg::FrontFace::CLOCKWISE); + camera->getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); + + camera->addChild(mClipCullNode); + camera->setNodeMask(Mask_RenderToTexture); + + SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + } + + void apply(osg::Camera* camera) override + { + camera->setViewMatrix(mViewMatrix); + camera->setCullMask(mNodeMask); + } + + void setInterior(bool isInterior) + { + mInterior = isInterior; + mNodeMask = calcNodeMask(); + } + + void setWaterLevel(float waterLevel) + { + mViewMatrix = osg::Matrix::scale(1, 1, -1) * osg::Matrix::translate(0, 0, 2 * waterLevel); + mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, waterLevel))); + } + + void setScene(osg::Node* scene) + { + if (mScene) + mClipCullNode->removeChild(mScene); + mScene = scene; + mClipCullNode->addChild(scene); + } + + void showWorld(bool show) + { + if (show) + mNodeMask = calcNodeMask(); + else + mNodeMask = calcNodeMask() & ~sToggleWorldMask; + } + + private: + unsigned int calcNodeMask() + { + int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); + reflectionDetail = std::clamp(reflectionDetail, mInterior ? 2 : 0, 5); + unsigned int extraMask = 0; + if (reflectionDetail >= 1) + extraMask |= Mask_Terrain; + if (reflectionDetail >= 2) + extraMask |= Mask_Static; + if (reflectionDetail >= 3) + extraMask |= Mask_Effect | Mask_ParticleSystem | Mask_Object; + if (reflectionDetail >= 4) + extraMask |= Mask_Player | Mask_Actor; + if (reflectionDetail >= 5) + extraMask |= Mask_Groundcover; + return Mask_Scene | Mask_Sky | Mask_Lighting | extraMask; + } + + osg::ref_ptr mClipCullNode; + osg::ref_ptr mScene; + osg::Node::NodeMask mNodeMask; + osg::Matrix mViewMatrix{ osg::Matrix::identity() }; + bool mInterior; + }; + + /// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. + class DepthClampCallback : public osg::Drawable::DrawCallback + { + public: + void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override + { + static bool supported = osg::isGLExtensionOrVersionSupported( + renderInfo.getState()->getContextID(), "GL_ARB_depth_clamp", 3.3); + if (!supported) + { + drawable->drawImplementation(renderInfo); + return; + } + + glEnable(GL_DEPTH_CLAMP); + + drawable->drawImplementation(renderInfo); + + // restore default + glDisable(GL_DEPTH_CLAMP); + } + }; + + Water::Water(osg::Group* parent, osg::Group* sceneRoot, Resource::ResourceSystem* resourceSystem, + osgUtil::IncrementalCompileOperation* ico) + : mRainIntensityUpdater(nullptr) + , mParent(parent) + , mSceneRoot(sceneRoot) + , mResourceSystem(resourceSystem) + , mEnabled(true) + , mToggled(true) + , mTop(0) + , mInterior(false) + , mShowWorld(true) + , mCullCallback(nullptr) + , mShaderWaterStateSetUpdater(nullptr) + { + mSimulation = std::make_unique(mSceneRoot, resourceSystem); + + mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits * 150, 40, 900); + mWaterGeom->setDrawCallback(new DepthClampCallback); + mWaterGeom->setNodeMask(Mask_Water); + mWaterGeom->setDataVariance(osg::Object::STATIC); + mWaterGeom->setName("Water Geometry"); + + mWaterNode = new osg::PositionAttitudeTransform; + mWaterNode->setName("Water Root"); + mWaterNode->addChild(mWaterGeom); + mWaterNode->addCullCallback(new FudgeCallback); + + // simple water fallback for the local map + osg::ref_ptr geom2(osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES)); + createSimpleWaterStateSet(geom2, Fallback::Map::getFloat("Water_Map_Alpha")); + geom2->setNodeMask(Mask_SimpleWater); + geom2->setName("Simple Water Geometry"); + mWaterNode->addChild(geom2); + + mSceneRoot->addChild(mWaterNode); + + setHeight(mTop); + + updateWaterMaterial(); + + if (ico) + ico->add(mWaterNode); + } + + void Water::setCullCallback(osg::Callback* callback) + { + if (mCullCallback) + { + mWaterNode->removeCullCallback(mCullCallback); + if (mReflection) + mReflection->removeCullCallback(mCullCallback); + if (mRefraction) + mRefraction->removeCullCallback(mCullCallback); + } + + mCullCallback = callback; + + if (callback) + { + mWaterNode->addCullCallback(callback); + if (mReflection) + mReflection->addCullCallback(callback); + if (mRefraction) + mRefraction->addCullCallback(callback); + } + } + + void Water::updateWaterMaterial() + { + if (mShaderWaterStateSetUpdater) + { + mWaterNode->removeCullCallback(mShaderWaterStateSetUpdater); + mShaderWaterStateSetUpdater = nullptr; + } + if (mReflection) + { + mParent->removeChild(mReflection); + mReflection = nullptr; + } + if (mRefraction) + { + mParent->removeChild(mRefraction); + mRefraction = nullptr; + } + if (mRipples) + { + mParent->removeChild(mRipples); + mRipples = nullptr; + mSimulation->setRipples(nullptr); + } + + mWaterNode->setStateSet(nullptr); + mWaterGeom->setStateSet(nullptr); + mWaterGeom->setUpdateCallback(nullptr); + + if (Settings::Manager::getBool("shader", "Water")) + { + unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); + + mReflection = new Reflection(rttSize, mInterior); + mReflection->setWaterLevel(mTop); + mReflection->setScene(mSceneRoot); + if (mCullCallback) + mReflection->addCullCallback(mCullCallback); + mParent->addChild(mReflection); + + if (Settings::Manager::getBool("refraction", "Water")) + { + mRefraction = new Refraction(rttSize); + mRefraction->setWaterLevel(mTop); + mRefraction->setScene(mSceneRoot); + if (mCullCallback) + mRefraction->addCullCallback(mCullCallback); + mParent->addChild(mRefraction); + } + + mRipples = new Ripples(mResourceSystem); + mSimulation->setRipples(mRipples); + mParent->addChild(mRipples); + + showWorld(mShowWorld); + + createShaderWaterStateSet(mWaterNode); } else - traverse(node, nv); - } -}; + createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha")); -class RainIntensityUpdater : public SceneUtil::StateSetUpdater -{ -public: - RainIntensityUpdater() - : mRainIntensity(0.f) - { + updateVisible(); } - void setRainIntensity(float rainIntensity) + osg::Node* Water::getReflectionNode() { - mRainIntensity = rainIntensity; + return mReflection; } -protected: - void setDefaults(osg::StateSet* stateset) override + osg::Node* Water::getRefractionNode() { - osg::ref_ptr rainIntensityUniform = new osg::Uniform("rainIntensity", 0.0f); - stateset->addUniform(rainIntensityUniform.get()); + return mRefraction; } - void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override + osg::Vec3d Water::getPosition() const { - osg::ref_ptr rainIntensityUniform = stateset->getUniform("rainIntensity"); - if (rainIntensityUniform != nullptr) - rainIntensityUniform->set(mRainIntensity); + return mWaterNode->getPosition(); } -private: - float mRainIntensity; -}; - -osg::ref_ptr readPngImage (const std::string& file) -{ - // use boost in favor of osgDB::readImage, to handle utf-8 path issues on Windows - boost::filesystem::ifstream inStream; - inStream.open(file, std::ios_base::in | std::ios_base::binary); - if (inStream.fail()) - Log(Debug::Error) << "Error: Failed to open " << file; - osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); - if (!reader) + void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { - Log(Debug::Error) << "Error: Failed to read " << file << ", no png readerwriter found"; - return osg::ref_ptr(); - } - osgDB::ReaderWriter::ReadResult result = reader->readImage(inStream); - if (!result.success()) - Log(Debug::Error) << "Error: Failed to read " << file << ": " << result.message() << " code " << result.status(); + osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); - return result.getImage(); -} + node->setStateSet(stateset); + node->setUpdateCallback(nullptr); + mRainIntensityUpdater = nullptr; - -class Refraction : public osg::Camera -{ -public: - Refraction() - { - unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); - setRenderOrder(osg::Camera::PRE_RENDER, 1); - setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); - setReferenceFrame(osg::Camera::RELATIVE_RF); - setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); - osg::Camera::setName("RefractionCamera"); - setCullCallback(new InheritViewPointCallback); - setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); - - setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting|Mask_Groundcover); - setNodeMask(Mask_RenderToTexture); - setViewport(0, 0, rttSize, rttSize); - - // No need for Update traversal since the scene is already updated as part of the main scene graph - // A double update would mess with the light collection (in addition to being plain redundant) - setUpdateCallback(new NoTraverseCallback); - - // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog - // assign large value to effectively turn off fog - // shaders don't respect glDisable(GL_FOG) - osg::ref_ptr fog (new osg::Fog); - fog->setStart(10000000); - fog->setEnd(10000000); - getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); - - mClipCullNode = new ClipCullNode; - osg::Camera::addChild(mClipCullNode); - - mRefractionTexture = new osg::Texture2D; - mRefractionTexture->setTextureSize(rttSize, rttSize); - mRefractionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mRefractionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mRefractionTexture->setInternalFormat(GL_RGB); - mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - - SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mRefractionTexture); - - mRefractionDepthTexture = new osg::Texture2D; - mRefractionDepthTexture->setTextureSize(rttSize, rttSize); - mRefractionDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); - mRefractionDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24); - mRefractionDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mRefractionDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mRefractionDepthTexture->setSourceType(GL_UNSIGNED_INT); - mRefractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - - attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); - - if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709 - SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); - } - - void setScene(osg::Node* scene) - { - if (mScene) - mClipCullNode->removeChild(mScene); - mScene = scene; - mClipCullNode->addChild(scene); - } - - 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))); - } - - osg::Texture2D* getRefractionTexture() const - { - return mRefractionTexture.get(); - } - - osg::Texture2D* getRefractionDepthTexture() const - { - return mRefractionDepthTexture.get(); - } - -private: - osg::ref_ptr mClipCullNode; - osg::ref_ptr mRefractionTexture; - osg::ref_ptr mRefractionDepthTexture; - osg::ref_ptr mScene; -}; - -class Reflection : public osg::Camera -{ -public: - Reflection(bool isInterior) - { - setRenderOrder(osg::Camera::PRE_RENDER); - setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); - setReferenceFrame(osg::Camera::RELATIVE_RF); - setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); - osg::Camera::setName("ReflectionCamera"); - setCullCallback(new InheritViewPointCallback); - - setInterior(isInterior); - setNodeMask(Mask_RenderToTexture); - - unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); - setViewport(0, 0, rttSize, rttSize); - - // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph - // A double update would mess with the light collection (in addition to being plain redundant) - setUpdateCallback(new NoTraverseCallback); - - mReflectionTexture = new osg::Texture2D; - mReflectionTexture->setTextureSize(rttSize, rttSize); - mReflectionTexture->setInternalFormat(GL_RGB); - mReflectionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mReflectionTexture); - - // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. - osg::ref_ptr frontFace (new osg::FrontFace); - frontFace->setMode(osg::FrontFace::CLOCKWISE); - getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); - - mClipCullNode = new ClipCullNode; - osg::Camera::addChild(mClipCullNode); - - SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); - } - - void setInterior(bool isInterior) - { - int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - reflectionDetail = std::min(5, std::max(isInterior ? 2 : 0, reflectionDetail)); - unsigned int extraMask = 0; - if(reflectionDetail >= 1) extraMask |= Mask_Terrain; - if(reflectionDetail >= 2) extraMask |= Mask_Static; - if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object; - if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor; - if(reflectionDetail >= 5) extraMask |= Mask_Groundcover; - setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); - } - - void setWaterLevel(float waterLevel) - { - setViewMatrix(osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,2 * waterLevel)); - mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,1), osg::Vec3d(0,0,waterLevel))); - } - - void setScene(osg::Node* scene) - { - if (mScene) - mClipCullNode->removeChild(mScene); - mScene = scene; - mClipCullNode->addChild(scene); - } - - osg::Texture2D* getReflectionTexture() const - { - return mReflectionTexture.get(); - } - -private: - osg::ref_ptr mReflectionTexture; - osg::ref_ptr mClipCullNode; - osg::ref_ptr mScene; -}; - -/// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. -class DepthClampCallback : public osg::Drawable::DrawCallback -{ -public: - void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const override - { - static bool supported = osg::isGLExtensionOrVersionSupported(renderInfo.getState()->getContextID(), "GL_ARB_depth_clamp", 3.3); - if (!supported) + // Add animated textures + std::vector> textures; + const int frameCount = std::clamp(Fallback::Map::getInt("Water_SurfaceFrameCount"), 0, 320); + std::string_view texture = Fallback::Map::getString("Water_SurfaceTexture"); + for (int i = 0; i < frameCount; ++i) { - drawable->drawImplementation(renderInfo); + std::ostringstream texname; + texname << "textures/water/" << texture << std::setw(2) << std::setfill('0') << i << ".dds"; + osg::ref_ptr tex( + new osg::Texture2D(mResourceSystem->getImageManager()->getImage(texname.str()))); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); + } + + if (textures.empty()) return; - } - glEnable(GL_DEPTH_CLAMP); + float fps = Fallback::Map::getFloat("Water_SurfaceFPS"); - drawable->drawImplementation(renderInfo); + osg::ref_ptr controller(new NifOsg::FlipController(0, 1.f / fps, textures)); + controller->setSource(std::make_shared()); + node->setUpdateCallback(controller); - // restore default - glDisable(GL_DEPTH_CLAMP); - } -}; + stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); -Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, - osgUtil::IncrementalCompileOperation *ico, const std::string& resourcePath) - : mRainIntensityUpdater(nullptr) - , mParent(parent) - , mSceneRoot(sceneRoot) - , mResourceSystem(resourceSystem) - , mResourcePath(resourcePath) - , mEnabled(true) - , mToggled(true) - , mTop(0) - , mInterior(false) - , mCullCallback(nullptr) -{ - mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem)); - - mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900); - mWaterGeom->setDrawCallback(new DepthClampCallback); - mWaterGeom->setNodeMask(Mask_Water); - mWaterGeom->setDataVariance(osg::Object::STATIC); - - mWaterNode = new osg::PositionAttitudeTransform; - mWaterNode->setName("Water Root"); - mWaterNode->addChild(mWaterGeom); - mWaterNode->addCullCallback(new FudgeCallback); - - // simple water fallback for the local map - osg::ref_ptr geom2 (osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES)); - createSimpleWaterStateSet(geom2, Fallback::Map::getFloat("Water_Map_Alpha")); - geom2->setNodeMask(Mask_SimpleWater); - mWaterNode->addChild(geom2); - - mSceneRoot->addChild(mWaterNode); - - setHeight(mTop); - - updateWaterMaterial(); - - if (ico) - ico->add(mWaterNode); -} - -void Water::setCullCallback(osg::Callback* callback) -{ - if (mCullCallback) - { - mWaterNode->removeCullCallback(mCullCallback); - if (mReflection) - mReflection->removeCullCallback(mCullCallback); - if (mRefraction) - mRefraction->removeCullCallback(mCullCallback); + // use a shader to render the simple water, ensuring that fog is applied per pixel as required. + // this could be removed if a more detailed water mesh, using some sort of paging solution, is implemented. + Resource::SceneManager* sceneManager = mResourceSystem->getSceneManager(); + bool oldValue = sceneManager->getForceShaders(); + sceneManager->setForceShaders(true); + sceneManager->recreateShaders(node); + sceneManager->setForceShaders(oldValue); } - mCullCallback = callback; - - if (callback) + class ShaderWaterStateSetUpdater : public SceneUtil::StateSetUpdater { - mWaterNode->addCullCallback(callback); - if (mReflection) - mReflection->addCullCallback(callback); - if (mRefraction) - mRefraction->addCullCallback(callback); - } -} - -void Water::updateWaterMaterial() -{ - if (mReflection) - { - mReflection->removeChildren(0, mReflection->getNumChildren()); - mParent->removeChild(mReflection); - mReflection = nullptr; - } - if (mRefraction) - { - mRefraction->removeChildren(0, mRefraction->getNumChildren()); - mParent->removeChild(mRefraction); - mRefraction = nullptr; - } - - if (Settings::Manager::getBool("shader", "Water")) - { - mReflection = new Reflection(mInterior); - mReflection->setWaterLevel(mTop); - mReflection->setScene(mSceneRoot); - if (mCullCallback) - mReflection->addCullCallback(mCullCallback); - mParent->addChild(mReflection); - - if (Settings::Manager::getBool("refraction", "Water")) + public: + ShaderWaterStateSetUpdater(Water* water, Reflection* reflection, Refraction* refraction, Ripples* ripples, + osg::ref_ptr program, osg::ref_ptr normalMap) + : mWater(water) + , mReflection(reflection) + , mRefraction(refraction) + , mRipples(ripples) + , mProgram(program) + , mNormalMap(normalMap) { - mRefraction = new Refraction; - mRefraction->setWaterLevel(mTop); - mRefraction->setScene(mSceneRoot); - if (mCullCallback) - mRefraction->addCullCallback(mCullCallback); - mParent->addChild(mRefraction); } - createShaderWaterStateSet(mWaterGeom, mReflection, mRefraction); - } - else - createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha")); + void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("normalMap", 0)); + stateset->setTextureAttributeAndModes(0, mNormalMap, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON); - updateVisible(); -} + stateset->addUniform(new osg::Uniform("reflectionMap", 1)); + if (mRefraction) + { + stateset->addUniform(new osg::Uniform("refractionMap", 2)); + stateset->addUniform(new osg::Uniform("refractionDepthMap", 3)); + stateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin"); + } + else + { + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); + osg::ref_ptr depth = new SceneUtil::AutoDepth; + depth->setWriteMask(false); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + if (mRipples) + { + stateset->addUniform(new osg::Uniform("rippleMap", 4)); + } + stateset->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWater->getPosition()))); + } -osg::Camera *Water::getReflectionCamera() -{ - return mReflection; -} + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + osgUtil::CullVisitor* cv = static_cast(nv); + stateset->setTextureAttributeAndModes(1, mReflection->getColorTexture(cv), osg::StateAttribute::ON); -osg::Camera *Water::getRefractionCamera() -{ - return mRefraction; -} + if (mRefraction) + { + stateset->setTextureAttributeAndModes(2, mRefraction->getColorTexture(cv), osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(3, mRefraction->getDepthTexture(cv), osg::StateAttribute::ON); + } + if (mRipples) + { + stateset->setTextureAttributeAndModes(4, mRipples->getColorTexture(), osg::StateAttribute::ON); + } + stateset->getUniform("nodePosition")->set(osg::Vec3f(mWater->getPosition())); + } -void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) -{ - osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); + private: + Water* mWater; + Reflection* mReflection; + Refraction* mRefraction; + Ripples* mRipples; + osg::ref_ptr mProgram; + osg::ref_ptr mNormalMap; + }; - node->setStateSet(stateset); - node->setUpdateCallback(nullptr); - mRainIntensityUpdater = nullptr; - - // Add animated textures - std::vector > textures; - int frameCount = std::max(0, std::min(Fallback::Map::getInt("Water_SurfaceFrameCount"), 320)); - const std::string& texture = Fallback::Map::getString("Water_SurfaceTexture"); - for (int i=0; i tex (new osg::Texture2D(mResourceSystem->getImageManager()->getImage(texname.str()))); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - textures.push_back(tex); + // use a define map to conditionally compile the shader + std::map defineMap; + defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); + const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); + defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); + defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::mWorldScaleFactor); + defineMap["ripple_map_size"] = std::to_string(RipplesSurface::mRTTSize) + ".0"; + + Stereo::shaderStereoDefines(defineMap); + + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); + osg::ref_ptr program = shaderMgr.getProgram("water", defineMap); + + osg::ref_ptr normalMap( + new osg::Texture2D(mResourceSystem->getImageManager()->getImage("textures/omw/water_nm.png"))); + if (normalMap->getImage()) + normalMap->getImage()->flipVertical(); + normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + normalMap->setMaxAnisotropy(16); + normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); + normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + mRainIntensityUpdater = new RainIntensityUpdater(); + node->setUpdateCallback(mRainIntensityUpdater); + + mShaderWaterStateSetUpdater + = new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, mRipples, program, normalMap); + node->addCullCallback(mShaderWaterStateSetUpdater); } - if (textures.empty()) - return; - - float fps = Fallback::Map::getFloat("Water_SurfaceFPS"); - - osg::ref_ptr controller (new NifOsg::FlipController(0, 1.f/fps, textures)); - controller->setSource(std::shared_ptr(new SceneUtil::FrameTimeSource)); - node->setUpdateCallback(controller); - - stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); - - // use a shader to render the simple water, ensuring that fog is applied per pixel as required. - // this could be removed if a more detailed water mesh, using some sort of paging solution, is implemented. - Resource::SceneManager* sceneManager = mResourceSystem->getSceneManager(); - bool oldValue = sceneManager->getForceShaders(); - sceneManager->setForceShaders(true); - sceneManager->recreateShaders(node); - sceneManager->setForceShaders(oldValue); -} - -void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction) -{ - // use a define map to conditionally compile the shader - std::map defineMap; - defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0"))); - - Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); - osg::ref_ptr vertexShader (shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); - osg::ref_ptr fragmentShader (shaderMgr.getShader("water_fragment.glsl", defineMap, osg::Shader::FRAGMENT)); - - osg::ref_ptr normalMap (new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png"))); - - if (normalMap->getImage()) - normalMap->getImage()->flipVertical(); - normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - normalMap->setMaxAnisotropy(16); - normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); - normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - - osg::ref_ptr shaderStateset = new osg::StateSet; - shaderStateset->addUniform(new osg::Uniform("normalMap", 0)); - shaderStateset->addUniform(new osg::Uniform("reflectionMap", 1)); - - shaderStateset->setTextureAttributeAndModes(0, normalMap, osg::StateAttribute::ON); - shaderStateset->setTextureAttributeAndModes(1, reflection->getReflectionTexture(), osg::StateAttribute::ON); - - if (refraction) + void Water::processChangedSettings(const Settings::CategorySettingVector& settings) { - shaderStateset->setTextureAttributeAndModes(2, refraction->getRefractionTexture(), osg::StateAttribute::ON); - shaderStateset->setTextureAttributeAndModes(3, refraction->getRefractionDepthTexture(), osg::StateAttribute::ON); - shaderStateset->addUniform(new osg::Uniform("refractionMap", 2)); - shaderStateset->addUniform(new osg::Uniform("refractionDepthMap", 3)); - shaderStateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin"); + updateWaterMaterial(); } - else + + Water::~Water() { - shaderStateset->setMode(GL_BLEND, osg::StateAttribute::ON); + mParent->removeChild(mWaterNode); - shaderStateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); - - osg::ref_ptr depth (new osg::Depth); - depth->setWriteMask(false); - shaderStateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + if (mReflection) + { + mParent->removeChild(mReflection); + mReflection = nullptr; + } + if (mRefraction) + { + mParent->removeChild(mRefraction); + mRefraction = nullptr; + } + if (mRipples) + { + mParent->removeChild(mRipples); + mRipples = nullptr; + mSimulation->setRipples(nullptr); + } } - shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - - osg::ref_ptr program (new osg::Program); - program->addShader(vertexShader); - program->addShader(fragmentShader); - auto method = mResourceSystem->getSceneManager()->getLightingMethod(); - if (method == SceneUtil::LightingMethod::SingleUBO) - program->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); - shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); - - node->setStateSet(shaderStateset); - - mRainIntensityUpdater = new RainIntensityUpdater(); - node->setUpdateCallback(mRainIntensityUpdater); -} - -void Water::processChangedSettings(const Settings::CategorySettingVector& settings) -{ - updateWaterMaterial(); -} - -Water::~Water() -{ - mParent->removeChild(mWaterNode); - - if (mReflection) + void Water::listAssetsToPreload(std::vector& textures) { - mReflection->removeChildren(0, mReflection->getNumChildren()); - mParent->removeChild(mReflection); - mReflection = nullptr; + const int frameCount = std::clamp(Fallback::Map::getInt("Water_SurfaceFrameCount"), 0, 320); + std::string_view texture = Fallback::Map::getString("Water_SurfaceTexture"); + for (int i = 0; i < frameCount; ++i) + { + std::ostringstream texname; + texname << "textures/water/" << texture << std::setw(2) << std::setfill('0') << i << ".dds"; + textures.push_back(texname.str()); + } } - if (mRefraction) + + void Water::setEnabled(bool enabled) { - mRefraction->removeChildren(0, mRefraction->getNumChildren()); - mParent->removeChild(mRefraction); - mRefraction = nullptr; + mEnabled = enabled; + updateVisible(); } -} -void Water::listAssetsToPreload(std::vector &textures) -{ - int frameCount = std::max(0, std::min(Fallback::Map::getInt("Water_SurfaceFrameCount"), 320)); - const std::string& texture = Fallback::Map::getString("Water_SurfaceTexture"); - for (int i=0; igetCell()->isExterior(); + bool wasInterior = mInterior; + if (!isInterior) + { + mWaterNode->setPosition( + getSceneNodeCoordinates(store->getCell()->getGridX(), store->getCell()->getGridY())); + mInterior = false; + } + else + { + mWaterNode->setPosition(osg::Vec3f(0, 0, mTop)); + mInterior = true; + } + if (mInterior != wasInterior && mReflection) + mReflection->setInterior(mInterior); } -} -void Water::setEnabled(bool enabled) -{ - mEnabled = enabled; - updateVisible(); -} - -void Water::changeCell(const MWWorld::CellStore* store) -{ - bool isInterior = !store->getCell()->isExterior(); - bool wasInterior = mInterior; - if (!isInterior) + void Water::setHeight(const float height) { - mWaterNode->setPosition(getSceneNodeCoordinates(store->getCell()->mData.mX, store->getCell()->mData.mY)); - mInterior = false; + mTop = height; + + mSimulation->setWaterHeight(height); + + osg::Vec3f pos = mWaterNode->getPosition(); + pos.z() = height; + mWaterNode->setPosition(pos); + + if (mReflection) + mReflection->setWaterLevel(mTop); + if (mRefraction) + mRefraction->setWaterLevel(mTop); } - else + + void Water::setRainIntensity(float rainIntensity) { - mWaterNode->setPosition(osg::Vec3f(0,0,mTop)); - mInterior = true; + if (mRainIntensityUpdater) + mRainIntensityUpdater->setRainIntensity(rainIntensity); } - if(mInterior != wasInterior && mReflection) - mReflection->setInterior(mInterior); - // create a new StateSet to prevent threading issues - osg::ref_ptr nodeStateSet (new osg::StateSet); - nodeStateSet->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWaterNode->getPosition()))); - mWaterNode->setStateSet(nodeStateSet); -} + void Water::update(float dt, bool paused) + { + if (!paused) + { + mSimulation->update(dt); + } -void Water::setHeight(const float height) -{ - mTop = height; + if (mRipples) + { + mRipples->setPaused(paused); + } + } - mSimulation->setWaterHeight(height); + void Water::updateVisible() + { + bool visible = mEnabled && mToggled; + mWaterNode->setNodeMask(visible ? ~0u : 0u); + if (mRefraction) + mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0u); + if (mReflection) + mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0u); + if (mRipples) + mRipples->setNodeMask(visible ? Mask_RenderToTexture : 0u); + } - osg::Vec3f pos = mWaterNode->getPosition(); - pos.z() = height; - mWaterNode->setPosition(pos); + bool Water::toggle() + { + mToggled = !mToggled; + updateVisible(); + return mToggled; + } - if (mReflection) - mReflection->setWaterLevel(mTop); - if (mRefraction) - mRefraction->setWaterLevel(mTop); -} + bool Water::isUnderwater(const osg::Vec3f& pos) const + { + return pos.z() < mTop && mToggled && mEnabled; + } -void Water::setRainIntensity(float rainIntensity) -{ - if (mRainIntensityUpdater) - mRainIntensityUpdater->setRainIntensity(rainIntensity); -} + osg::Vec3f Water::getSceneNodeCoordinates(int gridX, int gridY) + { + return osg::Vec3f(static_cast(gridX * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), + static_cast(gridY * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), mTop); + } -void Water::update(float dt) -{ - mSimulation->update(dt); -} + void Water::addEmitter(const MWWorld::Ptr& ptr, float scale, float force) + { + mSimulation->addEmitter(ptr, scale, force); + } -void Water::updateVisible() -{ - bool visible = mEnabled && mToggled; - mWaterNode->setNodeMask(visible ? ~0u : 0u); - if (mRefraction) - mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0u); - if (mReflection) - mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0u); -} + void Water::removeEmitter(const MWWorld::Ptr& ptr) + { + mSimulation->removeEmitter(ptr); + } -bool Water::toggle() -{ - mToggled = !mToggled; - updateVisible(); - return mToggled; -} + void Water::updateEmitterPtr(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) + { + mSimulation->updateEmitterPtr(old, ptr); + } -bool Water::isUnderwater(const osg::Vec3f &pos) const -{ - return pos.z() < mTop && mToggled && mEnabled; -} + void Water::emitRipple(const osg::Vec3f& pos) + { + mSimulation->emitRipple(pos); + } -osg::Vec3f Water::getSceneNodeCoordinates(int gridX, int gridY) -{ - return osg::Vec3f(static_cast(gridX * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), - static_cast(gridY * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), mTop); -} + void Water::removeCell(const MWWorld::CellStore* store) + { + mSimulation->removeCell(store); + } -void Water::addEmitter (const MWWorld::Ptr& ptr, float scale, float force) -{ - mSimulation->addEmitter (ptr, scale, force); -} + void Water::clearRipples() + { + mSimulation->clear(); + } -void Water::removeEmitter (const MWWorld::Ptr& ptr) -{ - mSimulation->removeEmitter (ptr); -} - -void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) -{ - mSimulation->updateEmitterPtr(old, ptr); -} - -void Water::emitRipple(const osg::Vec3f &pos) -{ - mSimulation->emitRipple(pos); -} - -void Water::removeCell(const MWWorld::CellStore *store) -{ - mSimulation->removeCell(store); -} - -void Water::clearRipples() -{ - mSimulation->clear(); -} + void Water::showWorld(bool show) + { + if (mReflection) + mReflection->showWorld(show); + if (mRefraction) + mRefraction->showWorld(show); + mShowWorld = show; + } } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index ec7dc95db..0204cb430 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -4,9 +4,9 @@ #include #include -#include +#include #include -#include +#include #include @@ -16,6 +16,7 @@ namespace osg class PositionAttitudeTransform; class Geometry; class Node; + class Callback; } namespace osgUtil @@ -46,6 +47,7 @@ namespace MWRender class Reflection; class RippleSimulation; class RainIntensityUpdater; + class Ripples; /// Water rendering class Water @@ -63,31 +65,29 @@ namespace MWRender osg::ref_ptr mRefraction; osg::ref_ptr mReflection; - - const std::string mResourcePath; + osg::ref_ptr mRipples; bool mEnabled; bool mToggled; float mTop; bool mInterior; + bool mShowWorld; osg::Callback* mCullCallback; + osg::ref_ptr mShaderWaterStateSetUpdater; osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); void createSimpleWaterStateSet(osg::Node* node, float alpha); - /// @param reflection the reflection camera (required) - /// @param refraction the refraction camera (optional) - void createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction); + void createShaderWaterStateSet(osg::Node* node); void updateWaterMaterial(); public: - Water(osg::Group* parent, osg::Group* sceneRoot, - Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, - const std::string& resourcePath); + Water(osg::Group* parent, osg::Group* sceneRoot, Resource::ResourceSystem* resourceSystem, + osgUtil::IncrementalCompileOperation* ico); ~Water(); void setCullCallback(osg::Callback* callback); @@ -101,9 +101,9 @@ namespace MWRender bool isUnderwater(const osg::Vec3f& pos) const; /// adds an emitter, position will be tracked automatically using its scene node - void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); - void removeEmitter (const MWWorld::Ptr& ptr); - void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void addEmitter(const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); + void removeEmitter(const MWWorld::Ptr& ptr); + void updateEmitterPtr(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); void emitRipple(const osg::Vec3f& pos); void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell @@ -114,12 +114,16 @@ namespace MWRender void setHeight(const float height); void setRainIntensity(const float rainIntensity); - void update(float dt); + void update(float dt, bool paused); - osg::Camera *getReflectionCamera(); - osg::Camera *getRefractionCamera(); + osg::Node* getReflectionNode(); + osg::Node* getRefractionNode(); + + osg::Vec3d getPosition() const; void processChangedSettings(const Settings::CategorySettingVector& settings); + + void showWorld(bool show); }; } diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 12007f93e..2f4d3b5ef 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -5,6 +5,7 @@ #include #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -16,14 +17,16 @@ */ #include "../mwbase/world.hpp" +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/weapontype.hpp" @@ -33,66 +36,81 @@ namespace MWRender { -float WeaponAnimationTime::getValue(osg::NodeVisitor*) -{ - if (mWeaponGroup.empty()) - return 0; - - float current = mAnimation->getCurrentTime(mWeaponGroup); - if (current == -1) - return 0; - return current - mStartTime; -} - -void WeaponAnimationTime::setGroup(const std::string &group, bool relativeTime) -{ - mWeaponGroup = group; - mRelativeTime = relativeTime; - - if (mRelativeTime) - mStartTime = mAnimation->getStartTime(mWeaponGroup); - else - mStartTime = 0; -} - -void WeaponAnimationTime::updateStartTime() -{ - setGroup(mWeaponGroup, mRelativeTime); -} - -WeaponAnimation::WeaponAnimation() - : mPitchFactor(0) -{ -} - -WeaponAnimation::~WeaponAnimation() -{ - -} - -void WeaponAnimation::attachArrow(MWWorld::Ptr actor) -{ - const MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); - MWWorld::ConstContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot == inv.end()) - return; - if (weaponSlot->getTypeName() != typeid(ESM::Weapon).name()) - return; - - int type = weaponSlot->get()->mBase->mData.mType; - ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; - if (weapclass == ESM::WeaponType::Thrown) + float WeaponAnimationTime::getValue(osg::NodeVisitor*) { - std::string soundid = weaponSlot->getClass().getUpSoundId(*weaponSlot); - if(!soundid.empty()) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(actor, soundid, 1.0f, 1.0f); - } - showWeapon(true); + if (mWeaponGroup.empty()) + return 0; + + float current = mAnimation->getCurrentTime(mWeaponGroup); + if (current == -1) + return 0; + return current - mStartTime; } - else if (weapclass == ESM::WeaponType::Ranged) + + void WeaponAnimationTime::setGroup(const std::string& group, bool relativeTime) { + mWeaponGroup = group; + mRelativeTime = relativeTime; + + if (mRelativeTime) + mStartTime = mAnimation->getStartTime(mWeaponGroup); + else + mStartTime = 0; + } + + void WeaponAnimationTime::updateStartTime() + { + setGroup(mWeaponGroup, mRelativeTime); + } + + WeaponAnimation::WeaponAnimation() + : mPitchFactor(0) + { + } + + WeaponAnimation::~WeaponAnimation() {} + + void WeaponAnimation::attachArrow(const MWWorld::Ptr& actor) + { + const MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ConstContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot == inv.end()) + return; + if (weaponSlot->getType() != ESM::Weapon::sRecordId) + return; + + int type = weaponSlot->get()->mBase->mData.mType; + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; + if (weapclass == ESM::WeaponType::Thrown) + { + const auto& soundid = weaponSlot->getClass().getUpSoundId(*weaponSlot); + if (!soundid.empty()) + { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(actor, soundid, 1.0f, 1.0f); + } + showWeapon(true); + } + else if (weapclass == ESM::WeaponType::Ranged) + { + osg::Group* parent = getArrowBone(); + if (!parent) + return; + + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + std::string model = ammo->getClass().getModel(*ammo); + + osg::ref_ptr arrow = getResourceSystem()->getSceneManager()->getInstance(model, parent); + + mAmmunition = std::make_unique(arrow); + } + } + + void WeaponAnimation::detachArrow(MWWorld::Ptr actor) + { +<<<<<<< HEAD osg::Group* parent = getArrowBone(); if (!parent) return; @@ -297,60 +315,134 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) MWBase::Environment::get().getWorld()->launchProjectile(actor, ammoPtr, launchPos, orient, weaponPtr, speed, attackStrength); inv.remove(ammoPtr, 1, actor); +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mAmmunition.reset(); } -} -void WeaponAnimation::addControllers(const std::map >& nodes, - std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) -{ - for (int i=0; i<2; ++i) + void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) { - mSpineControllers[i] = nullptr; + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end()) + return; + if (weapon->getType() != ESM::Weapon::sRecordId) + return; - std::map >::const_iterator found = nodes.find(i == 0 ? "bip01 spine1" : "bip01 spine2"); - if (found != nodes.end()) + // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's + // orientation dictates otherwise. + osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) + * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); + + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + + MWMechanics::applyFatigueLoss(actor, *weapon, attackStrength); + + if (MWMechanics::getWeaponType(weapon->get()->mBase->mData.mType)->mWeaponClass + == ESM::WeaponType::Thrown) { - osg::Node* node = found->second; - mSpineControllers[i] = new RotateController(objectRoot); - node->addUpdateCallback(mSpineControllers[i]); - map.emplace_back(node, mSpineControllers[i]); + // Thrown weapons get detached now + osg::Node* weaponNode = getWeaponNode(); + if (!weaponNode) + return; + osg::NodePathList nodepaths = weaponNode->getParentalNodePaths(); + if (nodepaths.empty()) + return; + osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); + + float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); + float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); + float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * attackStrength; + + MWWorld::Ptr weaponPtr = *weapon; + MWBase::Environment::get().getWorld()->launchProjectile( + actor, weaponPtr, launchPos, orient, weaponPtr, speed, attackStrength); + + showWeapon(false); + + inv.remove(*weapon, 1); + } + else + { + // With bows and crossbows only the used arrow/bolt gets detached + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + + if (!mAmmunition) + return; + + osg::ref_ptr ammoNode = mAmmunition->getNode(); + osg::NodePathList nodepaths = ammoNode->getParentalNodePaths(); + if (nodepaths.empty()) + return; + osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); + + float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->mValue.getFloat(); + float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * attackStrength; + + MWWorld::Ptr weaponPtr = *weapon; + MWWorld::Ptr ammoPtr = *ammo; + MWBase::Environment::get().getWorld()->launchProjectile( + actor, ammoPtr, launchPos, orient, weaponPtr, speed, attackStrength); + + inv.remove(ammoPtr, 1); + mAmmunition.reset(); } } -} -void WeaponAnimation::deleteControllers() -{ - for (int i=0; i<2; ++i) - mSpineControllers[i] = nullptr; -} - -void WeaponAnimation::configureControllers(float characterPitchRadians) -{ - if (mPitchFactor == 0.f || characterPitchRadians == 0.f) + void WeaponAnimation::addControllers(const Animation::NodeMap& nodes, + std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot) { - setControllerEnabled(false); - return; + for (int i = 0; i < 2; ++i) + { + mSpineControllers[i] = nullptr; + + Animation::NodeMap::const_iterator found = nodes.find(i == 0 ? "bip01 spine1" : "bip01 spine2"); + if (found != nodes.end()) + { + osg::Node* node = found->second; + mSpineControllers[i] = new RotateController(objectRoot); + node->addUpdateCallback(mSpineControllers[i]); + map.emplace_back(node, mSpineControllers[i]); + } + } } - float pitch = characterPitchRadians * mPitchFactor; - osg::Quat rotate (pitch/2, osg::Vec3f(-1,0,0)); - setControllerRotate(rotate); - setControllerEnabled(true); -} + void WeaponAnimation::deleteControllers() + { + for (int i = 0; i < 2; ++i) + mSpineControllers[i] = nullptr; + } -void WeaponAnimation::setControllerRotate(const osg::Quat& rotate) -{ - for (int i=0; i<2; ++i) - if (mSpineControllers[i]) - mSpineControllers[i]->setRotate(rotate); -} + void WeaponAnimation::configureControllers(float characterPitchRadians) + { + if (mPitchFactor == 0.f || characterPitchRadians == 0.f) + { + setControllerEnabled(false); + return; + } -void WeaponAnimation::setControllerEnabled(bool enabled) -{ - for (int i=0; i<2; ++i) - if (mSpineControllers[i]) - mSpineControllers[i]->setEnabled(enabled); -} + float pitch = characterPitchRadians * mPitchFactor; + osg::Quat rotate(pitch / 2, osg::Vec3f(-1, 0, 0)); + setControllerRotate(rotate); + setControllerEnabled(true); + } + + void WeaponAnimation::setControllerRotate(const osg::Quat& rotate) + { + for (int i = 0; i < 2; ++i) + if (mSpineControllers[i]) + mSpineControllers[i]->setRotate(rotate); + } + + void WeaponAnimation::setControllerEnabled(bool enabled) + { + for (int i = 0; i < 2; ++i) + if (mSpineControllers[i]) + mSpineControllers[i]->setEnabled(enabled); + } } diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index d02107333..ac9babb85 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -18,8 +18,14 @@ namespace MWRender std::string mWeaponGroup; float mStartTime; bool mRelativeTime; + public: - WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0), mRelativeTime(false) {} + WeaponAnimationTime(Animation* animation) + : mAnimation(animation) + , mStartTime(0) + , mRelativeTime(false) + { + } void setGroup(const std::string& group, bool relativeTime); void updateStartTime(); @@ -34,7 +40,7 @@ namespace MWRender virtual ~WeaponAnimation(); /// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op. - void attachArrow(MWWorld::Ptr actor); + void attachArrow(const MWWorld::Ptr& actor); void detachArrow(MWWorld::Ptr actor); @@ -42,8 +48,8 @@ namespace MWRender void releaseArrow(MWWorld::Ptr actor, float attackStrength); /// Add WeaponAnimation-related controllers to \a nodes and store the added controllers in \a map. - void addControllers(const std::map >& nodes, - std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); + void addControllers(const Animation::NodeMap& nodes, + std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); void deleteControllers(); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 58d6641dd..c958f54a0 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -22,270 +22,314 @@ #include #include -#include #include +#include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/aiactivate.hpp" #include "../mwmechanics/aiescort.hpp" +#include "../mwmechanics/aiface.hpp" #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" -#include "../mwmechanics/aiface.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/world.hpp" #include "interpretercontext.hpp" #include "ref.hpp" - namespace MWScript { namespace Ai { - template + template class OpAiActivate : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId objectID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); + // The value of the reset argument doesn't actually matter + bool repeat = arg0; + for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - // discard additional arguments (reset), because we have no idea what they mean. - for (unsigned int i=0; i + template class OpAiTravel : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float x = runtime[0].mFloat; + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + + // The value of the reset argument doesn't actually matter + bool repeat = arg0; + for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - Interpreter::Type_Float y = runtime[0].mFloat; - runtime.pop(); + if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) + return; - Interpreter::Type_Float z = runtime[0].mFloat; - runtime.pop(); + MWMechanics::AiTravel travelPackage(x, y, z, repeat); + ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(travelPackage, ptr); - // discard additional arguments (reset), because we have no idea what they mean. - for (unsigned int i=0; i + template class OpAiEscort : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + Interpreter::Type_Float duration = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + + // The value of the reset argument doesn't actually matter + bool repeat = arg0; + for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - Interpreter::Type_Float duration = runtime[0].mFloat; - runtime.pop(); + if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) + return; - Interpreter::Type_Float x = runtime[0].mFloat; - runtime.pop(); + MWMechanics::AiEscort escortPackage(actorID, static_cast(duration), x, y, z, repeat); + ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(escortPackage, ptr); - Interpreter::Type_Float y = runtime[0].mFloat; - runtime.pop(); - - Interpreter::Type_Float z = runtime[0].mFloat; - runtime.pop(); - - // discard additional arguments (reset), because we have no idea what they mean. - for (unsigned int i=0; i(duration), x, y, z); - ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); - - Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; - } + Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; + } }; - template + template class OpAiEscortCell : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Float duration = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + + // The value of the reset argument doesn't actually matter + bool repeat = arg0; + for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) + return; - Interpreter::Type_Float duration = runtime[0].mFloat; - runtime.pop(); + if (cellID.empty()) + return; - Interpreter::Type_Float x = runtime[0].mFloat; - runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->get().search(cellID)) + return; - Interpreter::Type_Float y = runtime[0].mFloat; - runtime.pop(); + MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z, repeat); + ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(escortPackage, ptr); - Interpreter::Type_Float z = runtime[0].mFloat; - runtime.pop(); - - // discard additional arguments (reset), because we have no idea what they mean. - for (unsigned int i=0; igetStore().get().find(cellID); - - MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z); - ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); - - Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; - } + Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; + } }; - template + template class OpGetAiPackageDone : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + bool done = false; + if (ptr.getClass().isActor()) + done = ptr.getClass().getCreatureStats(ptr).getAiSequence().isPackageDone(); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats (ptr).getAiSequence().isPackageDone(); - - runtime.push (value); - } + runtime.push(done); + } }; - template + template class OpAiWander : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + Interpreter::Type_Integer range = static_cast(runtime[0].mFloat); + runtime.pop(); + + Interpreter::Type_Integer duration = static_cast(runtime[0].mFloat); + runtime.pop(); + + Interpreter::Type_Integer time = static_cast(runtime[0].mFloat); + runtime.pop(); + + // Chance for Idle is unused + if (arg0) { - MWWorld::Ptr ptr = R()(runtime); - - Interpreter::Type_Integer range = static_cast(runtime[0].mFloat); + --arg0; runtime.pop(); - - Interpreter::Type_Integer duration = static_cast(runtime[0].mFloat); - runtime.pop(); - - Interpreter::Type_Integer time = static_cast(runtime[0].mFloat); - runtime.pop(); - - // Chance for Idle is unused - if (arg0) - { - --arg0; - runtime.pop(); - } - - std::vector idleList; - bool repeat = false; - - // Chances for Idle2-Idle9 - for(int i=2; i<=9 && arg0; ++i) - { - if(!repeat) - repeat = true; - Interpreter::Type_Integer idleValue = runtime[0].mInteger; - idleValue = std::min(255, std::max(0, idleValue)); - idleList.push_back(idleValue); - runtime.pop(); - --arg0; - } - - if(arg0) - { - repeat = runtime[0].mInteger != 0; - runtime.pop(); - --arg0; - } - - // discard additional arguments (reset), because we have no idea what they mean. - for (unsigned int i=0; i idleList; + bool repeat = false; + + // Chances for Idle2-Idle9 + for (int i = 2; i <= 9 && arg0; ++i) + { + if (!repeat) + repeat = true; + Interpreter::Type_Integer idleValue = std::clamp(runtime[0].mInteger, 0, 255); + idleList.push_back(idleValue); + runtime.pop(); + --arg0; + } + + if (arg0) + { + repeat = runtime[0].mInteger != 0; + runtime.pop(); + --arg0; + } + + // discard additional arguments, because we have no idea what they mean. + for (unsigned int i = 0; i < arg0; ++i) + runtime.pop(); + + if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) + return; + + MWMechanics::AiWander wanderPackage(range, duration, time, idleList, repeat); + ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(wanderPackage, ptr); + } }; - template + template class OpGetAiSetting : public Interpreter::Opcode0 { - MWMechanics::CreatureStats::AiSetting mIndex; - public: - OpGetAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} + MWMechanics::AiSetting mIndex; - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + public: + OpGetAiSetting(MWMechanics::AiSetting index) + : mIndex(index) + { + } - runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified(false)); - } + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Integer value = 0; + if (ptr.getClass().isActor()) + value = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).getModified(false); + runtime.push(value); + } }; - template + template class OpModAiSetting : public Interpreter::Opcode0 { - MWMechanics::CreatureStats::AiSetting mIndex; - public: - OpModAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} + MWMechanics::AiSetting mIndex; - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; - runtime.pop(); + public: + OpModAiSetting(MWMechanics::AiSetting index) + : mIndex(index) + { + } - int modified = ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value; + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); - ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex, modified); - ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, modified); - } + if (!ptr.getClass().isActor()) + return; + + int modified = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).getBase() + value; + + ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, modified); + ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, modified); + } }; - template + template class OpSetAiSetting : public Interpreter::Opcode0 { - MWMechanics::CreatureStats::AiSetting mIndex; - public: - OpSetAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} + MWMechanics::AiSetting mIndex; - void execute (Interpreter::Runtime& runtime) override + public: + OpSetAiSetting(MWMechanics::AiSetting index) + : mIndex(index) + { + } + + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); + if (ptr.getClass().isActor()) { +<<<<<<< HEAD MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); @@ -303,6 +347,8 @@ namespace MWScript End of tes3mp addition */ +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, value); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value); @@ -325,26 +371,44 @@ namespace MWScript End of tes3mp addition */ } + } }; - template + template class OpAiFollow : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + Interpreter::Type_Float duration = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + + // The value of the reset argument doesn't actually matter + bool repeat = arg0; + for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - Interpreter::Type_Float duration = runtime[0].mFloat; - runtime.pop(); + if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) + return; - Interpreter::Type_Float x = runtime[0].mFloat; - runtime.pop(); + MWMechanics::AiFollow followPackage(actorID, duration, x, y, z, repeat); + ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(followPackage, ptr); +<<<<<<< HEAD Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); @@ -380,145 +444,160 @@ namespace MWScript End of tes3mp addition */ } +======= + Log(Debug::Info) << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration; + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; - template + template class OpAiFollowCell : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Float duration = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + + // The value of the reset argument doesn't actually matter + bool repeat = arg0; + for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) + return; - Interpreter::Type_Float duration = runtime[0].mFloat; - runtime.pop(); - - Interpreter::Type_Float x = runtime[0].mFloat; - runtime.pop(); - - Interpreter::Type_Float y = runtime[0].mFloat; - runtime.pop(); - - Interpreter::Type_Float z = runtime[0].mFloat; - runtime.pop(); - - // discard additional arguments (reset), because we have no idea what they mean. - for (unsigned int i=0; i + template class OpGetCurrentAIPackage : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + Interpreter::Type_Integer value = -1; + if (ptr.getClass().isActor()) { - MWWorld::Ptr ptr = R()(runtime); - - const auto value = static_cast(ptr.getClass().getCreatureStats (ptr).getAiSequence().getLastRunTypeId()); - - runtime.push (value); + const auto& stats = ptr.getClass().getCreatureStats(ptr); + if (!stats.isDead() || !stats.isDeathAnimationFinished()) + { + value = static_cast(stats.getAiSequence().getLastRunTypeId()); + } } + + runtime.push(value); + } }; - template + template class OpGetDetected : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr observer = R()(runtime, false); // required=false - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr observer = R()(runtime, false); // required=false + ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false); - MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false); + Interpreter::Type_Integer value = 0; + if (!actor.isEmpty()) + value = MWBase::Environment::get().getMechanicsManager()->isActorDetected(actor, observer); - Interpreter::Type_Integer value = 0; - if (!actor.isEmpty()) - value = MWBase::Environment::get().getMechanicsManager()->isActorDetected(actor, observer); - - runtime.push (value); - } + runtime.push(value); + } }; - template + template class OpGetLineOfSight : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { - void execute (Interpreter::Runtime& runtime) override + MWWorld::Ptr source = R()(runtime); + + ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false); + bool value = false; + if (!dest.isEmpty() && source.getClass().isActor() && dest.getClass().isActor()) { - - MWWorld::Ptr source = R()(runtime); - - std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - - MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false); - bool value = false; - if (!dest.isEmpty() && source.getClass().isActor() && dest.getClass().isActor()) - { - value = MWBase::Environment::get().getWorld()->getLOS(source,dest); - } - runtime.push (value); + value = MWBase::Environment::get().getWorld()->getLOS(source, dest); } + runtime.push(value); + } }; - template + template class OpGetTarget : public Interpreter::Opcode0 { - public: - void execute (Interpreter::Runtime &runtime) override + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr actor = R()(runtime); + ESM::RefId testedTargetId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + bool targetsAreEqual = false; + if (actor.getClass().isActor()) { - MWWorld::Ptr actor = R()(runtime); - std::string testedTargetId = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); - - bool targetsAreEqual = false; MWWorld::Ptr targetPtr; - if (creatureStats.getAiSequence().getCombatTarget (targetPtr)) + if (creatureStats.getAiSequence().getCombatTarget(targetPtr)) { if (!targetPtr.isEmpty() && targetPtr.getCellRef().getRefId() == testedTargetId) targetsAreEqual = true; } - else if (testedTargetId == "player") // Currently the player ID is hardcoded + else if (testedTargetId == "Player") // Currently the player ID is hardcoded { MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); bool greeting = mechMgr->getGreetingState(actor) == MWMechanics::Greet_InProgress; bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor); targetsAreEqual = (greeting && sayActive) || mechMgr->isTurningToPlayer(actor); } - runtime.push(int(targetsAreEqual)); } + runtime.push(targetsAreEqual); + } }; - template + template class OpStartCombat : public Interpreter::Opcode0 { - public: - void execute (Interpreter::Runtime &runtime) override - { - MWWorld::Ptr actor = R()(runtime); - std::string targetID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr actor = R()(runtime); + ESM::RefId targetID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); +<<<<<<< HEAD MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); /* @@ -554,30 +633,36 @@ namespace MWScript End of tes3mp addition */ } +======= + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); + if (!target.isEmpty()) + MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; - template + template class OpStopCombat : public Interpreter::Opcode0 { - public: - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr actor = R()(runtime); - MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); - creatureStats.getAiSequence().stopCombat(); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr actor = R()(runtime); + if (!actor.getClass().isActor()) + return; + MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); + } }; class OpToggleAI : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getMechanicsManager()->toggleAI(); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = MWBase::Environment::get().getMechanicsManager()->toggleAI(); - - runtime.getContext().report (enabled ? "AI -> On" : "AI -> Off"); - } + runtime.getContext().report(enabled ? "AI -> On" : "AI -> Off"); + } }; template @@ -594,74 +679,101 @@ namespace MWScript Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); + if (!actor.getClass().isActor() || actor == MWMechanics::getPlayer()) + return; + MWMechanics::AiFace facePackage(x, y); actor.getClass().getCreatureStats(actor).getAiSequence().stack(facePackage, actor); } }; - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment3 (Compiler::Ai::opcodeAIActivate, new OpAiActivate); - interpreter.installSegment3 (Compiler::Ai::opcodeAIActivateExplicit, new OpAiActivate); - interpreter.installSegment3 (Compiler::Ai::opcodeAiTravel, new OpAiTravel); - interpreter.installSegment3 (Compiler::Ai::opcodeAiTravelExplicit, new OpAiTravel); - interpreter.installSegment3 (Compiler::Ai::opcodeAiEscort, new OpAiEscort); - interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortExplicit, new OpAiEscort); - interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortCell, new OpAiEscortCell); - interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortCellExplicit, new OpAiEscortCell); - interpreter.installSegment3 (Compiler::Ai::opcodeAiWander, new OpAiWander); - interpreter.installSegment3 (Compiler::Ai::opcodeAiWanderExplicit, new OpAiWander); - interpreter.installSegment3 (Compiler::Ai::opcodeAiFollow, new OpAiFollow); - interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowExplicit, new OpAiFollow); - interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowCell, new OpAiFollowCell); - interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowCellExplicit, new OpAiFollowCell); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAiPackageDone, new OpGetAiPackageDone); + interpreter.installSegment3>(Compiler::Ai::opcodeAIActivate); + interpreter.installSegment3>(Compiler::Ai::opcodeAIActivateExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiTravel); + interpreter.installSegment3>(Compiler::Ai::opcodeAiTravelExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiEscort); + interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortCell); + interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortCellExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiWander); + interpreter.installSegment3>(Compiler::Ai::opcodeAiWanderExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiFollow); + interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowCell); + interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowCellExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetAiPackageDone); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAiPackageDoneExplicit, - new OpGetAiPackageDone); - interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage); - interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); - interpreter.installSegment5 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); - interpreter.installSegment5 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); - interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); - interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); - interpreter.installSegment5 (Compiler::Ai::opcodeGetTarget, new OpGetTarget); - interpreter.installSegment5 (Compiler::Ai::opcodeGetTargetExplicit, new OpGetTarget); - interpreter.installSegment5 (Compiler::Ai::opcodeStartCombat, new OpStartCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeStartCombatExplicit, new OpStartCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeStopCombat, new OpStopCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeStopCombatExplicit, new OpStopCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); + interpreter.installSegment5>(Compiler::Ai::opcodeGetAiPackageDoneExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetCurrentAiPackage); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetCurrentAiPackageExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetDetected); + interpreter.installSegment5>(Compiler::Ai::opcodeGetDetectedExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetLineOfSight); + interpreter.installSegment5>(Compiler::Ai::opcodeGetLineOfSightExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetTarget); + interpreter.installSegment5>(Compiler::Ai::opcodeGetTargetExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeStartCombat); + interpreter.installSegment5>(Compiler::Ai::opcodeStartCombatExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeStopCombat); + interpreter.installSegment5>(Compiler::Ai::opcodeStopCombatExplicit); + interpreter.installSegment5(Compiler::Ai::opcodeToggleAI); - interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFightExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFlee, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFleeExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarm, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarmExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); + interpreter.installSegment5>( + Compiler::Ai::opcodeSetHello, MWMechanics::AiSetting::Hello); + interpreter.installSegment5>( + Compiler::Ai::opcodeSetHelloExplicit, MWMechanics::AiSetting::Hello); + interpreter.installSegment5>( + Compiler::Ai::opcodeSetFight, MWMechanics::AiSetting::Fight); + interpreter.installSegment5>( + Compiler::Ai::opcodeSetFightExplicit, MWMechanics::AiSetting::Fight); + interpreter.installSegment5>( + Compiler::Ai::opcodeSetFlee, MWMechanics::AiSetting::Flee); + interpreter.installSegment5>( + Compiler::Ai::opcodeSetFleeExplicit, MWMechanics::AiSetting::Flee); + interpreter.installSegment5>( + Compiler::Ai::opcodeSetAlarm, MWMechanics::AiSetting::Alarm); + interpreter.installSegment5>( + Compiler::Ai::opcodeSetAlarmExplicit, MWMechanics::AiSetting::Alarm); - interpreter.installSegment5 (Compiler::Ai::opcodeModHello, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeModHelloExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFight, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFightExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFlee, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFleeExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeModAlarm, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - interpreter.installSegment5 (Compiler::Ai::opcodeModAlarmExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); + interpreter.installSegment5>( + Compiler::Ai::opcodeModHello, MWMechanics::AiSetting::Hello); + interpreter.installSegment5>( + Compiler::Ai::opcodeModHelloExplicit, MWMechanics::AiSetting::Hello); + interpreter.installSegment5>( + Compiler::Ai::opcodeModFight, MWMechanics::AiSetting::Fight); + interpreter.installSegment5>( + Compiler::Ai::opcodeModFightExplicit, MWMechanics::AiSetting::Fight); + interpreter.installSegment5>( + Compiler::Ai::opcodeModFlee, MWMechanics::AiSetting::Flee); + interpreter.installSegment5>( + Compiler::Ai::opcodeModFleeExplicit, MWMechanics::AiSetting::Flee); + interpreter.installSegment5>( + Compiler::Ai::opcodeModAlarm, MWMechanics::AiSetting::Alarm); + interpreter.installSegment5>( + Compiler::Ai::opcodeModAlarmExplicit, MWMechanics::AiSetting::Alarm); - interpreter.installSegment5 (Compiler::Ai::opcodeGetHello, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetHelloExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFight, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFightExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFlee, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetHello, MWMechanics::AiSetting::Hello); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetHelloExplicit, MWMechanics::AiSetting::Hello); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetFight, MWMechanics::AiSetting::Fight); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetFightExplicit, MWMechanics::AiSetting::Fight); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetFlee, MWMechanics::AiSetting::Flee); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetFleeExplicit, MWMechanics::AiSetting::Flee); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetAlarm, MWMechanics::AiSetting::Alarm); + interpreter.installSegment5>( + Compiler::Ai::opcodeGetAlarmExplicit, MWMechanics::AiSetting::Alarm); - interpreter.installSegment5 (Compiler::Ai::opcodeFace, new OpFace); - interpreter.installSegment5 (Compiler::Ai::opcodeFaceExplicit, new OpFace); + interpreter.installSegment5>(Compiler::Ai::opcodeFace); + interpreter.installSegment5>(Compiler::Ai::opcodeFaceExplicit); } } } diff --git a/apps/openmw/mwscript/aiextensions.hpp b/apps/openmw/mwscript/aiextensions.hpp index e9e36113c..5fd0db7d2 100644 --- a/apps/openmw/mwscript/aiextensions.hpp +++ b/apps/openmw/mwscript/aiextensions.hpp @@ -16,7 +16,7 @@ namespace MWScript /// \brief AI-related script functionality namespace Ai { - void installOpcodes (Interpreter::Interpreter& interpreter); + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 57c0fe371..e3f0052d6 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -1,7 +1,7 @@ #include "animationextensions.hpp" -#include #include +#include /* Start of tes3mp addition @@ -22,49 +22,56 @@ #include #include -#include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Animation { - template + template class OpSkipAnim : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - MWBase::Environment::get().getMechanicsManager()->skipAnimation (ptr); - } + MWBase::Environment::get().getMechanicsManager()->skipAnimation(ptr); + } }; - template + template class OpPlayAnim : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + if (!ptr.getRefData().isEnabled()) + return; + + std::string_view group = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Integer mode = 0; + + if (arg0 == 1) { - MWWorld::Ptr ptr = R()(runtime); - - if (!ptr.getRefData().isEnabled()) - return; - - std::string group = runtime.getStringLiteral (runtime[0].mInteger); + mode = runtime[0].mInteger; runtime.pop(); - Interpreter::Type_Integer mode = 0; + if (mode < 0 || mode > 2) + throw std::runtime_error("animation mode out of range"); + } +<<<<<<< HEAD if (arg0==1) { mode = runtime[0].mInteger; @@ -96,53 +103,56 @@ namespace MWScript MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, std::numeric_limits::max(), true); } +======= + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup( + ptr, group, mode, std::numeric_limits::max(), true); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; - template + template class OpLoopAnim : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + if (!ptr.getRefData().isEnabled()) + return; + + std::string_view group = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + Interpreter::Type_Integer loops = runtime[0].mInteger; + runtime.pop(); + + if (loops < 0) + throw std::runtime_error("number of animation loops must be non-negative"); + + Interpreter::Type_Integer mode = 0; + + if (arg0 == 1) { - MWWorld::Ptr ptr = R()(runtime); - - if (!ptr.getRefData().isEnabled()) - return; - - std::string group = runtime.getStringLiteral (runtime[0].mInteger); + mode = runtime[0].mInteger; runtime.pop(); - Interpreter::Type_Integer loops = runtime[0].mInteger; - runtime.pop(); + if (mode < 0 || mode > 2) + throw std::runtime_error("animation mode out of range"); + } - if (loops<0) - throw std::runtime_error ("number of animation loops must be non-negative"); - - Interpreter::Type_Integer mode = 0; - - if (arg0==1) - { - mode = runtime[0].mInteger; - runtime.pop(); - - if (mode<0 || mode>2) - throw std::runtime_error ("animation mode out of range"); - } - - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, loops + 1, true); - } + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(ptr, group, mode, loops + 1, true); + } }; - - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Animation::opcodeSkipAnim, new OpSkipAnim); - interpreter.installSegment5 (Compiler::Animation::opcodeSkipAnimExplicit, new OpSkipAnim); - interpreter.installSegment3 (Compiler::Animation::opcodePlayAnim, new OpPlayAnim); - interpreter.installSegment3 (Compiler::Animation::opcodePlayAnimExplicit, new OpPlayAnim); - interpreter.installSegment3 (Compiler::Animation::opcodeLoopAnim, new OpLoopAnim); - interpreter.installSegment3 (Compiler::Animation::opcodeLoopAnimExplicit, new OpLoopAnim); + interpreter.installSegment5>(Compiler::Animation::opcodeSkipAnim); + interpreter.installSegment5>(Compiler::Animation::opcodeSkipAnimExplicit); + interpreter.installSegment3>(Compiler::Animation::opcodePlayAnim); + interpreter.installSegment3>(Compiler::Animation::opcodePlayAnimExplicit); + interpreter.installSegment3>(Compiler::Animation::opcodeLoopAnim); + interpreter.installSegment3>(Compiler::Animation::opcodeLoopAnimExplicit); } } } diff --git a/apps/openmw/mwscript/animationextensions.hpp b/apps/openmw/mwscript/animationextensions.hpp index ff619ab73..a6805f7ee 100644 --- a/apps/openmw/mwscript/animationextensions.hpp +++ b/apps/openmw/mwscript/animationextensions.hpp @@ -15,9 +15,9 @@ namespace MWScript { namespace Animation { - void registerExtensions (Compiler::Extensions& extensions); + void registerExtensions(Compiler::Extensions& extensions); - void installOpcodes (Interpreter::Interpreter& interpreter); + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index b0977984f..147b9a5d8 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -7,15 +7,16 @@ #include #include -#include #include +#include -#include "../mwworld/actionteleport.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/actionteleport.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/scene.hpp" #include "../mwmechanics/actorutil.hpp" @@ -27,239 +28,231 @@ namespace MWScript { class OpCellChanged : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push (MWBase::Environment::get().getWorld()->hasCellChanged() ? 1 : 0); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWorldScene()->hasCellChanged() ? 1 : 0); + } }; class OpTestCells : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override + public: + void execute(Interpreter::Runtime& runtime) override + { + if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) { - if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) - { - runtime.getContext().report("Use TestCells from the main menu, when there is no active game session."); - return; - } - - bool wasConsole = MWBase::Environment::get().getWindowManager()->isConsoleMode(); - if (wasConsole) - MWBase::Environment::get().getWindowManager()->toggleConsole(); - - MWBase::Environment::get().getWorld()->testExteriorCells(); - - if (wasConsole) - MWBase::Environment::get().getWindowManager()->toggleConsole(); + runtime.getContext().report( + "Use TestCells from the main menu, when there is no active game session."); + return; } + + bool wasConsole = MWBase::Environment::get().getWindowManager()->isConsoleMode(); + if (wasConsole) + MWBase::Environment::get().getWindowManager()->toggleConsole(); + + MWBase::Environment::get().getWorldScene()->testExteriorCells(); + + if (wasConsole) + MWBase::Environment::get().getWindowManager()->toggleConsole(); + } }; class OpTestInteriorCells : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override + public: + void execute(Interpreter::Runtime& runtime) override + { + if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) { - if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) - { - runtime.getContext().report("Use TestInteriorCells from the main menu, when there is no active game session."); - return; - } - - bool wasConsole = MWBase::Environment::get().getWindowManager()->isConsoleMode(); - if (wasConsole) - MWBase::Environment::get().getWindowManager()->toggleConsole(); - - MWBase::Environment::get().getWorld()->testInteriorCells(); - - if (wasConsole) - MWBase::Environment::get().getWindowManager()->toggleConsole(); + runtime.getContext().report( + "Use TestInteriorCells from the main menu, when there is no active game session."); + return; } + + bool wasConsole = MWBase::Environment::get().getWindowManager()->isConsoleMode(); + if (wasConsole) + MWBase::Environment::get().getWindowManager()->toggleConsole(); + + MWBase::Environment::get().getWorldScene()->testInteriorCells(); + + if (wasConsole) + MWBase::Environment::get().getWindowManager()->toggleConsole(); + } }; class OpCOC : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + std::string_view cell = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + ESM::Position pos; + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Ptr playerPtr = world->getPlayerPtr(); + + if (const ESM::RefId refId = world->findExteriorPosition(cell, pos); !refId.empty()) { - std::string cell = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - ESM::Position pos; - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Ptr playerPtr = world->getPlayerPtr(); - - if (world->findExteriorPosition(cell, pos)) - { - MWWorld::ActionTeleport("", pos, false).execute(playerPtr); - world->adjustPosition(playerPtr, false); - } - else - { - // Change to interior even if findInteriorPosition() - // yields false. In this case position will be zero-point. - world->findInteriorPosition(cell, pos); - MWWorld::ActionTeleport(cell, pos, false).execute(playerPtr); - } + MWWorld::ActionTeleport(refId, pos, false).execute(playerPtr); + world->adjustPosition(playerPtr, false); + return; } + if (const ESM::RefId refId = world->findInteriorPosition(cell, pos); !refId.empty()) + { + MWWorld::ActionTeleport(refId, pos, false).execute(playerPtr); + return; + } + throw std::runtime_error("Cell " + std::string(cell) + " is not found"); + } }; class OpCOE : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + Interpreter::Type_Integer x = runtime[0].mInteger; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - Interpreter::Type_Integer x = runtime[0].mInteger; - runtime.pop(); + Interpreter::Type_Integer y = runtime[0].mInteger; + runtime.pop(); - Interpreter::Type_Integer y = runtime[0].mInteger; - runtime.pop(); + ESM::Position pos; + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Ptr playerPtr = world->getPlayerPtr(); - ESM::Position pos; - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Ptr playerPtr = world->getPlayerPtr(); + osg::Vec2 posFromIndex + = ESM::indexToPosition(ESM::ExteriorCellLocation(x, y, ESM::Cell::sDefaultWorldspaceId), true); + pos.pos[0] = posFromIndex.x(); + pos.pos[1] = posFromIndex.y(); + pos.pos[2] = 0; - world->indexToPosition (x, y, pos.pos[0], pos.pos[1], true); - pos.pos[2] = 0; + pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; - pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; - - MWWorld::ActionTeleport("", pos, false).execute(playerPtr); - world->adjustPosition(playerPtr, false); - } + MWWorld::ActionTeleport(ESM::RefId::esm3ExteriorCell(x, y), pos, false).execute(playerPtr); + world->adjustPosition(playerPtr, false); + } }; class OpGetInterior : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override + public: + void execute(Interpreter::Runtime& runtime) override + { + if (!MWMechanics::getPlayer().isInCell()) { - if (!MWMechanics::getPlayer().isInCell()) - { - runtime.push (0); - return; - } - - bool interior = - !MWMechanics::getPlayer().getCell()->getCell()->isExterior(); - - runtime.push (interior ? 1 : 0); + runtime.push(0); + return; } + + bool interior = !MWMechanics::getPlayer().getCell()->getCell()->isExterior(); + + runtime.push(interior ? 1 : 0); + } }; class OpGetPCCell : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + std::string_view name = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + if (!MWMechanics::getPlayer().isInCell()) { - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - if (!MWMechanics::getPlayer().isInCell()) - { - runtime.push(0); - return; - } - const MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); - - std::string current = MWBase::Environment::get().getWorld()->getCellName(cell); - Misc::StringUtils::lowerCaseInPlace(current); - - bool match = current.length()>=name.length() && - current.substr (0, name.length())==name; - - runtime.push (match ? 1 : 0); + runtime.push(0); + return; } + const MWWorld::CellStore* cell = MWMechanics::getPlayer().getCell(); + + std::string_view current = MWBase::Environment::get().getWorld()->getCellName(cell); + bool match = Misc::StringUtils::ciCompareLen(name, current, name.length()) == 0; + + runtime.push(match ? 1 : 0); + } }; class OpGetWaterLevel : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override + public: + void execute(Interpreter::Runtime& runtime) override + { + if (!MWMechanics::getPlayer().isInCell()) { - if (!MWMechanics::getPlayer().isInCell()) - { - runtime.push(0.f); - return; - } - MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); - if (cell->isExterior()) - runtime.push(0.f); // vanilla oddity, return 0 even though water is actually at -1 - else if (cell->getCell()->hasWater()) - runtime.push (cell->getWaterLevel()); - else - runtime.push (-std::numeric_limits::max()); + runtime.push(0.f); + return; } + MWWorld::CellStore* cell = MWMechanics::getPlayer().getCell(); + if (cell->isExterior()) + runtime.push(0.f); // vanilla oddity, return 0 even though water is actually at -1 + else if (cell->getCell()->hasWater()) + runtime.push(cell->getWaterLevel()); + else + runtime.push(-std::numeric_limits::max()); + } }; class OpSetWaterLevel : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + Interpreter::Type_Float level = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + if (!MWMechanics::getPlayer().isInCell()) { - Interpreter::Type_Float level = runtime[0].mFloat; - - if (!MWMechanics::getPlayer().isInCell()) - { - return; - } - - MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); - - if (cell->getCell()->isExterior()) - throw std::runtime_error("Can't set water level in exterior cell"); - - cell->setWaterLevel (level); - MWBase::Environment::get().getWorld()->setWaterHeight (cell->getWaterLevel()); + return; } + + MWWorld::CellStore* cell = MWMechanics::getPlayer().getCell(); + + if (cell->getCell()->isExterior()) + throw std::runtime_error("Can't set water level in exterior cell"); + + cell->setWaterLevel(level); + MWBase::Environment::get().getWorld()->setWaterHeight(cell->getWaterLevel()); + } }; class OpModWaterLevel : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + Interpreter::Type_Float level = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + if (!MWMechanics::getPlayer().isInCell()) { - Interpreter::Type_Float level = runtime[0].mFloat; - - if (!MWMechanics::getPlayer().isInCell()) - { - return; - } - - MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); - - if (cell->getCell()->isExterior()) - throw std::runtime_error("Can't set water level in exterior cell"); - - cell->setWaterLevel (cell->getWaterLevel()+level); - MWBase::Environment::get().getWorld()->setWaterHeight(cell->getWaterLevel()); + return; } + + MWWorld::CellStore* cell = MWMechanics::getPlayer().getCell(); + + if (cell->getCell()->isExterior()) + throw std::runtime_error("Can't set water level in exterior cell"); + + cell->setWaterLevel(cell->getWaterLevel() + level); + MWBase::Environment::get().getWorld()->setWaterHeight(cell->getWaterLevel()); + } }; - - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Cell::opcodeCellChanged, new OpCellChanged); - interpreter.installSegment5 (Compiler::Cell::opcodeTestCells, new OpTestCells); - interpreter.installSegment5 (Compiler::Cell::opcodeTestInteriorCells, new OpTestInteriorCells); - interpreter.installSegment5 (Compiler::Cell::opcodeCOC, new OpCOC); - interpreter.installSegment5 (Compiler::Cell::opcodeCOE, new OpCOE); - interpreter.installSegment5 (Compiler::Cell::opcodeGetInterior, new OpGetInterior); - interpreter.installSegment5 (Compiler::Cell::opcodeGetPCCell, new OpGetPCCell); - interpreter.installSegment5 (Compiler::Cell::opcodeGetWaterLevel, new OpGetWaterLevel); - interpreter.installSegment5 (Compiler::Cell::opcodeSetWaterLevel, new OpSetWaterLevel); - interpreter.installSegment5 (Compiler::Cell::opcodeModWaterLevel, new OpModWaterLevel); + interpreter.installSegment5(Compiler::Cell::opcodeCellChanged); + interpreter.installSegment5(Compiler::Cell::opcodeTestCells); + interpreter.installSegment5(Compiler::Cell::opcodeTestInteriorCells); + interpreter.installSegment5(Compiler::Cell::opcodeCOC); + interpreter.installSegment5(Compiler::Cell::opcodeCOE); + interpreter.installSegment5(Compiler::Cell::opcodeGetInterior); + interpreter.installSegment5(Compiler::Cell::opcodeGetPCCell); + interpreter.installSegment5(Compiler::Cell::opcodeGetWaterLevel); + interpreter.installSegment5(Compiler::Cell::opcodeSetWaterLevel); + interpreter.installSegment5(Compiler::Cell::opcodeModWaterLevel); } } } diff --git a/apps/openmw/mwscript/cellextensions.hpp b/apps/openmw/mwscript/cellextensions.hpp index 0891cb9dc..d3b8bc00b 100644 --- a/apps/openmw/mwscript/cellextensions.hpp +++ b/apps/openmw/mwscript/cellextensions.hpp @@ -15,11 +15,9 @@ namespace MWScript { /// \brief cell-related script functionality namespace Cell - { - void installOpcodes (Interpreter::Interpreter& interpreter); + { + void installOpcodes(Interpreter::Interpreter& interpreter); } } #endif - - diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 4a7038e1c..eb39f5762 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -2,98 +2,94 @@ #include "../mwworld/esmstore.hpp" -#include - #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/ptr.hpp" namespace MWScript { - CompilerContext::CompilerContext (Type type) - : mType (type) - {} + CompilerContext::CompilerContext(Type type) + : mType(type) + { + } bool CompilerContext::canDeclareLocals() const { - return mType==Type_Full; + return mType == Type_Full; } - char CompilerContext::getGlobalType (const std::string& name) const + char CompilerContext::getGlobalType(const std::string& name) const { - return MWBase::Environment::get().getWorld()->getGlobalVariableType (name); + return MWBase::Environment::get().getWorld()->getGlobalVariableType(name); } - std::pair CompilerContext::getMemberType (const std::string& name, - const std::string& id) const + std::pair CompilerContext::getMemberType(const std::string& name, const ESM::RefId& id) const { - std::string script; + ESM::RefId script; bool reference = false; - if (const ESM::Script *scriptRecord = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) + if (const ESM::Script* scriptRecord = MWBase::Environment::get().getESMStore()->get().search(id)) { script = scriptRecord->mId; } else { - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id); + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), id); - script = ref.getPtr().getClass().getScript (ref.getPtr()); + script = ref.getPtr().getClass().getScript(ref.getPtr()); reference = true; } char type = ' '; if (!script.empty()) - type = MWBase::Environment::get().getScriptManager()->getLocals (script).getType ( - Misc::StringUtils::lowerCase (name)); + type = MWBase::Environment::get().getScriptManager()->getLocals(script).getType( + Misc::StringUtils::lowerCase(name)); - return std::make_pair (type, reference); + return std::make_pair(type, reference); } - bool CompilerContext::isId (const std::string& name) const + bool CompilerContext::isId(const ESM::RefId& name) const { - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - return - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name) || - store.get().search (name); - } - - bool CompilerContext::isJournalId (const std::string& name) const - { - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); - - const ESM::Dialogue *topic = store.get().search (name); - - return topic && topic->mType==ESM::Dialogue::Journal; + return store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name) || store.get().search(name) + || store.get().search(name); } } diff --git a/apps/openmw/mwscript/compilercontext.hpp b/apps/openmw/mwscript/compilercontext.hpp index 00b10ea06..eaf794853 100644 --- a/apps/openmw/mwscript/compilercontext.hpp +++ b/apps/openmw/mwscript/compilercontext.hpp @@ -3,45 +3,43 @@ #include +namespace ESM +{ + class RefId; +} + namespace MWScript { class CompilerContext : public Compiler::Context { - public: + public: + enum Type + { + Type_Full, // global, local, targeted + Type_Dialogue, + Type_Console + }; - enum Type - { - Type_Full, // global, local, targeted - Type_Dialogue, - Type_Console - }; + private: + Type mType; - private: + public: + CompilerContext(Type type); - Type mType; + /// Is the compiler allowed to declare local variables? + bool canDeclareLocals() const override; - public: + /// 'l: long, 's': short, 'f': float, ' ': does not exist. + char getGlobalType(const std::string& name) const override; - CompilerContext (Type type); + std::pair getMemberType(const std::string& name, const ESM::RefId& id) const override; + ///< Return type of member variable \a name in script \a id or in script of reference of + /// \a id + /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist. + /// second: true: script of reference - /// Is the compiler allowed to declare local variables? - bool canDeclareLocals() const override; - - /// 'l: long, 's': short, 'f': float, ' ': does not exist. - char getGlobalType (const std::string& name) const override; - - std::pair getMemberType (const std::string& name, - const std::string& id) const override; - ///< Return type of member variable \a name in script \a id or in script of reference of - /// \a id - /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist. - /// second: true: script of reference - - bool isId (const std::string& name) const override; - ///< Does \a name match an ID, that can be referenced? - - bool isJournalId (const std::string& name) const override; - ///< Does \a name match a journal ID? + bool isId(const ESM::RefId& name) const override; + ///< Does \a name match an ID, that can be referenced? }; } diff --git a/apps/openmw/mwscript/consoleextensions.cpp b/apps/openmw/mwscript/consoleextensions.cpp index 749ec8096..08a13e43a 100644 --- a/apps/openmw/mwscript/consoleextensions.cpp +++ b/apps/openmw/mwscript/consoleextensions.cpp @@ -6,9 +6,6 @@ namespace MWScript { namespace Console { - void installOpcodes (Interpreter::Interpreter& interpreter) - { - - } + void installOpcodes(Interpreter::Interpreter& interpreter) {} } } diff --git a/apps/openmw/mwscript/consoleextensions.hpp b/apps/openmw/mwscript/consoleextensions.hpp index 5571a5469..828557271 100644 --- a/apps/openmw/mwscript/consoleextensions.hpp +++ b/apps/openmw/mwscript/consoleextensions.hpp @@ -16,7 +16,7 @@ namespace MWScript /// \brief Script functionality limited to the console namespace Console { - void installOpcodes (Interpreter::Interpreter& interpreter); + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 506bee6ca..780a1106c 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -27,19 +27,20 @@ #include #include -#include +#include -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwclass/container.hpp" - #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" @@ -50,42 +51,44 @@ namespace { - void addToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::Ptr& ptr, MWWorld::ContainerStore& store, bool resolve = true) + void addToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::ContainerStore& store, bool resolve = true) { if (itemPtr.getClass().getScript(itemPtr).empty()) { - store.add (itemPtr, count, ptr, true, resolve); + store.add(itemPtr, count, true, resolve); } else { // Adding just one item per time to make sure there isn't a stack of scripted items for (int i = 0; i < count; i++) - store.add (itemPtr, 1, ptr, true, resolve); + store.add(itemPtr, 1, true, resolve); } } - void addRandomToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::Ptr& owner, MWWorld::ContainerStore& store, bool topLevel = true) + void addRandomToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::ContainerStore& store, bool topLevel = true) { - if(itemPtr.getTypeName() == typeid(ESM::ItemLevList).name()) + if (itemPtr.getType() == ESM::ItemLevList::sRecordId) { const ESM::ItemLevList* levItemList = itemPtr.get()->mBase; - if(topLevel && count > 1 && levItemList->mFlags & ESM::ItemLevList::Each) + if (topLevel && count > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { - for(int i = 0; i < count; i++) - addRandomToStore(itemPtr, 1, owner, store, true); + for (int i = 0; i < count; i++) + addRandomToStore(itemPtr, 1, store, true); } else { - std::string itemId = MWMechanics::getLevelledItem(itemPtr.get()->mBase, false); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::RefId& itemId + = MWMechanics::getLevelledItem(itemPtr.get()->mBase, false, prng); if (itemId.empty()) return; - MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), itemId, 1); - addRandomToStore(manualRef.getPtr(), count, owner, store, false); + MWWorld::ManualRef manualRef(*MWBase::Environment::get().getESMStore(), itemId, 1); + addRandomToStore(manualRef.getPtr(), count, store, false); } } else - addToStore(itemPtr, count, owner, store); + addToStore(itemPtr, count, store); } } @@ -93,13 +96,42 @@ namespace MWScript { namespace Container { - template + template class OpAddItem : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + Interpreter::Type_Integer count = runtime[0].mInteger; + runtime.pop(); + + if (count < 0) + count = static_cast(count); + + // no-op + if (count == 0) + return; + + if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") + item = ESM::RefId::stringRefId("gold_001"); + + // Check if "item" can be placed in a container + MWWorld::ManualRef manualRef(*MWBase::Environment::get().getESMStore(), item, 1); + MWWorld::Ptr itemPtr = manualRef.getPtr(); + bool isLevelledList = itemPtr.getClass().getType() == ESM::ItemLevList::sRecordId; + if (!isLevelledList) + MWWorld::ContainerStore::getType(itemPtr); + + // Explicit calls to non-unique actors affect the base record + if (!R::implicit && ptr.getClass().isActor() + && MWBase::Environment::get().getESMStore()->getRefCount(ptr.getCellRef().getRefId()) > 1) { +<<<<<<< HEAD MWWorld::Ptr ptr = R()(runtime); std::string item = runtime.getStringLiteral (runtime[0].mInteger); @@ -226,99 +258,145 @@ namespace MWScript /* End of tes3mp addition */ +======= + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count); + return; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + + // Calls to unresolved containers affect the base record + if (ptr.getClass().getType() == ESM::Container::sRecordId + && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) + { + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count); + const ESM::Container* baseRecord + = MWBase::Environment::get().getESMStore()->get().find( + ptr.getCellRef().getRefId()); + const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); + for (const auto& container : ptrs) + { + // use the new base record + container.get()->mBase = baseRecord; + if (container.getRefData().getCustomData()) + { + auto& store = container.getClass().getContainerStore(container); + if (isLevelledList) + { + if (store.isResolved()) + { + addRandomToStore(itemPtr, count, store); + } + } + else + addToStore(itemPtr, count, store, store.isResolved()); + } + } + return; + } + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + if (isLevelledList) + addRandomToStore(itemPtr, count, store); + else + addToStore(itemPtr, count, store); + + // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + // The two GMST entries below expand to strings informing the player of what, and how many of it has + // been added to their inventory + std::string msgBox; + std::string_view itemName = itemPtr.getClass().getName(itemPtr); + if (count == 1) + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); + msgBox = ::Misc::StringUtils::format(msgBox, itemName); + } + else + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}"); + msgBox = ::Misc::StringUtils::format(msgBox, count, itemName); + } + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); + } + } }; - template + template class OpGetItemCount : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string item = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") + item = ESM::RefId::stringRefId("gold_001"); - if(::Misc::StringUtils::ciEqual(item, "gold_005") - || ::Misc::StringUtils::ciEqual(item, "gold_010") - || ::Misc::StringUtils::ciEqual(item, "gold_025") - || ::Misc::StringUtils::ciEqual(item, "gold_100")) - item = "gold_001"; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); - - runtime.push (store.count(item)); - } + runtime.push(store.count(item)); + } }; - template + template class OpRemoveItem : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + Interpreter::Type_Integer count = runtime[0].mInteger; + runtime.pop(); + + if (count < 0) + count = static_cast(count); + + // no-op + if (count == 0) + return; + + if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") + item = ESM::RefId::stringRefId("gold_001"); + + // Explicit calls to non-unique actors affect the base record + if (!R::implicit && ptr.getClass().isActor() + && MWBase::Environment::get().getESMStore()->getRefCount(ptr.getCellRef().getRefId()) > 1) { - MWWorld::Ptr ptr = R()(runtime); - - std::string item = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - Interpreter::Type_Integer count = runtime[0].mInteger; - runtime.pop(); - - if (count<0) - throw std::runtime_error ("second argument for RemoveItem must be non-negative"); - - // no-op - if (count == 0) - return; - - if(::Misc::StringUtils::ciEqual(item, "gold_005") - || ::Misc::StringUtils::ciEqual(item, "gold_010") - || ::Misc::StringUtils::ciEqual(item, "gold_025") - || ::Misc::StringUtils::ciEqual(item, "gold_100")) - item = "gold_001"; - - // Explicit calls to non-unique actors affect the base record - if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1) + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); + return; + } + // Calls to unresolved containers affect the base record instead + else if (ptr.getClass().getType() == ESM::Container::sRecordId + && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) + { + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); + const ESM::Container* baseRecord + = MWBase::Environment::get().getESMStore()->get().find( + ptr.getCellRef().getRefId()); + const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); + for (const auto& container : ptrs) { - ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); - return; - } - // Calls to unresolved containers affect the base record instead - else if(ptr.getClass().getTypeName() == typeid(ESM::Container).name() && - (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) - { - ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); - const ESM::Container* baseRecord = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getCellRef().getRefId()); - const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); - for(const auto& container : ptrs) + container.get()->mBase = baseRecord; + if (container.getRefData().getCustomData()) { - container.get()->mBase = baseRecord; - if(container.getRefData().getCustomData()) - { - auto& store = container.getClass().getContainerStore(container); - // Note that unlike AddItem, RemoveItem only removes from unresolved containers - if(!store.isResolved()) - store.remove(item, count, ptr, false, false); - } - } - return; - } - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); - - std::string itemName; - for (MWWorld::ConstContainerStoreIterator iter(store.cbegin()); iter != store.cend(); ++iter) - { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) - { - itemName = iter->getClass().getName(*iter); - break; + auto& store = container.getClass().getContainerStore(container); + // Note that unlike AddItem, RemoveItem only removes from unresolved containers + if (!store.isResolved()) + store.remove(item, count, false, false); } } + return; + } + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -355,6 +433,15 @@ namespace MWScript msgBox = ::Misc::StringUtils::format(msgBox, itemName); } MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); +======= + std::string_view itemName; + for (MWWorld::ConstContainerStoreIterator iter(store.cbegin()); iter != store.cend(); ++iter) + { + if (iter->getCellRef().getRefId() == item) + { + itemName = iter->getClass().getName(*iter); + break; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } /* Start of tes3mp addition @@ -382,224 +469,254 @@ namespace MWScript End of tes3mp addition */ } + + int numRemoved = store.remove(item, count); + + // Spawn a messagebox (only for items removed from player's inventory) + if ((numRemoved > 0) && (ptr == MWMechanics::getPlayer())) + { + // The two GMST entries below expand to strings informing the player of what, and how many of it has + // been removed from their inventory + std::string msgBox; + + if (numRemoved > 1) + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); + msgBox = ::Misc::StringUtils::format(msgBox, numRemoved, itemName); + } + else + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}"); + msgBox = ::Misc::StringUtils::format(msgBox, itemName); + } + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); + } + } }; template class OpEquip : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute(Interpreter::Runtime &runtime) override + ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + auto found = invStore.end(); + const auto& store = *MWBase::Environment::get().getESMStore(); + + // With soul gems we prefer filled ones. + for (auto it = invStore.begin(); it != invStore.end(); ++it) { - MWWorld::Ptr ptr = R()(runtime); - - std::string item = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); - MWWorld::ContainerStoreIterator it = invStore.begin(); - for (; it != invStore.end(); ++it) + if (it->getCellRef().getRefId() == item) { - if (::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) + found = it; + const ESM::RefId& soul = it->getCellRef().getSoul(); + if (!it->getClass().isSoulGem(*it) + || (!soul.empty() && store.get().search(soul))) break; } - if (it == invStore.end()) - { - it = ptr.getClass().getContainerStore (ptr).add (item, 1, ptr); - Log(Debug::Warning) << "Implicitly adding one " << item << - " to the inventory store of " << ptr.getCellRef().getRefId() << - " to fulfill the requirements of Equip instruction"; - } - - if (ptr == MWMechanics::getPlayer()) - MWBase::Environment::get().getWindowManager()->useItem(*it, true); - else - { - std::shared_ptr action = it->getClass().use(*it, true); - action->execute(ptr, true); - } } + + if (found == invStore.end()) + { + MWWorld::ManualRef ref(store, item, 1); + found = ptr.getClass().getContainerStore(ptr).add(ref.getPtr(), 1, false); + Log(Debug::Warning) << "Implicitly adding one " << item << " to the inventory store of " + << ptr.getCellRef().getRefId() + << " to fulfill the requirements of Equip instruction"; + } + + if (ptr == MWMechanics::getPlayer()) + MWBase::Environment::get().getWindowManager()->useItem(*found, true); + else + { + std::unique_ptr action = found->getClass().use(*found, true); + action->execute(ptr, true); + } + } }; template class OpGetArmorType : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute(Interpreter::Runtime &runtime) override + Interpreter::Type_Integer location = runtime[0].mInteger; + runtime.pop(); + + int slot; + switch (location) { - MWWorld::Ptr ptr = R()(runtime); + case 0: + slot = MWWorld::InventoryStore::Slot_Helmet; + break; + case 1: + slot = MWWorld::InventoryStore::Slot_Cuirass; + break; + case 2: + slot = MWWorld::InventoryStore::Slot_LeftPauldron; + break; + case 3: + slot = MWWorld::InventoryStore::Slot_RightPauldron; + break; + case 4: + slot = MWWorld::InventoryStore::Slot_Greaves; + break; + case 5: + slot = MWWorld::InventoryStore::Slot_Boots; + break; + case 6: + slot = MWWorld::InventoryStore::Slot_LeftGauntlet; + break; + case 7: + slot = MWWorld::InventoryStore::Slot_RightGauntlet; + break; + case 8: + slot = MWWorld::InventoryStore::Slot_CarriedLeft; // shield + break; + case 9: + slot = MWWorld::InventoryStore::Slot_LeftGauntlet; + break; + case 10: + slot = MWWorld::InventoryStore::Slot_RightGauntlet; + break; + default: + throw std::runtime_error("armor index out of range"); + } - Interpreter::Type_Integer location = runtime[0].mInteger; - runtime.pop(); + const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + MWWorld::ConstContainerStoreIterator it = invStore.getSlot(slot); - int slot; - switch (location) - { - case 0: - slot = MWWorld::InventoryStore::Slot_Helmet; - break; - case 1: - slot = MWWorld::InventoryStore::Slot_Cuirass; - break; - case 2: - slot = MWWorld::InventoryStore::Slot_LeftPauldron; - break; - case 3: - slot = MWWorld::InventoryStore::Slot_RightPauldron; - break; - case 4: - slot = MWWorld::InventoryStore::Slot_Greaves; - break; - case 5: - slot = MWWorld::InventoryStore::Slot_Boots; - break; - case 6: - slot = MWWorld::InventoryStore::Slot_LeftGauntlet; - break; - case 7: - slot = MWWorld::InventoryStore::Slot_RightGauntlet; - break; - case 8: - slot = MWWorld::InventoryStore::Slot_CarriedLeft; // shield - break; - case 9: - slot = MWWorld::InventoryStore::Slot_LeftGauntlet; - break; - case 10: - slot = MWWorld::InventoryStore::Slot_RightGauntlet; - break; - default: - throw std::runtime_error ("armor index out of range"); - } + if (it == invStore.end() || it->getType() != ESM::Armor::sRecordId) + { + runtime.push(-1); + return; + } - const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); - MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot); - - if (it == invStore.end() || it->getTypeName () != typeid(ESM::Armor).name()) - { - runtime.push(-1); - return; - } - - int skill = it->getClass().getEquipmentSkill (*it) ; - if (skill == ESM::Skill::HeavyArmor) - runtime.push(2); - else if (skill == ESM::Skill::MediumArmor) - runtime.push(1); - else if (skill == ESM::Skill::LightArmor) - runtime.push(0); - else - runtime.push(-1); + ESM::RefId skill = it->getClass().getEquipmentSkill(*it); + if (skill == ESM::Skill::HeavyArmor) + runtime.push(2); + else if (skill == ESM::Skill::MediumArmor) + runtime.push(1); + else if (skill == ESM::Skill::LightArmor) + runtime.push(0); + else + runtime.push(-1); } }; template class OpHasItemEquipped : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute(Interpreter::Runtime &runtime) override + ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { - MWWorld::Ptr ptr = R()(runtime); - - std::string item = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + MWWorld::ConstContainerStoreIterator it = invStore.getSlot(slot); + if (it != invStore.end() && it->getCellRef().getRefId() == item) { - MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot); - if (it != invStore.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) - { - runtime.push(1); - return; - } + runtime.push(1); + return; } - runtime.push(0); } + runtime.push(0); + } }; template class OpHasSoulGem : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute(Interpreter::Runtime &runtime) override + ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + int count = 0; + const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + for (MWWorld::ConstContainerStoreIterator it + = invStore.cbegin(MWWorld::ContainerStore::Type_Miscellaneous); + it != invStore.cend(); ++it) { - MWWorld::Ptr ptr = R()(runtime); - - const std::string &name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - int count = 0; - const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(MWWorld::ContainerStore::Type_Miscellaneous); - it != invStore.cend(); ++it) - { - if (::Misc::StringUtils::ciEqual(it->getCellRef().getSoul(), name)) - count += it->getRefData().getCount(); - } - runtime.push(count); + if (it->getCellRef().getSoul() == name) + count += it->getRefData().getCount(); } + runtime.push(count); + } }; template class OpGetWeaponType : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute(Interpreter::Runtime &runtime) override + const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + MWWorld::ConstContainerStoreIterator it = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (it == invStore.end()) { - MWWorld::Ptr ptr = R()(runtime); - - const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); - MWWorld::ConstContainerStoreIterator it = invStore.getSlot (MWWorld::InventoryStore::Slot_CarriedRight); - if (it == invStore.end()) + runtime.push(-1); + return; + } + else if (it->getType() != ESM::Weapon::sRecordId) + { + if (it->getType() == ESM::Lockpick::sRecordId) + { + runtime.push(-2); + } + else if (it->getType() == ESM::Probe::sRecordId) + { + runtime.push(-3); + } + else { runtime.push(-1); - return; } - else if (it->getTypeName() != typeid(ESM::Weapon).name()) - { - if (it->getTypeName() == typeid(ESM::Lockpick).name()) - { - runtime.push(-2); - } - else if (it->getTypeName() == typeid(ESM::Probe).name()) - { - runtime.push(-3); - } - else - { - runtime.push(-1); - } - return; - } - - runtime.push(it->get()->mBase->mData.mType); + return; } + + runtime.push(it->get()->mBase->mData.mType); + } }; - - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Container::opcodeAddItem, new OpAddItem); - interpreter.installSegment5 (Compiler::Container::opcodeAddItemExplicit, new OpAddItem); - interpreter.installSegment5 (Compiler::Container::opcodeGetItemCount, new OpGetItemCount); - interpreter.installSegment5 (Compiler::Container::opcodeGetItemCountExplicit, new OpGetItemCount); - interpreter.installSegment5 (Compiler::Container::opcodeRemoveItem, new OpRemoveItem); - interpreter.installSegment5 (Compiler::Container::opcodeRemoveItemExplicit, new OpRemoveItem); - interpreter.installSegment5 (Compiler::Container::opcodeEquip, new OpEquip); - interpreter.installSegment5 (Compiler::Container::opcodeEquipExplicit, new OpEquip); - interpreter.installSegment5 (Compiler::Container::opcodeGetArmorType, new OpGetArmorType); - interpreter.installSegment5 (Compiler::Container::opcodeGetArmorTypeExplicit, new OpGetArmorType); - interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquipped, new OpHasItemEquipped); - interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquippedExplicit, new OpHasItemEquipped); - interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGem, new OpHasSoulGem); - interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGemExplicit, new OpHasSoulGem); - interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponType, new OpGetWeaponType); - interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponTypeExplicit, new OpGetWeaponType); + interpreter.installSegment5>(Compiler::Container::opcodeAddItem); + interpreter.installSegment5>(Compiler::Container::opcodeAddItemExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeGetItemCount); + interpreter.installSegment5>(Compiler::Container::opcodeGetItemCountExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeRemoveItem); + interpreter.installSegment5>(Compiler::Container::opcodeRemoveItemExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeEquip); + interpreter.installSegment5>(Compiler::Container::opcodeEquipExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeGetArmorType); + interpreter.installSegment5>(Compiler::Container::opcodeGetArmorTypeExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeHasItemEquipped); + interpreter.installSegment5>( + Compiler::Container::opcodeHasItemEquippedExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeHasSoulGem); + interpreter.installSegment5>(Compiler::Container::opcodeHasSoulGemExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeGetWeaponType); + interpreter.installSegment5>(Compiler::Container::opcodeGetWeaponTypeExplicit); } } } diff --git a/apps/openmw/mwscript/containerextensions.hpp b/apps/openmw/mwscript/containerextensions.hpp index d5be8fb2a..c6b2434f3 100644 --- a/apps/openmw/mwscript/containerextensions.hpp +++ b/apps/openmw/mwscript/containerextensions.hpp @@ -16,7 +16,7 @@ namespace MWScript /// \brief Container-related script functionality (chests, NPCs, creatures) namespace Container { - void installOpcodes (Interpreter::Interpreter& interpreter); + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index b740438d4..3919f4f7b 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -3,8 +3,8 @@ #include #include -#include #include +#include /* Start of tes3mp addition @@ -25,6 +25,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -34,43 +36,46 @@ namespace MWScript { class OpSetControl : public Interpreter::Opcode0 { - std::string mControl; - bool mEnable; + std::string mControl; + bool mEnable; - public: + public: + OpSetControl(const std::string& control, bool enable) + : mControl(control) + , mEnable(enable) + { + } - OpSetControl (const std::string& control, bool enable) - : mControl (control), mEnable (enable) - {} - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::Environment::get() - .getInputManager() - ->toggleControlSwitch(mControl, mEnable); - } + void execute(Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getInputManager()->toggleControlSwitch(mControl, mEnable); + } }; class OpGetDisabled : public Interpreter::Opcode0 { - std::string mControl; + std::string mControl; - public: + public: + OpGetDisabled(const std::string& control) + : mControl(control) + { + } - OpGetDisabled (const std::string& control) - : mControl (control) - {} - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push(!MWBase::Environment::get().getInputManager()->getControlSwitch (mControl)); - } + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(!MWBase::Environment::get().getInputManager()->getControlSwitch(mControl)); + } }; class OpToggleCollision : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleCollisionMode(); +<<<<<<< HEAD void execute (Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleCollisionMode(); @@ -87,189 +92,194 @@ namespace MWScript runtime.getContext().report (enabled ? "Collision -> On" : "Collision -> Off"); } +======= + runtime.getContext().report(enabled ? "Collision -> On" : "Collision -> Off"); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; - template + template class OpClearMovementFlag : public Interpreter::Opcode0 { - MWMechanics::CreatureStats::Flag mFlag; + MWMechanics::CreatureStats::Flag mFlag; - public: + public: + OpClearMovementFlag(MWMechanics::CreatureStats::Flag flag) + : mFlag(flag) + { + } - OpClearMovementFlag (MWMechanics::CreatureStats::Flag flag) : mFlag (flag) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - ptr.getClass().getCreatureStats(ptr).setMovementFlag (mFlag, false); - } + ptr.getClass().getCreatureStats(ptr).setMovementFlag(mFlag, false); + } }; - template + template class OpSetMovementFlag : public Interpreter::Opcode0 { - MWMechanics::CreatureStats::Flag mFlag; + MWMechanics::CreatureStats::Flag mFlag; - public: + public: + OpSetMovementFlag(MWMechanics::CreatureStats::Flag flag) + : mFlag(flag) + { + } - OpSetMovementFlag (MWMechanics::CreatureStats::Flag flag) : mFlag (flag) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - ptr.getClass().getCreatureStats(ptr).setMovementFlag (mFlag, true); - } + ptr.getClass().getCreatureStats(ptr).setMovementFlag(mFlag, true); + } }; template class OpGetForceRun : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - } + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + runtime.push(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceRun)); + } }; template class OpGetForceJump : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); - } + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + runtime.push(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)); + } }; template class OpGetForceMoveJump : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); - } + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + runtime.push(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump)); + } }; template class OpGetForceSneak : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - } + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + runtime.push(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceSneak)); + } }; class OpGetPcRunning : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWBase::World* world = MWBase::Environment::get().getWorld(); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - MWBase::World* world = MWBase::Environment::get().getWorld(); + bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Run); + bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); + bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Run); - bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); - bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - - runtime.push(stanceOn && (running || inair)); - } + runtime.push(stanceOn && (running || inair)); + } }; class OpGetPcSneaking : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - runtime.push(MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr)); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + runtime.push(MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr)); + } }; - - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - for (int i=0; i( + Compiler::Control::opcodeEnable + i, Compiler::Control::controls[i], true); + interpreter.installSegment5( + Compiler::Control::opcodeDisable + i, Compiler::Control::controls[i], false); + interpreter.installSegment5( + Compiler::Control::opcodeGetDisabled + i, Compiler::Control::controls[i]); } - interpreter.installSegment5 (Compiler::Control::opcodeToggleCollision, new OpToggleCollision); + interpreter.installSegment5(Compiler::Control::opcodeToggleCollision); - //Force Run - interpreter.installSegment5 (Compiler::Control::opcodeClearForceRun, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceRunExplicit, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeForceRun, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeForceRunExplicit, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); + // Force Run + interpreter.installSegment5>( + Compiler::Control::opcodeClearForceRun, MWMechanics::CreatureStats::Flag_ForceRun); + interpreter.installSegment5>( + Compiler::Control::opcodeClearForceRunExplicit, MWMechanics::CreatureStats::Flag_ForceRun); + interpreter.installSegment5>( + Compiler::Control::opcodeForceRun, MWMechanics::CreatureStats::Flag_ForceRun); + interpreter.installSegment5>( + Compiler::Control::opcodeForceRunExplicit, MWMechanics::CreatureStats::Flag_ForceRun); - //Force Jump - interpreter.installSegment5 (Compiler::Control::opcodeClearForceJump, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceJumpExplicit, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); - interpreter.installSegment5 (Compiler::Control::opcodeForceJump, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); - interpreter.installSegment5 (Compiler::Control::opcodeForceJumpExplicit, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + // Force Jump + interpreter.installSegment5>( + Compiler::Control::opcodeClearForceJump, MWMechanics::CreatureStats::Flag_ForceJump); + interpreter.installSegment5>( + Compiler::Control::opcodeClearForceJumpExplicit, MWMechanics::CreatureStats::Flag_ForceJump); + interpreter.installSegment5>( + Compiler::Control::opcodeForceJump, MWMechanics::CreatureStats::Flag_ForceJump); + interpreter.installSegment5>( + Compiler::Control::opcodeForceJumpExplicit, MWMechanics::CreatureStats::Flag_ForceJump); - //Force MoveJump - interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJump, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJumpExplicit, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); - interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJump, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); - interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJumpExplicit, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + // Force MoveJump + interpreter.installSegment5>( + Compiler::Control::opcodeClearForceMoveJump, MWMechanics::CreatureStats::Flag_ForceMoveJump); + interpreter.installSegment5>( + Compiler::Control::opcodeClearForceMoveJumpExplicit, MWMechanics::CreatureStats::Flag_ForceMoveJump); + interpreter.installSegment5>( + Compiler::Control::opcodeForceMoveJump, MWMechanics::CreatureStats::Flag_ForceMoveJump); + interpreter.installSegment5>( + Compiler::Control::opcodeForceMoveJumpExplicit, MWMechanics::CreatureStats::Flag_ForceMoveJump); - //Force Sneak - interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneakExplicit, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeForceSneak, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeForceSneakExplicit, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); + // Force Sneak + interpreter.installSegment5>( + Compiler::Control::opcodeClearForceSneak, MWMechanics::CreatureStats::Flag_ForceSneak); + interpreter.installSegment5>( + Compiler::Control::opcodeClearForceSneakExplicit, MWMechanics::CreatureStats::Flag_ForceSneak); + interpreter.installSegment5>( + Compiler::Control::opcodeForceSneak, MWMechanics::CreatureStats::Flag_ForceSneak); + interpreter.installSegment5>( + Compiler::Control::opcodeForceSneakExplicit, MWMechanics::CreatureStats::Flag_ForceSneak); - interpreter.installSegment5 (Compiler::Control::opcodeGetPcRunning, new OpGetPcRunning); - interpreter.installSegment5 (Compiler::Control::opcodeGetPcSneaking, new OpGetPcSneaking); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceRun, new OpGetForceRun); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceRunExplicit, new OpGetForceRun); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceJump, new OpGetForceJump); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceJumpExplicit, new OpGetForceJump); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJump, new OpGetForceMoveJump); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJumpExplicit, new OpGetForceMoveJump); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneak, new OpGetForceSneak); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneakExplicit, new OpGetForceSneak); + interpreter.installSegment5(Compiler::Control::opcodeGetPcRunning); + interpreter.installSegment5(Compiler::Control::opcodeGetPcSneaking); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceRun); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceRunExplicit); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceJump); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceJumpExplicit); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceMoveJump); + interpreter.installSegment5>( + Compiler::Control::opcodeGetForceMoveJumpExplicit); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceSneak); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceSneakExplicit); } } } diff --git a/apps/openmw/mwscript/controlextensions.hpp b/apps/openmw/mwscript/controlextensions.hpp index b9c6654fe..57051cf75 100644 --- a/apps/openmw/mwscript/controlextensions.hpp +++ b/apps/openmw/mwscript/controlextensions.hpp @@ -16,7 +16,7 @@ namespace MWScript /// \brief player controls-related script functionality namespace Control { - void installOpcodes (Interpreter::Interpreter& interpreter); + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index fb71d9ea0..52e980356 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -16,17 +16,17 @@ #include #include #include -#include #include +#include -#include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/environment.hpp" #include "../mwbase/journal.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwworld/class.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -38,10 +38,23 @@ namespace MWScript template class OpJournal : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime, false); // required=false + if (ptr.isEmpty()) + ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - void execute (Interpreter::Runtime& runtime) override + ESM::RefId quest = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + Interpreter::Type_Integer index = runtime[0].mInteger; + runtime.pop(); + + // Invoking Journal with a non-existing index is allowed, and triggers no errors. Seriously? :( + try { +<<<<<<< HEAD MWWorld::Ptr ptr = R()(runtime, false); // required=false if (ptr.isEmpty()) ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -74,18 +87,30 @@ namespace MWScript if (MWBase::Environment::get().getJournal()->getJournalIndex(quest) < index) MWBase::Environment::get().getJournal()->setJournalIndex(quest, index); } +======= + MWBase::Environment::get().getJournal()->addEntry(quest, index, ptr); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + catch (...) + { + if (MWBase::Environment::get().getJournal()->getJournalIndex(quest) < index) + MWBase::Environment::get().getJournal()->setJournalIndex(quest, index); + } + } }; class OpSetJournalIndex : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + ESM::RefId quest = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - std::string quest = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + Interpreter::Type_Integer index = runtime[0].mInteger; + runtime.pop(); +<<<<<<< HEAD Interpreter::Type_Integer index = runtime[0].mInteger; runtime.pop(); @@ -103,28 +128,35 @@ namespace MWScript End of tes3mp addition */ } +======= + MWBase::Environment::get().getJournal()->setJournalIndex(quest, index); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; class OpGetJournalIndex : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + ESM::RefId quest = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - std::string quest = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + int index = MWBase::Environment::get().getJournal()->getJournalIndex(quest); - int index = MWBase::Environment::get().getJournal()->getJournalIndex (quest); - - runtime.push (index); - - } + runtime.push(index); + } }; class OpAddTopic : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + ESM::RefId topic = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); +<<<<<<< HEAD void execute (Interpreter::Runtime& runtime) override { std::string topic = runtime.getStringLiteral (runtime[0].mInteger); @@ -145,39 +177,49 @@ namespace MWScript MWBase::Environment::get().getDialogueManager()->addTopic(topic); } +======= + MWBase::Environment::get().getDialogueManager()->addTopic(topic); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; class OpChoice : public Interpreter::Opcode1 { - public: - - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWBase::DialogueManager* dialogue = MWBase::Environment::get().getDialogueManager(); + while (arg0 > 0) { - MWBase::DialogueManager* dialogue = MWBase::Environment::get().getDialogueManager(); - while(arg0>0) + std::string_view question = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + arg0 = arg0 - 1; + Interpreter::Type_Integer choice = 1; + if (arg0 > 0) { - std::string question = runtime.getStringLiteral (runtime[0].mInteger); + choice = runtime[0].mInteger; runtime.pop(); - arg0 = arg0 -1; - Interpreter::Type_Integer choice = 1; - if(arg0>0) - { - choice = runtime[0].mInteger; - runtime.pop(); - arg0 = arg0 -1; - } - dialogue->addChoice(question,choice); + arg0 = arg0 - 1; } + dialogue->addChoice(question, choice); } + } }; - template + template class OpForceGreeting : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + if (!ptr.getRefData().isEnabled()) + return; + + if (!ptr.getClass().isActor()) { +<<<<<<< HEAD MWWorld::Ptr ptr = R()(runtime); if (!ptr.getRefData().isEnabled()) @@ -202,87 +244,90 @@ namespace MWScript /* End of tes3mp change (major) */ +======= + const std::string error = "Warning: \"forcegreeting\" command works only for actors."; + runtime.getContext().report(error); + Log(Debug::Warning) << error; + return; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, ptr); + } }; class OpGoodbye : public Interpreter::Opcode0 { - public: - - void execute(Interpreter::Runtime& runtime) override - { - MWBase::Environment::get().getDialogueManager()->goodbye(); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getDialogueManager()->goodbye(); + } }; - template + template class OpModReputation : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; - runtime.pop(); - - ptr.getClass().getNpcStats (ptr).setReputation (ptr.getClass().getNpcStats (ptr).getReputation () + value); - } + ptr.getClass().getNpcStats(ptr).setReputation(ptr.getClass().getNpcStats(ptr).getReputation() + value); + } }; - template + template class OpSetReputation : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = runtime[0].mInteger; - runtime.pop(); - - ptr.getClass().getNpcStats (ptr).setReputation (value); - } + ptr.getClass().getNpcStats(ptr).setReputation(value); + } }; - template + template class OpGetReputation : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push (ptr.getClass().getNpcStats (ptr).getReputation ()); - } + runtime.push(ptr.getClass().getNpcStats(ptr).getReputation()); + } }; - template + template class OpSameFaction : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - - runtime.push(player.getClass().getNpcStats (player).isInFaction(ptr.getClass().getPrimaryFaction(ptr))); - } + runtime.push(player.getClass().getNpcStats(player).isInFaction(ptr.getClass().getPrimaryFaction(ptr))); + } }; class OpModFactionReaction : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - std::string faction1 = runtime.getStringLiteral (runtime[0].mInteger); + ESM::RefId faction1 = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); + ESM::RefId faction2 = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); int modReaction = runtime[0].mInteger; @@ -295,30 +340,27 @@ namespace MWScript class OpGetFactionReaction : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - std::string faction1 = runtime.getStringLiteral (runtime[0].mInteger); + ESM::RefId faction1 = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); + ESM::RefId faction2 = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - runtime.push(MWBase::Environment::get().getDialogueManager() - ->getFactionReaction(faction1, faction2)); + runtime.push(MWBase::Environment::get().getDialogueManager()->getFactionReaction(faction1, faction2)); } }; class OpSetFactionReaction : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - std::string faction1 = runtime.getStringLiteral (runtime[0].mInteger); + ESM::RefId faction1 = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); + ESM::RefId faction2 = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); int newValue = runtime[0].mInteger; @@ -332,7 +374,7 @@ namespace MWScript class OpClearInfoActor : public Interpreter::Opcode0 { public: - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); @@ -340,31 +382,31 @@ namespace MWScript } }; - - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Dialogue::opcodeJournal, new OpJournal); - interpreter.installSegment5 (Compiler::Dialogue::opcodeJournalExplicit, new OpJournal); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSetJournalIndex, new OpSetJournalIndex); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGetJournalIndex, new OpGetJournalIndex); - interpreter.installSegment5 (Compiler::Dialogue::opcodeAddTopic, new OpAddTopic); - interpreter.installSegment3 (Compiler::Dialogue::opcodeChoice,new OpChoice); - interpreter.installSegment5 (Compiler::Dialogue::opcodeForceGreeting, new OpForceGreeting); - interpreter.installSegment5 (Compiler::Dialogue::opcodeForceGreetingExplicit, new OpForceGreeting); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGoodbye, new OpGoodbye); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputation, new OpGetReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSetReputation, new OpSetReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeModReputation, new OpModReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSetReputationExplicit, new OpSetReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeModReputationExplicit, new OpModReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputationExplicit, new OpGetReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFaction, new OpSameFaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSetFactionReaction, new OpSetFactionReaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActor, new OpClearInfoActor); - interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActorExplicit, new OpClearInfoActor); + interpreter.installSegment5>(Compiler::Dialogue::opcodeJournal); + interpreter.installSegment5>(Compiler::Dialogue::opcodeJournalExplicit); + interpreter.installSegment5(Compiler::Dialogue::opcodeSetJournalIndex); + interpreter.installSegment5(Compiler::Dialogue::opcodeGetJournalIndex); + interpreter.installSegment5(Compiler::Dialogue::opcodeAddTopic); + interpreter.installSegment3(Compiler::Dialogue::opcodeChoice); + interpreter.installSegment5>(Compiler::Dialogue::opcodeForceGreeting); + interpreter.installSegment5>(Compiler::Dialogue::opcodeForceGreetingExplicit); + interpreter.installSegment5(Compiler::Dialogue::opcodeGoodbye); + interpreter.installSegment5>(Compiler::Dialogue::opcodeGetReputation); + interpreter.installSegment5>(Compiler::Dialogue::opcodeSetReputation); + interpreter.installSegment5>(Compiler::Dialogue::opcodeModReputation); + interpreter.installSegment5>(Compiler::Dialogue::opcodeSetReputationExplicit); + interpreter.installSegment5>(Compiler::Dialogue::opcodeModReputationExplicit); + interpreter.installSegment5>(Compiler::Dialogue::opcodeGetReputationExplicit); + interpreter.installSegment5>(Compiler::Dialogue::opcodeSameFaction); + interpreter.installSegment5>(Compiler::Dialogue::opcodeSameFactionExplicit); + interpreter.installSegment5(Compiler::Dialogue::opcodeModFactionReaction); + interpreter.installSegment5(Compiler::Dialogue::opcodeSetFactionReaction); + interpreter.installSegment5(Compiler::Dialogue::opcodeGetFactionReaction); + interpreter.installSegment5>(Compiler::Dialogue::opcodeClearInfoActor); + interpreter.installSegment5>( + Compiler::Dialogue::opcodeClearInfoActorExplicit); } } diff --git a/apps/openmw/mwscript/dialogueextensions.hpp b/apps/openmw/mwscript/dialogueextensions.hpp index 7b03154df..acb4e0683 100644 --- a/apps/openmw/mwscript/dialogueextensions.hpp +++ b/apps/openmw/mwscript/dialogueextensions.hpp @@ -16,7 +16,7 @@ namespace MWScript /// \brief Dialogue/Journal-related script functionality namespace Dialogue { - void installOpcodes (Interpreter::Interpreter& interpreter); + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index ccc579b30..d34c39c9d 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -479,5 +479,11 @@ op 0x200031c: GetDisabled, explicit op 0x200031d: StartScript, explicit op 0x200031e: GetDistance op 0x200031f: GetDistance, explicit +op 0x2000320: Help +op 0x2000321: ReloadLua +op 0x2000322: GetPCVisionBonus +op 0x2000323: SetPCVisionBonus +op 0x2000324: ModPCVisionBonus +op 0x2000325: TestModels, T3D -opcodes 0x2000320-0x3ffffff unused +opcodes 0x2000326-0x3ffffff unused diff --git a/apps/openmw/mwscript/extensions.cpp b/apps/openmw/mwscript/extensions.cpp index 12bf3413a..0f42b4925 100644 --- a/apps/openmw/mwscript/extensions.cpp +++ b/apps/openmw/mwscript/extensions.cpp @@ -1,45 +1,45 @@ #include "extensions.hpp" -#include #include +#include -#include "soundextensions.hpp" -#include "cellextensions.hpp" -#include "miscextensions.hpp" -#include "guiextensions.hpp" -#include "skyextensions.hpp" -#include "statsextensions.hpp" -#include "containerextensions.hpp" #include "aiextensions.hpp" +#include "animationextensions.hpp" +#include "cellextensions.hpp" +#include "consoleextensions.hpp" +#include "containerextensions.hpp" #include "controlextensions.hpp" #include "dialogueextensions.hpp" -#include "animationextensions.hpp" +#include "guiextensions.hpp" +#include "miscextensions.hpp" +#include "skyextensions.hpp" +#include "soundextensions.hpp" +#include "statsextensions.hpp" #include "transformationextensions.hpp" -#include "consoleextensions.hpp" #include "userextensions.hpp" namespace MWScript { - void installOpcodes (Interpreter::Interpreter& interpreter, bool consoleOnly) + void installOpcodes(Interpreter::Interpreter& interpreter, bool consoleOnly) { - Interpreter::installOpcodes (interpreter); - Cell::installOpcodes (interpreter); - Misc::installOpcodes (interpreter); - Gui::installOpcodes (interpreter); - Sound::installOpcodes (interpreter); - Sky::installOpcodes (interpreter); - Stats::installOpcodes (interpreter); - Container::installOpcodes (interpreter); - Ai::installOpcodes (interpreter); - Control::installOpcodes (interpreter); - Dialogue::installOpcodes (interpreter); - Animation::installOpcodes (interpreter); - Transformation::installOpcodes (interpreter); + Interpreter::installOpcodes(interpreter); + Cell::installOpcodes(interpreter); + Misc::installOpcodes(interpreter); + Gui::installOpcodes(interpreter); + Sound::installOpcodes(interpreter); + Sky::installOpcodes(interpreter); + Stats::installOpcodes(interpreter); + Container::installOpcodes(interpreter); + Ai::installOpcodes(interpreter); + Control::installOpcodes(interpreter); + Dialogue::installOpcodes(interpreter); + Animation::installOpcodes(interpreter); + Transformation::installOpcodes(interpreter); if (consoleOnly) { - Console::installOpcodes (interpreter); - User::installOpcodes (interpreter); + Console::installOpcodes(interpreter); + User::installOpcodes(interpreter); } } } diff --git a/apps/openmw/mwscript/extensions.hpp b/apps/openmw/mwscript/extensions.hpp index 67f6de5c5..43c5bb3b0 100644 --- a/apps/openmw/mwscript/extensions.hpp +++ b/apps/openmw/mwscript/extensions.hpp @@ -13,7 +13,7 @@ namespace Interpreter namespace MWScript { - void installOpcodes (Interpreter::Interpreter& interpreter, bool consoleOnly = false); + void installOpcodes(Interpreter::Interpreter& interpreter, bool consoleOnly = false); ///< \param consoleOnly include console only opcodes } diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 52eca6742..39b4b5830 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -1,27 +1,28 @@ #include "globalscripts.hpp" #include -#include -#include -#include +#include +#include +#include +#include +#include -#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/worldmodel.hpp" #include "interpretercontext.hpp" namespace { - struct ScriptCreatingVisitor : public boost::static_visitor + struct ScriptCreatingVisitor { - ESM::GlobalScript operator()(const MWWorld::Ptr &ptr) const + ESM::GlobalScript operator()(const MWWorld::Ptr& ptr) const { ESM::GlobalScript script; - script.mTargetRef.unset(); script.mRunning = false; if (!ptr.isEmpty()) { @@ -36,7 +37,7 @@ namespace return script; } - ESM::GlobalScript operator()(const std::pair &pair) const + ESM::GlobalScript operator()(const std::pair& pair) const { ESM::GlobalScript script; script.mTargetId = pair.second; @@ -46,89 +47,98 @@ namespace } }; - struct PtrGettingVisitor : public boost::static_visitor + struct PtrGettingVisitor { - const MWWorld::Ptr* operator()(const MWWorld::Ptr &ptr) const - { - return &ptr; - } + const MWWorld::Ptr* operator()(const MWWorld::Ptr& ptr) const { return &ptr; } - const MWWorld::Ptr* operator()(const std::pair &pair) const - { - return nullptr; - } + const MWWorld::Ptr* operator()(const std::pair& pair) const { return nullptr; } }; - struct PtrResolvingVisitor : public boost::static_visitor + struct PtrResolvingVisitor { - MWWorld::Ptr operator()(const MWWorld::Ptr &ptr) const - { - return ptr; - } + MWWorld::Ptr operator()(const MWWorld::Ptr& ptr) const { return ptr; } - MWWorld::Ptr operator()(const std::pair &pair) const + MWWorld::Ptr operator()(const std::pair& pair) const { if (pair.second.empty()) return MWWorld::Ptr(); - else if(pair.first.hasContentFile()) - return MWBase::Environment::get().getWorld()->searchPtrViaRefNum(pair.second, pair.first); + else if (pair.first.hasContentFile()) + return MWBase::Environment::get().getWorldModel()->getPtr(pair.first); return MWBase::Environment::get().getWorld()->searchPtr(pair.second, false); } }; - class MatchPtrVisitor : public boost::static_visitor + class MatchPtrVisitor { const MWWorld::Ptr& mPtr; + public: - MatchPtrVisitor(const MWWorld::Ptr& ptr) : mPtr(ptr) {} - - bool operator()(const MWWorld::Ptr &ptr) const + MatchPtrVisitor(const MWWorld::Ptr& ptr) + : mPtr(ptr) { - return ptr == mPtr; } - bool operator()(const std::pair &pair) const + bool operator()(const MWWorld::Ptr& ptr) const { return ptr == mPtr; } + + bool operator()(const std::pair& pair) const { return false; } + }; + + struct IdGettingVisitor + { + ESM::RefId operator()(const MWWorld::Ptr& ptr) const { - return false; + if (ptr.isEmpty()) + return ESM::RefId(); + return ptr.mRef->mRef.getRefId(); } + + ESM::RefId operator()(const std::pair& pair) const { return pair.second; } }; } namespace MWScript { - GlobalScriptDesc::GlobalScriptDesc() : mRunning (false) {} + GlobalScriptDesc::GlobalScriptDesc() + : mRunning(false) + { + } const MWWorld::Ptr* GlobalScriptDesc::getPtrIfPresent() const { - return boost::apply_visitor(PtrGettingVisitor(), mTarget); + return std::visit(PtrGettingVisitor(), mTarget); } MWWorld::Ptr GlobalScriptDesc::getPtr() { - MWWorld::Ptr ptr = boost::apply_visitor(PtrResolvingVisitor(), mTarget); + MWWorld::Ptr ptr = std::visit(PtrResolvingVisitor{}, mTarget); mTarget = ptr; return ptr; } - - GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store) - : mStore (store) - {} - - void GlobalScripts::addScript (const std::string& name, const MWWorld::Ptr& target) + ESM::RefId GlobalScriptDesc::getId() const { - const auto iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); + return std::visit(IdGettingVisitor{}, mTarget); + } - if (iter==mScripts.end()) + GlobalScripts::GlobalScripts(const MWWorld::ESMStore& store) + : mStore(store) + { + } + + void GlobalScripts::addScript(const ESM::RefId& name, const MWWorld::Ptr& target) + { + const auto iter = mScripts.find(name); + + if (iter == mScripts.end()) { - if (const ESM::Script *script = mStore.get().search(name)) + if (const ESM::Script* script = mStore.get().search(name)) { auto desc = std::make_shared(); MWWorld::Ptr ptr = target; desc->mTarget = ptr; desc->mRunning = true; - desc->mLocals.configure (*script); - mScripts.insert (std::make_pair(name, desc)); + desc->mLocals.configure(*script); + mScripts.insert(std::make_pair(name, desc)); } else { @@ -143,19 +153,19 @@ namespace MWScript } } - void GlobalScripts::removeScript (const std::string& name) + void GlobalScripts::removeScript(const ESM::RefId& name) { - const auto iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); + const auto iter = mScripts.find(name); - if (iter!=mScripts.end()) + if (iter != mScripts.end()) iter->second->mRunning = false; } - bool GlobalScripts::isRunning (const std::string& name) const + bool GlobalScripts::isRunning(const ESM::RefId& name) const { - const auto iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); + const auto iter = mScripts.find(name); - if (iter==mScripts.end()) + if (iter == mScripts.end()) return false; return iter->second->mRunning; @@ -197,30 +207,27 @@ namespace MWScript void GlobalScripts::addStartup() { // make list of global scripts to be added - std::vector scripts; + std::vector scripts; - scripts.emplace_back("main"); + scripts.emplace_back(ESM::RefId::stringRefId("main")); - for (MWWorld::Store::iterator iter = - mStore.get().begin(); - iter != mStore.get().end(); ++iter) + for (MWWorld::Store::iterator iter = mStore.get().begin(); + iter != mStore.get().end(); ++iter) { - scripts.push_back (iter->mId); + scripts.push_back(iter->mId); } // add scripts - for (std::vector::const_iterator iter (scripts.begin()); - iter!=scripts.end(); ++iter) + for (auto iter(scripts.begin()); iter != scripts.end(); ++iter) { try { - addScript (*iter); + addScript(*iter); } catch (const std::exception& exception) { - Log(Debug::Error) - << "Failed to add start script " << *iter << " because an exception has " - << "been thrown: " << exception.what(); + Log(Debug::Error) << "Failed to add start script " << *iter << " because an exception has " + << "been thrown: " << exception.what(); } } } @@ -230,30 +237,30 @@ namespace MWScript return mScripts.size(); } - void GlobalScripts::write (ESM::ESMWriter& writer, Loading::Listener& progress) const + void GlobalScripts::write(ESM::ESMWriter& writer, Loading::Listener& progress) const { - for (const auto& iter : mScripts) + for (const auto& [id, desc] : mScripts) { - ESM::GlobalScript script = boost::apply_visitor (ScriptCreatingVisitor(), iter.second->mTarget); + ESM::GlobalScript script = std::visit(ScriptCreatingVisitor{}, desc->mTarget); - script.mId = iter.first; + script.mId = id; - iter.second->mLocals.write (script.mLocals, iter.first); + desc->mLocals.write(script.mLocals, id); - script.mRunning = iter.second->mRunning ? 1 : 0; + script.mRunning = desc->mRunning ? 1 : 0; - writer.startRecord (ESM::REC_GSCR); - script.save (writer); - writer.endRecord (ESM::REC_GSCR); + writer.startRecord(ESM::REC_GSCR); + script.save(writer); + writer.endRecord(ESM::REC_GSCR); } } - bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) + bool GlobalScripts::readRecord(ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { - if (type==ESM::REC_GSCR) + if (type == ESM::REC_GSCR) { ESM::GlobalScript script; - script.load (reader); + script.load(reader); if (script.mTargetRef.hasContentFile()) { @@ -262,11 +269,11 @@ namespace MWScript script.mTargetRef.mContentFile = iter->second; } - auto iter = mScripts.find (script.mId); + auto iter = mScripts.find(script.mId); - if (iter==mScripts.end()) + if (iter == mScripts.end()) { - if (const ESM::Script *scriptRecord = mStore.get().search (script.mId)) + if (const ESM::Script* scriptRecord = mStore.get().search(script.mId)) { try { @@ -275,15 +282,14 @@ namespace MWScript { desc->mTarget = std::make_pair(script.mTargetRef, script.mTargetId); } - desc->mLocals.configure (*scriptRecord); + desc->mLocals.configure(*scriptRecord); - iter = mScripts.insert (std::make_pair (script.mId, desc)).first; + iter = mScripts.insert(std::make_pair(script.mId, desc)).first; } catch (const std::exception& exception) { - Log(Debug::Error) - << "Failed to add start script " << script.mId - << " because an exception has been thrown: " << exception.what(); + Log(Debug::Error) << "Failed to add start script " << script.mId + << " because an exception has been thrown: " << exception.what(); return true; } @@ -292,8 +298,8 @@ namespace MWScript return true; } - iter->second->mRunning = script.mRunning!=0; - iter->second->mLocals.read (script.mLocals, script.mId); + iter->second->mRunning = script.mRunning != 0; + iter->second->mLocals.read(script.mLocals, script.mId); return true; } @@ -301,31 +307,29 @@ namespace MWScript return false; } - Locals& GlobalScripts::getLocals (const std::string& name) + Locals& GlobalScripts::getLocals(const ESM::RefId& name) { - std::string name2 = ::Misc::StringUtils::lowerCase (name); - auto iter = mScripts.find (name2); + auto iter = mScripts.find(name); - if (iter==mScripts.end()) + if (iter == mScripts.end()) { - const ESM::Script *script = mStore.get().find (name); + const ESM::Script* script = mStore.get().find(name); auto desc = std::make_shared(); - desc->mLocals.configure (*script); + desc->mLocals.configure(*script); - iter = mScripts.insert (std::make_pair (name2, desc)).first; + iter = mScripts.emplace(name, desc).first; } return iter->second->mLocals; } - const Locals* GlobalScripts::getLocalsIfPresent (const std::string& name) const + const GlobalScriptDesc* GlobalScripts::getScriptIfPresent(const ESM::RefId& name) const { - std::string name2 = ::Misc::StringUtils::lowerCase (name); - auto iter = mScripts.find (name2); - if (iter==mScripts.end()) + auto iter = mScripts.find(name); + if (iter == mScripts.end()) return nullptr; - return &iter->second->mLocals; + return iter->second.get(); } void GlobalScripts::updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated) @@ -333,7 +337,7 @@ namespace MWScript MatchPtrVisitor visitor(base); for (const auto& script : mScripts) { - if (boost::apply_visitor (visitor, script.second->mTarget)) + if (std::visit(visitor, script.second->mTarget)) script.second->mTarget = updated; } } diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 049e78804..c2e1c58bb 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -1,14 +1,17 @@ #ifndef GAME_SCRIPT_GLOBALSCRIPTS_H #define GAME_SCRIPT_GLOBALSCRIPTS_H -#include - -#include +#include #include #include +#include +#include +#include #include +#include -#include +#include +#include #include "locals.hpp" @@ -18,7 +21,8 @@ namespace ESM { class ESMWriter; class ESMReader; - struct RefNum; + struct FormId; + using RefNum = FormId; } namespace Loading @@ -37,55 +41,56 @@ namespace MWScript { bool mRunning; Locals mLocals; - boost::variant > mTarget; // Used to start targeted script + std::variant> mTarget; // Used to start targeted script GlobalScriptDesc(); const MWWorld::Ptr* getPtrIfPresent() const; // Returns a Ptr if one has been resolved MWWorld::Ptr getPtr(); // Resolves mTarget to a Ptr and caches the (potentially empty) result + + ESM::RefId getId() const; // Returns the target's ID -- if any }; class GlobalScripts { - const MWWorld::ESMStore& mStore; - std::map > mScripts; + const MWWorld::ESMStore& mStore; + std::unordered_map> mScripts; - public: + public: + GlobalScripts(const MWWorld::ESMStore& store); - GlobalScripts (const MWWorld::ESMStore& store); + void addScript(const ESM::RefId& name, const MWWorld::Ptr& target = MWWorld::Ptr()); - void addScript (const std::string& name, const MWWorld::Ptr& target = MWWorld::Ptr()); + void removeScript(const ESM::RefId& name); - void removeScript (const std::string& name); + bool isRunning(const ESM::RefId& name) const; - bool isRunning (const std::string& name) const; + void run(); + ///< run all active global scripts - void run(); - ///< run all active global scripts + void clear(); - void clear(); + void addStartup(); + ///< Add startup script - void addStartup(); - ///< Add startup script + int countSavedGameRecords() const; - int countSavedGameRecords() const; + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; + bool readRecord(ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap); + ///< Records for variables that do not exist are dropped silently. + /// + /// \return Known type? - bool readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap); - ///< Records for variables that do not exist are dropped silently. - /// - /// \return Known type? + Locals& getLocals(const ESM::RefId& name); + ///< If the script \a name has not been added as a global script yet, it is added + /// automatically, but is not set to running state. - Locals& getLocals (const std::string& name); - ///< If the script \a name has not been added as a global script yet, it is added - /// automatically, but is not set to running state. + const GlobalScriptDesc* getScriptIfPresent(const ESM::RefId& name) const; - const Locals* getLocalsIfPresent (const std::string& name) const; - - void updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); - ///< Update the Ptrs stored in mTarget. Should be called after the reference has been moved to a new cell. + void updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); + ///< Update the Ptrs stored in mTarget. Should be called after the reference has been moved to a new cell. }; } diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 11ce35b77..fb763a538 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -14,15 +14,15 @@ #include #include -#include #include +#include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -35,36 +35,38 @@ namespace MWScript { class OpEnableWindow : public Interpreter::Opcode0 { - MWGui::GuiWindow mWindow; + MWGui::GuiWindow mWindow; - public: + public: + OpEnableWindow(MWGui::GuiWindow window) + : mWindow(window) + { + } - OpEnableWindow (MWGui::GuiWindow window) : mWindow (window) {} - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::Environment::get().getWindowManager()->allow (mWindow); - } + void execute(Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getWindowManager()->allow(mWindow); + } }; class OpEnableRest : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::Environment::get().getWindowManager()->enableRest(); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getWindowManager()->enableRest(); + } }; template class OpShowRestMenu : public Interpreter::Opcode0 { public: - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr bed = R()(runtime, false); +<<<<<<< HEAD if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWMechanics::getPlayer(), bed)) /* @@ -84,84 +86,81 @@ namespace MWScript /* End of tes3mp change (minor) */ +======= + if (bed.isEmpty() + || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWMechanics::getPlayer(), bed)) + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Rest, bed); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } }; class OpShowDialogue : public Interpreter::Opcode0 { - MWGui::GuiMode mDialogue; + MWGui::GuiMode mDialogue; - public: + public: + OpShowDialogue(MWGui::GuiMode dialogue) + : mDialogue(dialogue) + { + } - OpShowDialogue (MWGui::GuiMode dialogue) - : mDialogue (dialogue) - {} - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::Environment::get().getWindowManager()->pushGuiMode(mDialogue); - } + void execute(Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getWindowManager()->pushGuiMode(mDialogue); + } }; class OpGetButtonPressed : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push (MWBase::Environment::get().getWindowManager()->readPressedButton()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWindowManager()->readPressedButton()); + } }; class OpToggleFogOfWar : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.getContext().report(MWBase::Environment::get().getWindowManager()->toggleFogOfWar() ? "Fog of war -> On" - : "Fog of war -> Off"); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.getContext().report(MWBase::Environment::get().getWindowManager()->toggleFogOfWar() + ? "Fog of war -> On" + : "Fog of war -> Off"); + } }; class OpToggleFullHelp : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.getContext().report(MWBase::Environment::get().getWindowManager()->toggleFullHelp() ? "Full help -> On" - : "Full help -> Off"); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.getContext().report(MWBase::Environment::get().getWindowManager()->toggleFullHelp() + ? "Full help -> On" + : "Full help -> Off"); + } }; class OpShowMap : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - std::string cell = (runtime.getStringLiteral (runtime[0].mInteger)); - ::Misc::StringUtils::lowerCaseInPlace(cell); + std::string_view cell = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); - // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well." - // http://www.uesp.net/wiki/Tes3Mod:ShowMap + // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's + // House as well." http://www.uesp.net/wiki/Tes3Mod:ShowMap - const MWWorld::Store &cells = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& cells = MWBase::Environment::get().getESMStore()->get(); - MWWorld::Store::iterator it = cells.extBegin(); - for (; it != cells.extEnd(); ++it) + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); + + for (auto it = cells.extBegin(); it != cells.extEnd(); ++it) { - std::string name = it->mName; - ::Misc::StringUtils::lowerCaseInPlace(name); - if (name.find(cell) != std::string::npos) - MWBase::Environment::get().getWindowManager()->addVisitedLocation ( - it->mName, - it->getGridX(), - it->getGridY() - ); + const auto& cellName = it->mName; + if (Misc::StringUtils::ciStartsWith(cellName, cell)) + winMgr->addVisitedLocation(cellName, it->getGridX(), it->getGridY()); } } }; @@ -169,22 +168,16 @@ namespace MWScript class OpFillMap : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - const MWWorld::Store &cells = - MWBase::Environment::get().getWorld ()->getStore().get(); + const MWWorld::Store& cells = MWBase::Environment::get().getESMStore()->get(); - MWWorld::Store::iterator it = cells.extBegin(); - for (; it != cells.extEnd(); ++it) + for (auto it = cells.extBegin(); it != cells.extEnd(); ++it) { - std::string name = it->mName; - if (name != "") - MWBase::Environment::get().getWindowManager()->addVisitedLocation ( - name, - it->getGridX(), - it->getGridY() - ); + const std::string& name = it->mName; + if (!name.empty()) + MWBase::Environment::get().getWindowManager()->addVisitedLocation( + name, it->getGridX(), it->getGridY()); } } }; @@ -192,22 +185,20 @@ namespace MWScript class OpMenuTest : public Interpreter::Opcode1 { public: - - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { - int arg=0; - if(arg0>0) + int arg = 0; + if (arg0 > 0) { arg = runtime[0].mInteger; runtime.pop(); } - if (arg == 0) { MWGui::GuiMode modes[] = { MWGui::GM_Inventory, MWGui::GM_Container }; - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { if (MWBase::Environment::get().getWindowManager()->containsMode(modes[i])) MWBase::Environment::get().getWindowManager()->removeGuiMode(modes[i]); @@ -233,60 +224,49 @@ namespace MWScript class OpToggleMenus : public Interpreter::Opcode0 { public: - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { bool state = MWBase::Environment::get().getWindowManager()->toggleHud(); runtime.getContext().report(state ? "GUI -> On" : "GUI -> Off"); if (!state) { - while (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_None) // don't use isGuiMode, or we get an infinite loop for modal message boxes! + while (MWBase::Environment::get().getWindowManager()->getMode() + != MWGui::GM_None) // don't use isGuiMode, or we get an infinite loop for modal message boxes! MWBase::Environment::get().getWindowManager()->popGuiMode(); } } }; - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Gui::opcodeEnableBirthMenu, - new OpShowDialogue (MWGui::GM_Birth)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableClassMenu, - new OpShowDialogue (MWGui::GM_Class)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableNameMenu, - new OpShowDialogue (MWGui::GM_Name)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableRaceMenu, - new OpShowDialogue (MWGui::GM_Race)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsReviewMenu, - new OpShowDialogue (MWGui::GM_Review)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableLevelupMenu, - new OpShowDialogue (MWGui::GM_Levelup)); + interpreter.installSegment5(Compiler::Gui::opcodeEnableBirthMenu, MWGui::GM_Birth); + interpreter.installSegment5(Compiler::Gui::opcodeEnableClassMenu, MWGui::GM_Class); + interpreter.installSegment5(Compiler::Gui::opcodeEnableNameMenu, MWGui::GM_Name); + interpreter.installSegment5(Compiler::Gui::opcodeEnableRaceMenu, MWGui::GM_Race); + interpreter.installSegment5(Compiler::Gui::opcodeEnableStatsReviewMenu, MWGui::GM_Review); + interpreter.installSegment5(Compiler::Gui::opcodeEnableLevelupMenu, MWGui::GM_Levelup); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableInventoryMenu, - new OpEnableWindow (MWGui::GW_Inventory)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableMagicMenu, - new OpEnableWindow (MWGui::GW_Magic)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableMapMenu, - new OpEnableWindow (MWGui::GW_Map)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsMenu, - new OpEnableWindow (MWGui::GW_Stats)); + interpreter.installSegment5(Compiler::Gui::opcodeEnableInventoryMenu, MWGui::GW_Inventory); + interpreter.installSegment5(Compiler::Gui::opcodeEnableMagicMenu, MWGui::GW_Magic); + interpreter.installSegment5(Compiler::Gui::opcodeEnableMapMenu, MWGui::GW_Map); + interpreter.installSegment5(Compiler::Gui::opcodeEnableStatsMenu, MWGui::GW_Stats); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableRest, - new OpEnableRest ()); + interpreter.installSegment5(Compiler::Gui::opcodeEnableRest); - interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenu, - new OpShowRestMenu); - interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenuExplicit, new OpShowRestMenu); + interpreter.installSegment5>(Compiler::Gui::opcodeShowRestMenu); + interpreter.installSegment5>(Compiler::Gui::opcodeShowRestMenuExplicit); - interpreter.installSegment5 (Compiler::Gui::opcodeGetButtonPressed, new OpGetButtonPressed); + interpreter.installSegment5(Compiler::Gui::opcodeGetButtonPressed); - interpreter.installSegment5 (Compiler::Gui::opcodeToggleFogOfWar, new OpToggleFogOfWar); + interpreter.installSegment5(Compiler::Gui::opcodeToggleFogOfWar); - interpreter.installSegment5 (Compiler::Gui::opcodeToggleFullHelp, new OpToggleFullHelp); + interpreter.installSegment5(Compiler::Gui::opcodeToggleFullHelp); - interpreter.installSegment5 (Compiler::Gui::opcodeShowMap, new OpShowMap); - interpreter.installSegment5 (Compiler::Gui::opcodeFillMap, new OpFillMap); - interpreter.installSegment3 (Compiler::Gui::opcodeMenuTest, new OpMenuTest); - interpreter.installSegment5 (Compiler::Gui::opcodeToggleMenus, new OpToggleMenus); + interpreter.installSegment5(Compiler::Gui::opcodeShowMap); + interpreter.installSegment5(Compiler::Gui::opcodeFillMap); + interpreter.installSegment3(Compiler::Gui::opcodeMenuTest); + interpreter.installSegment5(Compiler::Gui::opcodeToggleMenus); } } } diff --git a/apps/openmw/mwscript/guiextensions.hpp b/apps/openmw/mwscript/guiextensions.hpp index ec775a51c..b44f954d6 100644 --- a/apps/openmw/mwscript/guiextensions.hpp +++ b/apps/openmw/mwscript/guiextensions.hpp @@ -15,10 +15,9 @@ namespace MWScript { /// \brief GUI-related script functionality namespace Gui - { - void installOpcodes (Interpreter::Interpreter& interpreter); + { + void installOpcodes(Interpreter::Interpreter& interpreter); } } #endif - diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 8dbb59b1f..ea08f566a 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -4,6 +4,7 @@ #include #include +#include /* Start of tes3mp addition @@ -23,23 +24,24 @@ #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" +#include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/inputmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/action.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" -#include "locals.hpp" #include "globalscripts.hpp" +#include "locals.hpp" namespace MWScript { +<<<<<<< HEAD /* Start of tes3mp addition @@ -71,10 +73,13 @@ namespace MWScript const MWWorld::Ptr InterpreterContext::getReferenceImp ( const std::string& id, bool activeOnly, bool doThrow) const +======= + const MWWorld::Ptr InterpreterContext::getReferenceImp(const ESM::RefId& id, bool activeOnly, bool doThrow) const +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { if (!id.empty()) { - return MWBase::Environment::get().getWorld()->getPtr (id, activeOnly); + return MWBase::Environment::get().getWorld()->getPtr(id, activeOnly); } else { @@ -88,56 +93,52 @@ namespace MWScript } } - const Locals& InterpreterContext::getMemberLocals (std::string& id, bool global) - const + const Locals& InterpreterContext::getMemberLocals(bool global, ESM::RefId& id) const { if (global) { - return MWBase::Environment::get().getScriptManager()->getGlobalScripts(). - getLocals (id); + return MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocals(id); } else { - const MWWorld::Ptr ptr = getReferenceImp (id, false); + const MWWorld::Ptr ptr = getReferenceImp(id, false); - id = ptr.getClass().getScript (ptr); + id = ptr.getClass().getScript(ptr); - ptr.getRefData().setLocals ( - *MWBase::Environment::get().getWorld()->getStore().get().find (id)); + ptr.getRefData().setLocals(*MWBase::Environment::get().getESMStore()->get().find(id)); return ptr.getRefData().getLocals(); } } - Locals& InterpreterContext::getMemberLocals (std::string& id, bool global) + Locals& InterpreterContext::getMemberLocals(bool global, ESM::RefId& id) { if (global) { - return MWBase::Environment::get().getScriptManager()->getGlobalScripts(). - getLocals (id); + return MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocals(id); } else { - const MWWorld::Ptr ptr = getReferenceImp (id, false); + const MWWorld::Ptr ptr = getReferenceImp(id, false); - id = ptr.getClass().getScript (ptr); + id = ptr.getClass().getScript(ptr); - ptr.getRefData().setLocals ( - *MWBase::Environment::get().getWorld()->getStore().get().find (id)); + ptr.getRefData().setLocals(*MWBase::Environment::get().getESMStore()->get().find(id)); return ptr.getRefData().getLocals(); } } - MissingImplicitRefError::MissingImplicitRefError() : std::runtime_error("no implicit reference") {} - - int InterpreterContext::findLocalVariableIndex (const std::string& scriptId, - const std::string& name, char type) const + MissingImplicitRefError::MissingImplicitRefError() + : std::runtime_error("no implicit reference") { - int index = MWBase::Environment::get().getScriptManager()->getLocals (scriptId). - searchIndex (type, name); + } - if (index!=-1) + int InterpreterContext::findLocalVariableIndex(const ESM::RefId& scriptId, std::string_view name, char type) const + { + int index = MWBase::Environment::get().getScriptManager()->getLocals(scriptId).searchIndex(type, name); + + if (index != -1) return index; std::ostringstream stream; @@ -146,22 +147,30 @@ namespace MWScript switch (type) { - case 's': stream << "short"; break; - case 'l': stream << "long"; break; - case 'f': stream << "float"; break; + case 's': + stream << "short"; + break; + case 'l': + stream << "long"; + break; + case 'f': + stream << "float"; + break; } stream << " member variable " << name << " in script " << scriptId; - throw std::runtime_error (stream.str().c_str()); + throw std::runtime_error(stream.str().c_str()); } - InterpreterContext::InterpreterContext (MWScript::Locals *locals, const MWWorld::Ptr& reference) - : mLocals (locals), mReference (reference) - {} + InterpreterContext::InterpreterContext(MWScript::Locals* locals, const MWWorld::Ptr& reference) + : mLocals(locals) + , mReference(reference) + { + } - InterpreterContext::InterpreterContext (std::shared_ptr globalScriptDesc) - : mLocals (&(globalScriptDesc->mLocals)) + InterpreterContext::InterpreterContext(std::shared_ptr globalScriptDesc) + : mLocals(&(globalScriptDesc->mLocals)) { const MWWorld::Ptr* ptr = globalScriptDesc->getPtrIfPresent(); // A nullptr here signifies that the script's target has not yet been resolved after loading the game. @@ -173,35 +182,37 @@ namespace MWScript mGlobalScriptDesc = globalScriptDesc; } - int InterpreterContext::getLocalShort (int index) const + ESM::RefId InterpreterContext::getTarget() const { - if (!mLocals) - throw std::runtime_error ("local variables not available in this context"); - - return mLocals->mShorts.at (index); + if (!mReference.isEmpty()) + return mReference.mRef->mRef.getRefId(); + else if (mGlobalScriptDesc) + return mGlobalScriptDesc->getId(); + return ESM::RefId(); } - int InterpreterContext::getLocalLong (int index) const + int InterpreterContext::getLocalShort(int index) const { if (!mLocals) - throw std::runtime_error ("local variables not available in this context"); + throw std::runtime_error("local variables not available in this context"); - return mLocals->mLongs.at (index); + return mLocals->mShorts.at(index); } - float InterpreterContext::getLocalFloat (int index) const + int InterpreterContext::getLocalLong(int index) const { if (!mLocals) - throw std::runtime_error ("local variables not available in this context"); + throw std::runtime_error("local variables not available in this context"); - return mLocals->mFloats.at (index); + return mLocals->mLongs.at(index); } - void InterpreterContext::setLocalShort (int index, int value) + float InterpreterContext::getLocalFloat(int index) const { if (!mLocals) - throw std::runtime_error ("local variables not available in this context"); + throw std::runtime_error("local variables not available in this context"); +<<<<<<< HEAD /* Start of tes3mp addition @@ -232,13 +243,17 @@ namespace MWScript /* End of tes3mp addition */ +======= + return mLocals->mFloats.at(index); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - void InterpreterContext::setLocalLong (int index, int value) + void InterpreterContext::setLocalShort(int index, int value) { if (!mLocals) - throw std::runtime_error ("local variables not available in this context"); + throw std::runtime_error("local variables not available in this context"); +<<<<<<< HEAD /* Start of tes3mp addition @@ -269,13 +284,17 @@ namespace MWScript /* End of tes3mp addition */ +======= + mLocals->mShorts.at(index) = value; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - void InterpreterContext::setLocalFloat (int index, float value) + void InterpreterContext::setLocalLong(int index, int value) { if (!mLocals) - throw std::runtime_error ("local variables not available in this context"); + throw std::runtime_error("local variables not available in this context"); +<<<<<<< HEAD /* Start of tes3mp addition @@ -312,39 +331,48 @@ namespace MWScript /* End of tes3mp addition */ +======= + mLocals->mLongs.at(index) = value; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - void InterpreterContext::messageBox (const std::string& message, - const std::vector& buttons) + void InterpreterContext::setLocalFloat(int index, float value) + { + if (!mLocals) + throw std::runtime_error("local variables not available in this context"); + + mLocals->mFloats.at(index) = value; + } + + void InterpreterContext::messageBox(std::string_view message, const std::vector& buttons) { if (buttons.empty()) - MWBase::Environment::get().getWindowManager()->messageBox (message); + MWBase::Environment::get().getWindowManager()->messageBox(message); else MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); } - void InterpreterContext::report (const std::string& message) + void InterpreterContext::report(const std::string& message) {} + + int InterpreterContext::getGlobalShort(std::string_view name) const { + return MWBase::Environment::get().getWorld()->getGlobalInt(name); } - int InterpreterContext::getGlobalShort (const std::string& name) const - { - return MWBase::Environment::get().getWorld()->getGlobalInt (name); - } - - int InterpreterContext::getGlobalLong (const std::string& name) const + int InterpreterContext::getGlobalLong(std::string_view name) const { // a global long is internally a float. - return MWBase::Environment::get().getWorld()->getGlobalInt (name); + return MWBase::Environment::get().getWorld()->getGlobalInt(name); } - float InterpreterContext::getGlobalFloat (const std::string& name) const + float InterpreterContext::getGlobalFloat(std::string_view name) const { - return MWBase::Environment::get().getWorld()->getGlobalFloat (name); + return MWBase::Environment::get().getWorld()->getGlobalFloat(name); } - void InterpreterContext::setGlobalShort (const std::string& name, int value) + void InterpreterContext::setGlobalShort(std::string_view name, int value) { +<<<<<<< HEAD /* Start of tes3mp addition @@ -371,10 +399,14 @@ namespace MWScript */ MWBase::Environment::get().getWorld()->setGlobalInt (name, value); +======= + MWBase::Environment::get().getWorld()->setGlobalInt(name, value); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - void InterpreterContext::setGlobalLong (const std::string& name, int value) + void InterpreterContext::setGlobalLong(std::string_view name, int value) { +<<<<<<< HEAD /* Start of tes3mp addition @@ -401,10 +433,14 @@ namespace MWScript */ MWBase::Environment::get().getWorld()->setGlobalInt (name, value); +======= + MWBase::Environment::get().getWorld()->setGlobalInt(name, value); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - void InterpreterContext::setGlobalFloat (const std::string& name, float value) + void InterpreterContext::setGlobalFloat(std::string_view name, float value) { +<<<<<<< HEAD /* Start of tes3mp addition @@ -436,41 +472,43 @@ namespace MWScript */ MWBase::Environment::get().getWorld()->setGlobalFloat (name, value); +======= + MWBase::Environment::get().getWorld()->setGlobalFloat(name, value); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } std::vector InterpreterContext::getGlobals() const { - const MWWorld::Store& globals = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store& globals = MWBase::Environment::get().getESMStore()->get(); std::vector ids; - for (auto& globalVariable : globals) + for (const auto& globalVariable : globals) { - ids.emplace_back(globalVariable.mId); + ids.emplace_back(globalVariable.mId.getRefIdString()); } return ids; } - char InterpreterContext::getGlobalType (const std::string& name) const + char InterpreterContext::getGlobalType(std::string_view name) const { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); return world->getGlobalVariableType(name); } - std::string InterpreterContext::getActionBinding(const std::string& targetAction) const + std::string InterpreterContext::getActionBinding(std::string_view targetAction) const { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); - std::vector actions = input->getActionKeySorting (); + const auto& actions = input->getActionKeySorting(); for (const int action : actions) { - std::string desc = input->getActionDescription (action); - if(desc == "") + std::string_view desc = input->getActionDescription(action); + if (desc.empty()) continue; - if(desc == targetAction) + if (desc == targetAction) { - if(input->joystickLastUsed()) + if (input->joystickLastUsed()) return input->getActionControllerBindingName(action); else return input->getActionKeyBindingName(action); @@ -480,7 +518,7 @@ namespace MWScript return "None"; } - std::string InterpreterContext::getActorName() const + std::string_view InterpreterContext::getActorName() const { const MWWorld::Ptr& ptr = getReferenceImp(); if (ptr.getClass().isNpc()) @@ -493,31 +531,31 @@ namespace MWScript return creature->mName; } - std::string InterpreterContext::getNPCRace() const + std::string_view InterpreterContext::getNPCRace() const { - ESM::NPC npc = *getReferenceImp().get()->mBase; - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mRace); + const ESM::NPC* npc = getReferenceImp().get()->mBase; + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(npc->mRace); return race->mName; } - std::string InterpreterContext::getNPCClass() const + std::string_view InterpreterContext::getNPCClass() const { - ESM::NPC npc = *getReferenceImp().get()->mBase; - const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mClass); + const ESM::NPC* npc = getReferenceImp().get()->mBase; + const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(npc->mClass); return class_->mName; } - std::string InterpreterContext::getNPCFaction() const + std::string_view InterpreterContext::getNPCFaction() const { - ESM::NPC npc = *getReferenceImp().get()->mBase; - const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mFaction); + const ESM::NPC* npc = getReferenceImp().get()->mBase; + const ESM::Faction* faction = MWBase::Environment::get().getESMStore()->get().find(npc->mFaction); return faction->mName; } - std::string InterpreterContext::getNPCRank() const + std::string_view InterpreterContext::getNPCRank() const { const MWWorld::Ptr& ptr = getReferenceImp(); - std::string faction = ptr.getClass().getPrimaryFaction(ptr); + const ESM::RefId& faction = ptr.getClass().getPrimaryFaction(ptr); if (faction.empty()) throw std::runtime_error("getNPCRank(): NPC is not in a faction"); @@ -525,44 +563,43 @@ namespace MWScript if (rank < 0 || rank > 9) throw std::runtime_error("getNPCRank(): invalid rank"); - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::ESMStore &store = world->getStore(); - const ESM::Faction *fact = store.get().find(faction); + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::ESMStore& store = world->getStore(); + const ESM::Faction* fact = store.get().find(faction); return fact->mRanks[rank]; } - std::string InterpreterContext::getPCName() const + std::string_view InterpreterContext::getPCName() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); - ESM::NPC player = *world->getPlayerPtr().get()->mBase; - return player.mName; + MWBase::World* world = MWBase::Environment::get().getWorld(); + return world->getPlayerPtr().get()->mBase->mName; } - std::string InterpreterContext::getPCRace() const + std::string_view InterpreterContext::getPCRace() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); - std::string race = world->getPlayerPtr().get()->mBase->mRace; + MWBase::World* world = MWBase::Environment::get().getWorld(); + const ESM::RefId& race = world->getPlayerPtr().get()->mBase->mRace; return world->getStore().get().find(race)->mName; } - std::string InterpreterContext::getPCClass() const + std::string_view InterpreterContext::getPCClass() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); - std::string class_ = world->getPlayerPtr().get()->mBase->mClass; + MWBase::World* world = MWBase::Environment::get().getWorld(); + const ESM::RefId& class_ = world->getPlayerPtr().get()->mBase->mClass; return world->getStore().get().find(class_)->mName; } - std::string InterpreterContext::getPCRank() const + std::string_view InterpreterContext::getPCRank() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - std::string factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); + const ESM::RefId& factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); if (factionId.empty()) throw std::runtime_error("getPCRank(): NPC is not in a faction"); - const std::map& ranks = player.getClass().getNpcStats (player).getFactionRanks(); - std::map::const_iterator it = ranks.find(Misc::StringUtils::lowerCase(factionId)); + const auto& ranks = player.getClass().getNpcStats(player).getFactionRanks(); + auto it = ranks.find(factionId); int rank = -1; if (it != ranks.end()) rank = it->second; @@ -572,26 +609,26 @@ namespace MWScript if (rank == -1) rank = 0; - const MWWorld::ESMStore &store = world->getStore(); - const ESM::Faction *faction = store.get().find(factionId); + const MWWorld::ESMStore& store = world->getStore(); + const ESM::Faction* faction = store.get().find(factionId); - if(rank < 0 || rank > 9) // there are only 10 ranks - return ""; + if (rank < 0 || rank > 9) // there are only 10 ranks + return {}; return faction->mRanks[rank]; } - std::string InterpreterContext::getPCNextRank() const + std::string_view InterpreterContext::getPCNextRank() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - std::string factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); + const ESM::RefId& factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); if (factionId.empty()) throw std::runtime_error("getPCNextRank(): NPC is not in a faction"); - const std::map& ranks = player.getClass().getNpcStats (player).getFactionRanks(); - std::map::const_iterator it = ranks.find(Misc::StringUtils::lowerCase(factionId)); + const auto& ranks = player.getClass().getNpcStats(player).getFactionRanks(); + auto it = ranks.find(factionId); int rank = -1; if (it != ranks.end()) rank = it->second; @@ -602,72 +639,74 @@ namespace MWScript if (rank > 9) rank = 9; - const MWWorld::ESMStore &store = world->getStore(); - const ESM::Faction *faction = store.get().find(factionId); + const MWWorld::ESMStore& store = world->getStore(); + const ESM::Faction* faction = store.get().find(factionId); - if(rank < 0) - return ""; + if (rank < 0) + return {}; return faction->mRanks[rank]; } int InterpreterContext::getPCBounty() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - return player.getClass().getNpcStats (player).getBounty(); + return player.getClass().getNpcStats(player).getBounty(); } - std::string InterpreterContext::getCurrentCellName() const + std::string_view InterpreterContext::getCurrentCellName() const { - return MWBase::Environment::get().getWorld()->getCellName(); + return MWBase::Environment::get().getWorld()->getCellName(); } - void InterpreterContext::executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor) + void InterpreterContext::executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) { - std::shared_ptr action = (ptr.getClass().activate(ptr, actor)); - action->execute (actor); + // MWScripted activations don't go through Lua because 1-frame delay can brake mwscripts. +#if 0 + MWBase::Environment::get().getLuaManager()->objectActivated(ptr, actor); + + // TODO: Enable this branch after implementing one of the options: + // 1) Pause this mwscript (or maybe all mwscripts) for one frame and continue from the same + // command when the activation is processed by Lua script. + // 2) Force Lua scripts to handle a zero-length extra frame right now, so when control + // returns to the mwscript, the activation is already processed. +#else + std::unique_ptr action = (ptr.getClass().activate(ptr, actor)); + action->execute(actor); if (action->getTarget() != MWWorld::Ptr() && action->getTarget() != ptr) { updatePtr(ptr, action->getTarget()); } +#endif } - int InterpreterContext::getMemberShort (const std::string& id, const std::string& name, - bool global) const + int InterpreterContext::getMemberShort(ESM::RefId id, std::string_view name, bool global) const { - std::string scriptId (id); + const Locals& locals = getMemberLocals(global, id); - const Locals& locals = getMemberLocals (scriptId, global); - - return locals.mShorts[findLocalVariableIndex (scriptId, name, 's')]; + return locals.mShorts[findLocalVariableIndex(id, name, 's')]; } - int InterpreterContext::getMemberLong (const std::string& id, const std::string& name, - bool global) const + int InterpreterContext::getMemberLong(ESM::RefId id, std::string_view name, bool global) const { - std::string scriptId (id); + const Locals& locals = getMemberLocals(global, id); - const Locals& locals = getMemberLocals (scriptId, global); - - return locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')]; + return locals.mLongs[findLocalVariableIndex(id, name, 'l')]; } - float InterpreterContext::getMemberFloat (const std::string& id, const std::string& name, - bool global) const + float InterpreterContext::getMemberFloat(ESM::RefId id, std::string_view name, bool global) const { - std::string scriptId (id); + const Locals& locals = getMemberLocals(global, id); - const Locals& locals = getMemberLocals (scriptId, global); - - return locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')]; + return locals.mFloats[findLocalVariableIndex(id, name, 'f')]; } - void InterpreterContext::setMemberShort (const std::string& id, const std::string& name, - int value, bool global) + void InterpreterContext::setMemberShort(ESM::RefId id, std::string_view name, int value, bool global) { - std::string scriptId (id); + Locals& locals = getMemberLocals(global, id); +<<<<<<< HEAD Locals& locals = getMemberLocals (scriptId, global); /* @@ -700,29 +739,28 @@ namespace MWScript /* End of tes3mp addition */ +======= + locals.mShorts[findLocalVariableIndex(id, name, 's')] = value; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - void InterpreterContext::setMemberLong (const std::string& id, const std::string& name, int value, bool global) + void InterpreterContext::setMemberLong(ESM::RefId id, std::string_view name, int value, bool global) { - std::string scriptId (id); + Locals& locals = getMemberLocals(global, id); - Locals& locals = getMemberLocals (scriptId, global); - - locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')] = value; + locals.mLongs[findLocalVariableIndex(id, name, 'l')] = value; } - void InterpreterContext::setMemberFloat (const std::string& id, const std::string& name, float value, bool global) + void InterpreterContext::setMemberFloat(ESM::RefId id, std::string_view name, float value, bool global) { - std::string scriptId (id); + Locals& locals = getMemberLocals(global, id); - Locals& locals = getMemberLocals (scriptId, global); - - locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')] = value; + locals.mFloats[findLocalVariableIndex(id, name, 'f')] = value; } - MWWorld::Ptr InterpreterContext::getReference(bool required) + MWWorld::Ptr InterpreterContext::getReference(bool required) const { - return getReferenceImp ("", true, required); + return getReferenceImp({}, true, required); } void InterpreterContext::updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated) diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 505cdd7b9..81bdb6a1c 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "globalscripts.hpp" @@ -16,37 +17,37 @@ namespace MWScript class MissingImplicitRefError : public std::runtime_error { - public: - MissingImplicitRefError(); + public: + MissingImplicitRefError(); }; class InterpreterContext : public Interpreter::Context { - Locals *mLocals; - mutable MWWorld::Ptr mReference; - std::shared_ptr mGlobalScriptDesc; + Locals* mLocals; + mutable MWWorld::Ptr mReference; + std::shared_ptr mGlobalScriptDesc; - /// If \a id is empty, a reference the script is run from is returned or in case - /// of a non-local script the reference derived from the target ID. - const MWWorld::Ptr getReferenceImp (const std::string& id = "", - bool activeOnly = false, bool doThrow=true) const; + /// If \a id is empty, a reference the script is run from is returned or in case + /// of a non-local script the reference derived from the target ID. + const MWWorld::Ptr getReferenceImp( + const ESM::RefId& id = ESM::RefId(), bool activeOnly = false, bool doThrow = true) const; - const Locals& getMemberLocals (std::string& id, bool global) const; - ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before + const Locals& getMemberLocals(bool global, ESM::RefId& id) const; + ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before - Locals& getMemberLocals (std::string& id, bool global); - ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before + Locals& getMemberLocals(bool global, ESM::RefId& id); + ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before - /// Throws an exception if local variable can't be found. - int findLocalVariableIndex (const std::string& scriptId, const std::string& name, - char type) const; + /// Throws an exception if local variable can't be found. + int findLocalVariableIndex(const ESM::RefId& scriptId, std::string_view name, char type) const; - public: - InterpreterContext (std::shared_ptr globalScriptDesc); + public: + InterpreterContext(std::shared_ptr globalScriptDesc); - InterpreterContext (MWScript::Locals *locals, const MWWorld::Ptr& reference); - ///< The ownership of \a locals is not transferred. 0-pointer allowed. + InterpreterContext(MWScript::Locals* locals, const MWWorld::Ptr& reference); + ///< The ownership of \a locals is not transferred. 0-pointer allowed. +<<<<<<< HEAD /* Start of tes3mp addition @@ -79,87 +80,92 @@ namespace MWScript */ int getLocalShort (int index) const override; +======= + ESM::RefId getTarget() const override; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - int getLocalLong (int index) const override; + int getLocalShort(int index) const override; - float getLocalFloat (int index) const override; + int getLocalLong(int index) const override; - void setLocalShort (int index, int value) override; + float getLocalFloat(int index) const override; - void setLocalLong (int index, int value) override; + void setLocalShort(int index, int value) override; - void setLocalFloat (int index, float value) override; + void setLocalLong(int index, int value) override; - using Interpreter::Context::messageBox; + void setLocalFloat(int index, float value) override; - void messageBox (const std::string& message, - const std::vector& buttons) override; + using Interpreter::Context::messageBox; - void report (const std::string& message) override; - ///< By default, do nothing. + void messageBox(std::string_view message, const std::vector& buttons) override; - int getGlobalShort (const std::string& name) const override; + void report(const std::string& message) override; + ///< By default, do nothing. - int getGlobalLong (const std::string& name) const override; + int getGlobalShort(std::string_view name) const override; - float getGlobalFloat (const std::string& name) const override; + int getGlobalLong(std::string_view name) const override; - void setGlobalShort (const std::string& name, int value) override; + float getGlobalFloat(std::string_view name) const override; - void setGlobalLong (const std::string& name, int value) override; + void setGlobalShort(std::string_view name, int value) override; - void setGlobalFloat (const std::string& name, float value) override; + void setGlobalLong(std::string_view name, int value) override; - std::vector getGlobals () const override; + void setGlobalFloat(std::string_view name, float value) override; - char getGlobalType (const std::string& name) const override; + std::vector getGlobals() const override; - std::string getActionBinding(const std::string& action) const override; + char getGlobalType(std::string_view name) const override; - std::string getActorName() const override; + std::string getActionBinding(std::string_view action) const override; - std::string getNPCRace() const override; + std::string_view getActorName() const override; - std::string getNPCClass() const override; + std::string_view getNPCRace() const override; - std::string getNPCFaction() const override; + std::string_view getNPCClass() const override; - std::string getNPCRank() const override; + std::string_view getNPCFaction() const override; - std::string getPCName() const override; + std::string_view getNPCRank() const override; - std::string getPCRace() const override; + std::string_view getPCName() const override; - std::string getPCClass() const override; + std::string_view getPCRace() const override; - std::string getPCRank() const override; + std::string_view getPCClass() const override; - std::string getPCNextRank() const override; + std::string_view getPCRank() const override; - int getPCBounty() const override; + std::string_view getPCNextRank() const override; - std::string getCurrentCellName() const override; + int getPCBounty() const override; - void executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor); - ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. + std::string_view getCurrentCellName() const override; - int getMemberShort (const std::string& id, const std::string& name, bool global) const override; + void executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor); + ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. - int getMemberLong (const std::string& id, const std::string& name, bool global) const override; + int getMemberShort(ESM::RefId id, std::string_view name, bool global) const override; - float getMemberFloat (const std::string& id, const std::string& name, bool global) const override; + int getMemberLong(ESM::RefId id, std::string_view name, bool global) const override; - void setMemberShort (const std::string& id, const std::string& name, int value, bool global) override; + float getMemberFloat(ESM::RefId id, std::string_view name, bool global) const override; - void setMemberLong (const std::string& id, const std::string& name, int value, bool global) override; + void setMemberShort(ESM::RefId id, std::string_view name, int value, bool global) override; - void setMemberFloat (const std::string& id, const std::string& name, float value, bool global) override; + void setMemberLong(ESM::RefId id, std::string_view name, int value, bool global) override; - MWWorld::Ptr getReference(bool required=true); - ///< Reference, that the script is running from (can be empty) + void setMemberFloat(ESM::RefId id, std::string_view name, float value, bool global) override; - void updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); - ///< Update the Ptr stored in mReference, if there is one stored there. Should be called after the reference has been moved to a new cell. + MWWorld::Ptr getReference(bool required = true) const; + ///< Reference, that the script is running from (can be empty) + + void updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); + ///< Update the Ptr stored in mReference, if there is one stored there. Should be called after the reference has + ///< been moved to a new cell. }; } diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 352dc67b3..f75ca4061 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -1,12 +1,11 @@ #include "locals.hpp" #include "globalscripts.hpp" -#include -#include -#include -#include #include -#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/scriptmanager.hpp" @@ -16,44 +15,47 @@ namespace MWScript { - void Locals::ensure (const std::string& scriptName) + void Locals::ensure(const ESM::RefId& scriptName) { if (!mInitialised) { - const ESM::Script *script = MWBase::Environment::get().getWorld()->getStore(). - get().find (scriptName); + const ESM::Script* script = MWBase::Environment::get().getESMStore()->get().find(scriptName); - configure (*script); + configure(*script); } } - Locals::Locals() : mInitialised (false) {} + Locals::Locals() + : mInitialised(false) + { + } - bool Locals::configure (const ESM::Script& script) + bool Locals::configure(const ESM::Script& script) { if (mInitialised) return false; - const Locals* global = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocalsIfPresent(script.mId); - if(global) + const GlobalScriptDesc* global + = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getScriptIfPresent(script.mId); + if (global) { - mShorts = global->mShorts; - mLongs = global->mLongs; - mFloats = global->mFloats; + mShorts = global->mLocals.mShorts; + mLongs = global->mLocals.mLongs; + mFloats = global->mLocals.mFloats; } else { - const Compiler::Locals& locals = - MWBase::Environment::get().getScriptManager()->getLocals (script.mId); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script.mId); mShorts.clear(); - mShorts.resize (locals.get ('s').size(), 0); + mShorts.resize(locals.get('s').size(), 0); mLongs.clear(); - mLongs.resize (locals.get ('l').size(), 0); + mLongs.resize(locals.get('l').size(), 0); mFloats.clear(); - mFloats.resize (locals.get ('f').size(), 0); + mFloats.resize(locals.get('f').size(), 0); } + mScriptId = script.mId; mInitialised = true; return true; } @@ -63,215 +65,184 @@ namespace MWScript return (mShorts.empty() && mLongs.empty() && mFloats.empty()); } - bool Locals::hasVar(const std::string &script, const std::string &var) + bool Locals::hasVar(const ESM::RefId& script, std::string_view var) { - try - { - ensure (script); + ensure(script); - const Compiler::Locals& locals = - MWBase::Environment::get().getScriptManager()->getLocals(script); - int index = locals.getIndex(var); - return (index != -1); - } - catch (const Compiler::SourceException&) + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + int index = locals.getIndex(var); + return (index != -1); + } + + double Locals::getVarAsDouble(const ESM::RefId& script, std::string_view var) + { + ensure(script); + + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + int index = locals.getIndex(var); + if (index == -1) + return 0; + switch (locals.getType(var)) { + case 's': + return mShorts.at(index); + + case 'l': + return mLongs.at(index); + + case 'f': + return mFloats.at(index); + default: + return 0; + } + } + + bool Locals::setVar(const ESM::RefId& script, std::string_view var, double val) + { + ensure(script); + + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + int index = locals.getIndex(var); + if (index == -1) return false; - } - } - - int Locals::getIntVar(const std::string &script, const std::string &var) - { - ensure (script); - - const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); - int index = locals.getIndex(var); - char type = locals.getType(var); - if(index != -1) + switch (locals.getType(var)) { - switch(type) - { - case 's': - return mShorts.at (index); + case 's': + mShorts.at(index) = static_cast(val); + break; - case 'l': - return mLongs.at (index); + case 'l': + mLongs.at(index) = static_cast(val); + break; - case 'f': - return static_cast(mFloats.at(index)); - default: - return 0; - } + case 'f': + mFloats.at(index) = static_cast(val); + break; } - return 0; + return true; } - float Locals::getFloatVar(const std::string &script, const std::string &var) - { - ensure (script); - - const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); - int index = locals.getIndex(var); - char type = locals.getType(var); - if(index != -1) - { - switch(type) - { - case 's': - return mShorts.at (index); - - case 'l': - return mLongs.at (index); - - case 'f': - return mFloats.at(index); - default: - return 0; - } - } - return 0; - } - - bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) - { - ensure (script); - - const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); - int index = locals.getIndex(var); - char type = locals.getType(var); - if(index != -1) - { - switch(type) - { - case 's': - mShorts.at (index) = val; break; - - case 'l': - mLongs.at (index) = val; break; - - case 'f': - mFloats.at(index) = static_cast(val); break; - } - return true; - } - return false; - } - - bool Locals::write (ESM::Locals& locals, const std::string& script) const + bool Locals::write(ESM::Locals& locals, const ESM::RefId& script) const { if (!mInitialised) return false; - try - { - const Compiler::Locals& declarations = - MWBase::Environment::get().getScriptManager()->getLocals(script); + const Compiler::Locals& declarations = MWBase::Environment::get().getScriptManager()->getLocals(script); - for (int i=0; i<3; ++i) + for (int i = 0; i < 3; ++i) + { + char type = 0; + + switch (i) { - char type = 0; + case 0: + type = 's'; + break; + case 1: + type = 'l'; + break; + case 2: + type = 'f'; + break; + } + + const std::vector& names = declarations.get(type); + + for (int i2 = 0; i2 < static_cast(names.size()); ++i2) + { + ESM::Variant value; switch (i) { - case 0: type = 's'; break; - case 1: type = 'l'; break; - case 2: type = 'f'; break; + case 0: + value.setType(ESM::VT_Int); + value.setInteger(mShorts.at(i2)); + break; + case 1: + value.setType(ESM::VT_Int); + value.setInteger(mLongs.at(i2)); + break; + case 2: + value.setType(ESM::VT_Float); + value.setFloat(mFloats.at(i2)); + break; } - const std::vector& names = declarations.get (type); - - for (int i2=0; i2 (names.size()); ++i2) - { - ESM::Variant value; - - switch (i) - { - case 0: value.setType (ESM::VT_Int); value.setInteger (mShorts.at (i2)); break; - case 1: value.setType (ESM::VT_Int); value.setInteger (mLongs.at (i2)); break; - case 2: value.setType (ESM::VT_Float); value.setFloat (mFloats.at (i2)); break; - } - - locals.mVariables.emplace_back (names[i2], value); - } + locals.mVariables.emplace_back(names[i2], value); } } - catch (const Compiler::SourceException&) - { - } return true; } - void Locals::read (const ESM::Locals& locals, const std::string& script) + void Locals::read(const ESM::Locals& locals, const ESM::RefId& script) { - ensure (script); + ensure(script); - try + const Compiler::Locals& declarations = MWBase::Environment::get().getScriptManager()->getLocals(script); + + int index = 0, numshorts = 0, numlongs = 0; + for (unsigned int v = 0; v < locals.mVariables.size(); ++v) { - const Compiler::Locals& declarations = - MWBase::Environment::get().getScriptManager()->getLocals(script); - - int index = 0, numshorts = 0, numlongs = 0; - for (unsigned int v=0; v >::const_iterator iter - = locals.mVariables.begin(); iter!=locals.mVariables.end(); ++iter,++index) - { - if (iter->first.empty()) - { - // no variable names available (this will happen for legacy, i.e. ESS-imported savegames only) - try - { - if (index >= numshorts+numlongs) - mFloats.at(index - (numshorts+numlongs)) = iter->second.getFloat(); - else if (index >= numshorts) - mLongs.at(index - numshorts) = iter->second.getInteger(); - else - mShorts.at(index) = iter->second.getInteger(); - } - catch (std::exception& e) - { - Log(Debug::Error) << "Failed to read local variable state for script '" - << script << "' (legacy format): " << e.what() - << "\nNum shorts: " << numshorts << " / " << mShorts.size() - << " Num longs: " << numlongs << " / " << mLongs.size(); - } - } - else - { - char type = declarations.getType (iter->first); - int index2 = declarations.getIndex (iter->first); - - // silently ignore locals that don't exist anymore - if (type == ' ' || index2 == -1) - continue; - - try - { - switch (type) - { - case 's': mShorts.at (index2) = iter->second.getInteger(); break; - case 'l': mLongs.at (index2) = iter->second.getInteger(); break; - case 'f': mFloats.at (index2) = iter->second.getFloat(); break; - } - } - catch (...) - { - // ignore type changes - /// \todo write to log - } - } - } + ESM::VarType type = locals.mVariables[v].second.getType(); + if (type == ESM::VT_Short) + ++numshorts; + else if (type == ESM::VT_Int) + ++numlongs; } - catch (const Compiler::SourceException&) + + for (std::vector>::const_iterator iter = locals.mVariables.begin(); + iter != locals.mVariables.end(); ++iter, ++index) { + if (iter->first.empty()) + { + // no variable names available (this will happen for legacy, i.e. ESS-imported savegames only) + try + { + if (index >= numshorts + numlongs) + mFloats.at(index - (numshorts + numlongs)) = iter->second.getFloat(); + else if (index >= numshorts) + mLongs.at(index - numshorts) = iter->second.getInteger(); + else + mShorts.at(index) = iter->second.getInteger(); + } + catch (std::exception& e) + { + Log(Debug::Error) << "Failed to read local variable state for script '" << script + << "' (legacy format): " << e.what() << "\nNum shorts: " << numshorts << " / " + << mShorts.size() << " Num longs: " << numlongs << " / " << mLongs.size(); + } + } + else + { + char type = declarations.getType(iter->first); + int index2 = declarations.getIndex(iter->first); + + // silently ignore locals that don't exist anymore + if (type == ' ' || index2 == -1) + continue; + + try + { + switch (type) + { + case 's': + mShorts.at(index2) = iter->second.getInteger(); + break; + case 'l': + mLongs.at(index2) = iter->second.getInteger(); + break; + case 'f': + mFloats.at(index2) = iter->second.getFloat(); + break; + } + } + catch (...) + { + // ignore type changes + /// \todo write to log + } + } } } } diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index d63411a94..1cc8fecb9 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -1,69 +1,79 @@ #ifndef GAME_SCRIPT_LOCALS_H #define GAME_SCRIPT_LOCALS_H +#include +#include #include +#include #include namespace ESM { class Script; struct Locals; + class RefId; } namespace MWScript { class Locals { - bool mInitialised; + bool mInitialised; + ESM::RefId mScriptId; - void ensure (const std::string& scriptName); + void ensure(const ESM::RefId& scriptName); - public: - std::vector mShorts; - std::vector mLongs; - std::vector mFloats; + public: + std::vector mShorts; + std::vector mLongs; + std::vector mFloats; - Locals(); + Locals(); - /// Are there any locals? - /// - /// \note Will return false, if locals have not been configured yet. - bool isEmpty() const; + const ESM::RefId& getScriptId() const { return mScriptId; } - /// \return Did the state of *this change from uninitialised to initialised? - bool configure (const ESM::Script& script); + /// Are there any locals? + /// + /// \note Will return false, if locals have not been configured yet. + bool isEmpty() const; - /// @note var needs to be in lowercase - /// - /// \note Locals will be automatically configured first, if necessary - bool setVarByInt(const std::string& script, const std::string& var, int val); + /// \return Did the state of *this change from uninitialised to initialised? + bool configure(const ESM::Script& script); - /// \note Locals will be automatically configured first, if necessary - // - // \note If it can not be determined if the variable exists, the error will be - // ignored and false will be returned. - bool hasVar(const std::string& script, const std::string& var); + /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary + bool setVarByInt(const ESM::RefId& script, std::string_view var, int val) { return setVar(script, var, val); } + bool setVar(const ESM::RefId& script, std::string_view var, double val); - /// if var does not exist, returns 0 - /// @note var needs to be in lowercase - /// - /// \note Locals will be automatically configured first, if necessary - int getIntVar (const std::string& script, const std::string& var); + /// \note Locals will be automatically configured first, if necessary + // + // \note If it can not be determined if the variable exists, the error will be + // ignored and false will be returned. + bool hasVar(const ESM::RefId& script, std::string_view var); - /// if var does not exist, returns 0 - /// @note var needs to be in lowercase - /// - /// \note Locals will be automatically configured first, if necessary - float getFloatVar (const std::string& script, const std::string& var); + /// if var does not exist, returns 0 + /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary + double getVarAsDouble(const ESM::RefId& script, std::string_view var); + int getIntVar(const ESM::RefId& script, std::string_view var) + { + return static_cast(getVarAsDouble(script, var)); + } + float getFloatVar(const ESM::RefId& script, std::string_view var) + { + return static_cast(getVarAsDouble(script, var)); + } - /// \note If locals have not been configured yet, no data is written. - /// - /// \return Locals written? - bool write (ESM::Locals& locals, const std::string& script) const; + /// \note If locals have not been configured yet, no data is written. + /// + /// \return Locals written? + bool write(ESM::Locals& locals, const ESM::RefId& script) const; - /// \note Locals will be automatically configured first, if necessary - void read (const ESM::Locals& locals, const std::string& script); + /// \note Locals will be automatically configured first, if necessary + void read(const ESM::Locals& locals, const ESM::RefId& script); }; } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 3ce755878..9ba67920f 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1,8 +1,11 @@ #include "miscextensions.hpp" +#include #include #include +#include +<<<<<<< HEAD /* Start of tes3mp addition @@ -18,43 +21,74 @@ */ #include +======= +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include +#include #include #include -#include #include +#include -#include #include +#include #include +#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #include #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" +#include "../mwbase/luamanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/player.hpp" -#include "../mwmechanics/aicast.hpp" -#include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/aicast.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellcasting.hpp" + +#include "../mwrender/animation.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -62,7 +96,42 @@ namespace { - void addToLevList(ESM::LevelledListBase* list, const std::string& itemId, int level) + struct TextureFetchVisitor : osg::NodeVisitor + { + std::vector> mTextures; + + TextureFetchVisitor(osg::NodeVisitor::TraversalMode mode = TRAVERSE_ALL_CHILDREN) + : osg::NodeVisitor(mode) + { + } + + void apply(osg::Node& node) override + { + const osg::StateSet* stateset = node.getStateSet(); + if (stateset) + { + const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + for (size_t i = 0; i < texAttributes.size(); i++) + { + const osg::StateAttribute* attr = stateset->getTextureAttribute(i, osg::StateAttribute::TEXTURE); + if (!attr) + continue; + const osg::Texture* texture = attr->asTexture(); + if (!texture) + continue; + const osg::Image* image = texture->getImage(0); + std::string fileName; + if (image) + fileName = image->getFileName(); + mTextures.emplace_back(texture->getName(), fileName); + } + } + + traverse(node); + } + }; + + void addToLevList(ESM::LevelledListBase* list, const ESM::RefId& itemId, int level) { for (auto& levelItem : list->mList) { @@ -76,7 +145,7 @@ namespace list->mList.push_back(item); } - void removeFromLevList(ESM::LevelledListBase* list, const std::string& itemId, int level) + void removeFromLevList(ESM::LevelledListBase* list, const ESM::RefId& itemId, int level) { // level of -1 removes all items with that itemId for (std::vector::iterator it = list->mList.begin(); it != list->mList.end();) @@ -86,7 +155,7 @@ namespace ++it; continue; } - if (Misc::StringUtils::ciEqual(itemId, it->mId)) + if (itemId == it->mId) it = list->mList.erase(it); else ++it; @@ -101,6 +170,7 @@ namespace MWScript { class OpMenuMode : public Interpreter::Opcode0 { +<<<<<<< HEAD public: void execute (Interpreter::Runtime& runtime) override @@ -116,76 +186,79 @@ namespace MWScript End of tes3mp change (major) */ } +======= + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWindowManager()->isGuiMode()); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; class OpRandom : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + Interpreter::Type_Integer limit = runtime[0].mInteger; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - Interpreter::Type_Integer limit = runtime[0].mInteger; - runtime.pop(); + if (limit < 0) + throw std::runtime_error("random: argument out of range (Don't be so negative!)"); - if (limit<0) - throw std::runtime_error ( - "random: argument out of range (Don't be so negative!)"); - - runtime.push (static_cast(::Misc::Rng::rollDice(limit))); // [o, limit) - } + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + runtime.push(static_cast(::Misc::Rng::rollDice(limit, prng))); // [o, limit) + } }; - template + template class OpStartScript : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr target = R()(runtime, false); - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name, target); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr target = R()(runtime, false); + ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript(name, target); + } }; class OpScriptRunning : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - runtime.push(MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name)); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + const ESM::RefId& name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + runtime.push(MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning(name)); + } }; class OpStopScript : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript (name); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + const ESM::RefId& name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript(name); + } }; class OpGetSecondsPassed : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push (MWBase::Environment::get().getFrameDuration()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getFrameDuration()); + } }; - template + template class OpEnable : public Interpreter::Opcode0 { +<<<<<<< HEAD public: void execute (Interpreter::Runtime& runtime) override @@ -232,11 +305,20 @@ namespace MWScript End of tes3mp change (major) */ } +======= + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getWorld()->enable(ptr); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; - template + template class OpDisable : public Interpreter::Opcode0 { +<<<<<<< HEAD public: void execute (Interpreter::Runtime& runtime) override @@ -283,32 +365,39 @@ namespace MWScript End of tes3mp change (major) */ } +======= + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getWorld()->disable(ptr); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; - template + template class OpGetDisabled : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - runtime.push (!ptr.getRefData().isEnabled()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push(!ptr.getRefData().isEnabled()); + } }; class OpPlayBink : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - std::string name = runtime.getStringLiteral (runtime[0].mInteger); + std::string_view name = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); bool allowSkipping = runtime[0].mInteger != 0; runtime.pop(); +<<<<<<< HEAD /* Start of tes3mp addition @@ -329,85 +418,82 @@ namespace MWScript */ MWBase::Environment::get().getWindowManager()->playVideo (name, allowSkipping); +======= + MWBase::Environment::get().getWindowManager()->playVideo(name, allowSkipping); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } }; class OpGetPcSleep : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - runtime.push (MWBase::Environment::get().getWindowManager ()->getPlayerSleeping()); + runtime.push(MWBase::Environment::get().getWindowManager()->getPlayerSleeping()); } }; class OpGetPcJumping : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWBase::World* world = MWBase::Environment::get().getWorld(); - runtime.push (world->getPlayer().getJumping()); + runtime.push(world->getPlayer().getJumping()); } }; class OpWakeUpPc : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - MWBase::Environment::get().getWindowManager ()->wakeUpPlayer(); + MWBase::Environment::get().getWindowManager()->wakeUpPlayer(); } }; class OpXBox : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push (0); - } + public: + void execute(Interpreter::Runtime& runtime) override { runtime.push(0); } }; template class OpOnActivate : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push (ptr.getRefData().onActivate()); - } + runtime.push(ptr.getRefData().onActivate()); + } }; template class OpActivate : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + InterpreterContext& context = static_cast(runtime.getContext()); - void execute (Interpreter::Runtime& runtime) override - { - InterpreterContext& context = - static_cast (runtime.getContext()); + MWWorld::Ptr ptr = R()(runtime); - MWWorld::Ptr ptr = R()(runtime); - - if (ptr.getRefData().activateByScript() || ptr.getContainerStore()) - context.executeActivation(ptr, MWMechanics::getPlayer()); - } + if (ptr.getRefData().activateByScript() || ptr.getContainerStore()) + context.executeActivation(ptr, MWMechanics::getPlayer()); + } }; - template + template class OpLock : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); +<<<<<<< HEAD void execute (Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); @@ -460,12 +546,35 @@ namespace MWScript { MWBase::Environment::get().getWorld()->activateDoor(ptr, MWWorld::DoorState::Idle); } +======= + Interpreter::Type_Integer lockLevel = ptr.getCellRef().getLockLevel(); + if (lockLevel == 0) + { // no lock level was ever set, set to 100 as default + lockLevel = 100; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + + if (arg0 == 1) + { + lockLevel = runtime[0].mInteger; + runtime.pop(); + } + + ptr.getCellRef().lock(lockLevel); + + // Instantly reset door to closed state + // This is done when using Lock in scripts, but not when using Lock spells. + if (ptr.getType() == ESM::Door::sRecordId && !ptr.getCellRef().getTeleport()) + { + MWBase::Environment::get().getWorld()->activateDoor(ptr, MWWorld::DoorState::Idle); + } + } }; - template + template class OpUnlock : public Interpreter::Opcode0 { +<<<<<<< HEAD public: void execute (Interpreter::Runtime& runtime) override @@ -502,158 +611,147 @@ namespace MWScript End of tes3mp change (major) */ } +======= + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + if (ptr.getCellRef().isLocked()) + ptr.getCellRef().unlock(); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; class OpToggleCollisionDebug : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode(MWRender::Render_CollisionDebug); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_CollisionDebug); - - runtime.getContext().report (enabled ? - "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off"); - } + runtime.getContext().report( + enabled ? "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off"); + } }; - class OpToggleCollisionBoxes : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode(MWRender::Render_CollisionDebug); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_CollisionDebug); - - runtime.getContext().report (enabled ? - "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off"); - } + runtime.getContext().report( + enabled ? "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off"); + } }; class OpToggleWireframe : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode(MWRender::Render_Wireframe); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_Wireframe); - - runtime.getContext().report (enabled ? - "Wireframe Rendering -> On" : "Wireframe Rendering -> Off"); - } + runtime.getContext().report(enabled ? "Wireframe Rendering -> On" : "Wireframe Rendering -> Off"); + } }; class OpToggleBorders : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleBorders(); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = - MWBase::Environment::get().getWorld()->toggleBorders(); - - runtime.getContext().report (enabled ? - "Border Rendering -> On" : "Border Rendering -> Off"); - } + runtime.getContext().report(enabled ? "Border Rendering -> On" : "Border Rendering -> Off"); + } }; class OpTogglePathgrid : public Interpreter::Opcode0 { public: - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { - bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_Pathgrid); + bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode(MWRender::Render_Pathgrid); - runtime.getContext().report (enabled ? - "Path Grid rendering -> On" : "Path Grid Rendering -> Off"); + runtime.getContext().report(enabled ? "Path Grid rendering -> On" : "Path Grid Rendering -> Off"); } }; class OpFadeIn : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + Interpreter::Type_Float time = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - Interpreter::Type_Float time = runtime[0].mFloat; - runtime.pop(); - - MWBase::Environment::get().getWindowManager()->fadeScreenIn(time, false); - } + MWBase::Environment::get().getWindowManager()->fadeScreenIn(time, false); + } }; class OpFadeOut : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + Interpreter::Type_Float time = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - Interpreter::Type_Float time = runtime[0].mFloat; - runtime.pop(); - - MWBase::Environment::get().getWindowManager()->fadeScreenOut(time, false); - } + MWBase::Environment::get().getWindowManager()->fadeScreenOut(time, false); + } }; class OpFadeTo : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + Interpreter::Type_Float alpha = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - Interpreter::Type_Float alpha = runtime[0].mFloat; - runtime.pop(); + Interpreter::Type_Float time = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float time = runtime[0].mFloat; - runtime.pop(); - - MWBase::Environment::get().getWindowManager()->fadeScreenTo(static_cast(alpha), time, false); - } + MWBase::Environment::get().getWindowManager()->fadeScreenTo(static_cast(alpha), time, false); + } }; class OpToggleWater : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.getContext().report(MWBase::Environment::get().getWorld()->toggleWater() ? "Water -> On" - : "Water -> Off"); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.getContext().report( + MWBase::Environment::get().getWorld()->toggleWater() ? "Water -> On" : "Water -> Off"); + } }; class OpToggleWorld : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.getContext().report(MWBase::Environment::get().getWorld()->toggleWorld() ? "World -> On" - : "World -> Off"); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.getContext().report( + MWBase::Environment::get().getWorld()->toggleWorld() ? "World -> On" : "World -> Off"); + } }; class OpDontSaveObject : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - // We are ignoring the DontSaveObject statement for now. Probably not worth - // bothering with. The incompatibility we are creating should be marginal at most. - } + public: + void execute(Interpreter::Runtime& runtime) override + { + // We are ignoring the DontSaveObject statement for now. Probably not worth + // bothering with. The incompatibility we are creating should be marginal at most. + } }; class OpPcForce1stPerson : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { if (!MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(true); @@ -662,7 +760,7 @@ namespace MWScript class OpPcForce3rdPerson : public Interpreter::Opcode0 { - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { if (MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(true); @@ -683,16 +781,17 @@ namespace MWScript static bool sActivate; public: - - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { - MWBase::World *world = - MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); - if (world->toggleVanityMode(sActivate)) { + if (world->toggleVanityMode(sActivate)) + { runtime.getContext().report(sActivate ? "Vanity Mode -> On" : "Vanity Mode -> Off"); sActivate = !sActivate; - } else { + } + else + { runtime.getContext().report("Vanity Mode -> No"); } } @@ -702,308 +801,310 @@ namespace MWScript template class OpGetLocked : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push (ptr.getCellRef().getLockLevel() > 0); - } + runtime.push(ptr.getCellRef().isLocked()); + } }; template class OpGetEffect : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + std::string_view effect = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + if (!ptr.getClass().isActor()) { - MWWorld::Ptr ptr = R()(runtime); + runtime.push(0); + return; + } - std::string effect = runtime.getStringLiteral(runtime[0].mInteger); - runtime.pop(); + long key; - if (!ptr.getClass().isActor()) + if (const auto k = ::Misc::StringUtils::toNumeric(effect.data()); + k.has_value() && *k >= 0 && *k <= 32767) + key = *k; + else + key = ESM::MagicEffect::effectGmstIdToIndex(effect); + + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + + const MWMechanics::MagicEffects& effects = stats.getMagicEffects(); + + for (const auto& activeEffect : effects) + { + if (activeEffect.first.mId == key && activeEffect.second.getModifier() > 0) { - runtime.push(0); + runtime.push(1); return; } - - char *end; - long key = strtol(effect.c_str(), &end, 10); - if(key < 0 || key > 32767 || *end != '\0') - key = ESM::MagicEffect::effectStringToId(effect); - - const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - - MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects(); - effects += stats.getActiveSpells().getMagicEffects(); - if (ptr.getClass().hasInventoryStore(ptr) && !stats.isDeathAnimationFinished()) - { - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - effects += store.getMagicEffects(); - } - - for (const auto& activeEffect : effects) - { - if (activeEffect.first.mId == key && activeEffect.second.getModifier() > 0) - { - runtime.push(1); - return; - } - } - runtime.push(0); - } + } + runtime.push(0); + } }; - template + template class OpAddSoulGem : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId creature = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string creature = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + ESM::RefId gem = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string gem = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + if (!ptr.getClass().hasInventoryStore(ptr)) + return; - if (!ptr.getClass().hasInventoryStore(ptr)) - return; + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + store.get().find( + creature); // This line throws an exception if it can't find the creature - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - store.get().find(creature); // This line throws an exception if it can't find the creature + MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1); - MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1, ptr); + // Set the soul on just one of the gems, not the whole stack + item.getContainerStore()->unstack(item); + item.getCellRef().setSoul(creature); - // Set the soul on just one of the gems, not the whole stack - item.getContainerStore()->unstack(item, ptr); - item.getCellRef().setSoul(creature); - - // Restack the gem with other gems with the same soul - item.getContainerStore()->restack(item); - } + // Restack the gem with other gems with the same soul + item.getContainerStore()->restack(item); + } }; - template + template class OpRemoveSoulGem : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId soul = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string soul = runtime.getStringLiteral (runtime[0].mInteger); + // throw away additional arguments + for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - // throw away additional arguments - for (unsigned int i=0; igetCellRef().getSoul() == soul) { - if (::Misc::StringUtils::ciEqual(it->getCellRef().getSoul(), soul)) - { - store.remove(*it, 1, ptr); - return; - } + store.remove(*it, 1); + return; } } + } }; - template + template class OpDrop : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { - void execute (Interpreter::Runtime& runtime) override + MWWorld::Ptr ptr = R()(runtime); + + ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + Interpreter::Type_Integer amount = runtime[0].mInteger; + runtime.pop(); + + if (amount < 0) + throw std::runtime_error("amount must be non-negative"); + + // no-op + if (amount == 0) + return; + + if (!ptr.getClass().isActor()) + return; + + if (ptr.getClass().hasInventoryStore(ptr)) { - - MWWorld::Ptr ptr = R()(runtime); - - std::string item = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - Interpreter::Type_Integer amount = runtime[0].mInteger; - runtime.pop(); - - if (amount<0) - throw std::runtime_error ("amount must be non-negative"); - - // no-op - if (amount == 0) - return; - - if (!ptr.getClass().isActor()) - return; - - if (ptr.getClass().hasInventoryStore(ptr)) + // Prefer dropping unequipped items first; re-stack if possible by unequipping items before dropping + // them. + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + int numNotEquipped = store.count(item); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { - // Prefer dropping unequipped items first; re-stack if possible by unequipping items before dropping them. - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - int numNotEquipped = store.count(item); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + MWWorld::ConstContainerStoreIterator it = store.getSlot(slot); + if (it != store.end() && it->getCellRef().getRefId() == item) { - MWWorld::ConstContainerStoreIterator it = store.getSlot (slot); - if (it != store.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) - { - numNotEquipped -= it->getRefData().getCount(); - } - } - - for (int slot = 0; slot < MWWorld::InventoryStore::Slots && amount > numNotEquipped; ++slot) - { - MWWorld::ContainerStoreIterator it = store.getSlot (slot); - if (it != store.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) - { - int numToRemove = std::min(amount - numNotEquipped, it->getRefData().getCount()); - store.unequipItemQuantity(*it, ptr, numToRemove); - numNotEquipped += numToRemove; - } - } - - for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item) && !store.isEquipped(*iter)) - { - int removed = store.remove(*iter, amount, ptr); - MWWorld::Ptr dropped = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); - dropped.getCellRef().setOwner(""); - - amount -= removed; - - if (amount <= 0) - break; - } + numNotEquipped -= it->getRefData().getCount(); } } - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), item, 1); - MWWorld::Ptr itemPtr(ref.getPtr()); - if (amount > 0) + for (int slot = 0; slot < MWWorld::InventoryStore::Slots && amount > numNotEquipped; ++slot) { - if (itemPtr.getClass().getScript(itemPtr).empty()) + MWWorld::ContainerStoreIterator it = store.getSlot(slot); + if (it != store.end() && it->getCellRef().getRefId() == item) { - MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, itemPtr, amount); - } - else - { - // Dropping one item per time to prevent making stacks of scripted items - for (int i = 0; i < amount; i++) - MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, itemPtr, 1); + int numToRemove = std::min(amount - numNotEquipped, it->getRefData().getCount()); + store.unequipItemQuantity(*it, numToRemove); + numNotEquipped += numToRemove; } } - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, itemPtr.getClass().getDownSoundId(itemPtr), 1.f, 1.f); + for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) + { + if (iter->getCellRef().getRefId() == item && !store.isEquipped(*iter)) + { + int removed = store.remove(*iter, amount); + MWWorld::Ptr dropped + = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); + dropped.getCellRef().setOwner(ESM::RefId()); + + amount -= removed; + + if (amount <= 0) + break; + } + } } + + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), item, 1); + MWWorld::Ptr itemPtr(ref.getPtr()); + if (amount > 0) + { + if (itemPtr.getClass().getScript(itemPtr).empty()) + { + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, itemPtr, amount); + } + else + { + // Dropping one item per time to prevent making stacks of scripted items + for (int i = 0; i < amount; i++) + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, itemPtr, 1); + } + } + + MWBase::Environment::get().getSoundManager()->playSound3D( + ptr, itemPtr.getClass().getDownSoundId(itemPtr), 1.f, 1.f); + } }; - template + template class OpDropSoulGem : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { - void execute (Interpreter::Runtime& runtime) override + MWWorld::Ptr ptr = R()(runtime); + + ESM::RefId soul = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + if (!ptr.getClass().hasInventoryStore(ptr)) + return; + + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + + for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) { - - MWWorld::Ptr ptr = R()(runtime); - - std::string soul = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - if (!ptr.getClass().hasInventoryStore(ptr)) - return; - - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - - for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) + if (iter->getCellRef().getSoul() == soul) { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().getSoul(), soul)) - { - MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1); - store.remove(*iter, 1, ptr); - break; - } + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1); + store.remove(*iter, 1); + break; } } + } }; template class OpGetAttacked : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push(ptr.getClass().getCreatureStats (ptr).getAttacked ()); - } + runtime.push(ptr.getClass().getCreatureStats(ptr).getAttacked()); + } }; template class OpGetWeaponDrawn : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + auto& cls = ptr.getClass(); + if (!cls.hasInventoryStore(ptr) && !cls.isBipedal(ptr)) { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push((ptr.getClass().hasInventoryStore(ptr) || ptr.getClass().isBipedal(ptr)) && - ptr.getClass().getCreatureStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); + runtime.push(0); + return; } + + if (cls.getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState::Weapon) + { + runtime.push(0); + return; + } + + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); + runtime.push(anim && anim->getWeaponsShown()); + } }; template class OpGetSpellReadied : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push(ptr.getClass().getCreatureStats (ptr).getDrawState () == MWMechanics::DrawState_Spell); - } + runtime.push(ptr.getClass().getCreatureStats(ptr).getDrawState() == MWMechanics::DrawState::Spell); + } }; template class OpGetSpellEffects : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + if (!ptr.getClass().isActor()) { - MWWorld::Ptr ptr = R()(runtime); - std::string id = runtime.getStringLiteral(runtime[0].mInteger); - runtime.pop(); - - if (!ptr.getClass().isActor()) - { - runtime.push(0); - return; - } - - const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - runtime.push(stats.getActiveSpells().isSpellActive(id) || stats.getSpells().isSpellActive(id)); + runtime.push(0); + return; } + + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + runtime.push(stats.getActiveSpells().isSpellActive(id)); + } }; class OpGetCurrentTime : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { runtime.push(MWBase::Environment::get().getWorld()->getTimeStamp().getHour()); } @@ -1012,8 +1113,14 @@ namespace MWScript template class OpSetDelete : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + int parameter = runtime[0].mInteger; + runtime.pop(); +<<<<<<< HEAD void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); @@ -1061,235 +1168,235 @@ namespace MWScript else throw std::runtime_error("SetDelete: unexpected parameter"); } +======= + if (parameter == 1) + MWBase::Environment::get().getWorld()->deleteObject(ptr); + else if (parameter == 0) + MWBase::Environment::get().getWorld()->undeleteObject(ptr); + else + throw std::runtime_error("SetDelete: unexpected parameter"); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; class OpGetSquareRoot : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + float param = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - float param = runtime[0].mFloat; - runtime.pop(); + if (param < 0) + throw std::runtime_error("square root of negative number (we aren't that imaginary)"); - runtime.push(std::sqrt (param)); - } + runtime.push(std::sqrt(param)); + } }; template class OpFall : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - } + public: + void execute(Interpreter::Runtime& runtime) override {} }; template class OpGetStandingPc : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWBase::Environment::get().getWorld()->getPlayerStandingOn(ptr)); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push(MWBase::Environment::get().getWorld()->getPlayerStandingOn(ptr)); + } }; template class OpGetStandingActor : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWBase::Environment::get().getWorld()->getActorStandingOn(ptr)); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push(MWBase::Environment::get().getWorld()->getActorStandingOn(ptr)); + } }; template class OpGetCollidingPc : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWBase::Environment::get().getWorld()->getPlayerCollidingWith(ptr)); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push(MWBase::Environment::get().getWorld()->getPlayerCollidingWith(ptr)); + } }; template class OpGetCollidingActor : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWBase::Environment::get().getWorld()->getActorCollidingWith(ptr)); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push(MWBase::Environment::get().getWorld()->getActorCollidingWith(ptr)); + } }; template class OpHurtStandingActor : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + float healthDiffPerSecond = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - float healthDiffPerSecond = runtime[0].mFloat; - runtime.pop(); - - MWBase::Environment::get().getWorld()->hurtStandingActors(ptr, healthDiffPerSecond); - } + MWBase::Environment::get().getWorld()->hurtStandingActors(ptr, healthDiffPerSecond); + } }; template class OpHurtCollidingActor : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + float healthDiffPerSecond = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - float healthDiffPerSecond = runtime[0].mFloat; - runtime.pop(); - - MWBase::Environment::get().getWorld()->hurtCollidingActors(ptr, healthDiffPerSecond); - } + MWBase::Environment::get().getWorld()->hurtCollidingActors(ptr, healthDiffPerSecond); + } }; class OpGetWindSpeed : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push(MWBase::Environment::get().getWorld()->getWindSpeed()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWorld()->getWindSpeed()); + } }; template class OpHitOnMe : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId objectID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject())); - - stats.setLastHitObject(std::string()); - } + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + bool hit = objectID == stats.getLastHitObject(); + runtime.push(hit); + if (hit) + stats.clearLastHitObject(); + } }; template class OpHitAttemptOnMe : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId objectID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitAttemptObject())); - - stats.setLastHitAttemptObject(std::string()); - } + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + bool hit = objectID == stats.getLastHitAttemptObject(); + runtime.push(hit); + if (hit) + stats.clearLastHitAttemptObject(); + } }; template class OpEnableTeleporting : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - world->enableTeleporting(Enable); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->enableTeleporting(Enable); + } }; template class OpEnableLevitation : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - world->enableLevitation(Enable); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->enableLevitation(Enable); + } }; template class OpShow : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime, false); - std::string var = runtime.getStringLiteral(runtime[0].mInteger); + std::string_view var = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); std::stringstream output; if (!ptr.isEmpty()) { - const std::string& script = ptr.getClass().getScript(ptr); + ESM::RefId script = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); if (!script.empty()) { - const Compiler::Locals& locals = - MWBase::Environment::get().getScriptManager()->getLocals(script); + const Compiler::Locals& locals + = MWBase::Environment::get().getScriptManager()->getLocals(script); char type = locals.getType(var); - std::string refId = ptr.getCellRef().getRefId(); + std::string refId = ptr.getCellRef().getRefId().getRefIdString(); if (refId.find(' ') != std::string::npos) refId = '"' + refId + '"'; switch (type) { - case 'l': - case 's': - output << refId << "." << var << " = " << ptr.getRefData().getLocals().getIntVar(script, var); - break; - case 'f': - output << refId << "." << var << " = " << ptr.getRefData().getLocals().getFloatVar(script, var); - break; + case 'l': + case 's': + output << refId << "." << var << " = " + << ptr.getRefData().getLocals().getIntVar(script, var); + break; + case 'f': + output << refId << "." << var << " = " + << ptr.getRefData().getLocals().getFloatVar(script, var); + break; } } } if (output.rdbuf()->in_avail() == 0) { - MWBase::World *world = MWBase::Environment::get().getWorld(); - char type = world->getGlobalVariableType (var); + MWBase::World* world = MWBase::Environment::get().getWorld(); + char type = world->getGlobalVariableType(var); switch (type) { - case 's': - output << var << " = " << runtime.getContext().getGlobalShort (var); - break; - case 'l': - output << var << " = " << runtime.getContext().getGlobalLong (var); - break; - case 'f': - output << var << " = " << runtime.getContext().getGlobalFloat (var); - break; - default: - output << "unknown variable"; + case 's': + output << var << " = " << runtime.getContext().getGlobalShort(var); + break; + case 'l': + output << var << " = " << runtime.getContext().getGlobalLong(var); + break; + case 'f': + output << var << " = " << runtime.getContext().getGlobalFloat(var); + break; + default: + output << "unknown variable"; } } runtime.getContext().report(output.str()); @@ -1299,73 +1406,74 @@ namespace MWScript template class OpShowVars : public Interpreter::Opcode0 { - void printLocalVars(Interpreter::Runtime &runtime, const MWWorld::Ptr &ptr) + void printLocalVars(Interpreter::Runtime& runtime, const MWWorld::Ptr& ptr) { std::stringstream str; - const std::string script = ptr.getClass().getScript(ptr); - if(script.empty()) - str<< ptr.getCellRef().getRefId()<<" does not have a script."; + const ESM::RefId& script = ptr.getClass().getScript(ptr); + if (script.empty()) + str << ptr.getCellRef().getRefId() << " does not have a script."; else { - str<< "Local variables for "<getLocals(script); + const Locals& locals = ptr.getRefData().getLocals(); + const Compiler::Locals& complocals + = MWBase::Environment::get().getScriptManager()->getLocals(script); - const std::vector *names = &complocals.get('s'); - for(size_t i = 0;i < names->size();++i) + const std::vector* names = &complocals.get('s'); + for (size_t i = 0; i < names->size(); ++i) { - if(i >= locals.mShorts.size()) + if (i >= locals.mShorts.size()) break; - str<size();++i) + for (size_t i = 0; i < names->size(); ++i) { - if(i >= locals.mLongs.size()) + if (i >= locals.mLongs.size()) break; - str<size();++i) + for (size_t i = 0; i < names->size(); ++i) { - if(i >= locals.mFloats.size()) + if (i >= locals.mFloats.size()) break; - str< names = runtime.getContext().getGlobals(); - for(size_t i = 0;i < names.size();++i) + for (size_t i = 0; i < names.size(); ++i) { - char type = world->getGlobalVariableType (names[i]); + char type = world->getGlobalVariableType(names[i]); str << std::endl << " " << names[i] << " = "; switch (type) { case 's': - str << runtime.getContext().getGlobalShort (names[i]) << " (short)"; + str << runtime.getContext().getGlobalShort(names[i]) << " (short)"; break; case 'l': - str << runtime.getContext().getGlobalLong (names[i]) << " (long)"; + str << runtime.getContext().getGlobalLong(names[i]) << " (long)"; break; case 'f': - str << runtime.getContext().getGlobalFloat (names[i]) << " (float)"; + str << runtime.getContext().getGlobalFloat(names[i]) << " (float)"; break; default: @@ -1374,7 +1482,7 @@ namespace MWScript } } - runtime.getContext().report (str.str()); + runtime.getContext().report(str.str()); } public: @@ -1394,7 +1502,7 @@ namespace MWScript class OpToggleScripts : public Interpreter::Opcode0 { public: - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getWorld()->toggleScripts(); @@ -1404,46 +1512,50 @@ namespace MWScript class OpToggleGodMode : public Interpreter::Opcode0 { - public: - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = MWBase::Environment::get().getWorld()->toggleGodMode(); + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleGodMode(); - runtime.getContext().report (enabled ? "God Mode -> On" : "God Mode -> Off"); - } + runtime.getContext().report(enabled ? "God Mode -> On" : "God Mode -> Off"); + } }; template class OpCast : public Interpreter::Opcode0 { public: - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - std::string spellId = runtime.getStringLiteral (runtime[0].mInteger); + ESM::RefId spellId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral (runtime[0].mInteger)); + ESM::RefId targetId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().search(spellId); if (!spell) { - runtime.getContext().report("spellcasting failed: cannot find spell \""+spellId+"\""); + runtime.getContext().report( + "spellcasting failed: cannot find spell \"" + spellId.getRefIdString() + "\""); return; } if (ptr == MWMechanics::getPlayer()) { - MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spellId); + MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spell->mId); return; } if (ptr.getClass().isActor()) { - MWMechanics::AiCast castPackage(targetId, spellId, true); - ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr); + if (!MWBase::Environment::get().getMechanicsManager()->isCastingSpell(ptr)) + { + MWMechanics::AiCast castPackage(targetId, spell->mId, true); + ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(castPackage, ptr); + } return; } @@ -1452,7 +1564,7 @@ namespace MWScript return; MWMechanics::CastSpell cast(ptr, target, false, true); - cast.playSpellCastingEffects(spell->mId, false); + cast.playSpellCastingEffects(spell); cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); @@ -1463,30 +1575,34 @@ namespace MWScript class OpExplodeSpell : public Interpreter::Opcode0 { public: - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - std::string spellId = runtime.getStringLiteral (runtime[0].mInteger); + ESM::RefId spellId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().search(spellId); if (!spell) { - runtime.getContext().report("spellcasting failed: cannot find spell \""+spellId+"\""); + runtime.getContext().report( + "spellcasting failed: cannot find spell \"" + spellId.getRefIdString() + "\""); return; } if (ptr == MWMechanics::getPlayer()) { - MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spellId); + MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spell->mId); return; } if (ptr.getClass().isActor()) { - MWMechanics::AiCast castPackage(ptr.getCellRef().getRefId(), spellId, true); - ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr); + if (!MWBase::Environment::get().getMechanicsManager()->isCastingSpell(ptr)) + { + MWMechanics::AiCast castPackage(ptr.getCellRef().getRefId(), spell->mId, true); + ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(castPackage, ptr); + } return; } @@ -1500,7 +1616,7 @@ namespace MWScript class OpGoToJail : public Interpreter::Opcode0 { public: - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWBase::World* world = MWBase::Environment::get().getWorld(); world->goToJail(); @@ -1510,7 +1626,7 @@ namespace MWScript class OpPayFine : public Interpreter::Opcode0 { public: - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); @@ -1522,7 +1638,7 @@ namespace MWScript class OpPayFineThief : public Interpreter::Opcode0 { public: - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); @@ -1532,29 +1648,27 @@ namespace MWScript class OpGetPcInJail : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime &runtime) override - { - runtime.push (MWBase::Environment::get().getWorld()->isPlayerInJail()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWorld()->isPlayerInJail()); + } }; class OpGetPcTraveling : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime &runtime) override - { - runtime.push (MWBase::Environment::get().getWorld()->isPlayerTraveling()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWorld()->isPlayerTraveling()); + } }; template class OpBetaComment : public Interpreter::Opcode1 { public: - void execute(Interpreter::Runtime &runtime, unsigned int arg0) override + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); @@ -1563,20 +1677,27 @@ namespace MWScript msg << "Report time: "; std::time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - msg << std::put_time(std::gmtime(¤tTime), "%Y.%m.%d %T UTC") << std::endl; + tm timeinfo{}; +#ifdef _WIN32 + gmtime_s(&timeinfo, ¤tTime); +#else + gmtime_r(¤tTime, &timeinfo); +#endif + msg << std::put_time(&timeinfo, "%Y.%m.%d %T UTC") << std::endl; - msg << "Content file: "; + msg << "Content file: " << ptr.getCellRef().getRefNum().mContentFile; if (!ptr.getCellRef().hasContentFile()) - msg << "[None]" << std::endl; + msg << " [None]" << std::endl; else { std::vector contentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); - msg << contentFiles.at (ptr.getCellRef().getRefNum().mContentFile) << std::endl; - msg << "RefNum: " << ptr.getCellRef().getRefNum().mIndex << std::endl; + msg << " [" << contentFiles.at(ptr.getCellRef().getRefNum().mContentFile) << "]" << std::endl; } + msg << "RefNum: " << ptr.getCellRef().getRefNum().mIndex << std::endl; + if (ptr.getRefData().isDeletedByContentFile()) msg << "[Deleted by content file]" << std::endl; if (!ptr.getRefData().getCount()) @@ -1590,17 +1711,65 @@ namespace MWScript MWWorld::CellStore* cell = ptr.getCell(); msg << "Cell: " << MWBase::Environment::get().getWorld()->getCellName(cell) << std::endl; if (cell->getCell()->isExterior()) - msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() << std::endl; - osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); + msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() + << std::endl; + osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); msg << "Coordinates: " << pos.x() << " " << pos.y() << " " << pos.z() << std::endl; auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - std::string model = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), vfs); + std::string model + = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), vfs); msg << "Model: " << model << std::endl; - if(!model.empty()) + if (!model.empty()) { const std::string archive = vfs->getArchive(model); - if(!archive.empty()) + if (!archive.empty()) msg << "(" << archive << ")" << std::endl; + TextureFetchVisitor visitor; + SceneUtil::PositionAttitudeTransform* baseNode = ptr.getRefData().getBaseNode(); + if (baseNode) + baseNode->accept(visitor); + // The instance might not have a physical model due to paging or scripting. + // If this is the case, fall back to the template + if (visitor.mTextures.empty()) + { + Resource::SceneManager* sceneManager + = MWBase::Environment::get().getResourceSystem()->getSceneManager(); + const_cast(sceneManager->getTemplate(model).get())->accept(visitor); + msg << "Bound textures: [None]" << std::endl; + if (!visitor.mTextures.empty()) + msg << "Model textures: "; + } + else + { + msg << "Bound textures: "; + } + if (!visitor.mTextures.empty()) + { + msg << std::endl; + std::string lastTextureSrc; + for (auto& [textureName, fileName] : visitor.mTextures) + { + std::string textureSrc; + if (!fileName.empty()) + textureSrc = vfs->getArchive(fileName); + + if (lastTextureSrc.empty() || textureSrc != lastTextureSrc) + { + lastTextureSrc = textureSrc; + if (lastTextureSrc.empty()) + lastTextureSrc = "[No Source]"; + + msg << " " << lastTextureSrc << std::endl; + } + msg << " "; + msg << (textureName.empty() ? "[Anonymous]: " : textureName) << ": "; + msg << (fileName.empty() ? "[No File]" : fileName) << std::endl; + } + } + else + { + msg << "[None]" << std::endl; + } } if (!ptr.getClass().getScript(ptr).empty()) msg << "Script: " << ptr.getClass().getScript(ptr) << std::endl; @@ -1608,7 +1777,7 @@ namespace MWScript while (arg0 > 0) { - std::string notes = runtime.getStringLiteral (runtime[0].mInteger); + std::string_view notes = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); if (!notes.empty()) msg << "Notes: " << notes << std::endl; @@ -1624,72 +1793,76 @@ namespace MWScript class OpAddToLevCreature : public Interpreter::Opcode0 { public: - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { - const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + const ESM::RefId& levId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - const std::string& creatureId = runtime.getStringLiteral(runtime[0].mInteger); + const ESM::RefId& creatureId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); int level = runtime[0].mInteger; runtime.pop(); - ESM::CreatureLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + ESM::CreatureLevList listCopy + = *MWBase::Environment::get().getESMStore()->get().find(levId); addToLevList(&listCopy, creatureId, level); - MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + MWBase::Environment::get().getESMStore()->overrideRecord(listCopy); } }; class OpRemoveFromLevCreature : public Interpreter::Opcode0 { public: - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { - const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + const ESM::RefId& levId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - const std::string& creatureId = runtime.getStringLiteral(runtime[0].mInteger); + const ESM::RefId& creatureId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); int level = runtime[0].mInteger; runtime.pop(); - ESM::CreatureLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + ESM::CreatureLevList listCopy + = *MWBase::Environment::get().getESMStore()->get().find(levId); removeFromLevList(&listCopy, creatureId, level); - MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + MWBase::Environment::get().getESMStore()->overrideRecord(listCopy); } }; class OpAddToLevItem : public Interpreter::Opcode0 { public: - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { - const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + const ESM::RefId& levId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - const std::string& itemId = runtime.getStringLiteral(runtime[0].mInteger); + const ESM::RefId& itemId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); int level = runtime[0].mInteger; runtime.pop(); - ESM::ItemLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + ESM::ItemLevList listCopy + = *MWBase::Environment::get().getESMStore()->get().find(levId); addToLevList(&listCopy, itemId, level); - MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + MWBase::Environment::get().getESMStore()->overrideRecord(listCopy); } }; class OpRemoveFromLevItem : public Interpreter::Opcode0 { public: - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { - const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + const ESM::RefId& levId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - const std::string& itemId = runtime.getStringLiteral(runtime[0].mInteger); + const ESM::RefId& itemId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); int level = runtime[0].mInteger; runtime.pop(); - ESM::ItemLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + ESM::ItemLevList listCopy + = *MWBase::Environment::get().getESMStore()->get().find(levId); removeFromLevList(&listCopy, itemId, level); - MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + MWBase::Environment::get().getESMStore()->overrideRecord(listCopy); } }; @@ -1697,219 +1870,299 @@ namespace MWScript class OpShowSceneGraph : public Interpreter::Opcode1 { public: - void execute(Interpreter::Runtime &runtime, unsigned int arg0) override + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime, false); int confirmed = 0; - if (arg0==1) + if (arg0 == 1) { confirmed = runtime[0].mInteger; runtime.pop(); } if (ptr.isEmpty() && !confirmed) - runtime.getContext().report("Exporting the entire scene graph will result in a large file. Confirm this action using 'showscenegraph 1' or select an object instead."); + runtime.getContext().report( + "Exporting the entire scene graph will result in a large file. Confirm this action using " + "'showscenegraph 1' or select an object instead."); else { - const std::string& filename = MWBase::Environment::get().getWorld()->exportSceneGraph(ptr); - runtime.getContext().report("Wrote '" + filename + "'"); + const auto filename = MWBase::Environment::get().getWorld()->exportSceneGraph(ptr); + runtime.getContext().report("Wrote '" + Files::pathToUnicodeString(filename) + "'"); } } }; class OpToggleNavMesh : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode(MWRender::Render_NavMesh); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_NavMesh); - - runtime.getContext().report (enabled ? - "Navigation Mesh Rendering -> On" : "Navigation Mesh Rendering -> Off"); - } + runtime.getContext().report( + enabled ? "Navigation Mesh Rendering -> On" : "Navigation Mesh Rendering -> Off"); + } }; class OpToggleActorsPaths : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode(MWRender::Render_ActorsPaths); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_ActorsPaths); - - runtime.getContext().report (enabled ? - "Agents Paths Rendering -> On" : "Agents Paths Rendering -> Off"); - } + runtime.getContext().report(enabled ? "Agents Paths Rendering -> On" : "Agents Paths Rendering -> Off"); + } }; class OpSetNavMeshNumberToRender : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + const auto navMeshNumber = runtime[0].mInteger; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + if (navMeshNumber < 0) { - const auto navMeshNumber = runtime[0].mInteger; - runtime.pop(); - - if (navMeshNumber < 0) - { - runtime.getContext().report("Invalid navmesh number: use not less than zero values"); - return; - } - - MWBase::Environment::get().getWorld()->setNavMeshNumberToRender(static_cast(navMeshNumber)); + runtime.getContext().report("Invalid navmesh number: use not less than zero values"); + return; } + + MWBase::Environment::get().getWorld()->setNavMeshNumberToRender( + static_cast(navMeshNumber)); + } }; template class OpRepairedOnMe : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - // Broken in vanilla and deliberately no-op. - runtime.push(0); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + // Broken in vanilla and deliberately no-op. + runtime.push(0); + } }; class OpToggleRecastMesh : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleRenderMode(MWRender::Render_RecastMesh); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_RecastMesh); - - runtime.getContext().report (enabled ? - "Recast Mesh Rendering -> On" : "Recast Mesh Rendering -> Off"); - } + runtime.getContext().report(enabled ? "Recast Mesh Rendering -> On" : "Recast Mesh Rendering -> Off"); + } }; - void installOpcodes (Interpreter::Interpreter& interpreter) + class OpHelp : public Interpreter::Opcode0 { - interpreter.installSegment5 (Compiler::Misc::opcodeMenuMode, new OpMenuMode); - interpreter.installSegment5 (Compiler::Misc::opcodeRandom, new OpRandom); - interpreter.installSegment5 (Compiler::Misc::opcodeScriptRunning, new OpScriptRunning); - interpreter.installSegment5 (Compiler::Misc::opcodeStartScript, new OpStartScript); - interpreter.installSegment5 (Compiler::Misc::opcodeStartScriptExplicit, new OpStartScript); - interpreter.installSegment5 (Compiler::Misc::opcodeStopScript, new OpStopScript); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSecondsPassed, new OpGetSecondsPassed); - interpreter.installSegment5 (Compiler::Misc::opcodeEnable, new OpEnable); - interpreter.installSegment5 (Compiler::Misc::opcodeEnableExplicit, new OpEnable); - interpreter.installSegment5 (Compiler::Misc::opcodeDisable, new OpDisable); - interpreter.installSegment5 (Compiler::Misc::opcodeDisableExplicit, new OpDisable); - interpreter.installSegment5 (Compiler::Misc::opcodeGetDisabled, new OpGetDisabled); - interpreter.installSegment5 (Compiler::Misc::opcodeGetDisabledExplicit, new OpGetDisabled); - interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); - interpreter.installSegment5 (Compiler::Misc::opcodeOnActivate, new OpOnActivate); - interpreter.installSegment5 (Compiler::Misc::opcodeOnActivateExplicit, new OpOnActivate); - interpreter.installSegment5 (Compiler::Misc::opcodeActivate, new OpActivate); - interpreter.installSegment5 (Compiler::Misc::opcodeActivateExplicit, new OpActivate); - interpreter.installSegment3 (Compiler::Misc::opcodeLock, new OpLock); - interpreter.installSegment3 (Compiler::Misc::opcodeLockExplicit, new OpLock); - interpreter.installSegment5 (Compiler::Misc::opcodeUnlock, new OpUnlock); - interpreter.installSegment5 (Compiler::Misc::opcodeUnlockExplicit, new OpUnlock); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleCollisionDebug, new OpToggleCollisionDebug); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleCollisionBoxes, new OpToggleCollisionBoxes); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleWireframe, new OpToggleWireframe); - interpreter.installSegment5 (Compiler::Misc::opcodeFadeIn, new OpFadeIn); - interpreter.installSegment5 (Compiler::Misc::opcodeFadeOut, new OpFadeOut); - interpreter.installSegment5 (Compiler::Misc::opcodeFadeTo, new OpFadeTo); - interpreter.installSegment5 (Compiler::Misc::opcodeTogglePathgrid, new OpTogglePathgrid); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleWater, new OpToggleWater); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleWorld, new OpToggleWorld); - interpreter.installSegment5 (Compiler::Misc::opcodeDontSaveObject, new OpDontSaveObject); - interpreter.installSegment5 (Compiler::Misc::opcodePcForce1stPerson, new OpPcForce1stPerson); - interpreter.installSegment5 (Compiler::Misc::opcodePcForce3rdPerson, new OpPcForce3rdPerson); - interpreter.installSegment5 (Compiler::Misc::opcodePcGet3rdPerson, new OpPcGet3rdPerson); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleVanityMode, new OpToggleVanityMode); - interpreter.installSegment5 (Compiler::Misc::opcodeGetPcSleep, new OpGetPcSleep); - interpreter.installSegment5 (Compiler::Misc::opcodeGetPcJumping, new OpGetPcJumping); - interpreter.installSegment5 (Compiler::Misc::opcodeWakeUpPc, new OpWakeUpPc); - interpreter.installSegment5 (Compiler::Misc::opcodePlayBink, new OpPlayBink); - interpreter.installSegment5 (Compiler::Misc::opcodePayFine, new OpPayFine); - interpreter.installSegment5 (Compiler::Misc::opcodePayFineThief, new OpPayFineThief); - interpreter.installSegment5 (Compiler::Misc::opcodeGoToJail, new OpGoToJail); - interpreter.installSegment5 (Compiler::Misc::opcodeGetLocked, new OpGetLocked); - interpreter.installSegment5 (Compiler::Misc::opcodeGetLockedExplicit, new OpGetLocked); - interpreter.installSegment5 (Compiler::Misc::opcodeGetEffect, new OpGetEffect); - interpreter.installSegment5 (Compiler::Misc::opcodeGetEffectExplicit, new OpGetEffect); - interpreter.installSegment5 (Compiler::Misc::opcodeAddSoulGem, new OpAddSoulGem); - interpreter.installSegment5 (Compiler::Misc::opcodeAddSoulGemExplicit, new OpAddSoulGem); - interpreter.installSegment3 (Compiler::Misc::opcodeRemoveSoulGem, new OpRemoveSoulGem); - interpreter.installSegment3 (Compiler::Misc::opcodeRemoveSoulGemExplicit, new OpRemoveSoulGem); - interpreter.installSegment5 (Compiler::Misc::opcodeDrop, new OpDrop); - interpreter.installSegment5 (Compiler::Misc::opcodeDropExplicit, new OpDrop); - interpreter.installSegment5 (Compiler::Misc::opcodeDropSoulGem, new OpDropSoulGem); - interpreter.installSegment5 (Compiler::Misc::opcodeDropSoulGemExplicit, new OpDropSoulGem); - interpreter.installSegment5 (Compiler::Misc::opcodeGetAttacked, new OpGetAttacked); - interpreter.installSegment5 (Compiler::Misc::opcodeGetAttackedExplicit, new OpGetAttacked); - interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawn, new OpGetWeaponDrawn); - interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawnExplicit, new OpGetWeaponDrawn); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadied, new OpGetSpellReadied); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadiedExplicit, new OpGetSpellReadied); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffects, new OpGetSpellEffects); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffectsExplicit, new OpGetSpellEffects); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCurrentTime, new OpGetCurrentTime); - interpreter.installSegment5 (Compiler::Misc::opcodeSetDelete, new OpSetDelete); - interpreter.installSegment5 (Compiler::Misc::opcodeSetDeleteExplicit, new OpSetDelete); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSquareRoot, new OpGetSquareRoot); - interpreter.installSegment5 (Compiler::Misc::opcodeFall, new OpFall); - interpreter.installSegment5 (Compiler::Misc::opcodeFallExplicit, new OpFall); - interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPc, new OpGetStandingPc); - interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPcExplicit, new OpGetStandingPc); - interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActor, new OpGetStandingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActorExplicit, new OpGetStandingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPc, new OpGetCollidingPc); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPcExplicit, new OpGetCollidingPc); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActor, new OpGetCollidingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActorExplicit, new OpGetCollidingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActor, new OpHurtStandingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActorExplicit, new OpHurtStandingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActor, new OpHurtCollidingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActorExplicit, new OpHurtCollidingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed); - interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMe, new OpHitAttemptOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMeExplicit, new OpHitAttemptOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeDisableTeleporting, new OpEnableTeleporting); - interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); - interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); - interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars); - interpreter.installSegment5 (Compiler::Misc::opcodeShow, new OpShow); - interpreter.installSegment5 (Compiler::Misc::opcodeShowExplicit, new OpShow); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleScripts, new OpToggleScripts); - interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); - interpreter.installSegment5 (Compiler::Misc::opcodeEnableLevitation, new OpEnableLevitation); - interpreter.installSegment5 (Compiler::Misc::opcodeCast, new OpCast); - interpreter.installSegment5 (Compiler::Misc::opcodeCastExplicit, new OpCast); - interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpell, new OpExplodeSpell); - interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell); - interpreter.installSegment5 (Compiler::Misc::opcodeGetPcInJail, new OpGetPcInJail); - interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling); - interpreter.installSegment3 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); - interpreter.installSegment3 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); - interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevCreature, new OpAddToLevCreature); - interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevCreature, new OpRemoveFromLevCreature); - interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevItem, new OpAddToLevItem); - interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevItem, new OpRemoveFromLevItem); - interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph); - interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths); - interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender); - interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMe, new OpRepairedOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMeExplicit, new OpRepairedOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleRecastMesh, new OpToggleRecastMesh); + public: + void execute(Interpreter::Runtime& runtime) override + { + std::stringstream message; + message << MWBase::Environment::get().getWindowManager()->getVersionDescription() << "\n\n"; + std::vector commands; + MWBase::Environment::get().getScriptManager()->getExtensions().listKeywords(commands); + for (const auto& command : commands) + message << command << "\n"; + runtime.getContext().report(message.str()); + } + }; + + class OpReloadLua : public Interpreter::Opcode0 + { + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getLuaManager()->reloadAllScripts(); + runtime.getContext().report("All Lua scripts are reloaded"); + } + }; + + class OpTestModels : public Interpreter::Opcode0 + { + template + void test(int& count) const + { + Resource::SceneManager* sceneManager + = MWBase::Environment::get().getResourceSystem()->getSceneManager(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + for (const T& record : store.get()) + { + MWWorld::ManualRef ref(store, record.mId); + std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); + if (!model.empty()) + { + sceneManager->getTemplate(model); + ++count; + } + } + } + + public: + void execute(Interpreter::Runtime& runtime) override + { + Resource::SceneManager* sceneManager + = MWBase::Environment::get().getResourceSystem()->getSceneManager(); + double delay = sceneManager->getExpiryDelay(); + sceneManager->setExpiryDelay(0.0); + int count = 0; + + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + test(count); + + sceneManager->setExpiryDelay(delay); + std::stringstream message; + message << "Attempted to load models for " << count << " objects. Check the log for details."; + runtime.getContext().report(message.str()); + } + }; + + void installOpcodes(Interpreter::Interpreter& interpreter) + { + interpreter.installSegment5(Compiler::Misc::opcodeMenuMode); + interpreter.installSegment5(Compiler::Misc::opcodeRandom); + interpreter.installSegment5(Compiler::Misc::opcodeScriptRunning); + interpreter.installSegment5>(Compiler::Misc::opcodeStartScript); + interpreter.installSegment5>(Compiler::Misc::opcodeStartScriptExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeStopScript); + interpreter.installSegment5(Compiler::Misc::opcodeGetSecondsPassed); + interpreter.installSegment5>(Compiler::Misc::opcodeEnable); + interpreter.installSegment5>(Compiler::Misc::opcodeEnableExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeDisable); + interpreter.installSegment5>(Compiler::Misc::opcodeDisableExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetDisabled); + interpreter.installSegment5>(Compiler::Misc::opcodeGetDisabledExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeXBox); + interpreter.installSegment5>(Compiler::Misc::opcodeOnActivate); + interpreter.installSegment5>(Compiler::Misc::opcodeOnActivateExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeActivate); + interpreter.installSegment5>(Compiler::Misc::opcodeActivateExplicit); + interpreter.installSegment3>(Compiler::Misc::opcodeLock); + interpreter.installSegment3>(Compiler::Misc::opcodeLockExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeUnlock); + interpreter.installSegment5>(Compiler::Misc::opcodeUnlockExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeToggleCollisionDebug); + interpreter.installSegment5(Compiler::Misc::opcodeToggleCollisionBoxes); + interpreter.installSegment5(Compiler::Misc::opcodeToggleWireframe); + interpreter.installSegment5(Compiler::Misc::opcodeFadeIn); + interpreter.installSegment5(Compiler::Misc::opcodeFadeOut); + interpreter.installSegment5(Compiler::Misc::opcodeFadeTo); + interpreter.installSegment5(Compiler::Misc::opcodeTogglePathgrid); + interpreter.installSegment5(Compiler::Misc::opcodeToggleWater); + interpreter.installSegment5(Compiler::Misc::opcodeToggleWorld); + interpreter.installSegment5(Compiler::Misc::opcodeDontSaveObject); + interpreter.installSegment5(Compiler::Misc::opcodePcForce1stPerson); + interpreter.installSegment5(Compiler::Misc::opcodePcForce3rdPerson); + interpreter.installSegment5(Compiler::Misc::opcodePcGet3rdPerson); + interpreter.installSegment5(Compiler::Misc::opcodeToggleVanityMode); + interpreter.installSegment5(Compiler::Misc::opcodeGetPcSleep); + interpreter.installSegment5(Compiler::Misc::opcodeGetPcJumping); + interpreter.installSegment5(Compiler::Misc::opcodeWakeUpPc); + interpreter.installSegment5(Compiler::Misc::opcodePlayBink); + interpreter.installSegment5(Compiler::Misc::opcodePayFine); + interpreter.installSegment5(Compiler::Misc::opcodePayFineThief); + interpreter.installSegment5(Compiler::Misc::opcodeGoToJail); + interpreter.installSegment5>(Compiler::Misc::opcodeGetLocked); + interpreter.installSegment5>(Compiler::Misc::opcodeGetLockedExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetEffect); + interpreter.installSegment5>(Compiler::Misc::opcodeGetEffectExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeAddSoulGem); + interpreter.installSegment5>(Compiler::Misc::opcodeAddSoulGemExplicit); + interpreter.installSegment3>(Compiler::Misc::opcodeRemoveSoulGem); + interpreter.installSegment3>(Compiler::Misc::opcodeRemoveSoulGemExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeDrop); + interpreter.installSegment5>(Compiler::Misc::opcodeDropExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeDropSoulGem); + interpreter.installSegment5>(Compiler::Misc::opcodeDropSoulGemExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetAttacked); + interpreter.installSegment5>(Compiler::Misc::opcodeGetAttackedExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetWeaponDrawn); + interpreter.installSegment5>(Compiler::Misc::opcodeGetWeaponDrawnExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellReadied); + interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellReadiedExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellEffects); + interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellEffectsExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeGetCurrentTime); + interpreter.installSegment5>(Compiler::Misc::opcodeSetDelete); + interpreter.installSegment5>(Compiler::Misc::opcodeSetDeleteExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeGetSquareRoot); + interpreter.installSegment5>(Compiler::Misc::opcodeFall); + interpreter.installSegment5>(Compiler::Misc::opcodeFallExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingPc); + interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingPcExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingActor); + interpreter.installSegment5>( + Compiler::Misc::opcodeGetStandingActorExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingPc); + interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingPcExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingActor); + interpreter.installSegment5>( + Compiler::Misc::opcodeGetCollidingActorExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeHurtStandingActor); + interpreter.installSegment5>( + Compiler::Misc::opcodeHurtStandingActorExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeHurtCollidingActor); + interpreter.installSegment5>( + Compiler::Misc::opcodeHurtCollidingActorExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeGetWindSpeed); + interpreter.installSegment5>(Compiler::Misc::opcodeHitOnMe); + interpreter.installSegment5>(Compiler::Misc::opcodeHitOnMeExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeHitAttemptOnMe); + interpreter.installSegment5>(Compiler::Misc::opcodeHitAttemptOnMeExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeDisableTeleporting); + interpreter.installSegment5>(Compiler::Misc::opcodeEnableTeleporting); + interpreter.installSegment5>(Compiler::Misc::opcodeShowVars); + interpreter.installSegment5>(Compiler::Misc::opcodeShowVarsExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeShow); + interpreter.installSegment5>(Compiler::Misc::opcodeShowExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeToggleGodMode); + interpreter.installSegment5(Compiler::Misc::opcodeToggleScripts); + interpreter.installSegment5>(Compiler::Misc::opcodeDisableLevitation); + interpreter.installSegment5>(Compiler::Misc::opcodeEnableLevitation); + interpreter.installSegment5>(Compiler::Misc::opcodeCast); + interpreter.installSegment5>(Compiler::Misc::opcodeCastExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeExplodeSpell); + interpreter.installSegment5>(Compiler::Misc::opcodeExplodeSpellExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeGetPcInJail); + interpreter.installSegment5(Compiler::Misc::opcodeGetPcTraveling); + interpreter.installSegment3>(Compiler::Misc::opcodeBetaComment); + interpreter.installSegment3>(Compiler::Misc::opcodeBetaCommentExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeAddToLevCreature); + interpreter.installSegment5(Compiler::Misc::opcodeRemoveFromLevCreature); + interpreter.installSegment5(Compiler::Misc::opcodeAddToLevItem); + interpreter.installSegment5(Compiler::Misc::opcodeRemoveFromLevItem); + interpreter.installSegment3>(Compiler::Misc::opcodeShowSceneGraph); + interpreter.installSegment3>(Compiler::Misc::opcodeShowSceneGraphExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeToggleBorders); + interpreter.installSegment5(Compiler::Misc::opcodeToggleNavMesh); + interpreter.installSegment5(Compiler::Misc::opcodeToggleActorsPaths); + interpreter.installSegment5(Compiler::Misc::opcodeSetNavMeshNumberToRender); + interpreter.installSegment5>(Compiler::Misc::opcodeRepairedOnMe); + interpreter.installSegment5>(Compiler::Misc::opcodeRepairedOnMeExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeToggleRecastMesh); + interpreter.installSegment5(Compiler::Misc::opcodeHelp); + interpreter.installSegment5(Compiler::Misc::opcodeReloadLua); + interpreter.installSegment5(Compiler::Misc::opcodeTestModels); } } } diff --git a/apps/openmw/mwscript/miscextensions.hpp b/apps/openmw/mwscript/miscextensions.hpp index 16ed9301e..0dbdd7ba5 100644 --- a/apps/openmw/mwscript/miscextensions.hpp +++ b/apps/openmw/mwscript/miscextensions.hpp @@ -14,11 +14,9 @@ namespace Interpreter namespace MWScript { namespace Misc - { - void installOpcodes (Interpreter::Interpreter& interpreter); + { + void installOpcodes(Interpreter::Interpreter& interpreter); } } #endif - - diff --git a/apps/openmw/mwscript/ref.cpp b/apps/openmw/mwscript/ref.cpp index 6347c2c2e..af6b205d3 100644 --- a/apps/openmw/mwscript/ref.cpp +++ b/apps/openmw/mwscript/ref.cpp @@ -7,10 +7,9 @@ #include "interpretercontext.hpp" -MWWorld::Ptr MWScript::ExplicitRef::operator() (Interpreter::Runtime& runtime, bool required, - bool activeOnly) const +MWWorld::Ptr MWScript::ExplicitRef::operator()(Interpreter::Runtime& runtime, bool required, bool activeOnly) const { - std::string id = runtime.getStringLiteral(runtime[0].mInteger); + ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); if (required) @@ -19,11 +18,9 @@ MWWorld::Ptr MWScript::ExplicitRef::operator() (Interpreter::Runtime& runtime, b return MWBase::Environment::get().getWorld()->searchPtr(id, activeOnly); } -MWWorld::Ptr MWScript::ImplicitRef::operator() (Interpreter::Runtime& runtime, bool required, - bool activeOnly) const +MWWorld::Ptr MWScript::ImplicitRef::operator()(Interpreter::Runtime& runtime, bool required, bool activeOnly) const { - MWScript::InterpreterContext& context - = static_cast (runtime.getContext()); + MWScript::InterpreterContext& context = static_cast(runtime.getContext()); return context.getReference(required); } diff --git a/apps/openmw/mwscript/ref.hpp b/apps/openmw/mwscript/ref.hpp index c52b419c1..166435b2c 100644 --- a/apps/openmw/mwscript/ref.hpp +++ b/apps/openmw/mwscript/ref.hpp @@ -1,8 +1,6 @@ #ifndef GAME_MWSCRIPT_REF_H #define GAME_MWSCRIPT_REF_H -#include - #include "../mwworld/ptr.hpp" namespace Interpreter @@ -16,16 +14,14 @@ namespace MWScript { static constexpr bool implicit = false; - MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, - bool activeOnly = false) const; + MWWorld::Ptr operator()(Interpreter::Runtime& runtime, bool required = true, bool activeOnly = false) const; }; struct ImplicitRef { static constexpr bool implicit = true; - MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, - bool activeOnly = false) const; + MWWorld::Ptr operator()(Interpreter::Runtime& runtime, bool required = true, bool activeOnly = false) const; }; } diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index e1652b311..14194de61 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -1,20 +1,21 @@ #include "scriptmanagerimp.hpp" -#include -#include -#include #include +#include +#include +#include #include -#include +#include +#include -#include +#include -#include #include #include #include +#include #include "../mwworld/esmstore.hpp" @@ -23,39 +24,39 @@ namespace MWScript { - ScriptManager::ScriptManager (const MWWorld::ESMStore& store, - Compiler::Context& compilerContext, int warningsMode, - const std::vector& scriptBlacklist) - : mErrorHandler(), mStore (store), - mCompilerContext (compilerContext), mParser (mErrorHandler, mCompilerContext), - mOpcodesInstalled (false), mGlobalScripts (store) + ScriptManager::ScriptManager(const MWWorld::ESMStore& store, Compiler::Context& compilerContext, int warningsMode, + const std::vector& scriptBlacklist) + : mErrorHandler() + , mStore(store) + , mCompilerContext(compilerContext) + , mParser(mErrorHandler, mCompilerContext) + , mOpcodesInstalled(false) + , mGlobalScripts(store) { - mErrorHandler.setWarningsMode (warningsMode); + mErrorHandler.setWarningsMode(warningsMode); - mScriptBlacklist.resize (scriptBlacklist.size()); + mScriptBlacklist.resize(scriptBlacklist.size()); - std::transform (scriptBlacklist.begin(), scriptBlacklist.end(), - mScriptBlacklist.begin(), Misc::StringUtils::lowerCase); - std::sort (mScriptBlacklist.begin(), mScriptBlacklist.end()); + std::sort(mScriptBlacklist.begin(), mScriptBlacklist.end()); } - bool ScriptManager::compile (const std::string& name) + bool ScriptManager::compile(const ESM::RefId& name) { mParser.reset(); mErrorHandler.reset(); - if (const ESM::Script *script = mStore.get().find (name)) + if (const ESM::Script* script = mStore.get().find(name)) { - mErrorHandler.setContext(name); + mErrorHandler.setContext(script->mId.getRefIdString()); bool Success = true; try { - std::istringstream input (script->mScriptText); + std::istringstream input(script->mScriptText); - Compiler::Scanner scanner (mErrorHandler, input, mCompilerContext.getExtensions()); + Compiler::Scanner scanner(mErrorHandler, input, mCompilerContext.getExtensions()); - scanner.scan (mParser); + scanner.scan(mParser); if (!mErrorHandler.isGood()) Success = false; @@ -78,9 +79,7 @@ namespace MWScript if (Success) { - std::vector code; - mParser.getCode(code); - mScripts.emplace(name, CompiledScript(code, mParser.getLocals())); + mScripts.emplace(name, CompiledScript(mParser.getProgram(), mParser.getLocals())); return true; } @@ -89,47 +88,48 @@ namespace MWScript return false; } - bool ScriptManager::run (const std::string& name, Interpreter::Context& interpreterContext) + bool ScriptManager::run(const ESM::RefId& name, Interpreter::Context& interpreterContext) { // compile script - ScriptCollection::iterator iter = mScripts.find (name); + auto iter = mScripts.find(name); - if (iter==mScripts.end()) + if (iter == mScripts.end()) { - if (!compile (name)) + if (!compile(name)) { // failed -> ignore script from now on. - std::vector empty; - mScripts.emplace(name, CompiledScript(empty, Compiler::Locals())); + mScripts.emplace(name, CompiledScript({}, Compiler::Locals())); return false; } - iter = mScripts.find (name); - assert (iter!=mScripts.end()); + iter = mScripts.find(name); + assert(iter != mScripts.end()); } // execute script - if (!iter->second.mByteCode.empty() && iter->second.mActive) + const auto& target = interpreterContext.getTarget(); + if (!iter->second.mProgram.mInstructions.empty() + && iter->second.mInactive.find(target) == iter->second.mInactive.end()) try { if (!mOpcodesInstalled) { - installOpcodes (mInterpreter); + installOpcodes(mInterpreter); mOpcodesInstalled = true; } - mInterpreter.run (&iter->second.mByteCode[0], iter->second.mByteCode.size(), interpreterContext); + mInterpreter.run(iter->second.mProgram, interpreterContext); return true; } catch (const MissingImplicitRefError& e) { - Log(Debug::Error) << "Execution of script " << name << " failed: " << e.what(); + Log(Debug::Error) << "Execution of script " << name << " failed: " << e.what(); } catch (const std::exception& e) { - Log(Debug::Error) << "Execution of script " << name << " failed: " << e.what(); + Log(Debug::Error) << "Execution of script " << name << " failed: " << e.what(); - iter->second.mActive = false; // don't execute again. + iter->second.mInactive.insert(target); // don't execute again. } return false; } @@ -138,7 +138,7 @@ namespace MWScript { for (auto& script : mScripts) { - script.second.mActive = true; + script.second.mInactive.clear(); } mGlobalScripts.clear(); @@ -151,8 +151,7 @@ namespace MWScript for (auto& script : mStore.get()) { - if (!std::binary_search (mScriptBlacklist.begin(), mScriptBlacklist.end(), - Misc::StringUtils::lowerCase(script.mId))) + if (!std::binary_search(mScriptBlacklist.begin(), mScriptBlacklist.end(), script.mId)) { ++count; @@ -161,49 +160,64 @@ namespace MWScript } } - return std::make_pair (count, success); + return std::make_pair(count, success); } - const Compiler::Locals& ScriptManager::getLocals (const std::string& name) + const Compiler::Locals& ScriptManager::getLocals(const ESM::RefId& name) { - std::string name2 = Misc::StringUtils::lowerCase (name); - { - ScriptCollection::iterator iter = mScripts.find (name2); + auto iter = mScripts.find(name); - if (iter!=mScripts.end()) + if (iter != mScripts.end()) return iter->second.mLocals; } { - std::map::iterator iter = mOtherLocals.find (name2); + auto iter = mOtherLocals.find(name); - if (iter!=mOtherLocals.end()) + if (iter != mOtherLocals.end()) return iter->second; } - if (const ESM::Script *script = mStore.get().search (name2)) + if (const ESM::Script* script = mStore.get().search(name)) { Compiler::Locals locals; - const Compiler::ContextOverride override(mErrorHandler, name2 + "[local variables]"); + const Compiler::ContextOverride override(mErrorHandler, name.getRefIdString() + "[local variables]"); - std::istringstream stream (script->mScriptText); - Compiler::QuickFileParser parser (mErrorHandler, mCompilerContext, locals); - Compiler::Scanner scanner (mErrorHandler, stream, mCompilerContext.getExtensions()); - scanner.scan (parser); + std::istringstream stream(script->mScriptText); + Compiler::QuickFileParser parser(mErrorHandler, mCompilerContext, locals); + Compiler::Scanner scanner(mErrorHandler, stream, mCompilerContext.getExtensions()); + try + { + scanner.scan(parser); + } + catch (const Compiler::SourceException&) + { + // error has already been reported via error handler + locals.clear(); + } + catch (const std::exception& error) + { + Log(Debug::Error) << "Error: An exception has been thrown: " << error.what(); + locals.clear(); + } - std::map::iterator iter = - mOtherLocals.emplace(name2, locals).first; + auto iter = mOtherLocals.emplace(name, locals).first; return iter->second; } - throw std::logic_error ("script " + name + " does not exist"); + throw std::logic_error("script " + name.toDebugString() + " does not exist"); } GlobalScripts& ScriptManager::getGlobalScripts() { return mGlobalScripts; } + + const Compiler::Extensions& ScriptManager::getExtensions() const + { + return *mCompilerContext.getExtensions(); + } } diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp index 7ddcd2489..de1ce286a 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.hpp +++ b/apps/openmw/mwscript/scriptmanagerimp.hpp @@ -2,14 +2,17 @@ #define GAME_SCRIPT_SCRIPTMANAGER_H #include +#include #include -#include #include +#include #include #include +#include + #include "../mwbase/scriptmanager.hpp" #include "globalscripts.hpp" @@ -34,57 +37,54 @@ namespace MWScript { class ScriptManager : public MWBase::ScriptManager { - Compiler::StreamErrorHandler mErrorHandler; - const MWWorld::ESMStore& mStore; - Compiler::Context& mCompilerContext; - Compiler::FileParser mParser; - Interpreter::Interpreter mInterpreter; - bool mOpcodesInstalled; + Compiler::StreamErrorHandler mErrorHandler; + const MWWorld::ESMStore& mStore; + Compiler::Context& mCompilerContext; + Compiler::FileParser mParser; + Interpreter::Interpreter mInterpreter; + bool mOpcodesInstalled; - struct CompiledScript + struct CompiledScript + { + Interpreter::Program mProgram; + Compiler::Locals mLocals; + std::set mInactive; + + explicit CompiledScript(Interpreter::Program&& program, const Compiler::Locals& locals) + : mProgram(std::move(program)) + , mLocals(locals) { - std::vector mByteCode; - Compiler::Locals mLocals; - bool mActive; + } + }; - CompiledScript(const std::vector& code, const Compiler::Locals& locals) - { - mByteCode = code; - mLocals = locals; - mActive = true; - } - }; + std::unordered_map mScripts; + GlobalScripts mGlobalScripts; + std::unordered_map mOtherLocals; + std::vector mScriptBlacklist; - typedef std::map ScriptCollection; + public: + ScriptManager(const MWWorld::ESMStore& store, Compiler::Context& compilerContext, int warningsMode, + const std::vector& scriptBlacklist); - ScriptCollection mScripts; - GlobalScripts mGlobalScripts; - std::map mOtherLocals; - std::vector mScriptBlacklist; + void clear() override; - public: + bool run(const ESM::RefId& name, Interpreter::Context& interpreterContext) override; + ///< Run the script with the given name (compile first, if not compiled yet) - ScriptManager (const MWWorld::ESMStore& store, - Compiler::Context& compilerContext, int warningsMode, - const std::vector& scriptBlacklist); + bool compile(const ESM::RefId& name) override; + ///< Compile script with the given namen + /// \return Success? - void clear() override; + std::pair compileAll() override; + ///< Compile all scripts + /// \return count, success - bool run (const std::string& name, Interpreter::Context& interpreterContext) override; - ///< Run the script with the given name (compile first, if not compiled yet) + const Compiler::Locals& getLocals(const ESM::RefId& name) override; + ///< Return locals for script \a name. - bool compile (const std::string& name) override; - ///< Compile script with the given namen - /// \return Success? + GlobalScripts& getGlobalScripts() override; - std::pair compileAll() override; - ///< Compile all scripts - /// \return count, success - - const Compiler::Locals& getLocals (const std::string& name) override; - ///< Return locals for script \a name. - - GlobalScripts& getGlobalScripts() override; + const Compiler::Extensions& getExtensions() const override; }; } diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index 2b6bf826f..2284e2a3a 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -4,13 +4,16 @@ #include +#include #include -#include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" + #include "interpretercontext.hpp" namespace MWScript @@ -19,115 +22,110 @@ namespace MWScript { class OpToggleSky : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + bool enabled = MWBase::Environment::get().getWorld()->toggleSky(); - void execute (Interpreter::Runtime& runtime) override - { - bool enabled = MWBase::Environment::get().getWorld()->toggleSky(); - - runtime.getContext().report (enabled ? "Sky -> On" : "Sky -> Off"); - } + runtime.getContext().report(enabled ? "Sky -> On" : "Sky -> Off"); + } }; class OpTurnMoonWhite : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::Environment::get().getWorld()->setMoonColour (false); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getWorld()->setMoonColour(false); + } }; class OpTurnMoonRed : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::Environment::get().getWorld()->setMoonColour (true); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getWorld()->setMoonColour(true); + } }; class OpGetMasserPhase : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push (MWBase::Environment::get().getWorld()->getMasserPhase()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWorld()->getMasserPhase()); + } }; class OpGetSecundaPhase : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push (MWBase::Environment::get().getWorld()->getSecundaPhase()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWorld()->getSecundaPhase()); + } }; class OpGetCurrentWeather : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.push (MWBase::Environment::get().getWorld()->getCurrentWeather()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + runtime.push(MWBase::Environment::get().getWorld()->getCurrentWeather()); + } }; class OpChangeWeather : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + ESM::RefId region = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - std::string region = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - Interpreter::Type_Integer id = runtime[0].mInteger; - runtime.pop(); + Interpreter::Type_Integer id = runtime[0].mInteger; + runtime.pop(); + const ESM::Region* reg = MWBase::Environment::get().getESMStore()->get().search(region); + if (reg) MWBase::Environment::get().getWorld()->changeWeather(region, id); - } + else + runtime.getContext().report("Warning: Region \"" + region.getRefIdString() + "\" was not found"); + } }; class OpModRegion : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + std::string_view region{ runtime.getStringLiteral(runtime[0].mInteger) }; + runtime.pop(); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + std::vector chances; + chances.reserve(10); + while (arg0 > 0) { - std::string region = runtime.getStringLiteral (runtime[0].mInteger); + chances.push_back(std::clamp(runtime[0].mInteger, 0, 127)); runtime.pop(); - - std::vector chances; - chances.reserve(10); - while(arg0 > 0) - { - chances.push_back(std::max(0, std::min(127, runtime[0].mInteger))); - runtime.pop(); - arg0--; - } - - MWBase::Environment::get().getWorld()->modRegion(region, chances); + arg0--; } + + MWBase::Environment::get().getWorld()->modRegion(ESM::RefId::stringRefId(region), chances); + } }; - - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Sky::opcodeToggleSky, new OpToggleSky); - interpreter.installSegment5 (Compiler::Sky::opcodeTurnMoonWhite, new OpTurnMoonWhite); - interpreter.installSegment5 (Compiler::Sky::opcodeTurnMoonRed, new OpTurnMoonRed); - interpreter.installSegment5 (Compiler::Sky::opcodeGetMasserPhase, new OpGetMasserPhase); - interpreter.installSegment5 (Compiler::Sky::opcodeGetSecundaPhase, new OpGetSecundaPhase); - interpreter.installSegment5 (Compiler::Sky::opcodeGetCurrentWeather, new OpGetCurrentWeather); - interpreter.installSegment5 (Compiler::Sky::opcodeChangeWeather, new OpChangeWeather); - interpreter.installSegment3 (Compiler::Sky::opcodeModRegion, new OpModRegion); + interpreter.installSegment5(Compiler::Sky::opcodeToggleSky); + interpreter.installSegment5(Compiler::Sky::opcodeTurnMoonWhite); + interpreter.installSegment5(Compiler::Sky::opcodeTurnMoonRed); + interpreter.installSegment5(Compiler::Sky::opcodeGetMasserPhase); + interpreter.installSegment5(Compiler::Sky::opcodeGetSecundaPhase); + interpreter.installSegment5(Compiler::Sky::opcodeGetCurrentWeather); + interpreter.installSegment5(Compiler::Sky::opcodeChangeWeather); + interpreter.installSegment3(Compiler::Sky::opcodeModRegion); } } } diff --git a/apps/openmw/mwscript/skyextensions.hpp b/apps/openmw/mwscript/skyextensions.hpp index 003f2fb19..f49aa04c9 100644 --- a/apps/openmw/mwscript/skyextensions.hpp +++ b/apps/openmw/mwscript/skyextensions.hpp @@ -15,8 +15,8 @@ namespace MWScript { /// \brief sky-related script functionality namespace Sky - { - void installOpcodes (Interpreter::Interpreter& interpreter); + { + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index efd3a020d..f33e98145 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -16,16 +16,16 @@ #include #include -#include #include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -34,48 +34,51 @@ namespace MWScript { namespace Sound { - template + template class OpSay : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + MWScript::InterpreterContext& context + = static_cast(runtime.getContext()); - MWScript::InterpreterContext& context - = static_cast (runtime.getContext()); + std::string file{ runtime.getStringLiteral(runtime[0].mInteger) }; + runtime.pop(); - std::string file = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + std::string_view text = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); - std::string text = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + MWBase::Environment::get().getSoundManager()->say(ptr, file); - MWBase::Environment::get().getSoundManager()->say (ptr, file); - - if (MWBase::Environment::get().getWindowManager ()->getSubtitlesEnabled()) - context.messageBox (text); - } + if (MWBase::Environment::get().getWindowManager()->getSubtitlesEnabled()) + context.messageBox(text); + } }; - template + template class OpSayDone : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push (MWBase::Environment::get().getSoundManager()->sayDone (ptr)); - } + runtime.push(MWBase::Environment::get().getSoundManager()->sayDone(ptr)); + } }; class OpStreamMusic : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + std::string sound{ runtime.getStringLiteral(runtime[0].mInteger) }; + runtime.pop(); +<<<<<<< HEAD void execute (Interpreter::Runtime& runtime) override { std::string sound = runtime.getStringLiteral (runtime[0].mInteger); @@ -99,169 +102,153 @@ namespace MWScript MWBase::Environment::get().getSoundManager()->streamMusic (sound); } +======= + MWBase::Environment::get().getSoundManager()->streamMusic(sound); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; class OpPlaySound : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + ESM::RefId sound = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - std::string sound = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - MWBase::Environment::get().getSoundManager()->playSound(sound, 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); - } + MWBase::Environment::get().getSoundManager()->playSound( + sound, 1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); + } }; class OpPlaySoundVP : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + ESM::RefId sound = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - std::string sound = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + Interpreter::Type_Float volume = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float volume = runtime[0].mFloat; - runtime.pop(); + Interpreter::Type_Float pitch = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float pitch = runtime[0].mFloat; - runtime.pop(); - - MWBase::Environment::get().getSoundManager()->playSound(sound, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); - } + MWBase::Environment::get().getSoundManager()->playSound( + sound, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); + } }; - template + template class OpPlaySound3D : public Interpreter::Opcode0 { - bool mLoop; + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - public: + ESM::RefId sound = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - OpPlaySound3D (bool loop) : mLoop (loop) {} - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - std::string sound = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0, - MWSound::Type::Sfx, - mLoop ? MWSound::PlayMode::LoopRemoveAtDistance - : MWSound::PlayMode::Normal); - } + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0, MWSound::Type::Sfx, + TLoop ? MWSound::PlayMode::LoopRemoveAtDistance : MWSound::PlayMode::Normal); + } }; - template + template class OpPlaySoundVP3D : public Interpreter::Opcode0 { - bool mLoop; + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - public: + ESM::RefId sound = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - OpPlaySoundVP3D (bool loop) : mLoop (loop) {} + Interpreter::Type_Float volume = runtime[0].mFloat; + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float pitch = runtime[0].mFloat; + runtime.pop(); - std::string sound = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - Interpreter::Type_Float volume = runtime[0].mFloat; - runtime.pop(); - - Interpreter::Type_Float pitch = runtime[0].mFloat; - runtime.pop(); - - MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, volume, pitch, - MWSound::Type::Sfx, - mLoop ? MWSound::PlayMode::LoopRemoveAtDistance - : MWSound::PlayMode::Normal); - - } + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, volume, pitch, MWSound::Type::Sfx, + TLoop ? MWSound::PlayMode::LoopRemoveAtDistance : MWSound::PlayMode::Normal); + } }; - template + template class OpStopSound : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId sound = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string sound = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - MWBase::Environment::get().getSoundManager()->stopSound3D (ptr, sound); - } + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, sound); + } }; - template + template class OpGetSoundPlaying : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + int index = runtime[0].mInteger; + runtime.pop(); + + bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying( + ptr, ESM::RefId::stringRefId(runtime.getStringLiteral(index))); + + // GetSoundPlaying called on an equipped item should also look for sounds played by the equipping actor. + if (!ret && ptr.getContainerStore()) { - MWWorld::Ptr ptr = R()(runtime); + MWWorld::Ptr cont = MWBase::Environment::get().getWorld()->findContainer(ptr); - int index = runtime[0].mInteger; - runtime.pop(); - - bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying ( - ptr, runtime.getStringLiteral (index)); - - // GetSoundPlaying called on an equipped item should also look for sounds played by the equipping actor. - if (!ret && ptr.getContainerStore()) + if (!cont.isEmpty() && cont.getClass().hasInventoryStore(cont) + && cont.getClass().getInventoryStore(cont).isEquipped(ptr)) { - MWWorld::Ptr cont = MWBase::Environment::get().getWorld()->findContainer(ptr); - - if (!cont.isEmpty() && cont.getClass().hasInventoryStore(cont) && cont.getClass().getInventoryStore(cont).isEquipped(ptr)) - { - ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying ( - cont, runtime.getStringLiteral (index)); - } + ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying( + cont, ESM::RefId::stringRefId(runtime.getStringLiteral(index))); } - - runtime.push(ret); } + + runtime.push(ret); + } }; - - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Sound::opcodeSay, new OpSay); - interpreter.installSegment5 (Compiler::Sound::opcodeSayDone, new OpSayDone); - interpreter.installSegment5 (Compiler::Sound::opcodeStreamMusic, new OpStreamMusic); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound, new OpPlaySound); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySoundVP, new OpPlaySoundVP); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3D, new OpPlaySound3D (false)); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DVP, new OpPlaySoundVP3D (false)); - interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3D, new OpPlaySound3D (true)); - interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DVP, - new OpPlaySoundVP3D (true)); - interpreter.installSegment5 (Compiler::Sound::opcodeStopSound, new OpStopSound); - interpreter.installSegment5 (Compiler::Sound::opcodeGetSoundPlaying, new OpGetSoundPlaying); + interpreter.installSegment5>(Compiler::Sound::opcodeSay); + interpreter.installSegment5>(Compiler::Sound::opcodeSayDone); + interpreter.installSegment5(Compiler::Sound::opcodeStreamMusic); + interpreter.installSegment5(Compiler::Sound::opcodePlaySound); + interpreter.installSegment5(Compiler::Sound::opcodePlaySoundVP); + interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3D); + interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3DVP); + interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3D); + interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3DVP); + interpreter.installSegment5>(Compiler::Sound::opcodeStopSound); + interpreter.installSegment5>(Compiler::Sound::opcodeGetSoundPlaying); - interpreter.installSegment5 (Compiler::Sound::opcodeSayExplicit, new OpSay); - interpreter.installSegment5 (Compiler::Sound::opcodeSayDoneExplicit, new OpSayDone); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DExplicit, - new OpPlaySound3D (false)); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DVPExplicit, - new OpPlaySoundVP3D (false)); - interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DExplicit, - new OpPlaySound3D (true)); - interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DVPExplicit, - new OpPlaySoundVP3D (true)); - interpreter.installSegment5 (Compiler::Sound::opcodeStopSoundExplicit, new OpStopSound); - interpreter.installSegment5 (Compiler::Sound::opcodeGetSoundPlayingExplicit, - new OpGetSoundPlaying); + interpreter.installSegment5>(Compiler::Sound::opcodeSayExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodeSayDoneExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3DExplicit); + interpreter.installSegment5>( + Compiler::Sound::opcodePlaySound3DVPExplicit); + interpreter.installSegment5>( + Compiler::Sound::opcodePlayLoopSound3DExplicit); + interpreter.installSegment5>( + Compiler::Sound::opcodePlayLoopSound3DVPExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodeStopSoundExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodeGetSoundPlayingExplicit); } } } diff --git a/apps/openmw/mwscript/soundextensions.hpp b/apps/openmw/mwscript/soundextensions.hpp index b92d7ea1b..a4aeacad6 100644 --- a/apps/openmw/mwscript/soundextensions.hpp +++ b/apps/openmw/mwscript/soundextensions.hpp @@ -15,10 +15,9 @@ namespace MWScript { namespace Sound { - // Script-extensions related to sound - void installOpcodes (Interpreter::Interpreter& interpreter); + // Script-extensions related to sound + void installOpcodes(Interpreter::Interpreter& interpreter); } } #endif - diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 01ae5426c..f90ea99a7 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -2,6 +2,7 @@ #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -14,15 +15,21 @@ */ #include +======= +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwworld/esmstore.hpp" -#include #include #include +#include #include -#include #include +#include + +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -33,445 +40,452 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/actorutil.hpp" -#include "../mwmechanics/spellcasting.hpp" #include "ref.hpp" namespace { - std::string getDialogueActorFaction(MWWorld::ConstPtr actor) + ESM::RefId getDialogueActorFaction(const MWWorld::ConstPtr& actor) { - std::string factionId = actor.getClass().getPrimaryFaction(actor); + ESM::RefId factionId = actor.getClass().getPrimaryFaction(actor); if (factionId.empty()) - throw std::runtime_error ( - "failed to determine dialogue actors faction (because actor is factionless)"); + throw std::runtime_error("failed to determine dialogue actors faction (because actor is factionless)"); return factionId; } + + void modStat(MWMechanics::AttributeValue& stat, float amount) + { + const float base = stat.getBase(); + const float modifier = stat.getModifier() - stat.getDamage(); + const float modified = base + modifier; + // Clamp to 100 unless base < 100 and we have a fortification going + if ((modifier <= 0.f || base >= 100.f) && amount > 0.f) + amount = std::clamp(100.f - modified, 0.f, amount); + // Clamp the modified value in a way that doesn't properly account for negative numbers + float newModified = modified + amount; + if (newModified < 0.f) + { + if (modified >= 0.f) + newModified = 0.f; + else if (newModified < modified) + newModified = modified; + } + // Calculate damage/fortification based on the clamped base value + stat.setBase(std::clamp(base + amount, 0.f, 100.f), true); + stat.setModifier(newModified - stat.getBase()); + } + + template + void updateBaseRecord(MWWorld::Ptr& ptr) + { + const auto& store = *MWBase::Environment::get().getESMStore(); + const T* base = store.get().find(ptr.getCellRef().getRefId()); + ptr.get()->mBase = base; + } } namespace MWScript { namespace Stats { - template + template class OpGetLevel : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).getLevel(); - Interpreter::Type_Integer value = - ptr.getClass() - .getCreatureStats (ptr) - .getLevel(); - - runtime.push (value); - } + runtime.push(value); + } }; - template + template class OpSetLevel : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); - Interpreter::Type_Integer value = runtime[0].mInteger; - runtime.pop(); - - ptr.getClass() - .getCreatureStats (ptr) - .setLevel(value); - } + ptr.getClass().getCreatureStats(ptr).setLevel(value); + } }; - template + template class OpGetAttribute : public Interpreter::Opcode0 { - int mIndex; + ESM::Attribute::AttributeID mIndex; - public: + public: + OpGetAttribute(ESM::Attribute::AttributeID index) + : mIndex(index) + { + } - OpGetAttribute (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float value = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).getModified(); - Interpreter::Type_Float value = - ptr.getClass() - .getCreatureStats (ptr) - .getAttribute(mIndex) - .getModified(); - - runtime.push (value); - } + runtime.push(value); + } }; - template + template class OpSetAttribute : public Interpreter::Opcode0 { - int mIndex; + ESM::Attribute::AttributeID mIndex; - public: + public: + OpSetAttribute(ESM::Attribute::AttributeID index) + : mIndex(index) + { + } - OpSetAttribute (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float value = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float value = runtime[0].mFloat; - runtime.pop(); - - MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); - attribute.setBase (value); - ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); - } + MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); + attribute.setBase(value, true); + ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); + } }; - template + template class OpModAttribute : public Interpreter::Opcode0 { - int mIndex; + ESM::Attribute::AttributeID mIndex; - public: + public: + OpModAttribute(ESM::Attribute::AttributeID index) + : mIndex(index) + { + } - OpModAttribute (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float value = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float value = runtime[0].mFloat; - runtime.pop(); - - MWMechanics::AttributeValue attribute = ptr.getClass() - .getCreatureStats(ptr) - .getAttribute(mIndex); - - if (value == 0) - return; - - if (((attribute.getBase() <= 0) && (value < 0)) - || ((attribute.getBase() >= 100) && (value > 0))) - return; - - if (value < 0) - attribute.setBase(std::max(0.f, attribute.getBase() + value)); - else - attribute.setBase(std::min(100.f, attribute.getBase() + value)); - - ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); - } + MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); + modStat(attribute, value); + ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); + } }; - template + template class OpGetDynamic : public Interpreter::Opcode0 { - int mIndex; + int mIndex; - public: + public: + OpGetDynamic(int index) + : mIndex(index) + { + } - OpGetDynamic (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float value; - void execute (Interpreter::Runtime& runtime) override + if (mIndex == 0 && ptr.getClass().hasItemHealth(ptr)) { - MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Float value; - - if (mIndex==0 && ptr.getClass().hasItemHealth (ptr)) - { - // health is a special case - value = static_cast(ptr.getClass().getItemMaxHealth(ptr)); - } else { - value = - ptr.getClass() - .getCreatureStats(ptr) - .getDynamic(mIndex) - .getCurrent(); - // GetMagicka shouldn't return negative values - if(mIndex == 1 && value < 0) - value = 0; - } - runtime.push (value); + // health is a special case + value = static_cast(ptr.getClass().getItemMaxHealth(ptr)); } + else + { + value = ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).getCurrent(); + // GetMagicka shouldn't return negative values + if (mIndex == 1 && value < 0) + value = 0; + } + runtime.push(value); + } }; - template + template class OpSetDynamic : public Interpreter::Opcode0 { - int mIndex; + int mIndex; - public: + public: + OpSetDynamic(int index) + : mIndex(index) + { + } - OpSetDynamic (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float value = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float value = runtime[0].mFloat; - runtime.pop(); + MWMechanics::DynamicStat stat(ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex)); - MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) - .getDynamic (mIndex)); + stat.setBase(value); + stat.setCurrent(stat.getModified(false), true, true); - stat.setModified (value, 0); - stat.setCurrent(value); - - ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); - } + ptr.getClass().getCreatureStats(ptr).setDynamic(mIndex, stat); + } }; - template + template class OpModDynamic : public Interpreter::Opcode0 { - int mIndex; + int mIndex; - public: + public: + OpModDynamic(int index) + : mIndex(index) + { + } - OpModDynamic (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + int peek = R::implicit ? 0 : runtime[0].mInteger; - void execute (Interpreter::Runtime& runtime) override + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Float diff = runtime[0].mFloat; + runtime.pop(); + + // workaround broken endgame scripts that kill dagoth ur + if (!R::implicit && ptr.getCellRef().getRefId() == "dagoth_ur_1") { - int peek = R::implicit ? 0 : runtime[0].mInteger; + runtime.push(peek); - MWWorld::Ptr ptr = R()(runtime); - - Interpreter::Type_Float diff = runtime[0].mFloat; - runtime.pop(); - - // workaround broken endgame scripts that kill dagoth ur - if (!R::implicit && - ::Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "dagoth_ur_1")) + if (R()(runtime, false, true).isEmpty()) { - runtime.push (peek); + Log(Debug::Warning) << "Warning: Compensating for broken script in Morrowind.esm by " + << "ignoring remote access to dagoth_ur_1"; - if (R()(runtime, false, true).isEmpty()) - { - Log(Debug::Warning) - << "Warning: Compensating for broken script in Morrowind.esm by " - << "ignoring remote access to dagoth_ur_1"; - - return; - } + return; } - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - - Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); - - MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) - .getDynamic (mIndex)); - - stat.setModified (diff + stat.getModified(), 0); - stat.setCurrentModified (diff + stat.getCurrentModified()); - - stat.setCurrent (diff + current); - - ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + + MWMechanics::DynamicStat stat = stats.getDynamic(mIndex); + + float current = stat.getCurrent(); + float base = diff + stat.getBase(); + if (mIndex != 2) + base = std::max(base, 0.f); + stat.setBase(base); + stat.setCurrent(diff + current, true, true); + + stats.setDynamic(mIndex, stat); + } }; - template + template class OpModCurrentDynamic : public Interpreter::Opcode0 { - int mIndex; + int mIndex; - public: + public: + OpModCurrentDynamic(int index) + : mIndex(index) + { + } - OpModCurrentDynamic (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + Interpreter::Type_Float diff = runtime[0].mFloat; + runtime.pop(); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + + Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); + + MWMechanics::DynamicStat stat(ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex)); + + bool allowDecreaseBelowZero = false; + if (mIndex == 2) // Fatigue-specific logic { - MWWorld::Ptr ptr = R()(runtime); - - Interpreter::Type_Float diff = runtime[0].mFloat; - runtime.pop(); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - - Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); - - MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) - .getDynamic (mIndex)); - - bool allowDecreaseBelowZero = false; - if (mIndex == 2) // Fatigue-specific logic - { - // For fatigue, a negative current value is allowed and means the actor will be knocked down - allowDecreaseBelowZero = true; - // Knock down the actor immediately if a non-positive new value is the case - if (diff + current <= 0.f) - ptr.getClass().getCreatureStats(ptr).setKnockedDown(true); - } - stat.setCurrent (diff + current, allowDecreaseBelowZero); - - ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); + // For fatigue, a negative current value is allowed and means the actor will be knocked down + allowDecreaseBelowZero = true; + // Knock down the actor immediately if a non-positive new value is the case + if (diff + current <= 0.f) + ptr.getClass().getCreatureStats(ptr).setKnockedDown(true); } + stat.setCurrent(diff + current, allowDecreaseBelowZero); + + ptr.getClass().getCreatureStats(ptr).setDynamic(mIndex, stat); + } }; - template + template class OpGetDynamicGetRatio : public Interpreter::Opcode0 { - int mIndex; + int mIndex; - public: + public: + OpGetDynamicGetRatio(int index) + : mIndex(index) + { + } - OpGetDynamicGetRatio (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - - Interpreter::Type_Float value = 0; - - Interpreter::Type_Float max = stats.getDynamic(mIndex).getModified(); - - if (max>0) - value = stats.getDynamic(mIndex).getCurrent() / max; - - runtime.push (value); - } + runtime.push(stats.getDynamic(mIndex).getRatio()); + } }; - template + template class OpGetSkill : public Interpreter::Opcode0 { - int mIndex; + ESM::RefId mId; - public: + public: + OpGetSkill(ESM::RefId id) + : mId(id) + { + } - OpGetSkill (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mId); - Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mIndex); - - runtime.push (value); - } + runtime.push(value); + } }; - template + template class OpSetSkill : public Interpreter::Opcode0 { - int mIndex; + ESM::RefId mId; - public: + public: + OpSetSkill(ESM::RefId id) + : mId(id) + { + } - OpSetSkill (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float value = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float value = runtime[0].mFloat; - runtime.pop(); + MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr); - MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats (ptr); - - stats.getSkill (mIndex).setBase (value); - } + stats.getSkill(mId).setBase(value, true); + } }; - template + template class OpModSkill : public Interpreter::Opcode0 { - int mIndex; + ESM::RefId mId; - public: + public: + OpModSkill(ESM::RefId id) + : mId(id) + { + } - OpModSkill (int index) : mIndex (index) {} + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float value = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float value = runtime[0].mFloat; - runtime.pop(); - - MWMechanics::SkillValue &skill = ptr.getClass() - .getNpcStats(ptr) - .getSkill(mIndex); - - if (value == 0) - return; - - if (((skill.getBase() <= 0.f) && (value < 0.f)) - || ((skill.getBase() >= 100.f) && (value > 0.f))) - return; - - if (value < 0) - skill.setBase(std::max(0.f, skill.getBase() + value)); - else - skill.setBase(std::min(100.f, skill.getBase() + value)); - } + MWMechanics::SkillValue& skill = ptr.getClass().getNpcStats(ptr).getSkill(mId); + modStat(skill, value); + } }; class OpGetPCCrimeLevel : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayerPtr(); - runtime.push (static_cast (player.getClass().getNpcStats (player).getBounty())); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayerPtr(); + runtime.push(static_cast(player.getClass().getNpcStats(player).getBounty())); + } }; class OpSetPCCrimeLevel : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayerPtr(); - void execute (Interpreter::Runtime& runtime) override - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayerPtr(); + int bounty = static_cast(runtime[0].mFloat); + runtime.pop(); + player.getClass().getNpcStats(player).setBounty(bounty); - int bounty = static_cast(runtime[0].mFloat); - runtime.pop(); - player.getClass().getNpcStats (player).setBounty(bounty); - - if (bounty == 0) - MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); - } + if (bounty == 0) + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); + } }; class OpModPCCrimeLevel : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayerPtr(); - void execute (Interpreter::Runtime& runtime) override - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayerPtr(); - - player.getClass().getNpcStats(player).setBounty(static_cast(runtime[0].mFloat) + player.getClass().getNpcStats(player).getBounty()); - runtime.pop(); - } + player.getClass().getNpcStats(player).setBounty( + static_cast(runtime[0].mFloat) + player.getClass().getNpcStats(player).getBounty()); + runtime.pop(); + } }; - template + template class OpAddSpell : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(id); + + MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + creatureStats.getSpells().add(spell); + ESM::Spell::SpellType type = static_cast(spell->mData.mType); + if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Power) { +<<<<<<< HEAD MWWorld::Ptr ptr = R()(runtime); std::string id = runtime.getStringLiteral (runtime[0].mInteger); @@ -507,16 +521,35 @@ namespace MWScript // Apply looping particles immediately for constant effects MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } +======= + // Add spell effect to *this actor's* queue immediately + creatureStats.getActiveSpells().addSpell(spell, ptr); + // Apply looping particles immediately for constant effects + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + } }; - template + template class OpRemoveSpell : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + creatureStats.getSpells().remove(id); + + MWBase::WindowManager* wm = MWBase::Environment::get().getWindowManager(); + + if (ptr == MWMechanics::getPlayer() && id == wm->getSelectedSpell()) { +<<<<<<< HEAD MWWorld::Ptr ptr = R()(runtime); std::string id = runtime.getStringLiteral (runtime[0].mInteger); @@ -567,71 +600,77 @@ namespace MWScript /* End of tes3mp change (major) */ +======= + wm->unsetSelectedSpell(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + } }; - template + template class OpRemoveSpellEffects : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId spellid = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string spellid = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); - ptr.getClass().getCreatureStats (ptr).getSpells().removeEffects(spellid); - } + ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); + } }; - template + template class OpRemoveEffects : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer effectId = runtime[0].mInteger; + runtime.pop(); - Interpreter::Type_Integer effectId = runtime[0].mInteger; - runtime.pop(); - - ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId); - } + ptr.getClass().getCreatureStats(ptr).getActiveSpells().purgeEffect(ptr, effectId); + } }; - template + template class OpGetSpell : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { - void execute (Interpreter::Runtime& runtime) override - { + MWWorld::Ptr ptr = R()(runtime); - MWWorld::Ptr ptr = R()(runtime); + ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string id = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + Interpreter::Type_Integer value = 0; - Interpreter::Type_Integer value = 0; + if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(id)) + value = 1; - if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(id)) - value = 1; - - runtime.push (value); - } + runtime.push(value); + } }; - template + template class OpPCJoinFaction : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr actor = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + ESM::RefId factionID; + + if (arg0 == 0) { +<<<<<<< HEAD MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID = ""; @@ -665,26 +704,58 @@ namespace MWScript End of tes3mp addition */ } +======= + factionID = getDialogueActorFaction(actor); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else + { + factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + } + // Make sure this faction exists + MWBase::Environment::get().getESMStore()->get().find(factionID); + + if (!factionID.empty()) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + player.getClass().getNpcStats(player).joinFaction(factionID); + } + } }; - template + template class OpPCRaiseRank : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr actor = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + ESM::RefId factionID; + + if (arg0 == 0) { - MWWorld::ConstPtr actor = R()(runtime, false); + factionID = getDialogueActorFaction(actor); + } + else + { + factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + } + // Make sure this faction exists + MWBase::Environment::get().getESMStore()->get().find(factionID); - std::string factionID = ""; - - if(arg0==0) + if (!factionID.empty()) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + if (!player.getClass().getNpcStats(player).isInFaction(factionID)) { - factionID = getDialogueActorFaction(actor); + player.getClass().getNpcStats(player).joinFaction(factionID); } else { +<<<<<<< HEAD factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } @@ -714,17 +785,28 @@ namespace MWScript /* End of tes3mp addition */ +======= + int currentRank = player.getClass().getNpcStats(player).getFactionRank(factionID); + player.getClass().getNpcStats(player).setFactionRank(factionID, currentRank + 1); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } + } }; - template + template class OpPCLowerRank : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr actor = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + ESM::RefId factionID; + + if (arg0 == 0) { +<<<<<<< HEAD MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID = ""; @@ -758,158 +840,166 @@ namespace MWScript End of tes3mp addition */ } +======= + factionID = getDialogueActorFaction(actor); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else + { + factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + } + // Make sure this faction exists + MWBase::Environment::get().getESMStore()->get().find(factionID); + + if (!factionID.empty()) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + int currentRank = player.getClass().getNpcStats(player).getFactionRank(factionID); + player.getClass().getNpcStats(player).setFactionRank(factionID, currentRank - 1); + } + } }; - template + template class OpGetPCRank : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr ptr = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + ESM::RefId factionID; + if (arg0 > 0) { - MWWorld::ConstPtr ptr = R()(runtime, false); - - std::string factionID = ""; - if(arg0 >0) - { - factionID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - } - else - { - factionID = ptr.getClass().getPrimaryFaction(ptr); - } - ::Misc::StringUtils::lowerCaseInPlace(factionID); - // Make sure this faction exists - MWBase::Environment::get().getWorld()->getStore().get().find(factionID); - - MWWorld::Ptr player = MWMechanics::getPlayer(); - if(factionID!="") - { - if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end()) - { - runtime.push(player.getClass().getNpcStats(player).getFactionRanks().at(factionID)); - } - else - { - runtime.push(-1); - } - } - else - { - runtime.push(-1); - } + factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); } + else + { + factionID = ptr.getClass().getPrimaryFaction(ptr); + } + // Make sure this faction exists + MWBase::Environment::get().getESMStore()->get().find(factionID); + + if (!factionID.empty()) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + runtime.push(player.getClass().getNpcStats(player).getFactionRank(factionID)); + } + else + { + runtime.push(-1); + } + } }; - template + template class OpModDisposition : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); - Interpreter::Type_Integer value = runtime[0].mInteger; - runtime.pop(); + if (ptr.getClass().isNpc()) + ptr.getClass().getNpcStats(ptr).setBaseDisposition( + ptr.getClass().getNpcStats(ptr).getBaseDisposition() + value); - if (ptr.getClass().isNpc()) - ptr.getClass().getNpcStats (ptr).setBaseDisposition - (ptr.getClass().getNpcStats (ptr).getBaseDisposition() + value); - - // else: must not throw exception (used by an Almalexia dialogue script) - } + // else: must not throw exception (used by an Almalexia dialogue script) + } }; - template + template class OpSetDisposition : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); - Interpreter::Type_Integer value = runtime[0].mInteger; - runtime.pop(); - - if (ptr.getClass().isNpc()) - ptr.getClass().getNpcStats (ptr).setBaseDisposition (value); - } + if (ptr.getClass().isNpc()) + ptr.getClass().getNpcStats(ptr).setBaseDisposition(value); + } }; - template + template class OpGetDisposition : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - if (!ptr.getClass().isNpc()) - runtime.push(0); - else - runtime.push (MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); - } + if (!ptr.getClass().isNpc()) + runtime.push(0); + else + runtime.push(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); + } }; class OpGetDeadCount : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - std::string id = runtime.getStringLiteral (runtime[0].mInteger); - runtime[0].mInteger = MWBase::Environment::get().getMechanicsManager()->countDeaths (id); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime[0].mInteger = MWBase::Environment::get().getMechanicsManager()->countDeaths(id); + } }; - template + template class OpGetPCFacRep : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr ptr = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + ESM::RefId factionId; + + if (arg0 == 1) { - MWWorld::ConstPtr ptr = R()(runtime, false); - - std::string factionId; - - if (arg0==1) - { - factionId = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - } - else - { - factionId = getDialogueActorFaction(ptr); - } - - if (factionId.empty()) - throw std::runtime_error ("failed to determine faction"); - - ::Misc::StringUtils::lowerCaseInPlace (factionId); - - MWWorld::Ptr player = MWMechanics::getPlayer(); - runtime.push ( - player.getClass().getNpcStats (player).getFactionReputation (factionId)); + factionId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); } + else + { + factionId = getDialogueActorFaction(ptr); + } + + if (factionId.empty()) + throw std::runtime_error("failed to determine faction"); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + runtime.push(player.getClass().getNpcStats(player).getFactionReputation(factionId)); + } }; - template + template class OpSetPCFacRep : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr ptr = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); + + ESM::RefId factionId; + + if (arg0 == 1) { - MWWorld::ConstPtr ptr = R()(runtime, false); - - Interpreter::Type_Integer value = runtime[0].mInteger; + factionId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); +<<<<<<< HEAD std::string factionId; @@ -940,20 +1030,40 @@ namespace MWScript /* End of tes3mp addition */ +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else + { + factionId = getDialogueActorFaction(ptr); + } + + if (factionId.empty()) + throw std::runtime_error("failed to determine faction"); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + player.getClass().getNpcStats(player).setFactionReputation(factionId, value); + } }; - template + template class OpModPCFacRep : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr ptr = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + Interpreter::Type_Integer value = runtime[0].mInteger; + runtime.pop(); + + ESM::RefId factionId; + + if (arg0 == 1) { - MWWorld::ConstPtr ptr = R()(runtime, false); - - Interpreter::Type_Integer value = runtime[0].mInteger; + factionId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); +<<<<<<< HEAD std::string factionId; @@ -987,106 +1097,117 @@ namespace MWScript /* End of tes3mp addition */ +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else + { + factionId = getDialogueActorFaction(ptr); + } + + if (factionId.empty()) + throw std::runtime_error("failed to determine faction"); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + player.getClass().getNpcStats(player).setFactionReputation( + factionId, player.getClass().getNpcStats(player).getFactionReputation(factionId) + value); + } }; - template + template class OpGetCommonDisease : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push (ptr.getClass().getCreatureStats (ptr).hasCommonDisease()); - } + runtime.push(ptr.getClass().getCreatureStats(ptr).hasCommonDisease()); + } }; - template + template class OpGetBlightDisease : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - - runtime.push (ptr.getClass().getCreatureStats (ptr).hasBlightDisease()); - } + runtime.push(ptr.getClass().getCreatureStats(ptr).hasBlightDisease()); + } }; - template + template class OpGetRace : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::ConstPtr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::ConstPtr ptr = R()(runtime); + ESM::RefId race = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - std::string race = runtime.getStringLiteral(runtime[0].mInteger); - ::Misc::StringUtils::lowerCaseInPlace(race); - runtime.pop(); + const ESM::RefId& npcRace = ptr.get()->mBase->mRace; - std::string npcRace = ptr.get()->mBase->mRace; - ::Misc::StringUtils::lowerCaseInPlace(npcRace); - - runtime.push (npcRace == race); + runtime.push(race == npcRace); } }; class OpGetWerewolfKills : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - - runtime.push (ptr.getClass().getNpcStats (ptr).getWerewolfKills ()); - } + runtime.push(ptr.getClass().getNpcStats(ptr).getWerewolfKills()); + } }; template class OpPcExpelled : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr ptr = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + ESM::RefId factionID; + if (arg0 > 0) { - MWWorld::ConstPtr ptr = R()(runtime, false); - - std::string factionID = ""; - if(arg0 >0 ) - { - factionID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - } - else - { - factionID = ptr.getClass().getPrimaryFaction(ptr); - } - ::Misc::StringUtils::lowerCaseInPlace(factionID); - MWWorld::Ptr player = MWMechanics::getPlayer(); - if(factionID!="") - { - runtime.push(player.getClass().getNpcStats(player).getExpelled(factionID)); - } - else - { - runtime.push(0); - } + factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); } + else + { + factionID = ptr.getClass().getPrimaryFaction(ptr); + } + MWWorld::Ptr player = MWMechanics::getPlayer(); + if (!factionID.empty()) + { + runtime.push(player.getClass().getNpcStats(player).getExpelled(factionID)); + } + else + { + runtime.push(0); + } + } }; template class OpPcExpell : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr ptr = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + ESM::RefId factionID; + if (arg0 > 0) { +<<<<<<< HEAD MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; @@ -1114,16 +1235,35 @@ namespace MWScript End of tes3mp addition */ } +======= + factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else + { + factionID = ptr.getClass().getPrimaryFaction(ptr); + } + MWWorld::Ptr player = MWMechanics::getPlayer(); + if (!factionID.empty()) + { + player.getClass().getNpcStats(player).expell(factionID); + } + } }; template class OpPcClearExpelled : public Interpreter::Opcode1 { - public: + public: + void execute(Interpreter::Runtime& runtime, unsigned int arg0) override + { + MWWorld::ConstPtr ptr = R()(runtime, false); - void execute (Interpreter::Runtime& runtime, unsigned int arg0) override + ESM::RefId factionID; + if (arg0 > 0) { +<<<<<<< HEAD MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; @@ -1150,150 +1290,150 @@ namespace MWScript /* End of tes3mp addition */ +======= + factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else + { + factionID = ptr.getClass().getPrimaryFaction(ptr); + } + MWWorld::Ptr player = MWMechanics::getPlayer(); + if (!factionID.empty()) + player.getClass().getNpcStats(player).clearExpelled(factionID); + } }; template class OpRaiseRank : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + const ESM::RefId& factionID = ptr.getClass().getPrimaryFaction(ptr); + if (factionID.empty()) + return; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + + // no-op when executed on the player + if (ptr == player) + return; + + // If we already changed rank for this NPC, modify current rank in the NPC stats. + // Otherwise take rank from base NPC record, increase it and put it to NPC data. + int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); + if (currentRank >= 0) + ptr.getClass().getNpcStats(ptr).setFactionRank(factionID, currentRank + 1); + else { - MWWorld::Ptr ptr = R()(runtime); - - std::string factionID = ptr.getClass().getPrimaryFaction(ptr); - if(factionID.empty()) - return; - - MWWorld::Ptr player = MWMechanics::getPlayer(); - - // no-op when executed on the player - if (ptr == player) - return; - - // If we already changed rank for this NPC, modify current rank in the NPC stats. - // Otherwise take rank from base NPC record, increase it and put it to NPC data. - int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); - if (currentRank >= 0) - ptr.getClass().getNpcStats(ptr).raiseRank(factionID); - else - { - int rank = ptr.getClass().getPrimaryFactionRank(ptr); - rank++; - ptr.getClass().getNpcStats(ptr).joinFaction(factionID); - for (int i=0; i class OpLowerRank : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + const ESM::RefId& factionID = ptr.getClass().getPrimaryFaction(ptr); + if (factionID.empty()) + return; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + + // no-op when executed on the player + if (ptr == player) + return; + + // If we already changed rank for this NPC, modify current rank in the NPC stats. + // Otherwise take rank from base NPC record, decrease it and put it to NPC data. + int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); + if (currentRank == 0) + return; + else if (currentRank > 0) + ptr.getClass().getNpcStats(ptr).setFactionRank(factionID, currentRank - 1); + else { - MWWorld::Ptr ptr = R()(runtime); - - std::string factionID = ptr.getClass().getPrimaryFaction(ptr); - if(factionID.empty()) - return; - - MWWorld::Ptr player = MWMechanics::getPlayer(); - - // no-op when executed on the player - if (ptr == player) - return; - - // If we already changed rank for this NPC, modify current rank in the NPC stats. - // Otherwise take rank from base NPC record, decrease it and put it to NPC data. - int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); - if (currentRank == 0) - return; - else if (currentRank > 0) - ptr.getClass().getNpcStats(ptr).lowerRank(factionID); - else - { - int rank = ptr.getClass().getPrimaryFactionRank(ptr); - rank--; - ptr.getClass().getNpcStats(ptr).joinFaction(factionID); - for (int i=0; i class OpOnDeath : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).hasDied(); - Interpreter::Type_Integer value = - ptr.getClass().getCreatureStats (ptr).hasDied(); + if (value) + ptr.getClass().getCreatureStats(ptr).clearHasDied(); - if (value) - ptr.getClass().getCreatureStats (ptr).clearHasDied(); - - runtime.push (value); - } + runtime.push(value); + } }; template class OpOnMurder : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).hasBeenMurdered(); - Interpreter::Type_Integer value = - ptr.getClass().getCreatureStats (ptr).hasBeenMurdered(); + if (value) + ptr.getClass().getCreatureStats(ptr).clearHasBeenMurdered(); - if (value) - ptr.getClass().getCreatureStats (ptr).clearHasBeenMurdered(); - - runtime.push (value); - } + runtime.push(value); + } }; template class OpOnKnockout : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).getKnockedDownOneFrame(); - Interpreter::Type_Integer value = - ptr.getClass().getCreatureStats (ptr).getKnockedDownOneFrame(); - - runtime.push (value); - } + runtime.push(value); + } }; template class OpIsWerewolf : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); + } }; template class OpSetWerewolf : public Interpreter::Opcode0 { +<<<<<<< HEAD public: void execute (Interpreter::Runtime& runtime) override @@ -1312,58 +1452,82 @@ namespace MWScript End of tes3mp addition */ } +======= + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptr, set); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; template class OpSetWerewolfAcrobatics : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); + } }; template class OpResurrect : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + if (ptr == MWMechanics::getPlayer()) { - MWWorld::Ptr ptr = R()(runtime); - - if (ptr == MWMechanics::getPlayer()) + MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Ended) + MWBase::Environment::get().getStateManager()->resumeGame(); + } + else if (ptr.getClass().getCreatureStats(ptr).isDead()) + { + bool wasEnabled = ptr.getRefData().isEnabled(); + MWBase::Environment::get().getWorld()->undeleteObject(ptr); + auto windowManager = MWBase::Environment::get().getWindowManager(); + bool wasOpen = windowManager->containsMode(MWGui::GM_Container); + windowManager->onDeleteCustomData(ptr); + // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). + MWBase::Environment::get().getWorld()->disable(ptr); + // The actor's base record may have changed after this specific reference was created. + // So we need to update to the current version + if (ptr.getClass().isNpc()) + updateBaseRecord(ptr); + else + updateBaseRecord(ptr); + if (wasOpen && !windowManager->containsMode(MWGui::GM_Container)) { + // Reopen the loot GUI if it was closed because we resurrected the actor we were looting MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); - if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Ended) - MWBase::Environment::get().getStateManager()->resumeGame(); + windowManager->forceLootMode(ptr); } - else if (ptr.getClass().getCreatureStats(ptr).isDead()) + else { - bool wasEnabled = ptr.getRefData().isEnabled(); - MWBase::Environment::get().getWorld()->undeleteObject(ptr); MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); - - // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). - MWBase::Environment::get().getWorld()->disable(ptr); // resets runtime state such as inventory, stats and AI. does not reset position in the world ptr.getRefData().setCustomData(nullptr); - if (wasEnabled) - MWBase::Environment::get().getWorld()->enable(ptr); } + if (wasEnabled) + MWBase::Environment::get().getWorld()->enable(ptr); } + } }; template class OpGetStat : public Interpreter::Opcode0 { public: - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { // dummy + runtime.pop(); runtime.push(0); } }; @@ -1375,28 +1539,28 @@ namespace MWScript int mNegativeEffect; public: - OpGetMagicEffect (int positiveEffect, int negativeEffect) + OpGetMagicEffect(int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); - float currentValue = effects.get(mPositiveEffect).getMagnitude(); + float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) - currentValue -= effects.get(mNegativeEffect).getMagnitude(); + currentValue -= effects.getOrDefault(mNegativeEffect).getMagnitude(); // GetResist* should take in account elemental shields if (mPositiveEffect == ESM::MagicEffect::ResistFire) - currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude(); + currentValue += effects.getOrDefault(ESM::MagicEffect::FireShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistShock) - currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude(); + currentValue += effects.getOrDefault(ESM::MagicEffect::LightningShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistFrost) - currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude(); + currentValue += effects.getOrDefault(ESM::MagicEffect::FrostShield).getMagnitude(); int ret = static_cast(currentValue); runtime.push(ret); @@ -1410,27 +1574,27 @@ namespace MWScript int mNegativeEffect; public: - OpSetMagicEffect (int positiveEffect, int negativeEffect) + OpSetMagicEffect(int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); - float currentValue = effects.get(mPositiveEffect).getMagnitude(); + float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) - currentValue -= effects.get(mNegativeEffect).getMagnitude(); + currentValue -= effects.getOrDefault(mNegativeEffect).getMagnitude(); // SetResist* should take in account elemental shields if (mPositiveEffect == ESM::MagicEffect::ResistFire) - currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude(); + currentValue += effects.getOrDefault(ESM::MagicEffect::FireShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistShock) - currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude(); + currentValue += effects.getOrDefault(ESM::MagicEffect::LightningShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistFrost) - currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude(); + currentValue += effects.getOrDefault(ESM::MagicEffect::FrostShield).getMagnitude(); int arg = runtime[0].mInteger; runtime.pop(); @@ -1445,13 +1609,13 @@ namespace MWScript int mNegativeEffect; public: - OpModMagicEffect (int positiveEffect, int negativeEffect) + OpModMagicEffect(int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } - void execute(Interpreter::Runtime &runtime) override + void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); @@ -1462,156 +1626,205 @@ namespace MWScript } }; + class OpGetPCVisionBonus : public Interpreter::Opcode0 + { + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWMechanics::EffectParam nightEye + = player.getClass().getCreatureStats(player).getMagicEffects().getOrDefault( + ESM::MagicEffect::NightEye); + runtime.push(std::clamp(nightEye.getMagnitude() / 100.f, 0.f, 1.f)); + } + }; + + class OpSetPCVisionBonus : public Interpreter::Opcode0 + { + public: + void execute(Interpreter::Runtime& runtime) override + { + float arg = runtime[0].mFloat; + runtime.pop(); + MWWorld::Ptr player = MWMechanics::getPlayer(); + auto& effects = player.getClass().getCreatureStats(player).getMagicEffects(); + float delta = std::clamp(arg * 100.f, 0.f, 100.f) + - effects.getOrDefault(ESM::MagicEffect::NightEye).getMagnitude(); + effects.modifyBase(ESM::MagicEffect::NightEye, static_cast(delta)); + } + }; + + class OpModPCVisionBonus : public Interpreter::Opcode0 + { + public: + void execute(Interpreter::Runtime& runtime) override + { + float arg = runtime[0].mFloat; + runtime.pop(); + MWWorld::Ptr player = MWMechanics::getPlayer(); + auto& effects = player.getClass().getCreatureStats(player).getMagicEffects(); + const MWMechanics::EffectParam nightEye = effects.getOrDefault(ESM::MagicEffect::NightEye); + float newBase = std::clamp(nightEye.getMagnitude() + arg * 100.f, 0.f, 100.f); + newBase -= nightEye.getModifier(); + float delta = std::clamp(newBase, 0.f, 100.f) - nightEye.getMagnitude(); + effects.modifyBase(ESM::MagicEffect::NightEye, static_cast(delta)); + } + }; + struct MagicEffect { int mPositiveEffect; int mNegativeEffect; }; - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - for (int i=0; i (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetAttributeExplicit+i, - new OpGetAttribute (i)); + auto id = static_cast(i); + interpreter.installSegment5>(Compiler::Stats::opcodeGetAttribute + i, id); + interpreter.installSegment5>( + Compiler::Stats::opcodeGetAttributeExplicit + i, id); - interpreter.installSegment5 (Compiler::Stats::opcodeSetAttribute+i, new OpSetAttribute (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeSetAttributeExplicit+i, - new OpSetAttribute (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeSetAttribute + i, id); + interpreter.installSegment5>( + Compiler::Stats::opcodeSetAttributeExplicit + i, id); - interpreter.installSegment5 (Compiler::Stats::opcodeModAttribute+i, new OpModAttribute (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeModAttributeExplicit+i, - new OpModAttribute (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeModAttribute + i, id); + interpreter.installSegment5>( + Compiler::Stats::opcodeModAttributeExplicit + i, id); } - for (int i=0; i (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicExplicit+i, - new OpGetDynamic (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamic + i, i); + interpreter.installSegment5>( + Compiler::Stats::opcodeGetDynamicExplicit + i, i); - interpreter.installSegment5 (Compiler::Stats::opcodeSetDynamic+i, new OpSetDynamic (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeSetDynamicExplicit+i, - new OpSetDynamic (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeSetDynamic + i, i); + interpreter.installSegment5>( + Compiler::Stats::opcodeSetDynamicExplicit + i, i); - interpreter.installSegment5 (Compiler::Stats::opcodeModDynamic+i, new OpModDynamic (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeModDynamicExplicit+i, - new OpModDynamic (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeModDynamic + i, i); + interpreter.installSegment5>( + Compiler::Stats::opcodeModDynamicExplicit + i, i); - interpreter.installSegment5 (Compiler::Stats::opcodeModCurrentDynamic+i, - new OpModCurrentDynamic (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeModCurrentDynamicExplicit+i, - new OpModCurrentDynamic (i)); + interpreter.installSegment5>( + Compiler::Stats::opcodeModCurrentDynamic + i, i); + interpreter.installSegment5>( + Compiler::Stats::opcodeModCurrentDynamicExplicit + i, i); - interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicGetRatio+i, - new OpGetDynamicGetRatio (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicGetRatioExplicit+i, - new OpGetDynamicGetRatio (i)); + interpreter.installSegment5>( + Compiler::Stats::opcodeGetDynamicGetRatio + i, i); + interpreter.installSegment5>( + Compiler::Stats::opcodeGetDynamicGetRatioExplicit + i, i); } - for (int i=0; i (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetSkillExplicit+i, new OpGetSkill (i)); + ESM::RefId id = ESM::Skill::indexToRefId(i); + interpreter.installSegment5>(Compiler::Stats::opcodeGetSkill + i, id); + interpreter.installSegment5>(Compiler::Stats::opcodeGetSkillExplicit + i, id); - interpreter.installSegment5 (Compiler::Stats::opcodeSetSkill+i, new OpSetSkill (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeSetSkillExplicit+i, new OpSetSkill (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeSetSkill + i, id); + interpreter.installSegment5>(Compiler::Stats::opcodeSetSkillExplicit + i, id); - interpreter.installSegment5 (Compiler::Stats::opcodeModSkill+i, new OpModSkill (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeModSkillExplicit+i, new OpModSkill (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeModSkill + i, id); + interpreter.installSegment5>(Compiler::Stats::opcodeModSkillExplicit + i, id); } - interpreter.installSegment5 (Compiler::Stats::opcodeGetPCCrimeLevel, new OpGetPCCrimeLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeSetPCCrimeLevel, new OpSetPCCrimeLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeModPCCrimeLevel, new OpModPCCrimeLevel); + interpreter.installSegment5(Compiler::Stats::opcodeGetPCCrimeLevel); + interpreter.installSegment5(Compiler::Stats::opcodeSetPCCrimeLevel); + interpreter.installSegment5(Compiler::Stats::opcodeModPCCrimeLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeAddSpell, new OpAddSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeAddSpellExplicit, new OpAddSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpell, new OpRemoveSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellExplicit, - new OpRemoveSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffects, new OpRemoveSpellEffects); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffectsExplicit, - new OpRemoveSpellEffects); - interpreter.installSegment5 (Compiler::Stats::opcodeResurrect, new OpResurrect); - interpreter.installSegment5 (Compiler::Stats::opcodeResurrectExplicit, - new OpResurrect); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffects, new OpRemoveEffects); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffectsExplicit, - new OpRemoveEffects); + interpreter.installSegment5>(Compiler::Stats::opcodeAddSpell); + interpreter.installSegment5>(Compiler::Stats::opcodeAddSpellExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpell); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellEffects); + interpreter.installSegment5>( + Compiler::Stats::opcodeRemoveSpellEffectsExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeResurrect); + interpreter.installSegment5>(Compiler::Stats::opcodeResurrectExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveEffects); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveEffectsExplicit); - interpreter.installSegment5 (Compiler::Stats::opcodeGetSpell, new OpGetSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeGetSpellExplicit, new OpGetSpell); + interpreter.installSegment5>(Compiler::Stats::opcodeGetSpell); + interpreter.installSegment5>(Compiler::Stats::opcodeGetSpellExplicit); - interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRank,new OpPCRaiseRank); - interpreter.installSegment3(Compiler::Stats::opcodePCLowerRank,new OpPCLowerRank); - interpreter.installSegment3(Compiler::Stats::opcodePCJoinFaction,new OpPCJoinFaction); - interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRankExplicit,new OpPCRaiseRank); - interpreter.installSegment3(Compiler::Stats::opcodePCLowerRankExplicit,new OpPCLowerRank); - interpreter.installSegment3(Compiler::Stats::opcodePCJoinFactionExplicit,new OpPCJoinFaction); - interpreter.installSegment3(Compiler::Stats::opcodeGetPCRank,new OpGetPCRank); - interpreter.installSegment3(Compiler::Stats::opcodeGetPCRankExplicit,new OpGetPCRank); + interpreter.installSegment3>(Compiler::Stats::opcodePCRaiseRank); + interpreter.installSegment3>(Compiler::Stats::opcodePCLowerRank); + interpreter.installSegment3>(Compiler::Stats::opcodePCJoinFaction); + interpreter.installSegment3>(Compiler::Stats::opcodePCRaiseRankExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodePCLowerRankExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodePCJoinFactionExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodeGetPCRank); + interpreter.installSegment3>(Compiler::Stats::opcodeGetPCRankExplicit); - interpreter.installSegment5(Compiler::Stats::opcodeModDisposition,new OpModDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeModDispositionExplicit,new OpModDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeSetDisposition,new OpSetDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeSetDispositionExplicit,new OpSetDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeGetDisposition,new OpGetDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeGetDispositionExplicit,new OpGetDisposition); + interpreter.installSegment5>(Compiler::Stats::opcodeModDisposition); + interpreter.installSegment5>(Compiler::Stats::opcodeModDispositionExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeSetDisposition); + interpreter.installSegment5>(Compiler::Stats::opcodeSetDispositionExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeGetDisposition); + interpreter.installSegment5>(Compiler::Stats::opcodeGetDispositionExplicit); - interpreter.installSegment5 (Compiler::Stats::opcodeGetLevel, new OpGetLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeGetLevelExplicit, new OpGetLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeSetLevel, new OpSetLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeSetLevelExplicit, new OpSetLevel); + interpreter.installSegment5>(Compiler::Stats::opcodeGetLevel); + interpreter.installSegment5>(Compiler::Stats::opcodeGetLevelExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeSetLevel); + interpreter.installSegment5>(Compiler::Stats::opcodeSetLevelExplicit); - interpreter.installSegment5 (Compiler::Stats::opcodeGetDeadCount, new OpGetDeadCount); + interpreter.installSegment5(Compiler::Stats::opcodeGetDeadCount); - interpreter.installSegment3 (Compiler::Stats::opcodeGetPCFacRep, new OpGetPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeGetPCFacRepExplicit, new OpGetPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeSetPCFacRep, new OpSetPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeSetPCFacRepExplicit, new OpSetPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeModPCFacRep, new OpModPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeModPCFacRepExplicit, new OpModPCFacRep); + interpreter.installSegment3>(Compiler::Stats::opcodeGetPCFacRep); + interpreter.installSegment3>(Compiler::Stats::opcodeGetPCFacRepExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodeSetPCFacRep); + interpreter.installSegment3>(Compiler::Stats::opcodeSetPCFacRepExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodeModPCFacRep); + interpreter.installSegment3>(Compiler::Stats::opcodeModPCFacRepExplicit); - interpreter.installSegment5 (Compiler::Stats::opcodeGetCommonDisease, new OpGetCommonDisease); - interpreter.installSegment5 (Compiler::Stats::opcodeGetCommonDiseaseExplicit, new OpGetCommonDisease); - interpreter.installSegment5 (Compiler::Stats::opcodeGetBlightDisease, new OpGetBlightDisease); - interpreter.installSegment5 (Compiler::Stats::opcodeGetBlightDiseaseExplicit, new OpGetBlightDisease); + interpreter.installSegment5>(Compiler::Stats::opcodeGetCommonDisease); + interpreter.installSegment5>( + Compiler::Stats::opcodeGetCommonDiseaseExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeGetBlightDisease); + interpreter.installSegment5>( + Compiler::Stats::opcodeGetBlightDiseaseExplicit); - interpreter.installSegment5 (Compiler::Stats::opcodeGetRace, new OpGetRace); - interpreter.installSegment5 (Compiler::Stats::opcodeGetRaceExplicit, new OpGetRace); - interpreter.installSegment5 (Compiler::Stats::opcodeGetWerewolfKills, new OpGetWerewolfKills); + interpreter.installSegment5>(Compiler::Stats::opcodeGetRace); + interpreter.installSegment5>(Compiler::Stats::opcodeGetRaceExplicit); + interpreter.installSegment5(Compiler::Stats::opcodeGetWerewolfKills); - interpreter.installSegment3 (Compiler::Stats::opcodePcExpelled, new OpPcExpelled); - interpreter.installSegment3 (Compiler::Stats::opcodePcExpelledExplicit, new OpPcExpelled); - interpreter.installSegment3 (Compiler::Stats::opcodePcExpell, new OpPcExpell); - interpreter.installSegment3 (Compiler::Stats::opcodePcExpellExplicit, new OpPcExpell); - interpreter.installSegment3 (Compiler::Stats::opcodePcClearExpelled, new OpPcClearExpelled); - interpreter.installSegment3 (Compiler::Stats::opcodePcClearExpelledExplicit, new OpPcClearExpelled); - interpreter.installSegment5 (Compiler::Stats::opcodeRaiseRank, new OpRaiseRank); - interpreter.installSegment5 (Compiler::Stats::opcodeRaiseRankExplicit, new OpRaiseRank); - interpreter.installSegment5 (Compiler::Stats::opcodeLowerRank, new OpLowerRank); - interpreter.installSegment5 (Compiler::Stats::opcodeLowerRankExplicit, new OpLowerRank); + interpreter.installSegment3>(Compiler::Stats::opcodePcExpelled); + interpreter.installSegment3>(Compiler::Stats::opcodePcExpelledExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodePcExpell); + interpreter.installSegment3>(Compiler::Stats::opcodePcExpellExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodePcClearExpelled); + interpreter.installSegment3>(Compiler::Stats::opcodePcClearExpelledExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeRaiseRank); + interpreter.installSegment5>(Compiler::Stats::opcodeRaiseRankExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeLowerRank); + interpreter.installSegment5>(Compiler::Stats::opcodeLowerRankExplicit); - interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath); - interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath); - interpreter.installSegment5 (Compiler::Stats::opcodeOnMurder, new OpOnMurder); - interpreter.installSegment5 (Compiler::Stats::opcodeOnMurderExplicit, new OpOnMurder); - interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout); - interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout); + interpreter.installSegment5>(Compiler::Stats::opcodeOnDeath); + interpreter.installSegment5>(Compiler::Stats::opcodeOnDeathExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeOnMurder); + interpreter.installSegment5>(Compiler::Stats::opcodeOnMurderExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeOnKnockout); + interpreter.installSegment5>(Compiler::Stats::opcodeOnKnockoutExplicit); - interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolf, new OpIsWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolfExplicit, new OpIsWerewolf); + interpreter.installSegment5>(Compiler::Stats::opcodeIsWerewolf); + interpreter.installSegment5>(Compiler::Stats::opcodeIsWerewolfExplicit); - interpreter.installSegment5 (Compiler::Stats::opcodeBecomeWerewolf, new OpSetWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeBecomeWerewolfExplicit, new OpSetWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolf, new OpSetWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics); - interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics); - interpreter.installSegment5 (Compiler::Stats::opcodeGetStat, new OpGetStat); - interpreter.installSegment5 (Compiler::Stats::opcodeGetStatExplicit, new OpGetStat); + interpreter.installSegment5>(Compiler::Stats::opcodeBecomeWerewolf); + interpreter.installSegment5>( + Compiler::Stats::opcodeBecomeWerewolfExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeUndoWerewolf); + interpreter.installSegment5>(Compiler::Stats::opcodeUndoWerewolfExplicit); + interpreter.installSegment5>( + Compiler::Stats::opcodeSetWerewolfAcrobatics); + interpreter.installSegment5>( + Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeGetStat); + interpreter.installSegment5>(Compiler::Stats::opcodeGetStatExplicit); static const MagicEffect sMagicEffects[] = { { ESM::MagicEffect::ResistMagicka, ESM::MagicEffect::WeaknessToMagicka }, @@ -1640,20 +1853,30 @@ namespace MWScript { ESM::MagicEffect::Sanctuary, -1 }, }; - for (int i=0; i<24; ++i) + for (int i = 0; i < 24; ++i) { int positive = sMagicEffects[i].mPositiveEffect; int negative = sMagicEffects[i].mNegativeEffect; - interpreter.installSegment5 (Compiler::Stats::opcodeGetMagicEffect+i, new OpGetMagicEffect (positive, negative)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetMagicEffectExplicit+i, new OpGetMagicEffect (positive, negative)); + interpreter.installSegment5>( + Compiler::Stats::opcodeGetMagicEffect + i, positive, negative); + interpreter.installSegment5>( + Compiler::Stats::opcodeGetMagicEffectExplicit + i, positive, negative); - interpreter.installSegment5 (Compiler::Stats::opcodeSetMagicEffect+i, new OpSetMagicEffect (positive, negative)); - interpreter.installSegment5 (Compiler::Stats::opcodeSetMagicEffectExplicit+i, new OpSetMagicEffect (positive, negative)); + interpreter.installSegment5>( + Compiler::Stats::opcodeSetMagicEffect + i, positive, negative); + interpreter.installSegment5>( + Compiler::Stats::opcodeSetMagicEffectExplicit + i, positive, negative); - interpreter.installSegment5 (Compiler::Stats::opcodeModMagicEffect+i, new OpModMagicEffect (positive, negative)); - interpreter.installSegment5 (Compiler::Stats::opcodeModMagicEffectExplicit+i, new OpModMagicEffect (positive, negative)); + interpreter.installSegment5>( + Compiler::Stats::opcodeModMagicEffect + i, positive, negative); + interpreter.installSegment5>( + Compiler::Stats::opcodeModMagicEffectExplicit + i, positive, negative); } + + interpreter.installSegment5(Compiler::Stats::opcodeGetPCVisionBonus); + interpreter.installSegment5(Compiler::Stats::opcodeSetPCVisionBonus); + interpreter.installSegment5(Compiler::Stats::opcodeModPCVisionBonus); } } } diff --git a/apps/openmw/mwscript/statsextensions.hpp b/apps/openmw/mwscript/statsextensions.hpp index 213b54967..07d6a34f5 100644 --- a/apps/openmw/mwscript/statsextensions.hpp +++ b/apps/openmw/mwscript/statsextensions.hpp @@ -15,8 +15,8 @@ namespace MWScript { /// \brief stats-related script functionality (creatures and NPCs) namespace Stats - { - void installOpcodes (Interpreter::Interpreter& interpreter); + { + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 31b6a988e..bcee8a65e 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -20,13 +20,13 @@ #include -#include +#include #include #include -#include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -35,8 +35,11 @@ #include "../mwworld/class.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/scene.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -45,80 +48,93 @@ namespace MWScript { namespace Transformation { - void moveStandingActors(const MWWorld::Ptr &ptr, const osg::Vec3f& diff) + void moveStandingActors(const MWWorld::Ptr& ptr, const osg::Vec3f& diff) { std::vector actors; - MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); + MWBase::Environment::get().getWorld()->getActorsStandingOn(ptr, actors); for (auto& actor : actors) - MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false, false); + MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false); } - template + template class OpGetDistance : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr from = R()(runtime, !R::implicit); + ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + if (from.isEmpty()) { - MWWorld::Ptr from = R()(runtime); - std::string name = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); + std::string error = "Missing implicit ref"; + runtime.getContext().report(error); + Log(Debug::Error) << error; + runtime.push(0.f); + return; + } - if (from.getContainerStore()) // is the object contained? + if (from.getContainerStore()) // is the object contained? + { + MWWorld::Ptr container = MWBase::Environment::get().getWorld()->findContainer(from); + + if (!container.isEmpty()) + from = container; + else { - MWWorld::Ptr container = MWBase::Environment::get().getWorld()->findContainer(from); - - if (!container.isEmpty()) - from = container; - else - { - std::string error = "Failed to find the container of object '" + from.getCellRef().getRefId() + "'"; - runtime.getContext().report(error); - Log(Debug::Error) << error; - runtime.push(0.f); - return; - } - } - - const MWWorld::Ptr to = MWBase::Environment::get().getWorld()->searchPtr(name, false); - if (to.isEmpty()) - { - std::string error = "Failed to find an instance of object '" + name + "'"; + const std::string error + = "Failed to find the container of object " + from.getCellRef().getRefId().toDebugString(); runtime.getContext().report(error); Log(Debug::Error) << error; runtime.push(0.f); return; } - - float distance; - // If the objects are in different worldspaces, return a large value (just like vanilla) - if (!to.isInCell() || !from.isInCell() || to.getCell()->getCell()->getCellId().mWorldspace != from.getCell()->getCell()->getCellId().mWorldspace) - distance = std::numeric_limits::max(); - else - { - double diff[3]; - - const float* const pos1 = to.getRefData().getPosition().pos; - const float* const pos2 = from.getRefData().getPosition().pos; - for (int i=0; i<3; ++i) - diff[i] = pos1[i] - pos2[i]; - - distance = static_cast(std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])); - } - - runtime.push(distance); } + + const MWWorld::Ptr to = MWBase::Environment::get().getWorld()->searchPtr(name, false); + if (to.isEmpty()) + { + const std::string error = "Failed to find an instance of object " + name.toDebugString(); + runtime.getContext().report(error); + Log(Debug::Error) << error; + runtime.push(0.f); + return; + } + + float distance; + // If the objects are in different worldspaces, return a large value (just like vanilla) + if (!to.isInCell() || !from.isInCell() + || to.getCell()->getCell()->getWorldSpace() != from.getCell()->getCell()->getWorldSpace()) + distance = std::numeric_limits::max(); + else + { + double diff[3]; + + const float* const pos1 = to.getRefData().getPosition().pos; + const float* const pos2 = from.getRefData().getPosition().pos; + for (int i = 0; i < 3; ++i) + diff[i] = pos1[i] - pos2[i]; + + distance = static_cast(std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])); + } + + runtime.push(distance); + } }; - template + template class OpSetScale : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float scale = runtime[0].mFloat; + runtime.pop(); +<<<<<<< HEAD Interpreter::Type_Float scale = runtime[0].mFloat; runtime.pop(); @@ -167,245 +183,307 @@ namespace MWScript End of tes3mp change (major) */ } +======= + MWBase::Environment::get().getWorld()->scaleObject(ptr, scale); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; - template + template class OpGetScale : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getCellRef().getScale()); - } + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push(ptr.getCellRef().getScale()); + } }; - template + template class OpModScale : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + Interpreter::Type_Float scale = runtime[0].mFloat; + runtime.pop(); - Interpreter::Type_Float scale = runtime[0].mFloat; - runtime.pop(); - - // add the parameter to the object's scale. - MWBase::Environment::get().getWorld()->scaleObject(ptr,ptr.getCellRef().getScale() + scale); - } + // add the parameter to the object's scale. + MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale() + scale); + } }; - template + template class OpSetAngle : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float angle = osg::DegreesToRadians(runtime[0].mFloat); + runtime.pop(); - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - Interpreter::Type_Float angle = osg::DegreesToRadians(runtime[0].mFloat); - runtime.pop(); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; + float az = ptr.getRefData().getPosition().rot[2]; - float ax = ptr.getRefData().getPosition().rot[0]; - float ay = ptr.getRefData().getPosition().rot[1]; - float az = ptr.getRefData().getPosition().rot[2]; - - // XYZ axis use the inverse (XYZ) rotation order like vanilla SetAngle. - // UWV axis use the standard (ZYX) rotation order like TESCS/OpenMW-CS and the rest of the game. - if (axis == "x") - MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_inverseOrder); - else if (axis == "y") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_inverseOrder); - else if (axis == "z") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_inverseOrder); - else if (axis == "u") - MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_none); - else if (axis == "w") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_none); - else if (axis == "v") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_none); - } + // XYZ axis use the inverse (XYZ) rotation order like vanilla SetAngle. + // UWV axis use the standard (ZYX) rotation order like TESCS/OpenMW-CS and the rest of the game. + if (axis == "x") + MWBase::Environment::get().getWorld()->rotateObject( + ptr, osg::Vec3f(angle, ay, az), MWBase::RotationFlag_inverseOrder); + else if (axis == "y") + MWBase::Environment::get().getWorld()->rotateObject( + ptr, osg::Vec3f(ax, angle, az), MWBase::RotationFlag_inverseOrder); + else if (axis == "z") + MWBase::Environment::get().getWorld()->rotateObject( + ptr, osg::Vec3f(ax, ay, angle), MWBase::RotationFlag_inverseOrder); + else if (axis == "u") + MWBase::Environment::get().getWorld()->rotateObject( + ptr, osg::Vec3f(angle, ay, az), MWBase::RotationFlag_none); + else if (axis == "w") + MWBase::Environment::get().getWorld()->rotateObject( + ptr, osg::Vec3f(ax, angle, az), MWBase::RotationFlag_none); + else if (axis == "v") + MWBase::Environment::get().getWorld()->rotateObject( + ptr, osg::Vec3f(ax, ay, angle), MWBase::RotationFlag_none); + } }; - template + template class OpGetStartingAngle : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + float ret = 0.f; + if (!axis.empty()) { - MWWorld::Ptr ptr = R()(runtime); - - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - if (axis == "x") + if (axis[0] == 'x') { - runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[0])); + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[0]); } - else if (axis == "y") + else if (axis[0] == 'y') { - runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[1])); + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[1]); } - else if (axis == "z") + else if (axis[0] == 'z') { - runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[2])); + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[2]); } } + runtime.push(ret); + } }; - template + template class OpGetAngle : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + float ret = 0.f; + if (!axis.empty()) { - MWWorld::Ptr ptr = R()(runtime); - - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - if (axis=="x") + if (axis[0] == 'x') { - runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0])); + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); } - else if (axis=="y") + else if (axis[0] == 'y') { - runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1])); + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); } - else if (axis=="z") + else if (axis[0] == 'z') { - runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2])); + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2]); } } + runtime.push(ret); + } }; - template + template class OpGetPos : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + float ret = 0.f; + if (!axis.empty()) { - MWWorld::Ptr ptr = R()(runtime); - - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - if(axis == "x") + if (axis[0] == 'x') { - runtime.push(ptr.getRefData().getPosition().pos[0]); + ret = ptr.getRefData().getPosition().pos[0]; } - else if(axis == "y") + else if (axis[0] == 'y') { - runtime.push(ptr.getRefData().getPosition().pos[1]); + ret = ptr.getRefData().getPosition().pos[1]; } - else if(axis == "z") + else if (axis[0] == 'z') { - runtime.push(ptr.getRefData().getPosition().pos[2]); + ret = ptr.getRefData().getPosition().pos[2]; } } + runtime.push(ret); + } }; - template + template class OpSetPos : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float pos = runtime[0].mFloat; + runtime.pop(); + + if (!ptr.isInCell()) + return; + + // Note: SetPos does not skip weather transitions in vanilla engine, so we do not call + // setTeleported(true) here. + + const auto curPos = ptr.getRefData().getPosition().asVec3(); + auto newPos = curPos; + if (axis == "x") { - MWWorld::Ptr ptr = R()(runtime); - - if (!ptr.isInCell()) - return; - - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - Interpreter::Type_Float pos = runtime[0].mFloat; - runtime.pop(); - - // Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here. - - const auto curPos = ptr.getRefData().getPosition().asVec3(); - auto newPos = curPos; - if(axis == "x") - { - newPos[0] = pos; - } - else if(axis == "y") - { - newPos[1] = pos; - } - 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(curPos); - - if (pos < terrainHeight) - pos = terrainHeight; - } - - newPos[2] = pos; - } - else - { - return; - } - - dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true, true)); + newPos[0] = pos; } + else if (axis == "y") + { + newPos[1] = pos; + } + 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( + curPos, ptr.getCell()->getCell()->getWorldSpace()); + + if (pos < terrainHeight) + pos = terrainHeight; + } + + newPos[2] = pos; + } + else + { + return; + } + + dynamic_cast(runtime.getContext()) + .updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true)); + } }; - template + template class OpGetStartingPos : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + float ret = 0.f; + if (!axis.empty()) { - MWWorld::Ptr ptr = R()(runtime); - - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - if(axis == "x") + if (axis[0] == 'x') { - runtime.push(ptr.getCellRef().getPosition().pos[0]); + ret = ptr.getCellRef().getPosition().pos[0]; } - else if(axis == "y") + else if (axis[0] == 'y') { - runtime.push(ptr.getCellRef().getPosition().pos[1]); + ret = ptr.getCellRef().getPosition().pos[1]; } - else if(axis == "z") + else if (axis[0] == 'z') { - runtime.push(ptr.getCellRef().getPosition().pos[2]); + ret = ptr.getCellRef().getPosition().pos[2]; } } + runtime.push(ret); + } }; - template + template class OpPositionCell : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float zRot = runtime[0].mFloat; + runtime.pop(); + std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + if (ptr.getContainerStore()) + return; + + bool isPlayer = ptr == MWMechanics::getPlayer(); + auto world = MWBase::Environment::get().getWorld(); + auto worldModel = MWBase::Environment::get().getWorldModel(); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).setTeleported(true); + if (isPlayer) + world->getPlayer().setTeleported(true); + + MWWorld::CellStore* store = worldModel->findCell(cellID); + + if (store != nullptr && store->isExterior()) + store = &worldModel->getExterior( + ESM::positionToExteriorCellLocation(x, y, store->getCell()->getWorldSpace())); + + if (store == nullptr) { - MWWorld::Ptr ptr = R()(runtime); - - if (ptr.getContainerStore()) + // cell not found, move to exterior instead if moving the player (vanilla PositionCell + // compatibility) + std::string error = "PositionCell: unknown interior cell (" + std::string(cellID) + ")"; + if (isPlayer) + error += ", moving to exterior instead"; + runtime.getContext().report(error); + if (!isPlayer) + { + Log(Debug::Error) << error; return; +<<<<<<< HEAD if (ptr == MWMechanics::getPlayer()) { @@ -500,70 +578,112 @@ namespace MWScript MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); ptr.getClass().adjustPosition(ptr, false); +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + Log(Debug::Warning) << error; + const ESM::ExteriorCellLocation cellIndex + = ESM::positionToExteriorCellLocation(x, y, ESM::Cell::sDefaultWorldspaceId); + store = &worldModel->getExterior(cellIndex); } + + MWWorld::Ptr base = ptr; + ptr = world->moveObject(ptr, store, osg::Vec3f(x, y, z)); + dynamic_cast(runtime.getContext()).updatePtr(base, ptr); + + auto rot = ptr.getRefData().getPosition().asRotationVec3(); + // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south + // = 10800, west = 16200) except for when you position the player, then degrees must be used. See + // "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. + if (!isPlayer) + zRot = zRot / 60.0f; + rot.z() = osg::DegreesToRadians(zRot); + world->rotateObject(ptr, rot); + + bool cellActive = MWBase::Environment::get().getWorldScene()->isCellActive(*ptr.getCell()); + ptr.getClass().adjustPosition(ptr, isPlayer || !cellActive); + } }; - template + template class OpPosition : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float zRot = runtime[0].mFloat; + runtime.pop(); + + if (!ptr.isInCell()) + return; + + bool isPlayer = ptr == MWMechanics::getPlayer(); + auto world = MWBase::Environment::get().getWorld(); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).setTeleported(true); + if (isPlayer) + world->getPlayer().setTeleported(true); + const ESM::ExteriorCellLocation location + = ESM::positionToExteriorCellLocation(x, y, ESM::Cell::sDefaultWorldspaceId); + + // another morrowind oddity: player will be moved to the exterior cell at this location, + // non-player actors will move within the cell they are in. + MWWorld::Ptr base = ptr; + if (isPlayer) { - MWWorld::Ptr ptr = R()(runtime); - - if (!ptr.isInCell()) - return; - - if (ptr == MWMechanics::getPlayer()) - { - MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); - } - - Interpreter::Type_Float x = runtime[0].mFloat; - runtime.pop(); - Interpreter::Type_Float y = runtime[0].mFloat; - runtime.pop(); - Interpreter::Type_Float z = runtime[0].mFloat; - runtime.pop(); - Interpreter::Type_Float zRot = runtime[0].mFloat; - runtime.pop(); - int cx,cy; - MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); - - // another morrowind oddity: player will be moved to the exterior cell at this location, - // non-player actors will move within the cell they are in. - MWWorld::Ptr base = ptr; - if (ptr == MWMechanics::getPlayer()) - { - MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); - } - else - { - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true); - } - dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - - float ax = ptr.getRefData().getPosition().rot[0]; - float ay = ptr.getRefData().getPosition().rot[1]; - // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) - // except for when you position the player, then degrees must be used. - // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. - if(ptr != MWMechanics::getPlayer()) - zRot = zRot/60.0f; - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); - ptr.getClass().adjustPosition(ptr, false); + MWWorld::CellStore* cell = &MWBase::Environment::get().getWorldModel()->getExterior(location); + ptr = world->moveObject(ptr, cell, osg::Vec3(x, y, z)); } + else + { + ptr = world->moveObject(ptr, osg::Vec3f(x, y, z), true, true); + } + dynamic_cast(runtime.getContext()).updatePtr(base, ptr); + + auto rot = ptr.getRefData().getPosition().asRotationVec3(); + // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = + // 10800, west = 16200) except for when you position the player, then degrees must be used. See + // "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. + if (!isPlayer) + zRot = zRot / 60.0f; + rot.z() = osg::DegreesToRadians(zRot); + world->rotateObject(ptr, rot); + bool cellActive = MWBase::Environment::get().getWorldScene()->isCellActive(*ptr.getCell()); + ptr.getClass().adjustPosition(ptr, isPlayer || !cellActive); + } }; class OpPlaceItemCell : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + const ESM::RefId itemID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + std::string_view cellName = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; + runtime.pop(); + + MWWorld::CellStore* const store = MWBase::Environment::get().getWorldModel()->findCell(cellName); + if (store == nullptr) { +<<<<<<< HEAD std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); @@ -647,15 +767,54 @@ namespace MWScript End of tes3mp change (major) */ } +======= + const std::string message = "unknown cell (" + std::string(cellName) + ")"; + runtime.getContext().report(message); + Log(Debug::Error) << message; + return; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + + ESM::Position pos; + pos.pos[0] = x; + pos.pos[1] = y; + pos.pos[2] = z; + pos.rot[0] = pos.rot[1] = 0; + pos.rot[2] = osg::DegreesToRadians(zRotDegrees); + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), itemID); + ref.getPtr().mRef->mData.mPhysicsPostponed = !ref.getPtr().getClass().isActor(); + ref.getPtr().getCellRef().setPosition(pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(), store, pos); + placed.getClass().adjustPosition(placed, true); + } }; class OpPlaceItem : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + ESM::RefId itemID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); - void execute (Interpreter::Runtime& runtime) override + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; + runtime.pop(); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + + if (!player.isInCell()) + throw std::runtime_error("player not in a cell"); + + MWWorld::CellStore* store = nullptr; + if (player.getCell()->isExterior()) { +<<<<<<< HEAD std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -732,20 +891,63 @@ namespace MWScript /* End of tes3mp change (major) */ +======= + const ESM::ExteriorCellLocation cellIndex + = ESM::positionToExteriorCellLocation(x, y, player.getCell()->getCell()->getWorldSpace()); + store = &MWBase::Environment::get().getWorldModel()->getExterior(cellIndex); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else + store = player.getCell(); + + ESM::Position pos; + pos.pos[0] = x; + pos.pos[1] = y; + pos.pos[2] = z; + pos.rot[0] = pos.rot[1] = 0; + pos.rot[2] = osg::DegreesToRadians(zRotDegrees); + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), itemID); + ref.getPtr().mRef->mData.mPhysicsPostponed = !ref.getPtr().getClass().isActor(); + ref.getPtr().getCellRef().setPosition(pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(), store, pos); + placed.getClass().adjustPosition(placed, true); + } }; - template + template class OpPlaceAt : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr actor = pc ? MWMechanics::getPlayer() : R()(runtime); - void execute (Interpreter::Runtime& runtime) override + ESM::RefId itemID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); + runtime.pop(); + + Interpreter::Type_Integer count = runtime[0].mInteger; + runtime.pop(); + Interpreter::Type_Float distance = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Integer direction = runtime[0].mInteger; + runtime.pop(); + + if (direction < 0 || direction > 3) + throw std::runtime_error("invalid direction"); + + if (count < 0) + throw std::runtime_error("count must be non-negative"); + + if (!actor.isInCell()) + throw std::runtime_error("actor is not in a cell"); + + for (int i = 0; i < count; ++i) { - MWWorld::Ptr actor = pc - ? MWMechanics::getPlayer() - : R()(runtime); + // create item + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), itemID, 1); + ref.getPtr().mRef->mData.mPhysicsPostponed = !ref.getPtr().getClass().isActor(); +<<<<<<< HEAD std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -812,84 +1014,92 @@ namespace MWScript End of tes3mp change (major) */ } +======= + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->safePlaceObject( + ref.getPtr(), actor, actor.getCell(), direction, distance); + MWBase::Environment::get().getWorld()->scaleObject(ptr, actor.getCellRef().getScale()); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + } }; - template + template class OpRotate : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + const MWWorld::Ptr& ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - const MWWorld::Ptr& ptr = R()(runtime); + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float rotation + = osg::DegreesToRadians(runtime[0].mFloat * MWBase::Environment::get().getFrameDuration()); + runtime.pop(); - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); - runtime.pop(); - - float ax = ptr.getRefData().getPosition().rot[0]; - float ay = ptr.getRefData().getPosition().rot[1]; - float az = ptr.getRefData().getPosition().rot[2]; - - if (axis == "x") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax+rotation,ay,az); - else if (axis == "y") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az); - else if (axis == "z") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation); - } + auto rot = ptr.getRefData().getPosition().asRotationVec3(); + // Regardless of the axis argument, the player may only be rotated on Z + if (axis == "z" || MWMechanics::getPlayer() == ptr) + rot.z() += rotation; + else if (axis == "x") + rot.x() += rotation; + else if (axis == "y") + rot.y() += rotation; + MWBase::Environment::get().getWorld()->rotateObject(ptr, rot); + } }; - template + template class OpRotateWorld : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float rotation + = osg::DegreesToRadians(runtime[0].mFloat * MWBase::Environment::get().getFrameDuration()); + runtime.pop(); - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); - runtime.pop(); + if (!ptr.getRefData().getBaseNode()) + return; - if (!ptr.getRefData().getBaseNode()) - return; + // We can rotate actors only around Z axis + if (ptr.getClass().isActor() && (axis == "x" || axis == "y")) + return; - // We can rotate actors only around Z axis - if (ptr.getClass().isActor() && (axis == "x" || axis == "y")) - return; + osg::Quat rot; + if (axis == "x") + rot = osg::Quat(rotation, -osg::X_AXIS); + else if (axis == "y") + rot = osg::Quat(rotation, -osg::Y_AXIS); + else if (axis == "z") + rot = osg::Quat(rotation, -osg::Z_AXIS); + else + return; - osg::Quat rot; - if (axis == "x") - rot = osg::Quat(rotation, -osg::X_AXIS); - else if (axis == "y") - rot = osg::Quat(rotation, -osg::Y_AXIS); - else if (axis == "z") - rot = osg::Quat(rotation, -osg::Z_AXIS); - else - return; - - osg::Quat attitude = ptr.getRefData().getBaseNode()->getAttitude(); - MWBase::Environment::get().getWorld()->rotateWorldObject(ptr, attitude * rot); - } + osg::Quat attitude = ptr.getRefData().getBaseNode()->getAttitude(); + MWBase::Environment::get().getWorld()->rotateWorldObject(ptr, attitude * rot); + } }; - template + template class OpSetAtStart : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; - if (!ptr.isInCell()) - return; + MWBase::Environment::get().getWorld()->rotateObject( + ptr, ptr.getCellRef().getPosition().asRotationVec3()); +<<<<<<< HEAD float xr = ptr.getCellRef().getPosition().rot[0]; float yr = ptr.getCellRef().getPosition().rot[1]; float zr = ptr.getCellRef().getPosition().rot[2]; @@ -900,96 +1110,100 @@ namespace MWScript MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2])); } +======= + dynamic_cast(runtime.getContext()) + .updatePtr(ptr, + MWBase::Environment::get().getWorld()->moveObject( + ptr, ptr.getCellRef().getPosition().asVec3())); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 }; - template + template class OpMove : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + const MWWorld::Ptr& ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override + if (!ptr.isInCell()) + return; + + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float movement = (runtime[0].mFloat * MWBase::Environment::get().getFrameDuration()); + runtime.pop(); + + osg::Vec3f posChange; + if (axis == "x") { - const MWWorld::Ptr& ptr = R()(runtime); - - if (!ptr.isInCell()) - return; - - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); - runtime.pop(); - - osg::Vec3f posChange; - if (axis == "x") - { - posChange=osg::Vec3f(movement, 0, 0); - } - else if (axis == "y") - { - posChange=osg::Vec3f(0, movement, 0); - } - else if (axis == "z") - { - posChange=osg::Vec3f(0, 0, movement); - } - else - return; - - // is it correct that disabled objects can't be Move-d? - if (!ptr.getRefData().getBaseNode()) - return; - - osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; - - // We should move actors, standing on moving object, too. - // This approach can be used to create elevators. - moveStandingActors(ptr, diff); - dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false, true)); + posChange = osg::Vec3f(movement, 0, 0); } + else if (axis == "y") + { + posChange = osg::Vec3f(0, movement, 0); + } + else if (axis == "z") + { + posChange = osg::Vec3f(0, 0, movement); + } + else + return; + + // is it correct that disabled objects can't be Move-d? + if (!ptr.getRefData().getBaseNode()) + return; + + osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; + + // We should move actors, standing on moving object, too. + // This approach can be used to create elevators. + moveStandingActors(ptr, diff); + dynamic_cast(runtime.getContext()) + .updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); + } }; - template + template class OpMoveWorld : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { - MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; - if (!ptr.isInCell()) - return; + std::string_view axis = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float movement = (runtime[0].mFloat * MWBase::Environment::get().getFrameDuration()); + runtime.pop(); - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); - runtime.pop(); + osg::Vec3f diff; - osg::Vec3f diff; + if (axis == "x") + diff.x() = movement; + else if (axis == "y") + diff.y() = movement; + else if (axis == "z") + diff.z() = movement; + else + return; - if (axis == "x") - diff.x() = movement; - else if (axis == "y") - diff.y() = movement; - else if (axis == "z") - diff.z() = movement; - else - return; - - // We should move actors, standing on moving object, too. - // This approach can be used to create elevators. - moveStandingActors(ptr, diff); - dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false, true)); - } + // We should move actors, standing on moving object, too. + // This approach can be used to create elevators. + moveStandingActors(ptr, diff); + dynamic_cast(runtime.getContext()) + .updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); + } }; class OpResetActors : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWorld()->resetActors(); } @@ -998,56 +1212,62 @@ namespace MWScript class OpFixme : public Interpreter::Opcode0 { public: - - void execute (Interpreter::Runtime& runtime) override + void execute(Interpreter::Runtime& runtime) override { MWBase::Environment::get().getWorld()->fixPosition(); } }; - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5(Compiler::Transformation::opcodeGetDistance, new OpGetDistance); - interpreter.installSegment5(Compiler::Transformation::opcodeGetDistanceExplicit, new OpGetDistance); - interpreter.installSegment5(Compiler::Transformation::opcodeSetScale,new OpSetScale); - interpreter.installSegment5(Compiler::Transformation::opcodeSetScaleExplicit,new OpSetScale); - interpreter.installSegment5(Compiler::Transformation::opcodeSetAngle,new OpSetAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeSetAngleExplicit,new OpSetAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeGetScale,new OpGetScale); - interpreter.installSegment5(Compiler::Transformation::opcodeGetScaleExplicit,new OpGetScale); - interpreter.installSegment5(Compiler::Transformation::opcodeGetAngle,new OpGetAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeGetAngleExplicit,new OpGetAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeGetPos,new OpGetPos); - interpreter.installSegment5(Compiler::Transformation::opcodeGetPosExplicit,new OpGetPos); - interpreter.installSegment5(Compiler::Transformation::opcodeSetPos,new OpSetPos); - interpreter.installSegment5(Compiler::Transformation::opcodeSetPosExplicit,new OpSetPos); - interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingPos,new OpGetStartingPos); - interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingPosExplicit,new OpGetStartingPos); - interpreter.installSegment5(Compiler::Transformation::opcodePosition,new OpPosition); - interpreter.installSegment5(Compiler::Transformation::opcodePositionExplicit,new OpPosition); - interpreter.installSegment5(Compiler::Transformation::opcodePositionCell,new OpPositionCell); - interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt); - interpreter.installSegment5(Compiler::Transformation::opcodeModScale,new OpModScale); - interpreter.installSegment5(Compiler::Transformation::opcodeModScaleExplicit,new OpModScale); - interpreter.installSegment5(Compiler::Transformation::opcodeRotate,new OpRotate); - interpreter.installSegment5(Compiler::Transformation::opcodeRotateExplicit,new OpRotate); - interpreter.installSegment5(Compiler::Transformation::opcodeRotateWorld,new OpRotateWorld); - interpreter.installSegment5(Compiler::Transformation::opcodeRotateWorldExplicit,new OpRotateWorld); - interpreter.installSegment5(Compiler::Transformation::opcodeSetAtStart,new OpSetAtStart); - interpreter.installSegment5(Compiler::Transformation::opcodeSetAtStartExplicit,new OpSetAtStart); - interpreter.installSegment5(Compiler::Transformation::opcodeMove,new OpMove); - interpreter.installSegment5(Compiler::Transformation::opcodeMoveExplicit,new OpMove); - interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorld,new OpMoveWorld); - interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorldExplicit,new OpMoveWorld); - interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeResetActors, new OpResetActors); - interpreter.installSegment5(Compiler::Transformation::opcodeFixme, new OpFixme); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetDistance); + interpreter.installSegment5>( + Compiler::Transformation::opcodeGetDistanceExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetScale); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetScaleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetAngle); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetAngleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetScale); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetScaleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetAngle); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetAngleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetPos); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetPosExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetPos); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetPosExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingPos); + interpreter.installSegment5>( + Compiler::Transformation::opcodeGetStartingPosExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodePosition); + interpreter.installSegment5>(Compiler::Transformation::opcodePositionExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodePositionCell); + interpreter.installSegment5>( + Compiler::Transformation::opcodePositionCellExplicit); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem); + interpreter.installSegment5>(Compiler::Transformation::opcodePlaceAtPc); + interpreter.installSegment5>(Compiler::Transformation::opcodePlaceAtMe); + interpreter.installSegment5>( + Compiler::Transformation::opcodePlaceAtMeExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeModScale); + interpreter.installSegment5>(Compiler::Transformation::opcodeModScaleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeRotate); + interpreter.installSegment5>(Compiler::Transformation::opcodeRotateExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeRotateWorld); + interpreter.installSegment5>( + Compiler::Transformation::opcodeRotateWorldExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetAtStart); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetAtStartExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeMove); + interpreter.installSegment5>(Compiler::Transformation::opcodeMoveExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeMoveWorld); + interpreter.installSegment5>(Compiler::Transformation::opcodeMoveWorldExplicit); + interpreter.installSegment5>( + Compiler::Transformation::opcodeGetStartingAngle); + interpreter.installSegment5>( + Compiler::Transformation::opcodeGetStartingAngleExplicit); + interpreter.installSegment5(Compiler::Transformation::opcodeResetActors); + interpreter.installSegment5(Compiler::Transformation::opcodeFixme); } } } diff --git a/apps/openmw/mwscript/transformationextensions.hpp b/apps/openmw/mwscript/transformationextensions.hpp index 7a4d29e06..949431b10 100644 --- a/apps/openmw/mwscript/transformationextensions.hpp +++ b/apps/openmw/mwscript/transformationextensions.hpp @@ -15,8 +15,8 @@ namespace MWScript { /// \brief stats-related script functionality (creatures and NPCs) namespace Transformation - { - void installOpcodes (Interpreter::Interpreter& interpreter); + { + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwscript/userextensions.cpp b/apps/openmw/mwscript/userextensions.cpp index 3f443304d..91e04e5f1 100644 --- a/apps/openmw/mwscript/userextensions.cpp +++ b/apps/openmw/mwscript/userextensions.cpp @@ -1,12 +1,11 @@ #include "userextensions.hpp" -#include #include -#include -#include -#include #include +#include +#include +#include #include "ref.hpp" @@ -19,59 +18,48 @@ namespace MWScript { class OpUser1 : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.getContext().report ("user1: not in use"); - } + public: + void execute(Interpreter::Runtime& runtime) override { runtime.getContext().report("user1: not in use"); } }; class OpUser2 : public Interpreter::Opcode0 { - public: - - void execute (Interpreter::Runtime& runtime) override - { - runtime.getContext().report ("user2: not in use"); - } + public: + void execute(Interpreter::Runtime& runtime) override { runtime.getContext().report("user2: not in use"); } }; - template + template class OpUser3 : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + // MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { -// MWWorld::Ptr ptr = R()(runtime); - - runtime.getContext().report ("user3: not in use"); - } + runtime.getContext().report("user3: not in use"); + } }; - template + template class OpUser4 : public Interpreter::Opcode0 { - public: + public: + void execute(Interpreter::Runtime& runtime) override + { + // MWWorld::Ptr ptr = R()(runtime); - void execute (Interpreter::Runtime& runtime) override - { -// MWWorld::Ptr ptr = R()(runtime); - - runtime.getContext().report ("user4: not in use"); - } + runtime.getContext().report("user4: not in use"); + } }; - - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::User::opcodeUser1, new OpUser1); - interpreter.installSegment5 (Compiler::User::opcodeUser2, new OpUser2); - interpreter.installSegment5 (Compiler::User::opcodeUser3, new OpUser3); - interpreter.installSegment5 (Compiler::User::opcodeUser3Explicit, new OpUser3); - interpreter.installSegment5 (Compiler::User::opcodeUser4, new OpUser4); - interpreter.installSegment5 (Compiler::User::opcodeUser4Explicit, new OpUser4); + interpreter.installSegment5(Compiler::User::opcodeUser1); + interpreter.installSegment5(Compiler::User::opcodeUser2); + interpreter.installSegment5>(Compiler::User::opcodeUser3); + interpreter.installSegment5>(Compiler::User::opcodeUser3Explicit); + interpreter.installSegment5>(Compiler::User::opcodeUser4); + interpreter.installSegment5>(Compiler::User::opcodeUser4Explicit); } } } diff --git a/apps/openmw/mwscript/userextensions.hpp b/apps/openmw/mwscript/userextensions.hpp index da6e0faa6..2310c2e9a 100644 --- a/apps/openmw/mwscript/userextensions.hpp +++ b/apps/openmw/mwscript/userextensions.hpp @@ -16,7 +16,7 @@ namespace MWScript /// \brief Temporary script functionality limited to the console namespace User { - void installOpcodes (Interpreter::Interpreter& interpreter); + void installOpcodes(Interpreter::Interpreter& interpreter); } } diff --git a/apps/openmw/mwsound/alext.h b/apps/openmw/mwsound/alext.h index 7162fa955..f30cbbdc6 100644 --- a/apps/openmw/mwsound/alext.h +++ b/apps/openmw/mwsound/alext.h @@ -1,141 +1,150 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2008 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to https://www.gnu.org/copyleft/lgpl.html - */ - #ifndef AL_ALEXT_H #define AL_ALEXT_H #include -/* Define int64_t and uint64_t types */ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -#include -#elif defined(_WIN32) && defined(__GNUC__) +/* Define int64 and uint64 types */ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__cplusplus) && __cplusplus >= 201103L) #include +typedef int64_t _alsoft_int64_t; +typedef uint64_t _alsoft_uint64_t; #elif defined(_WIN32) -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; +typedef __int64 _alsoft_int64_t; +typedef unsigned __int64 _alsoft_uint64_t; #else /* Fallback if nothing above works */ -#include +#include +typedef int64_t _alsoft_int64_t; +typedef uint64_t _alsoft_uint64_t; #endif -#include "alc.h" #include "al.h" +#include "alc.h" #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif #ifndef AL_LOKI_IMA_ADPCM_format #define AL_LOKI_IMA_ADPCM_format 1 -#define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 -#define AL_FORMAT_IMA_ADPCM_STEREO16_EXT 0x10001 +#define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 +#define AL_FORMAT_IMA_ADPCM_STEREO16_EXT 0x10001 #endif #ifndef AL_LOKI_WAVE_format #define AL_LOKI_WAVE_format 1 -#define AL_FORMAT_WAVE_EXT 0x10002 +#define AL_FORMAT_WAVE_EXT 0x10002 #endif #ifndef AL_EXT_vorbis #define AL_EXT_vorbis 1 -#define AL_FORMAT_VORBIS_EXT 0x10003 +#define AL_FORMAT_VORBIS_EXT 0x10003 #endif #ifndef AL_LOKI_quadriphonic #define AL_LOKI_quadriphonic 1 -#define AL_FORMAT_QUAD8_LOKI 0x10004 -#define AL_FORMAT_QUAD16_LOKI 0x10005 +#define AL_FORMAT_QUAD8_LOKI 0x10004 +#define AL_FORMAT_QUAD16_LOKI 0x10005 #endif #ifndef AL_EXT_float32 #define AL_EXT_float32 1 -#define AL_FORMAT_MONO_FLOAT32 0x10010 -#define AL_FORMAT_STEREO_FLOAT32 0x10011 +#define AL_FORMAT_MONO_FLOAT32 0x10010 +#define AL_FORMAT_STEREO_FLOAT32 0x10011 #endif #ifndef AL_EXT_double #define AL_EXT_double 1 -#define AL_FORMAT_MONO_DOUBLE_EXT 0x10012 -#define AL_FORMAT_STEREO_DOUBLE_EXT 0x10013 +#define AL_FORMAT_MONO_DOUBLE_EXT 0x10012 +#define AL_FORMAT_STEREO_DOUBLE_EXT 0x10013 #endif #ifndef AL_EXT_MULAW #define AL_EXT_MULAW 1 -#define AL_FORMAT_MONO_MULAW_EXT 0x10014 -#define AL_FORMAT_STEREO_MULAW_EXT 0x10015 +#define AL_FORMAT_MONO_MULAW_EXT 0x10014 +#define AL_FORMAT_STEREO_MULAW_EXT 0x10015 #endif #ifndef AL_EXT_ALAW #define AL_EXT_ALAW 1 -#define AL_FORMAT_MONO_ALAW_EXT 0x10016 -#define AL_FORMAT_STEREO_ALAW_EXT 0x10017 +#define AL_FORMAT_MONO_ALAW_EXT 0x10016 +#define AL_FORMAT_STEREO_ALAW_EXT 0x10017 #endif #ifndef ALC_LOKI_audio_channel #define ALC_LOKI_audio_channel 1 -#define ALC_CHAN_MAIN_LOKI 0x500001 -#define ALC_CHAN_PCM_LOKI 0x500002 -#define ALC_CHAN_CD_LOKI 0x500003 +#define ALC_CHAN_MAIN_LOKI 0x500001 +#define ALC_CHAN_PCM_LOKI 0x500002 +#define ALC_CHAN_CD_LOKI 0x500003 #endif #ifndef AL_EXT_MCFORMATS #define AL_EXT_MCFORMATS 1 -#define AL_FORMAT_QUAD8 0x1204 -#define AL_FORMAT_QUAD16 0x1205 -#define AL_FORMAT_QUAD32 0x1206 -#define AL_FORMAT_REAR8 0x1207 -#define AL_FORMAT_REAR16 0x1208 -#define AL_FORMAT_REAR32 0x1209 -#define AL_FORMAT_51CHN8 0x120A -#define AL_FORMAT_51CHN16 0x120B -#define AL_FORMAT_51CHN32 0x120C -#define AL_FORMAT_61CHN8 0x120D -#define AL_FORMAT_61CHN16 0x120E -#define AL_FORMAT_61CHN32 0x120F -#define AL_FORMAT_71CHN8 0x1210 -#define AL_FORMAT_71CHN16 0x1211 -#define AL_FORMAT_71CHN32 0x1212 +/* Provides support for surround sound buffer formats with 8, 16, and 32-bit + * samples. + * + * QUAD8: Unsigned 8-bit, Quadraphonic (Front Left, Front Right, Rear Left, + * Rear Right). + * QUAD16: Signed 16-bit, Quadraphonic. + * QUAD32: 32-bit float, Quadraphonic. + * REAR8: Unsigned 8-bit, Rear Stereo (Rear Left, Rear Right). + * REAR16: Signed 16-bit, Rear Stereo. + * REAR32: 32-bit float, Rear Stereo. + * 51CHN8: Unsigned 8-bit, 5.1 Surround (Front Left, Front Right, Front Center, + * LFE, Side Left, Side Right). Note that some audio systems may label + * 5.1's Side channels as Rear or Surround; they are equivalent for the + * purposes of this extension. + * 51CHN16: Signed 16-bit, 5.1 Surround. + * 51CHN32: 32-bit float, 5.1 Surround. + * 61CHN8: Unsigned 8-bit, 6.1 Surround (Front Left, Front Right, Front Center, + * LFE, Rear Center, Side Left, Side Right). + * 61CHN16: Signed 16-bit, 6.1 Surround. + * 61CHN32: 32-bit float, 6.1 Surround. + * 71CHN8: Unsigned 8-bit, 7.1 Surround (Front Left, Front Right, Front Center, + * LFE, Rear Left, Rear Right, Side Left, Side Right). + * 71CHN16: Signed 16-bit, 7.1 Surround. + * 71CHN32: 32-bit float, 7.1 Surround. + */ +#define AL_FORMAT_QUAD8 0x1204 +#define AL_FORMAT_QUAD16 0x1205 +#define AL_FORMAT_QUAD32 0x1206 +#define AL_FORMAT_REAR8 0x1207 +#define AL_FORMAT_REAR16 0x1208 +#define AL_FORMAT_REAR32 0x1209 +#define AL_FORMAT_51CHN8 0x120A +#define AL_FORMAT_51CHN16 0x120B +#define AL_FORMAT_51CHN32 0x120C +#define AL_FORMAT_61CHN8 0x120D +#define AL_FORMAT_61CHN16 0x120E +#define AL_FORMAT_61CHN32 0x120F +#define AL_FORMAT_71CHN8 0x1210 +#define AL_FORMAT_71CHN16 0x1211 +#define AL_FORMAT_71CHN32 0x1212 #endif #ifndef AL_EXT_MULAW_MCFORMATS #define AL_EXT_MULAW_MCFORMATS 1 -#define AL_FORMAT_MONO_MULAW 0x10014 -#define AL_FORMAT_STEREO_MULAW 0x10015 -#define AL_FORMAT_QUAD_MULAW 0x10021 -#define AL_FORMAT_REAR_MULAW 0x10022 -#define AL_FORMAT_51CHN_MULAW 0x10023 -#define AL_FORMAT_61CHN_MULAW 0x10024 -#define AL_FORMAT_71CHN_MULAW 0x10025 +#define AL_FORMAT_MONO_MULAW 0x10014 +#define AL_FORMAT_STEREO_MULAW 0x10015 +#define AL_FORMAT_QUAD_MULAW 0x10021 +#define AL_FORMAT_REAR_MULAW 0x10022 +#define AL_FORMAT_51CHN_MULAW 0x10023 +#define AL_FORMAT_61CHN_MULAW 0x10024 +#define AL_FORMAT_71CHN_MULAW 0x10025 #endif #ifndef AL_EXT_IMA4 #define AL_EXT_IMA4 1 -#define AL_FORMAT_MONO_IMA4 0x1300 -#define AL_FORMAT_STEREO_IMA4 0x1301 +#define AL_FORMAT_MONO_IMA4 0x1300 +#define AL_FORMAT_STEREO_IMA4 0x1301 #endif #ifndef AL_EXT_STATIC_BUFFER #define AL_EXT_STATIC_BUFFER 1 -typedef ALvoid (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALint,ALenum,ALvoid*,ALsizei,ALsizei); + typedef void(AL_APIENTRY* PFNALBUFFERDATASTATICPROC)(const ALint, ALenum, ALvoid*, ALsizei, ALsizei); #ifdef AL_ALEXT_PROTOTYPES -AL_API ALvoid AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, ALvoid *data, ALsizei len, ALsizei freq); + AL_API void AL_APIENTRY alBufferDataStatic( + const ALint buffer, ALenum format, ALvoid* data, ALsizei len, ALsizei freq); #endif #endif @@ -146,234 +155,244 @@ AL_API ALvoid AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, #ifndef ALC_EXT_disconnect #define ALC_EXT_disconnect 1 -#define ALC_CONNECTED 0x313 +#define ALC_CONNECTED 0x313 #endif #ifndef ALC_EXT_thread_local_context #define ALC_EXT_thread_local_context 1 -typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context); -typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void); + typedef ALCboolean(ALC_APIENTRY* PFNALCSETTHREADCONTEXTPROC)(ALCcontext* context); + typedef ALCcontext*(ALC_APIENTRY* PFNALCGETTHREADCONTEXTPROC)(void); #ifdef AL_ALEXT_PROTOTYPES -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context); -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); + ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext* context); + ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); #endif #endif #ifndef AL_EXT_source_distance_model #define AL_EXT_source_distance_model 1 -#define AL_SOURCE_DISTANCE_MODEL 0x200 +#define AL_SOURCE_DISTANCE_MODEL 0x200 #endif #ifndef AL_SOFT_buffer_sub_data #define AL_SOFT_buffer_sub_data 1 -#define AL_BYTE_RW_OFFSETS_SOFT 0x1031 -#define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 -typedef ALvoid (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); +#define AL_BYTE_RW_OFFSETS_SOFT 0x1031 +#define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 + typedef void(AL_APIENTRY* PFNALBUFFERSUBDATASOFTPROC)(ALuint, ALenum, const ALvoid*, ALsizei, ALsizei); #ifdef AL_ALEXT_PROTOTYPES -AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); + AL_API void AL_APIENTRY alBufferSubDataSOFT( + ALuint buffer, ALenum format, const ALvoid* data, ALsizei offset, ALsizei length); #endif #endif #ifndef AL_SOFT_loop_points #define AL_SOFT_loop_points 1 -#define AL_LOOP_POINTS_SOFT 0x2015 +#define AL_LOOP_POINTS_SOFT 0x2015 #endif #ifndef AL_EXT_FOLDBACK #define AL_EXT_FOLDBACK 1 -#define AL_EXT_FOLDBACK_NAME "AL_EXT_FOLDBACK" -#define AL_FOLDBACK_EVENT_BLOCK 0x4112 -#define AL_FOLDBACK_EVENT_START 0x4111 -#define AL_FOLDBACK_EVENT_STOP 0x4113 -#define AL_FOLDBACK_MODE_MONO 0x4101 -#define AL_FOLDBACK_MODE_STEREO 0x4102 -typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei); -typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK); -typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void); +#define AL_EXT_FOLDBACK_NAME "AL_EXT_FOLDBACK" +#define AL_FOLDBACK_EVENT_BLOCK 0x4112 +#define AL_FOLDBACK_EVENT_START 0x4111 +#define AL_FOLDBACK_EVENT_STOP 0x4113 +#define AL_FOLDBACK_MODE_MONO 0x4101 +#define AL_FOLDBACK_MODE_STEREO 0x4102 + typedef void(AL_APIENTRY* LPALFOLDBACKCALLBACK)(ALenum, ALsizei); + typedef void(AL_APIENTRY* LPALREQUESTFOLDBACKSTART)(ALenum, ALsizei, ALsizei, ALfloat*, LPALFOLDBACKCALLBACK); + typedef void(AL_APIENTRY* LPALREQUESTFOLDBACKSTOP)(void); #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback); -AL_API void AL_APIENTRY alRequestFoldbackStop(void); + AL_API void AL_APIENTRY alRequestFoldbackStart( + ALenum mode, ALsizei count, ALsizei length, ALfloat* mem, LPALFOLDBACKCALLBACK callback); + AL_API void AL_APIENTRY alRequestFoldbackStop(void); #endif #endif #ifndef ALC_EXT_DEDICATED #define ALC_EXT_DEDICATED 1 -#define AL_DEDICATED_GAIN 0x0001 -#define AL_EFFECT_DEDICATED_DIALOGUE 0x9001 +#define AL_DEDICATED_GAIN 0x0001 +#define AL_EFFECT_DEDICATED_DIALOGUE 0x9001 #define AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT 0x9000 #endif #ifndef AL_SOFT_buffer_samples #define AL_SOFT_buffer_samples 1 /* Channel configurations */ -#define AL_MONO_SOFT 0x1500 -#define AL_STEREO_SOFT 0x1501 -#define AL_REAR_SOFT 0x1502 -#define AL_QUAD_SOFT 0x1503 -#define AL_5POINT1_SOFT 0x1504 -#define AL_6POINT1_SOFT 0x1505 -#define AL_7POINT1_SOFT 0x1506 +#define AL_MONO_SOFT 0x1500 +#define AL_STEREO_SOFT 0x1501 +#define AL_REAR_SOFT 0x1502 +#define AL_QUAD_SOFT 0x1503 +#define AL_5POINT1_SOFT 0x1504 +#define AL_6POINT1_SOFT 0x1505 +#define AL_7POINT1_SOFT 0x1506 /* Sample types */ -#define AL_BYTE_SOFT 0x1400 -#define AL_UNSIGNED_BYTE_SOFT 0x1401 -#define AL_SHORT_SOFT 0x1402 -#define AL_UNSIGNED_SHORT_SOFT 0x1403 -#define AL_INT_SOFT 0x1404 -#define AL_UNSIGNED_INT_SOFT 0x1405 -#define AL_FLOAT_SOFT 0x1406 -#define AL_DOUBLE_SOFT 0x1407 -#define AL_BYTE3_SOFT 0x1408 -#define AL_UNSIGNED_BYTE3_SOFT 0x1409 +#define AL_BYTE_SOFT 0x1400 +#define AL_UNSIGNED_BYTE_SOFT 0x1401 +#define AL_SHORT_SOFT 0x1402 +#define AL_UNSIGNED_SHORT_SOFT 0x1403 +#define AL_INT_SOFT 0x1404 +#define AL_UNSIGNED_INT_SOFT 0x1405 +#define AL_FLOAT_SOFT 0x1406 +#define AL_DOUBLE_SOFT 0x1407 +#define AL_BYTE3_SOFT 0x1408 +#define AL_UNSIGNED_BYTE3_SOFT 0x1409 /* Storage formats */ -#define AL_MONO8_SOFT 0x1100 -#define AL_MONO16_SOFT 0x1101 -#define AL_MONO32F_SOFT 0x10010 -#define AL_STEREO8_SOFT 0x1102 -#define AL_STEREO16_SOFT 0x1103 -#define AL_STEREO32F_SOFT 0x10011 -#define AL_QUAD8_SOFT 0x1204 -#define AL_QUAD16_SOFT 0x1205 -#define AL_QUAD32F_SOFT 0x1206 -#define AL_REAR8_SOFT 0x1207 -#define AL_REAR16_SOFT 0x1208 -#define AL_REAR32F_SOFT 0x1209 -#define AL_5POINT1_8_SOFT 0x120A -#define AL_5POINT1_16_SOFT 0x120B -#define AL_5POINT1_32F_SOFT 0x120C -#define AL_6POINT1_8_SOFT 0x120D -#define AL_6POINT1_16_SOFT 0x120E -#define AL_6POINT1_32F_SOFT 0x120F -#define AL_7POINT1_8_SOFT 0x1210 -#define AL_7POINT1_16_SOFT 0x1211 -#define AL_7POINT1_32F_SOFT 0x1212 +#define AL_MONO8_SOFT 0x1100 +#define AL_MONO16_SOFT 0x1101 +#define AL_MONO32F_SOFT 0x10010 +#define AL_STEREO8_SOFT 0x1102 +#define AL_STEREO16_SOFT 0x1103 +#define AL_STEREO32F_SOFT 0x10011 +#define AL_QUAD8_SOFT 0x1204 +#define AL_QUAD16_SOFT 0x1205 +#define AL_QUAD32F_SOFT 0x1206 +#define AL_REAR8_SOFT 0x1207 +#define AL_REAR16_SOFT 0x1208 +#define AL_REAR32F_SOFT 0x1209 +#define AL_5POINT1_8_SOFT 0x120A +#define AL_5POINT1_16_SOFT 0x120B +#define AL_5POINT1_32F_SOFT 0x120C +#define AL_6POINT1_8_SOFT 0x120D +#define AL_6POINT1_16_SOFT 0x120E +#define AL_6POINT1_32F_SOFT 0x120F +#define AL_7POINT1_8_SOFT 0x1210 +#define AL_7POINT1_16_SOFT 0x1211 +#define AL_7POINT1_32F_SOFT 0x1212 /* Buffer attributes */ -#define AL_INTERNAL_FORMAT_SOFT 0x2008 -#define AL_BYTE_LENGTH_SOFT 0x2009 -#define AL_SAMPLE_LENGTH_SOFT 0x200A -#define AL_SEC_LENGTH_SOFT 0x200B +#define AL_INTERNAL_FORMAT_SOFT 0x2008 +#define AL_BYTE_LENGTH_SOFT 0x2009 +#define AL_SAMPLE_LENGTH_SOFT 0x200A +#define AL_SEC_LENGTH_SOFT 0x200B -typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*); -typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*); -typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*); -typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum); + typedef void(AL_APIENTRY* LPALBUFFERSAMPLESSOFT)(ALuint, ALuint, ALenum, ALsizei, ALenum, ALenum, const ALvoid*); + typedef void(AL_APIENTRY* LPALBUFFERSUBSAMPLESSOFT)(ALuint, ALsizei, ALsizei, ALenum, ALenum, const ALvoid*); + typedef void(AL_APIENTRY* LPALGETBUFFERSAMPLESSOFT)(ALuint, ALsizei, ALsizei, ALenum, ALenum, ALvoid*); + typedef ALboolean(AL_APIENTRY* LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum); #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); -AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); -AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data); -AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); + AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, + ALsizei samples, ALenum channels, ALenum type, const ALvoid* data); + AL_API void AL_APIENTRY alBufferSubSamplesSOFT( + ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid* data); + AL_API void AL_APIENTRY alGetBufferSamplesSOFT( + ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid* data); + AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); #endif #endif #ifndef AL_SOFT_direct_channels #define AL_SOFT_direct_channels 1 -#define AL_DIRECT_CHANNELS_SOFT 0x1033 +#define AL_DIRECT_CHANNELS_SOFT 0x1033 #endif #ifndef ALC_SOFT_loopback #define ALC_SOFT_loopback 1 -#define ALC_FORMAT_CHANNELS_SOFT 0x1990 -#define ALC_FORMAT_TYPE_SOFT 0x1991 +#define ALC_FORMAT_CHANNELS_SOFT 0x1990 +#define ALC_FORMAT_TYPE_SOFT 0x1991 /* Sample types */ -#define ALC_BYTE_SOFT 0x1400 -#define ALC_UNSIGNED_BYTE_SOFT 0x1401 -#define ALC_SHORT_SOFT 0x1402 -#define ALC_UNSIGNED_SHORT_SOFT 0x1403 -#define ALC_INT_SOFT 0x1404 -#define ALC_UNSIGNED_INT_SOFT 0x1405 -#define ALC_FLOAT_SOFT 0x1406 +#define ALC_BYTE_SOFT 0x1400 +#define ALC_UNSIGNED_BYTE_SOFT 0x1401 +#define ALC_SHORT_SOFT 0x1402 +#define ALC_UNSIGNED_SHORT_SOFT 0x1403 +#define ALC_INT_SOFT 0x1404 +#define ALC_UNSIGNED_INT_SOFT 0x1405 +#define ALC_FLOAT_SOFT 0x1406 /* Channel configurations */ -#define ALC_MONO_SOFT 0x1500 -#define ALC_STEREO_SOFT 0x1501 -#define ALC_QUAD_SOFT 0x1503 -#define ALC_5POINT1_SOFT 0x1504 -#define ALC_6POINT1_SOFT 0x1505 -#define ALC_7POINT1_SOFT 0x1506 +#define ALC_MONO_SOFT 0x1500 +#define ALC_STEREO_SOFT 0x1501 +#define ALC_QUAD_SOFT 0x1503 +#define ALC_5POINT1_SOFT 0x1504 +#define ALC_6POINT1_SOFT 0x1505 +#define ALC_7POINT1_SOFT 0x1506 -typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*); -typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum); -typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei); + typedef ALCdevice*(ALC_APIENTRY* LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*); + typedef ALCboolean(ALC_APIENTRY* LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*, ALCsizei, ALCenum, ALCenum); + typedef void(ALC_APIENTRY* LPALCRENDERSAMPLESSOFT)(ALCdevice*, ALCvoid*, ALCsizei); #ifdef AL_ALEXT_PROTOTYPES -ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName); -ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type); -ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); + ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar* deviceName); + ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT( + ALCdevice* device, ALCsizei freq, ALCenum channels, ALCenum type); + ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice* device, ALCvoid* buffer, ALCsizei samples); #endif #endif #ifndef AL_EXT_STEREO_ANGLES #define AL_EXT_STEREO_ANGLES 1 -#define AL_STEREO_ANGLES 0x1030 +#define AL_STEREO_ANGLES 0x1030 #endif #ifndef AL_EXT_SOURCE_RADIUS #define AL_EXT_SOURCE_RADIUS 1 -#define AL_SOURCE_RADIUS 0x1031 +#define AL_SOURCE_RADIUS 0x1031 #endif #ifndef AL_SOFT_source_latency #define AL_SOFT_source_latency 1 -#define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200 -#define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 -typedef int64_t ALint64SOFT; -typedef uint64_t ALuint64SOFT; -typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble); -typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble); -typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*); -typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT); -typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT); -typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*); +#define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200 +#define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 + typedef _alsoft_int64_t ALint64SOFT; + typedef _alsoft_uint64_t ALuint64SOFT; + typedef void(AL_APIENTRY* LPALSOURCEDSOFT)(ALuint, ALenum, ALdouble); + typedef void(AL_APIENTRY* LPALSOURCE3DSOFT)(ALuint, ALenum, ALdouble, ALdouble, ALdouble); + typedef void(AL_APIENTRY* LPALSOURCEDVSOFT)(ALuint, ALenum, const ALdouble*); + typedef void(AL_APIENTRY* LPALGETSOURCEDSOFT)(ALuint, ALenum, ALdouble*); + typedef void(AL_APIENTRY* LPALGETSOURCE3DSOFT)(ALuint, ALenum, ALdouble*, ALdouble*, ALdouble*); + typedef void(AL_APIENTRY* LPALGETSOURCEDVSOFT)(ALuint, ALenum, ALdouble*); + typedef void(AL_APIENTRY* LPALSOURCEI64SOFT)(ALuint, ALenum, ALint64SOFT); + typedef void(AL_APIENTRY* LPALSOURCE3I64SOFT)(ALuint, ALenum, ALint64SOFT, ALint64SOFT, ALint64SOFT); + typedef void(AL_APIENTRY* LPALSOURCEI64VSOFT)(ALuint, ALenum, const ALint64SOFT*); + typedef void(AL_APIENTRY* LPALGETSOURCEI64SOFT)(ALuint, ALenum, ALint64SOFT*); + typedef void(AL_APIENTRY* LPALGETSOURCE3I64SOFT)(ALuint, ALenum, ALint64SOFT*, ALint64SOFT*, ALint64SOFT*); + typedef void(AL_APIENTRY* LPALGETSOURCEI64VSOFT)(ALuint, ALenum, ALint64SOFT*); #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value); -AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3); -AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values); -AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value); -AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3); -AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values); -AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value); -AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3); -AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values); -AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value); -AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3); -AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values); + AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value); + AL_API void AL_APIENTRY alSource3dSOFT( + ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3); + AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble* values); + AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble* value); + AL_API void AL_APIENTRY alGetSource3dSOFT( + ALuint source, ALenum param, ALdouble* value1, ALdouble* value2, ALdouble* value3); + AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble* values); + AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value); + AL_API void AL_APIENTRY alSource3i64SOFT( + ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3); + AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT* values); + AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT* value); + AL_API void AL_APIENTRY alGetSource3i64SOFT( + ALuint source, ALenum param, ALint64SOFT* value1, ALint64SOFT* value2, ALint64SOFT* value3); + AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT* values); #endif #endif #ifndef ALC_EXT_DEFAULT_FILTER_ORDER #define ALC_EXT_DEFAULT_FILTER_ORDER 1 -#define ALC_DEFAULT_FILTER_ORDER 0x1100 +#define ALC_DEFAULT_FILTER_ORDER 0x1100 #endif #ifndef AL_SOFT_deferred_updates #define AL_SOFT_deferred_updates 1 -#define AL_DEFERRED_UPDATES_SOFT 0xC002 -typedef ALvoid (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void); -typedef ALvoid (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void); +#define AL_DEFERRED_UPDATES_SOFT 0xC002 + typedef void(AL_APIENTRY* LPALDEFERUPDATESSOFT)(void); + typedef void(AL_APIENTRY* LPALPROCESSUPDATESSOFT)(void); #ifdef AL_ALEXT_PROTOTYPES -AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void); -AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void); + AL_API void AL_APIENTRY alDeferUpdatesSOFT(void); + AL_API void AL_APIENTRY alProcessUpdatesSOFT(void); #endif #endif #ifndef AL_SOFT_block_alignment #define AL_SOFT_block_alignment 1 -#define AL_UNPACK_BLOCK_ALIGNMENT_SOFT 0x200C -#define AL_PACK_BLOCK_ALIGNMENT_SOFT 0x200D +#define AL_UNPACK_BLOCK_ALIGNMENT_SOFT 0x200C +#define AL_PACK_BLOCK_ALIGNMENT_SOFT 0x200D #endif #ifndef AL_SOFT_MSADPCM #define AL_SOFT_MSADPCM 1 -#define AL_FORMAT_MONO_MSADPCM_SOFT 0x1302 -#define AL_FORMAT_STEREO_MSADPCM_SOFT 0x1303 +#define AL_FORMAT_MONO_MSADPCM_SOFT 0x1302 +#define AL_FORMAT_STEREO_MSADPCM_SOFT 0x1303 #endif #ifndef AL_SOFT_source_length @@ -385,78 +404,234 @@ AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void); #ifndef ALC_SOFT_pause_device #define ALC_SOFT_pause_device 1 -typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device); -typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device); + typedef void(ALC_APIENTRY* LPALCDEVICEPAUSESOFT)(ALCdevice* device); + typedef void(ALC_APIENTRY* LPALCDEVICERESUMESOFT)(ALCdevice* device); #ifdef AL_ALEXT_PROTOTYPES -ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device); -ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device); + ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice* device); + ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice* device); #endif #endif #ifndef AL_EXT_BFORMAT #define AL_EXT_BFORMAT 1 -#define AL_FORMAT_BFORMAT2D_8 0x20021 -#define AL_FORMAT_BFORMAT2D_16 0x20022 -#define AL_FORMAT_BFORMAT2D_FLOAT32 0x20023 -#define AL_FORMAT_BFORMAT3D_8 0x20031 -#define AL_FORMAT_BFORMAT3D_16 0x20032 -#define AL_FORMAT_BFORMAT3D_FLOAT32 0x20033 +/* Provides support for B-Format ambisonic buffers (first-order, FuMa scaling + * and layout). + * + * BFORMAT2D_8: Unsigned 8-bit, 3-channel non-periphonic (WXY). + * BFORMAT2D_16: Signed 16-bit, 3-channel non-periphonic (WXY). + * BFORMAT2D_FLOAT32: 32-bit float, 3-channel non-periphonic (WXY). + * BFORMAT3D_8: Unsigned 8-bit, 4-channel periphonic (WXYZ). + * BFORMAT3D_16: Signed 16-bit, 4-channel periphonic (WXYZ). + * BFORMAT3D_FLOAT32: 32-bit float, 4-channel periphonic (WXYZ). + */ +#define AL_FORMAT_BFORMAT2D_8 0x20021 +#define AL_FORMAT_BFORMAT2D_16 0x20022 +#define AL_FORMAT_BFORMAT2D_FLOAT32 0x20023 +#define AL_FORMAT_BFORMAT3D_8 0x20031 +#define AL_FORMAT_BFORMAT3D_16 0x20032 +#define AL_FORMAT_BFORMAT3D_FLOAT32 0x20033 #endif #ifndef AL_EXT_MULAW_BFORMAT #define AL_EXT_MULAW_BFORMAT 1 -#define AL_FORMAT_BFORMAT2D_MULAW 0x10031 -#define AL_FORMAT_BFORMAT3D_MULAW 0x10032 +#define AL_FORMAT_BFORMAT2D_MULAW 0x10031 +#define AL_FORMAT_BFORMAT3D_MULAW 0x10032 #endif #ifndef ALC_SOFT_HRTF #define ALC_SOFT_HRTF 1 -#define ALC_HRTF_SOFT 0x1992 -#define ALC_DONT_CARE_SOFT 0x0002 -#define ALC_HRTF_STATUS_SOFT 0x1993 -#define ALC_HRTF_DISABLED_SOFT 0x0000 -#define ALC_HRTF_ENABLED_SOFT 0x0001 -#define ALC_HRTF_DENIED_SOFT 0x0002 -#define ALC_HRTF_REQUIRED_SOFT 0x0003 -#define ALC_HRTF_HEADPHONES_DETECTED_SOFT 0x0004 -#define ALC_HRTF_UNSUPPORTED_FORMAT_SOFT 0x0005 -#define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 -#define ALC_HRTF_SPECIFIER_SOFT 0x1995 -#define ALC_HRTF_ID_SOFT 0x1996 -typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index); -typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs); +#define ALC_HRTF_SOFT 0x1992 +#define ALC_DONT_CARE_SOFT 0x0002 +#define ALC_HRTF_STATUS_SOFT 0x1993 +#define ALC_HRTF_DISABLED_SOFT 0x0000 +#define ALC_HRTF_ENABLED_SOFT 0x0001 +#define ALC_HRTF_DENIED_SOFT 0x0002 +#define ALC_HRTF_REQUIRED_SOFT 0x0003 +#define ALC_HRTF_HEADPHONES_DETECTED_SOFT 0x0004 +#define ALC_HRTF_UNSUPPORTED_FORMAT_SOFT 0x0005 +#define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 +#define ALC_HRTF_SPECIFIER_SOFT 0x1995 +#define ALC_HRTF_ID_SOFT 0x1996 + typedef const ALCchar*(ALC_APIENTRY* LPALCGETSTRINGISOFT)(ALCdevice* device, ALCenum paramName, ALCsizei index); + typedef ALCboolean(ALC_APIENTRY* LPALCRESETDEVICESOFT)(ALCdevice* device, const ALCint* attribs); #ifdef AL_ALEXT_PROTOTYPES -ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index); -ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs); + ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice* device, ALCenum paramName, ALCsizei index); + ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice* device, const ALCint* attribs); #endif #endif #ifndef AL_SOFT_gain_clamp_ex #define AL_SOFT_gain_clamp_ex 1 -#define AL_GAIN_LIMIT_SOFT 0x200E +#define AL_GAIN_LIMIT_SOFT 0x200E #endif #ifndef AL_SOFT_source_resampler #define AL_SOFT_source_resampler -#define AL_NUM_RESAMPLERS_SOFT 0x1210 -#define AL_DEFAULT_RESAMPLER_SOFT 0x1211 -#define AL_SOURCE_RESAMPLER_SOFT 0x1212 -#define AL_RESAMPLER_NAME_SOFT 0x1213 -typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index); +#define AL_NUM_RESAMPLERS_SOFT 0x1210 +#define AL_DEFAULT_RESAMPLER_SOFT 0x1211 +#define AL_SOURCE_RESAMPLER_SOFT 0x1212 +#define AL_RESAMPLER_NAME_SOFT 0x1213 + typedef const ALchar*(AL_APIENTRY* LPALGETSTRINGISOFT)(ALenum pname, ALsizei index); #ifdef AL_ALEXT_PROTOTYPES -AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index); + AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index); #endif #endif #ifndef AL_SOFT_source_spatialize #define AL_SOFT_source_spatialize -#define AL_SOURCE_SPATIALIZE_SOFT 0x1214 -#define AL_AUTO_SOFT 0x0002 +#define AL_SOURCE_SPATIALIZE_SOFT 0x1214 +#define AL_AUTO_SOFT 0x0002 #endif #ifndef ALC_SOFT_output_limiter #define ALC_SOFT_output_limiter -#define ALC_OUTPUT_LIMITER_SOFT 0x199A +#define ALC_OUTPUT_LIMITER_SOFT 0x199A +#endif + +#ifndef ALC_SOFT_device_clock +#define ALC_SOFT_device_clock 1 + typedef _alsoft_int64_t ALCint64SOFT; + typedef _alsoft_uint64_t ALCuint64SOFT; +#define ALC_DEVICE_CLOCK_SOFT 0x1600 +#define ALC_DEVICE_LATENCY_SOFT 0x1601 +#define ALC_DEVICE_CLOCK_LATENCY_SOFT 0x1602 +#define AL_SAMPLE_OFFSET_CLOCK_SOFT 0x1202 +#define AL_SEC_OFFSET_CLOCK_SOFT 0x1203 + typedef void(ALC_APIENTRY* LPALCGETINTEGER64VSOFT)( + ALCdevice* device, ALCenum pname, ALsizei size, ALCint64SOFT* values); +#ifdef AL_ALEXT_PROTOTYPES + ALC_API void ALC_APIENTRY alcGetInteger64vSOFT( + ALCdevice* device, ALCenum pname, ALsizei size, ALCint64SOFT* values); +#endif +#endif + +#ifndef AL_SOFT_direct_channels_remix +#define AL_SOFT_direct_channels_remix 1 +#define AL_DROP_UNMATCHED_SOFT 0x0001 +#define AL_REMIX_UNMATCHED_SOFT 0x0002 +#endif + +#ifndef AL_SOFT_bformat_ex +#define AL_SOFT_bformat_ex 1 +#define AL_AMBISONIC_LAYOUT_SOFT 0x1997 +#define AL_AMBISONIC_SCALING_SOFT 0x1998 + +/* Ambisonic layouts */ +#define AL_FUMA_SOFT 0x0000 +#define AL_ACN_SOFT 0x0001 + +/* Ambisonic scalings (normalization) */ +/*#define AL_FUMA_SOFT*/ +#define AL_SN3D_SOFT 0x0001 +#define AL_N3D_SOFT 0x0002 +#endif + +#ifndef ALC_SOFT_loopback_bformat +#define ALC_SOFT_loopback_bformat 1 +#define ALC_AMBISONIC_LAYOUT_SOFT 0x1997 +#define ALC_AMBISONIC_SCALING_SOFT 0x1998 +#define ALC_AMBISONIC_ORDER_SOFT 0x1999 +#define ALC_MAX_AMBISONIC_ORDER_SOFT 0x199B + +#define ALC_BFORMAT3D_SOFT 0x1507 + +/* Ambisonic layouts */ +#define ALC_FUMA_SOFT 0x0000 +#define ALC_ACN_SOFT 0x0001 + +/* Ambisonic scalings (normalization) */ +/*#define ALC_FUMA_SOFT*/ +#define ALC_SN3D_SOFT 0x0001 +#define ALC_N3D_SOFT 0x0002 +#endif + +#ifndef AL_SOFT_effect_target +#define AL_SOFT_effect_target +#define AL_EFFECTSLOT_TARGET_SOFT 0x199C +#endif + +#ifndef AL_SOFT_events +#define AL_SOFT_events 1 +#define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x19A2 +#define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x19A3 +#define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x19A4 +#define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x19A5 +#define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x19A6 + typedef void(AL_APIENTRY* ALEVENTPROCSOFT)( + ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar* message, void* userParam); + typedef void(AL_APIENTRY* LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum* types, ALboolean enable); + typedef void(AL_APIENTRY* LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void* userParam); + typedef void*(AL_APIENTRY* LPALGETPOINTERSOFT)(ALenum pname); + typedef void(AL_APIENTRY* LPALGETPOINTERVSOFT)(ALenum pname, void** values); +#ifdef AL_ALEXT_PROTOTYPES + AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum* types, ALboolean enable); + AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void* userParam); + AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname); + AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void** values); +#endif +#endif + +#ifndef ALC_SOFT_reopen_device +#define ALC_SOFT_reopen_device + typedef ALCboolean(ALC_APIENTRY* LPALCREOPENDEVICESOFT)( + ALCdevice* device, const ALCchar* deviceName, const ALCint* attribs); +#ifdef AL_ALEXT_PROTOTYPES + ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice* device, const ALCchar* deviceName, const ALCint* attribs); +#endif +#endif + +#ifndef AL_SOFT_callback_buffer +#define AL_SOFT_callback_buffer +#define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 +#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 + typedef ALsizei(AL_APIENTRY* ALBUFFERCALLBACKTYPESOFT)(ALvoid* userptr, ALvoid* sampledata, ALsizei numbytes); + typedef void(AL_APIENTRY* LPALBUFFERCALLBACKSOFT)( + ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid* userptr); + typedef void(AL_APIENTRY* LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid** value); + typedef void(AL_APIENTRY* LPALGETBUFFER3PTRSOFT)( + ALuint buffer, ALenum param, ALvoid** value1, ALvoid** value2, ALvoid** value3); + typedef void(AL_APIENTRY* LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid** values); +#ifdef AL_ALEXT_PROTOTYPES + AL_API void AL_APIENTRY alBufferCallbackSOFT( + ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid* userptr); + AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid** ptr); + AL_API void AL_APIENTRY alGetBuffer3PtrSOFT( + ALuint buffer, ALenum param, ALvoid** ptr0, ALvoid** ptr1, ALvoid** ptr2); + AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid** ptr); +#endif +#endif + +#ifndef AL_SOFT_UHJ +#define AL_SOFT_UHJ +#define AL_FORMAT_UHJ2CHN8_SOFT 0x19A2 +#define AL_FORMAT_UHJ2CHN16_SOFT 0x19A3 +#define AL_FORMAT_UHJ2CHN_FLOAT32_SOFT 0x19A4 +#define AL_FORMAT_UHJ3CHN8_SOFT 0x19A5 +#define AL_FORMAT_UHJ3CHN16_SOFT 0x19A6 +#define AL_FORMAT_UHJ3CHN_FLOAT32_SOFT 0x19A7 +#define AL_FORMAT_UHJ4CHN8_SOFT 0x19A8 +#define AL_FORMAT_UHJ4CHN16_SOFT 0x19A9 +#define AL_FORMAT_UHJ4CHN_FLOAT32_SOFT 0x19AA + +#define AL_STEREO_MODE_SOFT 0x19B0 +#define AL_NORMAL_SOFT 0x0000 +#define AL_SUPER_STEREO_SOFT 0x0001 +#define AL_SUPER_STEREO_WIDTH_SOFT 0x19B1 +#endif + +#ifndef ALC_SOFT_output_mode +#define ALC_SOFT_output_mode +#define ALC_OUTPUT_MODE_SOFT 0x19AC +#define ALC_ANY_SOFT 0x19AD +/*#define ALC_MONO_SOFT 0x1500*/ +/*#define ALC_STEREO_SOFT 0x1501*/ +#define ALC_STEREO_BASIC_SOFT 0x19AE +#define ALC_STEREO_UHJ_SOFT 0x19AF +#define ALC_STEREO_HRTF_SOFT 0x19B2 +/*#define ALC_QUAD_SOFT 0x1503*/ +#define ALC_SURROUND_5_1_SOFT 0x1504 +#define ALC_SURROUND_6_1_SOFT 0x1505 +#define ALC_SURROUND_7_1_SOFT 0x1506 #endif #ifdef __cplusplus diff --git a/apps/openmw/mwsound/efx-presets.h b/apps/openmw/mwsound/efx-presets.h index 8539fd517..a9662936d 100644 --- a/apps/openmw/mwsound/efx-presets.h +++ b/apps/openmw/mwsound/efx-presets.h @@ -5,7 +5,8 @@ #ifndef EFXEAXREVERBPROPERTIES_DEFINED #define EFXEAXREVERBPROPERTIES_DEFINED -typedef struct { +typedef struct +{ float flDensity; float flDiffusion; float flGain; @@ -28,375 +29,827 @@ typedef struct { float flHFReference; float flLFReference; float flRoomRolloffFactor; - int iDecayHFLimit; + int iDecayHFLimit; } EFXEAXREVERBPROPERTIES, *LPEFXEAXREVERBPROPERTIES; #endif /* Default Presets */ -#define EFX_REVERB_PRESET_GENERIC \ - { 1.0000f, 1.0000f, 0.3162f, 0.8913f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0500f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_GENERIC \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.8913f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0500f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_PADDEDCELL \ - { 0.1715f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.1700f, 0.1000f, 1.0000f, 0.2500f, 0.0010f, { 0.0000f, 0.0000f, 0.0000f }, 1.2691f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_PADDEDCELL \ + { \ + 0.1715f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.1700f, 0.1000f, 1.0000f, 0.2500f, 0.0010f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2691f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ROOM \ - { 0.4287f, 1.0000f, 0.3162f, 0.5929f, 1.0000f, 0.4000f, 0.8300f, 1.0000f, 0.1503f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 1.0629f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ROOM \ + { \ + 0.4287f, 1.0000f, 0.3162f, 0.5929f, 1.0000f, 0.4000f, 0.8300f, 1.0000f, 0.1503f, 0.0020f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.0629f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_BATHROOM \ - { 0.1715f, 1.0000f, 0.3162f, 0.2512f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.6531f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 3.2734f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_BATHROOM \ + { \ + 0.1715f, 1.0000f, 0.3162f, 0.2512f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.6531f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 3.2734f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_LIVINGROOM \ - { 0.9766f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.5000f, 0.1000f, 1.0000f, 0.2051f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2805f, 0.0040f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_LIVINGROOM \ + { \ + 0.9766f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.5000f, 0.1000f, 1.0000f, 0.2051f, 0.0030f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.2805f, 0.0040f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_STONEROOM \ - { 1.0000f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 2.3100f, 0.6400f, 1.0000f, 0.4411f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1003f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_STONEROOM \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 2.3100f, 0.6400f, 1.0000f, 0.4411f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1003f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_AUDITORIUM \ - { 1.0000f, 1.0000f, 0.3162f, 0.5781f, 1.0000f, 4.3200f, 0.5900f, 1.0000f, 0.4032f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.7170f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_AUDITORIUM \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.5781f, 1.0000f, 4.3200f, 0.5900f, 1.0000f, 0.4032f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7170f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CONCERTHALL \ - { 1.0000f, 1.0000f, 0.3162f, 0.5623f, 1.0000f, 3.9200f, 0.7000f, 1.0000f, 0.2427f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.9977f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CONCERTHALL \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.5623f, 1.0000f, 3.9200f, 0.7000f, 1.0000f, 0.2427f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.9977f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CAVE \ - { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 2.9100f, 1.3000f, 1.0000f, 0.5000f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.7063f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_CAVE \ + { \ + 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 2.9100f, 1.3000f, 1.0000f, 0.5000f, 0.0150f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7063f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_ARENA \ - { 1.0000f, 1.0000f, 0.3162f, 0.4477f, 1.0000f, 7.2400f, 0.3300f, 1.0000f, 0.2612f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.0186f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ARENA \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.4477f, 1.0000f, 7.2400f, 0.3300f, 1.0000f, 0.2612f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.0186f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_HANGAR \ - { 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 10.0500f, 0.2300f, 1.0000f, 0.5000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2560f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_HANGAR \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 10.0500f, 0.2300f, 1.0000f, 0.5000f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2560f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CARPETEDHALLWAY \ - { 0.4287f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 0.3000f, 0.1000f, 1.0000f, 0.1215f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.1531f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CARPETEDHALLWAY \ + { \ + 0.4287f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 0.3000f, 0.1000f, 1.0000f, 0.1215f, 0.0020f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.1531f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_HALLWAY \ - { 0.3645f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 1.4900f, 0.5900f, 1.0000f, 0.2458f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.6615f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_HALLWAY \ + { \ + 0.3645f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 1.4900f, 0.5900f, 1.0000f, 0.2458f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.6615f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_STONECORRIDOR \ - { 1.0000f, 1.0000f, 0.3162f, 0.7612f, 1.0000f, 2.7000f, 0.7900f, 1.0000f, 0.2472f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 1.5758f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_STONECORRIDOR \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.7612f, 1.0000f, 2.7000f, 0.7900f, 1.0000f, 0.2472f, 0.0130f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.5758f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ALLEY \ - { 1.0000f, 0.3000f, 0.3162f, 0.7328f, 1.0000f, 1.4900f, 0.8600f, 1.0000f, 0.2500f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.9954f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.9500f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ALLEY \ + { \ + 1.0000f, 0.3000f, 0.3162f, 0.7328f, 1.0000f, 1.4900f, 0.8600f, 1.0000f, 0.2500f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.9954f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.9500f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FOREST \ - { 1.0000f, 0.3000f, 0.3162f, 0.0224f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.0525f, 0.1620f, { 0.0000f, 0.0000f, 0.0000f }, 0.7682f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FOREST \ + { \ + 1.0000f, 0.3000f, 0.3162f, 0.0224f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.0525f, 0.1620f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7682f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 1.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CITY \ - { 1.0000f, 0.5000f, 0.3162f, 0.3981f, 1.0000f, 1.4900f, 0.6700f, 1.0000f, 0.0730f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1427f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CITY \ + { \ + 1.0000f, 0.5000f, 0.3162f, 0.3981f, 1.0000f, 1.4900f, 0.6700f, 1.0000f, 0.0730f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.1427f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_MOUNTAINS \ - { 1.0000f, 0.2700f, 0.3162f, 0.0562f, 1.0000f, 1.4900f, 0.2100f, 1.0000f, 0.0407f, 0.3000f, { 0.0000f, 0.0000f, 0.0000f }, 0.1919f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_MOUNTAINS \ + { \ + 1.0000f, 0.2700f, 0.3162f, 0.0562f, 1.0000f, 1.4900f, 0.2100f, 1.0000f, 0.0407f, 0.3000f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.1919f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_QUARRY \ - { 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0000f, 0.0610f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.7000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_QUARRY \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0000f, 0.0610f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.7000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_PLAIN \ - { 1.0000f, 0.2100f, 0.3162f, 0.1000f, 1.0000f, 1.4900f, 0.5000f, 1.0000f, 0.0585f, 0.1790f, { 0.0000f, 0.0000f, 0.0000f }, 0.1089f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_PLAIN \ + { \ + 1.0000f, 0.2100f, 0.3162f, 0.1000f, 1.0000f, 1.4900f, 0.5000f, 1.0000f, 0.0585f, 0.1790f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.1089f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_PARKINGLOT \ - { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 1.6500f, 1.5000f, 1.0000f, 0.2082f, 0.0080f, { 0.0000f, 0.0000f, 0.0000f }, 0.2652f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_PARKINGLOT \ + { \ + 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 1.6500f, 1.5000f, 1.0000f, 0.2082f, 0.0080f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.2652f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_SEWERPIPE \ - { 0.3071f, 0.8000f, 0.3162f, 0.3162f, 1.0000f, 2.8100f, 0.1400f, 1.0000f, 1.6387f, 0.0140f, { 0.0000f, 0.0000f, 0.0000f }, 3.2471f, 0.0210f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SEWERPIPE \ + { \ + 0.3071f, 0.8000f, 0.3162f, 0.3162f, 1.0000f, 2.8100f, 0.1400f, 1.0000f, 1.6387f, 0.0140f, \ + { 0.0000f, 0.0000f, 0.0000f }, 3.2471f, 0.0210f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_UNDERWATER \ - { 0.3645f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 1.4900f, 0.1000f, 1.0000f, 0.5963f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 7.0795f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 1.1800f, 0.3480f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_UNDERWATER \ + { \ + 0.3645f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 1.4900f, 0.1000f, 1.0000f, 0.5963f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 7.0795f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 1.1800f, \ + 0.3480f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_DRUGGED \ - { 0.4287f, 0.5000f, 0.3162f, 1.0000f, 1.0000f, 8.3900f, 1.3900f, 1.0000f, 0.8760f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 3.1081f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_DRUGGED \ + { \ + 0.4287f, 0.5000f, 0.3162f, 1.0000f, 1.0000f, 8.3900f, 1.3900f, 1.0000f, 0.8760f, 0.0020f, \ + { 0.0000f, 0.0000f, 0.0000f }, 3.1081f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_DIZZY \ - { 0.3645f, 0.6000f, 0.3162f, 0.6310f, 1.0000f, 17.2300f, 0.5600f, 1.0000f, 0.1392f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.4937f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.8100f, 0.3100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_DIZZY \ + { \ + 0.3645f, 0.6000f, 0.3162f, 0.6310f, 1.0000f, 17.2300f, 0.5600f, 1.0000f, 0.1392f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.4937f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.8100f, \ + 0.3100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_PSYCHOTIC \ - { 0.0625f, 0.5000f, 0.3162f, 0.8404f, 1.0000f, 7.5600f, 0.9100f, 1.0000f, 0.4864f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 2.4378f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 4.0000f, 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_PSYCHOTIC \ + { \ + 0.0625f, 0.5000f, 0.3162f, 0.8404f, 1.0000f, 7.5600f, 0.9100f, 1.0000f, 0.4864f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 2.4378f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 4.0000f, \ + 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } /* Castle Presets */ -#define EFX_REVERB_PRESET_CASTLE_SMALLROOM \ - { 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 1.2200f, 0.8300f, 0.3100f, 0.8913f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CASTLE_SMALLROOM \ + { \ + 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 1.2200f, 0.8300f, 0.3100f, 0.8913f, 0.0220f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, \ + 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CASTLE_SHORTPASSAGE \ - { 1.0000f, 0.8900f, 0.3162f, 0.3162f, 0.1000f, 2.3200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CASTLE_SHORTPASSAGE \ + { \ + 1.0000f, 0.8900f, 0.3162f, 0.3162f, 0.1000f, 2.3200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, \ + 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CASTLE_MEDIUMROOM \ - { 1.0000f, 0.9300f, 0.3162f, 0.2818f, 0.1000f, 2.0400f, 0.8300f, 0.4600f, 0.6310f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1550f, 0.0300f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CASTLE_MEDIUMROOM \ + { \ + 1.0000f, 0.9300f, 0.3162f, 0.2818f, 0.1000f, 2.0400f, 0.8300f, 0.4600f, 0.6310f, 0.0220f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1550f, 0.0300f, 0.2500f, \ + 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CASTLE_LARGEROOM \ - { 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.1259f, 2.5300f, 0.8300f, 0.5000f, 0.4467f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1850f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CASTLE_LARGEROOM \ + { \ + 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.1259f, 2.5300f, 0.8300f, 0.5000f, 0.4467f, 0.0340f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1850f, 0.0700f, 0.2500f, \ + 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CASTLE_LONGPASSAGE \ - { 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 3.4200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CASTLE_LONGPASSAGE \ + { \ + 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 3.4200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, \ + 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CASTLE_HALL \ - { 1.0000f, 0.8100f, 0.3162f, 0.2818f, 0.1778f, 3.1400f, 0.7900f, 0.6200f, 0.1778f, 0.0560f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CASTLE_HALL \ + { \ + 1.0000f, 0.8100f, 0.3162f, 0.2818f, 0.1778f, 3.1400f, 0.7900f, 0.6200f, 0.1778f, 0.0560f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CASTLE_CUPBOARD \ - { 1.0000f, 0.8900f, 0.3162f, 0.2818f, 0.1000f, 0.6700f, 0.8700f, 0.3100f, 1.4125f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 3.5481f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CASTLE_CUPBOARD \ + { \ + 1.0000f, 0.8900f, 0.3162f, 0.2818f, 0.1000f, 0.6700f, 0.8700f, 0.3100f, 1.4125f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 3.5481f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, \ + 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CASTLE_COURTYARD \ - { 1.0000f, 0.4200f, 0.3162f, 0.4467f, 0.1995f, 2.1300f, 0.6100f, 0.2300f, 0.2239f, 0.1600f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3700f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_CASTLE_COURTYARD \ + { \ + 1.0000f, 0.4200f, 0.3162f, 0.4467f, 0.1995f, 2.1300f, 0.6100f, 0.2300f, 0.2239f, 0.1600f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3700f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_CASTLE_ALCOVE \ - { 1.0000f, 0.8900f, 0.3162f, 0.5012f, 0.1000f, 1.6400f, 0.8700f, 0.3100f, 1.0000f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CASTLE_ALCOVE \ + { \ + 1.0000f, 0.8900f, 0.3162f, 0.5012f, 0.1000f, 1.6400f, 0.8700f, 0.3100f, 1.0000f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, \ + 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 \ + } /* Factory Presets */ -#define EFX_REVERB_PRESET_FACTORY_SMALLROOM \ - { 0.3645f, 0.8200f, 0.3162f, 0.7943f, 0.5012f, 1.7200f, 0.6500f, 1.3100f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.1190f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_SMALLROOM \ + { \ + 0.3645f, 0.8200f, 0.3162f, 0.7943f, 0.5012f, 1.7200f, 0.6500f, 1.3100f, 0.7079f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.1190f, 0.0700f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FACTORY_SHORTPASSAGE \ - { 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 2.5300f, 0.6500f, 1.3100f, 1.0000f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_SHORTPASSAGE \ + { \ + 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 2.5300f, 0.6500f, 1.3100f, 1.0000f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FACTORY_MEDIUMROOM \ - { 0.4287f, 0.8200f, 0.2512f, 0.7943f, 0.5012f, 2.7600f, 0.6500f, 1.3100f, 0.2818f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1740f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_MEDIUMROOM \ + { \ + 0.4287f, 0.8200f, 0.2512f, 0.7943f, 0.5012f, 2.7600f, 0.6500f, 1.3100f, 0.2818f, 0.0220f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1740f, 0.0700f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FACTORY_LARGEROOM \ - { 0.4287f, 0.7500f, 0.2512f, 0.7079f, 0.6310f, 4.2400f, 0.5100f, 1.3100f, 0.1778f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2310f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_LARGEROOM \ + { \ + 0.4287f, 0.7500f, 0.2512f, 0.7079f, 0.6310f, 4.2400f, 0.5100f, 1.3100f, 0.1778f, 0.0390f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2310f, 0.0700f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FACTORY_LONGPASSAGE \ - { 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 4.0600f, 0.6500f, 1.3100f, 1.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_LONGPASSAGE \ + { \ + 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 4.0600f, 0.6500f, 1.3100f, 1.0000f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FACTORY_HALL \ - { 0.4287f, 0.7500f, 0.3162f, 0.7079f, 0.6310f, 7.4300f, 0.5100f, 1.3100f, 0.0631f, 0.0730f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_HALL \ + { \ + 0.4287f, 0.7500f, 0.3162f, 0.7079f, 0.6310f, 7.4300f, 0.5100f, 1.3100f, 0.0631f, 0.0730f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0700f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FACTORY_CUPBOARD \ - { 0.3071f, 0.6300f, 0.2512f, 0.7943f, 0.5012f, 0.4900f, 0.6500f, 1.3100f, 1.2589f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.1070f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_CUPBOARD \ + { \ + 0.3071f, 0.6300f, 0.2512f, 0.7943f, 0.5012f, 0.4900f, 0.6500f, 1.3100f, 1.2589f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.1070f, 0.0700f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FACTORY_COURTYARD \ - { 0.3071f, 0.5700f, 0.3162f, 0.3162f, 0.6310f, 2.3200f, 0.2900f, 0.5600f, 0.2239f, 0.1400f, { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2900f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_COURTYARD \ + { \ + 0.3071f, 0.5700f, 0.3162f, 0.3162f, 0.6310f, 2.3200f, 0.2900f, 0.5600f, 0.2239f, 0.1400f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2900f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_FACTORY_ALCOVE \ - { 0.3645f, 0.5900f, 0.2512f, 0.7943f, 0.5012f, 3.1400f, 0.6500f, 1.3100f, 1.4125f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1140f, 0.1000f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_FACTORY_ALCOVE \ + { \ + 0.3645f, 0.5900f, 0.2512f, 0.7943f, 0.5012f, 3.1400f, 0.6500f, 1.3100f, 1.4125f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1140f, 0.1000f, 0.2500f, \ + 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 \ + } /* Ice Palace Presets */ -#define EFX_REVERB_PRESET_ICEPALACE_SMALLROOM \ - { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 1.5100f, 1.5300f, 0.2700f, 0.8913f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1640f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_SMALLROOM \ + { \ + 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 1.5100f, 1.5300f, 0.2700f, 0.8913f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1640f, 0.1400f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ICEPALACE_SHORTPASSAGE \ - { 1.0000f, 0.7500f, 0.3162f, 0.5623f, 0.2818f, 1.7900f, 1.4600f, 0.2800f, 0.5012f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.0900f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_SHORTPASSAGE \ + { \ + 1.0000f, 0.7500f, 0.3162f, 0.5623f, 0.2818f, 1.7900f, 1.4600f, 0.2800f, 0.5012f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.0900f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ICEPALACE_MEDIUMROOM \ - { 1.0000f, 0.8700f, 0.3162f, 0.5623f, 0.4467f, 2.2200f, 1.5300f, 0.3200f, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.1200f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_MEDIUMROOM \ + { \ + 1.0000f, 0.8700f, 0.3162f, 0.5623f, 0.4467f, 2.2200f, 1.5300f, 0.3200f, 0.3981f, 0.0390f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.1200f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ICEPALACE_LARGEROOM \ - { 1.0000f, 0.8100f, 0.3162f, 0.5623f, 0.4467f, 3.1400f, 1.5300f, 0.3200f, 0.2512f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_LARGEROOM \ + { \ + 1.0000f, 0.8100f, 0.3162f, 0.5623f, 0.4467f, 3.1400f, 1.5300f, 0.3200f, 0.2512f, 0.0390f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.1100f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ICEPALACE_LONGPASSAGE \ - { 1.0000f, 0.7700f, 0.3162f, 0.5623f, 0.3981f, 3.0100f, 1.4600f, 0.2800f, 0.7943f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.0400f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_LONGPASSAGE \ + { \ + 1.0000f, 0.7700f, 0.3162f, 0.5623f, 0.3981f, 3.0100f, 1.4600f, 0.2800f, 0.7943f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.0400f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ICEPALACE_HALL \ - { 1.0000f, 0.7600f, 0.3162f, 0.4467f, 0.5623f, 5.4900f, 1.5300f, 0.3800f, 0.1122f, 0.0540f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0520f, { 0.0000f, 0.0000f, 0.0000f }, 0.2260f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_HALL \ + { \ + 1.0000f, 0.7600f, 0.3162f, 0.4467f, 0.5623f, 5.4900f, 1.5300f, 0.3800f, 0.1122f, 0.0540f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0520f, { 0.0000f, 0.0000f, 0.0000f }, 0.2260f, 0.1100f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ICEPALACE_CUPBOARD \ - { 1.0000f, 0.8300f, 0.3162f, 0.5012f, 0.2239f, 0.7600f, 1.5300f, 0.2600f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1430f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_CUPBOARD \ + { \ + 1.0000f, 0.8300f, 0.3162f, 0.5012f, 0.2239f, 0.7600f, 1.5300f, 0.2600f, 1.1220f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1430f, 0.0800f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ICEPALACE_COURTYARD \ - { 1.0000f, 0.5900f, 0.3162f, 0.2818f, 0.3162f, 2.0400f, 1.2000f, 0.3800f, 0.3162f, 0.1730f, { 0.0000f, 0.0000f, 0.0000f }, 0.3162f, 0.0430f, { 0.0000f, 0.0000f, 0.0000f }, 0.2350f, 0.4800f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_COURTYARD \ + { \ + 1.0000f, 0.5900f, 0.3162f, 0.2818f, 0.3162f, 2.0400f, 1.2000f, 0.3800f, 0.3162f, 0.1730f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.3162f, 0.0430f, { 0.0000f, 0.0000f, 0.0000f }, 0.2350f, 0.4800f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_ICEPALACE_ALCOVE \ - { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 2.7600f, 1.4600f, 0.2800f, 1.1220f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1610f, 0.0900f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_ICEPALACE_ALCOVE \ + { \ + 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 2.7600f, 1.4600f, 0.2800f, 1.1220f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1610f, 0.0900f, 0.2500f, \ + 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 \ + } /* Space Station Presets */ -#define EFX_REVERB_PRESET_SPACESTATION_SMALLROOM \ - { 0.2109f, 0.7000f, 0.3162f, 0.7079f, 0.8913f, 1.7200f, 0.8200f, 0.5500f, 0.7943f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 0.1880f, 0.2600f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPACESTATION_SMALLROOM \ + { \ + 0.2109f, 0.7000f, 0.3162f, 0.7079f, 0.8913f, 1.7200f, 0.8200f, 0.5500f, 0.7943f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 0.1880f, 0.2600f, 0.2500f, \ + 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPACESTATION_SHORTPASSAGE \ - { 0.2109f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 3.5700f, 0.5000f, 0.5500f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1720f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPACESTATION_SHORTPASSAGE \ + { \ + 0.2109f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 3.5700f, 0.5000f, 0.5500f, 1.0000f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1720f, 0.2000f, 0.2500f, \ + 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPACESTATION_MEDIUMROOM \ - { 0.2109f, 0.7500f, 0.3162f, 0.6310f, 0.8913f, 3.0100f, 0.5000f, 0.5500f, 0.3981f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2090f, 0.3100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPACESTATION_MEDIUMROOM \ + { \ + 0.2109f, 0.7500f, 0.3162f, 0.6310f, 0.8913f, 3.0100f, 0.5000f, 0.5500f, 0.3981f, 0.0340f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2090f, 0.3100f, 0.2500f, \ + 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPACESTATION_LARGEROOM \ - { 0.3645f, 0.8100f, 0.3162f, 0.6310f, 0.8913f, 3.8900f, 0.3800f, 0.6100f, 0.3162f, 0.0560f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2330f, 0.2800f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPACESTATION_LARGEROOM \ + { \ + 0.3645f, 0.8100f, 0.3162f, 0.6310f, 0.8913f, 3.8900f, 0.3800f, 0.6100f, 0.3162f, 0.0560f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2330f, 0.2800f, 0.2500f, \ + 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPACESTATION_LONGPASSAGE \ - { 0.4287f, 0.8200f, 0.3162f, 0.6310f, 0.8913f, 4.6200f, 0.6200f, 0.5500f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPACESTATION_LONGPASSAGE \ + { \ + 0.4287f, 0.8200f, 0.3162f, 0.6310f, 0.8913f, 4.6200f, 0.6200f, 0.5500f, 1.0000f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2300f, 0.2500f, \ + 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPACESTATION_HALL \ - { 0.4287f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 7.1100f, 0.3800f, 0.6100f, 0.1778f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2500f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPACESTATION_HALL \ + { \ + 0.4287f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 7.1100f, 0.3800f, 0.6100f, 0.1778f, 0.1000f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2500f, 0.2500f, \ + 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPACESTATION_CUPBOARD \ - { 0.1715f, 0.5600f, 0.3162f, 0.7079f, 0.8913f, 0.7900f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1810f, 0.3100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPACESTATION_CUPBOARD \ + { \ + 0.1715f, 0.5600f, 0.3162f, 0.7079f, 0.8913f, 0.7900f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1810f, 0.3100f, 0.2500f, \ + 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPACESTATION_ALCOVE \ - { 0.2109f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.1600f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1920f, 0.2100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPACESTATION_ALCOVE \ + { \ + 0.2109f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.1600f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1920f, 0.2100f, 0.2500f, \ + 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 \ + } /* Wooden Galleon Presets */ -#define EFX_REVERB_PRESET_WOODEN_SMALLROOM \ - { 1.0000f, 1.0000f, 0.3162f, 0.1122f, 0.3162f, 0.7900f, 0.3200f, 0.8700f, 1.0000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_SMALLROOM \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.1122f, 0.3162f, 0.7900f, 0.3200f, 0.8700f, 1.0000f, 0.0320f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_WOODEN_SHORTPASSAGE \ - { 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.7500f, 0.5000f, 0.8700f, 0.8913f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_SHORTPASSAGE \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.7500f, 0.5000f, 0.8700f, 0.8913f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_WOODEN_MEDIUMROOM \ - { 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.2818f, 1.4700f, 0.4200f, 0.8200f, 0.8913f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_MEDIUMROOM \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.2818f, 1.4700f, 0.4200f, 0.8200f, 0.8913f, 0.0490f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_WOODEN_LARGEROOM \ - { 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.2818f, 2.6500f, 0.3300f, 0.8200f, 0.8913f, 0.0660f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_LARGEROOM \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.2818f, 2.6500f, 0.3300f, 0.8200f, 0.8913f, 0.0660f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_WOODEN_LONGPASSAGE \ - { 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.3162f, 1.9900f, 0.4000f, 0.7900f, 1.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.4467f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_LONGPASSAGE \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.3162f, 1.9900f, 0.4000f, 0.7900f, 1.0000f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.4467f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_WOODEN_HALL \ - { 1.0000f, 1.0000f, 0.3162f, 0.0794f, 0.2818f, 3.4500f, 0.3000f, 0.8200f, 0.8913f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_HALL \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.0794f, 0.2818f, 3.4500f, 0.3000f, 0.8200f, 0.8913f, 0.0880f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_WOODEN_CUPBOARD \ - { 1.0000f, 1.0000f, 0.3162f, 0.1413f, 0.3162f, 0.5600f, 0.4600f, 0.9100f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_CUPBOARD \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.1413f, 0.3162f, 0.5600f, 0.4600f, 0.9100f, 1.1220f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_WOODEN_COURTYARD \ - { 1.0000f, 0.6500f, 0.3162f, 0.0794f, 0.3162f, 1.7900f, 0.3500f, 0.7900f, 0.5623f, 0.1230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_COURTYARD \ + { \ + 1.0000f, 0.6500f, 0.3162f, 0.0794f, 0.3162f, 1.7900f, 0.3500f, 0.7900f, 0.5623f, 0.1230f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.1000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_WOODEN_ALCOVE \ - { 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.2200f, 0.6200f, 0.9100f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_WOODEN_ALCOVE \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.2200f, 0.6200f, 0.9100f, 1.1220f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 \ + } /* Sports Presets */ -#define EFX_REVERB_PRESET_SPORT_EMPTYSTADIUM \ - { 1.0000f, 1.0000f, 0.3162f, 0.4467f, 0.7943f, 6.2600f, 0.5100f, 1.1000f, 0.0631f, 0.1830f, { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPORT_EMPTYSTADIUM \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.4467f, 0.7943f, 6.2600f, 0.5100f, 1.1000f, 0.0631f, 0.1830f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPORT_SQUASHCOURT \ - { 1.0000f, 0.7500f, 0.3162f, 0.3162f, 0.7943f, 2.2200f, 0.9100f, 1.1600f, 0.4467f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1260f, 0.1900f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPORT_SQUASHCOURT \ + { \ + 1.0000f, 0.7500f, 0.3162f, 0.3162f, 0.7943f, 2.2200f, 0.9100f, 1.1600f, 0.4467f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1260f, 0.1900f, 0.2500f, \ + 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPORT_SMALLSWIMMINGPOOL \ - { 1.0000f, 0.7000f, 0.3162f, 0.7943f, 0.8913f, 2.7600f, 1.2500f, 1.1400f, 0.6310f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_SPORT_SMALLSWIMMINGPOOL \ + { \ + 1.0000f, 0.7000f, 0.3162f, 0.7943f, 0.8913f, 2.7600f, 1.2500f, 1.1400f, 0.6310f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, \ + 0.1900f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_SPORT_LARGESWIMMINGPOOL \ - { 1.0000f, 0.8200f, 0.3162f, 0.7943f, 1.0000f, 5.4900f, 1.3100f, 1.1400f, 0.4467f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2220f, 0.5500f, 1.1590f, 0.2100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_SPORT_LARGESWIMMINGPOOL \ + { \ + 1.0000f, 0.8200f, 0.3162f, 0.7943f, 1.0000f, 5.4900f, 1.3100f, 1.1400f, 0.4467f, 0.0390f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2220f, 0.5500f, 1.1590f, \ + 0.2100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_SPORT_GYMNASIUM \ - { 1.0000f, 0.8100f, 0.3162f, 0.4467f, 0.8913f, 3.1400f, 1.0600f, 1.3500f, 0.3981f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0450f, { 0.0000f, 0.0000f, 0.0000f }, 0.1460f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPORT_GYMNASIUM \ + { \ + 1.0000f, 0.8100f, 0.3162f, 0.4467f, 0.8913f, 3.1400f, 1.0600f, 1.3500f, 0.3981f, 0.0290f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0450f, { 0.0000f, 0.0000f, 0.0000f }, 0.1460f, 0.1400f, 0.2500f, \ + 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPORT_FULLSTADIUM \ - { 1.0000f, 1.0000f, 0.3162f, 0.0708f, 0.7943f, 5.2500f, 0.1700f, 0.8000f, 0.1000f, 0.1880f, { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPORT_FULLSTADIUM \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.0708f, 0.7943f, 5.2500f, 0.1700f, 0.8000f, 0.1000f, 0.1880f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SPORT_STADIUMTANNOY \ - { 1.0000f, 0.7800f, 0.3162f, 0.5623f, 0.5012f, 2.5300f, 0.8800f, 0.6800f, 0.2818f, 0.2300f, { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_SPORT_STADIUMTANNOY \ + { \ + 1.0000f, 0.7800f, 0.3162f, 0.5623f, 0.5012f, 2.5300f, 0.8800f, 0.6800f, 0.2818f, 0.2300f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } /* Prefab Presets */ -#define EFX_REVERB_PRESET_PREFAB_WORKSHOP \ - { 0.4287f, 1.0000f, 0.3162f, 0.1413f, 0.3981f, 0.7600f, 1.0000f, 1.0000f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_PREFAB_WORKSHOP \ + { \ + 0.4287f, 1.0000f, 0.3162f, 0.1413f, 0.3981f, 0.7600f, 1.0000f, 1.0000f, 1.0000f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_PREFAB_SCHOOLROOM \ - { 0.4022f, 0.6900f, 0.3162f, 0.6310f, 0.5012f, 0.9800f, 0.4500f, 0.1800f, 1.4125f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_PREFAB_SCHOOLROOM \ + { \ + 0.4022f, 0.6900f, 0.3162f, 0.6310f, 0.5012f, 0.9800f, 0.4500f, 0.1800f, 1.4125f, 0.0170f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, \ + 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_PREFAB_PRACTISEROOM \ - { 0.4022f, 0.8700f, 0.3162f, 0.3981f, 0.5012f, 1.1200f, 0.5600f, 0.1800f, 1.2589f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_PREFAB_PRACTISEROOM \ + { \ + 0.4022f, 0.8700f, 0.3162f, 0.3981f, 0.5012f, 1.1200f, 0.5600f, 0.1800f, 1.2589f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, \ + 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_PREFAB_OUTHOUSE \ - { 1.0000f, 0.8200f, 0.3162f, 0.1122f, 0.1585f, 1.3800f, 0.3800f, 0.3500f, 0.8913f, 0.0240f, { 0.0000f, 0.0000f, -0.0000f }, 0.6310f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.1210f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_PREFAB_OUTHOUSE \ + { \ + 1.0000f, 0.8200f, 0.3162f, 0.1122f, 0.1585f, 1.3800f, 0.3800f, 0.3500f, 0.8913f, 0.0240f, \ + { 0.0000f, 0.0000f, -0.0000f }, 0.6310f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.1210f, 0.1700f, \ + 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_PREFAB_CARAVAN \ - { 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.1259f, 0.4300f, 1.5000f, 1.0000f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_PREFAB_CARAVAN \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.1259f, 0.4300f, 1.5000f, 1.0000f, 1.0000f, 0.0120f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } /* Dome and Pipe Presets */ -#define EFX_REVERB_PRESET_DOME_TOMB \ - { 1.0000f, 0.7900f, 0.3162f, 0.3548f, 0.2239f, 4.1800f, 0.2100f, 0.1000f, 0.3868f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 1.6788f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.1900f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_DOME_TOMB \ + { \ + 1.0000f, 0.7900f, 0.3162f, 0.3548f, 0.2239f, 4.1800f, 0.2100f, 0.1000f, 0.3868f, 0.0300f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.6788f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.1900f, 0.2500f, \ + 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_PIPE_SMALL \ - { 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 5.0400f, 0.1000f, 0.1000f, 0.5012f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 2.5119f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_PIPE_SMALL \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 5.0400f, 0.1000f, 0.1000f, 0.5012f, 0.0320f, \ + { 0.0000f, 0.0000f, 0.0000f }, 2.5119f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_DOME_SAINTPAULS \ - { 1.0000f, 0.8700f, 0.3162f, 0.3548f, 0.2239f, 10.4800f, 0.1900f, 0.1000f, 0.1778f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0420f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1200f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_DOME_SAINTPAULS \ + { \ + 1.0000f, 0.8700f, 0.3162f, 0.3548f, 0.2239f, 10.4800f, 0.1900f, 0.1000f, 0.1778f, 0.0900f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0420f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1200f, 0.2500f, \ + 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_PIPE_LONGTHIN \ - { 0.2560f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 9.2100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_PIPE_LONGTHIN \ + { \ + 0.2560f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 9.2100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_PIPE_LARGE \ - { 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 8.4500f, 0.1000f, 0.1000f, 0.3981f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_PIPE_LARGE \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 8.4500f, 0.1000f, 0.1000f, 0.3981f, 0.0460f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_PIPE_RESONANT \ - { 0.1373f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 6.8100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_PIPE_RESONANT \ + { \ + 0.1373f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 6.8100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 \ + } /* Outdoors Presets */ -#define EFX_REVERB_PRESET_OUTDOORS_BACKYARD \ - { 1.0000f, 0.4500f, 0.3162f, 0.2512f, 0.5012f, 1.1200f, 0.3400f, 0.4600f, 0.4467f, 0.0690f, { 0.0000f, 0.0000f, -0.0000f }, 0.7079f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_OUTDOORS_BACKYARD \ + { \ + 1.0000f, 0.4500f, 0.3162f, 0.2512f, 0.5012f, 1.1200f, 0.3400f, 0.4600f, 0.4467f, 0.0690f, \ + { 0.0000f, 0.0000f, -0.0000f }, 0.7079f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, \ + 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_OUTDOORS_ROLLINGPLAINS \ - { 1.0000f, 0.0000f, 0.3162f, 0.0112f, 0.6310f, 2.1300f, 0.2100f, 0.4600f, 0.1778f, 0.3000f, { 0.0000f, 0.0000f, -0.0000f }, 0.4467f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_OUTDOORS_ROLLINGPLAINS \ + { \ + 1.0000f, 0.0000f, 0.3162f, 0.0112f, 0.6310f, 2.1300f, 0.2100f, 0.4600f, 0.1778f, 0.3000f, \ + { 0.0000f, 0.0000f, -0.0000f }, 0.4467f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, \ + 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_OUTDOORS_DEEPCANYON \ - { 1.0000f, 0.7400f, 0.3162f, 0.1778f, 0.6310f, 3.8900f, 0.2100f, 0.4600f, 0.3162f, 0.2230f, { 0.0000f, 0.0000f, -0.0000f }, 0.3548f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_OUTDOORS_DEEPCANYON \ + { \ + 1.0000f, 0.7400f, 0.3162f, 0.1778f, 0.6310f, 3.8900f, 0.2100f, 0.4600f, 0.3162f, 0.2230f, \ + { 0.0000f, 0.0000f, -0.0000f }, 0.3548f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, \ + 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_OUTDOORS_CREEK \ - { 1.0000f, 0.3500f, 0.3162f, 0.1778f, 0.5012f, 2.1300f, 0.2100f, 0.4600f, 0.3981f, 0.1150f, { 0.0000f, 0.0000f, -0.0000f }, 0.1995f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_OUTDOORS_CREEK \ + { \ + 1.0000f, 0.3500f, 0.3162f, 0.1778f, 0.5012f, 2.1300f, 0.2100f, 0.4600f, 0.3981f, 0.1150f, \ + { 0.0000f, 0.0000f, -0.0000f }, 0.1995f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, \ + 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_OUTDOORS_VALLEY \ - { 1.0000f, 0.2800f, 0.3162f, 0.0282f, 0.1585f, 2.8800f, 0.2600f, 0.3500f, 0.1413f, 0.2630f, { 0.0000f, 0.0000f, -0.0000f }, 0.3981f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_OUTDOORS_VALLEY \ + { \ + 1.0000f, 0.2800f, 0.3162f, 0.0282f, 0.1585f, 2.8800f, 0.2600f, 0.3500f, 0.1413f, 0.2630f, \ + { 0.0000f, 0.0000f, -0.0000f }, 0.3981f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3400f, \ + 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 \ + } /* Mood Presets */ -#define EFX_REVERB_PRESET_MOOD_HEAVEN \ - { 1.0000f, 0.9400f, 0.3162f, 0.7943f, 0.4467f, 5.0400f, 1.1200f, 0.5600f, 0.2427f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0800f, 2.7420f, 0.0500f, 0.9977f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_MOOD_HEAVEN \ + { \ + 1.0000f, 0.9400f, 0.3162f, 0.7943f, 0.4467f, 5.0400f, 1.1200f, 0.5600f, 0.2427f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0800f, 2.7420f, \ + 0.0500f, 0.9977f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_MOOD_HELL \ - { 1.0000f, 0.5700f, 0.3162f, 0.3548f, 0.4467f, 3.5700f, 0.4900f, 2.0000f, 0.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1100f, 0.0400f, 2.1090f, 0.5200f, 0.9943f, 5000.0000f, 139.5000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_MOOD_HELL \ + { \ + 1.0000f, 0.5700f, 0.3162f, 0.3548f, 0.4467f, 3.5700f, 0.4900f, 2.0000f, 0.0000f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1100f, 0.0400f, 2.1090f, \ + 0.5200f, 0.9943f, 5000.0000f, 139.5000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_MOOD_MEMORY \ - { 1.0000f, 0.8500f, 0.3162f, 0.6310f, 0.3548f, 4.0600f, 0.8200f, 0.5600f, 0.0398f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.4740f, 0.4500f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_MOOD_MEMORY \ + { \ + 1.0000f, 0.8500f, 0.3162f, 0.6310f, 0.3548f, 4.0600f, 0.8200f, 0.5600f, 0.0398f, 0.0000f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.4740f, \ + 0.4500f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } /* Driving Presets */ -#define EFX_REVERB_PRESET_DRIVING_COMMENTATOR \ - { 1.0000f, 0.0000f, 0.3162f, 0.5623f, 0.5012f, 2.4200f, 0.8800f, 0.6800f, 0.1995f, 0.0930f, { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_DRIVING_COMMENTATOR \ + { \ + 1.0000f, 0.0000f, 0.3162f, 0.5623f, 0.5012f, 2.4200f, 0.8800f, 0.6800f, 0.1995f, 0.0930f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, \ + 0.0000f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_DRIVING_PITGARAGE \ - { 0.4287f, 0.5900f, 0.3162f, 0.7079f, 0.5623f, 1.7200f, 0.9300f, 0.8700f, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_DRIVING_PITGARAGE \ + { \ + 0.4287f, 0.5900f, 0.3162f, 0.7079f, 0.5623f, 1.7200f, 0.9300f, 0.8700f, 0.5623f, 0.0000f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1100f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_DRIVING_INCAR_RACER \ - { 0.0832f, 0.8000f, 0.3162f, 1.0000f, 0.7943f, 0.1700f, 2.0000f, 0.4100f, 1.7783f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_DRIVING_INCAR_RACER \ + { \ + 0.0832f, 0.8000f, 0.3162f, 1.0000f, 0.7943f, 0.1700f, 2.0000f, 0.4100f, 1.7783f, 0.0070f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_DRIVING_INCAR_SPORTS \ - { 0.0832f, 0.8000f, 0.3162f, 0.6310f, 1.0000f, 0.1700f, 0.7500f, 0.4100f, 1.0000f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_DRIVING_INCAR_SPORTS \ + { \ + 0.0832f, 0.8000f, 0.3162f, 0.6310f, 1.0000f, 0.1700f, 0.7500f, 0.4100f, 1.0000f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_DRIVING_INCAR_LUXURY \ - { 0.2560f, 1.0000f, 0.3162f, 0.1000f, 0.5012f, 0.1300f, 0.4100f, 0.4600f, 0.7943f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_DRIVING_INCAR_LUXURY \ + { \ + 0.2560f, 1.0000f, 0.3162f, 0.1000f, 0.5012f, 0.1300f, 0.4100f, 0.4600f, 0.7943f, 0.0100f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_DRIVING_FULLGRANDSTAND \ - { 1.0000f, 1.0000f, 0.3162f, 0.2818f, 0.6310f, 3.0100f, 1.3700f, 1.2800f, 0.3548f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 0.1778f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_DRIVING_FULLGRANDSTAND \ + { \ + 1.0000f, 1.0000f, 0.3162f, 0.2818f, 0.6310f, 3.0100f, 1.3700f, 1.2800f, 0.3548f, 0.0900f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.1778f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_DRIVING_EMPTYGRANDSTAND \ - { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 0.7943f, 4.6200f, 1.7500f, 1.4000f, 0.2082f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_DRIVING_EMPTYGRANDSTAND \ + { \ + 1.0000f, 1.0000f, 0.3162f, 1.0000f, 0.7943f, 4.6200f, 1.7500f, 1.4000f, 0.2082f, 0.0900f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_DRIVING_TUNNEL \ - { 1.0000f, 0.8100f, 0.3162f, 0.3981f, 0.8913f, 3.4200f, 0.9400f, 1.3100f, 0.7079f, 0.0510f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.0500f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 155.3000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_DRIVING_TUNNEL \ + { \ + 1.0000f, 0.8100f, 0.3162f, 0.3981f, 0.8913f, 3.4200f, 0.9400f, 1.3100f, 0.7079f, 0.0510f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.0500f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 155.3000f, 0.0000f, 0x1 \ + } /* City Presets */ -#define EFX_REVERB_PRESET_CITY_STREETS \ - { 1.0000f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.7900f, 1.1200f, 0.9100f, 0.2818f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 0.1995f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CITY_STREETS \ + { \ + 1.0000f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.7900f, 1.1200f, 0.9100f, 0.2818f, 0.0460f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.1995f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CITY_SUBWAY \ - { 1.0000f, 0.7400f, 0.3162f, 0.7079f, 0.8913f, 3.0100f, 1.2300f, 0.9100f, 0.7079f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.2100f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CITY_SUBWAY \ + { \ + 1.0000f, 0.7400f, 0.3162f, 0.7079f, 0.8913f, 3.0100f, 1.2300f, 0.9100f, 0.7079f, 0.0460f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.2100f, 0.2500f, \ + 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CITY_MUSEUM \ - { 1.0000f, 0.8200f, 0.3162f, 0.1778f, 0.1778f, 3.2800f, 1.4000f, 0.5700f, 0.2512f, 0.0390f, { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_CITY_MUSEUM \ + { \ + 1.0000f, 0.8200f, 0.3162f, 0.1778f, 0.1778f, 3.2800f, 1.4000f, 0.5700f, 0.2512f, 0.0390f, \ + { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, \ + 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_CITY_LIBRARY \ - { 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.0891f, 2.7600f, 0.8900f, 0.4100f, 0.3548f, 0.0290f, { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_CITY_LIBRARY \ + { \ + 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.0891f, 2.7600f, 0.8900f, 0.4100f, 0.3548f, 0.0290f, \ + { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, \ + 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 \ + } -#define EFX_REVERB_PRESET_CITY_UNDERPASS \ - { 1.0000f, 0.8200f, 0.3162f, 0.4467f, 0.8913f, 3.5700f, 1.1200f, 0.9100f, 0.3981f, 0.0590f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1400f, 0.2500f, 0.0000f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CITY_UNDERPASS \ + { \ + 1.0000f, 0.8200f, 0.3162f, 0.4467f, 0.8913f, 3.5700f, 1.1200f, 0.9100f, 0.3981f, 0.0590f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1400f, 0.2500f, \ + 0.0000f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CITY_ABANDONED \ - { 1.0000f, 0.6900f, 0.3162f, 0.7943f, 0.8913f, 3.2800f, 1.1700f, 0.9100f, 0.4467f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9966f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CITY_ABANDONED \ + { \ + 1.0000f, 0.6900f, 0.3162f, 0.7943f, 0.8913f, 3.2800f, 1.1700f, 0.9100f, 0.4467f, 0.0440f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, \ + 0.0000f, 0.9966f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } /* Misc. Presets */ -#define EFX_REVERB_PRESET_DUSTYROOM \ - { 0.3645f, 0.5600f, 0.3162f, 0.7943f, 0.7079f, 1.7900f, 0.3800f, 0.2100f, 0.5012f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0060f, { 0.0000f, 0.0000f, 0.0000f }, 0.2020f, 0.0500f, 0.2500f, 0.0000f, 0.9886f, 13046.0000f, 163.3000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_DUSTYROOM \ + { \ + 0.3645f, 0.5600f, 0.3162f, 0.7943f, 0.7079f, 1.7900f, 0.3800f, 0.2100f, 0.5012f, 0.0020f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0060f, { 0.0000f, 0.0000f, 0.0000f }, 0.2020f, 0.0500f, 0.2500f, \ + 0.0000f, 0.9886f, 13046.0000f, 163.3000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_CHAPEL \ - { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 1.0000f, 4.6200f, 0.6400f, 1.2300f, 0.4467f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.1100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } +#define EFX_REVERB_PRESET_CHAPEL \ + { \ + 1.0000f, 0.8400f, 0.3162f, 0.5623f, 1.0000f, 4.6200f, 0.6400f, 1.2300f, 0.4467f, 0.0320f, \ + { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, \ + 0.1100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 \ + } -#define EFX_REVERB_PRESET_SMALLWATERROOM \ - { 1.0000f, 0.7000f, 0.3162f, 0.4477f, 1.0000f, 1.5100f, 1.2500f, 1.1400f, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +#define EFX_REVERB_PRESET_SMALLWATERROOM \ + { \ + 1.0000f, 0.7000f, 0.3162f, 0.4477f, 1.0000f, 1.5100f, 1.2500f, 1.1400f, 0.8913f, 0.0200f, \ + { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, \ + 0.1900f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x0 \ + } #endif /* EFX_PRESETS_H */ diff --git a/apps/openmw/mwsound/efx.h b/apps/openmw/mwsound/efx.h index 57766983f..718a9f79d 100644 --- a/apps/openmw/mwsound/efx.h +++ b/apps/openmw/mwsound/efx.h @@ -1,761 +1,752 @@ #ifndef AL_EFX_H #define AL_EFX_H - -#include "alc.h" #include "al.h" #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -#define ALC_EXT_EFX_NAME "ALC_EXT_EFX" - -#define ALC_EFX_MAJOR_VERSION 0x20001 -#define ALC_EFX_MINOR_VERSION 0x20002 -#define ALC_MAX_AUXILIARY_SENDS 0x20003 +#define ALC_EXT_EFX_NAME "ALC_EXT_EFX" +#define ALC_EFX_MAJOR_VERSION 0x20001 +#define ALC_EFX_MINOR_VERSION 0x20002 +#define ALC_MAX_AUXILIARY_SENDS 0x20003 /* Listener properties. */ -#define AL_METERS_PER_UNIT 0x20004 +#define AL_METERS_PER_UNIT 0x20004 /* Source properties. */ -#define AL_DIRECT_FILTER 0x20005 -#define AL_AUXILIARY_SEND_FILTER 0x20006 -#define AL_AIR_ABSORPTION_FACTOR 0x20007 -#define AL_ROOM_ROLLOFF_FACTOR 0x20008 -#define AL_CONE_OUTER_GAINHF 0x20009 -#define AL_DIRECT_FILTER_GAINHF_AUTO 0x2000A -#define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B -#define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C - +#define AL_DIRECT_FILTER 0x20005 +#define AL_AUXILIARY_SEND_FILTER 0x20006 +#define AL_AIR_ABSORPTION_FACTOR 0x20007 +#define AL_ROOM_ROLLOFF_FACTOR 0x20008 +#define AL_CONE_OUTER_GAINHF 0x20009 +#define AL_DIRECT_FILTER_GAINHF_AUTO 0x2000A +#define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B +#define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C /* Effect properties. */ /* Reverb effect parameters */ -#define AL_REVERB_DENSITY 0x0001 -#define AL_REVERB_DIFFUSION 0x0002 -#define AL_REVERB_GAIN 0x0003 -#define AL_REVERB_GAINHF 0x0004 -#define AL_REVERB_DECAY_TIME 0x0005 -#define AL_REVERB_DECAY_HFRATIO 0x0006 -#define AL_REVERB_REFLECTIONS_GAIN 0x0007 -#define AL_REVERB_REFLECTIONS_DELAY 0x0008 -#define AL_REVERB_LATE_REVERB_GAIN 0x0009 -#define AL_REVERB_LATE_REVERB_DELAY 0x000A -#define AL_REVERB_AIR_ABSORPTION_GAINHF 0x000B -#define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C -#define AL_REVERB_DECAY_HFLIMIT 0x000D +#define AL_REVERB_DENSITY 0x0001 +#define AL_REVERB_DIFFUSION 0x0002 +#define AL_REVERB_GAIN 0x0003 +#define AL_REVERB_GAINHF 0x0004 +#define AL_REVERB_DECAY_TIME 0x0005 +#define AL_REVERB_DECAY_HFRATIO 0x0006 +#define AL_REVERB_REFLECTIONS_GAIN 0x0007 +#define AL_REVERB_REFLECTIONS_DELAY 0x0008 +#define AL_REVERB_LATE_REVERB_GAIN 0x0009 +#define AL_REVERB_LATE_REVERB_DELAY 0x000A +#define AL_REVERB_AIR_ABSORPTION_GAINHF 0x000B +#define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C +#define AL_REVERB_DECAY_HFLIMIT 0x000D /* EAX Reverb effect parameters */ -#define AL_EAXREVERB_DENSITY 0x0001 -#define AL_EAXREVERB_DIFFUSION 0x0002 -#define AL_EAXREVERB_GAIN 0x0003 -#define AL_EAXREVERB_GAINHF 0x0004 -#define AL_EAXREVERB_GAINLF 0x0005 -#define AL_EAXREVERB_DECAY_TIME 0x0006 -#define AL_EAXREVERB_DECAY_HFRATIO 0x0007 -#define AL_EAXREVERB_DECAY_LFRATIO 0x0008 -#define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 -#define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A -#define AL_EAXREVERB_REFLECTIONS_PAN 0x000B -#define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C -#define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D -#define AL_EAXREVERB_LATE_REVERB_PAN 0x000E -#define AL_EAXREVERB_ECHO_TIME 0x000F -#define AL_EAXREVERB_ECHO_DEPTH 0x0010 -#define AL_EAXREVERB_MODULATION_TIME 0x0011 -#define AL_EAXREVERB_MODULATION_DEPTH 0x0012 -#define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 -#define AL_EAXREVERB_HFREFERENCE 0x0014 -#define AL_EAXREVERB_LFREFERENCE 0x0015 -#define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 -#define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 +#define AL_EAXREVERB_DENSITY 0x0001 +#define AL_EAXREVERB_DIFFUSION 0x0002 +#define AL_EAXREVERB_GAIN 0x0003 +#define AL_EAXREVERB_GAINHF 0x0004 +#define AL_EAXREVERB_GAINLF 0x0005 +#define AL_EAXREVERB_DECAY_TIME 0x0006 +#define AL_EAXREVERB_DECAY_HFRATIO 0x0007 +#define AL_EAXREVERB_DECAY_LFRATIO 0x0008 +#define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 +#define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A +#define AL_EAXREVERB_REFLECTIONS_PAN 0x000B +#define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C +#define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D +#define AL_EAXREVERB_LATE_REVERB_PAN 0x000E +#define AL_EAXREVERB_ECHO_TIME 0x000F +#define AL_EAXREVERB_ECHO_DEPTH 0x0010 +#define AL_EAXREVERB_MODULATION_TIME 0x0011 +#define AL_EAXREVERB_MODULATION_DEPTH 0x0012 +#define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 +#define AL_EAXREVERB_HFREFERENCE 0x0014 +#define AL_EAXREVERB_LFREFERENCE 0x0015 +#define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 +#define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 /* Chorus effect parameters */ -#define AL_CHORUS_WAVEFORM 0x0001 -#define AL_CHORUS_PHASE 0x0002 -#define AL_CHORUS_RATE 0x0003 -#define AL_CHORUS_DEPTH 0x0004 -#define AL_CHORUS_FEEDBACK 0x0005 -#define AL_CHORUS_DELAY 0x0006 +#define AL_CHORUS_WAVEFORM 0x0001 +#define AL_CHORUS_PHASE 0x0002 +#define AL_CHORUS_RATE 0x0003 +#define AL_CHORUS_DEPTH 0x0004 +#define AL_CHORUS_FEEDBACK 0x0005 +#define AL_CHORUS_DELAY 0x0006 /* Distortion effect parameters */ -#define AL_DISTORTION_EDGE 0x0001 -#define AL_DISTORTION_GAIN 0x0002 -#define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 -#define AL_DISTORTION_EQCENTER 0x0004 -#define AL_DISTORTION_EQBANDWIDTH 0x0005 +#define AL_DISTORTION_EDGE 0x0001 +#define AL_DISTORTION_GAIN 0x0002 +#define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 +#define AL_DISTORTION_EQCENTER 0x0004 +#define AL_DISTORTION_EQBANDWIDTH 0x0005 /* Echo effect parameters */ -#define AL_ECHO_DELAY 0x0001 -#define AL_ECHO_LRDELAY 0x0002 -#define AL_ECHO_DAMPING 0x0003 -#define AL_ECHO_FEEDBACK 0x0004 -#define AL_ECHO_SPREAD 0x0005 +#define AL_ECHO_DELAY 0x0001 +#define AL_ECHO_LRDELAY 0x0002 +#define AL_ECHO_DAMPING 0x0003 +#define AL_ECHO_FEEDBACK 0x0004 +#define AL_ECHO_SPREAD 0x0005 /* Flanger effect parameters */ -#define AL_FLANGER_WAVEFORM 0x0001 -#define AL_FLANGER_PHASE 0x0002 -#define AL_FLANGER_RATE 0x0003 -#define AL_FLANGER_DEPTH 0x0004 -#define AL_FLANGER_FEEDBACK 0x0005 -#define AL_FLANGER_DELAY 0x0006 +#define AL_FLANGER_WAVEFORM 0x0001 +#define AL_FLANGER_PHASE 0x0002 +#define AL_FLANGER_RATE 0x0003 +#define AL_FLANGER_DEPTH 0x0004 +#define AL_FLANGER_FEEDBACK 0x0005 +#define AL_FLANGER_DELAY 0x0006 /* Frequency shifter effect parameters */ -#define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 -#define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 -#define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 +#define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 +#define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 +#define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 /* Vocal morpher effect parameters */ -#define AL_VOCAL_MORPHER_PHONEMEA 0x0001 -#define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 -#define AL_VOCAL_MORPHER_PHONEMEB 0x0003 -#define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 -#define AL_VOCAL_MORPHER_WAVEFORM 0x0005 -#define AL_VOCAL_MORPHER_RATE 0x0006 +#define AL_VOCAL_MORPHER_PHONEMEA 0x0001 +#define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 +#define AL_VOCAL_MORPHER_PHONEMEB 0x0003 +#define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 +#define AL_VOCAL_MORPHER_WAVEFORM 0x0005 +#define AL_VOCAL_MORPHER_RATE 0x0006 /* Pitchshifter effect parameters */ -#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 -#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 +#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 +#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 /* Ringmodulator effect parameters */ -#define AL_RING_MODULATOR_FREQUENCY 0x0001 -#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 -#define AL_RING_MODULATOR_WAVEFORM 0x0003 +#define AL_RING_MODULATOR_FREQUENCY 0x0001 +#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 +#define AL_RING_MODULATOR_WAVEFORM 0x0003 /* Autowah effect parameters */ -#define AL_AUTOWAH_ATTACK_TIME 0x0001 -#define AL_AUTOWAH_RELEASE_TIME 0x0002 -#define AL_AUTOWAH_RESONANCE 0x0003 -#define AL_AUTOWAH_PEAK_GAIN 0x0004 +#define AL_AUTOWAH_ATTACK_TIME 0x0001 +#define AL_AUTOWAH_RELEASE_TIME 0x0002 +#define AL_AUTOWAH_RESONANCE 0x0003 +#define AL_AUTOWAH_PEAK_GAIN 0x0004 /* Compressor effect parameters */ -#define AL_COMPRESSOR_ONOFF 0x0001 +#define AL_COMPRESSOR_ONOFF 0x0001 /* Equalizer effect parameters */ -#define AL_EQUALIZER_LOW_GAIN 0x0001 -#define AL_EQUALIZER_LOW_CUTOFF 0x0002 -#define AL_EQUALIZER_MID1_GAIN 0x0003 -#define AL_EQUALIZER_MID1_CENTER 0x0004 -#define AL_EQUALIZER_MID1_WIDTH 0x0005 -#define AL_EQUALIZER_MID2_GAIN 0x0006 -#define AL_EQUALIZER_MID2_CENTER 0x0007 -#define AL_EQUALIZER_MID2_WIDTH 0x0008 -#define AL_EQUALIZER_HIGH_GAIN 0x0009 -#define AL_EQUALIZER_HIGH_CUTOFF 0x000A +#define AL_EQUALIZER_LOW_GAIN 0x0001 +#define AL_EQUALIZER_LOW_CUTOFF 0x0002 +#define AL_EQUALIZER_MID1_GAIN 0x0003 +#define AL_EQUALIZER_MID1_CENTER 0x0004 +#define AL_EQUALIZER_MID1_WIDTH 0x0005 +#define AL_EQUALIZER_MID2_GAIN 0x0006 +#define AL_EQUALIZER_MID2_CENTER 0x0007 +#define AL_EQUALIZER_MID2_WIDTH 0x0008 +#define AL_EQUALIZER_HIGH_GAIN 0x0009 +#define AL_EQUALIZER_HIGH_CUTOFF 0x000A /* Effect type */ -#define AL_EFFECT_FIRST_PARAMETER 0x0000 -#define AL_EFFECT_LAST_PARAMETER 0x8000 -#define AL_EFFECT_TYPE 0x8001 +#define AL_EFFECT_FIRST_PARAMETER 0x0000 +#define AL_EFFECT_LAST_PARAMETER 0x8000 +#define AL_EFFECT_TYPE 0x8001 /* Effect types, used with the AL_EFFECT_TYPE property */ -#define AL_EFFECT_NULL 0x0000 -#define AL_EFFECT_REVERB 0x0001 -#define AL_EFFECT_CHORUS 0x0002 -#define AL_EFFECT_DISTORTION 0x0003 -#define AL_EFFECT_ECHO 0x0004 -#define AL_EFFECT_FLANGER 0x0005 -#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 -#define AL_EFFECT_VOCAL_MORPHER 0x0007 -#define AL_EFFECT_PITCH_SHIFTER 0x0008 -#define AL_EFFECT_RING_MODULATOR 0x0009 -#define AL_EFFECT_AUTOWAH 0x000A -#define AL_EFFECT_COMPRESSOR 0x000B -#define AL_EFFECT_EQUALIZER 0x000C -#define AL_EFFECT_EAXREVERB 0x8000 +#define AL_EFFECT_NULL 0x0000 +#define AL_EFFECT_REVERB 0x0001 +#define AL_EFFECT_CHORUS 0x0002 +#define AL_EFFECT_DISTORTION 0x0003 +#define AL_EFFECT_ECHO 0x0004 +#define AL_EFFECT_FLANGER 0x0005 +#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 +#define AL_EFFECT_VOCAL_MORPHER 0x0007 +#define AL_EFFECT_PITCH_SHIFTER 0x0008 +#define AL_EFFECT_RING_MODULATOR 0x0009 +#define AL_EFFECT_AUTOWAH 0x000A +#define AL_EFFECT_COMPRESSOR 0x000B +#define AL_EFFECT_EQUALIZER 0x000C +#define AL_EFFECT_EAXREVERB 0x8000 /* Auxiliary Effect Slot properties. */ -#define AL_EFFECTSLOT_EFFECT 0x0001 -#define AL_EFFECTSLOT_GAIN 0x0002 -#define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 +#define AL_EFFECTSLOT_EFFECT 0x0001 +#define AL_EFFECTSLOT_GAIN 0x0002 +#define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 /* NULL Auxiliary Slot ID to disable a source send. */ -#define AL_EFFECTSLOT_NULL 0x0000 - +#define AL_EFFECTSLOT_NULL 0x0000 /* Filter properties. */ /* Lowpass filter parameters */ -#define AL_LOWPASS_GAIN 0x0001 -#define AL_LOWPASS_GAINHF 0x0002 +#define AL_LOWPASS_GAIN 0x0001 +#define AL_LOWPASS_GAINHF 0x0002 /* Highpass filter parameters */ -#define AL_HIGHPASS_GAIN 0x0001 -#define AL_HIGHPASS_GAINLF 0x0002 +#define AL_HIGHPASS_GAIN 0x0001 +#define AL_HIGHPASS_GAINLF 0x0002 /* Bandpass filter parameters */ -#define AL_BANDPASS_GAIN 0x0001 -#define AL_BANDPASS_GAINLF 0x0002 -#define AL_BANDPASS_GAINHF 0x0003 +#define AL_BANDPASS_GAIN 0x0001 +#define AL_BANDPASS_GAINLF 0x0002 +#define AL_BANDPASS_GAINHF 0x0003 /* Filter type */ -#define AL_FILTER_FIRST_PARAMETER 0x0000 -#define AL_FILTER_LAST_PARAMETER 0x8000 -#define AL_FILTER_TYPE 0x8001 +#define AL_FILTER_FIRST_PARAMETER 0x0000 +#define AL_FILTER_LAST_PARAMETER 0x8000 +#define AL_FILTER_TYPE 0x8001 /* Filter types, used with the AL_FILTER_TYPE property */ -#define AL_FILTER_NULL 0x0000 -#define AL_FILTER_LOWPASS 0x0001 -#define AL_FILTER_HIGHPASS 0x0002 -#define AL_FILTER_BANDPASS 0x0003 +#define AL_FILTER_NULL 0x0000 +#define AL_FILTER_LOWPASS 0x0001 +#define AL_FILTER_HIGHPASS 0x0002 +#define AL_FILTER_BANDPASS 0x0003 + /* Effect object function types. */ + typedef void(AL_APIENTRY* LPALGENEFFECTS)(ALsizei, ALuint*); + typedef void(AL_APIENTRY* LPALDELETEEFFECTS)(ALsizei, const ALuint*); + typedef ALboolean(AL_APIENTRY* LPALISEFFECT)(ALuint); + typedef void(AL_APIENTRY* LPALEFFECTI)(ALuint, ALenum, ALint); + typedef void(AL_APIENTRY* LPALEFFECTIV)(ALuint, ALenum, const ALint*); + typedef void(AL_APIENTRY* LPALEFFECTF)(ALuint, ALenum, ALfloat); + typedef void(AL_APIENTRY* LPALEFFECTFV)(ALuint, ALenum, const ALfloat*); + typedef void(AL_APIENTRY* LPALGETEFFECTI)(ALuint, ALenum, ALint*); + typedef void(AL_APIENTRY* LPALGETEFFECTIV)(ALuint, ALenum, ALint*); + typedef void(AL_APIENTRY* LPALGETEFFECTF)(ALuint, ALenum, ALfloat*); + typedef void(AL_APIENTRY* LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*); -/* Effect object function types. */ -typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint); -typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*); + /* Filter object function types. */ + typedef void(AL_APIENTRY* LPALGENFILTERS)(ALsizei, ALuint*); + typedef void(AL_APIENTRY* LPALDELETEFILTERS)(ALsizei, const ALuint*); + typedef ALboolean(AL_APIENTRY* LPALISFILTER)(ALuint); + typedef void(AL_APIENTRY* LPALFILTERI)(ALuint, ALenum, ALint); + typedef void(AL_APIENTRY* LPALFILTERIV)(ALuint, ALenum, const ALint*); + typedef void(AL_APIENTRY* LPALFILTERF)(ALuint, ALenum, ALfloat); + typedef void(AL_APIENTRY* LPALFILTERFV)(ALuint, ALenum, const ALfloat*); + typedef void(AL_APIENTRY* LPALGETFILTERI)(ALuint, ALenum, ALint*); + typedef void(AL_APIENTRY* LPALGETFILTERIV)(ALuint, ALenum, ALint*); + typedef void(AL_APIENTRY* LPALGETFILTERF)(ALuint, ALenum, ALfloat*); + typedef void(AL_APIENTRY* LPALGETFILTERFV)(ALuint, ALenum, ALfloat*); -/* Filter object function types. */ -typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint); -typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*); - -/* Auxiliary Effect Slot object function types. */ -typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); + /* Auxiliary Effect Slot object function types. */ + typedef void(AL_APIENTRY* LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); + typedef void(AL_APIENTRY* LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*); + typedef ALboolean(AL_APIENTRY* LPALISAUXILIARYEFFECTSLOT)(ALuint); + typedef void(AL_APIENTRY* LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint); + typedef void(AL_APIENTRY* LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*); + typedef void(AL_APIENTRY* LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat); + typedef void(AL_APIENTRY* LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*); + typedef void(AL_APIENTRY* LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*); + typedef void(AL_APIENTRY* LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); + typedef void(AL_APIENTRY* LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*); + typedef void(AL_APIENTRY* LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); #ifdef AL_ALEXT_PROTOTYPES -AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); -AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects); -AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); -AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); -AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues); -AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); -AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues); -AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); -AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); -AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); -AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); + AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint* effects); + AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint* effects); + AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); + AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); + AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint* piValues); + AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); + AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat* pflValues); + AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint* piValue); + AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint* piValues); + AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat* pflValue); + AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat* pflValues); -AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); -AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters); -AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); -AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); -AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues); -AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); -AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues); -AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); -AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); -AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); -AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); + AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint* filters); + AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint* filters); + AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); + AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); + AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint* piValues); + AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); + AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat* pflValues); + AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint* piValue); + AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint* piValues); + AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat* pflValue); + AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat* pflValues); -AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); -AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots); -AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues); -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); -AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues); -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); -AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); + AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint* effectslots); + AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint* effectslots); + AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); + AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); + AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint* piValues); + AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); + AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat* pflValues); + AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint* piValue); + AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint* piValues); + AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat* pflValue); + AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat* pflValues); #endif /* Filter ranges and defaults. */ /* Lowpass filter */ -#define AL_LOWPASS_MIN_GAIN (0.0f) -#define AL_LOWPASS_MAX_GAIN (1.0f) -#define AL_LOWPASS_DEFAULT_GAIN (1.0f) +#define AL_LOWPASS_MIN_GAIN (0.0f) +#define AL_LOWPASS_MAX_GAIN (1.0f) +#define AL_LOWPASS_DEFAULT_GAIN (1.0f) -#define AL_LOWPASS_MIN_GAINHF (0.0f) -#define AL_LOWPASS_MAX_GAINHF (1.0f) -#define AL_LOWPASS_DEFAULT_GAINHF (1.0f) +#define AL_LOWPASS_MIN_GAINHF (0.0f) +#define AL_LOWPASS_MAX_GAINHF (1.0f) +#define AL_LOWPASS_DEFAULT_GAINHF (1.0f) /* Highpass filter */ -#define AL_HIGHPASS_MIN_GAIN (0.0f) -#define AL_HIGHPASS_MAX_GAIN (1.0f) -#define AL_HIGHPASS_DEFAULT_GAIN (1.0f) +#define AL_HIGHPASS_MIN_GAIN (0.0f) +#define AL_HIGHPASS_MAX_GAIN (1.0f) +#define AL_HIGHPASS_DEFAULT_GAIN (1.0f) -#define AL_HIGHPASS_MIN_GAINLF (0.0f) -#define AL_HIGHPASS_MAX_GAINLF (1.0f) -#define AL_HIGHPASS_DEFAULT_GAINLF (1.0f) +#define AL_HIGHPASS_MIN_GAINLF (0.0f) +#define AL_HIGHPASS_MAX_GAINLF (1.0f) +#define AL_HIGHPASS_DEFAULT_GAINLF (1.0f) /* Bandpass filter */ -#define AL_BANDPASS_MIN_GAIN (0.0f) -#define AL_BANDPASS_MAX_GAIN (1.0f) -#define AL_BANDPASS_DEFAULT_GAIN (1.0f) +#define AL_BANDPASS_MIN_GAIN (0.0f) +#define AL_BANDPASS_MAX_GAIN (1.0f) +#define AL_BANDPASS_DEFAULT_GAIN (1.0f) -#define AL_BANDPASS_MIN_GAINHF (0.0f) -#define AL_BANDPASS_MAX_GAINHF (1.0f) -#define AL_BANDPASS_DEFAULT_GAINHF (1.0f) - -#define AL_BANDPASS_MIN_GAINLF (0.0f) -#define AL_BANDPASS_MAX_GAINLF (1.0f) -#define AL_BANDPASS_DEFAULT_GAINLF (1.0f) +#define AL_BANDPASS_MIN_GAINHF (0.0f) +#define AL_BANDPASS_MAX_GAINHF (1.0f) +#define AL_BANDPASS_DEFAULT_GAINHF (1.0f) +#define AL_BANDPASS_MIN_GAINLF (0.0f) +#define AL_BANDPASS_MAX_GAINLF (1.0f) +#define AL_BANDPASS_DEFAULT_GAINLF (1.0f) /* Effect parameter ranges and defaults. */ /* Standard reverb effect */ -#define AL_REVERB_MIN_DENSITY (0.0f) -#define AL_REVERB_MAX_DENSITY (1.0f) -#define AL_REVERB_DEFAULT_DENSITY (1.0f) +#define AL_REVERB_MIN_DENSITY (0.0f) +#define AL_REVERB_MAX_DENSITY (1.0f) +#define AL_REVERB_DEFAULT_DENSITY (1.0f) -#define AL_REVERB_MIN_DIFFUSION (0.0f) -#define AL_REVERB_MAX_DIFFUSION (1.0f) -#define AL_REVERB_DEFAULT_DIFFUSION (1.0f) +#define AL_REVERB_MIN_DIFFUSION (0.0f) +#define AL_REVERB_MAX_DIFFUSION (1.0f) +#define AL_REVERB_DEFAULT_DIFFUSION (1.0f) -#define AL_REVERB_MIN_GAIN (0.0f) -#define AL_REVERB_MAX_GAIN (1.0f) -#define AL_REVERB_DEFAULT_GAIN (0.32f) +#define AL_REVERB_MIN_GAIN (0.0f) +#define AL_REVERB_MAX_GAIN (1.0f) +#define AL_REVERB_DEFAULT_GAIN (0.32f) -#define AL_REVERB_MIN_GAINHF (0.0f) -#define AL_REVERB_MAX_GAINHF (1.0f) -#define AL_REVERB_DEFAULT_GAINHF (0.89f) +#define AL_REVERB_MIN_GAINHF (0.0f) +#define AL_REVERB_MAX_GAINHF (1.0f) +#define AL_REVERB_DEFAULT_GAINHF (0.89f) -#define AL_REVERB_MIN_DECAY_TIME (0.1f) -#define AL_REVERB_MAX_DECAY_TIME (20.0f) -#define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) +#define AL_REVERB_MIN_DECAY_TIME (0.1f) +#define AL_REVERB_MAX_DECAY_TIME (20.0f) +#define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) -#define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) -#define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) -#define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) +#define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) +#define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) +#define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) -#define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) -#define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) -#define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) +#define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) +#define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) +#define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) -#define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) -#define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) -#define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) +#define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) +#define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) +#define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) -#define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) -#define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) -#define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) +#define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) +#define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) +#define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) -#define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) -#define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) -#define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) +#define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) +#define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) +#define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) -#define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) -#define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) -#define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) +#define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +#define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +#define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) -#define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) -#define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) -#define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) -#define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE -#define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE -#define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE +#define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE +#define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE +#define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE /* EAX reverb effect */ -#define AL_EAXREVERB_MIN_DENSITY (0.0f) -#define AL_EAXREVERB_MAX_DENSITY (1.0f) -#define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) +#define AL_EAXREVERB_MIN_DENSITY (0.0f) +#define AL_EAXREVERB_MAX_DENSITY (1.0f) +#define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) -#define AL_EAXREVERB_MIN_DIFFUSION (0.0f) -#define AL_EAXREVERB_MAX_DIFFUSION (1.0f) -#define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) +#define AL_EAXREVERB_MIN_DIFFUSION (0.0f) +#define AL_EAXREVERB_MAX_DIFFUSION (1.0f) +#define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) -#define AL_EAXREVERB_MIN_GAIN (0.0f) -#define AL_EAXREVERB_MAX_GAIN (1.0f) -#define AL_EAXREVERB_DEFAULT_GAIN (0.32f) +#define AL_EAXREVERB_MIN_GAIN (0.0f) +#define AL_EAXREVERB_MAX_GAIN (1.0f) +#define AL_EAXREVERB_DEFAULT_GAIN (0.32f) -#define AL_EAXREVERB_MIN_GAINHF (0.0f) -#define AL_EAXREVERB_MAX_GAINHF (1.0f) -#define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) +#define AL_EAXREVERB_MIN_GAINHF (0.0f) +#define AL_EAXREVERB_MAX_GAINHF (1.0f) +#define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) -#define AL_EAXREVERB_MIN_GAINLF (0.0f) -#define AL_EAXREVERB_MAX_GAINLF (1.0f) -#define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) +#define AL_EAXREVERB_MIN_GAINLF (0.0f) +#define AL_EAXREVERB_MAX_GAINLF (1.0f) +#define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) -#define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) -#define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) -#define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) +#define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) +#define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) -#define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) -#define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) -#define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) +#define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) +#define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) -#define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) -#define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) -#define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) +#define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) +#define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) -#define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) -#define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) -#define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) +#define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) +#define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) -#define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) -#define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) -#define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) +#define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) +#define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) #define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f) -#define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) -#define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) -#define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) +#define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) +#define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) -#define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) -#define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) -#define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) +#define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) +#define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) #define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f) -#define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) -#define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) -#define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) +#define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) +#define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) +#define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) -#define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) -#define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) -#define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) +#define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) +#define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) +#define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) -#define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) -#define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) -#define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) +#define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) +#define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) +#define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) -#define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) -#define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) -#define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) +#define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) +#define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) +#define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) -#define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) -#define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +#define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +#define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) #define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) -#define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) -#define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) -#define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) +#define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) +#define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) +#define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) -#define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) -#define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) -#define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) +#define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) +#define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) +#define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) -#define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) -#define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) #define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) -#define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE -#define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE -#define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE +#define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE +#define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE +#define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE /* Chorus effect */ -#define AL_CHORUS_WAVEFORM_SINUSOID (0) -#define AL_CHORUS_WAVEFORM_TRIANGLE (1) +#define AL_CHORUS_WAVEFORM_SINUSOID (0) +#define AL_CHORUS_WAVEFORM_TRIANGLE (1) -#define AL_CHORUS_MIN_WAVEFORM (0) -#define AL_CHORUS_MAX_WAVEFORM (1) -#define AL_CHORUS_DEFAULT_WAVEFORM (1) +#define AL_CHORUS_MIN_WAVEFORM (0) +#define AL_CHORUS_MAX_WAVEFORM (1) +#define AL_CHORUS_DEFAULT_WAVEFORM (1) -#define AL_CHORUS_MIN_PHASE (-180) -#define AL_CHORUS_MAX_PHASE (180) -#define AL_CHORUS_DEFAULT_PHASE (90) +#define AL_CHORUS_MIN_PHASE (-180) +#define AL_CHORUS_MAX_PHASE (180) +#define AL_CHORUS_DEFAULT_PHASE (90) -#define AL_CHORUS_MIN_RATE (0.0f) -#define AL_CHORUS_MAX_RATE (10.0f) -#define AL_CHORUS_DEFAULT_RATE (1.1f) +#define AL_CHORUS_MIN_RATE (0.0f) +#define AL_CHORUS_MAX_RATE (10.0f) +#define AL_CHORUS_DEFAULT_RATE (1.1f) -#define AL_CHORUS_MIN_DEPTH (0.0f) -#define AL_CHORUS_MAX_DEPTH (1.0f) -#define AL_CHORUS_DEFAULT_DEPTH (0.1f) +#define AL_CHORUS_MIN_DEPTH (0.0f) +#define AL_CHORUS_MAX_DEPTH (1.0f) +#define AL_CHORUS_DEFAULT_DEPTH (0.1f) -#define AL_CHORUS_MIN_FEEDBACK (-1.0f) -#define AL_CHORUS_MAX_FEEDBACK (1.0f) -#define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) +#define AL_CHORUS_MIN_FEEDBACK (-1.0f) +#define AL_CHORUS_MAX_FEEDBACK (1.0f) +#define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) -#define AL_CHORUS_MIN_DELAY (0.0f) -#define AL_CHORUS_MAX_DELAY (0.016f) -#define AL_CHORUS_DEFAULT_DELAY (0.016f) +#define AL_CHORUS_MIN_DELAY (0.0f) +#define AL_CHORUS_MAX_DELAY (0.016f) +#define AL_CHORUS_DEFAULT_DELAY (0.016f) /* Distortion effect */ -#define AL_DISTORTION_MIN_EDGE (0.0f) -#define AL_DISTORTION_MAX_EDGE (1.0f) -#define AL_DISTORTION_DEFAULT_EDGE (0.2f) +#define AL_DISTORTION_MIN_EDGE (0.0f) +#define AL_DISTORTION_MAX_EDGE (1.0f) +#define AL_DISTORTION_DEFAULT_EDGE (0.2f) -#define AL_DISTORTION_MIN_GAIN (0.01f) -#define AL_DISTORTION_MAX_GAIN (1.0f) -#define AL_DISTORTION_DEFAULT_GAIN (0.05f) +#define AL_DISTORTION_MIN_GAIN (0.01f) +#define AL_DISTORTION_MAX_GAIN (1.0f) +#define AL_DISTORTION_DEFAULT_GAIN (0.05f) -#define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) -#define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) -#define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) +#define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) +#define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) +#define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) -#define AL_DISTORTION_MIN_EQCENTER (80.0f) -#define AL_DISTORTION_MAX_EQCENTER (24000.0f) -#define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) +#define AL_DISTORTION_MIN_EQCENTER (80.0f) +#define AL_DISTORTION_MAX_EQCENTER (24000.0f) +#define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) -#define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) -#define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) -#define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) +#define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) +#define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) +#define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) /* Echo effect */ -#define AL_ECHO_MIN_DELAY (0.0f) -#define AL_ECHO_MAX_DELAY (0.207f) -#define AL_ECHO_DEFAULT_DELAY (0.1f) +#define AL_ECHO_MIN_DELAY (0.0f) +#define AL_ECHO_MAX_DELAY (0.207f) +#define AL_ECHO_DEFAULT_DELAY (0.1f) -#define AL_ECHO_MIN_LRDELAY (0.0f) -#define AL_ECHO_MAX_LRDELAY (0.404f) -#define AL_ECHO_DEFAULT_LRDELAY (0.1f) +#define AL_ECHO_MIN_LRDELAY (0.0f) +#define AL_ECHO_MAX_LRDELAY (0.404f) +#define AL_ECHO_DEFAULT_LRDELAY (0.1f) -#define AL_ECHO_MIN_DAMPING (0.0f) -#define AL_ECHO_MAX_DAMPING (0.99f) -#define AL_ECHO_DEFAULT_DAMPING (0.5f) +#define AL_ECHO_MIN_DAMPING (0.0f) +#define AL_ECHO_MAX_DAMPING (0.99f) +#define AL_ECHO_DEFAULT_DAMPING (0.5f) -#define AL_ECHO_MIN_FEEDBACK (0.0f) -#define AL_ECHO_MAX_FEEDBACK (1.0f) -#define AL_ECHO_DEFAULT_FEEDBACK (0.5f) +#define AL_ECHO_MIN_FEEDBACK (0.0f) +#define AL_ECHO_MAX_FEEDBACK (1.0f) +#define AL_ECHO_DEFAULT_FEEDBACK (0.5f) -#define AL_ECHO_MIN_SPREAD (-1.0f) -#define AL_ECHO_MAX_SPREAD (1.0f) -#define AL_ECHO_DEFAULT_SPREAD (-1.0f) +#define AL_ECHO_MIN_SPREAD (-1.0f) +#define AL_ECHO_MAX_SPREAD (1.0f) +#define AL_ECHO_DEFAULT_SPREAD (-1.0f) /* Flanger effect */ -#define AL_FLANGER_WAVEFORM_SINUSOID (0) -#define AL_FLANGER_WAVEFORM_TRIANGLE (1) +#define AL_FLANGER_WAVEFORM_SINUSOID (0) +#define AL_FLANGER_WAVEFORM_TRIANGLE (1) -#define AL_FLANGER_MIN_WAVEFORM (0) -#define AL_FLANGER_MAX_WAVEFORM (1) -#define AL_FLANGER_DEFAULT_WAVEFORM (1) +#define AL_FLANGER_MIN_WAVEFORM (0) +#define AL_FLANGER_MAX_WAVEFORM (1) +#define AL_FLANGER_DEFAULT_WAVEFORM (1) -#define AL_FLANGER_MIN_PHASE (-180) -#define AL_FLANGER_MAX_PHASE (180) -#define AL_FLANGER_DEFAULT_PHASE (0) +#define AL_FLANGER_MIN_PHASE (-180) +#define AL_FLANGER_MAX_PHASE (180) +#define AL_FLANGER_DEFAULT_PHASE (0) -#define AL_FLANGER_MIN_RATE (0.0f) -#define AL_FLANGER_MAX_RATE (10.0f) -#define AL_FLANGER_DEFAULT_RATE (0.27f) +#define AL_FLANGER_MIN_RATE (0.0f) +#define AL_FLANGER_MAX_RATE (10.0f) +#define AL_FLANGER_DEFAULT_RATE (0.27f) -#define AL_FLANGER_MIN_DEPTH (0.0f) -#define AL_FLANGER_MAX_DEPTH (1.0f) -#define AL_FLANGER_DEFAULT_DEPTH (1.0f) +#define AL_FLANGER_MIN_DEPTH (0.0f) +#define AL_FLANGER_MAX_DEPTH (1.0f) +#define AL_FLANGER_DEFAULT_DEPTH (1.0f) -#define AL_FLANGER_MIN_FEEDBACK (-1.0f) -#define AL_FLANGER_MAX_FEEDBACK (1.0f) -#define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) +#define AL_FLANGER_MIN_FEEDBACK (-1.0f) +#define AL_FLANGER_MAX_FEEDBACK (1.0f) +#define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) -#define AL_FLANGER_MIN_DELAY (0.0f) -#define AL_FLANGER_MAX_DELAY (0.004f) -#define AL_FLANGER_DEFAULT_DELAY (0.002f) +#define AL_FLANGER_MIN_DELAY (0.0f) +#define AL_FLANGER_MAX_DELAY (0.004f) +#define AL_FLANGER_DEFAULT_DELAY (0.002f) /* Frequency shifter effect */ -#define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) -#define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) -#define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) +#define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) +#define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) +#define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) -#define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) -#define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) +#define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) +#define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) #define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0) -#define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) -#define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) -#define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) +#define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) +#define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) +#define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) #define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0) #define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2) #define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0) /* Vocal morpher effect */ -#define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) -#define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) -#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) +#define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) +#define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) #define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24) #define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0) -#define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) -#define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) -#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) +#define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) +#define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) #define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24) #define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0) -#define AL_VOCAL_MORPHER_PHONEME_A (0) -#define AL_VOCAL_MORPHER_PHONEME_E (1) -#define AL_VOCAL_MORPHER_PHONEME_I (2) -#define AL_VOCAL_MORPHER_PHONEME_O (3) -#define AL_VOCAL_MORPHER_PHONEME_U (4) -#define AL_VOCAL_MORPHER_PHONEME_AA (5) -#define AL_VOCAL_MORPHER_PHONEME_AE (6) -#define AL_VOCAL_MORPHER_PHONEME_AH (7) -#define AL_VOCAL_MORPHER_PHONEME_AO (8) -#define AL_VOCAL_MORPHER_PHONEME_EH (9) -#define AL_VOCAL_MORPHER_PHONEME_ER (10) -#define AL_VOCAL_MORPHER_PHONEME_IH (11) -#define AL_VOCAL_MORPHER_PHONEME_IY (12) -#define AL_VOCAL_MORPHER_PHONEME_UH (13) -#define AL_VOCAL_MORPHER_PHONEME_UW (14) -#define AL_VOCAL_MORPHER_PHONEME_B (15) -#define AL_VOCAL_MORPHER_PHONEME_D (16) -#define AL_VOCAL_MORPHER_PHONEME_F (17) -#define AL_VOCAL_MORPHER_PHONEME_G (18) -#define AL_VOCAL_MORPHER_PHONEME_J (19) -#define AL_VOCAL_MORPHER_PHONEME_K (20) -#define AL_VOCAL_MORPHER_PHONEME_L (21) -#define AL_VOCAL_MORPHER_PHONEME_M (22) -#define AL_VOCAL_MORPHER_PHONEME_N (23) -#define AL_VOCAL_MORPHER_PHONEME_P (24) -#define AL_VOCAL_MORPHER_PHONEME_R (25) -#define AL_VOCAL_MORPHER_PHONEME_S (26) -#define AL_VOCAL_MORPHER_PHONEME_T (27) -#define AL_VOCAL_MORPHER_PHONEME_V (28) -#define AL_VOCAL_MORPHER_PHONEME_Z (29) +#define AL_VOCAL_MORPHER_PHONEME_A (0) +#define AL_VOCAL_MORPHER_PHONEME_E (1) +#define AL_VOCAL_MORPHER_PHONEME_I (2) +#define AL_VOCAL_MORPHER_PHONEME_O (3) +#define AL_VOCAL_MORPHER_PHONEME_U (4) +#define AL_VOCAL_MORPHER_PHONEME_AA (5) +#define AL_VOCAL_MORPHER_PHONEME_AE (6) +#define AL_VOCAL_MORPHER_PHONEME_AH (7) +#define AL_VOCAL_MORPHER_PHONEME_AO (8) +#define AL_VOCAL_MORPHER_PHONEME_EH (9) +#define AL_VOCAL_MORPHER_PHONEME_ER (10) +#define AL_VOCAL_MORPHER_PHONEME_IH (11) +#define AL_VOCAL_MORPHER_PHONEME_IY (12) +#define AL_VOCAL_MORPHER_PHONEME_UH (13) +#define AL_VOCAL_MORPHER_PHONEME_UW (14) +#define AL_VOCAL_MORPHER_PHONEME_B (15) +#define AL_VOCAL_MORPHER_PHONEME_D (16) +#define AL_VOCAL_MORPHER_PHONEME_F (17) +#define AL_VOCAL_MORPHER_PHONEME_G (18) +#define AL_VOCAL_MORPHER_PHONEME_J (19) +#define AL_VOCAL_MORPHER_PHONEME_K (20) +#define AL_VOCAL_MORPHER_PHONEME_L (21) +#define AL_VOCAL_MORPHER_PHONEME_M (22) +#define AL_VOCAL_MORPHER_PHONEME_N (23) +#define AL_VOCAL_MORPHER_PHONEME_P (24) +#define AL_VOCAL_MORPHER_PHONEME_R (25) +#define AL_VOCAL_MORPHER_PHONEME_S (26) +#define AL_VOCAL_MORPHER_PHONEME_T (27) +#define AL_VOCAL_MORPHER_PHONEME_V (28) +#define AL_VOCAL_MORPHER_PHONEME_Z (29) -#define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) -#define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) -#define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) +#define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) +#define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) +#define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) -#define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) -#define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) -#define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) +#define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) +#define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) +#define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) -#define AL_VOCAL_MORPHER_MIN_RATE (0.0f) -#define AL_VOCAL_MORPHER_MAX_RATE (10.0f) -#define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) +#define AL_VOCAL_MORPHER_MIN_RATE (0.0f) +#define AL_VOCAL_MORPHER_MAX_RATE (10.0f) +#define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) /* Pitch shifter effect */ -#define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) -#define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) -#define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) +#define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) +#define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) +#define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) -#define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) -#define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) -#define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) +#define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) +#define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) +#define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) /* Ring modulator effect */ -#define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) -#define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) -#define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) +#define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) +#define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) +#define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) -#define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) -#define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) +#define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) +#define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) #define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f) -#define AL_RING_MODULATOR_SINUSOID (0) -#define AL_RING_MODULATOR_SAWTOOTH (1) -#define AL_RING_MODULATOR_SQUARE (2) +#define AL_RING_MODULATOR_SINUSOID (0) +#define AL_RING_MODULATOR_SAWTOOTH (1) +#define AL_RING_MODULATOR_SQUARE (2) -#define AL_RING_MODULATOR_MIN_WAVEFORM (0) -#define AL_RING_MODULATOR_MAX_WAVEFORM (2) -#define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) +#define AL_RING_MODULATOR_MIN_WAVEFORM (0) +#define AL_RING_MODULATOR_MAX_WAVEFORM (2) +#define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) /* Autowah effect */ -#define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) -#define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) -#define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) +#define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) +#define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) +#define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) -#define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) -#define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) -#define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) +#define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) +#define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) +#define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) -#define AL_AUTOWAH_MIN_RESONANCE (2.0f) -#define AL_AUTOWAH_MAX_RESONANCE (1000.0f) -#define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) +#define AL_AUTOWAH_MIN_RESONANCE (2.0f) +#define AL_AUTOWAH_MAX_RESONANCE (1000.0f) +#define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) -#define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) -#define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) -#define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) +#define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) +#define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) +#define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) /* Compressor effect */ -#define AL_COMPRESSOR_MIN_ONOFF (0) -#define AL_COMPRESSOR_MAX_ONOFF (1) -#define AL_COMPRESSOR_DEFAULT_ONOFF (1) +#define AL_COMPRESSOR_MIN_ONOFF (0) +#define AL_COMPRESSOR_MAX_ONOFF (1) +#define AL_COMPRESSOR_DEFAULT_ONOFF (1) /* Equalizer effect */ -#define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) -#define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) -#define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) +#define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) +#define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) -#define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) -#define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) -#define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) +#define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) +#define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) +#define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) -#define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) -#define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) -#define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) +#define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) +#define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) -#define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) -#define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) -#define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) +#define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) +#define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) +#define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) -#define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) -#define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) -#define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) +#define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) +#define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) +#define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) -#define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) -#define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) -#define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) +#define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) +#define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) -#define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) -#define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) -#define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) +#define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) +#define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) +#define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) -#define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) -#define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) -#define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) +#define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) +#define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) +#define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) -#define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) -#define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) -#define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) - -#define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) -#define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) -#define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) +#define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) +#define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) +#define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) +#define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) +#define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) /* Source parameter value ranges and defaults. */ -#define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) -#define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) -#define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) +#define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) +#define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) +#define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) -#define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) -#define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) -#define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) -#define AL_MIN_CONE_OUTER_GAINHF (0.0f) -#define AL_MAX_CONE_OUTER_GAINHF (1.0f) -#define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) +#define AL_MIN_CONE_OUTER_GAINHF (0.0f) +#define AL_MAX_CONE_OUTER_GAINHF (1.0f) +#define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) -#define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE -#define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE -#define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE +#define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE +#define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE +#define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE -#define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE -#define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE +#define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE +#define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE #define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE #define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE #define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE #define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE - /* Listener parameter value ranges and defaults. */ -#define AL_MIN_METERS_PER_UNIT FLT_MIN -#define AL_MAX_METERS_PER_UNIT FLT_MAX -#define AL_DEFAULT_METERS_PER_UNIT (1.0f) - +#define AL_MIN_METERS_PER_UNIT FLT_MIN +#define AL_MAX_METERS_PER_UNIT FLT_MAX +#define AL_DEFAULT_METERS_PER_UNIT (1.0f) #ifdef __cplusplus -} /* extern "C" */ +} /* extern "C" */ #endif #endif /* AL_EFX_H */ diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 0a9641635..3766add47 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -2,8 +2,8 @@ #include -#include #include +#include #include #include @@ -11,460 +11,467 @@ namespace MWSound { -int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) -{ - try + int FFmpeg_Decoder::readPacket(void* user_data, uint8_t* buf, int buf_size) + { + try + { + std::istream& stream = *static_cast(user_data)->mDataStream; + stream.clear(); + stream.read((char*)buf, buf_size); + std::streamsize count = stream.gcount(); + if (count == 0) + return AVERROR_EOF; + return count; + } + catch (std::exception&) + { + return AVERROR_UNKNOWN; + } + } + + int FFmpeg_Decoder::writePacket(void*, uint8_t*, int) + { + Log(Debug::Error) << "can't write to read-only stream"; + return -1; + } + + int64_t FFmpeg_Decoder::seek(void* user_data, int64_t offset, int whence) { std::istream& stream = *static_cast(user_data)->mDataStream; + + whence &= ~AVSEEK_FORCE; + stream.clear(); - stream.read((char*)buf, buf_size); - return stream.gcount(); - } - catch (std::exception& ) - { - return 0; - } -} -int FFmpeg_Decoder::writePacket(void *, uint8_t *, int) -{ - Log(Debug::Error) << "can't write to read-only stream"; - return -1; -} - -int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) -{ - std::istream& stream = *static_cast(user_data)->mDataStream; - - whence &= ~AVSEEK_FORCE; - - stream.clear(); - - if(whence == AVSEEK_SIZE) - { - size_t prev = stream.tellg(); - stream.seekg(0, std::ios_base::end); - size_t size = stream.tellg(); - stream.seekg(prev, std::ios_base::beg); - return size; - } - - if(whence == SEEK_SET) - stream.seekg(offset, std::ios_base::beg); - else if(whence == SEEK_CUR) - stream.seekg(offset, std::ios_base::cur); - else if(whence == SEEK_END) - stream.seekg(offset, std::ios_base::end); - else - return -1; - - return stream.tellg(); -} - - -/* Used by getAV*Data to search for more compressed data, and buffer it in the - * correct stream. It won't buffer data for streams that the app doesn't have a - * handle for. */ -bool FFmpeg_Decoder::getNextPacket() -{ - if(!mStream) - return false; - - int stream_idx = mStream - mFormatCtx->streams; - while(av_read_frame(mFormatCtx, &mPacket) >= 0) - { - /* Check if the packet belongs to this stream */ - if(stream_idx == mPacket.stream_index) + if (whence == AVSEEK_SIZE) { - if(mPacket.pts != (int64_t)AV_NOPTS_VALUE) - mNextPts = av_q2d((*mStream)->time_base)*mPacket.pts; - return true; + size_t prev = stream.tellg(); + stream.seekg(0, std::ios_base::end); + size_t size = stream.tellg(); + stream.seekg(prev, std::ios_base::beg); + return size; } - /* Free the packet and look for another */ - av_packet_unref(&mPacket); + if (whence == SEEK_SET) + stream.seekg(offset, std::ios_base::beg); + else if (whence == SEEK_CUR) + stream.seekg(offset, std::ios_base::cur); + else if (whence == SEEK_END) + stream.seekg(offset, std::ios_base::end); + else + return -1; + + return stream.tellg(); } - return false; -} - -bool FFmpeg_Decoder::getAVAudioData() -{ - bool got_frame = false; - - if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) - return false; - - do { - /* Decode some data, and check for errors */ - int ret = avcodec_receive_frame(mCodecCtx, mFrame); - if (ret == AVERROR(EAGAIN)) - { - if (mPacket.size == 0 && !getNextPacket()) - return false; - ret = avcodec_send_packet(mCodecCtx, &mPacket); - av_packet_unref(&mPacket); - if (ret == 0) - continue; - } - if (ret != 0) + /* Used by getAV*Data to search for more compressed data, and buffer it in the + * correct stream. It won't buffer data for streams that the app doesn't have a + * handle for. */ + bool FFmpeg_Decoder::getNextPacket() + { + if (!mStream) return false; - av_packet_unref(&mPacket); - - if (mFrame->nb_samples == 0) - continue; - got_frame = true; - - if(mSwr) + int stream_idx = mStream - mFormatCtx->streams; + while (av_read_frame(mFormatCtx, &mPacket) >= 0) { - if(!mDataBuf || mDataBufLen < mFrame->nb_samples) + /* Check if the packet belongs to this stream */ + if (stream_idx == mPacket.stream_index) { - av_freep(&mDataBuf); - if(av_samples_alloc(&mDataBuf, nullptr, av_get_channel_layout_nb_channels(mOutputChannelLayout), - mFrame->nb_samples, mOutputSampleFormat, 0) < 0) - return false; - else - mDataBufLen = mFrame->nb_samples; + if (mPacket.pts != (int64_t)AV_NOPTS_VALUE) + mNextPts = av_q2d((*mStream)->time_base) * mPacket.pts; + return true; } - if(swr_convert(mSwr, (uint8_t**)&mDataBuf, mFrame->nb_samples, - (const uint8_t**)mFrame->extended_data, mFrame->nb_samples) < 0) - { - return false; - } - mFrameData = &mDataBuf; - } - else - mFrameData = &mFrame->data[0]; - - } while(!got_frame); - mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate; - - return true; -} - -size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) -{ - size_t dec = 0; - - while(dec < length) - { - /* If there's no decoded data, find some */ - if(mFramePos >= mFrameSize) - { - if(!getAVAudioData()) - break; - mFramePos = 0; - mFrameSize = mFrame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout) * - av_get_bytes_per_sample(mOutputSampleFormat); + /* Free the packet and look for another */ + av_packet_unref(&mPacket); } - /* Get the amount of bytes remaining to be written, and clamp to - * the amount of decoded data we have */ - size_t rem = std::min(length-dec, mFrameSize-mFramePos); - - /* Copy the data to the app's buffer and increment */ - memcpy(data, mFrameData[0]+mFramePos, rem); - data = (char*)data + rem; - dec += rem; - mFramePos += rem; + return false; } - /* Return the number of bytes we were able to get */ - return dec; -} - -void FFmpeg_Decoder::open(const std::string &fname) -{ - close(); - mDataStream = mResourceMgr->get(fname); - - if((mFormatCtx=avformat_alloc_context()) == nullptr) - throw std::runtime_error("Failed to allocate context"); - - try + bool FFmpeg_Decoder::getAVAudioData() { - mFormatCtx->pb = avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek); - if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), nullptr, nullptr) != 0) + bool got_frame = false; + + if (mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) + return false; + + do { - // "Note that a user-supplied AVFormatContext will be freed on failure". - if (mFormatCtx) + /* Decode some data, and check for errors */ + int ret = avcodec_receive_frame(mCodecCtx, mFrame); + if (ret == AVERROR(EAGAIN)) { - if (mFormatCtx->pb != nullptr) + if (mPacket.size == 0 && !getNextPacket()) + return false; + ret = avcodec_send_packet(mCodecCtx, &mPacket); + av_packet_unref(&mPacket); + if (ret == 0) + continue; + } + if (ret != 0) + return false; + + av_packet_unref(&mPacket); + + if (mFrame->nb_samples == 0) + continue; + got_frame = true; + + if (mSwr) + { + if (!mDataBuf || mDataBufLen < mFrame->nb_samples) { - if (mFormatCtx->pb->buffer != nullptr) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; + av_freep(&mDataBuf); + if (av_samples_alloc(&mDataBuf, nullptr, av_get_channel_layout_nb_channels(mOutputChannelLayout), + mFrame->nb_samples, mOutputSampleFormat, 0) + < 0) + return false; + else + mDataBufLen = mFrame->nb_samples; } - avformat_free_context(mFormatCtx); + + if (swr_convert(mSwr, (uint8_t**)&mDataBuf, mFrame->nb_samples, (const uint8_t**)mFrame->extended_data, + mFrame->nb_samples) + < 0) + { + return false; + } + mFrameData = &mDataBuf; } - mFormatCtx = nullptr; - throw std::runtime_error("Failed to allocate input stream"); - } + else + mFrameData = &mFrame->data[0]; - if(avformat_find_stream_info(mFormatCtx, nullptr) < 0) - throw std::runtime_error("Failed to find stream info in "+fname); + } while (!got_frame); + mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate; - for(size_t j = 0;j < mFormatCtx->nb_streams;j++) + return true; + } + + size_t FFmpeg_Decoder::readAVAudioData(void* data, size_t length) + { + size_t dec = 0; + + while (dec < length) { - if(mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + /* If there's no decoded data, find some */ + if (mFramePos >= mFrameSize) { - mStream = &mFormatCtx->streams[j]; - break; + if (!getAVAudioData()) + break; + mFramePos = 0; + mFrameSize = mFrame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout) + * av_get_bytes_per_sample(mOutputSampleFormat); } - } - if(!mStream) - throw std::runtime_error("No audio streams in "+fname); +<<<<<<< HEAD const AVCodec *codec = avcodec_find_decoder((*mStream)->codecpar->codec_id); if(!codec) - { - std::string ss = "No codec found for id " + - std::to_string((*mStream)->codecpar->codec_id); - throw std::runtime_error(ss); +======= + /* Get the amount of bytes remaining to be written, and clamp to + * the amount of decoded data we have */ + size_t rem = std::min(length - dec, mFrameSize - mFramePos); + + /* Copy the data to the app's buffer and increment */ + memcpy(data, mFrameData[0] + mFramePos, rem); + data = (char*)data + rem; + dec += rem; + mFramePos += rem; } - AVCodecContext *avctx = avcodec_alloc_context3(codec); - avcodec_parameters_to_context(avctx, (*mStream)->codecpar); + /* Return the number of bytes we were able to get */ + return dec; + } + + void FFmpeg_Decoder::open(const std::string& fname) + { + close(); + mDataStream = mResourceMgr->get(fname); + + if ((mFormatCtx = avformat_alloc_context()) == nullptr) + throw std::runtime_error("Failed to allocate context"); + + try +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 + { + mFormatCtx->pb = avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek); + if (!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), nullptr, nullptr) != 0) + { + // "Note that a user-supplied AVFormatContext will be freed on failure". + if (mFormatCtx) + { + if (mFormatCtx->pb != nullptr) + { + if (mFormatCtx->pb->buffer != nullptr) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = nullptr; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = nullptr; + } + avformat_free_context(mFormatCtx); + } + mFormatCtx = nullptr; + throw std::runtime_error("Failed to allocate input stream"); + } + + if (avformat_find_stream_info(mFormatCtx, nullptr) < 0) + throw std::runtime_error("Failed to find stream info in " + fname); + + for (size_t j = 0; j < mFormatCtx->nb_streams; j++) + { + if (mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + { + mStream = &mFormatCtx->streams[j]; + break; + } + } + if (!mStream) + throw std::runtime_error("No audio streams in " + fname); + + const AVCodec* codec = avcodec_find_decoder((*mStream)->codecpar->codec_id); + if (!codec) + { + std::string ss = "No codec found for id " + std::to_string((*mStream)->codecpar->codec_id); + throw std::runtime_error(ss); + } + + AVCodecContext* avctx = avcodec_alloc_context3(codec); + avcodec_parameters_to_context(avctx, (*mStream)->codecpar); // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 - av_codec_set_pkt_timebase(avctx, (*mStream)->time_base); + av_codec_set_pkt_timebase(avctx, (*mStream)->time_base); #endif - mCodecCtx = avctx; + mCodecCtx = avctx; - if(avcodec_open2(mCodecCtx, codec, nullptr) < 0) - throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); + if (avcodec_open2(mCodecCtx, codec, nullptr) < 0) + throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); - mFrame = av_frame_alloc(); + mFrame = av_frame_alloc(); - if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) - mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support - else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) - mOutputSampleFormat = AV_SAMPLE_FMT_U8; - else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16P) - mOutputSampleFormat = AV_SAMPLE_FMT_S16; - else - mOutputSampleFormat = AV_SAMPLE_FMT_S16; + if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + // FIXME: Check for AL_EXT_FLOAT32 support + // else if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) + // mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else + mOutputSampleFormat = AV_SAMPLE_FMT_S16; - mOutputChannelLayout = (*mStream)->codecpar->channel_layout; - if(mOutputChannelLayout == 0) - mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels); + mOutputChannelLayout = (*mStream)->codecpar->channel_layout; + if (mOutputChannelLayout == 0) + mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels); - mCodecCtx->channel_layout = mOutputChannelLayout; + mCodecCtx->channel_layout = mOutputChannelLayout; + } + catch (...) + { + if (mStream) + avcodec_free_context(&mCodecCtx); + mStream = nullptr; + + if (mFormatCtx != nullptr) + { + if (mFormatCtx->pb->buffer != nullptr) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = nullptr; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = nullptr; + + avformat_close_input(&mFormatCtx); + } + } } - catch(...) + + void FFmpeg_Decoder::close() { - if(mStream) + if (mStream) avcodec_free_context(&mCodecCtx); mStream = nullptr; - if (mFormatCtx != nullptr) - { - if (mFormatCtx->pb->buffer != nullptr) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; + av_packet_unref(&mPacket); + av_freep(&mDataBuf); + av_frame_free(&mFrame); + swr_free(&mSwr); + if (mFormatCtx) + { + if (mFormatCtx->pb != nullptr) + { + // 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 != nullptr) + { + av_freep(&mFormatCtx->pb->buffer); + } +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + avio_context_free(&mFormatCtx->pb); +#else + av_freep(&mFormatCtx->pb); +#endif + } avformat_close_input(&mFormatCtx); } + + mDataStream.reset(); } -} -void FFmpeg_Decoder::close() -{ - if(mStream) - avcodec_free_context(&mCodecCtx); - mStream = nullptr; - - av_packet_unref(&mPacket); - av_freep(&mDataBuf); - av_frame_free(&mFrame); - swr_free(&mSwr); - - if(mFormatCtx) + std::string FFmpeg_Decoder::getName() { - if (mFormatCtx->pb != nullptr) - { - // 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 != nullptr) - { - av_freep(&mFormatCtx->pb->buffer); - } -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) - avio_context_free(&mFormatCtx->pb); -#else - av_freep(&mFormatCtx->pb); -#endif - } - avformat_close_input(&mFormatCtx); - } - - mDataStream.reset(); -} - -std::string FFmpeg_Decoder::getName() -{ // In the FFMpeg 4.0 a "filename" field was replaced by "url" #if LIBAVCODEC_VERSION_INT < 3805796 - return mFormatCtx->filename; + return mFormatCtx->filename; #else - return mFormatCtx->url; + return mFormatCtx->url; #endif -} - -void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) -{ - if(!mStream) - throw std::runtime_error("No audio stream info"); - - if(mOutputSampleFormat == AV_SAMPLE_FMT_U8) - *type = SampleType_UInt8; - else if(mOutputSampleFormat == AV_SAMPLE_FMT_S16) - *type = SampleType_Int16; - else if(mOutputSampleFormat == AV_SAMPLE_FMT_FLT) - *type = SampleType_Float32; - else - { - mOutputSampleFormat = AV_SAMPLE_FMT_S16; - *type = SampleType_Int16; } - if(mOutputChannelLayout == AV_CH_LAYOUT_MONO) - *chans = ChannelConfig_Mono; - else if(mOutputChannelLayout == AV_CH_LAYOUT_STEREO) - *chans = ChannelConfig_Stereo; - else if(mOutputChannelLayout == AV_CH_LAYOUT_QUAD) - *chans = ChannelConfig_Quad; - else if(mOutputChannelLayout == AV_CH_LAYOUT_5POINT1) - *chans = ChannelConfig_5point1; - else if(mOutputChannelLayout == AV_CH_LAYOUT_7POINT1) - *chans = ChannelConfig_7point1; - else + void FFmpeg_Decoder::getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) { - char str[1024]; - av_get_channel_layout_string(str, sizeof(str), mCodecCtx->channels, mCodecCtx->channel_layout); - Log(Debug::Error) << "Unsupported channel layout: "<< str; + if (!mStream) + throw std::runtime_error("No audio stream info"); - if(mCodecCtx->channels == 1) - { - mOutputChannelLayout = AV_CH_LAYOUT_MONO; - *chans = ChannelConfig_Mono; - } + if (mOutputSampleFormat == AV_SAMPLE_FMT_U8) + *type = SampleType_UInt8; + else if (mOutputSampleFormat == AV_SAMPLE_FMT_S16) + *type = SampleType_Int16; + else if (mOutputSampleFormat == AV_SAMPLE_FMT_FLT) + *type = SampleType_Float32; else { - mOutputChannelLayout = AV_CH_LAYOUT_STEREO; + mOutputSampleFormat = AV_SAMPLE_FMT_S16; + *type = SampleType_Int16; + } + + if (mOutputChannelLayout == AV_CH_LAYOUT_MONO) + *chans = ChannelConfig_Mono; + else if (mOutputChannelLayout == AV_CH_LAYOUT_STEREO) *chans = ChannelConfig_Stereo; + else if (mOutputChannelLayout == AV_CH_LAYOUT_QUAD) + *chans = ChannelConfig_Quad; + else if (mOutputChannelLayout == AV_CH_LAYOUT_5POINT1) + *chans = ChannelConfig_5point1; + else if (mOutputChannelLayout == AV_CH_LAYOUT_7POINT1) + *chans = ChannelConfig_7point1; + else + { + char str[1024]; + av_get_channel_layout_string(str, sizeof(str), mCodecCtx->channels, mCodecCtx->channel_layout); + Log(Debug::Error) << "Unsupported channel layout: " << str; + + if (mCodecCtx->channels == 1) + { + mOutputChannelLayout = AV_CH_LAYOUT_MONO; + *chans = ChannelConfig_Mono; + } + else + { + mOutputChannelLayout = AV_CH_LAYOUT_STEREO; + *chans = ChannelConfig_Stereo; + } + } + + *samplerate = mCodecCtx->sample_rate; + int64_t ch_layout = mCodecCtx->channel_layout; + if (ch_layout == 0) + ch_layout = av_get_default_channel_layout(mCodecCtx->channels); + + if (mOutputSampleFormat != mCodecCtx->sample_fmt || mOutputChannelLayout != ch_layout) + { + mSwr = swr_alloc_set_opts(mSwr, // SwrContext + mOutputChannelLayout, // output ch layout + mOutputSampleFormat, // output sample format + mCodecCtx->sample_rate, // output sample rate + ch_layout, // input ch layout + mCodecCtx->sample_fmt, // input sample format + mCodecCtx->sample_rate, // input sample rate + 0, // logging level offset + nullptr); // log context + if (!mSwr) + throw std::runtime_error("Couldn't allocate SwrContext"); + int init = swr_init(mSwr); + if (init < 0) + throw std::runtime_error("Couldn't initialize SwrContext: " + std::to_string(init)); } } - *samplerate = mCodecCtx->sample_rate; - int64_t ch_layout = mCodecCtx->channel_layout; - if(ch_layout == 0) - ch_layout = av_get_default_channel_layout(mCodecCtx->channels); - - if(mOutputSampleFormat != mCodecCtx->sample_fmt || - mOutputChannelLayout != ch_layout) + size_t FFmpeg_Decoder::read(char* buffer, size_t bytes) { - mSwr = swr_alloc_set_opts(mSwr, // SwrContext - mOutputChannelLayout, // output ch layout - mOutputSampleFormat, // output sample format - mCodecCtx->sample_rate, // output sample rate - ch_layout, // input ch layout - mCodecCtx->sample_fmt, // input sample format - mCodecCtx->sample_rate, // input sample rate - 0, // logging level offset - nullptr); // log context - if(!mSwr) - throw std::runtime_error("Couldn't allocate SwrContext"); - int init=swr_init(mSwr); - if(init < 0) - throw std::runtime_error("Couldn't initialize SwrContext: "+std::to_string(init)); - } -} - -size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) -{ - if(!mStream) - { - Log(Debug::Error) << "No audio stream"; - return 0; - } - return readAVAudioData(buffer, bytes); -} - -void FFmpeg_Decoder::readAll(std::vector &output) -{ - if(!mStream) - { - Log(Debug::Error) << "No audio stream"; - return; + if (!mStream) + { + Log(Debug::Error) << "No audio stream"; + return 0; + } + return readAVAudioData(buffer, bytes); } - while(getAVAudioData()) + void FFmpeg_Decoder::readAll(std::vector& output) { - size_t got = mFrame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout) * - av_get_bytes_per_sample(mOutputSampleFormat); - const char *inbuf = reinterpret_cast(mFrameData[0]); - output.insert(output.end(), inbuf, inbuf+got); + if (!mStream) + { + Log(Debug::Error) << "No audio stream"; + return; + } + + while (getAVAudioData()) + { + size_t got = mFrame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout) + * av_get_bytes_per_sample(mOutputSampleFormat); + const char* inbuf = reinterpret_cast(mFrameData[0]); + output.insert(output.end(), inbuf, inbuf + got); + } } -} -size_t FFmpeg_Decoder::getSampleOffset() -{ - int delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) / - av_get_bytes_per_sample(mOutputSampleFormat); - return (int)(mNextPts*mCodecCtx->sample_rate) - delay; -} - -FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) - : Sound_Decoder(vfs) - , mFormatCtx(nullptr) - , mCodecCtx(nullptr) - , mStream(nullptr) - , mFrame(nullptr) - , mFrameSize(0) - , mFramePos(0) - , mNextPts(0.0) - , mSwr(nullptr) - , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) - , mOutputChannelLayout(0) - , mDataBuf(nullptr) - , mFrameData(nullptr) - , mDataBufLen(0) -{ - memset(&mPacket, 0, sizeof(mPacket)); - - /* We need to make sure ffmpeg is initialized. Optionally silence warning - * output from the lib */ - static bool done_init = false; - if(!done_init) + size_t FFmpeg_Decoder::getSampleOffset() { + int delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) + / av_get_bytes_per_sample(mOutputSampleFormat); + return (int)(mNextPts * mCodecCtx->sample_rate) - delay; + } + + FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) + : Sound_Decoder(vfs) + , mFormatCtx(nullptr) + , mCodecCtx(nullptr) + , mStream(nullptr) + , mFrame(nullptr) + , mFrameSize(0) + , mFramePos(0) + , mNextPts(0.0) + , mSwr(nullptr) + , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) + , mOutputChannelLayout(0) + , mDataBuf(nullptr) + , mFrameData(nullptr) + , mDataBufLen(0) + { + memset(&mPacket, 0, sizeof(mPacket)); + + /* We need to make sure ffmpeg is initialized. Optionally silence warning + * output from the lib */ + static bool done_init = false; + if (!done_init) + { // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 - av_register_all(); + av_register_all(); #endif - av_log_set_level(AV_LOG_ERROR); - done_init = true; + av_log_set_level(AV_LOG_ERROR); + done_init = true; + } } -} -FFmpeg_Decoder::~FFmpeg_Decoder() -{ - close(); -} + FFmpeg_Decoder::~FFmpeg_Decoder() + { + close(); + } } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 0a67a4758..88dd3316f 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -1,11 +1,11 @@ #ifndef GAME_SOUND_FFMPEG_DECODER_H #define GAME_SOUND_FFMPEG_DECODER_H -#include +#include #if defined(_MSC_VER) - #pragma warning (push) - #pragma warning (disable : 4244) +#pragma warning(push) +#pragma warning(disable : 4244) #endif extern "C" @@ -21,63 +21,61 @@ extern "C" } #if defined(_MSC_VER) - #pragma warning (pop) +#pragma warning(pop) #endif -#include +#include #include -#include #include "sound_decoder.hpp" - namespace MWSound { class FFmpeg_Decoder final : public Sound_Decoder { - AVFormatContext *mFormatCtx; - AVCodecContext *mCodecCtx; - AVStream **mStream; + AVFormatContext* mFormatCtx; + AVCodecContext* mCodecCtx; + AVStream** mStream; AVPacket mPacket; - AVFrame *mFrame; + AVFrame* mFrame; int mFrameSize; int mFramePos; double mNextPts; - SwrContext *mSwr; + SwrContext* mSwr; enum AVSampleFormat mOutputSampleFormat; int64_t mOutputChannelLayout; - uint8_t *mDataBuf; - uint8_t **mFrameData; + uint8_t* mDataBuf; + uint8_t** mFrameData; int mDataBufLen; bool getNextPacket(); Files::IStreamPtr mDataStream; - static int readPacket(void *user_data, uint8_t *buf, int buf_size); - static int writePacket(void *user_data, uint8_t *buf, int buf_size); - static int64_t seek(void *user_data, int64_t offset, int whence); + static int readPacket(void* user_data, uint8_t* buf, int buf_size); + static int writePacket(void* user_data, uint8_t* buf, int buf_size); + static int64_t seek(void* user_data, int64_t offset, int whence); bool getAVAudioData(); - size_t readAVAudioData(void *data, size_t length); + size_t readAVAudioData(void* data, size_t length); - void open(const std::string &fname) override; + void open(const std::string& fname) override; void close() override; std::string getName() override; - void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) override; + void getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) override; - size_t read(char *buffer, size_t bytes) override; - void readAll(std::vector &output) override; + 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); + FFmpeg_Decoder& operator=(const FFmpeg_Decoder& rhs); + FFmpeg_Decoder(const FFmpeg_Decoder& rhs); public: explicit FFmpeg_Decoder(const VFS::Manager* vfs); diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index ae31d6094..b1c1a3f2a 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -1,72 +1,68 @@ #include "loudness.hpp" -#include -#include #include - -#include "soundmanagerimp.hpp" +#include +#include +#include namespace MWSound { -void Sound_Loudness::analyzeLoudness(const std::vector< char >& data) -{ - mQueue.insert( mQueue.end(), data.begin(), data.end() ); - if (!mQueue.size()) - return; - - int samplesPerSegment = static_cast(mSampleRate / mSamplesPerSec); - int numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); - int advance = framesToBytes(1, mChannelConfig, mSampleType); - - - int segment=0; - int sample=0; - while (segment < numSamples/samplesPerSegment) + void Sound_Loudness::analyzeLoudness(const std::vector& data) { - float sum=0; - int samplesAdded = 0; - while (sample < numSamples && sample < (segment+1)*samplesPerSegment) + mQueue.insert(mQueue.end(), data.begin(), data.end()); + if (!mQueue.size()) + return; + + int samplesPerSegment = static_cast(mSampleRate / mSamplesPerSec); + int numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); + int advance = framesToBytes(1, mChannelConfig, mSampleType); + + int segment = 0; + int sample = 0; + while (segment < numSamples / samplesPerSegment) { - // get sample on a scale from -1 to 1 - float value = 0; - if (mSampleType == SampleType_UInt8) - value = ((char)(mQueue[sample*advance]^0x80))/128.f; - else if (mSampleType == SampleType_Int16) + float sum = 0; + int samplesAdded = 0; + while (sample < numSamples && sample < (segment + 1) * samplesPerSegment) { - value = *reinterpret_cast(&mQueue[sample*advance]); - value /= float(std::numeric_limits::max()); - } - else if (mSampleType == SampleType_Float32) - { - value = *reinterpret_cast(&mQueue[sample*advance]); - value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already. + // get sample on a scale from -1 to 1 + float value = 0; + if (mSampleType == SampleType_UInt8) + value = ((char)(mQueue[sample * advance] ^ 0x80)) / 128.f; + else if (mSampleType == SampleType_Int16) + { + value = *reinterpret_cast(&mQueue[sample * advance]); + value /= float(std::numeric_limits::max()); + } + else if (mSampleType == SampleType_Float32) + { + value = *reinterpret_cast(&mQueue[sample * advance]); + value = std::clamp(value, -1.f, 1.f); // Float samples *should* be scaled to [-1,1] already. + } + + sum += value * value; + ++samplesAdded; + ++sample; } - sum += value*value; - ++samplesAdded; - ++sample; + float rms = 0; // root mean square + if (samplesAdded > 0) + rms = std::sqrt(sum / samplesAdded); + mSamples.push_back(rms); + ++segment; } - float rms = 0; // root mean square - if (samplesAdded > 0) - rms = std::sqrt(sum / samplesAdded); - mSamples.push_back(rms); - ++segment; + mQueue.erase(mQueue.begin(), mQueue.begin() + sample * advance); } - mQueue.erase(mQueue.begin(), mQueue.begin() + sample*advance); -} + float Sound_Loudness::getLoudnessAtTime(float sec) const + { + if (mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) + return 0.0f; - -float Sound_Loudness::getLoudnessAtTime(float sec) const -{ - if(mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) - return 0.0f; - - size_t index = static_cast(sec * mSamplesPerSec); - index = std::max(0, std::min(index, mSamples.size()-1)); - return mSamples[index]; -} + size_t index = std::clamp(sec * mSamplesPerSec, 0, mSamples.size() - 1); + return mSamples[index]; + } } diff --git a/apps/openmw/mwsound/loudness.hpp b/apps/openmw/mwsound/loudness.hpp index 939d83dee..1800ec246 100644 --- a/apps/openmw/mwsound/loudness.hpp +++ b/apps/openmw/mwsound/loudness.hpp @@ -1,56 +1,59 @@ #ifndef GAME_SOUND_LOUDNESS_H #define GAME_SOUND_LOUDNESS_H -#include #include +#include #include "sound_decoder.hpp" namespace MWSound { -class Sound_Loudness { - float mSamplesPerSec; - int mSampleRate; - ChannelConfig mChannelConfig; - SampleType mSampleType; + class Sound_Loudness + { + float mSamplesPerSec; + int mSampleRate; + ChannelConfig mChannelConfig; + SampleType mSampleType; - // Loudness sample info - std::vector mSamples; + // Loudness sample info + std::vector mSamples; - std::deque mQueue; + std::deque mQueue; -public: - /** - * @param samplesPerSecond How many loudness values per second of audio to compute. - * @param sampleRate the sample rate of the sound buffer - * @param chans channel layout of the buffer - * @param type sample type of the buffer - */ - Sound_Loudness(float samplesPerSecond, int sampleRate, ChannelConfig chans, SampleType type) - : mSamplesPerSec(samplesPerSecond) - , mSampleRate(sampleRate) - , mChannelConfig(chans) - , mSampleType(type) - { } + public: + /** + * @param samplesPerSecond How many loudness values per second of audio to compute. + * @param sampleRate the sample rate of the sound buffer + * @param chans channel layout of the buffer + * @param type sample type of the buffer + */ + Sound_Loudness(float samplesPerSecond, int sampleRate, ChannelConfig chans, SampleType type) + : mSamplesPerSec(samplesPerSecond) + , mSampleRate(sampleRate) + , mChannelConfig(chans) + , mSampleType(type) + { + } - /** - * Analyzes the energy (closely related to loudness) of a sound buffer. - * The buffer will be divided into segments according to \a valuesPerSecond, - * and for each segment a loudness value in the range of [0,1] will be computed. - * The computed values are then added to the mSamples vector. This method should be called continuously - * with chunks of audio until the whole audio file is processed. - * If the size of \a data does not exactly fit a number of loudness samples, the remainder - * will be kept in the mQueue and used in the next call to analyzeLoudness. - * @param data the sound buffer to analyze, containing raw samples - */ - void analyzeLoudness(const std::vector& data); + /** + * Analyzes the energy (closely related to loudness) of a sound buffer. + * The buffer will be divided into segments according to \a valuesPerSecond, + * and for each segment a loudness value in the range of [0,1] will be computed. + * The computed values are then added to the mSamples vector. This method should be called continuously + * with chunks of audio until the whole audio file is processed. + * If the size of \a data does not exactly fit a number of loudness samples, the remainder + * will be kept in the mQueue and used in the next call to analyzeLoudness. + * @param data the sound buffer to analyze, containing raw samples + */ + void analyzeLoudness(const std::vector& data); - /** - * Get loudness at a particular time. Before calling this, the stream has to be analyzed up to that point in time (see analyzeLoudness()). - */ - float getLoudnessAtTime(float sec) const; -}; + /** + * Get loudness at a particular time. Before calling this, the stream has to be analyzed up to that point in + * time (see analyzeLoudness()). + */ + float getLoudnessAtTime(float sec) const; + }; } diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index d8c1c928e..1bb5275c4 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -7,7 +7,6 @@ #include "../mwbase/soundmanager.hpp" #include "sound_decoder.hpp" -#include "sound.hpp" namespace MWSound { @@ -25,78 +24,75 @@ namespace MWSound private: MWSound::MovieAudioDecoder* mDecoder; - void open(const std::string &fname) override; + 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; + 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 { public: - MovieAudioDecoder(Video::VideoState *videoState) - : Video::MovieAudioDecoder(videoState), mAudioTrack(nullptr) + MovieAudioDecoder(Video::VideoState* videoState) + : Video::MovieAudioDecoder(videoState) + , mAudioTrack(nullptr) + , mDecoderBridge(std::make_shared(this)) { - mDecoderBridge.reset(new MWSoundDecoderBridge(this)); } size_t getSampleOffset() { - ssize_t clock_delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) / - av_get_bytes_per_sample(mOutputSampleFormat); - return (size_t)(mAudioClock*mAudioContext->sample_rate) - clock_delay; + ssize_t clock_delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) + / av_get_bytes_per_sample(mOutputSampleFormat); + return (size_t)(mAudioClock * mAudioContext->sample_rate) - clock_delay; } - std::string getStreamName() - { - return std::string(); - } + std::string getStreamName() { return std::string(); } private: // MovieAudioDecoder overrides double getAudioClock() override { - return (double)getSampleOffset()/(double)mAudioContext->sample_rate - - MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack); + return (double)getSampleOffset() / (double)mAudioContext->sample_rate + - MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack); } void adjustAudioSettings(AVSampleFormat& sampleFormat, uint64_t& channelLayout, int& sampleRate) override { if (sampleFormat == AV_SAMPLE_FMT_U8P || sampleFormat == AV_SAMPLE_FMT_U8) sampleFormat = AV_SAMPLE_FMT_U8; - else if (sampleFormat == AV_SAMPLE_FMT_S16P || sampleFormat == AV_SAMPLE_FMT_S16) - sampleFormat = AV_SAMPLE_FMT_S16; - else if (sampleFormat == AV_SAMPLE_FMT_FLTP || sampleFormat == AV_SAMPLE_FMT_FLT) - sampleFormat = AV_SAMPLE_FMT_S16; // FIXME: check for AL_EXT_FLOAT32 support + // else if (sampleFormat == AV_SAMPLE_FMT_S16P || sampleFormat == AV_SAMPLE_FMT_S16) + // sampleFormat = AV_SAMPLE_FMT_S16; + // FIXME: check for AL_EXT_FLOAT32 support + // else if (sampleFormat == AV_SAMPLE_FMT_FLTP || sampleFormat == AV_SAMPLE_FMT_FLT) + // sampleFormat = AV_SAMPLE_FMT_S16; else sampleFormat = AV_SAMPLE_FMT_S16; if (channelLayout == AV_CH_LAYOUT_5POINT1 || channelLayout == AV_CH_LAYOUT_7POINT1 - || channelLayout == AV_CH_LAYOUT_QUAD) // FIXME: check for AL_EXT_MCFORMATS support + || channelLayout == AV_CH_LAYOUT_QUAD) // FIXME: check for AL_EXT_MCFORMATS support channelLayout = AV_CH_LAYOUT_STEREO; - else if (channelLayout != AV_CH_LAYOUT_MONO - && channelLayout != AV_CH_LAYOUT_STEREO) + else if (channelLayout != AV_CH_LAYOUT_MONO && channelLayout != AV_CH_LAYOUT_STEREO) channelLayout = AV_CH_LAYOUT_STEREO; } public: ~MovieAudioDecoder() { - if(mAudioTrack) + if (mAudioTrack) MWBase::Environment::get().getSoundManager()->stopTrack(mAudioTrack); mAudioTrack = nullptr; mDecoderBridge.reset(); } - MWBase::SoundStream *mAudioTrack; + MWBase::SoundStream* mAudioTrack; std::shared_ptr mDecoderBridge; }; - - void MWSoundDecoderBridge::open(const std::string &fname) + void MWSoundDecoderBridge::open(const std::string& fname) { throw std::runtime_error("Method not implemented"); } @@ -107,7 +103,7 @@ namespace MWSound return mDecoder->getStreamName(); } - void MWSoundDecoderBridge::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) + void MWSoundDecoderBridge::getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) { *samplerate = mDecoder->getOutputSampleRate(); @@ -123,8 +119,7 @@ namespace MWSound else if (outputChannelLayout == AV_CH_LAYOUT_QUAD) *chans = ChannelConfig_Quad; else - throw std::runtime_error("Unsupported channel layout: "+ - std::to_string(outputChannelLayout)); + throw std::runtime_error("Unsupported channel layout: " + std::to_string(outputChannelLayout)); AVSampleFormat outputSampleFormat = mDecoder->getOutputSampleFormat(); if (outputSampleFormat == AV_SAMPLE_FMT_U8) @@ -137,11 +132,11 @@ 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); } } - size_t MWSoundDecoderBridge::read(char *buffer, size_t bytes) + size_t MWSoundDecoderBridge::read(char* buffer, size_t bytes) { return mDecoder->read(buffer, bytes); } @@ -151,15 +146,13 @@ namespace MWSound return mDecoder->getSampleOffset(); } - - - std::shared_ptr MovieAudioFactory::createDecoder(Video::VideoState* videoState) + std::unique_ptr MovieAudioFactory::createDecoder(Video::VideoState* videoState) { - std::shared_ptr decoder(new MWSound::MovieAudioDecoder(videoState)); + auto decoder = std::make_unique(videoState); decoder->setupFormat(); - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - MWBase::SoundStream *sound = sndMgr->playTrack(decoder->mDecoderBridge, MWSound::Type::Movie); + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + MWBase::SoundStream* sound = sndMgr->playTrack(decoder->mDecoderBridge, MWSound::Type::Movie); if (!sound) { decoder.reset(); diff --git a/apps/openmw/mwsound/movieaudiofactory.hpp b/apps/openmw/mwsound/movieaudiofactory.hpp index 63b8fd7e9..0af1066af 100644 --- a/apps/openmw/mwsound/movieaudiofactory.hpp +++ b/apps/openmw/mwsound/movieaudiofactory.hpp @@ -8,7 +8,7 @@ namespace MWSound class MovieAudioFactory : public Video::MovieAudioFactory { - std::shared_ptr createDecoder(Video::VideoState* videoState) override; + std::unique_ptr createDecoder(Video::VideoState* videoState) override; }; } diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 67b52309d..363a0d06b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1,25 +1,27 @@ #include -#include -#include -#include #include #include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include -#include +#include #include #include +#include +#include #include -#include "openal_output.hpp" -#include "sound_decoder.hpp" -#include "sound.hpp" -#include "soundmanagerimp.hpp" #include "loudness.hpp" +#include "openal_output.hpp" +#include "sound.hpp" +#include "sound_decoder.hpp" +#include "soundmanagerimp.hpp" #include "efx-presets.h" @@ -27,1502 +29,1644 @@ #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif - #define MAKE_PTRID(id) ((void*)(uintptr_t)id) #define GET_PTRID(ptr) ((ALuint)(uintptr_t)ptr) namespace { -const int sLoudnessFPS = 20; // loudness values per second of audio + const int sLoudnessFPS = 20; // loudness values per second of audio -ALCenum checkALCError(ALCdevice *device, const char *func, int line) -{ - ALCenum err = alcGetError(device); - if(err != ALC_NO_ERROR) - Log(Debug::Error) << "ALC error "<< alcGetString(device, err) << " (" << err << ") @ " << func << ":" << line; - return err; -} + ALCenum checkALCError(ALCdevice* device, const char* func, int line) + { + ALCenum err = alcGetError(device); + if (err != ALC_NO_ERROR) + Log(Debug::Error) << "ALC error " << alcGetString(device, err) << " (" << err << ") @ " << func << ":" + << line; + return err; + } #define getALCError(d) checkALCError((d), __FUNCTION__, __LINE__) -ALenum checkALError(const char *func, int line) -{ - ALenum err = alGetError(); - if(err != AL_NO_ERROR) - Log(Debug::Error) << "AL error " << alGetString(err) << " (" << err << ") @ " << func << ":" << line; - return err; -} + ALenum checkALError(const char* func, int line) + { + ALenum err = alGetError(); + if (err != AL_NO_ERROR) + Log(Debug::Error) << "AL error " << alGetString(err) << " (" << err << ") @ " << func << ":" << line; + return err; + } #define getALError() checkALError(__FUNCTION__, __LINE__) -// Helper to get an OpenAL extension function -template -void convertPointer(T& dest, R src) -{ - memcpy(&dest, &src, sizeof(src)); -} - -template -void getALCFunc(T& func, ALCdevice *device, const char *name) -{ - void* funcPtr = alcGetProcAddress(device, name); - convertPointer(func, funcPtr); -} - -template -void getALFunc(T& func, const char *name) -{ - void* funcPtr = alGetProcAddress(name); - convertPointer(func, funcPtr); -} - -// Effect objects -LPALGENEFFECTS alGenEffects; -LPALDELETEEFFECTS alDeleteEffects; -LPALISEFFECT alIsEffect; -LPALEFFECTI alEffecti; -LPALEFFECTIV alEffectiv; -LPALEFFECTF alEffectf; -LPALEFFECTFV alEffectfv; -LPALGETEFFECTI alGetEffecti; -LPALGETEFFECTIV alGetEffectiv; -LPALGETEFFECTF alGetEffectf; -LPALGETEFFECTFV alGetEffectfv; -// Filter objects -LPALGENFILTERS alGenFilters; -LPALDELETEFILTERS alDeleteFilters; -LPALISFILTER alIsFilter; -LPALFILTERI alFilteri; -LPALFILTERIV alFilteriv; -LPALFILTERF alFilterf; -LPALFILTERFV alFilterfv; -LPALGETFILTERI alGetFilteri; -LPALGETFILTERIV alGetFilteriv; -LPALGETFILTERF alGetFilterf; -LPALGETFILTERFV alGetFilterfv; -// Auxiliary slot objects -LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; -LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; -LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; -LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; -LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; -LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; -LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; -LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; -LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; -LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; -LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; - - -void LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES &props) -{ - ALint type = AL_NONE; - alGetEffecti(effect, AL_EFFECT_TYPE, &type); - if(type == AL_EFFECT_EAXREVERB) + // Helper to get an OpenAL extension function + template + void convertPointer(T& dest, R src) { - alEffectf(effect, AL_EAXREVERB_DIFFUSION, props.flDiffusion); - alEffectf(effect, AL_EAXREVERB_DENSITY, props.flDensity); - alEffectf(effect, AL_EAXREVERB_GAIN, props.flGain); - alEffectf(effect, AL_EAXREVERB_GAINHF, props.flGainHF); - alEffectf(effect, AL_EAXREVERB_GAINLF, props.flGainLF); - alEffectf(effect, AL_EAXREVERB_DECAY_TIME, props.flDecayTime); - alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, props.flDecayHFRatio); - alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, props.flDecayLFRatio); - alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, props.flReflectionsGain); - alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, props.flReflectionsDelay); - alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, props.flReflectionsPan); - alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, props.flLateReverbGain); - alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, props.flLateReverbDelay); - alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, props.flLateReverbPan); - alEffectf(effect, AL_EAXREVERB_ECHO_TIME, props.flEchoTime); - alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, props.flEchoDepth); - alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, props.flModulationTime); - alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, props.flModulationDepth); - alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); - alEffectf(effect, AL_EAXREVERB_HFREFERENCE, props.flHFReference); - alEffectf(effect, AL_EAXREVERB_LFREFERENCE, props.flLFReference); - alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); - alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, props.iDecayHFLimit ? AL_TRUE : AL_FALSE); + memcpy(&dest, &src, sizeof(src)); } - else if(type == AL_EFFECT_REVERB) - { - alEffectf(effect, AL_REVERB_DIFFUSION, props.flDiffusion); - alEffectf(effect, AL_REVERB_DENSITY, props.flDensity); - alEffectf(effect, AL_REVERB_GAIN, props.flGain); - alEffectf(effect, AL_REVERB_GAINHF, props.flGainHF); - alEffectf(effect, AL_REVERB_DECAY_TIME, props.flDecayTime); - alEffectf(effect, AL_REVERB_DECAY_HFRATIO, props.flDecayHFRatio); - alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, props.flReflectionsGain); - alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, props.flReflectionsDelay); - alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, props.flLateReverbGain); - alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, props.flLateReverbDelay); - alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); - alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); - alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, props.iDecayHFLimit ? AL_TRUE : AL_FALSE); - } - getALError(); -} + template + void getALCFunc(T& func, ALCdevice* device, const char* name) + { + void* funcPtr = alcGetProcAddress(device, name); + convertPointer(func, funcPtr); + } + + template + void getALFunc(T& func, const char* name) + { + void* funcPtr = alGetProcAddress(name); + convertPointer(func, funcPtr); + } + + // Effect objects + LPALGENEFFECTS alGenEffects; + LPALDELETEEFFECTS alDeleteEffects; + LPALISEFFECT alIsEffect; + LPALEFFECTI alEffecti; + LPALEFFECTIV alEffectiv; + LPALEFFECTF alEffectf; + LPALEFFECTFV alEffectfv; + LPALGETEFFECTI alGetEffecti; + LPALGETEFFECTIV alGetEffectiv; + LPALGETEFFECTF alGetEffectf; + LPALGETEFFECTFV alGetEffectfv; + // Filter objects + LPALGENFILTERS alGenFilters; + LPALDELETEFILTERS alDeleteFilters; + LPALISFILTER alIsFilter; + LPALFILTERI alFilteri; + LPALFILTERIV alFilteriv; + LPALFILTERF alFilterf; + LPALFILTERFV alFilterfv; + LPALGETFILTERI alGetFilteri; + LPALGETFILTERIV alGetFilteriv; + LPALGETFILTERF alGetFilterf; + LPALGETFILTERFV alGetFilterfv; + // Auxiliary slot objects + LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; + LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; + LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; + LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; + LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; + LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; + LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; + LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; + LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; + LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; + LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; + + LPALEVENTCONTROLSOFT alEventControlSOFT; + LPALEVENTCALLBACKSOFT alEventCallbackSOFT; + LPALCREOPENDEVICESOFT alcReopenDeviceSOFT; + + void LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES& props) + { + ALint type = AL_NONE; + alGetEffecti(effect, AL_EFFECT_TYPE, &type); + if (type == AL_EFFECT_EAXREVERB) + { + alEffectf(effect, AL_EAXREVERB_DIFFUSION, props.flDiffusion); + alEffectf(effect, AL_EAXREVERB_DENSITY, props.flDensity); + alEffectf(effect, AL_EAXREVERB_GAIN, props.flGain); + alEffectf(effect, AL_EAXREVERB_GAINHF, props.flGainHF); + alEffectf(effect, AL_EAXREVERB_GAINLF, props.flGainLF); + alEffectf(effect, AL_EAXREVERB_DECAY_TIME, props.flDecayTime); + alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, props.flDecayHFRatio); + alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, props.flDecayLFRatio); + alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, props.flReflectionsGain); + alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, props.flReflectionsDelay); + alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, props.flReflectionsPan); + alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, props.flLateReverbGain); + alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, props.flLateReverbDelay); + alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, props.flLateReverbPan); + alEffectf(effect, AL_EAXREVERB_ECHO_TIME, props.flEchoTime); + alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, props.flEchoDepth); + alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, props.flModulationTime); + alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, props.flModulationDepth); + alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); + alEffectf(effect, AL_EAXREVERB_HFREFERENCE, props.flHFReference); + alEffectf(effect, AL_EAXREVERB_LFREFERENCE, props.flLFReference); + alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); + alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, props.iDecayHFLimit ? AL_TRUE : AL_FALSE); + } + else if (type == AL_EFFECT_REVERB) + { + alEffectf(effect, AL_REVERB_DIFFUSION, props.flDiffusion); + alEffectf(effect, AL_REVERB_DENSITY, props.flDensity); + alEffectf(effect, AL_REVERB_GAIN, props.flGain); + alEffectf(effect, AL_REVERB_GAINHF, props.flGainHF); + alEffectf(effect, AL_REVERB_DECAY_TIME, props.flDecayTime); + alEffectf(effect, AL_REVERB_DECAY_HFRATIO, props.flDecayHFRatio); + alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, props.flReflectionsGain); + alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, props.flReflectionsDelay); + alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, props.flLateReverbGain); + alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, props.flLateReverbDelay); + alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); + alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); + alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, props.iDecayHFLimit ? AL_TRUE : AL_FALSE); + } + getALError(); + } + + std::basic_string_view getDeviceName(ALCdevice* device) + { + const ALCchar* name = nullptr; + if (alcIsExtensionPresent(device, "ALC_ENUMERATE_ALL_EXT")) + name = alcGetString(device, ALC_ALL_DEVICES_SPECIFIER); + if (alcGetError(device) != AL_NO_ERROR || !name) + name = alcGetString(device, ALC_DEVICE_SPECIFIER); + if (name == nullptr) // Prevent assigning nullptr to std::string + return {}; + return name; + } } namespace MWSound { -static ALenum getALFormat(ChannelConfig chans, SampleType type) -{ - struct FormatEntry { - ALenum format; - ChannelConfig chans; - SampleType type; - }; - struct FormatEntryExt { - const char name[32]; - ChannelConfig chans; - SampleType type; - }; - static const std::array fmtlist{{ - { AL_FORMAT_MONO16, ChannelConfig_Mono, SampleType_Int16 }, - { AL_FORMAT_MONO8, ChannelConfig_Mono, SampleType_UInt8 }, - { AL_FORMAT_STEREO16, ChannelConfig_Stereo, SampleType_Int16 }, - { AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 }, - }}; - - for(auto &fmt : fmtlist) + static ALenum getALFormat(ChannelConfig chans, SampleType type) { - if(fmt.chans == chans && fmt.type == type) - return fmt.format; - } - - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) - { - static const std::array mcfmtlist{{ - { "AL_FORMAT_QUAD16", ChannelConfig_Quad, SampleType_Int16 }, - { "AL_FORMAT_QUAD8", ChannelConfig_Quad, SampleType_UInt8 }, - { "AL_FORMAT_51CHN16", ChannelConfig_5point1, SampleType_Int16 }, - { "AL_FORMAT_51CHN8", ChannelConfig_5point1, SampleType_UInt8 }, - { "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 }, - { "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 }, - }}; - - for(auto &fmt : mcfmtlist) + struct FormatEntry { - if(fmt.chans == chans && fmt.type == type) - { - ALenum format = alGetEnumValue(fmt.name); - if(format != 0 && format != -1) - return format; - } - } - } - if(alIsExtensionPresent("AL_EXT_FLOAT32")) - { - static const std::array fltfmtlist{{ - { "AL_FORMAT_MONO_FLOAT32", ChannelConfig_Mono, SampleType_Float32 }, - { "AL_FORMAT_STEREO_FLOAT32", ChannelConfig_Stereo, SampleType_Float32 }, - }}; - - for(auto &fmt : fltfmtlist) + ALenum format; + ChannelConfig chans; + SampleType type; + }; + struct FormatEntryExt { - if(fmt.chans == chans && fmt.type == type) - { - ALenum format = alGetEnumValue(fmt.name); - if(format != 0 && format != -1) - return format; - } + const char name[32]; + ChannelConfig chans; + SampleType type; + }; + static const std::array fmtlist{ { + { AL_FORMAT_MONO16, ChannelConfig_Mono, SampleType_Int16 }, + { AL_FORMAT_MONO8, ChannelConfig_Mono, SampleType_UInt8 }, + { AL_FORMAT_STEREO16, ChannelConfig_Stereo, SampleType_Int16 }, + { AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 }, + } }; + + for (auto& fmt : fmtlist) + { + if (fmt.chans == chans && fmt.type == type) + return fmt.format; } - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + if (alIsExtensionPresent("AL_EXT_MCFORMATS")) { - static const std::array fltmcfmtlist{{ - { "AL_FORMAT_QUAD32", ChannelConfig_Quad, SampleType_Float32 }, - { "AL_FORMAT_51CHN32", ChannelConfig_5point1, SampleType_Float32 }, - { "AL_FORMAT_71CHN32", ChannelConfig_7point1, SampleType_Float32 }, - }}; + static const std::array mcfmtlist{ { + { "AL_FORMAT_QUAD16", ChannelConfig_Quad, SampleType_Int16 }, + { "AL_FORMAT_QUAD8", ChannelConfig_Quad, SampleType_UInt8 }, + { "AL_FORMAT_51CHN16", ChannelConfig_5point1, SampleType_Int16 }, + { "AL_FORMAT_51CHN8", ChannelConfig_5point1, SampleType_UInt8 }, + { "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 }, + { "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 }, + } }; - for(auto &fmt : fltmcfmtlist) + for (auto& fmt : mcfmtlist) { - if(fmt.chans == chans && fmt.type == type) + if (fmt.chans == chans && fmt.type == type) { ALenum format = alGetEnumValue(fmt.name); - if(format != 0 && format != -1) + if (format != 0 && format != -1) return format; } } } - } - - Log(Debug::Warning) << "Unsupported sound format (" << getChannelConfigName(chans) << ", " << getSampleTypeName(type) << ")"; - return AL_NONE; -} - - -// -// A streaming OpenAL sound. -// -class OpenAL_SoundStream -{ - static const ALfloat sBufferLength; - -private: - ALuint mSource; - - std::array mBuffers; - ALint mCurrentBufIdx; - - ALenum mFormat; - ALsizei mSampleRate; - ALuint mBufferSize; - ALuint mFrameSize; - ALint mSilence; - - DecoderPtr mDecoder; - - std::unique_ptr mLoudnessAnalyzer; - - std::atomic mIsFinished; - - void updateAll(bool local); - - OpenAL_SoundStream(const OpenAL_SoundStream &rhs); - OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); - - friend class OpenAL_Output; - -public: - OpenAL_SoundStream(ALuint src, DecoderPtr decoder); - ~OpenAL_SoundStream(); - - bool init(bool getLoudnessData=false); - - bool isPlaying(); - double getStreamDelay() const; - double getStreamOffset() const; - - float getCurrentLoudness() const; - - bool process(); - ALint refillQueue(); -}; -const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; - - -// -// A background streaming thread (keeps active streams processed) -// -struct OpenAL_Output::StreamThread -{ - typedef std::vector StreamVec; - StreamVec mStreams; - - std::atomic mQuitNow; - std::mutex mMutex; - std::condition_variable mCondVar; - std::thread mThread; - - StreamThread() - : mQuitNow(false) - , mThread([this] { run(); }) - { - } - ~StreamThread() - { - mQuitNow = true; - mMutex.lock(); mMutex.unlock(); - mCondVar.notify_all(); - mThread.join(); - } - - // thread entry point - void run() - { - std::unique_lock lock(mMutex); - while(!mQuitNow) + if (alIsExtensionPresent("AL_EXT_FLOAT32")) { - StreamVec::iterator iter = mStreams.begin(); - while(iter != mStreams.end()) + static const std::array fltfmtlist{ { + { "AL_FORMAT_MONO_FLOAT32", ChannelConfig_Mono, SampleType_Float32 }, + { "AL_FORMAT_STEREO_FLOAT32", ChannelConfig_Stereo, SampleType_Float32 }, + } }; + + for (auto& fmt : fltfmtlist) { - if((*iter)->process() == false) - iter = mStreams.erase(iter); - else - ++iter; + if (fmt.chans == chans && fmt.type == type) + { + ALenum format = alGetEnumValue(fmt.name); + if (format != 0 && format != -1) + return format; + } } - mCondVar.wait_for(lock, std::chrono::milliseconds(50)); + if (alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + static const std::array fltmcfmtlist{ { + { "AL_FORMAT_QUAD32", ChannelConfig_Quad, SampleType_Float32 }, + { "AL_FORMAT_51CHN32", ChannelConfig_5point1, SampleType_Float32 }, + { "AL_FORMAT_71CHN32", ChannelConfig_7point1, SampleType_Float32 }, + } }; + + for (auto& fmt : fltmcfmtlist) + { + if (fmt.chans == chans && fmt.type == type) + { + ALenum format = alGetEnumValue(fmt.name); + if (format != 0 && format != -1) + return format; + } + } + } } + + Log(Debug::Warning) << "Unsupported sound format (" << getChannelConfigName(chans) << ", " + << getSampleTypeName(type) << ")"; + return AL_NONE; } - void add(OpenAL_SoundStream *stream) + // + // A streaming OpenAL sound. + // + class OpenAL_SoundStream { - std::lock_guard lock(mMutex); - if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end()) + static const ALfloat sBufferLength; + + private: + ALuint mSource; + + std::array mBuffers; + ALint mCurrentBufIdx; + + ALenum mFormat; + ALsizei mSampleRate; + ALuint mBufferSize; + ALuint mFrameSize; + ALint mSilence; + + DecoderPtr mDecoder; + + std::unique_ptr mLoudnessAnalyzer; + + std::atomic mIsFinished; + + void updateAll(bool local); + + OpenAL_SoundStream(const OpenAL_SoundStream& rhs); + OpenAL_SoundStream& operator=(const OpenAL_SoundStream& rhs); + + friend class OpenAL_Output; + + public: + OpenAL_SoundStream(ALuint src, DecoderPtr decoder); + ~OpenAL_SoundStream(); + + bool init(bool getLoudnessData = false); + + bool isPlaying(); + double getStreamDelay() const; + double getStreamOffset() const; + + float getCurrentLoudness() const; + + bool process(); + ALint refillQueue(); + }; + const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; + + // + // A background streaming thread (keeps active streams processed) + // + struct OpenAL_Output::StreamThread + { + std::vector mStreams; + + std::atomic mQuitNow; + std::mutex mMutex; + std::condition_variable mCondVar; + std::thread mThread; + + StreamThread() + : mQuitNow(false) + , mThread([this] { run(); }) { - mStreams.push_back(stream); + } + ~StreamThread() + { + mQuitNow = true; + mMutex.lock(); + mMutex.unlock(); mCondVar.notify_all(); + mThread.join(); } - } - void remove(OpenAL_SoundStream *stream) + // thread entry point + void run() + { + std::unique_lock lock(mMutex); + while (!mQuitNow) + { + auto iter = mStreams.begin(); + while (iter != mStreams.end()) + { + if ((*iter)->process() == false) + iter = mStreams.erase(iter); + else + ++iter; + } + + mCondVar.wait_for(lock, std::chrono::milliseconds(50)); + } + } + + void add(OpenAL_SoundStream* stream) + { + std::lock_guard lock(mMutex); + if (std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end()) + { + mStreams.push_back(stream); + mCondVar.notify_all(); + } + } + + void remove(OpenAL_SoundStream* stream) + { + std::lock_guard lock(mMutex); + auto iter = std::find(mStreams.begin(), mStreams.end(), stream); + if (iter != mStreams.end()) + mStreams.erase(iter); + } + + void removeAll() + { + std::lock_guard lock(mMutex); + mStreams.clear(); + } + + StreamThread(const StreamThread& rhs) = delete; + StreamThread& operator=(const StreamThread& rhs) = delete; + }; + + class OpenAL_Output::DefaultDeviceThread { - std::lock_guard lock(mMutex); - StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream); - if(iter != mStreams.end()) mStreams.erase(iter); - } + public: + std::basic_string mCurrentName; - void removeAll() + private: + OpenAL_Output& mOutput; + + std::atomic mQuitNow; + std::mutex mMutex; + std::condition_variable mCondVar; + std::thread mThread; + + DefaultDeviceThread(const DefaultDeviceThread&) = delete; + DefaultDeviceThread& operator=(const DefaultDeviceThread&) = delete; + + void run() + { + Misc::setCurrentThreadIdlePriority(); + std::unique_lock lock(mMutex); + while (!mQuitNow) + { + { + const std::lock_guard openLock(mOutput.mReopenMutex); + auto defaultName = getDeviceName(nullptr); + if (mCurrentName != defaultName) + { + Log(Debug::Info) << "Default audio device changed"; + ALCboolean reopened + = alcReopenDeviceSOFT(mOutput.mDevice, nullptr, mOutput.mContextAttributes.data()); + if (reopened == AL_FALSE) + { + mCurrentName = defaultName; + Log(Debug::Warning) << "Failed to switch to new audio device"; + } + else + mCurrentName = getDeviceName(mOutput.mDevice); + } + } + mCondVar.wait_for(lock, std::chrono::seconds(2)); + } + } + + public: + DefaultDeviceThread(OpenAL_Output& output, std::basic_string_view name = {}) + : mCurrentName(name) + , mOutput(output) + , mQuitNow(false) + , mThread([this] { run(); }) + { + } + + ~DefaultDeviceThread() + { + mQuitNow = true; + mMutex.lock(); + mMutex.unlock(); + mCondVar.notify_all(); + mThread.join(); + } + }; + + OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) + : mSource(src) + , mCurrentBufIdx(0) + , mFormat(AL_NONE) + , mSampleRate(0) + , mBufferSize(0) + , mFrameSize(0) + , mSilence(0) + , mDecoder(std::move(decoder)) + , mLoudnessAnalyzer(nullptr) + , mIsFinished(true) { - std::lock_guard lock(mMutex); - mStreams.clear(); + mBuffers.fill(0); } -private: - StreamThread(const StreamThread &rhs); - StreamThread& operator=(const StreamThread &rhs); -}; - - -OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) - : mSource(src), mCurrentBufIdx(0), mFormat(AL_NONE), mSampleRate(0) - , mBufferSize(0), mFrameSize(0), mSilence(0), mDecoder(std::move(decoder)) - , mLoudnessAnalyzer(nullptr), mIsFinished(true) -{ - mBuffers.fill(0); -} - -OpenAL_SoundStream::~OpenAL_SoundStream() -{ - if(mBuffers[0] && alIsBuffer(mBuffers[0])) - alDeleteBuffers(mBuffers.size(), mBuffers.data()); - alGetError(); - - mDecoder->close(); -} - -bool OpenAL_SoundStream::init(bool getLoudnessData) -{ - alGenBuffers(mBuffers.size(), mBuffers.data()); - ALenum err = getALError(); - if(err != AL_NO_ERROR) - return false; - - ChannelConfig chans; - SampleType type; - - try { - mDecoder->getInfo(&mSampleRate, &chans, &type); - mFormat = getALFormat(chans, type); - } - catch(std::exception &e) + OpenAL_SoundStream::~OpenAL_SoundStream() { - Log(Debug::Error) << "Failed to get stream info: " << e.what(); - return false; + if (mBuffers[0] && alIsBuffer(mBuffers[0])) + alDeleteBuffers(mBuffers.size(), mBuffers.data()); + alGetError(); + + mDecoder->close(); } - switch(type) + bool OpenAL_SoundStream::init(bool getLoudnessData) { - case SampleType_UInt8: mSilence = 0x80; break; - case SampleType_Int16: mSilence = 0x00; break; - case SampleType_Float32: mSilence = 0x00; break; - } + alGenBuffers(mBuffers.size(), mBuffers.data()); + ALenum err = getALError(); + if (err != AL_NO_ERROR) + return false; - mFrameSize = framesToBytes(1, chans, type); - mBufferSize = static_cast(sBufferLength*mSampleRate); - mBufferSize *= mFrameSize; + ChannelConfig chans; + SampleType type; - if (getLoudnessData) - mLoudnessAnalyzer.reset(new Sound_Loudness(sLoudnessFPS, mSampleRate, chans, type)); + try + { + mDecoder->getInfo(&mSampleRate, &chans, &type); + mFormat = getALFormat(chans, type); + } + catch (std::exception& e) + { + Log(Debug::Error) << "Failed to get stream info: " << e.what(); + return false; + } - mIsFinished = false; - return true; -} + switch (type) + { + case SampleType_UInt8: + mSilence = 0x80; + break; + case SampleType_Int16: + mSilence = 0x00; + break; + case SampleType_Float32: + mSilence = 0x00; + break; + } -bool OpenAL_SoundStream::isPlaying() -{ - ALint state; + mFrameSize = framesToBytes(1, chans, type); + mBufferSize = static_cast(sBufferLength * mSampleRate); + mBufferSize *= mFrameSize; - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - getALError(); + if (getLoudnessData) + mLoudnessAnalyzer = std::make_unique(sLoudnessFPS, mSampleRate, chans, type); - if(state == AL_PLAYING || state == AL_PAUSED) + mIsFinished = false; return true; - return !mIsFinished; -} - -double OpenAL_SoundStream::getStreamDelay() const -{ - ALint state = AL_STOPPED; - double d = 0.0; - ALint offset; - - alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - if(state == AL_PLAYING || state == AL_PAUSED) - { - ALint queued; - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - ALint inqueue = mBufferSize/mFrameSize*queued - offset; - d = (double)inqueue / (double)mSampleRate; } - getALError(); - return d; -} - -double OpenAL_SoundStream::getStreamOffset() const -{ - ALint state = AL_STOPPED; - ALint offset; - double t; - - alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - if(state == AL_PLAYING || state == AL_PAUSED) + bool OpenAL_SoundStream::isPlaying() { - ALint queued; - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - ALint inqueue = mBufferSize/mFrameSize*queued - offset; - t = (double)(mDecoder->getSampleOffset() - inqueue) / (double)mSampleRate; - } - else - { - /* Underrun, or not started yet. The decoder offset is where we'll play - * next. */ - t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; + ALint state; + + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + getALError(); + + if (state == AL_PLAYING || state == AL_PAUSED) + return true; + return !mIsFinished; } - getALError(); - return t; -} + double OpenAL_SoundStream::getStreamDelay() const + { + ALint state = AL_STOPPED; + double d = 0.0; + ALint offset; -float OpenAL_SoundStream::getCurrentLoudness() const -{ - if (!mLoudnessAnalyzer.get()) - return 0.f; - - float time = getStreamOffset(); - return mLoudnessAnalyzer->getLoudnessAtTime(time); -} - -bool OpenAL_SoundStream::process() -{ - try { - if(refillQueue() > 0) + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING || state == AL_PAUSED) { - ALint state; - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - if(state != AL_PLAYING && state != AL_PAUSED) - { - // Ensure all processed buffers are removed so we don't replay them. - refillQueue(); + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + ALint inqueue = mBufferSize / mFrameSize * queued - offset; + d = (double)inqueue / (double)mSampleRate; + } - alSourcePlay(mSource); + getALError(); + return d; + } + + double OpenAL_SoundStream::getStreamOffset() const + { + ALint state = AL_STOPPED; + ALint offset; + double t; + + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING || state == AL_PAUSED) + { + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + ALint inqueue = mBufferSize / mFrameSize * queued - offset; + t = (double)(mDecoder->getSampleOffset() - inqueue) / (double)mSampleRate; + } + else + { + /* Underrun, or not started yet. The decoder offset is where we'll play + * next. */ + t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; + } + + getALError(); + return t; + } + + float OpenAL_SoundStream::getCurrentLoudness() const + { + if (!mLoudnessAnalyzer.get()) + return 0.f; + + float time = getStreamOffset(); + return mLoudnessAnalyzer->getLoudnessAtTime(time); + } + + bool OpenAL_SoundStream::process() + { + try + { + if (refillQueue() > 0) + { + ALint state; + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if (state != AL_PLAYING && state != AL_PAUSED) + { + // Ensure all processed buffers are removed so we don't replay them. + refillQueue(); + + alSourcePlay(mSource); + } } } - } - catch(std::exception&) { - Log(Debug::Error) << "Error updating stream \"" << mDecoder->getName() << "\""; - mIsFinished = true; - } - return !mIsFinished; -} - -ALint OpenAL_SoundStream::refillQueue() -{ - ALint processed; - alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); - while(processed > 0) - { - ALuint buf; - alSourceUnqueueBuffers(mSource, 1, &buf); - --processed; - } - - ALint queued; - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - if(!mIsFinished && (ALuint)queued < mBuffers.size()) - { - std::vector data(mBufferSize); - for(;!mIsFinished && (ALuint)queued < mBuffers.size();++queued) + catch (std::exception&) { - size_t got = mDecoder->read(data.data(), data.size()); - if(got < data.size()) - { - mIsFinished = true; - std::fill(data.begin()+got, data.end(), mSilence); - } - if(got > 0) - { - if (mLoudnessAnalyzer.get()) - mLoudnessAnalyzer->analyzeLoudness(data); + Log(Debug::Error) << "Error updating stream \"" << mDecoder->getName() << "\""; + mIsFinished = true; + } + return !mIsFinished; + } - ALuint bufid = mBuffers[mCurrentBufIdx]; - alBufferData(bufid, mFormat, data.data(), data.size(), mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - mCurrentBufIdx = (mCurrentBufIdx+1) % mBuffers.size(); + ALint OpenAL_SoundStream::refillQueue() + { + ALint processed; + alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); + while (processed > 0) + { + ALuint buf; + alSourceUnqueueBuffers(mSource, 1, &buf); + --processed; + } + + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + if (!mIsFinished && (ALuint)queued < mBuffers.size()) + { + std::vector data(mBufferSize); + for (; !mIsFinished && (ALuint)queued < mBuffers.size(); ++queued) + { + size_t got = mDecoder->read(data.data(), data.size()); + if (got < data.size()) + { + mIsFinished = true; + std::fill(data.begin() + got, data.end(), mSilence); + } + if (got > 0) + { + if (mLoudnessAnalyzer.get()) + mLoudnessAnalyzer->analyzeLoudness(data); + + ALuint bufid = mBuffers[mCurrentBufIdx]; + alBufferData(bufid, mFormat, data.data(), data.size(), mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); + mCurrentBufIdx = (mCurrentBufIdx + 1) % mBuffers.size(); + } } } + + return queued; + } + + // + // An OpenAL output device + // + std::vector OpenAL_Output::enumerate() + { + std::vector devlist; + const ALCchar* devnames; + + if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) + devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + else + devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + while (devnames && *devnames) + { + devlist.emplace_back(devnames); + devnames += strlen(devnames) + 1; + } + return devlist; + } + + void OpenAL_Output::eventCallback( + ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar* message, void* userParam) + { + if (eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) + static_cast(userParam)->onDisconnect(); + } + + void OpenAL_Output::onDisconnect() + { + if (!mInitialized || !alcReopenDeviceSOFT) + return; + const std::lock_guard lock(mReopenMutex); + Log(Debug::Warning) << "Audio device disconnected, attempting to reopen..."; + ALCboolean reopened = alcReopenDeviceSOFT(mDevice, mDeviceName.c_str(), mContextAttributes.data()); + if (reopened == AL_FALSE && !mDeviceName.empty()) + { + reopened = alcReopenDeviceSOFT(mDevice, nullptr, mContextAttributes.data()); + if (reopened == AL_TRUE && !mDefaultDeviceThread) + mDefaultDeviceThread = std::make_unique(*this); + } + if (reopened == AL_FALSE) + Log(Debug::Error) << "Failed to reopen audio device"; + else + { + Log(Debug::Info) << "Reopened audio device"; + if (mDefaultDeviceThread) + mDefaultDeviceThread->mCurrentName = getDeviceName(mDevice); + } } - return queued; -} - - -// -// An OpenAL output device -// -std::vector OpenAL_Output::enumerate() -{ - std::vector devlist; - const ALCchar *devnames; - - if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) - devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); - else - devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); - while(devnames && *devnames) + bool OpenAL_Output::init(const std::string& devname, const std::string& hrtfname, HrtfMode hrtfmode) { - devlist.emplace_back(devnames); - devnames += strlen(devnames)+1; - } - return devlist; -} + deinit(); + std::lock_guard lock(mReopenMutex); -bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode) -{ - deinit(); + Log(Debug::Info) << "Initializing OpenAL..."; - Log(Debug::Info) << "Initializing OpenAL..."; + mDeviceName = devname; + mDevice = alcOpenDevice(devname.c_str()); + if (!mDevice && !devname.empty()) + { + Log(Debug::Warning) << "Failed to open \"" << devname << "\", trying default"; + mDevice = alcOpenDevice(nullptr); + mDeviceName.clear(); + } - mDevice = alcOpenDevice(devname.c_str()); - if(!mDevice && !devname.empty()) - { - Log(Debug::Warning) << "Failed to open \"" << devname << "\", trying default"; - mDevice = alcOpenDevice(nullptr); + if (!mDevice) + { + Log(Debug::Error) << "Failed to open default audio device"; + return false; + } + + auto name = getDeviceName(mDevice); + Log(Debug::Info) << "Opened \"" << name << "\""; + + ALCint major = 0, minor = 0; + alcGetIntegerv(mDevice, ALC_MAJOR_VERSION, 1, &major); + alcGetIntegerv(mDevice, ALC_MINOR_VERSION, 1, &minor); + Log(Debug::Info) << " ALC Version: " << major << "." << minor << "\n" + << " ALC Extensions: " << alcGetString(mDevice, ALC_EXTENSIONS); + + ALC.EXT_EFX = alcIsExtensionPresent(mDevice, "ALC_EXT_EFX"); + ALC.SOFT_HRTF = alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF"); + + mContextAttributes.clear(); + mContextAttributes.reserve(15); + if (ALC.SOFT_HRTF) + { + LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; + getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); + + mContextAttributes.push_back(ALC_HRTF_SOFT); + mContextAttributes.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE + : hrtfmode == HrtfMode::Enable ? ALC_TRUE + : + /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); + if (!hrtfname.empty()) + { + ALCint index = -1; + ALCint num_hrtf; + alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); + for (ALCint i = 0; i < num_hrtf; ++i) + { + const ALCchar* entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); + if (hrtfname == entry) + { + index = i; + break; + } + } + + if (index < 0) + Log(Debug::Warning) << "Failed to find HRTF \"" << hrtfname << "\", using default"; + else + { + mContextAttributes.push_back(ALC_HRTF_ID_SOFT); + mContextAttributes.push_back(index); + } + } + } + mContextAttributes.push_back(0); + + mContext = alcCreateContext(mDevice, mContextAttributes.data()); + if (!mContext || alcMakeContextCurrent(mContext) == ALC_FALSE) + { + Log(Debug::Error) << "Failed to setup audio context: " << alcGetString(mDevice, alcGetError(mDevice)); + if (mContext) + alcDestroyContext(mContext); + mContext = nullptr; + alcCloseDevice(mDevice); + mDevice = nullptr; + return false; + } + + Log(Debug::Info) << " Vendor: " << alGetString(AL_VENDOR) << "\n" + << " Renderer: " << alGetString(AL_RENDERER) << "\n" + << " Version: " << alGetString(AL_VERSION) << "\n" + << " Extensions: " << alGetString(AL_EXTENSIONS); + + if (alIsExtensionPresent("AL_SOFT_events")) + { + getALFunc(alEventControlSOFT, "alEventControlSOFT"); + getALFunc(alEventCallbackSOFT, "alEventCallbackSOFT"); + } + if (alcIsExtensionPresent(mDevice, "ALC_SOFT_reopen_device")) + getALFunc(alcReopenDeviceSOFT, "alcReopenDeviceSOFT"); + if (alEventControlSOFT) + { + static const std::array events{ { AL_EVENT_TYPE_DISCONNECTED_SOFT } }; + alEventControlSOFT(events.size(), events.data(), AL_TRUE); + alEventCallbackSOFT(&OpenAL_Output::eventCallback, this); + } + else + Log(Debug::Warning) << "Cannot detect audio device changes"; + if (mDeviceName.empty() && !name.empty()) + { + // If we opened the default device, switch devices if a new default is selected + if (alcReopenDeviceSOFT) + mDefaultDeviceThread = std::make_unique(*this, name); + else + Log(Debug::Warning) << "Cannot switch audio devices if the default changes"; + } + + if (!ALC.SOFT_HRTF) + Log(Debug::Warning) << "HRTF status unavailable"; + else + { + ALCint hrtf_state; + alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); + if (!hrtf_state) + Log(Debug::Info) << "HRTF disabled"; + else + { + const ALCchar* hrtf = alcGetString(mDevice, ALC_HRTF_SPECIFIER_SOFT); + Log(Debug::Info) << "Enabled HRTF " << hrtf; + } + } + + AL.SOFT_source_spatialize = alIsExtensionPresent("AL_SOFT_source_spatialize"); + + ALCuint maxtotal; + ALCint maxmono = 0, maxstereo = 0; + alcGetIntegerv(mDevice, ALC_MONO_SOURCES, 1, &maxmono); + alcGetIntegerv(mDevice, ALC_STEREO_SOURCES, 1, &maxstereo); + if (getALCError(mDevice) != ALC_NO_ERROR) + maxtotal = 256; + else + { + maxtotal = std::min(maxmono + maxstereo, 256); + if (maxtotal == 0) // workaround for broken implementations + maxtotal = 256; + } + for (size_t i = 0; i < maxtotal; i++) + { + ALuint src = 0; + alGenSources(1, &src); + if (alGetError() != AL_NO_ERROR) + break; + mFreeSources.push_back(src); + } + if (mFreeSources.empty()) + { + Log(Debug::Warning) << "Could not allocate any sound sourcess"; + alcMakeContextCurrent(nullptr); + alcDestroyContext(mContext); + mContext = nullptr; + alcCloseDevice(mDevice); + mDevice = nullptr; + return false; + } + Log(Debug::Info) << "Allocated " << mFreeSources.size() << " sound sources"; + + if (ALC.EXT_EFX) + { +#define LOAD_FUNC(x) getALFunc(x, #x) + LOAD_FUNC(alGenEffects); + LOAD_FUNC(alDeleteEffects); + LOAD_FUNC(alIsEffect); + LOAD_FUNC(alEffecti); + LOAD_FUNC(alEffectiv); + LOAD_FUNC(alEffectf); + LOAD_FUNC(alEffectfv); + LOAD_FUNC(alGetEffecti); + LOAD_FUNC(alGetEffectiv); + LOAD_FUNC(alGetEffectf); + LOAD_FUNC(alGetEffectfv); + LOAD_FUNC(alGenFilters); + LOAD_FUNC(alDeleteFilters); + LOAD_FUNC(alIsFilter); + LOAD_FUNC(alFilteri); + LOAD_FUNC(alFilteriv); + LOAD_FUNC(alFilterf); + LOAD_FUNC(alFilterfv); + LOAD_FUNC(alGetFilteri); + LOAD_FUNC(alGetFilteriv); + LOAD_FUNC(alGetFilterf); + LOAD_FUNC(alGetFilterfv); + LOAD_FUNC(alGenAuxiliaryEffectSlots); + LOAD_FUNC(alDeleteAuxiliaryEffectSlots); + LOAD_FUNC(alIsAuxiliaryEffectSlot); + LOAD_FUNC(alAuxiliaryEffectSloti); + LOAD_FUNC(alAuxiliaryEffectSlotiv); + LOAD_FUNC(alAuxiliaryEffectSlotf); + LOAD_FUNC(alAuxiliaryEffectSlotfv); + LOAD_FUNC(alGetAuxiliaryEffectSloti); + LOAD_FUNC(alGetAuxiliaryEffectSlotiv); + LOAD_FUNC(alGetAuxiliaryEffectSlotf); + LOAD_FUNC(alGetAuxiliaryEffectSlotfv); +#undef LOAD_FUNC + if (getALError() != AL_NO_ERROR) + { + ALC.EXT_EFX = false; + goto skip_efx; + } + + alGenFilters(1, &mWaterFilter); + if (alGetError() == AL_NO_ERROR) + { + alFilteri(mWaterFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + if (alGetError() == AL_NO_ERROR) + { + Log(Debug::Info) << "Low-pass filter supported"; + alFilterf(mWaterFilter, AL_LOWPASS_GAIN, 0.9f); + alFilterf(mWaterFilter, AL_LOWPASS_GAINHF, 0.125f); + } + else + { + alDeleteFilters(1, &mWaterFilter); + mWaterFilter = 0; + } + alGetError(); + } + + alGenAuxiliaryEffectSlots(1, &mEffectSlot); + alGetError(); + + alGenEffects(1, &mDefaultEffect); + if (alGetError() == AL_NO_ERROR) + { + alEffecti(mDefaultEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + if (alGetError() == AL_NO_ERROR) + Log(Debug::Info) << "EAX Reverb supported"; + else + { + alEffecti(mDefaultEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); + if (alGetError() == AL_NO_ERROR) + Log(Debug::Info) << "Standard Reverb supported"; + } + EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_LIVINGROOM; + props.flGain = 0.0f; + LoadEffect(mDefaultEffect, props); + } + + alGenEffects(1, &mWaterEffect); + if (alGetError() == AL_NO_ERROR) + { + alEffecti(mWaterEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + if (alGetError() != AL_NO_ERROR) + { + alEffecti(mWaterEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); + alGetError(); + } + LoadEffect(mWaterEffect, EFX_REVERB_PRESET_UNDERWATER); + } + + alListenerf(AL_METERS_PER_UNIT, 1.0f / Constants::UnitsPerMeter); + } + skip_efx: + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + // Speed of sound is in units per second. Take the sound speed in air (assumed + // meters per second), multiply by the units per meter to get the speed in u/s. + alSpeedOfSound(Constants::SoundSpeedInAir * Constants::UnitsPerMeter); + alGetError(); + + mInitialized = true; + return true; } - if(!mDevice) + void OpenAL_Output::deinit() { - Log(Debug::Error) << "Failed to open default audio device"; - return false; + mStreamThread->removeAll(); + mDefaultDeviceThread.reset(); + + for (ALuint source : mFreeSources) + alDeleteSources(1, &source); + mFreeSources.clear(); + + if (mEffectSlot) + alDeleteAuxiliaryEffectSlots(1, &mEffectSlot); + mEffectSlot = 0; + if (mDefaultEffect) + alDeleteEffects(1, &mDefaultEffect); + mDefaultEffect = 0; + if (mWaterEffect) + alDeleteEffects(1, &mWaterEffect); + mWaterEffect = 0; + if (mWaterFilter) + alDeleteFilters(1, &mWaterFilter); + mWaterFilter = 0; + + if (alEventCallbackSOFT) + alEventCallbackSOFT(nullptr, nullptr); + + alcMakeContextCurrent(nullptr); + if (mContext) + alcDestroyContext(mContext); + mContext = nullptr; + if (mDevice) + alcCloseDevice(mDevice); + mDevice = nullptr; + + mInitialized = false; } - const ALCchar *name = nullptr; - if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT")) - name = alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER); - if(alcGetError(mDevice) != AL_NO_ERROR || !name) - name = alcGetString(mDevice, ALC_DEVICE_SPECIFIER); - Log(Debug::Info) << "Opened \"" << name << "\""; - - ALCint major=0, minor=0; - alcGetIntegerv(mDevice, ALC_MAJOR_VERSION, 1, &major); - alcGetIntegerv(mDevice, ALC_MINOR_VERSION, 1, &minor); - Log(Debug::Info) << " ALC Version: " << major << "." << minor <<"\n" << - " ALC Extensions: " << alcGetString(mDevice, ALC_EXTENSIONS); - - ALC.EXT_EFX = alcIsExtensionPresent(mDevice, "ALC_EXT_EFX"); - ALC.SOFT_HRTF = alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF"); - - std::vector attrs; - attrs.reserve(15); - if(ALC.SOFT_HRTF) + std::vector OpenAL_Output::enumerateHrtf() { + std::vector ret; + + if (!mDevice || !ALC.SOFT_HRTF) + return ret; + LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); + ALCint num_hrtf; + alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); + ret.reserve(num_hrtf); + for (ALCint i = 0; i < num_hrtf; ++i) + { + const ALCchar* entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); + ret.emplace_back(entry); + } + + return ret; + } + + void OpenAL_Output::setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) + { + if (!mDevice || !ALC.SOFT_HRTF) + { + Log(Debug::Info) << "HRTF extension not present"; + return; + } + + LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; + getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); + + LPALCRESETDEVICESOFT alcResetDeviceSOFT = nullptr; + getALCFunc(alcResetDeviceSOFT, mDevice, "alcResetDeviceSOFT"); + + std::vector attrs; + attrs.reserve(15); + attrs.push_back(ALC_HRTF_SOFT); - attrs.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE : - hrtfmode == HrtfMode::Enable ? ALC_TRUE : - /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); - if(!hrtfname.empty()) + attrs.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE + : hrtfmode == HrtfMode::Enable ? ALC_TRUE + : + /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); + if (!hrtfname.empty()) { ALCint index = -1; ALCint num_hrtf; alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); - for(ALCint i = 0;i < num_hrtf;++i) + for (ALCint i = 0; i < num_hrtf; ++i) { - const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); - if(hrtfname == entry) + const ALCchar* entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); + if (hrtfname == entry) { index = i; break; } } - if(index < 0) - Log(Debug::Warning) << "Failed to find HRTF \"" << hrtfname << "\", using default"; + if (index < 0) + Log(Debug::Warning) << "Failed to find HRTF name \"" << hrtfname << "\", using default"; else { attrs.push_back(ALC_HRTF_ID_SOFT); attrs.push_back(index); } } - } - attrs.push_back(0); + attrs.push_back(0); + alcResetDeviceSOFT(mDevice, attrs.data()); - mContext = alcCreateContext(mDevice, attrs.data()); - if(!mContext || alcMakeContextCurrent(mContext) == ALC_FALSE) - { - Log(Debug::Error) << "Failed to setup audio context: "< OpenAL_Output::loadSound(const std::string& fname) { - maxtotal = std::min(maxmono+maxstereo, 256); - if (maxtotal == 0) // workaround for broken implementations - maxtotal = 256; - } - for(size_t i = 0;i < maxtotal;i++) - { - ALuint src = 0; - alGenSources(1, &src); - if(alGetError() != AL_NO_ERROR) - break; - mFreeSources.push_back(src); - } - if(mFreeSources.empty()) - { - Log(Debug::Warning) << "Could not allocate any sound sourcess"; - alcMakeContextCurrent(nullptr); - alcDestroyContext(mContext); - mContext = nullptr; - alcCloseDevice(mDevice); - mDevice = nullptr; - return false; - } - Log(Debug::Info) << "Allocated " << mFreeSources.size() << " sound sources"; - - if(ALC.EXT_EFX) - { -#define LOAD_FUNC(x) getALFunc(x, #x) - LOAD_FUNC(alGenEffects); - LOAD_FUNC(alDeleteEffects); - LOAD_FUNC(alIsEffect); - LOAD_FUNC(alEffecti); - LOAD_FUNC(alEffectiv); - LOAD_FUNC(alEffectf); - LOAD_FUNC(alEffectfv); - LOAD_FUNC(alGetEffecti); - LOAD_FUNC(alGetEffectiv); - LOAD_FUNC(alGetEffectf); - LOAD_FUNC(alGetEffectfv); - LOAD_FUNC(alGenFilters); - LOAD_FUNC(alDeleteFilters); - LOAD_FUNC(alIsFilter); - LOAD_FUNC(alFilteri); - LOAD_FUNC(alFilteriv); - LOAD_FUNC(alFilterf); - LOAD_FUNC(alFilterfv); - LOAD_FUNC(alGetFilteri); - LOAD_FUNC(alGetFilteriv); - LOAD_FUNC(alGetFilterf); - LOAD_FUNC(alGetFilterfv); - LOAD_FUNC(alGenAuxiliaryEffectSlots); - LOAD_FUNC(alDeleteAuxiliaryEffectSlots); - LOAD_FUNC(alIsAuxiliaryEffectSlot); - LOAD_FUNC(alAuxiliaryEffectSloti); - LOAD_FUNC(alAuxiliaryEffectSlotiv); - LOAD_FUNC(alAuxiliaryEffectSlotf); - LOAD_FUNC(alAuxiliaryEffectSlotfv); - LOAD_FUNC(alGetAuxiliaryEffectSloti); - LOAD_FUNC(alGetAuxiliaryEffectSlotiv); - LOAD_FUNC(alGetAuxiliaryEffectSlotf); - LOAD_FUNC(alGetAuxiliaryEffectSlotfv); -#undef LOAD_FUNC - if(getALError() != AL_NO_ERROR) - { - ALC.EXT_EFX = false; - goto skip_efx; - } - - alGenFilters(1, &mWaterFilter); - if(alGetError() == AL_NO_ERROR) - { - alFilteri(mWaterFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); - if(alGetError() == AL_NO_ERROR) - { - Log(Debug::Info) << "Low-pass filter supported"; - alFilterf(mWaterFilter, AL_LOWPASS_GAIN, 0.9f); - alFilterf(mWaterFilter, AL_LOWPASS_GAINHF, 0.125f); - } - else - { - alDeleteFilters(1, &mWaterFilter); - mWaterFilter = 0; - } - alGetError(); - } - - alGenAuxiliaryEffectSlots(1, &mEffectSlot); - alGetError(); - - alGenEffects(1, &mDefaultEffect); - if(alGetError() == AL_NO_ERROR) - { - alEffecti(mDefaultEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - if(alGetError() == AL_NO_ERROR) - Log(Debug::Info) << "EAX Reverb supported"; - else - { - alEffecti(mDefaultEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); - if(alGetError() == AL_NO_ERROR) - Log(Debug::Info) << "Standard Reverb supported"; - } - EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_LIVINGROOM; - props.flGain = 0.0f; - LoadEffect(mDefaultEffect, props); - } - - alGenEffects(1, &mWaterEffect); - if(alGetError() == AL_NO_ERROR) - { - alEffecti(mWaterEffect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - if(alGetError() != AL_NO_ERROR) - { - alEffecti(mWaterEffect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); - alGetError(); - } - LoadEffect(mWaterEffect, EFX_REVERB_PRESET_UNDERWATER); - } - - alListenerf(AL_METERS_PER_UNIT, 1.0f / Constants::UnitsPerMeter); - } -skip_efx: - alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); - // Speed of sound is in units per second. Take the sound speed in air (assumed - // meters per second), multiply by the units per meter to get the speed in u/s. - alSpeedOfSound(Constants::SoundSpeedInAir * Constants::UnitsPerMeter); - alGetError(); - - mInitialized = true; - return true; -} - -void OpenAL_Output::deinit() -{ - mStreamThread->removeAll(); - - for(ALuint source : mFreeSources) - alDeleteSources(1, &source); - mFreeSources.clear(); - - if(mEffectSlot) - alDeleteAuxiliaryEffectSlots(1, &mEffectSlot); - mEffectSlot = 0; - if(mDefaultEffect) - alDeleteEffects(1, &mDefaultEffect); - mDefaultEffect = 0; - if(mWaterEffect) - alDeleteEffects(1, &mWaterEffect); - mWaterEffect = 0; - if(mWaterFilter) - alDeleteFilters(1, &mWaterFilter); - mWaterFilter = 0; - - alcMakeContextCurrent(nullptr); - if(mContext) - alcDestroyContext(mContext); - mContext = nullptr; - if(mDevice) - alcCloseDevice(mDevice); - mDevice = nullptr; - - mInitialized = false; -} - - -std::vector OpenAL_Output::enumerateHrtf() -{ - std::vector ret; - - if(!mDevice || !ALC.SOFT_HRTF) - return ret; - - LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; - getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); - - ALCint num_hrtf; - alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); - ret.reserve(num_hrtf); - for(ALCint i = 0;i < num_hrtf;++i) - { - const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); - ret.emplace_back(entry); - } - - return ret; -} - -void OpenAL_Output::setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) -{ - if(!mDevice || !ALC.SOFT_HRTF) - { - Log(Debug::Info) << "HRTF extension not present"; - return; - } - - LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; - getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); - - LPALCRESETDEVICESOFT alcResetDeviceSOFT = nullptr; - getALCFunc(alcResetDeviceSOFT, mDevice, "alcResetDeviceSOFT"); - - std::vector attrs; - attrs.reserve(15); - - attrs.push_back(ALC_HRTF_SOFT); - attrs.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE : - hrtfmode == HrtfMode::Enable ? ALC_TRUE : - /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); - if(!hrtfname.empty()) - { - ALCint index = -1; - ALCint num_hrtf; - alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); - for(ALCint i = 0;i < num_hrtf;++i) - { - const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); - if(hrtfname == entry) - { - index = i; - break; - } - } - - if(index < 0) - Log(Debug::Warning) << "Failed to find HRTF name \"" << hrtfname << "\", using default"; - else - { - attrs.push_back(ALC_HRTF_ID_SOFT); - attrs.push_back(index); - } - } - attrs.push_back(0); - alcResetDeviceSOFT(mDevice, attrs.data()); - - ALCint hrtf_state; - alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); - if(!hrtf_state) - Log(Debug::Info) << "HRTF disabled"; - else - { - const ALCchar *hrtf = alcGetString(mDevice, ALC_HRTF_SPECIFIER_SOFT); - Log(Debug::Info) << "Enabled HRTF " << hrtf; - } -} - - -std::pair OpenAL_Output::loadSound(const std::string &fname) -{ - getALError(); - - std::vector data; - ALenum format = AL_NONE; - int srate = 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) - { - Log(Debug::Error) << "Failed to load audio from " << fname << ": " << e.what(); - } - - 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; - alGenBuffers(1, &buf); - alBufferData(buf, format, data.data(), data.size(), srate); - alGetBufferi(buf, AL_SIZE, &size); - if(getALError() != AL_NO_ERROR) - { - if(buf && alIsBuffer(buf)) - alDeleteBuffers(1, &buf); getALError(); - return std::make_pair(nullptr, 0); - } - return std::make_pair(MAKE_PTRID(buf), size); -} -size_t OpenAL_Output::unloadSound(Sound_Handle data) -{ - ALuint buffer = GET_PTRID(data); - if(!buffer) return 0; + std::vector data; + ALenum format = AL_NONE; + int srate = 0; - // Make sure no sources are playing this buffer before unloading it. - SoundVec::const_iterator iter = mActiveSounds.begin(); - for(;iter != mActiveSounds.end();++iter) - { - if(!(*iter)->mHandle) - continue; - - ALuint source = GET_PTRID((*iter)->mHandle); - ALint srcbuf; - alGetSourcei(source, AL_BUFFER, &srcbuf); - if((ALuint)srcbuf == buffer) + try { - alSourceStop(source); - alSourcei(source, AL_BUFFER, 0); + DecoderPtr decoder = mManager.getDecoder(); + decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, decoder->mResourceMgr)); + + ChannelConfig chans; + SampleType type; + decoder->getInfo(&srate, &chans, &type); + format = getALFormat(chans, type); + if (format) + decoder->readAll(data); } - } - ALint size = 0; - alGetBufferi(buffer, AL_SIZE, &size); - alDeleteBuffers(1, &buffer); - getALError(); - return size; -} - - -void OpenAL_Output::initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv) -{ - alSourcef(source, AL_REFERENCE_DISTANCE, 1.0f); - alSourcef(source, AL_MAX_DISTANCE, 1000.0f); - alSourcef(source, AL_ROLLOFF_FACTOR, 0.0f); - alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); - if(AL.SOFT_source_spatialize) - alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_FALSE); - - if(useenv) - { - if(mWaterFilter) - alSourcei(source, AL_DIRECT_FILTER, - (mListenerEnv == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL - ); - else if(mListenerEnv == Env_Underwater) + catch (std::exception& e) { - gain *= 0.9f; - pitch *= 0.7f; + Log(Debug::Error) << "Failed to load audio from " << fname << ": " << e.what(); } - if(mEffectSlot) - alSource3i(source, AL_AUXILIARY_SEND_FILTER, mEffectSlot, 0, AL_FILTER_NULL); - } - else - { - if(mWaterFilter) - alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); - if(mEffectSlot) - alSource3i(source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); - } - alSourcef(source, AL_GAIN, gain); - alSourcef(source, AL_PITCH, pitch); - alSourcefv(source, AL_POSITION, pos.ptr()); - alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); -} - -void OpenAL_Output::initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv) -{ - alSourcef(source, AL_REFERENCE_DISTANCE, mindist); - alSourcef(source, AL_MAX_DISTANCE, maxdist); - alSourcef(source, AL_ROLLOFF_FACTOR, 1.0f); - alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); - if(AL.SOFT_source_spatialize) - alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); - - if((pos - mListenerPos).length2() > maxdist*maxdist) - gain = 0.0f; - if(useenv) - { - if(mWaterFilter) - alSourcei(source, AL_DIRECT_FILTER, - (mListenerEnv == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL - ); - else if(mListenerEnv == Env_Underwater) + if (data.empty()) { - gain *= 0.9f; - pitch *= 0.7f; + // If we failed to get any usable audio, substitute with silence. + format = AL_FORMAT_MONO8; + srate = 8000; + data.assign(8000, -128); } - if(mEffectSlot) - alSource3i(source, AL_AUXILIARY_SEND_FILTER, mEffectSlot, 0, AL_FILTER_NULL); - } - else - { - if(mWaterFilter) - alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); - if(mEffectSlot) - alSource3i(source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); + + ALint size; + ALuint buf = 0; + alGenBuffers(1, &buf); + alBufferData(buf, format, data.data(), data.size(), srate); + alGetBufferi(buf, AL_SIZE, &size); + if (getALError() != AL_NO_ERROR) + { + if (buf && alIsBuffer(buf)) + alDeleteBuffers(1, &buf); + getALError(); + return std::make_pair(nullptr, 0); + } + return std::make_pair(MAKE_PTRID(buf), size); } - alSourcef(source, AL_GAIN, gain); - alSourcef(source, AL_PITCH, pitch); - alSourcefv(source, AL_POSITION, pos.ptr()); - alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); -} - -void OpenAL_Output::updateCommon(ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv, bool is3d) -{ - if(is3d) + size_t OpenAL_Output::unloadSound(Sound_Handle data) { - if((pos - mListenerPos).length2() > maxdist*maxdist) + ALuint buffer = GET_PTRID(data); + if (!buffer) + return 0; + + // Make sure no sources are playing this buffer before unloading it. + SoundVec::const_iterator iter = mActiveSounds.begin(); + for (; iter != mActiveSounds.end(); ++iter) + { + if (!(*iter)->mHandle) + continue; + + ALuint source = GET_PTRID((*iter)->mHandle); + ALint srcbuf; + alGetSourcei(source, AL_BUFFER, &srcbuf); + if ((ALuint)srcbuf == buffer) + { + alSourceStop(source); + alSourcei(source, AL_BUFFER, 0); + } + } + ALint size = 0; + alGetBufferi(buffer, AL_SIZE, &size); + alDeleteBuffers(1, &buffer); + getALError(); + return size; + } + + void OpenAL_Output::initCommon2D( + ALuint source, const osg::Vec3f& pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv) + { + alSourcef(source, AL_REFERENCE_DISTANCE, 1.0f); + alSourcef(source, AL_MAX_DISTANCE, 1000.0f); + alSourcef(source, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); + if (AL.SOFT_source_spatialize) + alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_FALSE); + + if (useenv) + { + if (mWaterFilter) + alSourcei(source, AL_DIRECT_FILTER, (mListenerEnv == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL); + else if (mListenerEnv == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + if (mEffectSlot) + alSource3i(source, AL_AUXILIARY_SEND_FILTER, mEffectSlot, 0, AL_FILTER_NULL); + } + else + { + if (mWaterFilter) + alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); + if (mEffectSlot) + alSource3i(source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); + } + + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + } + + void OpenAL_Output::initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist, + ALfloat gain, ALfloat pitch, bool loop, bool useenv) + { + alSourcef(source, AL_REFERENCE_DISTANCE, mindist); + alSourcef(source, AL_MAX_DISTANCE, maxdist); + alSourcef(source, AL_ROLLOFF_FACTOR, 1.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); + alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); + if (AL.SOFT_source_spatialize) + alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); + + if ((pos - mListenerPos).length2() > maxdist * maxdist) gain = 0.0f; - } - if(useenv && mListenerEnv == Env_Underwater && !mWaterFilter) - { - gain *= 0.9f; - pitch *= 0.7f; - } - - alSourcef(source, AL_GAIN, gain); - alSourcef(source, AL_PITCH, pitch); - alSourcefv(source, AL_POSITION, pos.ptr()); - alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); -} - - -bool OpenAL_Output::playSound(Sound *sound, Sound_Handle data, float offset) -{ - ALuint source; - - if(mFreeSources.empty()) - { - Log(Debug::Warning) << "No free sources!"; - return false; - } - source = mFreeSources.front(); - - initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), - sound->getIsLooping(), sound->getUseEnv()); - alSourcei(source, AL_BUFFER, GET_PTRID(data)); - alSourcef(source, AL_SEC_OFFSET, offset); - if(getALError() != AL_NO_ERROR) - { - alSourceRewind(source); - alSourcei(source, AL_BUFFER, 0); - alGetError(); - return false; - } - - alSourcePlay(source); - if(getALError() != AL_NO_ERROR) - { - alSourceRewind(source); - alSourcei(source, AL_BUFFER, 0); - alGetError(); - return false; - } - - mFreeSources.pop_front(); - sound->mHandle = MAKE_PTRID(source); - mActiveSounds.push_back(sound); - - return true; -} - -bool OpenAL_Output::playSound3D(Sound *sound, Sound_Handle data, float offset) -{ - ALuint source; - - if(mFreeSources.empty()) - { - Log(Debug::Warning) << "No free sources!"; - return false; - } - source = mFreeSources.front(); - - initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), - sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), - sound->getUseEnv()); - alSourcei(source, AL_BUFFER, GET_PTRID(data)); - alSourcef(source, AL_SEC_OFFSET, offset); - if(getALError() != AL_NO_ERROR) - { - alSourceRewind(source); - alSourcei(source, AL_BUFFER, 0); - alGetError(); - return false; - } - - alSourcePlay(source); - if(getALError() != AL_NO_ERROR) - { - alSourceRewind(source); - alSourcei(source, AL_BUFFER, 0); - alGetError(); - return false; - } - - mFreeSources.pop_front(); - sound->mHandle = MAKE_PTRID(source); - mActiveSounds.push_back(sound); - - return true; -} - -void OpenAL_Output::finishSound(Sound *sound) -{ - if(!sound->mHandle) return; - ALuint source = GET_PTRID(sound->mHandle); - sound->mHandle = nullptr; - - // Rewind the stream to put the source back into an AL_INITIAL state, for - // the next time it's used. - alSourceRewind(source); - alSourcei(source, AL_BUFFER, 0); - getALError(); - - mFreeSources.push_back(source); - mActiveSounds.erase(std::find(mActiveSounds.begin(), mActiveSounds.end(), sound)); -} - -bool OpenAL_Output::isSoundPlaying(Sound *sound) -{ - if(!sound->mHandle) return false; - ALuint source = GET_PTRID(sound->mHandle); - ALint state = AL_STOPPED; - - alGetSourcei(source, AL_SOURCE_STATE, &state); - getALError(); - - return state == AL_PLAYING || state == AL_PAUSED; -} - -void OpenAL_Output::updateSound(Sound *sound) -{ - if(!sound->mHandle) return; - ALuint source = GET_PTRID(sound->mHandle); - - updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), - sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); - getALError(); -} - - -bool OpenAL_Output::streamSound(DecoderPtr decoder, Stream *sound, bool getLoudnessData) -{ - if(mFreeSources.empty()) - { - Log(Debug::Warning) << "No free sources!"; - return false; - } - ALuint source = mFreeSources.front(); - - if(sound->getIsLooping()) - Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; - - initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), - false, sound->getUseEnv()); - if(getALError() != AL_NO_ERROR) - return false; - - OpenAL_SoundStream *stream = new OpenAL_SoundStream(source, std::move(decoder)); - if(!stream->init(getLoudnessData)) - { - delete stream; - return false; - } - mStreamThread->add(stream); - - mFreeSources.pop_front(); - sound->mHandle = stream; - mActiveStreams.push_back(sound); - return true; -} - -bool OpenAL_Output::streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData) -{ - if(mFreeSources.empty()) - { - Log(Debug::Warning) << "No free sources!"; - return false; - } - ALuint source = mFreeSources.front(); - - if(sound->getIsLooping()) - Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; - - initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), - sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv()); - if(getALError() != AL_NO_ERROR) - return false; - - OpenAL_SoundStream *stream = new OpenAL_SoundStream(source, std::move(decoder)); - if(!stream->init(getLoudnessData)) - { - delete stream; - return false; - } - mStreamThread->add(stream); - - mFreeSources.pop_front(); - sound->mHandle = stream; - mActiveStreams.push_back(sound); - return true; -} - -void OpenAL_Output::finishStream(Stream *sound) -{ - if(!sound->mHandle) return; - OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); - ALuint source = stream->mSource; - - sound->mHandle = nullptr; - mStreamThread->remove(stream); - - // Rewind the stream to put the source back into an AL_INITIAL state, for - // the next time it's used. - alSourceRewind(source); - alSourcei(source, AL_BUFFER, 0); - getALError(); - - mFreeSources.push_back(source); - mActiveStreams.erase(std::find(mActiveStreams.begin(), mActiveStreams.end(), sound)); - - delete stream; -} - -double OpenAL_Output::getStreamDelay(Stream *sound) -{ - if(!sound->mHandle) return 0.0; - OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); - return stream->getStreamDelay(); -} - -double OpenAL_Output::getStreamOffset(Stream *sound) -{ - if(!sound->mHandle) return 0.0; - OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); - std::lock_guard lock(mStreamThread->mMutex); - return stream->getStreamOffset(); -} - -float OpenAL_Output::getStreamLoudness(Stream *sound) -{ - if(!sound->mHandle) return 0.0; - OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); - std::lock_guard lock(mStreamThread->mMutex); - return stream->getCurrentLoudness(); -} - -bool OpenAL_Output::isStreamPlaying(Stream *sound) -{ - if(!sound->mHandle) return false; - OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); - std::lock_guard lock(mStreamThread->mMutex); - return stream->isPlaying(); -} - -void OpenAL_Output::updateStream(Stream *sound) -{ - if(!sound->mHandle) return; - OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); - ALuint source = stream->mSource; - - updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), - sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); - getALError(); -} - - -void OpenAL_Output::startUpdate() -{ - alcSuspendContext(alcGetCurrentContext()); -} - -void OpenAL_Output::finishUpdate() -{ - alcProcessContext(alcGetCurrentContext()); -} - - -void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) -{ - if(mContext) - { - ALfloat orient[6] = { - atdir.x(), atdir.y(), atdir.z(), - updir.x(), updir.y(), updir.z() - }; - alListenerfv(AL_POSITION, pos.ptr()); - alListenerfv(AL_ORIENTATION, orient); - - if(env != mListenerEnv) + if (useenv) { - alSpeedOfSound(((env == Env_Underwater) ? Constants::SoundSpeedUnderwater : Constants::SoundSpeedInAir) * Constants::UnitsPerMeter); - - // Update active sources with the environment's direct filter - if(mWaterFilter) + if (mWaterFilter) + alSourcei(source, AL_DIRECT_FILTER, (mListenerEnv == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL); + else if (mListenerEnv == Env_Underwater) { - ALuint filter = (env == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL; - for(Sound *sound : mActiveSounds) - { - if(sound->getUseEnv()) - alSourcei(GET_PTRID(sound->mHandle), AL_DIRECT_FILTER, filter); - } - for(Stream *sound : mActiveStreams) - { - if(sound->getUseEnv()) - alSourcei( - reinterpret_cast(sound->mHandle)->mSource, - AL_DIRECT_FILTER, filter - ); - } + gain *= 0.9f; + pitch *= 0.7f; } - // Update the environment effect - if(mEffectSlot) - alAuxiliaryEffectSloti(mEffectSlot, AL_EFFECTSLOT_EFFECT, - (env == Env_Underwater) ? mWaterEffect : mDefaultEffect - ); + if (mEffectSlot) + alSource3i(source, AL_AUXILIARY_SEND_FILTER, mEffectSlot, 0, AL_FILTER_NULL); } - getALError(); - } - - mListenerPos = pos; - mListenerEnv = env; -} - - -void OpenAL_Output::pauseSounds(int types) -{ - std::vector sources; - for(Sound *sound : mActiveSounds) - { - if((types&sound->getPlayType())) - sources.push_back(GET_PTRID(sound->mHandle)); - } - for(Stream *sound : mActiveStreams) - { - if((types&sound->getPlayType())) + else { - OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); - sources.push_back(stream->mSource); + if (mWaterFilter) + alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); + if (mEffectSlot) + alSource3i(source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); } - } - if(!sources.empty()) - { - alSourcePausev(sources.size(), sources.data()); - getALError(); - } -} -void OpenAL_Output::pauseActiveDevice() -{ - if (mDevice == nullptr) - return; - - if(alcIsExtensionPresent(mDevice, "ALC_SOFT_PAUSE_DEVICE")) - { - LPALCDEVICEPAUSESOFT alcDevicePauseSOFT = nullptr; - getALCFunc(alcDevicePauseSOFT, mDevice, "alcDevicePauseSOFT"); - alcDevicePauseSOFT(mDevice); - getALCError(mDevice); + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } - alListenerf(AL_GAIN, 0.0f); -} - -void OpenAL_Output::resumeActiveDevice() -{ - if (mDevice == nullptr) - return; - - if(alcIsExtensionPresent(mDevice, "ALC_SOFT_PAUSE_DEVICE")) + void OpenAL_Output::updateCommon( + ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv) { - LPALCDEVICERESUMESOFT alcDeviceResumeSOFT = nullptr; - getALCFunc(alcDeviceResumeSOFT, mDevice, "alcDeviceResumeSOFT"); - alcDeviceResumeSOFT(mDevice); - getALCError(mDevice); - } - - alListenerf(AL_GAIN, 1.0f); -} - -void OpenAL_Output::resumeSounds(int types) -{ - std::vector sources; - for(Sound *sound : mActiveSounds) - { - if((types&sound->getPlayType())) - sources.push_back(GET_PTRID(sound->mHandle)); - } - for(Stream *sound : mActiveStreams) - { - if((types&sound->getPlayType())) + if (useenv && mListenerEnv == Env_Underwater && !mWaterFilter) { - OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); - sources.push_back(stream->mSource); + gain *= 0.9f; + pitch *= 0.7f; } + + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } - if(!sources.empty()) + + bool OpenAL_Output::playSound(Sound* sound, Sound_Handle data, float offset) { - alSourcePlayv(sources.size(), sources.data()); + ALuint source; + + if (mFreeSources.empty()) + { + Log(Debug::Warning) << "No free sources!"; + return false; + } + source = mFreeSources.front(); + + initCommon2D(source, sound->getPosition(), sound->getRealVolume(), getTimeScaledPitch(sound), + sound->getIsLooping(), sound->getUseEnv()); + alSourcei(source, AL_BUFFER, GET_PTRID(data)); + alSourcef(source, AL_SEC_OFFSET, offset); + if (getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); + return false; + } + + alSourcePlay(source); + if (getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); + return false; + } + + mFreeSources.pop_front(); + sound->mHandle = MAKE_PTRID(source); + mActiveSounds.push_back(sound); + + return true; + } + + bool OpenAL_Output::playSound3D(Sound* sound, Sound_Handle data, float offset) + { + ALuint source; + + if (mFreeSources.empty()) + { + Log(Debug::Warning) << "No free sources!"; + return false; + } + source = mFreeSources.front(); + + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + sound->getRealVolume(), getTimeScaledPitch(sound), sound->getIsLooping(), sound->getUseEnv()); + alSourcei(source, AL_BUFFER, GET_PTRID(data)); + alSourcef(source, AL_SEC_OFFSET, offset); + if (getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); + return false; + } + + alSourcePlay(source); + if (getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); + return false; + } + + mFreeSources.pop_front(); + sound->mHandle = MAKE_PTRID(source); + mActiveSounds.push_back(sound); + + return true; + } + + void OpenAL_Output::finishSound(Sound* sound) + { + if (!sound->mHandle) + return; + ALuint source = GET_PTRID(sound->mHandle); + sound->mHandle = nullptr; + + // Rewind the stream to put the source back into an AL_INITIAL state, for + // the next time it's used. + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + getALError(); + + mFreeSources.push_back(source); + mActiveSounds.erase(std::find(mActiveSounds.begin(), mActiveSounds.end(), sound)); + } + + bool OpenAL_Output::isSoundPlaying(Sound* sound) + { + if (!sound->mHandle) + return false; + ALuint source = GET_PTRID(sound->mHandle); + ALint state = AL_STOPPED; + + alGetSourcei(source, AL_SOURCE_STATE, &state); + getALError(); + + return state == AL_PLAYING || state == AL_PAUSED; + } + + void OpenAL_Output::updateSound(Sound* sound) + { + if (!sound->mHandle) + return; + ALuint source = GET_PTRID(sound->mHandle); + + updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), + getTimeScaledPitch(sound), sound->getUseEnv()); getALError(); } -} + bool OpenAL_Output::streamSound(DecoderPtr decoder, Stream* sound, bool getLoudnessData) + { + if (mFreeSources.empty()) + { + Log(Debug::Warning) << "No free sources!"; + return false; + } + ALuint source = mFreeSources.front(); -OpenAL_Output::OpenAL_Output(SoundManager &mgr) - : Sound_Output(mgr) - , mDevice(nullptr), mContext(nullptr) - , mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal) - , mWaterFilter(0), mWaterEffect(0), mDefaultEffect(0), mEffectSlot(0) - , mStreamThread(new StreamThread) -{ -} + if (sound->getIsLooping()) + Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; -OpenAL_Output::~OpenAL_Output() -{ - OpenAL_Output::deinit(); -} + initCommon2D( + source, sound->getPosition(), sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); + if (getALError() != AL_NO_ERROR) + return false; + + OpenAL_SoundStream* stream = new OpenAL_SoundStream(source, std::move(decoder)); + if (!stream->init(getLoudnessData)) + { + delete stream; + return false; + } + mStreamThread->add(stream); + + mFreeSources.pop_front(); + sound->mHandle = stream; + mActiveStreams.push_back(sound); + return true; + } + + bool OpenAL_Output::streamSound3D(DecoderPtr decoder, Stream* sound, bool getLoudnessData) + { + if (mFreeSources.empty()) + { + Log(Debug::Warning) << "No free sources!"; + return false; + } + ALuint source = mFreeSources.front(); + + if (sound->getIsLooping()) + Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; + + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + sound->getRealVolume(), getTimeScaledPitch(sound), false, sound->getUseEnv()); + if (getALError() != AL_NO_ERROR) + return false; + + OpenAL_SoundStream* stream = new OpenAL_SoundStream(source, std::move(decoder)); + if (!stream->init(getLoudnessData)) + { + delete stream; + return false; + } + mStreamThread->add(stream); + + mFreeSources.pop_front(); + sound->mHandle = stream; + mActiveStreams.push_back(sound); + return true; + } + + void OpenAL_Output::finishStream(Stream* sound) + { + if (!sound->mHandle) + return; + OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); + ALuint source = stream->mSource; + + sound->mHandle = nullptr; + mStreamThread->remove(stream); + + // Rewind the stream to put the source back into an AL_INITIAL state, for + // the next time it's used. + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + getALError(); + + mFreeSources.push_back(source); + mActiveStreams.erase(std::find(mActiveStreams.begin(), mActiveStreams.end(), sound)); + + delete stream; + } + + double OpenAL_Output::getStreamDelay(Stream* sound) + { + if (!sound->mHandle) + return 0.0; + OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); + return stream->getStreamDelay(); + } + + double OpenAL_Output::getStreamOffset(Stream* sound) + { + if (!sound->mHandle) + return 0.0; + OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); + std::lock_guard lock(mStreamThread->mMutex); + return stream->getStreamOffset(); + } + + float OpenAL_Output::getStreamLoudness(Stream* sound) + { + if (!sound->mHandle) + return 0.0; + OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); + std::lock_guard lock(mStreamThread->mMutex); + return stream->getCurrentLoudness(); + } + + bool OpenAL_Output::isStreamPlaying(Stream* sound) + { + if (!sound->mHandle) + return false; + OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); + std::lock_guard lock(mStreamThread->mMutex); + return stream->isPlaying(); + } + + void OpenAL_Output::updateStream(Stream* sound) + { + if (!sound->mHandle) + return; + OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); + ALuint source = stream->mSource; + + updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), + getTimeScaledPitch(sound), sound->getUseEnv()); + getALError(); + } + + void OpenAL_Output::startUpdate() + { + alcSuspendContext(alcGetCurrentContext()); + } + + void OpenAL_Output::finishUpdate() + { + alcProcessContext(alcGetCurrentContext()); + } + + void OpenAL_Output::updateListener( + const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) + { + if (mContext) + { + ALfloat orient[6] = { atdir.x(), atdir.y(), atdir.z(), updir.x(), updir.y(), updir.z() }; + alListenerfv(AL_POSITION, pos.ptr()); + alListenerfv(AL_ORIENTATION, orient); + + if (env != mListenerEnv) + { + alSpeedOfSound(((env == Env_Underwater) ? Constants::SoundSpeedUnderwater : Constants::SoundSpeedInAir) + * Constants::UnitsPerMeter); + + // Update active sources with the environment's direct filter + if (mWaterFilter) + { + ALuint filter = (env == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL; + for (Sound* sound : mActiveSounds) + { + if (sound->getUseEnv()) + alSourcei(GET_PTRID(sound->mHandle), AL_DIRECT_FILTER, filter); + } + for (Stream* sound : mActiveStreams) + { + if (sound->getUseEnv()) + alSourcei(reinterpret_cast(sound->mHandle)->mSource, AL_DIRECT_FILTER, + filter); + } + } + // Update the environment effect + if (mEffectSlot) + alAuxiliaryEffectSloti( + mEffectSlot, AL_EFFECTSLOT_EFFECT, (env == Env_Underwater) ? mWaterEffect : mDefaultEffect); + } + getALError(); + } + + mListenerPos = pos; + mListenerEnv = env; + } + + void OpenAL_Output::pauseSounds(int types) + { + std::vector sources; + for (Sound* sound : mActiveSounds) + { + if ((types & sound->getPlayType())) + sources.push_back(GET_PTRID(sound->mHandle)); + } + for (Stream* sound : mActiveStreams) + { + if ((types & sound->getPlayType())) + { + OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); + sources.push_back(stream->mSource); + } + } + if (!sources.empty()) + { + alSourcePausev(sources.size(), sources.data()); + getALError(); + } + } + + void OpenAL_Output::pauseActiveDevice() + { + if (mDevice == nullptr) + return; + + if (alcIsExtensionPresent(mDevice, "ALC_SOFT_PAUSE_DEVICE")) + { + LPALCDEVICEPAUSESOFT alcDevicePauseSOFT = nullptr; + getALCFunc(alcDevicePauseSOFT, mDevice, "alcDevicePauseSOFT"); + alcDevicePauseSOFT(mDevice); + getALCError(mDevice); + } + + alListenerf(AL_GAIN, 0.0f); + } + + void OpenAL_Output::resumeActiveDevice() + { + if (mDevice == nullptr) + return; + + if (alcIsExtensionPresent(mDevice, "ALC_SOFT_PAUSE_DEVICE")) + { + LPALCDEVICERESUMESOFT alcDeviceResumeSOFT = nullptr; + getALCFunc(alcDeviceResumeSOFT, mDevice, "alcDeviceResumeSOFT"); + alcDeviceResumeSOFT(mDevice); + getALCError(mDevice); + } + + alListenerf(AL_GAIN, 1.0f); + } + + void OpenAL_Output::resumeSounds(int types) + { + std::vector sources; + for (Sound* sound : mActiveSounds) + { + if ((types & sound->getPlayType())) + sources.push_back(GET_PTRID(sound->mHandle)); + } + for (Stream* sound : mActiveStreams) + { + if ((types & sound->getPlayType())) + { + OpenAL_SoundStream* stream = reinterpret_cast(sound->mHandle); + sources.push_back(stream->mSource); + } + } + if (!sources.empty()) + { + alSourcePlayv(sources.size(), sources.data()); + getALError(); + } + } + + OpenAL_Output::OpenAL_Output(SoundManager& mgr) + : Sound_Output(mgr) + , mDevice(nullptr) + , mContext(nullptr) + , mListenerPos(0.0f, 0.0f, 0.0f) + , mListenerEnv(Env_Normal) + , mWaterFilter(0) + , mWaterEffect(0) + , mDefaultEffect(0) + , mEffectSlot(0) + , mStreamThread(std::make_unique()) + { + } + + OpenAL_Output::~OpenAL_Output() + { + OpenAL_Output::deinit(); + } + + float OpenAL_Output::getTimeScaledPitch(SoundBase* sound) + { + const bool shouldScale = !(sound->mParams.mFlags & PlayMode::NoScaling); + return shouldScale ? sound->getPitch() * mManager.getSimulationTimeScale() : sound->getPitch(); + } } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 2a19e6768..eed23ac65 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -1,13 +1,14 @@ #ifndef GAME_SOUND_OPENAL_OUTPUT_H #define GAME_SOUND_OPENAL_OUTPUT_H +#include +#include +#include #include #include -#include -#include -#include "alc.h" #include "al.h" +#include "alc.h" #include "alext.h" #include "sound_output.hpp" @@ -15,21 +16,24 @@ namespace MWSound { class SoundManager; + class SoundBase; class Sound; class Stream; class OpenAL_Output : public Sound_Output { - ALCdevice *mDevice; - ALCcontext *mContext; + ALCdevice* mDevice; + ALCcontext* mContext; - struct { + struct + { bool EXT_EFX : 1; bool SOFT_HRTF : 1; - } ALC = {false, false}; - struct { + } ALC = { false, false }; + struct + { bool SOFT_source_spatialize : 1; - } AL = {false}; + } AL = { false }; typedef std::deque IDDq; IDDq mFreeSources; @@ -50,44 +54,61 @@ namespace MWSound struct StreamThread; std::unique_ptr mStreamThread; - void initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); - void initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv); + std::string mDeviceName; + std::vector mContextAttributes; + std::mutex mReopenMutex; - void updateCommon(ALuint source, const osg::Vec3f &pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv, bool is3d); + class DefaultDeviceThread; + std::unique_ptr mDefaultDeviceThread; - OpenAL_Output& operator=(const OpenAL_Output &rhs); - OpenAL_Output(const OpenAL_Output &rhs); + void initCommon2D(ALuint source, const osg::Vec3f& pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); + void initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, + ALfloat pitch, bool loop, bool useenv); + + void updateCommon( + ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv); + + float getTimeScaledPitch(SoundBase* sound); + + OpenAL_Output& operator=(const OpenAL_Output& rhs); + OpenAL_Output(const OpenAL_Output& rhs); + + static void eventCallback( + ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar* message, void* userParam); + + void onDisconnect(); public: std::vector enumerate() override; - bool init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode) override; + bool init(const std::string& devname, const std::string& hrtfname, HrtfMode hrtfmode) override; void deinit() override; std::vector enumerateHrtf() override; - void setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) override; + void setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) override; - std::pair loadSound(const std::string &fname) override; + std::pair loadSound(const std::string& fname) override; size_t unloadSound(Sound_Handle data) override; - bool playSound(Sound *sound, Sound_Handle data, float offset) override; - bool playSound3D(Sound *sound, Sound_Handle data, float offset) override; - void finishSound(Sound *sound) override; - bool isSoundPlaying(Sound *sound) override; - void updateSound(Sound *sound) override; + bool playSound(Sound* sound, Sound_Handle data, float offset) override; + bool playSound3D(Sound* sound, Sound_Handle data, float offset) override; + void finishSound(Sound* sound) override; + bool isSoundPlaying(Sound* sound) override; + void updateSound(Sound* sound) override; - bool streamSound(DecoderPtr decoder, Stream *sound, bool getLoudnessData=false) override; - bool streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData) override; - void finishStream(Stream *sound) override; - double getStreamDelay(Stream *sound) override; - double getStreamOffset(Stream *sound) override; - float getStreamLoudness(Stream *sound) override; - bool isStreamPlaying(Stream *sound) override; - void updateStream(Stream *sound) override; + bool streamSound(DecoderPtr decoder, Stream* sound, bool getLoudnessData = false) override; + bool streamSound3D(DecoderPtr decoder, Stream* sound, bool getLoudnessData) override; + void finishStream(Stream* sound) override; + double getStreamDelay(Stream* sound) override; + double getStreamOffset(Stream* sound) override; + float getStreamLoudness(Stream* sound) override; + bool isStreamPlaying(Stream* sound) override; + void updateStream(Stream* sound) override; void startUpdate() override; void finishUpdate() override; - void updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) override; + void updateListener( + const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) override; void pauseSounds(int types) override; void resumeSounds(int types) override; @@ -95,7 +116,7 @@ namespace MWSound void pauseActiveDevice() override; void resumeActiveDevice() override; - OpenAL_Output(SoundManager &mgr); + OpenAL_Output(SoundManager& mgr); virtual ~OpenAL_Output(); }; } diff --git a/apps/openmw/mwsound/regionsoundselector.cpp b/apps/openmw/mwsound/regionsoundselector.cpp index 752c4a26b..388fe3bf9 100644 --- a/apps/openmw/mwsound/regionsoundselector.cpp +++ b/apps/openmw/mwsound/regionsoundselector.cpp @@ -1,11 +1,13 @@ #include "regionsoundselector.hpp" +#include #include #include #include #include +#include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" @@ -13,19 +15,19 @@ namespace MWSound { namespace { - int addChance(int result, const ESM::Region::SoundRef &v) + int addChance(int result, const ESM::Region::SoundRef& v) { return result + v.mChance; } } RegionSoundSelector::RegionSoundSelector() - : mMinTimeBetweenSounds(Fallback::Map::getFloat("Weather_Minimum_Time_Between_Environmental_Sounds")) - , mMaxTimeBetweenSounds(Fallback::Map::getFloat("Weather_Maximum_Time_Between_Environmental_Sounds")) - {} + : mMinTimeBetweenSounds(Fallback::Map::getFloat("Weather_Minimum_Time_Between_Environmental_Sounds")) + , mMaxTimeBetweenSounds(Fallback::Map::getFloat("Weather_Maximum_Time_Between_Environmental_Sounds")) + { + } - std::optional RegionSoundSelector::getNextRandom(float duration, const std::string& regionName, - const MWBase::World& world) + std::optional RegionSoundSelector::getNextRandom(float duration, const ESM::RefId& regionName) { mTimePassed += duration; @@ -42,7 +44,8 @@ namespace MWSound mSumChance = 0; } - const ESM::Region* const region = world.getStore().get().search(mLastRegionName); + const ESM::Region* const region + = MWBase::Environment::get().getESMStore()->get().search(mLastRegionName); if (region == nullptr) return {}; @@ -57,8 +60,7 @@ namespace MWSound const int r = Misc::Rng::rollDice(std::max(mSumChance, 100)); int pos = 0; - const auto isSelected = [&] (const ESM::Region::SoundRef& sound) - { + const auto isSelected = [&](const ESM::Region::SoundRef& sound) { if (r - pos < sound.mChance) return true; pos += sound.mChance; diff --git a/apps/openmw/mwsound/regionsoundselector.hpp b/apps/openmw/mwsound/regionsoundselector.hpp index 35df8a531..1a9e6e450 100644 --- a/apps/openmw/mwsound/regionsoundselector.hpp +++ b/apps/openmw/mwsound/regionsoundselector.hpp @@ -1,6 +1,7 @@ #ifndef GAME_SOUND_REGIONSOUNDSELECTOR_H #define GAME_SOUND_REGIONSOUNDSELECTOR_H +#include #include #include @@ -13,19 +14,18 @@ namespace MWSound { class RegionSoundSelector { - public: - std::optional getNextRandom(float duration, const std::string& regionName, - const MWBase::World& world); + public: + std::optional getNextRandom(float duration, const ESM::RefId& regionName); - RegionSoundSelector(); + RegionSoundSelector(); - private: - float mTimeToNextEnvSound = 0.0f; - int mSumChance = 0; - std::string mLastRegionName; - float mTimePassed = 0.0; - float mMinTimeBetweenSounds; - float mMaxTimeBetweenSounds; + private: + float mTimeToNextEnvSound = 0.0f; + int mSumChance = 0; + ESM::RefId mLastRegionName; + float mTimePassed = 0.0; + float mMinTimeBetweenSounds; + float mMaxTimeBetweenSounds; }; } diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index d2e65c989..48e897534 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -11,26 +11,39 @@ namespace MWSound enum PlayModeEx { Play_2D = 0, + Play_StopAtFadeEnd = 1 << 28, + Play_FadeExponential = 1 << 29, + Play_InFade = 1 << 30, Play_3D = 1 << 31, + Play_FadeFlagsMask = (Play_StopAtFadeEnd | Play_FadeExponential), }; // For testing individual PlayMode flags - inline int operator&(int a, PlayMode b) { return a & static_cast(b); } - inline int operator&(PlayMode a, PlayMode b) { return static_cast(a) & static_cast(b); } + inline int operator&(int a, PlayMode b) + { + return a & static_cast(b); + } + inline int operator&(PlayMode a, PlayMode b) + { + return static_cast(a) & static_cast(b); + } struct SoundParams { osg::Vec3f mPos; - float mVolume = 1; - float mBaseVolume = 1; - float mPitch = 1; - float mMinDistance = 1; - float mMaxDistance = 1000; + float mVolume = 1.0f; + float mBaseVolume = 1.0f; + float mPitch = 1.0f; + float mMinDistance = 1.0f; + float mMaxDistance = 1000.0f; int mFlags = 0; - float mFadeOutTime = 0; + float mFadeVolume = 1.0f; + float mFadeTarget = 0.0f; + float mFadeStep = 0.0f; }; - class SoundBase { + class SoundBase + { SoundBase& operator=(const SoundBase&) = delete; SoundBase(const SoundBase&) = delete; SoundBase(SoundBase&&) = delete; @@ -43,32 +56,111 @@ namespace MWSound friend class OpenAL_Output; public: - void setPosition(const osg::Vec3f &pos) { mParams.mPos = pos; } + void setPosition(const osg::Vec3f& pos) { mParams.mPos = pos; } void setVolume(float volume) { mParams.mVolume = volume; } void setBaseVolume(float volume) { mParams.mBaseVolume = volume; } - void setFadeout(float duration) { mParams.mFadeOutTime = duration; } - void updateFade(float duration) + void setFadeout(float duration) { setFade(duration, 0.0, Play_StopAtFadeEnd); } + + /// Fade to the given linear gain within the specified amount of time. + /// Note that the fade gain is independent of the sound volume. + /// + /// \param duration specifies the duration of the fade. For *linear* + /// fades (default) this will be exactly the time at which the desired + /// volume is reached. Let v0 be the initial volume, v1 be the target + /// volume, and t0 be the initial time. Then the volume over time is + /// given as + /// + /// v(t) = v0 + (v1 - v0) * (t - t0) / duration if t <= t0 + duration + /// v(t) = v1 if t > t0 + duration + /// + /// For *exponential* fades this determines the time-constant of the + /// exponential process describing the fade. In particular, we guarantee + /// that we reach v0 + 0.99 * (v1 - v0) within the given duration. + /// + /// v(t) = v1 + (v0 - v1) * exp(-4.6 * (t0 - t) / duration) + /// + /// where -4.6 is approximately log(1%) (i.e., -40 dB). + /// + /// This interpolation mode is meant for environmental sound effects to + /// achieve less jarring transitions. + /// + /// \param targetVolume is the linear gain that should be reached at + /// the end of the fade. + /// + /// \param flags may be a combination of Play_FadeExponential and + /// Play_StopAtFadeEnd. If Play_StopAtFadeEnd is set, stops the sound + /// once the fade duration has passed or the target volume has been + /// reached. If Play_FadeExponential is set, enables the exponential + /// fade mode (see above). + void setFade(float duration, float targetVolume, int flags = 0) { - if (mParams.mFadeOutTime > 0.0f) + // Approximation of log(1%) (i.e., -40 dB). + constexpr float minus40Decibel = -4.6f; + + // Do nothing if already at the target, unless we need to trigger a stop event + if ((mParams.mFadeVolume == targetVolume) && !(flags & Play_StopAtFadeEnd)) + return; + + mParams.mFadeTarget = targetVolume; + mParams.mFlags = (mParams.mFlags & ~Play_FadeFlagsMask) | (flags & Play_FadeFlagsMask) | Play_InFade; + if (duration > 0.0f) { - float soundDuration = std::min(duration, mParams.mFadeOutTime); - mParams.mVolume *= (mParams.mFadeOutTime - soundDuration) / mParams.mFadeOutTime; - mParams.mFadeOutTime -= soundDuration; + if (mParams.mFlags & Play_FadeExponential) + mParams.mFadeStep = -minus40Decibel / duration; + else + mParams.mFadeStep = (mParams.mFadeTarget - mParams.mFadeVolume) / duration; + } + else + { + mParams.mFadeVolume = mParams.mFadeTarget; + mParams.mFadeStep = 0.0f; } } - const osg::Vec3f &getPosition() const { return mParams.mPos; } - float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume; } + /// Updates the internal fading logic. + /// + /// \param dt is the time in seconds since the last call to update. + /// + /// \return true if the sound is still active, false if the sound has + /// reached a fading destination that was marked with Play_StopAtFadeEnd. + bool updateFade(float dt) + { + // Mark fade as done at this volume difference (-80dB when fading to zero) + constexpr float minVolumeDifference = 1e-4f; + + if (!getInFade()) + return true; + + // Perform the actual fade operation + const float deltaBefore = mParams.mFadeTarget - mParams.mFadeVolume; + if (mParams.mFlags & Play_FadeExponential) + mParams.mFadeVolume += mParams.mFadeStep * deltaBefore * dt; + else + mParams.mFadeVolume += mParams.mFadeStep * dt; + const float deltaAfter = mParams.mFadeTarget - mParams.mFadeVolume; + + // Abort fade if we overshot or reached the minimum difference + if ((std::signbit(deltaBefore) != std::signbit(deltaAfter)) || (std::abs(deltaAfter) < minVolumeDifference)) + { + mParams.mFadeVolume = mParams.mFadeTarget; + mParams.mFlags &= ~Play_InFade; + } + + return getInFade() || !(mParams.mFlags & Play_StopAtFadeEnd); + } + + const osg::Vec3f& getPosition() const { return mParams.mPos; } + float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume * mParams.mFadeVolume; } float getPitch() const { return mParams.mPitch; } float getMinDistance() const { return mParams.mMinDistance; } float getMaxDistance() const { return mParams.mMaxDistance; } - MWSound::Type getPlayType() const - { return static_cast(mParams.mFlags & MWSound::Type::Mask); } + MWSound::Type getPlayType() const { return static_cast(mParams.mFlags & MWSound::Type::Mask); } bool getUseEnv() const { return !(mParams.mFlags & MWSound::PlayMode::NoEnv); } bool getIsLooping() const { return mParams.mFlags & MWSound::PlayMode::Loop; } bool getDistanceCull() const { return mParams.mFlags & MWSound::PlayMode::RemoveAtDistance; } bool getIs3D() const { return mParams.mFlags & Play_3D; } + bool getInFade() const { return mParams.mFlags & Play_InFade; } void init(const SoundParams& params) { @@ -79,22 +171,24 @@ namespace MWSound SoundBase() = default; }; - class Sound : public SoundBase { + class Sound : public SoundBase + { Sound& operator=(const Sound&) = delete; Sound(const Sound&) = delete; Sound(Sound&&) = delete; public: - Sound() { } + Sound() = default; }; - class Stream : public SoundBase { + class Stream : public SoundBase + { Stream& operator=(const Stream&) = delete; Stream(const Stream&) = delete; Stream(Stream&&) = delete; public: - Stream() { } + Stream() = default; }; } diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index cb71cb56d..29f162543 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -5,8 +5,9 @@ #include "../mwworld/esmstore.hpp" #include +#include #include -#include +#include #include #include @@ -23,9 +24,8 @@ namespace MWSound float mAudioMaxDistanceMult; }; - AudioParams makeAudioParams(const MWBase::World& world) + AudioParams makeAudioParams(const MWWorld::Store& settings) { - const auto& settings = world.getStore().get(); AudioParams params; params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat(); params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat(); @@ -35,11 +35,13 @@ namespace MWSound } } - SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) : - mVfs(&vfs), - mOutput(&output), - mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024), - mBufferCacheMin(std::min(static_cast(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax)) + SoundBufferPool::SoundBufferPool(Sound_Output& output) + : mOutput(&output) + , mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024) + , mBufferCacheMin( + std::min(static_cast(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) + * 1024 * 1024, + mBufferCacheMax)) { } @@ -48,7 +50,7 @@ namespace MWSound clear(); } - Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const + Sound_Buffer* SoundBufferPool::lookup(const ESM::RefId& soundId) const { const auto it = mBufferNameMap.find(soundId); if (it != mBufferNameMap.end()) @@ -60,12 +62,12 @@ namespace MWSound return nullptr; } - Sound_Buffer* SoundBufferPool::load(const std::string& soundId) + Sound_Buffer* SoundBufferPool::load(const ESM::RefId& soundId) { if (mBufferNameMap.empty()) { - for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get()) - insertSound(Misc::StringUtils::lowerCase(sound.mId), sound); + for (const ESM::Sound& sound : MWBase::Environment::get().getESMStore()->get()) + insertSound(sound.mId, sound); } Sound_Buffer* sfx; @@ -74,7 +76,7 @@ namespace MWSound sfx = it->second; else { - const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get().search(soundId); + const ESM::Sound* sound = MWBase::Environment::get().getESMStore()->get().search(soundId); if (sound == nullptr) return {}; sfx = insertSound(soundId, *sound); @@ -103,18 +105,19 @@ namespace MWSound void SoundBufferPool::clear() { - for (auto &sfx : mSoundBuffers) + for (auto& sfx : mSoundBuffers) { - if(sfx.mHandle) + if (sfx.mHandle) mOutput->unloadSound(sfx.mHandle); sfx.mHandle = nullptr; } mUnusedBuffers.clear(); } - Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound) + Sound_Buffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM::Sound& sound) { - static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld()); + static const AudioParams audioParams + = makeAudioParams(MWBase::Environment::get().getESMStore()->get()); float volume = static_cast(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0)); float min = sound.mData.mMinRange; @@ -131,7 +134,7 @@ namespace MWSound max = std::max(min, max); Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); - mVfs->normalizeFilename(sfx.mResourceName); + VFS::Path::normalizeFilenameInPlace(sfx.mResourceName); mBufferNameMap.emplace(soundId, &sfx); return &sfx; diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 5c45ac08a..a56bcc04f 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -2,11 +2,12 @@ #define GAME_SOUND_SOUND_BUFFER_H #include -#include #include +#include #include #include "sound_output.hpp" +#include namespace ESM { @@ -24,82 +25,85 @@ namespace MWSound class Sound_Buffer { - public: - template - Sound_Buffer(T&& resname, float volume, float mindist, float maxdist) - : mResourceName(std::forward(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) - {} + public: + template + Sound_Buffer(T&& resname, float volume, float mindist, float maxdist) + : mResourceName(std::forward(resname)) + , mVolume(volume) + , mMinDist(mindist) + , mMaxDist(maxdist) + { + } - const std::string& getResourceName() const noexcept { return mResourceName; } + const std::string& getResourceName() const noexcept { return mResourceName; } - Sound_Handle getHandle() const noexcept { return mHandle; } + Sound_Handle getHandle() const noexcept { return mHandle; } - float getVolume() const noexcept { return mVolume; } + float getVolume() const noexcept { return mVolume; } - float getMinDist() const noexcept { return mMinDist; } + float getMinDist() const noexcept { return mMinDist; } - float getMaxDist() const noexcept { return mMaxDist; } + float getMaxDist() const noexcept { return mMaxDist; } - private: - std::string mResourceName; - float mVolume; - float mMinDist; - float mMaxDist; - Sound_Handle mHandle = nullptr; - std::size_t mUses = 0; + private: + std::string mResourceName; + float mVolume; + float mMinDist; + float mMaxDist; + Sound_Handle mHandle = nullptr; + std::size_t mUses = 0; - friend class SoundBufferPool; + friend class SoundBufferPool; }; class SoundBufferPool { - public: - SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output); + public: + SoundBufferPool(Sound_Output& output); - SoundBufferPool(const SoundBufferPool&) = delete; + SoundBufferPool(const SoundBufferPool&) = delete; - ~SoundBufferPool(); + ~SoundBufferPool(); - /// Lookup a soundId for its sound data (resource name, local volume, - /// minRange, and maxRange) - Sound_Buffer* lookup(const std::string& soundId) const; + /// Lookup a soundId for its sound data (resource name, local volume, + /// minRange, and maxRange) + Sound_Buffer* lookup(const ESM::RefId& soundId) const; - /// Lookup a soundId for its sound data (resource name, local volume, - /// minRange, and maxRange), and ensure it's ready for use. - Sound_Buffer* load(const std::string& soundId); + /// Lookup a soundId for its sound data (resource name, local volume, + /// minRange, and maxRange), and ensure it's ready for use. + Sound_Buffer* load(const ESM::RefId& soundId); - void use(Sound_Buffer& sfx) + void use(Sound_Buffer& sfx) + { + if (sfx.mUses++ == 0) { - if (sfx.mUses++ == 0) - { - const auto it = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), &sfx); - if (it != mUnusedBuffers.end()) - mUnusedBuffers.erase(it); - } + const auto it = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), &sfx); + if (it != mUnusedBuffers.end()) + mUnusedBuffers.erase(it); } + } - void release(Sound_Buffer& sfx) - { - if (--sfx.mUses == 0) - mUnusedBuffers.push_front(&sfx); - } + void release(Sound_Buffer& sfx) + { + if (--sfx.mUses == 0) + mUnusedBuffers.push_front(&sfx); + } - void clear(); + void clear(); - private: - const VFS::Manager* const mVfs; - Sound_Output* mOutput; - std::deque mSoundBuffers; - std::unordered_map mBufferNameMap; - std::size_t mBufferCacheMax; - std::size_t mBufferCacheMin; - std::size_t mBufferCacheSize = 0; - // NOTE: unused buffers are stored in front-newest order. - std::deque mUnusedBuffers; + private: + Sound_Output* mOutput; + std::deque mSoundBuffers; + std::unordered_map mBufferNameMap; + std::size_t mBufferCacheMax; + std::size_t mBufferCacheMin; + std::size_t mBufferCacheSize = 0; + // NOTE: unused buffers are stored in front-newest order. + std::deque mUnusedBuffers; - inline Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound& sound); + inline Sound_Buffer* insertSound(const ESM::RefId& soundId, const ESM::Sound& sound); - inline void unloadUnused(); + inline void unloadUnused(); }; } diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 34bae87d7..f6dcdb703 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -11,21 +11,23 @@ namespace VFS namespace MWSound { - enum SampleType { + enum SampleType + { SampleType_UInt8, SampleType_Int16, SampleType_Float32 }; - const char *getSampleTypeName(SampleType type); + const char* getSampleTypeName(SampleType type); - enum ChannelConfig { + enum ChannelConfig + { ChannelConfig_Mono, ChannelConfig_Stereo, ChannelConfig_Quad, ChannelConfig_5point1, ChannelConfig_7point1 }; - const char *getChannelConfigName(ChannelConfig config); + const char* getChannelConfigName(ChannelConfig config); size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type); size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type); @@ -34,23 +36,25 @@ namespace MWSound { const VFS::Manager* mResourceMgr; - virtual void open(const std::string &fname) = 0; + virtual void open(const std::string& fname) = 0; virtual void close() = 0; virtual std::string getName() = 0; - virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; + virtual void getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) = 0; - virtual size_t read(char *buffer, size_t bytes) = 0; - virtual void readAll(std::vector &output); + virtual size_t read(char* buffer, size_t bytes) = 0; + virtual void readAll(std::vector& output); virtual size_t getSampleOffset() = 0; - Sound_Decoder(const VFS::Manager* resourceMgr) : mResourceMgr(resourceMgr) - { } - virtual ~Sound_Decoder() { } + Sound_Decoder(const VFS::Manager* resourceMgr) + : mResourceMgr(resourceMgr) + { + } + virtual ~Sound_Decoder() {} private: - Sound_Decoder(const Sound_Decoder &rhs); - Sound_Decoder& operator=(const Sound_Decoder &rhs); + Sound_Decoder(const Sound_Decoder& rhs); + Sound_Decoder& operator=(const Sound_Decoder& rhs); }; } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 9ec8b17dc..d7c35fbbc 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -1,8 +1,8 @@ #ifndef GAME_SOUND_SOUND_OUTPUT_H #define GAME_SOUND_SOUND_OUTPUT_H -#include #include +#include #include #include "../mwbase/soundmanager.hpp" @@ -15,11 +15,12 @@ namespace MWSound class Stream; // An opaque handle for the implementation's sound buffers. - typedef void *Sound_Handle; + typedef void* Sound_Handle; // An opaque handle for the implementation's sound instances. - typedef void *Sound_Instance; + typedef void* Sound_Instance; - enum class HrtfMode { + enum class HrtfMode + { Disable, Enable, Auto @@ -33,37 +34,39 @@ namespace MWSound class Sound_Output { - SoundManager &mManager; + SoundManager& mManager; virtual std::vector enumerate() = 0; - virtual bool init(const std::string &devname, const std::string &hrtfname, HrtfMode hrtfmode) = 0; + virtual bool init(const std::string& devname, const std::string& hrtfname, HrtfMode hrtfmode) = 0; virtual void deinit() = 0; virtual std::vector enumerateHrtf() = 0; - virtual void setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) = 0; + virtual void setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) = 0; - virtual std::pair loadSound(const std::string &fname) = 0; + virtual std::pair loadSound(const std::string& fname) = 0; virtual size_t unloadSound(Sound_Handle data) = 0; - virtual bool playSound(Sound *sound, Sound_Handle data, float offset) = 0; - virtual bool playSound3D(Sound *sound, Sound_Handle data, float offset) = 0; - virtual void finishSound(Sound *sound) = 0; - virtual bool isSoundPlaying(Sound *sound) = 0; - virtual void updateSound(Sound *sound) = 0; + virtual bool playSound(Sound* sound, Sound_Handle data, float offset) = 0; + virtual bool playSound3D(Sound* sound, Sound_Handle data, float offset) = 0; + virtual void finishSound(Sound* sound) = 0; + virtual bool isSoundPlaying(Sound* sound) = 0; + virtual void updateSound(Sound* sound) = 0; - virtual bool streamSound(DecoderPtr decoder, Stream *sound, bool getLoudnessData=false) = 0; - virtual bool streamSound3D(DecoderPtr decoder, Stream *sound, bool getLoudnessData) = 0; - virtual void finishStream(Stream *sound) = 0; - virtual double getStreamDelay(Stream *sound) = 0; - virtual double getStreamOffset(Stream *sound) = 0; - virtual float getStreamLoudness(Stream *sound) = 0; - virtual bool isStreamPlaying(Stream *sound) = 0; - virtual void updateStream(Stream *sound) = 0; + virtual bool streamSound(DecoderPtr decoder, Stream* sound, bool getLoudnessData = false) = 0; + virtual bool streamSound3D(DecoderPtr decoder, Stream* sound, bool getLoudnessData) = 0; + virtual void finishStream(Stream* sound) = 0; + virtual double getStreamDelay(Stream* sound) = 0; + virtual double getStreamOffset(Stream* sound) = 0; + virtual float getStreamLoudness(Stream* sound) = 0; + virtual bool isStreamPlaying(Stream* sound) = 0; + virtual void updateStream(Stream* sound) = 0; virtual void startUpdate() = 0; virtual void finishUpdate() = 0; - virtual void updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) = 0; + virtual void updateListener( + const osg::Vec3f& pos, const osg::Vec3f& atdir, const osg::Vec3f& updir, Environment env) + = 0; virtual void pauseSounds(int types) = 0; virtual void resumeSounds(int types) = 0; @@ -71,17 +74,20 @@ namespace MWSound virtual void pauseActiveDevice() = 0; virtual void resumeActiveDevice() = 0; - Sound_Output& operator=(const Sound_Output &rhs); - Sound_Output(const Sound_Output &rhs); + Sound_Output& operator=(const Sound_Output& rhs); + Sound_Output(const Sound_Output& rhs); protected: bool mInitialized; - Sound_Output(SoundManager &mgr) - : mManager(mgr), mInitialized(false) - { } + Sound_Output(SoundManager& mgr) + : mManager(mgr) + , mInitialized(false) + { + } + public: - virtual ~Sound_Output() { } + virtual ~Sound_Output() {} bool isInitialized() const { return mInitialized; } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index fc9735d57..146d947ab 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -3,36 +3,39 @@ #include #include #include +#include #include -#include #include +#include +#include #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" +#include "sound.hpp" #include "sound_buffer.hpp" #include "sound_decoder.hpp" #include "sound_output.hpp" -#include "sound.hpp" -#include "openal_output.hpp" #include "ffmpeg_decoder.hpp" - +#include "openal_output.hpp" namespace MWSound { namespace { constexpr float sMinUpdateInterval = 1.0f / 30.0f; + constexpr float sSfxFadeInDuration = 1.0f; + constexpr float sSfxFadeOutDuration = 1.0f; WaterSoundUpdaterSettings makeWaterSoundUpdaterSettings() { @@ -42,25 +45,47 @@ namespace MWSound settings.mNearWaterPoints = Fallback::Map::getInt("Water_NearWaterPoints"); settings.mNearWaterIndoorTolerance = Fallback::Map::getFloat("Water_NearWaterIndoorTolerance"); settings.mNearWaterOutdoorTolerance = Fallback::Map::getFloat("Water_NearWaterOutdoorTolerance"); - settings.mNearWaterIndoorID = Misc::StringUtils::lowerCase(Fallback::Map::getString("Water_NearWaterIndoorID")); - settings.mNearWaterOutdoorID = Misc::StringUtils::lowerCase(Fallback::Map::getString("Water_NearWaterOutdoorID")); + settings.mNearWaterIndoorID = ESM::RefId::stringRefId(Fallback::Map::getString("Water_NearWaterIndoorID")); + settings.mNearWaterOutdoorID + = ESM::RefId::stringRefId(Fallback::Map::getString("Water_NearWaterOutdoorID")); return settings; } + + float initialFadeVolume(float squaredDist, Sound_Buffer* sfx, Type type, PlayMode mode) + { + // If a sound is farther away than its maximum distance, start playing it with a zero fade volume. + // It can still become audible once the player moves closer. + const float maxDist = sfx->getMaxDist(); + if (squaredDist > (maxDist * maxDist)) + return 0.0f; + + // This is a *heuristic* that causes environment sounds to fade in. The idea is the following: + // - Only looped sounds playing through the effects channel are environment sounds + // - Do not fade in sounds if the player is already so close that the sound plays at maximum volume + const float minDist = sfx->getMinDist(); + if ((squaredDist > (minDist * minDist)) && (type == Type::Sfx) && (mode & PlayMode::Loop)) + return 0.0f; + + return 1.0; + } } // For combining PlayMode and Type flags - inline int operator|(PlayMode a, Type b) { return static_cast(a) | static_cast(b); } + inline int operator|(PlayMode a, Type b) + { + return static_cast(a) | static_cast(b); + } SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound) : mVFS(vfs) - , mOutput(new OpenAL_Output(*this)) + , mOutput(std::make_unique(*this)) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) - , mSoundBuffers(*vfs, *mOutput) + , mSoundBuffers(*mOutput) , mListenerUnderwater(false) - , mListenerPos(0,0,0) - , mListenerDir(1,0,0) - , mListenerUp(0,0,1) + , mListenerPos(0, 0, 0) + , mListenerDir(1, 0, 0) + , mListenerUp(0, 0, 1) , mUnderwaterSound(nullptr) , mNearWaterSound(nullptr) , mPlaybackPaused(false) @@ -68,19 +93,18 @@ namespace MWSound , mLastCell(nullptr) , mCurrentRegionSound(nullptr) { - if(!useSound) + if (!useSound) { Log(Debug::Info) << "Sound disabled."; return; } - std::string hrtfname = Settings::Manager::getString("hrtf", "Sound"); + const std::string& hrtfname = Settings::Manager::getString("hrtf", "Sound"); int hrtfstate = Settings::Manager::getInt("hrtf enable", "Sound"); - HrtfMode hrtfmode = hrtfstate < 0 ? HrtfMode::Auto : - hrtfstate > 0 ? HrtfMode::Enable : HrtfMode::Disable; + HrtfMode hrtfmode = hrtfstate < 0 ? HrtfMode::Auto : hrtfstate > 0 ? HrtfMode::Enable : HrtfMode::Disable; - std::string devname = Settings::Manager::getString("device", "Sound"); - if(!mOutput->init(devname, hrtfname, hrtfmode)) + const std::string& devname = Settings::Manager::getString("device", "Sound"); + if (!mOutput->init(devname, hrtfname, hrtfmode)) { Log(Debug::Error) << "Failed to initialize audio output, sound disabled"; return; @@ -90,21 +114,30 @@ namespace MWSound std::stringstream stream; stream << "Enumerated output devices:\n"; - for(const std::string &name : names) + for (const std::string& name : names) stream << " " << name; Log(Debug::Info) << stream.str(); stream.str(""); names = mOutput->enumerateHrtf(); - if(!names.empty()) + if (!names.empty()) { stream << "Enumerated HRTF names:\n"; - for(const std::string &name : names) + for (const std::string& name : names) stream << " " << name; Log(Debug::Info) << stream.str(); } + + // TODO: dehardcode this + std::vector titleMusic; + std::string_view titlefile = "music/special/morrowind title.mp3"; + if (mVFS->exists(titlefile)) + titleMusic.emplace_back(titlefile); + else + Log(Debug::Warning) << "Title music not found"; + mMusicFiles["Title"] = titleMusic; } SoundManager::~SoundManager() @@ -120,27 +153,15 @@ namespace MWSound return std::make_shared(mVFS); } - DecoderPtr SoundManager::loadVoice(const std::string &voicefile) + DecoderPtr SoundManager::loadVoice(const std::string& voicefile) { try { DecoderPtr decoder = getDecoder(); - - // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if(mVFS->exists(voicefile)) - decoder->open(voicefile); - else - { - std::string file = voicefile; - std::string::size_type pos = file.rfind('.'); - if(pos != std::string::npos) - file = file.substr(0, pos)+".mp3"; - decoder->open(file); - } - + decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, decoder->mResourceMgr)); return decoder; } - catch(std::exception &e) + catch (std::exception& e) { Log(Debug::Error) << "Failed to load audio from " << voicefile << ": " << e.what(); } @@ -158,27 +179,31 @@ namespace MWSound return mStreams.get(); } - StreamPtr SoundManager::playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal) + StreamPtr SoundManager::playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal) { MWBase::World* world = MWBase::Environment::get().getWorld(); - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); - static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->mValue.getFloat(); - static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->mValue.getFloat(); + static const float fAudioMinDistanceMult + = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); + static const float fAudioMaxDistanceMult + = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); + static const float fAudioVoiceDefaultMinDistance + = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->mValue.getFloat(); + static const float fAudioVoiceDefaultMaxDistance + = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->mValue.getFloat(); static float minDistance = std::max(fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult, 1.0f); static float maxDistance = std::max(fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult, minDistance); bool played; float basevol = volumeFromType(Type::Voice); StreamPtr sound = getStreamRef(); - if(playlocal) + if (playlocal) { sound->init([&] { SoundParams params; params.mBaseVolume = basevol; params.mFlags = PlayMode::NoEnv | Type::Voice | Play_2D; return params; - } ()); + }()); played = mOutput->streamSound(decoder, sound.get(), true); } else @@ -191,10 +216,10 @@ namespace MWSound params.mMaxDistance = maxDistance; params.mFlags = PlayMode::Normal | Type::Voice | Play_3D; return params; - } ()); + }()); played = mOutput->streamSound3D(decoder, sound.get(), true); } - if(!played) + if (!played) return nullptr; return sound; } @@ -207,7 +232,7 @@ namespace MWSound void SoundManager::stopMusic() { - if(mMusic) + if (mMusic) { mOutput->finishStream(mMusic.get()); mMusic = nullptr; @@ -216,23 +241,34 @@ namespace MWSound void SoundManager::streamMusicFull(const std::string& filename) { - if(!mOutput->isInitialized()) + if (!mOutput->isInitialized()) return; + + stopMusic(); + if (filename.empty()) + return; + Log(Debug::Info) << "Playing " << filename; mLastPlayedMusic = filename; - stopMusic(); - DecoderPtr decoder = getDecoder(); - decoder->open(filename); + try + { + decoder->open(filename); + } + catch (std::exception& e) + { + Log(Debug::Error) << "Failed to load audio from " << filename << ": " << e.what(); + return; + } mMusic = getStreamRef(); mMusic->init([&] { SoundParams params; params.mBaseVolume = volumeFromType(Type::Music); - params.mFlags = PlayMode::NoEnv | Type::Music | Play_2D; + params.mFlags = PlayMode::NoEnvNoScaling | Type::Music | Play_2D; return params; - } ()); + }()); mOutput->streamSound(decoder, mMusic.get()); } @@ -251,13 +287,19 @@ namespace MWSound void SoundManager::startRandomTitle() { - const std::vector &filelist = mMusicFiles[mCurrentPlaylist]; - auto &tracklist = mMusicToPlay[mCurrentPlaylist]; + const std::vector& filelist = mMusicFiles[mCurrentPlaylist]; + if (filelist.empty()) + { + advanceMusic(std::string()); + return; + } + + auto& tracklist = mMusicToPlay[mCurrentPlaylist]; // Do a Fisher-Yates shuffle // Repopulate if playlist is empty - if(tracklist.empty()) + if (tracklist.empty()) { tracklist.resize(filelist.size()); std::iota(tracklist.begin(), tracklist.end(), 0); @@ -266,8 +308,8 @@ namespace MWSound int i = Misc::Rng::rollDice(tracklist.size()); // Reshuffle if last played music is the same after a repopulation - if(filelist[tracklist[i]] == mLastPlayedMusic) - i = (i+1) % tracklist.size(); + if (filelist[tracklist[i]] == mLastPlayedMusic) + i = (i + 1) % tracklist.size(); // Remove music from list after advancing music advanceMusic(filelist[tracklist[i]]); @@ -275,10 +317,9 @@ namespace MWSound tracklist.pop_back(); } - void SoundManager::streamMusic(const std::string& filename) { - advanceMusic("Music/"+filename); + advanceMusic("Music/" + filename); } bool SoundManager::isMusicPlaying() @@ -286,7 +327,7 @@ namespace MWSound return mMusic && mOutput->isStreamPlaying(mMusic.get()); } - void SoundManager::playPlaylist(const std::string &playlist) + void SoundManager::playPlaylist(const std::string& playlist) { if (mCurrentPlaylist == playlist) return; @@ -294,90 +335,49 @@ namespace MWSound if (mMusicFiles.find(playlist) == mMusicFiles.end()) { std::vector filelist; - const std::map& index = mVFS->getIndex(); - - std::string pattern = "Music/" + playlist; - mVFS->normalizeFilename(pattern); - - std::map::const_iterator found = index.lower_bound(pattern); - while (found != index.end()) - { - if (found->first.size() >= pattern.size() && found->first.substr(0, pattern.size()) == pattern) - filelist.push_back(found->first); - else - break; - ++found; - } + for (const auto& name : mVFS->getRecursiveDirectoryIterator("Music/" + playlist + '/')) + filelist.push_back(name); mMusicFiles[playlist] = filelist; } - if (mMusicFiles[playlist].empty()) + // No Battle music? Use Explore playlist + if (playlist == "Battle" && mMusicFiles[playlist].empty()) + { + playPlaylist("Explore"); return; + } mCurrentPlaylist = playlist; startRandomTitle(); } - void SoundManager::playTitleMusic() + void SoundManager::say(const MWWorld::ConstPtr& ptr, const std::string& filename) { - if (mCurrentPlaylist == "Title") + if (!mOutput->isInitialized()) return; - if (mMusicFiles.find("Title") == mMusicFiles.end()) - { - std::vector filelist; - const std::map& index = mVFS->getIndex(); - // Is there an ini setting for this filename or something? - std::string filename = "music/special/morrowind title.mp3"; - auto found = index.find(filename); - if (found != index.end()) - { - filelist.emplace_back(found->first); - mMusicFiles["Title"] = filelist; - } - else - { - Log(Debug::Warning) << "Title music not found"; - return; - } - } - - if (mMusicFiles["Title"].empty()) - return; - - mCurrentPlaylist = "Title"; - startRandomTitle(); - } - - void SoundManager::say(const MWWorld::ConstPtr &ptr, const std::string &filename) - { - if(!mOutput->isInitialized()) - return; - - std::string voicefile = "Sound/"+filename; - - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + DecoderPtr decoder = loadVoice("Sound/" + filename); if (!decoder) return; - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); stopSay(ptr); StreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); - if(!sound) return; + if (!sound) + return; - mSaySoundsQueue.emplace(ptr, std::move(sound)); + mSaySoundsQueue.emplace(ptr.mRef, SaySound{ ptr.mCell, std::move(sound) }); } - float SoundManager::getSaySoundLoudness(const MWWorld::ConstPtr &ptr) const + float SoundManager::getSaySoundLoudness(const MWWorld::ConstPtr& ptr) const { - SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); - if(snditer != mActiveSaySounds.end()) + SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr.mRef); + if (snditer != mActiveSaySounds.end()) { - Stream *sound = snditer->second.get(); + Stream* sound = snditer->second.mStream.get(); return mOutput->getStreamLoudness(sound); } @@ -386,49 +386,47 @@ namespace MWSound void SoundManager::say(const std::string& filename) { - if(!mOutput->isInitialized()) + if (!mOutput->isInitialized()) return; - std::string voicefile = "Sound/"+filename; - - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + DecoderPtr decoder = loadVoice("Sound/" + filename); if (!decoder) return; stopSay(MWWorld::ConstPtr()); StreamPtr sound = playVoice(decoder, osg::Vec3f(), true); - if(!sound) return; + if (!sound) + return; - mActiveSaySounds.emplace(MWWorld::ConstPtr(), std::move(sound)); + mActiveSaySounds.emplace(nullptr, SaySound{ nullptr, std::move(sound) }); } - bool SoundManager::sayDone(const MWWorld::ConstPtr &ptr) const + bool SoundManager::sayDone(const MWWorld::ConstPtr& ptr) const { - SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); - if(snditer != mActiveSaySounds.end()) + SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr.mRef); + if (snditer != mActiveSaySounds.end()) { - if(mOutput->isStreamPlaying(snditer->second.get())) + if (mOutput->isStreamPlaying(snditer->second.mStream.get())) return false; return true; } return true; } - bool SoundManager::sayActive(const MWWorld::ConstPtr &ptr) const + bool SoundManager::sayActive(const MWWorld::ConstPtr& ptr) const { - SaySoundMap::const_iterator snditer = mSaySoundsQueue.find(ptr); - if(snditer != mSaySoundsQueue.end()) + SaySoundMap::const_iterator snditer = mSaySoundsQueue.find(ptr.mRef); + if (snditer != mSaySoundsQueue.end()) { - if(mOutput->isStreamPlaying(snditer->second.get())) + if (mOutput->isStreamPlaying(snditer->second.mStream.get())) return true; return false; } - snditer = mActiveSaySounds.find(ptr); - if(snditer != mActiveSaySounds.end()) + snditer = mActiveSaySounds.find(ptr.mRef); + if (snditer != mActiveSaySounds.end()) { - if(mOutput->isStreamPlaying(snditer->second.get())) + if (mOutput->isStreamPlaying(snditer->second.mStream.get())) return true; return false; } @@ -436,37 +434,36 @@ namespace MWSound return false; } - void SoundManager::stopSay(const MWWorld::ConstPtr &ptr) + void SoundManager::stopSay(const MWWorld::ConstPtr& ptr) { - SaySoundMap::iterator snditer = mSaySoundsQueue.find(ptr); - if(snditer != mSaySoundsQueue.end()) + SaySoundMap::iterator snditer = mSaySoundsQueue.find(ptr.mRef); + if (snditer != mSaySoundsQueue.end()) { - mOutput->finishStream(snditer->second.get()); + mOutput->finishStream(snditer->second.mStream.get()); mSaySoundsQueue.erase(snditer); } - snditer = mActiveSaySounds.find(ptr); - if(snditer != mActiveSaySounds.end()) + snditer = mActiveSaySounds.find(ptr.mRef); + if (snditer != mActiveSaySounds.end()) { - mOutput->finishStream(snditer->second.get()); + mOutput->finishStream(snditer->second.mStream.get()); mActiveSaySounds.erase(snditer); } } - - Stream *SoundManager::playTrack(const DecoderPtr& decoder, Type type) + Stream* SoundManager::playTrack(const DecoderPtr& decoder, Type type) { - if(!mOutput->isInitialized()) + if (!mOutput->isInitialized()) return nullptr; StreamPtr track = getStreamRef(); track->init([&] { SoundParams params; params.mBaseVolume = volumeFromType(type); - params.mFlags = PlayMode::NoEnv | type | Play_2D; + params.mFlags = PlayMode::NoEnvNoScaling | type | Play_2D; return params; - } ()); - if(!mOutput->streamSound(decoder, track.get())) + }()); + if (!mOutput->streamSound(decoder, track.get())) return nullptr; Stream* result = track.get(); @@ -475,28 +472,29 @@ namespace MWSound return result; } - void SoundManager::stopTrack(Stream *stream) + void SoundManager::stopTrack(Stream* stream) { mOutput->finishStream(stream); TrackList::iterator iter = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), stream, - [] (const StreamPtr& lhs, Stream* rhs) { return lhs.get() < rhs; }); - if(iter != mActiveTracks.end() && iter->get() == stream) + [](const StreamPtr& lhs, Stream* rhs) { return lhs.get() < rhs; }); + if (iter != mActiveTracks.end() && iter->get() == stream) mActiveTracks.erase(iter); } - double SoundManager::getTrackTimeDelay(Stream *stream) + double SoundManager::getTrackTimeDelay(Stream* stream) { return mOutput->getStreamDelay(stream); } - - Sound* SoundManager::playSound(const std::string& soundId, float volume, float pitch, Type type, PlayMode mode, float offset) + Sound* SoundManager::playSound( + const ESM::RefId& soundId, float volume, float pitch, Type type, PlayMode mode, float offset) { - if(!mOutput->isInitialized()) + if (!mOutput->isInitialized()) return nullptr; - Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); - if(!sfx) return nullptr; + Sound_Buffer* sfx = mSoundBuffers.load(soundId); + if (!sfx) + return nullptr; // Only one copy of given sound can be played at time, so stop previous copy stopSound(sfx, MWWorld::ConstPtr()); @@ -509,37 +507,38 @@ namespace MWSound params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; - } ()); - if(!mOutput->playSound(sound.get(), sfx->getHandle(), offset)) + }()); + if (!mOutput->playSound(sound.get(), sfx->getHandle(), offset)) return nullptr; Sound* result = sound.get(); - mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mActiveSounds[nullptr].mList.emplace_back(std::move(sound), sfx); mSoundBuffers.use(*sfx); return result; } - Sound *SoundManager::playSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId, - float volume, float pitch, Type type, PlayMode mode, - float offset) + Sound* SoundManager::playSound3D(const MWWorld::ConstPtr& ptr, const ESM::RefId& soundId, float volume, float pitch, + Type type, PlayMode mode, float offset) { - if(!mOutput->isInitialized()) + if (!mOutput->isInitialized()) return nullptr; const osg::Vec3f objpos(ptr.getRefData().getPosition().asVec3()); - if ((mode & PlayMode::RemoveAtDistance) && (mListenerPos - objpos).length2() > 2000 * 2000) + const float squaredDist = (mListenerPos - objpos).length2(); + if ((mode & PlayMode::RemoveAtDistance) && squaredDist > 2000 * 2000) return nullptr; // Look up the sound in the ESM data - Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); - if(!sfx) return nullptr; + Sound_Buffer* sfx = mSoundBuffers.load(soundId); + if (!sfx) + return nullptr; // Only one copy of given sound can be played at time on ptr, so stop previous copy stopSound(sfx, ptr); bool played; SoundPtr sound = getSoundRef(); - if(!(mode&PlayMode::NoPlayerLocal) && ptr == MWMechanics::getPlayer()) + if (!(mode & PlayMode::NoPlayerLocal) && ptr == MWMechanics::getPlayer()) { sound->init([&] { SoundParams params; @@ -548,7 +547,7 @@ namespace MWSound params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; - } ()); + }()); played = mOutput->playSound(sound.get(), sfx->getHandle(), offset); } else @@ -558,33 +557,39 @@ namespace MWSound params.mPos = objpos; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); + params.mFadeVolume = initialFadeVolume(squaredDist, sfx, type, mode); params.mPitch = pitch; params.mMinDistance = sfx->getMinDist(); params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; - } ()); + }()); played = mOutput->playSound3D(sound.get(), sfx->getHandle(), offset); } - if(!played) + if (!played) return nullptr; Sound* result = sound.get(); - mActiveSounds[ptr].emplace_back(std::move(sound), sfx); + auto it = mActiveSounds.find(ptr.mRef); + if (it == mActiveSounds.end()) + it = mActiveSounds.emplace(ptr.mRef, ActiveSound{ ptr.mCell, {} }).first; + it->second.mList.emplace_back(std::move(sound), sfx); mSoundBuffers.use(*sfx); return result; } - Sound *SoundManager::playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, - float volume, float pitch, Type type, PlayMode mode, - float offset) + Sound* SoundManager::playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch, + Type type, PlayMode mode, float offset) { - if(!mOutput->isInitialized()) + if (!mOutput->isInitialized()) return nullptr; // Look up the sound in the ESM data - Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); - if(!sfx) return nullptr; + Sound_Buffer* sfx = mSoundBuffers.load(soundId); + if (!sfx) + return nullptr; + + const float squaredDist = (mListenerPos - initialPos).length2(); SoundPtr sound = getSoundRef(); sound->init([&] { @@ -592,125 +597,127 @@ namespace MWSound params.mPos = initialPos; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); + params.mFadeVolume = initialFadeVolume(squaredDist, sfx, type, mode); params.mPitch = pitch; params.mMinDistance = sfx->getMinDist(); params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; - } ()); - if(!mOutput->playSound3D(sound.get(), sfx->getHandle(), offset)) + }()); + if (!mOutput->playSound3D(sound.get(), sfx->getHandle(), offset)) return nullptr; Sound* result = sound.get(); - mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mActiveSounds[nullptr].mList.emplace_back(std::move(sound), sfx); mSoundBuffers.use(*sfx); return result; } - void SoundManager::stopSound(Sound *sound) + void SoundManager::stopSound(Sound* sound) { - if(sound) + if (sound) mOutput->finishSound(sound); } - void SoundManager::stopSound(Sound_Buffer *sfx, const MWWorld::ConstPtr &ptr) + void SoundManager::stopSound(Sound_Buffer* sfx, const MWWorld::ConstPtr& ptr) { - SoundMap::iterator snditer = mActiveSounds.find(ptr); - if(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr.mRef); + if (snditer != mActiveSounds.end()) { - for(SoundBufferRefPair &snd : snditer->second) + for (SoundBufferRefPair& snd : snditer->second.mList) { - if(snd.second == sfx) + if (snd.second == sfx) mOutput->finishSound(snd.first.get()); } } } - void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId) + void SoundManager::stopSound3D(const MWWorld::ConstPtr& ptr, const ESM::RefId& soundId) { - if(!mOutput->isInitialized()) + if (!mOutput->isInitialized()) return; - Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); - if (!sfx) return; + Sound_Buffer* sfx = mSoundBuffers.lookup(soundId); + if (!sfx) + return; stopSound(sfx, ptr); } - void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr) + void SoundManager::stopSound3D(const MWWorld::ConstPtr& ptr) { - SoundMap::iterator snditer = mActiveSounds.find(ptr); - if(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr.mRef); + if (snditer != mActiveSounds.end()) { - for(SoundBufferRefPair &snd : snditer->second) + for (SoundBufferRefPair& snd : snditer->second.mList) mOutput->finishSound(snd.first.get()); } - SaySoundMap::iterator sayiter = mSaySoundsQueue.find(ptr); - if(sayiter != mSaySoundsQueue.end()) - mOutput->finishStream(sayiter->second.get()); - sayiter = mActiveSaySounds.find(ptr); - if(sayiter != mActiveSaySounds.end()) - mOutput->finishStream(sayiter->second.get()); + SaySoundMap::iterator sayiter = mSaySoundsQueue.find(ptr.mRef); + if (sayiter != mSaySoundsQueue.end()) + mOutput->finishStream(sayiter->second.mStream.get()); + sayiter = mActiveSaySounds.find(ptr.mRef); + if (sayiter != mActiveSaySounds.end()) + mOutput->finishStream(sayiter->second.mStream.get()); } - void SoundManager::stopSound(const MWWorld::CellStore *cell) + void SoundManager::stopSound(const MWWorld::CellStore* cell) { - for(SoundMap::value_type &snd : mActiveSounds) + for (auto& [ref, sound] : mActiveSounds) { - if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) + if (ref != nullptr && ref != MWMechanics::getPlayer().mRef && sound.mCell == cell) { - for(SoundBufferRefPair &sndbuf : snd.second) + for (SoundBufferRefPair& sndbuf : sound.mList) mOutput->finishSound(sndbuf.first.get()); } } - for(SaySoundMap::value_type &snd : mSaySoundsQueue) + for (const auto& [ref, sound] : mSaySoundsQueue) { - if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) - mOutput->finishStream(snd.second.get()); + if (ref != nullptr && ref != MWMechanics::getPlayer().mRef && sound.mCell == cell) + mOutput->finishStream(sound.mStream.get()); } - for(SaySoundMap::value_type &snd : mActiveSaySounds) + for (const auto& [ref, sound] : mActiveSaySounds) { - if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) - mOutput->finishStream(snd.second.get()); + if (ref != nullptr && ref != MWMechanics::getPlayer().mRef && sound.mCell == cell) + mOutput->finishStream(sound.mStream.get()); } } - void SoundManager::fadeOutSound3D(const MWWorld::ConstPtr &ptr, - const std::string& soundId, float duration) + void SoundManager::fadeOutSound3D(const MWWorld::ConstPtr& ptr, const ESM::RefId& soundId, float duration) { - SoundMap::iterator snditer = mActiveSounds.find(ptr); - if(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr.mRef); + if (snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer* sfx = mSoundBuffers.lookup(soundId); if (sfx == nullptr) return; - for(SoundBufferRefPair &sndbuf : snditer->second) + for (SoundBufferRefPair& sndbuf : snditer->second.mList) { - if(sndbuf.second == sfx) + if (sndbuf.second == sfx) sndbuf.first->setFadeout(duration); } } } - bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr &ptr, const std::string& soundId) const + bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr& ptr, const ESM::RefId& soundId) const { - SoundMap::const_iterator snditer = mActiveSounds.find(ptr); - if(snditer != mActiveSounds.end()) + SoundMap::const_iterator snditer = mActiveSounds.find(ptr.mRef); + if (snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); - return std::find_if(snditer->second.cbegin(), snditer->second.cend(), - [this,sfx](const SoundBufferRefPair &snd) -> bool - { return snd.second == sfx && mOutput->isSoundPlaying(snd.first.get()); } - ) != snditer->second.cend(); + Sound_Buffer* sfx = mSoundBuffers.lookup(soundId); + return std::find_if(snditer->second.mList.cbegin(), snditer->second.mList.cend(), + [this, sfx](const SoundBufferRefPair& snd) -> bool { + return snd.second == sfx && mOutput->isSoundPlaying(snd.first.get()); + }) + != snditer->second.mList.cend(); } return false; } void SoundManager::pauseSounds(BlockerType blocker, int types) { - if(mOutput->isInitialized()) + if (mOutput->isInitialized()) { if (mPausedSoundTypes[blocker] != 0) resumeSounds(blocker); @@ -723,7 +730,7 @@ namespace MWSound void SoundManager::resumeSounds(BlockerType blocker) { - if(mOutput->isInitialized()) + if (mOutput->isInitialized()) { mPausedSoundTypes[blocker] = 0; int types = int(Type::Mask); @@ -757,16 +764,16 @@ namespace MWSound void SoundManager::updateRegionSound(float duration) { - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::ConstPtr player = world->getPlayerPtr(); - const ESM::Cell *cell = player.getCell()->getCell(); + auto cell = player.getCell()->getCell(); if (!cell->isExterior()) return; if (mCurrentRegionSound && mOutput->isSoundPlaying(mCurrentRegionSound)) return; - if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->mRegion, *world)) + if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->getRegion())) mCurrentRegionSound = playSound(*next, 1.0f, 1.0f); } @@ -774,7 +781,8 @@ namespace MWSound { MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::ConstPtr player = world->getPlayerPtr(); - const ESM::Cell *curcell = player.getCell()->getCell(); + + const MWWorld::Cell* curcell = player.getCell()->getCell(); const auto update = mWaterSoundUpdater.update(player, *world); WaterSoundAction action; @@ -787,10 +795,10 @@ namespace MWSound break; case WaterSoundAction::SetVolume: mNearWaterSound->setVolume(update.mVolume * sfx->getVolume()); + mNearWaterSound->setFade(sSfxFadeInDuration, 1.0f, Play_FadeExponential); break; case WaterSoundAction::FinishSound: - mOutput->finishSound(mNearWaterSound); - mNearWaterSound = nullptr; + mNearWaterSound->setFade(sSfxFadeOutDuration, 0.0f, Play_FadeExponential | Play_StopAtFadeEnd); break; case WaterSoundAction::PlaySound: if (mNearWaterSound) @@ -803,41 +811,62 @@ namespace MWSound } std::pair SoundManager::getWaterSoundAction( - const WaterSoundUpdate& update, const ESM::Cell* cell) const + const WaterSoundUpdate& update, const MWWorld::Cell* cell) const { if (mNearWaterSound) { if (update.mVolume == 0.0f) - return {WaterSoundAction::FinishSound, nullptr}; + return { WaterSoundAction::FinishSound, nullptr }; bool soundIdChanged = false; Sound_Buffer* sfx = mSoundBuffers.lookup(update.mId); if (mLastCell != cell) { - const auto snditer = mActiveSounds.find(MWWorld::ConstPtr()); + const auto snditer = mActiveSounds.find(nullptr); if (snditer != mActiveSounds.end()) { - const auto pairiter = std::find_if( - snditer->second.begin(), snditer->second.end(), - [this](const SoundBufferRefPairList::value_type &item) -> bool - { return mNearWaterSound == item.first.get(); } - ); - if (pairiter != snditer->second.end() && pairiter->second != sfx) + const auto pairiter = std::find_if(snditer->second.mList.begin(), snditer->second.mList.end(), + [this](const SoundBufferRefPairList::value_type& item) -> bool { + return mNearWaterSound == item.first.get(); + }); + if (pairiter != snditer->second.mList.end() && pairiter->second != sfx) soundIdChanged = true; } } if (soundIdChanged) - return {WaterSoundAction::PlaySound, nullptr}; + return { WaterSoundAction::PlaySound, nullptr }; if (sfx) - return {WaterSoundAction::SetVolume, sfx}; + return { WaterSoundAction::SetVolume, sfx }; } else if (update.mVolume > 0.0f) - return {WaterSoundAction::PlaySound, nullptr}; + return { WaterSoundAction::PlaySound, nullptr }; - return {WaterSoundAction::DoNothing, nullptr}; + return { WaterSoundAction::DoNothing, nullptr }; + } + + void SoundManager::cull3DSound(SoundBase* sound) + { + // Hard-coded distance of 2000.0f is from vanilla Morrowind + const float maxDist = sound->getDistanceCull() ? 2000.0f : sound->getMaxDistance(); + const float squaredMaxDist = maxDist * maxDist; + + const osg::Vec3f pos = sound->getPosition(); + const float squaredDist = (mListenerPos - pos).length2(); + + if (squaredDist > squaredMaxDist) + { + // If getDistanceCull() is set, delete the sound after it has faded out + sound->setFade( + sSfxFadeOutDuration, 0.0f, Play_FadeExponential | (sound->getDistanceCull() ? Play_StopAtFadeEnd : 0)); + } + else + { + // Fade sounds back in once they are in range + sound->setFade(sSfxFadeInDuration, 1.0f, Play_FadeExponential); + } } void SoundManager::updateSounds(float duration) @@ -862,52 +891,42 @@ namespace MWSound mTimePassed = 0.0f; // Make sure music is still playing - if(!isMusicPlaying() && !mCurrentPlaylist.empty()) + if (!isMusicPlaying() && !mCurrentPlaylist.empty()) startRandomTitle(); Environment env = Env_Normal; if (mListenerUnderwater) env = Env_Underwater; - else if(mUnderwaterSound) + else if (mUnderwaterSound) { mOutput->finishSound(mUnderwaterSound); mUnderwaterSound = nullptr; } mOutput->startUpdate(); - mOutput->updateListener( - mListenerPos, - mListenerDir, - mListenerUp, - env - ); + mOutput->updateListener(mListenerPos, mListenerDir, mListenerUp, env); updateMusic(duration); // Check if any sounds are finished playing, and trash them SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + while (snditer != mActiveSounds.end()) { MWWorld::ConstPtr ptr = snditer->first; - SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); - while(sndidx != snditer->second.end()) + SoundBufferRefPairList::iterator sndidx = snditer->second.mList.begin(); + while (sndidx != snditer->second.mList.end()) { - Sound *sound = sndidx->first.get(); + Sound* sound = sndidx->first.get(); - if(!ptr.isEmpty() && sound->getIs3D()) + if (sound->getIs3D()) { - const ESM::Position &pos = ptr.getRefData().getPosition(); - const osg::Vec3f objpos(pos.asVec3()); - sound->setPosition(objpos); + if (!ptr.isEmpty()) + sound->setPosition(ptr.getRefData().getPosition().asVec3()); - if(sound->getDistanceCull()) - { - if((mListenerPos - objpos).length2() > 2000*2000) - mOutput->finishSound(sound); - } + cull3DSound(sound); } - if(!mOutput->isSoundPlaying(sound)) + if (!sound->updateFade(duration) || !mOutput->isSoundPlaying(sound)) { mOutput->finishSound(sound); if (sound == mUnderwaterSound) @@ -915,59 +934,53 @@ namespace MWSound if (sound == mNearWaterSound) mNearWaterSound = nullptr; mSoundBuffers.release(*sndidx->second); - sndidx = snditer->second.erase(sndidx); + sndidx = snditer->second.mList.erase(sndidx); } else { - sound->updateFade(duration); - mOutput->updateSound(sound); ++sndidx; } } - if(snditer->second.empty()) + if (snditer->second.mList.empty()) snditer = mActiveSounds.erase(snditer); else ++snditer; } SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); - while(sayiter != mActiveSaySounds.end()) + while (sayiter != mActiveSaySounds.end()) { MWWorld::ConstPtr ptr = sayiter->first; - Stream *sound = sayiter->second.get(); - if(!ptr.isEmpty() && sound->getIs3D()) + Stream* sound = sayiter->second.mStream.get(); + if (sound->getIs3D()) { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); - sound->setPosition(pos); - - if(sound->getDistanceCull()) + if (!ptr.isEmpty()) { - if((mListenerPos - pos).length2() > 2000*2000) - mOutput->finishStream(sound); + MWBase::World* world = MWBase::Environment::get().getWorld(); + sound->setPosition(world->getActorHeadTransform(ptr).getTrans()); } + + cull3DSound(sound); } - if(!mOutput->isStreamPlaying(sound)) + if (!sound->updateFade(duration) || !mOutput->isStreamPlaying(sound)) { mOutput->finishStream(sound); - mActiveSaySounds.erase(sayiter++); + sayiter = mActiveSaySounds.erase(sayiter); } else { - sound->updateFade(duration); - mOutput->updateStream(sound); ++sayiter; } } TrackList::iterator trkiter = mActiveTracks.begin(); - for(;trkiter != mActiveTracks.end();++trkiter) + while (trkiter != mActiveTracks.end()) { - Stream *sound = trkiter->get(); - if(!mOutput->isStreamPlaying(sound)) + Stream* sound = trkiter->get(); + if (!mOutput->isStreamPlaying(sound)) { mOutput->finishStream(sound); trkiter = mActiveTracks.erase(trkiter); @@ -981,73 +994,71 @@ namespace MWSound } } - if(mListenerUnderwater) + if (mListenerUnderwater) { // Play underwater sound (after updating sounds) - if(!mUnderwaterSound) - mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Type::Sfx, PlayMode::LoopNoEnv); + if (!mUnderwaterSound) + mUnderwaterSound + = playSound(ESM::RefId::stringRefId("Underwater"), 1.0f, 1.0f, Type::Sfx, PlayMode::LoopNoEnv); } mOutput->finishUpdate(); } - void SoundManager::updateMusic(float duration) { - if (!mNextMusic.empty()) + if (!mMusic || !mMusic->updateFade(duration) || !mOutput->isStreamPlaying(mMusic.get())) { - mMusic->updateFade(duration); - - mOutput->updateStream(mMusic.get()); - - if (mMusic->getRealVolume() <= 0.f) + stopMusic(); + if (!mNextMusic.empty()) { streamMusicFull(mNextMusic); mNextMusic.clear(); } } + else + { + mOutput->updateStream(mMusic.get()); + } } - void SoundManager::update(float duration) { - if(!mOutput->isInitialized() || mPlaybackPaused) + if (!mOutput->isInitialized() || mPlaybackPaused) return; updateSounds(duration); - if (MWBase::Environment::get().getStateManager()->getState()!= - MWBase::StateManager::State_NoGame) + if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) { updateRegionSound(duration); updateWaterSound(); } } - void SoundManager::processChangedSettings(const Settings::CategorySettingVector& settings) { mVolumeSettings.update(); - if(!mOutput->isInitialized()) + if (!mOutput->isInitialized()) return; mOutput->startUpdate(); - for(SoundMap::value_type &snd : mActiveSounds) + for (SoundMap::value_type& snd : mActiveSounds) { - for(SoundBufferRefPair &sndbuf : snd.second) + for (SoundBufferRefPair& sndbuf : snd.second.mList) { - Sound *sound = sndbuf.first.get(); + Sound* sound = sndbuf.first.get(); sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateSound(sound); } } - for(SaySoundMap::value_type &snd : mActiveSaySounds) + for (SaySoundMap::value_type& snd : mActiveSaySounds) { - Stream *sound = snd.second.get(); + Stream* sound = snd.second.mStream.get(); sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } - for(SaySoundMap::value_type &snd : mSaySoundsQueue) + for (SaySoundMap::value_type& snd : mSaySoundsQueue) { - Stream *sound = snd.second.get(); + Stream* sound = snd.second.mStream.get(); sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } @@ -1056,7 +1067,7 @@ namespace MWSound sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound.get()); } - if(mMusic) + if (mMusic) { mMusic->setBaseVolume(volumeFromType(mMusic->getPlayType())); mOutput->updateStream(mMusic.get()); @@ -1064,100 +1075,110 @@ namespace MWSound mOutput->finishUpdate(); } - void SoundManager::setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) + void SoundManager::setListenerPosDir( + const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater) { mListenerPos = pos; mListenerDir = dir; - mListenerUp = up; + mListenerUp = up; mListenerUnderwater = underwater; mWaterSoundUpdater.setUnderwater(underwater); } - void SoundManager::updatePtr(const MWWorld::ConstPtr &old, const MWWorld::ConstPtr &updated) + void SoundManager::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) { - SoundMap::iterator snditer = mActiveSounds.find(old); - if(snditer != mActiveSounds.end()) - { - SoundBufferRefPairList sndlist = std::move(snditer->second); - mActiveSounds.erase(snditer); - mActiveSounds.emplace(updated, std::move(sndlist)); - } + SoundMap::iterator snditer = mActiveSounds.find(old.mRef); + if (snditer != mActiveSounds.end()) + snditer->second.mCell = updated.mCell; - SaySoundMap::iterator sayiter = mSaySoundsQueue.find(old); - if(sayiter != mSaySoundsQueue.end()) - { - StreamPtr stream = std::move(sayiter->second); - mSaySoundsQueue.erase(sayiter); - mSaySoundsQueue.emplace(updated, std::move(stream)); - } + if (const auto it = mSaySoundsQueue.find(old.mRef); it != mSaySoundsQueue.end()) + it->second.mCell = updated.mCell; - sayiter = mActiveSaySounds.find(old); - if(sayiter != mActiveSaySounds.end()) - { - StreamPtr stream = std::move(sayiter->second); - mActiveSaySounds.erase(sayiter); - mActiveSaySounds.emplace(updated, std::move(stream)); - } + if (const auto it = mActiveSaySounds.find(old.mRef); it != mActiveSaySounds.end()) + it->second.mCell = updated.mCell; } // Default readAll implementation, for decoders that can't do anything // better - void Sound_Decoder::readAll(std::vector &output) + void Sound_Decoder::readAll(std::vector& output) { size_t total = output.size(); size_t got; - output.resize(total+32768); - while((got=read(&output[total], output.size()-total)) > 0) + output.resize(total + 32768); + while ((got = read(&output[total], output.size() - total)) > 0) { total += got; - output.resize(total*2); + output.resize(total * 2); } output.resize(total); } - - const char *getSampleTypeName(SampleType type) + const char* getSampleTypeName(SampleType type) { - switch(type) + switch (type) { - case SampleType_UInt8: return "U8"; - case SampleType_Int16: return "S16"; - case SampleType_Float32: return "Float32"; + case SampleType_UInt8: + return "U8"; + case SampleType_Int16: + return "S16"; + case SampleType_Float32: + return "Float32"; } return "(unknown sample type)"; } - const char *getChannelConfigName(ChannelConfig config) + const char* getChannelConfigName(ChannelConfig config) { - switch(config) + switch (config) { - case ChannelConfig_Mono: return "Mono"; - case ChannelConfig_Stereo: return "Stereo"; - case ChannelConfig_Quad: return "Quad"; - case ChannelConfig_5point1: return "5.1 Surround"; - case ChannelConfig_7point1: return "7.1 Surround"; + case ChannelConfig_Mono: + return "Mono"; + case ChannelConfig_Stereo: + return "Stereo"; + case ChannelConfig_Quad: + return "Quad"; + case ChannelConfig_5point1: + return "5.1 Surround"; + case ChannelConfig_7point1: + return "7.1 Surround"; } return "(unknown channel config)"; } size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type) { - switch(config) + switch (config) { - case ChannelConfig_Mono: frames *= 1; break; - case ChannelConfig_Stereo: frames *= 2; break; - case ChannelConfig_Quad: frames *= 4; break; - case ChannelConfig_5point1: frames *= 6; break; - case ChannelConfig_7point1: frames *= 8; break; + case ChannelConfig_Mono: + frames *= 1; + break; + case ChannelConfig_Stereo: + frames *= 2; + break; + case ChannelConfig_Quad: + frames *= 4; + break; + case ChannelConfig_5point1: + frames *= 6; + break; + case ChannelConfig_7point1: + frames *= 8; + break; } - switch(type) + switch (type) { - case SampleType_UInt8: frames *= 1; break; - case SampleType_Int16: frames *= 2; break; - case SampleType_Float32: frames *= 4; break; + case SampleType_UInt8: + frames *= 1; + break; + case SampleType_Int16: + frames *= 2; + break; + case SampleType_Float32: + frames *= 4; + break; } return frames; } @@ -1171,9 +1192,9 @@ namespace MWSound { SoundManager::stopMusic(); - for(SoundMap::value_type &snd : mActiveSounds) + for (SoundMap::value_type& snd : mActiveSounds) { - for(SoundBufferRefPair &sndbuf : snd.second) + for (SoundBufferRefPair& sndbuf : snd.second.mList) { mOutput->finishSound(sndbuf.first.get()); mSoundBuffers.release(*sndbuf.second); @@ -1183,15 +1204,15 @@ namespace MWSound mUnderwaterSound = nullptr; mNearWaterSound = nullptr; - for(SaySoundMap::value_type &snd : mSaySoundsQueue) - mOutput->finishStream(snd.second.get()); + for (SaySoundMap::value_type& snd : mSaySoundsQueue) + mOutput->finishStream(snd.second.mStream.get()); mSaySoundsQueue.clear(); - for(SaySoundMap::value_type &snd : mActiveSaySounds) - mOutput->finishStream(snd.second.get()); + for (SaySoundMap::value_type& snd : mActiveSaySounds) + mOutput->finishStream(snd.second.mStream.get()); mActiveSaySounds.clear(); - for(StreamPtr& sound : mActiveTracks) + for (StreamPtr& sound : mActiveTracks) mOutput->finishStream(sound.get()); mActiveTracks.clear(); mPlaybackPaused = false; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 934402cd4..474c8f50b 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -1,23 +1,23 @@ #ifndef GAME_SOUND_SOUNDMANAGER_H #define GAME_SOUND_SOUNDMANAGER_H +#include #include #include -#include -#include #include +#include -#include -#include #include +#include +#include #include "../mwbase/soundmanager.hpp" #include "regionsoundselector.hpp" -#include "watersoundupdater.hpp" +#include "sound_buffer.hpp" #include "type.hpp" #include "volumesettings.hpp" -#include "sound_buffer.hpp" +#include "watersoundupdater.hpp" namespace VFS { @@ -30,10 +30,16 @@ namespace ESM struct Cell; } +namespace MWWorld +{ + class Cell; +} + namespace MWSound { class Sound_Output; struct Sound_Decoder; + class SoundBase; class Sound; class Stream; @@ -63,10 +69,23 @@ namespace MWSound typedef std::pair SoundBufferRefPair; typedef std::vector SoundBufferRefPairList; - typedef std::map SoundMap; + + struct ActiveSound + { + const MWWorld::CellStore* mCell = nullptr; + SoundBufferRefPairList mList; + }; + + typedef std::map SoundMap; SoundMap mActiveSounds; - typedef std::map SaySoundMap; + struct SaySound + { + const MWWorld::CellStore* mCell; + StreamPtr mStream; + }; + + typedef std::map SaySoundMap; SaySoundMap mSaySoundsQueue; SaySoundMap mActiveSaySounds; @@ -83,8 +102,8 @@ namespace MWSound int mPausedSoundTypes[BlockerType::MaxCount] = {}; - Sound *mUnderwaterSound; - Sound *mNearWaterSound; + Sound* mUnderwaterSound; + Sound* mNearWaterSound; std::string mNextMusic; bool mPlaybackPaused; @@ -93,24 +112,26 @@ namespace MWSound float mTimePassed; - const ESM::Cell *mLastCell; + const MWWorld::Cell* mLastCell; Sound* mCurrentRegionSound; - Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); + Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound* sound); // returns a decoder to start streaming, or nullptr if the sound was not found - DecoderPtr loadVoice(const std::string &voicefile); + DecoderPtr loadVoice(const std::string& voicefile); SoundPtr getSoundRef(); StreamPtr getStreamRef(); - StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal); + StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal); void streamMusicFull(const std::string& filename); void advanceMusic(const std::string& filename); void startRandomTitle(); + void cull3DSound(SoundBase* sound); + void updateSounds(float duration); void updateRegionSound(float duration); void updateWaterSound(); @@ -126,17 +147,17 @@ namespace MWSound PlaySound, }; - std::pair getWaterSoundAction(const WaterSoundUpdate& update, - const ESM::Cell* cell) const; + std::pair getWaterSoundAction( + const WaterSoundUpdate& update, const MWWorld::Cell* cell) const; - SoundManager(const SoundManager &rhs); - SoundManager& operator=(const SoundManager &rhs); + SoundManager(const SoundManager& rhs); + SoundManager& operator=(const SoundManager& rhs); protected: DecoderPtr getDecoder(); friend class OpenAL_Output; - void stopSound(Sound_Buffer *sfx, const MWWorld::ConstPtr &ptr); + void stopSound(Sound_Buffer* sfx, const MWWorld::ConstPtr& ptr); ///< Stop the given object from playing given sound buffer. public: @@ -155,14 +176,12 @@ namespace MWSound bool isMusicPlaying() override; ///< Returns true if music is playing - void playPlaylist(const std::string &playlist) override; + void playPlaylist(const std::string& playlist) override; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist + /// Title music playlist is predefined - void playTitleMusic() override; - ///< Start playing title music - - void say(const MWWorld::ConstPtr &reference, const std::string& filename) override; + void say(const MWWorld::ConstPtr& reference, const std::string& filename) override; ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. @@ -170,13 +189,13 @@ namespace MWSound ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. - bool sayActive(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const override; + bool sayActive(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) const override; ///< Is actor not speaking? - bool sayDone(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const override; + bool sayDone(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) const override; ///< For scripting backward compatibility - void stopSay(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) override; + void stopSay(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) override; ///< Stop an actor speaking float getSaySoundLoudness(const MWWorld::ConstPtr& reference) const override; @@ -184,55 +203,57 @@ namespace MWSound /// and get an average loudness value (scale [0,1]) at the current time position. /// If the actor is not saying anything, returns 0. - Stream *playTrack(const DecoderPtr& decoder, Type type) override; + Stream* playTrack(const DecoderPtr& decoder, Type type) override; ///< Play a 2D audio track, using a custom decoder - void stopTrack(Stream *stream) override; + void stopTrack(Stream* stream) override; ///< Stop the given audio track from playing - double getTrackTimeDelay(Stream *stream) override; + double getTrackTimeDelay(Stream* stream) override; ///< Retives the time delay, in seconds, of the audio track (must be a sound /// returned by \ref playTrack). Only intended to be called by the track /// decoder's read method. - Sound *playSound(const std::string& soundId, float volume, float pitch, Type type=Type::Sfx, PlayMode mode=PlayMode::Normal, float offset=0) override; + Sound* playSound(const ESM::RefId& soundId, float volume, float pitch, Type type = Type::Sfx, + PlayMode mode = PlayMode::Normal, float offset = 0) override; ///< Play a sound, independently of 3D-position ///< @param offset Number of seconds into the sound to start playback. - Sound *playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, - float volume, float pitch, Type type=Type::Sfx, - PlayMode mode=PlayMode::Normal, float offset=0) override; - ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. + Sound* playSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId, float volume, float pitch, + Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0) override; + ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless + ///< Play_NoTrack is specified. ///< @param offset Number of seconds into the sound to start playback. - Sound *playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, - float volume, float pitch, Type type, PlayMode mode, float offset=0) override; - ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition. + Sound* playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch, + Type type, PlayMode mode, float offset = 0) override; + ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using + ///< Sound::setPosition. ///< @param offset Number of seconds into the sound to start playback. - void stopSound(Sound *sound) override; + void stopSound(Sound* sound) override; ///< Stop the given sound from playing /// @note no-op if \a sound is null - void stopSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId) override; + void stopSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) override; ///< Stop the given object from playing the given sound, - void stopSound3D(const MWWorld::ConstPtr &reference) override; + void stopSound3D(const MWWorld::ConstPtr& reference) override; ///< Stop the given object from playing all sounds. - void stopSound(const MWWorld::CellStore *cell) override; + void stopSound(const MWWorld::CellStore* cell) override; ///< Stop all sounds for the given cell. - void fadeOutSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float duration) override; + void fadeOutSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId, float duration) override; ///< Fade out given sound (that is already playing) of given object ///< @param reference Reference to object, whose sound is faded out ///< @param soundId ID of the sound to fade out. ///< @param duration Time until volume reaches 0. - bool getSoundPlaying(const MWWorld::ConstPtr &reference, const std::string& soundId) const override; + bool getSoundPlaying(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) const override; ///< Is the given sound currently playing on the given object? - void pauseSounds(MWSound::BlockerType blocker, int types=int(Type::Mask)) override; + void pauseSounds(MWSound::BlockerType blocker, int types = int(Type::Mask)) override; ///< Pauses all currently playing sounds, including music. void resumeSounds(MWSound::BlockerType blocker) override; @@ -241,11 +262,12 @@ namespace MWSound void pausePlayback() override; void resumePlayback() override; - void update(float duration) override; + void update(float duration); - void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) override; + void setListenerPosDir( + const osg::Vec3f& pos, const osg::Vec3f& dir, const osg::Vec3f& up, bool underwater) override; - void updatePtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) override; + void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) override; void clear() override; }; diff --git a/apps/openmw/mwsound/type.hpp b/apps/openmw/mwsound/type.hpp index 9f95bfa40..d7bc6dd05 100644 --- a/apps/openmw/mwsound/type.hpp +++ b/apps/openmw/mwsound/type.hpp @@ -5,12 +5,12 @@ namespace MWSound { enum class Type { - Sfx = 1 << 4, /* Normal SFX sound */ - Voice = 1 << 5, /* Voice sound */ - Foot = 1 << 6, /* Footstep sound */ - Music = 1 << 7, /* Music track */ - Movie = 1 << 8, /* Movie audio track */ - Mask = Sfx | Voice | Foot | Music | Movie + Sfx = 1 << 5, /* Normal SFX sound */ + Voice = 1 << 6, /* Voice sound */ + Foot = 1 << 7, /* Footstep sound */ + Music = 1 << 8, /* Music track */ + Movie = 1 << 9, /* Movie audio track */ + Mask = Sfx | Voice | Foot | Music | Movie }; } diff --git a/apps/openmw/mwsound/volumesettings.cpp b/apps/openmw/mwsound/volumesettings.cpp index cc4eac3d6..4306bc526 100644 --- a/apps/openmw/mwsound/volumesettings.cpp +++ b/apps/openmw/mwsound/volumesettings.cpp @@ -10,16 +10,16 @@ namespace MWSound { float clamp(float value) { - return std::max(0.0f, std::min(1.0f, value)); + return std::clamp(value, 0.f, 1.f); } } VolumeSettings::VolumeSettings() - : mMasterVolume(clamp(Settings::Manager::getFloat("master volume", "Sound"))), - mSFXVolume(clamp(Settings::Manager::getFloat("sfx volume", "Sound"))), - mMusicVolume(clamp(Settings::Manager::getFloat("music volume", "Sound"))), - mVoiceVolume(clamp(Settings::Manager::getFloat("voice volume", "Sound"))), - mFootstepsVolume(clamp(Settings::Manager::getFloat("footsteps volume", "Sound"))) + : mMasterVolume(clamp(Settings::Manager::getFloat("master volume", "Sound"))) + , mSFXVolume(clamp(Settings::Manager::getFloat("sfx volume", "Sound"))) + , mMusicVolume(clamp(Settings::Manager::getFloat("music volume", "Sound"))) + , mVoiceVolume(clamp(Settings::Manager::getFloat("voice volume", "Sound"))) + , mFootstepsVolume(clamp(Settings::Manager::getFloat("footsteps volume", "Sound"))) { } @@ -27,7 +27,7 @@ namespace MWSound { float volume = mMasterVolume; - switch(type) + switch (type) { case Type::Sfx: volume *= mSFXVolume; diff --git a/apps/openmw/mwsound/volumesettings.hpp b/apps/openmw/mwsound/volumesettings.hpp index eec5f5c1b..884a3e1bc 100644 --- a/apps/openmw/mwsound/volumesettings.hpp +++ b/apps/openmw/mwsound/volumesettings.hpp @@ -7,19 +7,19 @@ namespace MWSound { class VolumeSettings { - public: - VolumeSettings(); + public: + VolumeSettings(); - float getVolumeFromType(Type type) const; + float getVolumeFromType(Type type) const; - void update(); + void update(); - private: - float mMasterVolume; - float mSFXVolume; - float mMusicVolume; - float mVoiceVolume; - float mFootstepsVolume; + private: + float mMasterVolume; + float mSFXVolume; + float mMusicVolume; + float mVoiceVolume; + float mFootstepsVolume; }; } diff --git a/apps/openmw/mwsound/watersoundupdater.cpp b/apps/openmw/mwsound/watersoundupdater.cpp index b1646c404..51385dcde 100644 --- a/apps/openmw/mwsound/watersoundupdater.cpp +++ b/apps/openmw/mwsound/watersoundupdater.cpp @@ -4,7 +4,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/ptr.hpp" -#include +#include #include @@ -53,7 +53,8 @@ namespace MWSound { const float terrainX = pos.x() - mSettings.mNearWaterRadius + x * step; const float terrainY = pos.y() - mSettings.mNearWaterRadius + y * step; - const float height = world.getTerrainHeightAt(osg::Vec3f(terrainX, terrainY, 0.0f)); + const float height = world.getTerrainHeightAt( + osg::Vec3f(terrainX, terrainY, 0.0f), cell.getCell()->getWorldSpace()); if (height < 0) underwaterPoints++; diff --git a/apps/openmw/mwsound/watersoundupdater.hpp b/apps/openmw/mwsound/watersoundupdater.hpp index b20b9db6c..b0538b984 100644 --- a/apps/openmw/mwsound/watersoundupdater.hpp +++ b/apps/openmw/mwsound/watersoundupdater.hpp @@ -1,6 +1,7 @@ #ifndef GAME_SOUND_WATERSOUNDUPDATER_H #define GAME_SOUND_WATERSOUNDUPDATER_H +#include "components/esm/refid.hpp" #include namespace MWBase @@ -21,33 +22,30 @@ namespace MWSound int mNearWaterPoints; float mNearWaterIndoorTolerance; float mNearWaterOutdoorTolerance; - std::string mNearWaterIndoorID; - std::string mNearWaterOutdoorID; + ESM::RefId mNearWaterIndoorID; + ESM::RefId mNearWaterOutdoorID; }; struct WaterSoundUpdate { - std::string mId; + ESM::RefId mId; float mVolume; }; class WaterSoundUpdater { - public: - explicit WaterSoundUpdater(const WaterSoundUpdaterSettings& settings); + public: + explicit WaterSoundUpdater(const WaterSoundUpdaterSettings& settings); - WaterSoundUpdate update(const MWWorld::ConstPtr& player, const MWBase::World& world) const; + WaterSoundUpdate update(const MWWorld::ConstPtr& player, const MWBase::World& world) const; - void setUnderwater(bool value) - { - mListenerUnderwater = value; - } + void setUnderwater(bool value) { mListenerUnderwater = value; } - private: - const WaterSoundUpdaterSettings mSettings; - bool mListenerUnderwater = false; + private: + const WaterSoundUpdaterSettings mSettings; + bool mListenerUnderwater = false; - float getVolume(const MWWorld::ConstPtr& player, const MWBase::World& world) const; + float getVolume(const MWWorld::ConstPtr& player, const MWBase::World& world) const; }; } diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index df1ab1bdf..85f5087fe 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -1,153 +1,167 @@ #include "character.hpp" +#include #include +#include #include +#include +#include -#include - -#include #include +#include +#include -bool MWState::operator< (const Slot& left, const Slot& right) +#include + +bool MWState::operator<(const Slot& left, const Slot& right) { - return left.mTimeStamp& contentFiles) +{ + for (const std::string& c : contentFiles) + { + if (Misc::StringUtils::ciEndsWith(c, ".esm") || Misc::StringUtils::ciEndsWith(c, ".omwgame")) + return c; + } + return ""; +} -void MWState::Character::addSlot (const boost::filesystem::path& path, const std::string& game) +void MWState::Character::addSlot(const std::filesystem::path& path, const std::string& game) { Slot slot; slot.mPath = path; - slot.mTimeStamp = boost::filesystem::last_write_time (path); + slot.mTimeStamp = std::filesystem::last_write_time(path); ESM::ESMReader reader; - reader.open (slot.mPath.string()); + reader.open(slot.mPath); - if (reader.getRecName()!=ESM::REC_SAVE) + if (reader.getRecName() != ESM::REC_SAVE) return; // invalid save file -> ignore reader.getRecHeader(); - slot.mProfile.load (reader); + slot.mProfile.load(reader); - if (Misc::StringUtils::lowerCase (slot.mProfile.mContentFiles.at (0))!= - Misc::StringUtils::lowerCase (game)) + if (!Misc::StringUtils::ciEqual(getFirstGameFile(slot.mProfile.mContentFiles), game)) return; // this file is for a different game -> ignore - mSlots.push_back (slot); + mSlots.push_back(slot); } -void MWState::Character::addSlot (const ESM::SavedGame& profile) +void MWState::Character::addSlot(const ESM::SavedGame& profile) { Slot slot; std::ostringstream stream; // The profile description is user-supplied, so we need to escape the path - for (std::string::const_iterator it = profile.mDescription.begin(); it != profile.mDescription.end(); ++it) + Utf8Stream description(profile.mDescription); + while (!description.eof()) { - if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters - stream << *it; + auto c = description.consume(); + if (c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters + stream << static_cast(c); else - stream << "_"; + stream << '_'; } const std::string ext = ".omwsave"; slot.mPath = mPath / (stream.str() + ext); // Append an index if necessary to ensure a unique file - int i=0; - while (boost::filesystem::exists(slot.mPath)) + int i = 0; + while (std::filesystem::exists(slot.mPath)) { const std::string test = stream.str() + " - " + std::to_string(++i); slot.mPath = mPath / (test + ext); } slot.mProfile = profile; - slot.mTimeStamp = std::time (nullptr); + slot.mTimeStamp = std::filesystem::file_time_type::clock::now(); - mSlots.push_back (slot); + mSlots.push_back(slot); } -MWState::Character::Character (const boost::filesystem::path& saves, const std::string& game) -: mPath (saves) +MWState::Character::Character(std::filesystem::path saves, const std::string& game) + : mPath(std::move(saves)) { - if (!boost::filesystem::is_directory (mPath)) + if (!std::filesystem::is_directory(mPath)) { - boost::filesystem::create_directories (mPath); + std::filesystem::create_directories(mPath); } else { - for (boost::filesystem::directory_iterator iter (mPath); - iter!=boost::filesystem::directory_iterator(); ++iter) + for (const auto& iter : std::filesystem::directory_iterator(mPath)) { - boost::filesystem::path slotPath = *iter; - try { - addSlot (slotPath, game); + addSlot(iter, game); } - catch (...) {} // ignoring bad saved game files for now + catch (...) + { + } // ignoring bad saved game files for now } - std::sort (mSlots.begin(), mSlots.end()); + std::sort(mSlots.begin(), mSlots.end()); } } void MWState::Character::cleanup() { - if (mSlots.size() == 0) + if (mSlots.empty()) { // All slots are gone, no need to keep the empty directory - if (boost::filesystem::is_directory (mPath)) + if (std::filesystem::is_directory(mPath)) { // Extra safety check to make sure the directory is empty (e.g. slots failed to parse header) - boost::filesystem::directory_iterator it(mPath); - if (it == boost::filesystem::directory_iterator()) - boost::filesystem::remove_all(mPath); + std::filesystem::directory_iterator it(mPath); + if (it == std::filesystem::directory_iterator()) + std::filesystem::remove_all(mPath); } } } -const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile) +const MWState::Slot* MWState::Character::createSlot(const ESM::SavedGame& profile) { - addSlot (profile); + addSlot(profile); return &mSlots.back(); } -void MWState::Character::deleteSlot (const Slot *slot) +void MWState::Character::deleteSlot(const Slot* slot) { - int index = slot - &mSlots[0]; + int index = slot - mSlots.data(); - if (index<0 || index>=static_cast (mSlots.size())) + if (index < 0 || index >= static_cast(mSlots.size())) { // sanity check; not entirely reliable - throw std::logic_error ("slot not found"); + throw std::logic_error("slot not found"); } - boost::filesystem::remove(slot->mPath); + std::filesystem::remove(slot->mPath); - mSlots.erase (mSlots.begin()+index); + mSlots.erase(mSlots.begin() + index); } -const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile) +const MWState::Slot* MWState::Character::updateSlot(const Slot* slot, const ESM::SavedGame& profile) { - int index = slot - &mSlots[0]; + int index = slot - mSlots.data(); - if (index<0 || index>=static_cast (mSlots.size())) + if (index < 0 || index >= static_cast(mSlots.size())) { // sanity check; not entirely reliable - throw std::logic_error ("slot not found"); + throw std::logic_error("slot not found"); } Slot newSlot = *slot; newSlot.mProfile = profile; - newSlot.mTimeStamp = std::time (nullptr); + newSlot.mTimeStamp = std::filesystem::file_time_type::clock::now(); - mSlots.erase (mSlots.begin()+index); + mSlots.erase(mSlots.begin() + index); - mSlots.push_back (newSlot); + mSlots.push_back(newSlot); return &mSlots.back(); } @@ -162,26 +176,21 @@ MWState::Character::SlotIterator MWState::Character::end() const return mSlots.rend(); } -ESM::SavedGame MWState::Character::getSignature() const +const ESM::SavedGame& MWState::Character::getSignature() const { if (mSlots.empty()) - throw std::logic_error ("character signature not available"); + throw std::logic_error("character signature not available"); - std::vector::const_iterator iter (mSlots.begin()); + const auto tiePlayerLevelAndTimeStamp + = [](const Slot& v) { return std::tie(v.mProfile.mPlayerLevel, v.mTimeStamp); }; - Slot slot = *iter; + const auto lessByPlayerLevelAndTimeStamp + = [&](const Slot& l, const Slot& r) { return tiePlayerLevelAndTimeStamp(l) < tiePlayerLevelAndTimeStamp(r); }; - for (++iter; iter!=mSlots.end(); ++iter) - if (iter->mProfile.mPlayerLevel>slot.mProfile.mPlayerLevel) - slot = *iter; - else if (iter->mProfile.mPlayerLevel==slot.mProfile.mPlayerLevel && - iter->mTimeStamp>slot.mTimeStamp) - slot = *iter; - - return slot.mProfile; + return std::max_element(mSlots.begin(), mSlots.end(), lessByPlayerLevelAndTimeStamp)->mProfile; } -const boost::filesystem::path& MWState::Character::getPath() const +const std::filesystem::path& MWState::Character::getPath() const { return mPath; } diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 32c79a183..7b9eba2fe 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -1,69 +1,68 @@ #ifndef GAME_STATE_CHARACTER_H #define GAME_STATE_CHARACTER_H -#include +#include -#include +#include namespace MWState { struct Slot { - boost::filesystem::path mPath; + std::filesystem::path mPath; ESM::SavedGame mProfile; - std::time_t mTimeStamp; + std::filesystem::file_time_type mTimeStamp; }; - bool operator< (const Slot& left, const Slot& right); + bool operator<(const Slot& left, const Slot& right); + + std::string getFirstGameFile(const std::vector& contentFiles); class Character { - public: + public: + typedef std::vector::const_reverse_iterator SlotIterator; - typedef std::vector::const_reverse_iterator SlotIterator; + private: + std::filesystem::path mPath; + std::vector mSlots; - private: + void addSlot(const std::filesystem::path& path, const std::string& game); - boost::filesystem::path mPath; - std::vector mSlots; + void addSlot(const ESM::SavedGame& profile); - void addSlot (const boost::filesystem::path& path, const std::string& game); + public: + Character(std::filesystem::path saves, const std::string& game); - void addSlot (const ESM::SavedGame& profile); + void cleanup(); + ///< Delete the directory we used, if it is empty - public: + const Slot* createSlot(const ESM::SavedGame& profile); + ///< Create new slot. + /// + /// \attention The ownership of the slot is not transferred. - Character (const boost::filesystem::path& saves, const std::string& game); + /// \note Slot must belong to this character. + /// + /// \attention The \a slot pointer will be invalidated by this call. + void deleteSlot(const Slot* slot); - void cleanup(); - ///< Delete the directory we used, if it is empty + const Slot* updateSlot(const Slot* slot, const ESM::SavedGame& profile); + /// \note Slot must belong to this character. + /// + /// \attention The \a slot pointer will be invalidated by this call. - const Slot *createSlot (const ESM::SavedGame& profile); - ///< Create new slot. - /// - /// \attention The ownership of the slot is not transferred. + SlotIterator begin() const; + ///< Any call to createSlot and updateSlot can invalidate the returned iterator. - /// \note Slot must belong to this character. - /// - /// \attention The \a slot pointer will be invalidated by this call. - void deleteSlot (const Slot *slot); + SlotIterator end() const; - const Slot *updateSlot (const Slot *slot, const ESM::SavedGame& profile); - /// \note Slot must belong to this character. - /// - /// \attention The \a slot pointer will be invalidated by this call. + const std::filesystem::path& getPath() const; - SlotIterator begin() const; - ///< Any call to createSlot and updateSlot can invalidate the returned iterator. - - SlotIterator end() const; - - const boost::filesystem::path& getPath() const; - - ESM::SavedGame getSignature() const; - ///< Return signature information for this character. - /// - /// \attention This function must not be called if there are no slots. + const ESM::SavedGame& getSignature() const; + ///< Return signature information for this character. + /// + /// \attention This function must not be called if there are no slots. }; } diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index a324dfe0f..c41ddd42c 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -1,42 +1,44 @@ #include "charactermanager.hpp" #include +#include #include +#include -#include +#include -MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, - const std::string& game) -: mPath (saves), mCurrent (nullptr), mGame (game) +MWState::CharacterManager::CharacterManager(std::filesystem::path saves, const std::vector& contentFiles) + : mPath(std::move(saves)) + , mCurrent(nullptr) + , mGame(getFirstGameFile(contentFiles)) { - if (!boost::filesystem::is_directory (mPath)) + if (!std::filesystem::is_directory(mPath)) { - boost::filesystem::create_directories (mPath); + std::filesystem::create_directories(mPath); } else { - for (boost::filesystem::directory_iterator iter (mPath); - iter!=boost::filesystem::directory_iterator(); ++iter) + for (std::filesystem::directory_iterator iter(mPath); iter != std::filesystem::directory_iterator(); ++iter) { - boost::filesystem::path characterDir = *iter; + std::filesystem::path characterDir = *iter; - if (boost::filesystem::is_directory (characterDir)) + if (std::filesystem::is_directory(characterDir)) { - Character character (characterDir, mGame); + Character character(characterDir, mGame); - if (character.begin()!=character.end()) - mCharacters.push_back (character); + if (character.begin() != character.end()) + mCharacters.push_back(character); } } } } -MWState::Character *MWState::CharacterManager::getCurrentCharacter () +MWState::Character* MWState::CharacterManager::getCurrentCharacter() { return mCurrent; } -void MWState::CharacterManager::deleteSlot(const MWState::Character *character, const MWState::Slot *slot) +void MWState::CharacterManager::deleteSlot(const MWState::Character* character, const MWState::Slot* slot) { std::list::iterator it = findCharacter(character); @@ -57,24 +59,26 @@ MWState::Character* MWState::CharacterManager::createCharacter(const std::string std::ostringstream stream; // The character name is user-supplied, so we need to escape the path - for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) + Utf8Stream nameStream(name); + while (!nameStream.eof()) { - if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters - stream << *it; + auto c = nameStream.consume(); + if (c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters + stream << static_cast(c); else - stream << "_"; + stream << '_'; } - boost::filesystem::path path = mPath / stream.str(); + std::filesystem::path path = mPath / stream.str(); // Append an index if necessary to ensure a unique directory - int i=0; - while (boost::filesystem::exists(path)) + int i = 0; + while (std::filesystem::exists(path)) { - std::ostringstream test; - test << stream.str(); - test << " - " << ++i; - path = mPath / test.str(); + std::ostringstream test; + test << stream.str(); + test << " - " << ++i; + path = mPath / test.str(); } mCharacters.emplace_back(path, mGame); @@ -90,11 +94,11 @@ std::list::iterator MWState::CharacterManager::findCharacter break; } if (it == mCharacters.end()) - throw std::logic_error ("invalid character"); + throw std::logic_error("invalid character"); return it; } -void MWState::CharacterManager::setCurrentCharacter (const Character *character) +void MWState::CharacterManager::setCurrentCharacter(const Character* character) { if (!character) mCurrent = nullptr; @@ -106,7 +110,6 @@ void MWState::CharacterManager::setCurrentCharacter (const Character *character) } } - std::list::const_iterator MWState::CharacterManager::begin() const { return mCharacters.begin(); diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp index 2daf73401..dac189e68 100644 --- a/apps/openmw/mwstate/charactermanager.hpp +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -1,7 +1,8 @@ #ifndef GAME_STATE_CHARACTERMANAGER_H #define GAME_STATE_CHARACTERMANAGER_H -#include +#include +#include #include "character.hpp" @@ -9,42 +10,40 @@ namespace MWState { class CharacterManager { - boost::filesystem::path mPath; + std::filesystem::path mPath; - // Uses std::list, so that mCurrent stays valid when characters are deleted - std::list mCharacters; + // Uses std::list, so that mCurrent stays valid when characters are deleted + std::list mCharacters; - Character *mCurrent; - std::string mGame; + Character* mCurrent; + std::string mGame; - private: + private: + CharacterManager(const CharacterManager&); + ///< Not implemented - CharacterManager (const CharacterManager&); - ///< Not implemented + CharacterManager& operator=(const CharacterManager&); + ///< Not implemented - CharacterManager& operator= (const CharacterManager&); - ///< Not implemented + std::list::iterator findCharacter(const MWState::Character* character); - std::list::iterator findCharacter(const MWState::Character* character); + public: + CharacterManager(std::filesystem::path saves, const std::vector& contentFiles); - public: + Character* getCurrentCharacter(); + ///< @note May return null - CharacterManager (const boost::filesystem::path& saves, const std::string& game); + void deleteSlot(const MWState::Character* character, const MWState::Slot* slot); - Character *getCurrentCharacter (); - ///< @note May return null + Character* createCharacter(const std::string& name); + ///< Create new character within saved game management + /// \param name Name for the character (does not need to be unique) - void deleteSlot(const MWState::Character *character, const MWState::Slot *slot); + void setCurrentCharacter(const Character* character); - Character* createCharacter(const std::string& name); - ///< Create new character within saved game management - /// \param name Name for the character (does not need to be unique) + std::list::const_iterator begin() const; - void setCurrentCharacter (const Character *character); - - std::list::const_iterator begin() const; - - std::list::const_iterator end() const; + std::list::const_iterator end() const; }; } diff --git a/apps/openmw/mwstate/quicksavemanager.cpp b/apps/openmw/mwstate/quicksavemanager.cpp index bf1781520..1028866aa 100644 --- a/apps/openmw/mwstate/quicksavemanager.cpp +++ b/apps/openmw/mwstate/quicksavemanager.cpp @@ -1,26 +1,26 @@ #include "quicksavemanager.hpp" -MWState::QuickSaveManager::QuickSaveManager(std::string &saveName, unsigned int maxSaves) - : mSaveName(saveName) - , mMaxSaves(maxSaves) - , mSlotsVisited(0) - , mOldestSlotVisited(nullptr) +MWState::QuickSaveManager::QuickSaveManager(std::string& saveName, unsigned int maxSaves) + : mSaveName(saveName) + , mMaxSaves(maxSaves) + , mSlotsVisited(0) + , mOldestSlotVisited(nullptr) { } -void MWState::QuickSaveManager::visitSave(const Slot *saveSlot) +void MWState::QuickSaveManager::visitSave(const Slot* saveSlot) { - if(mSaveName == saveSlot->mProfile.mDescription) + if (mSaveName == saveSlot->mProfile.mDescription) { ++mSlotsVisited; - if(isOldestSave(saveSlot)) + if (isOldestSave(saveSlot)) mOldestSlotVisited = saveSlot; } } -bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) const +bool MWState::QuickSaveManager::isOldestSave(const Slot* compare) const { - if(mOldestSlotVisited == nullptr) + if (mOldestSlotVisited == nullptr) return true; return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp); } @@ -30,9 +30,9 @@ bool MWState::QuickSaveManager::shouldCreateNewSlot() const return (mSlotsVisited < mMaxSaves); } -const MWState::Slot *MWState::QuickSaveManager::getNextQuickSaveSlot() +const MWState::Slot* MWState::QuickSaveManager::getNextQuickSaveSlot() { - if(shouldCreateNewSlot()) + if (shouldCreateNewSlot()) return nullptr; return mOldestSlotVisited; } diff --git a/apps/openmw/mwstate/quicksavemanager.hpp b/apps/openmw/mwstate/quicksavemanager.hpp index 3272b24b5..3fa24d34d 100644 --- a/apps/openmw/mwstate/quicksavemanager.hpp +++ b/apps/openmw/mwstate/quicksavemanager.hpp @@ -5,26 +5,30 @@ #include "character.hpp" -namespace MWState{ - class QuickSaveManager{ +namespace MWState +{ + class QuickSaveManager + { std::string mSaveName; unsigned int mMaxSaves; unsigned int mSlotsVisited; - const Slot *mOldestSlotVisited; + const Slot* mOldestSlotVisited; + private: bool shouldCreateNewSlot() const; - bool isOldestSave(const Slot *compare) const; + bool isOldestSave(const Slot* compare) const; + public: - QuickSaveManager(std::string &saveName, unsigned int maxSaves); + QuickSaveManager(std::string& saveName, unsigned int maxSaves); ///< A utility class to manage multiple quicksave slots /// /// \param saveName The name of the save ("QuickSave", "AutoSave", etc) /// \param maxSaves The maximum number of save slots to create before recycling old ones - void visitSave(const Slot *saveSlot); + void visitSave(const Slot* saveSlot); ///< Visits the given \a slot \a - const Slot *getNextQuickSaveSlot(); + const Slot* getNextQuickSaveSlot(); ///< Get the slot that the next quicksave should use. /// ///\return Either the oldest quicksave slot visited, or nullptr if a new slot can be made diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index ffc496268..8583574bb 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -1,48 +1,53 @@ #include "statemanagerimp.hpp" +#include + #include -#include -#include -#include -#include +#include +#include +#include +#include + +#include #include +#include #include #include #include -#include -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/journal.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" +#include "../mwbase/journal.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" -#include "../mwbase/inputmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/globals.hpp" +#include "../mwworld/scene.hpp" +#include "../mwworld/worldmodel.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" #include "../mwscript/globalscripts.hpp" #include "quicksavemanager.hpp" -void MWState::StateManager::cleanup (bool force) +void MWState::StateManager::cleanup(bool force) { - if (mState!=State_NoGame || force) + if (mState != State_NoGame || force) { MWBase::Environment::get().getSoundManager()->clear(); MWBase::Environment::get().getDialogueManager()->clear(); @@ -59,26 +64,25 @@ void MWState::StateManager::cleanup (bool force) MWMechanics::CreatureStats::cleanup(); } + MWBase::Environment::get().getLuaManager()->clear(); } -std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) - const +std::map MWState::StateManager::buildContentFileIndexMap(const ESM::ESMReader& reader) const { - const std::vector& current = - MWBase::Environment::get().getWorld()->getContentFiles(); + const std::vector& current = MWBase::Environment::get().getWorld()->getContentFiles(); const std::vector& prev = reader.getGameFiles(); std::map map; - for (int iPrev = 0; iPrev (prev.size()); ++iPrev) + for (int iPrev = 0; iPrev < static_cast(prev.size()); ++iPrev) { - std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name); + std::string id = Misc::StringUtils::lowerCase(prev[iPrev].name); - for (int iCurrent = 0; iCurrent (current.size()); ++iCurrent) - if (id==Misc::StringUtils::lowerCase (current[iCurrent])) + for (int iCurrent = 0; iCurrent < static_cast(current.size()); ++iCurrent) + if (id == Misc::StringUtils::lowerCase(current[iCurrent])) { - map.insert (std::make_pair (iPrev, iCurrent)); + map.insert(std::make_pair(iPrev, iCurrent)); break; } } @@ -86,10 +90,13 @@ std::map MWState::StateManager::buildContentFileIndexMap (const ESM::E return map; } -MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game) -: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0) +MWState::StateManager::StateManager(const std::filesystem::path& saves, const std::vector& contentFiles) + : mQuitRequest(false) + , mAskLoadRecent(false) + , mState(State_NoGame) + , mCharacterManager(saves, contentFiles) + , mTimePlayed(0) { - } void MWState::StateManager::requestQuit() @@ -107,21 +114,22 @@ void MWState::StateManager::askLoadRecent() if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu) return; - if( !mAskLoadRecent ) + if (!mAskLoadRecent) { const MWState::Character* character = getCurrentCharacter(); - if(!character || character->begin() == character->end())//no saves + if (!character || character->begin() == character->end()) // no saves { - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); } else { MWState::Slot lastSave = *character->begin(); std::vector buttons; - buttons.emplace_back("#{sYes}"); - buttons.emplace_back("#{sNo}"); - std::string tag("%s"); - std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag); + buttons.emplace_back("#{Interface:Yes}"); + buttons.emplace_back("#{Interface:No}"); + std::string message + = MWBase::Environment::get().getL10nManager()->getMessage("OMWEngine", "AskLoadLastSave"); + std::string_view tag = "%s"; size_t pos = message.find(tag); message.replace(pos, tag.length(), lastSave.mProfile.mDescription); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); @@ -135,19 +143,19 @@ MWState::StateManager::State MWState::StateManager::getState() const return mState; } -void MWState::StateManager::newGame (bool bypass) +void MWState::StateManager::newGame(bool bypass) { cleanup(); if (!bypass) - MWBase::Environment::get().getWindowManager()->setNewGame (true); + MWBase::Environment::get().getWindowManager()->setNewGame(true); try { Log(Debug::Info) << "Starting a new game"; MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - - MWBase::Environment::get().getWorld()->startNewGame (bypass); + MWBase::Environment::get().getLuaManager()->newGameStarted(); + MWBase::Environment::get().getWorld()->startNewGame(bypass); mState = State_Running; @@ -160,12 +168,12 @@ void MWState::StateManager::newGame (bool bypass) error << "Failed to start new game: " << e.what(); Log(Debug::Error) << error.str(); - cleanup (true); + cleanup(true); - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); std::vector buttons; - buttons.emplace_back("#{sOk}"); + buttons.emplace_back("#{Interface:OK}"); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); } } @@ -180,16 +188,20 @@ void MWState::StateManager::resumeGame() mState = State_Running; } -void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) +void MWState::StateManager::saveGame(const std::string& description, const Slot* slot) { MWState::Character* character = getCurrentCharacter(); try { + const auto start = std::chrono::steady_clock::now(); + + MWBase::Environment::get().getWindowManager()->asyncPrepareSaveMap(); + if (!character) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); - std::string name = player.get()->mBase->mName; + const std::string& name = player.get()->mBase->mName; character = mCharacterManager.createCharacter(name); mCharacterManager.setCurrentCharacter(character); @@ -204,26 +216,26 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot profile.mContentFiles = world.getContentFiles(); profile.mPlayerName = player.get()->mBase->mName; - profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); + profile.mPlayerLevel = player.getClass().getNpcStats(player).getLevel(); - std::string classId = player.get()->mBase->mClass; + const ESM::RefId& classId = player.get()->mBase->mClass; if (world.getStore().get().isDynamic(classId)) profile.mPlayerClassName = world.getStore().get().find(classId)->mName; else profile.mPlayerClassId = classId; - profile.mPlayerCell = world.getCellName(); + profile.mPlayerCellName = world.getCellName(); profile.mInGameTime = world.getEpochTimeStamp(); profile.mTimePlayed = mTimePlayed; profile.mDescription = description; - Log(Debug::Info) << "Making a screenshot for saved game '" << description << "'";; + Log(Debug::Info) << "Making a screenshot for saved game '" << description << "'"; writeScreenshot(profile.mScreenshot); if (!slot) - slot = character->createSlot (profile); + slot = character->createSlot(profile); else - slot = character->updateSlot (slot, profile); + slot = character->updateSlot(slot, profile); // Make sure the animation state held by references is up to date before saving the game. MWBase::Environment::get().getMechanicsManager()->persistAnimationStates(); @@ -239,7 +251,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot for (const std::string& contentFile : MWBase::Environment::get().getWorld()->getContentFiles()) writer.addMaster(contentFile, 0); // not using the size information anyway -> use value of 0 - writer.setFormat (ESM::SavedGame::sCurrentFormat); + writer.setFormatVersion(ESM::CurrentSaveGameFormatVersion); // all unused writer.setVersion(0); @@ -247,40 +259,45 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.setAuthor(""); writer.setDescription(""); - int recordCount = 1 // saved game header - +MWBase::Environment::get().getJournal()->countSavedGameRecords() - +MWBase::Environment::get().getWorld()->countSavedGameRecords() - +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() - +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() - +MWBase::Environment::get().getWindowManager()->countSavedGameRecords() - +MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords() - +MWBase::Environment::get().getInputManager()->countSavedGameRecords(); - writer.setRecordCount (recordCount); + int recordCount = 1 // saved game header + + MWBase::Environment::get().getJournal()->countSavedGameRecords() + + MWBase::Environment::get().getLuaManager()->countSavedGameRecords() + + MWBase::Environment::get().getWorld()->countSavedGameRecords() + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() + + MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() + + MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords() + + MWBase::Environment::get().getInputManager()->countSavedGameRecords() + + MWBase::Environment::get().getWindowManager()->countSavedGameRecords(); + writer.setRecordCount(recordCount); - writer.save (stream); + writer.save(stream); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); // Using only Cells for progress information, since they typically have the largest records by far listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); - listener.setLabel("#{sNotifyMessage4}", true); + listener.setLabel("#{OMWEngine:SavingInProgress}", true); Loading::ScopedLoad load(&listener); - writer.startRecord (ESM::REC_SAVE); - slot->mProfile.save (writer); - writer.endRecord (ESM::REC_SAVE); + writer.startRecord(ESM::REC_SAVE); + slot->mProfile.save(writer); + writer.endRecord(ESM::REC_SAVE); - MWBase::Environment::get().getJournal()->write (writer, listener); - MWBase::Environment::get().getDialogueManager()->write (writer, listener); - MWBase::Environment::get().getWorld()->write (writer, listener); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); - MWBase::Environment::get().getWindowManager()->write(writer, listener); + MWBase::Environment::get().getJournal()->write(writer, listener); + MWBase::Environment::get().getDialogueManager()->write(writer, listener); + // LuaManager::write should be called before World::write because world also saves + // local scripts that depend on LuaManager. + MWBase::Environment::get().getLuaManager()->write(writer, listener); + MWBase::Environment::get().getWorld()->write(writer, listener); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().write(writer, listener); MWBase::Environment::get().getMechanicsManager()->write(writer, listener); MWBase::Environment::get().getInputManager()->write(writer, listener); + MWBase::Environment::get().getWindowManager()->write(writer, listener); // Ensure we have written the number of records that was estimated - if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record - Log(Debug::Warning) << "Warning: number of written savegame records does not match. Estimated: " << recordCount+1 << ", written: " << writer.getRecordCount(); + if (writer.getRecordCount() != recordCount + 1) // 1 extra for TES3 record + Log(Debug::Warning) << "Warning: number of written savegame records does not match. Estimated: " + << recordCount + 1 << ", written: " << writer.getRecordCount(); writer.close(); @@ -288,14 +305,20 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot throw std::runtime_error("Write operation failed (memory stream)"); // All good, write to file - boost::filesystem::ofstream filestream (slot->mPath, std::ios::binary); + std::ofstream filestream(slot->mPath, std::ios::binary); filestream << stream.rdbuf(); if (filestream.fail()) throw std::runtime_error("Write operation failed (file stream)"); - Settings::Manager::setString ("character", "Saves", - slot->mPath.parent_path().filename().string()); + Settings::Manager::setString( + "character", "Saves", Files::pathToUnicodeString(slot->mPath.parent_path().filename())); + + const auto finish = std::chrono::steady_clock::now(); + + Log(Debug::Info) << '\'' << description << "' is saved in " + << std::chrono::duration_cast>(finish - start).count() + << "ms"; } catch (const std::exception& e) { @@ -305,11 +328,11 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot Log(Debug::Error) << error.str(); std::vector buttons; - buttons.emplace_back("#{sOk}"); + buttons.emplace_back("#{Interface:OK}"); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); // If no file was written, clean up the slot - if (character && slot && !boost::filesystem::exists(slot->mPath)) + if (character && slot && !std::filesystem::exists(slot->mPath)) { character->deleteSlot(slot); character->cleanup(); @@ -317,8 +340,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot } } -void MWState::StateManager::quickSave (std::string name) +void MWState::StateManager::quickSave(std::string name) { +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -331,43 +355,47 @@ void MWState::StateManager::quickSave (std::string name) if (!(mState==State_Running && MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1 // char gen +======= + if (!(mState == State_Running + && MWBase::Environment::get().getWorld()->getGlobalInt(MWWorld::Globals::sCharGenState) == -1 // char gen +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 && MWBase::Environment::get().getWindowManager()->isSavingAllowed())) { - //You can not save your game right now - MWBase::Environment::get().getWindowManager()->messageBox("#{sSaveGameDenied}"); + // You can not save your game right now + MWBase::Environment::get().getWindowManager()->messageBox("#{OMWEngine:SaveGameDenied}"); return; } int maxSaves = Settings::Manager::getInt("max quicksaves", "Saves"); - if(maxSaves < 1) + if (maxSaves < 1) maxSaves = 1; - Character* currentCharacter = getCurrentCharacter(); //Get current character + Character* currentCharacter = getCurrentCharacter(); // Get current character QuickSaveManager saveFinder = QuickSaveManager(name, maxSaves); if (currentCharacter) { for (auto& save : *currentCharacter) { - //Visiting slots allows the quicksave finder to find the oldest quicksave + // Visiting slots allows the quicksave finder to find the oldest quicksave saveFinder.visitSave(&save); } } - //Once all the saves have been visited, the save finder can tell us which - //one to replace (or create) + // Once all the saves have been visited, the save finder can tell us which + // one to replace (or create) saveGame(name, saveFinder.getNextQuickSaveSlot()); } -void MWState::StateManager::loadGame(const std::string& filepath) +void MWState::StateManager::loadGame(const std::filesystem::path& filepath) { for (const auto& character : mCharacterManager) { for (const auto& slot : character) { - if (slot.mPath == boost::filesystem::path(filepath)) + if (slot.mPath == filepath) { - loadGame(&character, slot.mPath.string()); + loadGame(&character, slot.mPath); return; } } @@ -377,26 +405,34 @@ void MWState::StateManager::loadGame(const std::string& filepath) loadGame(character, filepath); } -void MWState::StateManager::loadGame (const Character *character, const std::string& filepath) +struct VersionMismatchError : public std::runtime_error +{ + using std::runtime_error::runtime_error; +}; + +void MWState::StateManager::loadGame(const Character* character, const std::filesystem::path& filepath) { try { cleanup(); - Log(Debug::Info) << "Reading save file " << boost::filesystem::path(filepath).filename().string(); + Log(Debug::Info) << "Reading save file " << filepath.filename(); ESM::ESMReader reader; - reader.open (filepath); + reader.open(filepath); - if (reader.getFormat() > ESM::SavedGame::sCurrentFormat) - throw std::runtime_error("This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file."); + if (reader.getFormatVersion() > ESM::CurrentSaveGameFormatVersion) + throw VersionMismatchError( + "This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade " + "to the newest OpenMW version to load this file."); - std::map contentFileMap = buildContentFileIndexMap (reader); + std::map contentFileMap = buildContentFileIndexMap(reader); + MWBase::Environment::get().getLuaManager()->setContentFileMapping(contentFileMap); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener.setProgressRange(100); - listener.setLabel("#{sLoadingMessage14}"); + listener.setLabel("#{OMWEngine:LoadingInProgress}"); Loading::ScopedLoad load(&listener); @@ -409,36 +445,39 @@ void MWState::StateManager::loadGame (const Character *character, const std::str ESM::NAME n = reader.getRecName(); reader.getRecHeader(); - switch (n.intval) + switch (n.toInt()) { case ESM::REC_SAVE: + { + ESM::SavedGame profile; + profile.load(reader); + if (!verifyProfile(profile)) { - ESM::SavedGame profile; - profile.load(reader); - if (!verifyProfile(profile)) - { - cleanup (true); - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - return; - } - mTimePlayed = profile.mTimePlayed; - Log(Debug::Info) << "Loading saved game '" << profile.mDescription << "' for character '" << profile.mPlayerName << "'"; + cleanup(true); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); + return; } - break; + mTimePlayed = profile.mTimePlayed; + Log(Debug::Info) << "Loading saved game '" << profile.mDescription << "' for character '" + << profile.mPlayerName << "'"; + } + break; case ESM::REC_JOUR: case ESM::REC_JOUR_LEGACY: case ESM::REC_QUES: - MWBase::Environment::get().getJournal()->readRecord (reader, n.intval); + MWBase::Environment::get().getJournal()->readRecord(reader, n.toInt()); break; case ESM::REC_DIAS: - MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.intval); + MWBase::Environment::get().getDialogueManager()->readRecord(reader, n.toInt()); break; case ESM::REC_ALCH: + case ESM::REC_MISC: + case ESM::REC_ACTI: case ESM::REC_ARMO: case ESM::REC_BOOK: case ESM::REC_CLAS: @@ -460,7 +499,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_LEVI: case ESM::REC_CREA: case ESM::REC_CONT: - MWBase::Environment::get().getWorld()->readRecord(reader, n.intval, contentFileMap); + case ESM::REC_RAND: + MWBase::Environment::get().getWorld()->readRecord(reader, n.toInt(), contentFileMap); break; case ESM::REC_CAM_: @@ -469,7 +509,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_GSCR: - MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord( + reader, n.toInt(), contentFileMap); break; case ESM::REC_GMAP: @@ -477,29 +518,33 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_ASPL: case ESM::REC_MARK: - MWBase::Environment::get().getWindowManager()->readRecord(reader, n.intval); + MWBase::Environment::get().getWindowManager()->readRecord(reader, n.toInt()); break; case ESM::REC_DCOU: case ESM::REC_STLN: - MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.intval); + MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.toInt()); break; case ESM::REC_INPU: - MWBase::Environment::get().getInputManager()->readRecord(reader, n.intval); + MWBase::Environment::get().getInputManager()->readRecord(reader, n.toInt()); + break; + + case ESM::REC_LUAM: + MWBase::Environment::get().getLuaManager()->readRecord(reader, n.toInt()); break; default: // ignore invalid records - Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toString(); + Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toStringView(); reader.skipRecord(); } - int progressPercent = static_cast(float(reader.getFileOffset())/total*100); + int progressPercent = static_cast(float(reader.getFileOffset()) / total * 100); if (progressPercent > currentPercent) { - listener.increaseProgress(progressPercent-currentPercent); + listener.increaseProgress(progressPercent - currentPercent); currentPercent = progressPercent; } } @@ -509,8 +554,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str mState = State_Running; if (character) - Settings::Manager::setString ("character", "Saves", - character->getPath().filename().string()); + Settings::Manager::setString( + "character", "Saves", Files::pathToUnicodeString(character->getPath().filename())); MWBase::Environment::get().getWindowManager()->setNewGame(false); MWBase::Environment::get().getWorld()->saveLoaded(); @@ -526,26 +571,26 @@ void MWState::StateManager::loadGame (const Character *character, const std::str if (ptr.isInCell()) { - const ESM::CellId& cellId = ptr.getCell()->getCell()->getCellId(); + const ESM::RefId cellId = ptr.getCell()->getCell()->getId(); // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again - MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false, false); + MWBase::Environment::get().getWorld()->changeToCell(cellId, ptr.getRefData().getPosition(), false, false); } else { // Cell no longer exists (i.e. changed game files), choose a default cell - Log(Debug::Warning) << "Warning: Player character's cell no longer exists, changing to the default cell"; - MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(0,0); - float x,y; - MWBase::Environment::get().getWorld()->indexToPosition(0,0,x,y,false); + Log(Debug::Warning) << "Player character's cell no longer exists, changing to the default cell"; + ESM::ExteriorCellLocation cellIndex(0, 0, ESM::Cell::sDefaultWorldspaceId); + MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getExterior(cellIndex); + osg::Vec2 posFromIndex = ESM::indexToPosition(cellIndex, false); ESM::Position pos; - pos.pos[0] = x; - pos.pos[1] = y; + pos.pos[0] = posFromIndex.x(); + pos.pos[1] = posFromIndex.y(); pos.pos[2] = 0; // should be adjusted automatically (adjustPlayerPos=true) pos.rot[0] = 0; pos.rot[1] = 0; pos.rot[2] = 0; - MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getCellId(), pos, true, false); + MWBase::Environment::get().getWorld()->changeToCell(cell.getCell()->getId(), pos, true, false); } MWBase::Environment::get().getWorld()->updateProjectilesCasters(); @@ -556,26 +601,34 @@ void MWState::StateManager::loadGame (const Character *character, const std::str // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. // But make sure the flag is cleared anyway in case it was set from an earlier game. - MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + MWBase::Environment::get().getWorldScene()->markCellAsUnchanged(); + + MWBase::Environment::get().getLuaManager()->gameLoaded(); } catch (const std::exception& e) { - std::stringstream error; - error << "Failed to load saved game: " << e.what(); + Log(Debug::Error) << "Failed to load saved game: " << e.what(); - Log(Debug::Error) << error.str(); - cleanup (true); + cleanup(true); - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); std::vector buttons; - buttons.emplace_back("#{sOk}"); - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); + buttons.emplace_back("#{Interface:OK}"); + + std::string error; + if (typeid(e) == typeid(VersionMismatchError)) + error = "#{OMWEngine:LoadingFailed}: #{OMWEngine:LoadingRequiresNewVersionError}"; + else + error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what()); + + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error, buttons); } } void MWState::StateManager::quickLoad() { +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -587,19 +640,22 @@ void MWState::StateManager::quickLoad() */ if (Character* currentCharacter = getCurrentCharacter ()) +======= + if (Character* currentCharacter = getCurrentCharacter()) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { if (currentCharacter->begin() == currentCharacter->end()) return; - loadGame (currentCharacter, currentCharacter->begin()->mPath.string()); //Get newest save + loadGame(currentCharacter, currentCharacter->begin()->mPath); // Get newest save } } -void MWState::StateManager::deleteGame(const MWState::Character *character, const MWState::Slot *slot) +void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot) { mCharacterManager.deleteSlot(character, slot); } -MWState::Character *MWState::StateManager::getCurrentCharacter () +MWState::Character* MWState::StateManager::getCurrentCharacter() { return mCharacterManager.getCurrentCharacter(); } @@ -614,7 +670,7 @@ MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd() return mCharacterManager.end(); } -void MWState::StateManager::update (float duration) +void MWState::StateManager::update(float duration) { mTimePlayed += duration; @@ -622,19 +678,19 @@ void MWState::StateManager::update (float duration) if (mAskLoadRecent) { int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - MWState::Character *curCharacter = getCurrentCharacter(); - if(iButton==0 && curCharacter) + MWState::Character* curCharacter = getCurrentCharacter(); + if (iButton == 0 && curCharacter) { mAskLoadRecent = false; - //Load last saved game for current character + // Load last saved game for current character MWState::Slot lastSave = *curCharacter->begin(); - loadGame(curCharacter, lastSave.mPath.string()); + loadGame(curCharacter, lastSave.mPath); } - else if(iButton==1) + else if (iButton == 1) { mAskLoadRecent = false; - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); } } } @@ -646,7 +702,7 @@ bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const for (const std::string& contentFile : profile.mContentFiles) { if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), contentFile) - == selectedContentFiles.end()) + == selectedContentFiles.end()) { Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing."; notFound = true; @@ -655,9 +711,10 @@ bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const if (notFound) { std::vector buttons; - buttons.emplace_back("#{sYes}"); - buttons.emplace_back("#{sNo}"); - MWBase::Environment::get().getWindowManager()->interactiveMessageBox("#{sMissingMastersMsg}", buttons, true); + buttons.emplace_back("#{Interface:Yes}"); + buttons.emplace_back("#{Interface:No}"); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox( + "#{OMWEngine:MissingContentFilesConfirmation}", buttons, true); int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); if (selectedButton == 1 || selectedButton == -1) return false; @@ -665,11 +722,11 @@ bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const return true; } -void MWState::StateManager::writeScreenshot(std::vector &imageData) const +void MWState::StateManager::writeScreenshot(std::vector& imageData) const { - int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing + int screenshotW = 259 * 2, screenshotH = 133 * 2; // *2 to get some nice antialiasing - osg::ref_ptr screenshot (new osg::Image); + osg::ref_ptr screenshot(new osg::Image); MWBase::Environment::get().getWorld()->screenshot(screenshot.get(), screenshotW, screenshotH); @@ -690,5 +747,4 @@ void MWState::StateManager::writeScreenshot(std::vector &imageData) const std::string data = ostream.str(); imageData = std::vector(data.begin(), data.end()); - } diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 3534dabf2..f9ba51d45 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -1,90 +1,88 @@ #ifndef GAME_STATE_STATEMANAGER_H #define GAME_STATE_STATEMANAGER_H +#include #include #include "../mwbase/statemanager.hpp" -#include - #include "charactermanager.hpp" namespace MWState { class StateManager : public MWBase::StateManager { - bool mQuitRequest; - bool mAskLoadRecent; - State mState; - CharacterManager mCharacterManager; - double mTimePlayed; + bool mQuitRequest; + bool mAskLoadRecent; + State mState; + CharacterManager mCharacterManager; + double mTimePlayed; - private: + private: + void cleanup(bool force = false); - void cleanup (bool force = false); + bool verifyProfile(const ESM::SavedGame& profile) const; - bool verifyProfile (const ESM::SavedGame& profile) const; + void writeScreenshot(std::vector& imageData) const; - void writeScreenshot (std::vector& imageData) const; + std::map buildContentFileIndexMap(const ESM::ESMReader& reader) const; - std::map buildContentFileIndexMap (const ESM::ESMReader& reader) const; + public: + StateManager(const std::filesystem::path& saves, const std::vector& contentFiles); - public: + void requestQuit() override; - StateManager (const boost::filesystem::path& saves, const std::string& game); + bool hasQuitRequest() const override; - void requestQuit() override; + void askLoadRecent() override; - bool hasQuitRequest() const override; + State getState() const override; - void askLoadRecent() override; + void newGame(bool bypass = false) override; + ///< Start a new game. + /// + /// \param bypass Skip new game mechanics. - State getState() const override; + void endGame(); - void newGame (bool bypass = false) override; - ///< Start a new game. - /// - /// \param bypass Skip new game mechanics. + void resumeGame() override; - void endGame() override; + void deleteGame(const MWState::Character* character, const MWState::Slot* slot) override; + ///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted + ///< too. - void resumeGame() override; + void saveGame(const std::string& description, const Slot* slot = nullptr) override; + ///< Write a saved game to \a slot or create a new slot if \a slot == 0. + /// + /// \note Slot must belong to the current character. - void deleteGame (const MWState::Character *character, const MWState::Slot *slot) override; - ///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted too. + /// Saves a file, using supplied filename, overwritting if needed + /** This is mostly used for quicksaving and autosaving, for they use the same name over and over again + \param name Name of save, defaults to "Quicksave"**/ + void quickSave(std::string name = "Quicksave") override; - void saveGame (const std::string& description, const Slot *slot = nullptr) override; - ///< Write a saved game to \a slot or create a new slot if \a slot == 0. - /// - /// \note Slot must belong to the current character. + /// Loads the last saved file + /** Used for quickload **/ + void quickLoad() override; - ///Saves a file, using supplied filename, overwritting if needed - /** This is mostly used for quicksaving and autosaving, for they use the same name over and over again - \param name Name of save, defaults to "Quicksave"**/ - void quickSave(std::string name = "Quicksave") override; + void loadGame(const std::filesystem::path& filepath) override; + ///< Load a saved game directly from the given file path. This will search the CharacterManager + /// for a Character containing this save file, and set this Character current if one was found. + /// Otherwise, a new Character will be created. - ///Loads the last saved file - /** Used for quickload **/ - void quickLoad() override; + void loadGame(const Character* character, const std::filesystem::path& filepath) override; + ///< Load a saved game file belonging to the given character. - void loadGame (const std::string& filepath) override; - ///< Load a saved game directly from the given file path. This will search the CharacterManager - /// for a Character containing this save file, and set this Character current if one was found. - /// Otherwise, a new Character will be created. + Character* getCurrentCharacter() override; + ///< @note May return null. - void loadGame (const Character *character, const std::string &filepath) override; - ///< Load a saved game file belonging to the given character. + CharacterIterator characterBegin() override; + ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned + /// iterator. - Character *getCurrentCharacter () override; - ///< @note May return null. + CharacterIterator characterEnd() override; - CharacterIterator characterBegin() override; - ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned - /// iterator. - - CharacterIterator characterEnd() override; - - void update (float duration) override; + void update(float duration); }; } diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp index eefe37c43..d194ca8e8 100644 --- a/apps/openmw/mwworld/action.cpp +++ b/apps/openmw/mwworld/action.cpp @@ -14,7 +14,6 @@ */ #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -31,14 +30,18 @@ void MWWorld::Action::setTarget(const MWWorld::Ptr& target) mTarget = target; } -MWWorld::Action::Action (bool keepSound, const Ptr& target) : mKeepSound (keepSound), mSoundOffset(0), mTarget (target) -{} +MWWorld::Action::Action(bool keepSound, const Ptr& target) + : mKeepSound(keepSound) + , mSoundOffset(0) + , mTarget(target) +{ +} MWWorld::Action::~Action() {} -void MWWorld::Action::execute (const Ptr& actor, bool noSound) +void MWWorld::Action::execute(const Ptr& actor, bool noSound) { - if(!mSoundId.empty() && !noSound) + if (!mSoundId.empty() && !noSound) { MWSound::PlayMode envType = MWSound::PlayMode::Normal; @@ -49,6 +52,7 @@ void MWWorld::Action::execute (const Ptr& actor, bool noSound) envType = MWSound::PlayMode::NoEnv; } +<<<<<<< HEAD if(mKeepSound && actor == MWMechanics::getPlayer()) { MWBase::Environment::get().getSoundManager()->playSound(mSoundId, 1.0, 1.0, @@ -119,18 +123,33 @@ void MWWorld::Action::execute (const Ptr& actor, bool noSound) End of tes3mp addition */ } +======= + if (mKeepSound && actor == MWMechanics::getPlayer()) + MWBase::Environment::get().getSoundManager()->playSound( + mSoundId, 1.0, 1.0, MWSound::Type::Sfx, envType, mSoundOffset); + else + { + bool local = mTarget.isEmpty() || !mTarget.isInCell(); // no usable target + if (mKeepSound) + MWBase::Environment::get().getSoundManager()->playSound3D( + (local ? actor : mTarget).getRefData().getPosition().asVec3(), mSoundId, 1.0, 1.0, + MWSound::Type::Sfx, envType, mSoundOffset); + else + MWBase::Environment::get().getSoundManager()->playSound3D( + local ? actor : mTarget, mSoundId, 1.0, 1.0, MWSound::Type::Sfx, envType, mSoundOffset); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } - executeImp (actor); + executeImp(actor); } -void MWWorld::Action::setSound (const std::string& id) +void MWWorld::Action::setSound(const ESM::RefId& id) { mSoundId = id; } void MWWorld::Action::setSoundOffset(float offset) { - mSoundOffset=offset; + mSoundOffset = offset; } diff --git a/apps/openmw/mwworld/action.hpp b/apps/openmw/mwworld/action.hpp index 2f9804053..56c8fd8b5 100644 --- a/apps/openmw/mwworld/action.hpp +++ b/apps/openmw/mwworld/action.hpp @@ -2,6 +2,7 @@ #define GAME_MWWORLD_ACTION_H #include +#include #include "ptr.hpp" @@ -10,37 +11,35 @@ namespace MWWorld /// \brief Abstract base for actions class Action { - std::string mSoundId; - bool mKeepSound; - float mSoundOffset; - Ptr mTarget; + ESM::RefId mSoundId; + bool mKeepSound; + float mSoundOffset; + Ptr mTarget; - // not implemented - Action (const Action& action); - Action& operator= (const Action& action); + // not implemented + Action(const Action& action); + Action& operator=(const Action& action); - virtual void executeImp (const Ptr& actor) = 0; + virtual void executeImp(const Ptr& actor) = 0; - protected: + protected: + void setTarget(const Ptr&); - void setTarget(const Ptr&); + public: + const Ptr& getTarget() const; - public: + Action(bool keepSound = false, const Ptr& target = Ptr()); + ///< \param keepSound Keep playing the sound even if the object the sound is played on is removed. - const Ptr& getTarget() const; + virtual ~Action(); - Action (bool keepSound = false, const Ptr& target = Ptr()); - ///< \param keepSound Keep playing the sound even if the object the sound is played on is removed. + virtual bool isNullAction() { return false; } + ///< Is running this action a no-op? (default false) - virtual ~Action(); + void execute(const Ptr& actor, bool noSound = false); - virtual bool isNullAction() { return false; } - ///< Is running this action a no-op? (default false) - - void execute (const Ptr& actor, bool noSound = false); - - void setSound (const std::string& id); - void setSoundOffset(float offset); + void setSound(const ESM::RefId& id); + void setSoundOffset(float offset); }; } diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp index 62b673dd2..3348dbbfb 100644 --- a/apps/openmw/mwworld/actionalchemy.cpp +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -8,18 +8,18 @@ namespace MWWorld { ActionAlchemy::ActionAlchemy(bool force) - : Action (false) - , mForce(force) + : Action(false) + , mForce(force) { } - void ActionAlchemy::executeImp (const Ptr& actor) + void ActionAlchemy::executeImp(const Ptr& actor) { if (actor != MWMechanics::getPlayer()) return; - if(!mForce && MWMechanics::isPlayerInCombat()) - { //Ensure we're not in combat + if (!mForce && MWMechanics::isPlayerInCombat()) + { // Ensure we're not in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage3}"); return; } diff --git a/apps/openmw/mwworld/actionalchemy.hpp b/apps/openmw/mwworld/actionalchemy.hpp index 194ef308b..d37850e6a 100644 --- a/apps/openmw/mwworld/actionalchemy.hpp +++ b/apps/openmw/mwworld/actionalchemy.hpp @@ -8,10 +8,10 @@ namespace MWWorld class ActionAlchemy : public Action { bool mForce; - void executeImp (const Ptr& actor) override; + void executeImp(const Ptr& actor) override; public: - ActionAlchemy(bool force=false); + ActionAlchemy(bool force = false); }; } diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index e3699a6ac..bf4fce38c 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -2,41 +2,18 @@ #include "class.hpp" -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/containerstore.hpp" - #include "../mwmechanics/actorutil.hpp" namespace MWWorld { - ActionApply::ActionApply (const Ptr& object, const std::string& id) - : Action (false, object), mId (id) - {} - - void ActionApply::executeImp (const Ptr& actor) + ActionApply::ActionApply(const Ptr& object, const ESM::RefId& id) + : Action(false, object) + , mId(id) { - MWBase::Environment::get().getWorld()->breakInvisibility(actor); - - actor.getClass().apply (actor, mId, actor); - - actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); } - - ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& object, const std::string& id, - int skillIndex, int usageType) - : Action (false, object), mId (id), mSkillIndex (skillIndex), mUsageType (usageType) - {} - - void ActionApplyWithSkill::executeImp (const Ptr& actor) + void ActionApply::executeImp(const Ptr& actor) { - MWBase::Environment::get().getWorld()->breakInvisibility(actor); - - if (actor.getClass().apply (actor, mId, actor) && mUsageType!=-1 && actor == MWMechanics::getPlayer()) - actor.getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); - - actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); + actor.getClass().consume(getTarget(), actor); } } diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 4350b7af4..645b30191 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -1,35 +1,20 @@ #ifndef GAME_MWWORLD_ACTIONAPPLY_H #define GAME_MWWORLD_ACTIONAPPLY_H -#include - #include "action.hpp" +#include +#include namespace MWWorld { class ActionApply : public Action { - std::string mId; + ESM::RefId mId; - void executeImp (const Ptr& actor) override; + void executeImp(const Ptr& actor) override; - public: - - ActionApply (const Ptr& object, const std::string& id); - }; - - class ActionApplyWithSkill : public Action - { - std::string mId; - int mSkillIndex; - int mUsageType; - - void executeImp (const Ptr& actor) override; - - public: - - ActionApplyWithSkill (const Ptr& object, const std::string& id, - int skillIndex, int usageType); + public: + ActionApply(const Ptr& object, const ESM::RefId& id); }; } diff --git a/apps/openmw/mwworld/actiondoor.cpp b/apps/openmw/mwworld/actiondoor.cpp index 6e3441e22..be0d5f329 100644 --- a/apps/openmw/mwworld/actiondoor.cpp +++ b/apps/openmw/mwworld/actiondoor.cpp @@ -5,11 +5,12 @@ namespace MWWorld { - ActionDoor::ActionDoor (const MWWorld::Ptr& object) : Action (false, object) + ActionDoor::ActionDoor(const MWWorld::Ptr& object) + : Action(false, object) { } - void ActionDoor::executeImp (const MWWorld::Ptr& actor) + void ActionDoor::executeImp(const MWWorld::Ptr& actor) { MWBase::Environment::get().getWorld()->activateDoor(getTarget()); } diff --git a/apps/openmw/mwworld/actiondoor.hpp b/apps/openmw/mwworld/actiondoor.hpp index 4be61f576..16b85b90a 100644 --- a/apps/openmw/mwworld/actiondoor.hpp +++ b/apps/openmw/mwworld/actiondoor.hpp @@ -8,10 +8,10 @@ namespace MWWorld { class ActionDoor : public Action { - void executeImp (const MWWorld::Ptr& actor) override; + void executeImp(const MWWorld::Ptr& actor) override; - public: - ActionDoor (const Ptr& object); + public: + ActionDoor(const Ptr& object); }; } diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index ef435cca9..b77fe146e 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -1,8 +1,6 @@ #include "actioneat.hpp" -#include - -#include "../mwworld/containerstore.hpp" +#include #include "../mwmechanics/actorutil.hpp" @@ -10,17 +8,14 @@ namespace MWWorld { - void ActionEat::executeImp (const Ptr& actor) + void ActionEat::executeImp(const Ptr& actor) { - // remove used item (assume the item is present in inventory) - getTarget().getContainerStore()->remove(getTarget(), 1, actor); - - // apply to actor - std::string id = getTarget().getCellRef().getRefId(); - - if (actor.getClass().apply (actor, id, actor) && actor == MWMechanics::getPlayer()) - actor.getClass().skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); + if (actor.getClass().consume(getTarget(), actor) && actor == MWMechanics::getPlayer()) + actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, 1); } - ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} + ActionEat::ActionEat(const MWWorld::Ptr& object) + : Action(false, object) + { + } } diff --git a/apps/openmw/mwworld/actioneat.hpp b/apps/openmw/mwworld/actioneat.hpp index ddc231d99..428635e4d 100644 --- a/apps/openmw/mwworld/actioneat.hpp +++ b/apps/openmw/mwworld/actioneat.hpp @@ -7,11 +7,10 @@ namespace MWWorld { class ActionEat : public Action { - void executeImp (const Ptr& actor) override; + void executeImp(const Ptr& actor) override; - public: - - ActionEat (const MWWorld::Ptr& object); + public: + ActionEat(const MWWorld::Ptr& object); }; } diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 92cd04438..58e6f013a 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -5,21 +5,18 @@ #include "../mwmechanics/actorutil.hpp" -#include - -#include "inventorystore.hpp" -#include "player.hpp" #include "class.hpp" +#include "inventorystore.hpp" namespace MWWorld { - ActionEquip::ActionEquip (const MWWorld::Ptr& object, bool force) - : Action (false, object) - , mForce(force) + ActionEquip::ActionEquip(const MWWorld::Ptr& object, bool force) + : Action(false, object) + , mForce(force) { } - void ActionEquip::executeImp (const Ptr& actor) + void ActionEquip::executeImp(const Ptr& actor) { MWWorld::Ptr object = getTarget(); MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); @@ -34,13 +31,13 @@ namespace MWWorld if (!mForce) { - std::pair result = object.getClass().canBeEquipped (object, actor); + auto result = object.getClass().canBeEquipped(object, actor); // display error message if the player tried to equip something if (!result.second.empty() && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox(result.second); - switch(result.first) + switch (result.first) { case 0: return; @@ -65,15 +62,11 @@ namespace MWWorld } if (it == invStore.end()) - { - std::stringstream error; - error << "ActionEquip can't find item " << object.getCellRef().getRefId(); - throw std::runtime_error(error.str()); - } + throw std::runtime_error("ActionEquip can't find item " + object.getCellRef().getRefId().toDebugString()); // equip the item in the first free slot - std::vector::const_iterator slot=slots_.first.begin(); - for (;slot!=slots_.first.end(); ++slot) + std::vector::const_iterator slot = slots_.first.begin(); + for (; slot != slots_.first.end(); ++slot) { // if the item is equipped already, nothing to do if (invStore.getSlot(*slot) == it) @@ -82,7 +75,7 @@ namespace MWWorld if (invStore.getSlot(*slot) == invStore.end()) { // slot is not occupied - invStore.equip(*slot, it, actor); + invStore.equip(*slot, it); break; } } @@ -95,17 +88,17 @@ namespace MWWorld bool reEquip = false; for (slot = slots_.first.begin(); slot != slots_.first.end(); ++slot) { - invStore.unequipSlot(*slot, actor, false); + invStore.unequipSlot(*slot, false); if (slot + 1 != slots_.first.end()) { - invStore.equip(*slot, invStore.getSlot(*(slot + 1)), actor); + invStore.equip(*slot, invStore.getSlot(*(slot + 1))); } else { - invStore.equip(*slot, it, actor); + invStore.equip(*slot, it); } - //Fix for issue of selected enchated item getting remmoved on cycle + // Fix for issue of selected enchated item getting remmoved on cycle if (invStore.getSlot(*slot) == enchItem) { reEquip = true; diff --git a/apps/openmw/mwworld/actionequip.hpp b/apps/openmw/mwworld/actionequip.hpp index 1ac3cb236..863d28164 100644 --- a/apps/openmw/mwworld/actionequip.hpp +++ b/apps/openmw/mwworld/actionequip.hpp @@ -9,11 +9,11 @@ namespace MWWorld { bool mForce; - void executeImp (const Ptr& actor) override; + void executeImp(const Ptr& actor) override; public: /// @param item to equip - ActionEquip (const Ptr& object, bool force=false); + ActionEquip(const Ptr& object, bool force = false); }; } diff --git a/apps/openmw/mwworld/actionharvest.cpp b/apps/openmw/mwworld/actionharvest.cpp index 4dd5b62aa..405361f57 100644 --- a/apps/openmw/mwworld/actionharvest.cpp +++ b/apps/openmw/mwworld/actionharvest.cpp @@ -4,6 +4,7 @@ #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -18,6 +19,9 @@ */ #include +======= +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -29,18 +33,19 @@ namespace MWWorld { - ActionHarvest::ActionHarvest (const MWWorld::Ptr& container) - : Action (true, container) + ActionHarvest::ActionHarvest(const MWWorld::Ptr& container) + : Action(true, container) { - setSound("Item Ingredient Up"); + setSound(ESM::RefId::stringRefId("Item Ingredient Up")); } - void ActionHarvest::executeImp (const MWWorld::Ptr& actor) + void ActionHarvest::executeImp(const MWWorld::Ptr& actor) { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; MWWorld::Ptr target = getTarget(); +<<<<<<< HEAD /* Start of tes3mp addition @@ -61,6 +66,9 @@ namespace MWWorld */ MWWorld::ContainerStore& store = target.getClass().getContainerStore (target); +======= + MWWorld::ContainerStore& store = target.getClass().getContainerStore(target); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 store.resolve(); MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor); std::map takenMap; @@ -70,9 +78,11 @@ namespace MWWorld continue; int itemCount = it->getRefData().getCount(); - // Note: it is important to check for crime before move an item from container. Otherwise owner check will not work - // for a last item in the container - empty harvested containers are considered as "allowed to use". + // Note: it is important to check for crime before move an item from container. Otherwise owner check will + // not work for a last item in the container - empty harvested containers are considered as "allowed to + // use". MWBase::Environment::get().getMechanicsManager()->itemTaken(actor, *it, target, itemCount); +<<<<<<< HEAD actorStore.add(*it, itemCount, actor); /* @@ -87,6 +97,12 @@ namespace MWWorld store.remove(*it, itemCount, getTarget()); takenMap[it->getClass().getName(*it)]+=itemCount; +======= + actorStore.add(*it, itemCount); + store.remove(*it, itemCount); + std::string name{ it->getClass().getName(*it) }; + takenMap[name] += itemCount; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } /* @@ -109,9 +125,9 @@ namespace MWWorld std::ostringstream stream; int lineCount = 0; const static int maxLines = 10; - for (auto & pair : takenMap) + for (const auto& pair : takenMap) { - std::string itemName = pair.first; + const std::string& itemName = pair.first; int itemCount = pair.second; lineCount++; if (lineCount == maxLines) @@ -119,7 +135,8 @@ namespace MWWorld else if (lineCount > maxLines) break; - // The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory + // The two GMST entries below expand to strings informing the player of what, and how many of it has + // been added to their inventory std::string msgBox; if (itemCount == 1) { diff --git a/apps/openmw/mwworld/actionharvest.hpp b/apps/openmw/mwworld/actionharvest.hpp index 2edc2f34e..0c0596a69 100644 --- a/apps/openmw/mwworld/actionharvest.hpp +++ b/apps/openmw/mwworld/actionharvest.hpp @@ -8,11 +8,11 @@ namespace MWWorld { class ActionHarvest : public Action { - void executeImp (const MWWorld::Ptr& actor) override; + void executeImp(const MWWorld::Ptr& actor) override; - public: - ActionHarvest (const Ptr& container); - ///< \param container The Container the Player has activated. + public: + ActionHarvest(const Ptr& container); + ///< \param container The Container the Player has activated. }; } diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp index 266ea4d95..f0d101be9 100644 --- a/apps/openmw/mwworld/actionopen.cpp +++ b/apps/openmw/mwworld/actionopen.cpp @@ -4,16 +4,17 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/disease.hpp" namespace MWWorld { - ActionOpen::ActionOpen (const MWWorld::Ptr& container) - : Action (false, container) + ActionOpen::ActionOpen(const MWWorld::Ptr& container) + : Action(false, container) { } - void ActionOpen::executeImp (const MWWorld::Ptr& actor) + void ActionOpen::executeImp(const MWWorld::Ptr& actor) { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index e9f76c4d7..b6d41f9a3 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -7,12 +7,11 @@ namespace MWWorld { class ActionOpen : public Action { - void executeImp (const MWWorld::Ptr& actor) override; - - public: - ActionOpen (const Ptr& container); - ///< \param container The Container the Player has activated. + void executeImp(const MWWorld::Ptr& actor) override; + public: + ActionOpen(const Ptr& container); + ///< \param container The Container the Player has activated. }; } diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index d81ad83f1..6ac7b2301 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -1,5 +1,6 @@ #include "actionread.hpp" +<<<<<<< HEAD /* Start of tes3mp addition @@ -10,61 +11,66 @@ /* End of tes3mp addition */ +======= +#include +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" -#include "player.hpp" #include "class.hpp" #include "esmstore.hpp" namespace MWWorld { - ActionRead::ActionRead (const MWWorld::Ptr& object) : Action (false, object) + ActionRead::ActionRead(const MWWorld::Ptr& object) + : Action(false, object) { } - void ActionRead::executeImp (const MWWorld::Ptr& actor) { + void ActionRead::executeImp(const MWWorld::Ptr& actor) + { if (actor != MWMechanics::getPlayer()) return; - //Ensure we're not in combat - if(MWMechanics::isPlayerInCombat() - // Reading in combat is still allowed if the scroll/book is not in the player inventory yet - // (since otherwise, there would be no way to pick it up) - && getTarget().getContainerStore() == &actor.getClass().getContainerStore(actor) - ) { + // Ensure we're not in combat + if (MWMechanics::isPlayerInCombat() + // Reading in combat is still allowed if the scroll/book is not in the player inventory yet + // (since otherwise, there would be no way to pick it up) + && getTarget().getContainerStore() == &actor.getClass().getContainerStore(actor)) + { MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage4}"); return; } - LiveCellRef *ref = getTarget().get(); + LiveCellRef* ref = getTarget().get(); if (ref->mBase->mData.mIsScroll) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Scroll, getTarget()); else MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Book, getTarget()); - MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); + MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); // Skill gain from books - if (ref->mBase->mData.mSkillId >= 0 && ref->mBase->mData.mSkillId < ESM::Skill::Length - && !npcStats.hasBeenUsed (ref->mBase->mId)) + ESM::RefId skill = ESM::Skill::indexToRefId(ref->mBase->mData.mSkillId); + if (!skill.empty() && !npcStats.hasBeenUsed(ref->mBase->mId)) { - MWWorld::LiveCellRef *playerRef = actor.get(); + MWWorld::LiveCellRef* playerRef = actor.get(); - const ESM::Class *class_ = - MWBase::Environment::get().getWorld()->getStore().get().find ( - playerRef->mBase->mClass - ); + const ESM::Class* class_ + = MWBase::Environment::get().getESMStore()->get().find(playerRef->mBase->mClass); - npcStats.increaseSkill (ref->mBase->mData.mSkillId, *class_, true, true); + npcStats.increaseSkill(skill, *class_, true, true); +<<<<<<< HEAD npcStats.flagAsUsed (ref->mBase->mId); /* @@ -76,7 +82,9 @@ namespace MWWorld /* End of tes3mp addition */ +======= + npcStats.flagAsUsed(ref->mBase->mId); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - } } diff --git a/apps/openmw/mwworld/actionread.hpp b/apps/openmw/mwworld/actionread.hpp index 6387933eb..194aca9f5 100644 --- a/apps/openmw/mwworld/actionread.hpp +++ b/apps/openmw/mwworld/actionread.hpp @@ -7,11 +7,11 @@ namespace MWWorld { class ActionRead : public Action { - void executeImp (const MWWorld::Ptr& actor) override; + void executeImp(const MWWorld::Ptr& actor) override; - public: - /// @param book or scroll to read - ActionRead (const Ptr& object); + public: + /// @param book or scroll to read + ActionRead(const Ptr& object); }; } diff --git a/apps/openmw/mwworld/actionrepair.cpp b/apps/openmw/mwworld/actionrepair.cpp index 4fe48b4b4..4d5ec2b2e 100644 --- a/apps/openmw/mwworld/actionrepair.cpp +++ b/apps/openmw/mwworld/actionrepair.cpp @@ -2,23 +2,22 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld { ActionRepair::ActionRepair(const Ptr& item, bool force) - : Action (false, item) + : Action(false, item) , mForce(force) { } - void ActionRepair::executeImp (const Ptr& actor) + void ActionRepair::executeImp(const Ptr& actor) { if (actor != MWMechanics::getPlayer()) return; - if(!mForce && MWMechanics::isPlayerInCombat()) + if (!mForce && MWMechanics::isPlayerInCombat()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage2}"); return; diff --git a/apps/openmw/mwworld/actionrepair.hpp b/apps/openmw/mwworld/actionrepair.hpp index 218077f5d..54611ff32 100644 --- a/apps/openmw/mwworld/actionrepair.hpp +++ b/apps/openmw/mwworld/actionrepair.hpp @@ -9,11 +9,11 @@ namespace MWWorld { bool mForce; - void executeImp (const Ptr& actor) override; + void executeImp(const Ptr& actor) override; public: /// @param item repair hammer - ActionRepair(const Ptr& item, bool force=false); + ActionRepair(const Ptr& item, bool force = false); }; } diff --git a/apps/openmw/mwworld/actionsoulgem.cpp b/apps/openmw/mwworld/actionsoulgem.cpp index f7823daba..74eda6bba 100644 --- a/apps/openmw/mwworld/actionsoulgem.cpp +++ b/apps/openmw/mwworld/actionsoulgem.cpp @@ -1,30 +1,52 @@ #include "actionsoulgem.hpp" -#include "../mwbase/windowmanager.hpp" +#include +#include + #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwworld/esmstore.hpp" + namespace MWWorld { - ActionSoulgem::ActionSoulgem(const Ptr &object) + ActionSoulgem::ActionSoulgem(const Ptr& object) : Action(false, object) { - } - void ActionSoulgem::executeImp(const Ptr &actor) + void ActionSoulgem::executeImp(const Ptr& actor) { if (actor != MWMechanics::getPlayer()) return; - if(MWMechanics::isPlayerInCombat()) { //Ensure we're not in combat + if (MWMechanics::isPlayerInCombat()) + { // Ensure we're not in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage5}"); return; } - MWBase::Environment::get().getWindowManager()->showSoulgemDialog(getTarget()); + + const auto& target = getTarget(); + const ESM::RefId& targetSoul = target.getCellRef().getSoul(); + + if (targetSoul.empty()) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage32}"); + return; + } + + if (!MWBase::Environment::get().getESMStore()->get().search(targetSoul)) + { + Log(Debug::Warning) << "Soul '" << targetSoul << "' not found (item: '" << target.getCellRef().getRefId() + << "')"; + return; + } + + MWBase::Environment::get().getWindowManager()->showSoulgemDialog(target); } - } diff --git a/apps/openmw/mwworld/actionsoulgem.hpp b/apps/openmw/mwworld/actionsoulgem.hpp index d2b14c912..f17ddb7bd 100644 --- a/apps/openmw/mwworld/actionsoulgem.hpp +++ b/apps/openmw/mwworld/actionsoulgem.hpp @@ -7,11 +7,11 @@ namespace MWWorld { class ActionSoulgem : public Action { - void executeImp (const MWWorld::Ptr& actor) override; + void executeImp(const MWWorld::Ptr& actor) override; - public: - /// @param soulgem to use - ActionSoulgem (const Ptr& object); + public: + /// @param soulgem to use + ActionSoulgem(const Ptr& object); }; } diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 4952f269d..0cb1e8dc9 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -13,9 +13,9 @@ */ #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwgui/inventorywindow.hpp" @@ -24,9 +24,12 @@ namespace MWWorld { - ActionTake::ActionTake (const MWWorld::Ptr& object) : Action (true, object) {} + ActionTake::ActionTake(const MWWorld::Ptr& object) + : Action(true, object) + { + } - void ActionTake::executeImp (const Ptr& actor) + void ActionTake::executeImp(const Ptr& actor) { // When in GUI mode, we should use drag and drop if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) @@ -40,6 +43,7 @@ namespace MWWorld } MWBase::Environment::get().getMechanicsManager()->itemTaken( +<<<<<<< HEAD actor, getTarget(), MWWorld::Ptr(), getTarget().getRefData().getCount()); MWWorld::Ptr newitem = *actor.getClass().getContainerStore (actor).add (getTarget(), getTarget().getRefData().getCount(), actor); @@ -59,6 +63,12 @@ namespace MWWorld */ MWBase::Environment::get().getWorld()->deleteObject (getTarget()); +======= + actor, getTarget(), MWWorld::Ptr(), getTarget().getRefData().getCount()); + MWWorld::Ptr newitem + = *actor.getClass().getContainerStore(actor).add(getTarget(), getTarget().getRefData().getCount()); + MWBase::Environment::get().getWorld()->deleteObject(getTarget()); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 setTarget(newitem); } } diff --git a/apps/openmw/mwworld/actiontake.hpp b/apps/openmw/mwworld/actiontake.hpp index bb9c0d380..7cb0e03dd 100644 --- a/apps/openmw/mwworld/actiontake.hpp +++ b/apps/openmw/mwworld/actiontake.hpp @@ -7,11 +7,10 @@ namespace MWWorld { class ActionTake : public Action { - void executeImp (const Ptr& actor) override; + void executeImp(const Ptr& actor) override; - public: - - ActionTake (const MWWorld::Ptr& object); + public: + ActionTake(const MWWorld::Ptr& object); }; } diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 0d0b94435..488e0d26f 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -7,9 +7,12 @@ namespace MWWorld { - ActionTalk::ActionTalk (const Ptr& actor) : Action (false, actor) {} + ActionTalk::ActionTalk(const Ptr& actor) + : Action(false, actor) + { + } - void ActionTalk::executeImp (const Ptr& actor) + void ActionTalk::executeImp(const Ptr& actor) { /* Start of tes3mp change (major) diff --git a/apps/openmw/mwworld/actiontalk.hpp b/apps/openmw/mwworld/actiontalk.hpp index 6dd70380f..f2d2843a6 100644 --- a/apps/openmw/mwworld/actiontalk.hpp +++ b/apps/openmw/mwworld/actiontalk.hpp @@ -7,12 +7,11 @@ namespace MWWorld { class ActionTalk : public Action { - void executeImp (const Ptr& actor) override; + void executeImp(const Ptr& actor) override; - public: - - ActionTalk (const Ptr& actor); - ///< \param actor The actor the player is talking to + public: + ActionTalk(const Ptr& actor); + ///< \param actor The actor the player is talking to }; } diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 2dbc84364..899f4ba8f 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -1,5 +1,6 @@ #include "actionteleport.hpp" +<<<<<<< HEAD /* Start of tes3mp addition @@ -19,28 +20,43 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +======= +#include +#include +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/worldmodel.hpp" #include "player.hpp" namespace MWWorld { - ActionTeleport::ActionTeleport (const std::string& cellName, - const ESM::Position& position, bool teleportFollowers) - : Action (true), mCellName (cellName), mPosition (position), mTeleportFollowers(teleportFollowers) + ActionTeleport::ActionTeleport(ESM::RefId cellId, const ESM::Position& position, bool teleportFollowers) + : Action(true) + , mCellId(cellId) + , mPosition(position) + , mTeleportFollowers(teleportFollowers) { } - void ActionTeleport::executeImp (const Ptr& actor) + void ActionTeleport::executeImp(const Ptr& actor) { if (mTeleportFollowers) { // Find any NPCs that are following the actor and teleport them with him std::set followers; - getFollowers(actor, followers, true); + + bool toExterior = MWBase::Environment::get().getWorldModel()->getCell(mCellId).isExterior(); + getFollowers(actor, followers, toExterior, true); for (std::set::iterator it = followers.begin(); it != followers.end(); ++it) teleport(*it); @@ -49,17 +65,20 @@ namespace MWWorld teleport(actor); } - void ActionTeleport::teleport(const Ptr &actor) + void ActionTeleport::teleport(const Ptr& actor) { MWBase::World* world = MWBase::Environment::get().getWorld(); - actor.getClass().getCreatureStats(actor).land(actor == world->getPlayerPtr()); - if(actor == world->getPlayerPtr()) + MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel(); + auto& stats = actor.getClass().getCreatureStats(actor); + stats.land(actor == world->getPlayerPtr()); + stats.setTeleported(true); + + Ptr teleported; + if (actor == world->getPlayerPtr()) { world->getPlayer().setTeleported(true); - if (mCellName.empty()) - world->changeToExteriorCell (mPosition, true); - else - world->changeToInteriorCell (mCellName, mPosition, true); + world->changeToCell(mCellId, mPosition, true); + teleported = world->getPlayerPtr(); } else { @@ -83,9 +102,8 @@ namespace MWWorld mwmp::CellController *cellController = mwmp::Main::get().getCellController(); if (actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(world->getPlayerPtr())) - actor.getClass().getCreatureStats(actor).getAiSequence().stopCombat(); - else if (mCellName.empty()) { +<<<<<<< HEAD int cellX; int cellY; world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); @@ -96,8 +114,14 @@ namespace MWWorld world->moveObject(actor,world->getExterior(cellX,cellY), mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); +======= + actor.getClass().getCreatureStats(actor).getAiSequence().stopCombat(); + return; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + else +<<<<<<< HEAD { newCellStore = world->getInterior(mCellName); if (cellController->isDedicatedActor(actor)) @@ -149,26 +173,40 @@ namespace MWWorld /* End of tes3mp addition */ +======= + teleported = world->moveObject(actor, &worldModel->getCell(mCellId), mPosition.asVec3(), true, true); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } + + if (!world->isWaterWalkingCastableOnTarget(teleported) && MWMechanics::hasWaterWalking(teleported)) + teleported.getClass() + .getCreatureStats(teleported) + .getActiveSpells() + .purgeEffect(actor, ESM::MagicEffect::WaterWalking); } - void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set& out, bool includeHostiles) { + void ActionTeleport::getFollowers( + const MWWorld::Ptr& actor, std::set& out, bool toExterior, bool includeHostiles) + { std::set followers; MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers); - for(std::set::iterator it = followers.begin();it != followers.end();++it) + for (std::set::iterator it = followers.begin(); it != followers.end(); ++it) { MWWorld::Ptr follower = *it; - std::string script = follower.getClass().getScript(follower); + const ESM::RefId& script = follower.getClass().getScript(follower); if (!includeHostiles && follower.getClass().getCreatureStats(follower).getAiSequence().isInCombat(actor)) continue; - if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) + if (!toExterior && !script.empty() + && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1 + && follower.getCell()->getCell()->isExterior()) continue; - if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() > 800 * 800) + if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() + > 800 * 800) continue; out.emplace(follower); diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp index 0a981a418..377e0ce51 100644 --- a/apps/openmw/mwworld/actionteleport.hpp +++ b/apps/openmw/mwworld/actionteleport.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -12,25 +13,26 @@ namespace MWWorld { class ActionTeleport : public Action { - std::string mCellName; - ESM::Position mPosition; - bool mTeleportFollowers; + ESM::RefId mCellId; + ESM::Position mPosition; + bool mTeleportFollowers; - /// Teleports this actor and also teleports anyone following that actor. - void executeImp (const Ptr& actor) override; + /// Teleports this actor and also teleports anyone following that actor. + void executeImp(const Ptr& actor) override; - /// Teleports only the given actor (internal use). - void teleport(const Ptr &actor); + /// Teleports only the given actor (internal use). + void teleport(const Ptr& actor); - public: + public: + /// If cellName is empty, an exterior cell is assumed. + /// @param teleportFollowers Whether to teleport any following actors of the target actor as well. + ActionTeleport(ESM::RefId cellId, const ESM::Position& position, bool teleportFollowers); - /// If cellName is empty, an exterior cell is assumed. - /// @param teleportFollowers Whether to teleport any following actors of the target actor as well. - ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers); - - /// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the output, - /// e.g. so that the teleport action can calm them. - static void getFollowers(const MWWorld::Ptr& actor, std::set& out, bool includeHostiles = false); + /// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the + /// output, + /// e.g. so that the teleport action can calm them. + static void getFollowers( + const MWWorld::Ptr& actor, std::set& out, bool toExterior, bool includeHostiles = false); }; } diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index f80a6b84e..534358bd4 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -19,7 +19,7 @@ namespace MWWorld { - void ActionTrap::executeImp(const Ptr &actor) + void ActionTrap::executeImp(const Ptr& actor) { osg::Vec3f actorPosition(actor.getRefData().getPosition().asVec3()); osg::Vec3f trapPosition(mTrapSource.getRefData().getPosition().asVec3()); @@ -29,7 +29,9 @@ namespace MWWorld // radius, because for most trap spells this is 1 foot, much less than the activation distance. // Using activation distance as the trap range. - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > trapRange) // player activated object outside range of trap + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() + && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() + > trapRange) // player activated object outside range of trap { MWMechanics::CastSpell cast(mTrapSource, mTrapSource); cast.mHitPosition = trapPosition; @@ -41,6 +43,7 @@ namespace MWWorld cast.mHitPosition = actorPosition; cast.cast(mSpellId); } +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -74,5 +77,8 @@ namespace MWWorld /* End of tes3mp addition */ +======= + mTrapSource.getCellRef().setTrap(ESM::RefId()); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } } diff --git a/apps/openmw/mwworld/actiontrap.hpp b/apps/openmw/mwworld/actiontrap.hpp index fe51959d1..02200db6b 100644 --- a/apps/openmw/mwworld/actiontrap.hpp +++ b/apps/openmw/mwworld/actiontrap.hpp @@ -9,19 +9,21 @@ namespace MWWorld { class ActionTrap : public Action { - std::string mSpellId; - MWWorld::Ptr mTrapSource; + ESM::RefId mSpellId; + MWWorld::Ptr mTrapSource; - void executeImp (const Ptr& actor) override; + void executeImp(const Ptr& actor) override; - public: - - /// @param spellId - /// @param trapSource - ActionTrap (const std::string& spellId, const Ptr& trapSource) - : Action(false, trapSource), mSpellId(spellId), mTrapSource(trapSource) {} + public: + /// @param spellId + /// @param trapSource + ActionTrap(const ESM::RefId& spellId, const Ptr& trapSource) + : Action(false, trapSource) + , mSpellId(spellId) + , mTrapSource(trapSource) + { + } }; } - #endif diff --git a/apps/openmw/mwworld/cell.cpp b/apps/openmw/mwworld/cell.cpp new file mode 100644 index 000000000..f4a57b87c --- /dev/null +++ b/apps/openmw/mwworld/cell.cpp @@ -0,0 +1,85 @@ +#include "cell.hpp" + +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "esmstore.hpp" + +namespace MWWorld +{ + Cell::Cell(const ESM4::Cell& cell) + : ESM::CellVariant(cell) + , mIsExterior(!(cell.mCellFlags & ESM4::CELL_Interior)) + , mIsQuasiExterior(cell.mCellFlags & ESM4::CELL_QuasiExt) + , mHasWater(cell.mCellFlags & ESM4::CELL_HasWater) + , mNoSleep(false) // No such notion in ESM4 + , mGridPos(cell.mX, cell.mY) + , mDisplayname(cell.mFullName) + , mNameID(cell.mEditorId) + , mRegion(ESM::RefId()) // Unimplemented for now + , mId(cell.mId) + , mParent(cell.mParent) + ,mMood{ + .mAmbiantColor = cell.mLighting.ambient, + .mDirectionalColor = cell.mLighting.directional, + .mFogColor = cell.mLighting.fogColor, + // TODO: use ESM4::Lighting fog parameters + .mFogDensity = 1.f,} + ,mWaterHeight(cell.mWaterHeight) + { + if (isExterior()) + { + auto& worldStore = MWBase::Environment::get().getESMStore()->get(); + const ESM4::World* cellWorld = worldStore.find(mParent); + mWaterHeight = cellWorld->mWaterLevel; + } + } + + Cell::Cell(const ESM::Cell& cell) + : ESM::CellVariant(cell) + , mIsExterior(!(cell.mData.mFlags & ESM::Cell::Interior)) + , mIsQuasiExterior(cell.mData.mFlags & ESM::Cell::QuasiEx) + , mHasWater(cell.mData.mFlags & ESM::Cell::HasWater) + , mNoSleep(cell.mData.mFlags & ESM::Cell::NoSleep) + , mGridPos(cell.getGridX(), cell.getGridY()) + , mDisplayname(cell.mName) + , mNameID(cell.mName) + , mRegion(cell.mRegion) + , mId(cell.mId) + , mParent(ESM::Cell::sDefaultWorldspaceId) + , mMood{ + .mAmbiantColor = cell.mAmbi.mAmbient, + .mDirectionalColor = cell.mAmbi.mSunlight, + .mFogColor = cell.mAmbi.mFog, + .mFogDensity = cell.mAmbi.mFogDensity, + } + ,mWaterHeight(cell.mWater) + { + if (isExterior()) + mWaterHeight = -1.f; + } + + std::string Cell::getDescription() const + { + return ESM::visit(ESM::VisitOverload{ + [&](const ESM::Cell& cell) { return cell.getDescription(); }, + [&](const ESM4::Cell& cell) { return cell.mEditorId; }, + }, + *this); + } + ESM::RefId Cell::getWorldSpace() const + { + if (isExterior()) + return mParent; + else + return mId; + } + + ESM::ExteriorCellLocation Cell::getExteriorCellLocation() const + { + return { mGridPos.x(), mGridPos.y(), getWorldSpace() }; + } +} diff --git a/apps/openmw/mwworld/cell.hpp b/apps/openmw/mwworld/cell.hpp new file mode 100644 index 000000000..827b360a4 --- /dev/null +++ b/apps/openmw/mwworld/cell.hpp @@ -0,0 +1,72 @@ +#ifndef OPENW_MWORLD_CELL +#define OPENW_MWORLD_CELL + +#include + +#include +#include +#include + +namespace ESM +{ + struct Cell; +} + +namespace ESM4 +{ + struct Cell; +} + +namespace MWWorld +{ + class CellStore; + + class Cell : public ESM::CellVariant + { + struct MoodData + { + uint32_t mAmbiantColor; + uint32_t mDirectionalColor; + uint32_t mFogColor; + float mFogDensity; + }; + + public: + explicit Cell(const ESM4::Cell& cell); + explicit Cell(const ESM::Cell& cell); + + int getGridX() const { return mGridPos.x(); } + int getGridY() const { return mGridPos.y(); } + bool isExterior() const { return mIsExterior; } + bool isQuasiExterior() const { return mIsQuasiExterior; } + bool hasWater() const { return mHasWater; } + bool noSleep() const { return mNoSleep; } + const ESM::RefId& getRegion() const { return mRegion; } + std::string_view getNameId() const { return mNameID; } + std::string_view getDisplayName() const { return mDisplayname; } + std::string getDescription() const; + const MoodData& getMood() const { return mMood; } + float getWaterHeight() const { return mWaterHeight; } + const ESM::RefId& getId() const { return mId; } + ESM::RefId getWorldSpace() const; + ESM::ExteriorCellLocation getExteriorCellLocation() const; + + private: + bool mIsExterior; + bool mIsQuasiExterior; + bool mHasWater; + bool mNoSleep; + + osg::Vec2i mGridPos; + std::string mDisplayname; // How the game displays it + std::string mNameID; // The name that will be used by the script and console commands + ESM::RefId mRegion; + ESM::RefId mId; + ESM::RefId mParent; + MoodData mMood; + + float mWaterHeight; + }; +} + +#endif diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 44afde22a..2d6a4e273 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -4,22 +4,47 @@ #include #include -#include -#include +#include +#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include #include -#include -#include +#include #include "../mwrender/landmanager.hpp" #include "cellstore.hpp" #include "class.hpp" +namespace +{ + template + bool contains(const std::vector& container, const Contained& contained, + float tolerance) + { + for (const auto& pos : contained) + { + bool found = false; + for (const auto& pos2 : container) + { + if ((pos.first - pos2.first).length2() < tolerance * tolerance && pos.second == pos2.second) + { + found = true; + break; + } + } + if (!found) + return false; + } + return true; + } +} + namespace MWWorld { @@ -47,7 +72,9 @@ namespace MWWorld { public: /// Constructor to be called from the main thread. - PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain, MWRender::LandManager* landManager, bool preloadInstances) + PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, + Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, + Terrain::World* terrain, MWRender::LandManager* landManager, bool preloadInstances) : mIsExterior(cell->getCell()->isExterior()) , mX(cell->getCell()->getGridX()) , mY(cell->getCell()->getGridY()) @@ -61,14 +88,11 @@ namespace MWWorld { mTerrainView = mTerrain->createView(); - ListModelsVisitor visitor (mMeshes); + ListModelsVisitor visitor(mMeshes); cell->forEach(visitor); } - void abort() override - { - mAbort = true; - } + void abort() override { mAbort = true; } /// Preload work to be called from the worker thread. void doWork() override @@ -78,14 +102,15 @@ namespace MWWorld try { mTerrain->cacheCell(mTerrainView.get(), mX, mY); - mPreloadedObjects.insert(mLandManager->getLand(mX, mY)); + mPreloadedObjects.insert( + mLandManager->getLand(ESM::ExteriorCellLocation(mX, mY, ESM::Cell::sDefaultWorldspaceId))); } - catch(std::exception&) + catch (std::exception&) { } } - for (std::string& mesh: mMeshes) + for (std::string& mesh : mMeshes) { if (mAbort) break; @@ -94,34 +119,26 @@ namespace MWWorld { mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); - bool animated = false; size_t slashpos = mesh.find_last_of("/\\"); - if (slashpos != std::string::npos && slashpos != mesh.size()-1) + if (slashpos != std::string::npos && slashpos != mesh.size() - 1) { Misc::StringUtils::lowerCaseInPlace(mesh); - if (mesh[slashpos+1] == 'x') + if (mesh[slashpos + 1] == 'x') { - std::string kfname = mesh; - if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) + if (mesh.size() > 4 && mesh.ends_with(".nif")) { - kfname.replace(kfname.size()-4, 4, ".kf"); + std::string kfname = mesh; + kfname.replace(kfname.size() - 4, 4, ".kf"); if (mSceneManager->getVFS()->exists(kfname)) - { mPreloadedObjects.insert(mKeyframeManager->get(kfname)); - animated = true; - } } } } - if (mPreloadInstances && animated) - mPreloadedObjects.insert(mSceneManager->cacheInstance(mesh)); - else - mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); + mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); if (mPreloadInstances) mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh)); else mPreloadedObjects.insert(mBulletShapeManager->getShape(mesh)); - } catch (std::exception&) { @@ -149,54 +166,42 @@ namespace MWWorld osg::ref_ptr mTerrainView; // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state - std::set > mPreloadedObjects; + std::set> mPreloadedObjects; }; class TerrainPreloadItem : public SceneUtil::WorkItem { public: - TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) + TerrainPreloadItem(const std::vector>& views, Terrain::World* world, + const std::vector& preloadPositions) : mAbort(false) - , mProgress(views.size()) - , mProgressRange(0) , mTerrainViews(views) , mWorld(world) , mPreloadPositions(preloadPositions) { } - bool storeViews(double referenceTime) - { - for (unsigned int i=0; istoreView(mTerrainViews[i], referenceTime)) - return false; - return true; - } - void doWork() override { - for (unsigned int i=0; ireset(); - mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mProgress[i], mProgressRange); + mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, + mLoadingReporter); } + mLoadingReporter.complete(); } - void abort() override - { - mAbort = true; - } + void abort() override { mAbort = true; } - int getProgress() const { return !mProgress.empty() ? mProgress[0].load() : 0; } - int getProgressRange() const { return !mProgress.empty() && mProgress[0].load() ? mProgressRange : 0; } + void wait(Loading::Listener& listener) const { mLoadingReporter.wait(listener); } private: std::atomic mAbort; - std::vector> mProgress; - int mProgressRange; - std::vector > mTerrainViews; + std::vector> mTerrainViews; Terrain::World* mWorld; std::vector mPreloadPositions; + Loading::Reporter mLoadingReporter; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. @@ -209,17 +214,15 @@ namespace MWWorld { } - void doWork() override - { - mResourceSystem->updateCache(mReferenceTime); - } + void doWork() override { mResourceSystem->updateCache(mReferenceTime); } private: double mReferenceTime; Resource::ResourceSystem* mResourceSystem; }; - CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager) + CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, + Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager) : mResourceSystem(resourceSystem) , mBulletShapeManager(bulletShapeManager) , mTerrain(terrain) @@ -229,48 +232,29 @@ namespace MWWorld , mMaxCacheSize(0) , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) - , mStoreViewsFailCount(0) + , mLoadedTerrainTimestamp(0.0) { } CellPreloader::~CellPreloader() { - if (mTerrainPreloadItem) - { - mTerrainPreloadItem->abort(); - mTerrainPreloadItem->waitTillDone(); - mTerrainPreloadItem = nullptr; - } - - if (mUpdateCacheItem) - { - mUpdateCacheItem->waitTillDone(); - mUpdateCacheItem = nullptr; - } - - for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) - it->second.mWorkItem->abort(); - - for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) - it->second.mWorkItem->waitTillDone(); - - mPreloadCells.clear(); + clearAllTasks(); } - void CellPreloader::preload(CellStore *cell, double timestamp) + void CellPreloader::preload(CellStore& cell, double timestamp) { if (!mWorkQueue) { Log(Debug::Error) << "Error: can't preload, no work queue set"; return; } - if (cell->getState() == CellStore::State_Unloaded) + if (cell.getState() == CellStore::State_Unloaded) { Log(Debug::Error) << "Error: can't preload objects for unloaded cell"; return; } - PreloadMap::iterator found = mPreloadCells.find(cell); + PreloadMap::iterator found = mPreloadCells.find(&cell); if (found != mPreloadCells.end()) { // already preloaded, nothing to do other than updating the timestamp @@ -302,22 +286,22 @@ namespace MWWorld return; } - osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain, mLandManager, mPreloadInstances)); + osg::ref_ptr item(new PreloadItem(&cell, mResourceSystem->getSceneManager(), mBulletShapeManager, + mResourceSystem->getKeyframeManager(), mTerrain, mLandManager, mPreloadInstances)); mWorkQueue->addWorkItem(item); - mPreloadCells[cell] = PreloadEntry(timestamp, item); + mPreloadCells[&cell] = PreloadEntry(timestamp, item); } - void CellPreloader::notifyLoaded(CellStore *cell) + void CellPreloader::notifyLoaded(CellStore* cell) { PreloadMap::iterator found = mPreloadCells.find(cell); if (found != mPreloadCells.end()) { - // do the deletion in the background thread if (found->second.mWorkItem) { found->second.mWorkItem->abort(); - mUnrefQueue->push(mPreloadCells[cell].mWorkItem); + found->second.mWorkItem = nullptr; } mPreloadCells.erase(found); @@ -331,7 +315,7 @@ namespace MWWorld if (it->second.mWorkItem) { it->second.mWorkItem->abort(); - mUnrefQueue->push(it->second.mWorkItem); + it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); @@ -347,7 +331,7 @@ namespace MWWorld if (it->second.mWorkItem) { it->second.mWorkItem->abort(); - mUnrefQueue->push(it->second.mWorkItem); + it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); } @@ -357,7 +341,8 @@ namespace MWWorld if (timestamp - mLastResourceCacheUpdate > 1.0 && (!mUpdateCacheItem || mUpdateCacheItem->isDone())) { - // the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations + // the resource cache is cleared from the worker thread so that we're not holding up the main thread with + // delete operations mUpdateCacheItem = new UpdateCacheItem(mResourceSystem, timestamp); mWorkQueue->addWorkItem(mUpdateCacheItem, true); mLastResourceCacheUpdate = timestamp; @@ -365,18 +350,8 @@ namespace MWWorld if (mTerrainPreloadItem && mTerrainPreloadItem->isDone()) { - if (!mTerrainPreloadItem->storeViews(timestamp)) - { - if (++mStoreViewsFailCount > 100) - { - OSG_ALWAYS << "paging views are rebuilt every frame, please check for faulty enable/disable scripts." << std::endl; - mStoreViewsFailCount = 0; - } - setTerrainPreloadPositions(std::vector()); - } - else - mStoreViewsFailCount = 0; - mTerrainPreloadItem = nullptr; + mLoadedTerrainPositions = mTerrainPreloadPositions; + mLoadedTerrainTimestamp = timestamp; } } @@ -410,43 +385,16 @@ namespace MWWorld mWorkQueue = workQueue; } - void CellPreloader::setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue) + void CellPreloader::syncTerrainLoad(Loading::Listener& listener) { - mUnrefQueue = unrefQueue; + if (mTerrainPreloadItem != nullptr && !mTerrainPreloadItem->isDone()) + mTerrainPreloadItem->wait(listener); } - bool CellPreloader::syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp) + void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid* exceptPos) { - if (!mTerrainPreloadItem) - return true; - else if (mTerrainPreloadItem->isDone()) - { - if (mTerrainPreloadItem->storeViews(timestamp)) - { - mTerrainPreloadItem = nullptr; - return true; - } - else - { - setTerrainPreloadPositions(std::vector()); - setTerrainPreloadPositions(positions); - return false; - } - } - else - { - progress = mTerrainPreloadItem->getProgress(); - progressRange = mTerrainPreloadItem->getProgressRange(); - return false; - } - } - - void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) - { - const float resetThreshold = ESM::Land::REAL_SIZE; - for (const auto& pos : mTerrainPreloadPositions) - if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second) - return; + if (exceptPos && contains(mTerrainPreloadPositions, std::array{ *exceptPos }, ESM::Land::REAL_SIZE)) + return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) { mTerrainPreloadItem->abort(); @@ -455,43 +403,24 @@ namespace MWWorld setTerrainPreloadPositions(std::vector()); } - bool contains(const std::vector& container, const std::vector& contained) - { - for (const auto& pos : contained) - { - bool found = false; - for (const auto& pos2 : container) - { - if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) - { - found = true; - break; - } - } - if (!found) return false; - } - return true; - } - - void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) + void CellPreloader::setTerrainPreloadPositions(const std::vector& positions) { if (positions.empty()) + { mTerrainPreloadPositions.clear(); - else if (contains(mTerrainPreloadPositions, positions)) + mLoadedTerrainPositions.clear(); + } + else if (contains(mTerrainPreloadPositions, positions, 128.f)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; else { if (mTerrainViews.size() > positions.size()) - { - for (unsigned int i=positions.size(); ipush(mTerrainViews[i]); mTerrainViews.resize(positions.size()); - } else if (mTerrainViews.size() < positions.size()) { - for (unsigned int i=mTerrainViews.size(); icreateView()); } @@ -504,4 +433,43 @@ namespace MWWorld } } + bool CellPreloader::isTerrainLoaded(const CellPreloader::PositionCellGrid& position, double referenceTime) const + { + return mLoadedTerrainTimestamp + mResourceSystem->getSceneManager()->getExpiryDelay() > referenceTime + && contains(mLoadedTerrainPositions, std::array{ position }, ESM::Land::REAL_SIZE); + } + + void CellPreloader::setTerrain(Terrain::World* terrain) + { + if (terrain != mTerrain) + { + clearAllTasks(); + mTerrain = terrain; + } + } + + void CellPreloader::clearAllTasks() + { + if (mTerrainPreloadItem) + { + mTerrainPreloadItem->abort(); + mTerrainPreloadItem->waitTillDone(); + mTerrainPreloadItem = nullptr; + } + + if (mUpdateCacheItem) + { + mUpdateCacheItem->waitTillDone(); + mUpdateCacheItem = nullptr; + } + + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it) + it->second.mWorkItem->abort(); + + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it) + it->second.mWorkItem->waitTillDone(); + + mPreloadCells.clear(); + } + } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index e719f2e60..15146b45b 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -1,11 +1,11 @@ #ifndef OPENMW_MWWORLD_CELLPRELOADER_H #define OPENMW_MWWORLD_CELLPRELOADER_H +#include #include -#include #include #include -#include +#include namespace Resource { @@ -19,16 +19,16 @@ namespace Terrain class View; } -namespace SceneUtil -{ - class UnrefQueue; -} - namespace MWRender { class LandManager; } +namespace Loading +{ + class Listener; +} + namespace MWWorld { class CellStore; @@ -37,12 +37,13 @@ namespace MWWorld class CellPreloader { public: - CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager); + CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, + Terrain::World* terrain, MWRender::LandManager* landManager); ~CellPreloader(); /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// @note The cell itself must be in State_Loaded or State_Preloaded. - void preload(MWWorld::CellStore* cell, double timestamp); + void preload(MWWorld::CellStore& cell, double timestamp); void notifyLoaded(MWWorld::CellStore* cell); @@ -67,28 +68,28 @@ namespace MWWorld void setWorkQueue(osg::ref_ptr workQueue); - void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); - typedef std::pair PositionCellGrid; void setTerrainPreloadPositions(const std::vector& positions); - bool syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp); - void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); + void syncTerrainLoad(Loading::Listener& listener); + void abortTerrainPreloadExcept(const PositionCellGrid* exceptPos); + bool isTerrainLoaded(const CellPreloader::PositionCellGrid& position, double referenceTime) const; + void setTerrain(Terrain::World* terrain); private: + void clearAllTasks(); + Resource::ResourceSystem* mResourceSystem; Resource::BulletShapeManager* mBulletShapeManager; Terrain::World* mTerrain; MWRender::LandManager* mLandManager; osg::ref_ptr mWorkQueue; - osg::ref_ptr mUnrefQueue; double mExpiryDelay; unsigned int mMinCacheSize; unsigned int mMaxCacheSize; bool mPreloadInstances; double mLastResourceCacheUpdate; - int mStoreViewsFailCount; struct PreloadEntry { @@ -110,10 +111,13 @@ namespace MWWorld // Cells that are currently being preloaded, or have already finished preloading PreloadMap mPreloadCells; - std::vector > mTerrainViews; + std::vector> mTerrainViews; std::vector mTerrainPreloadPositions; osg::ref_ptr mTerrainPreloadItem; osg::ref_ptr mUpdateCacheItem; + + std::vector mLoadedTerrainPositions; + double mLoadedTerrainTimestamp; }; } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 21780c172..cf59e9552 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -1,25 +1,74 @@ #include "cellref.hpp" -#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include namespace MWWorld { + CellRef::CellRef(const ESM::CellRef& ref) + : mCellRef(ESM::ReferenceVariant(ref)) + { + } + + CellRef::CellRef(const ESM4::Reference& ref) + : mCellRef(ESM::ReferenceVariant(ref)) + { + } const ESM::RefNum& CellRef::getRefNum() const { - return mCellRef.mRefNum; + return std::visit(ESM::VisitOverload{ + [&](const ESM4::Reference& ref) -> const ESM::RefNum& { return ref.mId; }, + [&](const ESM::CellRef& ref) -> const ESM::RefNum& { return ref.mRefNum; }, + }, + mCellRef.mVariant); } - bool CellRef::hasContentFile() const + const ESM::RefNum& CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum) { - return mCellRef.mRefNum.hasContentFile(); + ESM::RefNum& refNum = std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& ref) -> ESM::RefNum& { return ref.mId; }, + [&](ESM::CellRef& ref) -> ESM::RefNum& { return ref.mRefNum; }, + }, + mCellRef.mVariant); + if (!refNum.isSet()) + { + // Generated RefNums have negative mContentFile + assert(lastAssignedRefNum.mContentFile < 0); + lastAssignedRefNum.mIndex++; + if (lastAssignedRefNum.mIndex == 0) // mIndex overflow, so mContentFile should be changed + { + if (lastAssignedRefNum.mContentFile > std::numeric_limits::min()) + lastAssignedRefNum.mContentFile--; + else + Log(Debug::Error) << "RefNum counter overflow in CellRef::getOrAssignRefNum"; + } + refNum = lastAssignedRefNum; + mChanged = true; + } + return refNum; } - void CellRef::unsetRefNum() + void CellRef::setRefNum(ESM::RefNum refNum) { - mCellRef.mRefNum.unset(); + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& ref) { ref.mId = refNum; }, + [&](ESM::CellRef& ref) { ref.mRefNum = refNum; }, + }, + mCellRef.mVariant); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -75,6 +124,9 @@ namespace MWWorld { return mCellRef.mTeleport; } +======= + static const std::string emptyString = ""; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 /* Start of tes3mp addition @@ -91,9 +143,15 @@ namespace MWWorld ESM::Position CellRef::getDoorDest() const { - return mCellRef.mDoorDest; + + return std::visit(ESM::VisitOverload{ + [&](const ESM4::Reference& ref) { return ref.mDoor.destPos; }, + [&](const ESM::CellRef& ref) -> ESM::Position { return ref.mDoorDest; }, + }, + mCellRef.mVariant); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -108,10 +166,32 @@ namespace MWWorld */ std::string CellRef::getDestCell() const +======= + ESM::RefId CellRef::getDestCell() const +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - return mCellRef.mDestCell; - } + auto esm3Visit = [&](const ESM::CellRef& ref) -> ESM::RefId { + if (!ref.mDestCell.empty()) + { + return ESM::RefId::stringRefId(ref.mDestCell); + } + else + { + const auto cellPos = ESM::positionToExteriorCellLocation(ref.mDoorDest.pos[0], ref.mDoorDest.pos[1]); + return ESM::RefId::esm3ExteriorCell(cellPos.mX, cellPos.mY); + } + }; + auto esm4Visit = [&](const ESM4::Reference& ref) -> ESM::RefId { + if (ref.mDoor.destDoor.isZeroOrUnset()) + return ESM::RefId(); + const ESM4::Reference* refDest + = MWBase::Environment::get().getESMStore()->get().searchStatic(ref.mDoor.destDoor); + if (refDest) + return refDest->mParent; + return ESM::RefId(); + }; +<<<<<<< HEAD /* Start of tes3mp addition @@ -128,242 +208,255 @@ namespace MWWorld float CellRef::getScale() const { return mCellRef.mScale; +======= + return std::visit(ESM::VisitOverload{ esm3Visit, esm4Visit }, mCellRef.mVariant); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } void CellRef::setScale(float scale) { - if (scale != mCellRef.mScale) + if (scale != getScale()) { mChanged = true; - mCellRef.mScale = scale; + std::visit([scale](auto&& ref) { ref.mScale = scale; }, mCellRef.mVariant); } } - ESM::Position CellRef::getPosition() const - { - return mCellRef.mPos; - } - - void CellRef::setPosition(const ESM::Position &position) + void CellRef::setPosition(const ESM::Position& position) { mChanged = true; - mCellRef.mPos = position; + std::visit([&position](auto&& ref) { ref.mPos = position; }, mCellRef.mVariant); } float CellRef::getEnchantmentCharge() const { - return mCellRef.mEnchantmentCharge; + return std::visit(ESM::VisitOverload{ + [&](const ESM4::Reference& /*ref*/) { return 0.f; }, + [&](const ESM::CellRef& ref) { return ref.mEnchantmentCharge; }, + }, + mCellRef.mVariant); } - float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const + float CellRef::getNormalizedEnchantmentCharge(const ESM::Enchantment& enchantment) const { + const int maxCharge = MWMechanics::getEnchantmentCharge(enchantment); if (maxCharge == 0) { return 0; } - else if (mCellRef.mEnchantmentCharge == -1) + else if (getEnchantmentCharge() == -1) { return 1; } else { - return mCellRef.mEnchantmentCharge / static_cast(maxCharge); + return getEnchantmentCharge() / static_cast(maxCharge); } } void CellRef::setEnchantmentCharge(float charge) { - if (charge != mCellRef.mEnchantmentCharge) + if (charge != getEnchantmentCharge()) { mChanged = true; - mCellRef.mEnchantmentCharge = charge; - } - } - int CellRef::getCharge() const - { - return mCellRef.mChargeInt; + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mEnchantmentCharge = charge; }, + }, + mCellRef.mVariant); + } } void CellRef::setCharge(int charge) { - if (charge != mCellRef.mChargeInt) - { - mChanged = true; - mCellRef.mChargeInt = charge; - } + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mChargeInt = charge; }, + }, + mCellRef.mVariant); } void CellRef::applyChargeRemainderToBeSubtracted(float chargeRemainder) { - mCellRef.mChargeIntRemainder += std::abs(chargeRemainder); - if (mCellRef.mChargeIntRemainder > 1.0f) - { - float newChargeRemainder = (mCellRef.mChargeIntRemainder - std::floor(mCellRef.mChargeIntRemainder)); - if (mCellRef.mChargeInt <= static_cast(mCellRef.mChargeIntRemainder)) + auto esm3Visit = [&](ESM::CellRef& cellRef3) { + cellRef3.mChargeIntRemainder += std::abs(chargeRemainder); + if (cellRef3.mChargeIntRemainder > 1.0f) { - mCellRef.mChargeInt = 0; + float newChargeRemainder = (cellRef3.mChargeIntRemainder - std::floor(cellRef3.mChargeIntRemainder)); + if (cellRef3.mChargeInt <= static_cast(cellRef3.mChargeIntRemainder)) + { + cellRef3.mChargeInt = 0; + } + else + { + cellRef3.mChargeInt -= static_cast(cellRef3.mChargeIntRemainder); + } + cellRef3.mChargeIntRemainder = newChargeRemainder; } - else - { - mCellRef.mChargeInt -= static_cast(mCellRef.mChargeIntRemainder); - } - mCellRef.mChargeIntRemainder = newChargeRemainder; - } - } - - float CellRef::getChargeFloat() const - { - return mCellRef.mChargeFloat; + }; + std::visit( + ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + esm3Visit, + }, + mCellRef.mVariant); } void CellRef::setChargeFloat(float charge) { - if (charge != mCellRef.mChargeFloat) - { - mChanged = true; - mCellRef.mChargeFloat = charge; - } + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mChargeFloat = charge; }, + }, + mCellRef.mVariant); } - std::string CellRef::getOwner() const + const std::string& CellRef::getGlobalVariable() const { - return mCellRef.mOwner; - } - - std::string CellRef::getGlobalVariable() const - { - return mCellRef.mGlobalVariable; + return std::visit(ESM::VisitOverload{ + [&](const ESM4::Reference& /*ref*/) -> const std::string& { return emptyString; }, + [&](const ESM::CellRef& ref) -> const std::string& { return ref.mGlobalVariable; }, + }, + mCellRef.mVariant); } void CellRef::resetGlobalVariable() { - if (!mCellRef.mGlobalVariable.empty()) + if (!getGlobalVariable().empty()) { mChanged = true; - mCellRef.mGlobalVariable.erase(); + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mGlobalVariable.erase(); }, + }, + mCellRef.mVariant); } } void CellRef::setFactionRank(int factionRank) { - if (factionRank != mCellRef.mFactionRank) + if (factionRank != getFactionRank()) { mChanged = true; - mCellRef.mFactionRank = factionRank; + std::visit([&](auto&& ref) { ref.mFactionRank = factionRank; }, mCellRef.mVariant); } } - int CellRef::getFactionRank() const + void CellRef::setOwner(const ESM::RefId& owner) { - return mCellRef.mFactionRank; - } - - void CellRef::setOwner(const std::string &owner) - { - if (owner != mCellRef.mOwner) + if (owner != getOwner()) { - mChanged = true; - mCellRef.mOwner = owner; + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mOwner = owner; }, + }, + mCellRef.mVariant); } } - std::string CellRef::getSoul() const + void CellRef::setSoul(const ESM::RefId& soul) { - return mCellRef.mSoul; - } - - void CellRef::setSoul(const std::string &soul) - { - if (soul != mCellRef.mSoul) + if (soul != getSoul()) { mChanged = true; - mCellRef.mSoul = soul; + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mSoul = soul; }, + }, + mCellRef.mVariant); } } - std::string CellRef::getFaction() const + void CellRef::setFaction(const ESM::RefId& faction) { - return mCellRef.mFaction; - } - - void CellRef::setFaction(const std::string &faction) - { - if (faction != mCellRef.mFaction) + if (faction != getFaction()) { mChanged = true; - mCellRef.mFaction = faction; + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mFaction = faction; }, + }, + mCellRef.mVariant); } } - int CellRef::getLockLevel() const - { - return mCellRef.mLockLevel; - } - void CellRef::setLockLevel(int lockLevel) { - if (lockLevel != mCellRef.mLockLevel) + if (lockLevel != getLockLevel()) { mChanged = true; - mCellRef.mLockLevel = lockLevel; + std::visit([&](auto&& ref) { ref.mLockLevel = lockLevel; }, mCellRef.mVariant); } } void CellRef::lock(int lockLevel) { - if(lockLevel != 0) - setLockLevel(abs(lockLevel)); //Changes lock to locklevel, if positive - else - setLockLevel(ESM::UnbreakableLock); // If zero, set to max lock level + setLockLevel(lockLevel); + setLocked(true); } void CellRef::unlock() { - setLockLevel(-abs(mCellRef.mLockLevel)); //Makes lockLevel negative + setLockLevel(-getLockLevel()); + setLocked(false); } - std::string CellRef::getKey() const + bool CellRef::isLocked() const { - return mCellRef.mKey; + return std::visit([](auto&& ref) { return ref.mIsLocked; }, mCellRef.mVariant); } - std::string CellRef::getTrap() const + void CellRef::setLocked(bool locked) { - return mCellRef.mTrap; + std::visit([=](auto&& ref) { ref.mIsLocked = locked; }, mCellRef.mVariant); } - void CellRef::setTrap(const std::string& trap) + void CellRef::setTrap(const ESM::RefId& trap) { - if (trap != mCellRef.mTrap) + if (trap != getTrap()) { mChanged = true; - mCellRef.mTrap = trap; + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mTrap = trap; }, + }, + mCellRef.mVariant); } } - int CellRef::getGoldValue() const + void CellRef::setKey(const ESM::RefId& key) { - return mCellRef.mGoldValue; + if (key != getKey()) + { + mChanged = true; + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mKey = key; }, + }, + mCellRef.mVariant); + } } void CellRef::setGoldValue(int value) { - if (value != mCellRef.mGoldValue) + if (value != getGoldValue()) { mChanged = true; - mCellRef.mGoldValue = value; + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM::CellRef& ref) { ref.mGoldValue = value; }, + }, + mCellRef.mVariant); } } - void CellRef::writeState(ESM::ObjectState &state) const + void CellRef::writeState(ESM::ObjectState& state) const { - state.mRef = mCellRef; + std::visit(ESM::VisitOverload{ + [&](const ESM4::Reference& /*ref*/) {}, + [&](const ESM::CellRef& ref) { state.mRef = ref; }, + }, + mCellRef.mVariant); } - - bool CellRef::hasChanged() const - { - return mChanged; - } - } diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index d8d2f00c2..45cb88d73 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -1,10 +1,15 @@ #ifndef OPENMW_MWWORLD_CELLREF_H #define OPENMW_MWWORLD_CELLREF_H -#include +#include + +#include +#include +#include namespace ESM { + struct Enchantment; struct ObjectState; } @@ -14,19 +19,23 @@ namespace MWWorld /// \brief Encapsulated variant of ESM::CellRef with change tracking class CellRef { + protected: public: + explicit CellRef(const ESM::CellRef& ref); - CellRef (const ESM::CellRef& ref) - : mCellRef(ref) - { - mChanged = false; - } + explicit CellRef(const ESM4::Reference& ref); // Note: Currently unused for items in containers const ESM::RefNum& getRefNum() const; + // Returns RefNum. + // If RefNum is not set, assigns a generated one and changes the "lastAssignedRefNum" counter. + const ESM::RefNum& getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum); + + void setRefNum(ESM::RefNum refNum); + // Set RefNum to its default state. - void unsetRefNum(); + void unsetRefNum() { setRefNum({}); } /* Start of tes3mp addition @@ -61,17 +70,30 @@ namespace MWWorld */ /// Does the RefNum have a content file? - bool hasContentFile() const; + bool hasContentFile() const { return getRefNum().hasContentFile(); } // Id of object being referenced - std::string getRefId() const; - - // Pointer to ID of the object being referenced - const std::string* getRefIdPtr() const; + ESM::RefId getRefId() const + { + struct Visitor + { + ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mRefID; } + ESM::RefId operator()(const ESM4::Reference& ref) { return ref.mBaseObj; } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } // For doors - true if this door teleports to somewhere else, false // if it should open through animation. - bool getTeleport() const; + bool getTeleport() const + { + struct Visitor + { + bool operator()(const ESM::CellRef& ref) { return ref.mTeleport; } + bool operator()(const ESM4::Reference& ref) { return !ref.mDoor.destDoor.isZeroOrUnset(); } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } /* Start of tes3mp addition @@ -97,7 +119,7 @@ namespace MWWorld */ // Destination cell for doors (optional) - std::string getDestCell() const; + ESM::RefId getDestCell() const; /* Start of tes3mp addition @@ -110,80 +132,154 @@ namespace MWWorld */ // Scale applied to mesh - float getScale() const; + float getScale() const + { + return std::visit([&](auto&& ref) { return ref.mScale; }, mCellRef.mVariant); + } void setScale(float scale); // The *original* position and rotation as it was given in the Construction Set. // Current position and rotation of the object is stored in RefData. - ESM::Position getPosition() const; - void setPosition (const ESM::Position& position); + const ESM::Position& getPosition() const + { + return std::visit([](auto&& ref) -> const ESM::Position& { return ref.mPos; }, mCellRef.mVariant); + } + void setPosition(const ESM::Position& position); // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float getEnchantmentCharge() const; // Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment). - float getNormalizedEnchantmentCharge(int maxCharge) const; + float getNormalizedEnchantmentCharge(const ESM::Enchantment& enchantment) const; void setEnchantmentCharge(float charge); // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. // If this returns int(-1) it means full health. - int getCharge() const; - float getChargeFloat() const; // Implemented as union with int charge + int getCharge() const + { + struct Visitor + { + int operator()(const ESM::CellRef& ref) { return ref.mChargeInt; } + int operator()(const ESM4::Reference& /*ref*/) { return 0; } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } + float getChargeFloat() const + { + struct Visitor + { + float operator()(const ESM::CellRef& ref) { return ref.mChargeFloat; } + float operator()(const ESM4::Reference& /*ref*/) { return 0; } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } // Implemented as union with int charge void setCharge(int charge); void setChargeFloat(float charge); void applyChargeRemainderToBeSubtracted(float chargeRemainder); // Stores remainders and applies if > 1 // The NPC that owns this object (and will get angry if you steal it) - std::string getOwner() const; - void setOwner(const std::string& owner); + ESM::RefId getOwner() const + { + struct Visitor + { + ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mOwner; } + ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } + void setOwner(const ESM::RefId& owner); // Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed // even if it has an Owner field. // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. - std::string getGlobalVariable() const; + const std::string& getGlobalVariable() const; void resetGlobalVariable(); // ID of creature trapped in this soul gem - std::string getSoul() const; - void setSoul(const std::string& soul); + ESM::RefId getSoul() const + { + struct Visitor + { + ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mSoul; } + ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } + void setSoul(const ESM::RefId& soul); // The faction that owns this object (and will get angry if // you take it and are not a faction member) - std::string getFaction() const; - void setFaction (const std::string& faction); + ESM::RefId getFaction() const + { + struct Visitor + { + ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mFaction; } + ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } + void setFaction(const ESM::RefId& faction); // PC faction rank required to use the item. Sometimes is -1, which means "any rank". void setFactionRank(int factionRank); - int getFactionRank() const; + int getFactionRank() const + { + return std::visit([&](auto&& ref) { return ref.mFactionRank; }, mCellRef.mVariant); + } // Lock level for doors and containers // Positive for a locked door. 0 for a door that was never locked. // For an unlocked door, it is set to -(previous locklevel) - int getLockLevel() const; + int getLockLevel() const + { + return std::visit([](auto&& ref) { return static_cast(ref.mLockLevel); }, mCellRef.mVariant); + } void setLockLevel(int lockLevel); void lock(int lockLevel); void unlock(); - // Key and trap ID names, if any - std::string getKey() const; - std::string getTrap() const; - void setTrap(const std::string& trap); + bool isLocked() const; + void setLocked(bool locked); + // Key and trap ID names, if any + ESM::RefId getKey() const + { + return std::visit([](auto&& ref) -> const ESM::RefId& { return ref.mKey; }, mCellRef.mVariant); + } + void setKey(const ESM::RefId& key); + ESM::RefId getTrap() const + { + struct Visitor + { + ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mTrap; } + ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } + void setTrap(const ESM::RefId& trap); // This is 5 for Gold_005 references, 100 for Gold_100 and so on. - int getGoldValue() const; + int getGoldValue() const + { + struct Visitor + { + int operator()(const ESM::CellRef& ref) { return ref.mGoldValue; } + int operator()(const ESM4::Reference& /*ref*/) { return 0; } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } void setGoldValue(int value); // Write the content of this CellRef into the given ObjectState - void writeState (ESM::ObjectState& state) const; + void writeState(ESM::ObjectState& state) const; // Has this CellRef changed since it was originally loaded? - bool hasChanged() const; + bool hasChanged() const { return mChanged; } private: - bool mChanged; - ESM::CellRef mCellRef; + bool mChanged = false; + ESM::ReferenceVariant mCellRef; }; } diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 30be4a661..51e1d7df0 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -7,9 +7,13 @@ namespace MWWorld { + struct CellRefListBase + { + }; + /// \brief Collection of references of one type template - struct CellRefList + struct CellRefList : public CellRefListBase { typedef LiveCellRef LiveRef; typedef std::list List; @@ -22,16 +26,18 @@ namespace MWWorld /// and the build will fail with an ugly three-way cyclic header dependence /// so we need to pass the instantiation of the method to the linker, when /// all methods are known. - void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); + void load(ESM::CellRef& ref, bool deleted, const MWWorld::ESMStore& esmStore); - LiveRef &insert (const LiveRef &item) + void load(const ESM4::Reference& ref, bool deleted, const MWWorld::ESMStore& esmStore); + + LiveRef& insert(const LiveRef& item) { mList.push_back(item); return mList.back(); } /// Remove all references with the given refNum from this list. - void remove (const ESM::RefNum &refNum) + void remove(const ESM::RefNum& refNum) { for (typename List::iterator it = mList.begin(); it != mList.end();) { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 7b2791773..7a9ae1e23 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1,6 +1,8 @@ #include "cellstore.hpp" +#include "magiceffects.hpp" #include +#include /* Start of tes3mp addition @@ -17,18 +19,48 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -36,27 +68,61 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/recharge.hpp" +#include "../mwmechanics/spellutil.hpp" -#include "ptr.hpp" -#include "esmstore.hpp" #include "class.hpp" #include "containerstore.hpp" +#include "esmstore.hpp" +#include "inventorystore.hpp" +#include "ptr.hpp" +#include "worldmodel.hpp" namespace { - template - MWWorld::Ptr searchInContainerList (MWWorld::CellRefList& containerList, const std::string& id) + + template + struct RecordToState { - for (typename MWWorld::CellRefList::List::iterator iter (containerList.mList.begin()); - iter!=containerList.mList.end(); ++iter) + using StateType = ESM::ObjectState; + }; + + template <> + struct RecordToState + { + using StateType = ESM::NpcState; + }; + template <> + struct RecordToState + { + using StateType = ESM::CreatureState; + }; + template <> + struct RecordToState + { + using StateType = ESM::DoorState; + }; + template <> + struct RecordToState + { + using StateType = ESM::ContainerState; + }; + template <> + struct RecordToState + { + using StateType = ESM::CreatureLevListState; + }; + + template + MWWorld::Ptr searchInContainerList(MWWorld::CellRefList& containerList, const ESM::RefId& id) + { + for (auto iter(containerList.mList.begin()); iter != containerList.mList.end(); ++iter) { - MWWorld::Ptr container (&*iter, nullptr); + MWWorld::Ptr container(&*iter, nullptr); if (container.getRefData().getCustomData() == nullptr) continue; - MWWorld::Ptr ptr = - container.getClass().getContainerStore (container).search (id); + MWWorld::Ptr ptr = container.getClass().getContainerStore(container).search(id); if (!ptr.isEmpty()) return ptr; @@ -65,177 +131,247 @@ namespace return MWWorld::Ptr(); } - template - MWWorld::Ptr searchViaActorId (MWWorld::CellRefList& actorList, int actorId, - MWWorld::CellStore *cell, const std::map& toIgnore) + template + MWWorld::Ptr searchViaActorId(MWWorld::CellRefList& actorList, int actorId, MWWorld::CellStore* cell, + const std::map& toIgnore) { - for (typename MWWorld::CellRefList::List::iterator iter (actorList.mList.begin()); - iter!=actorList.mList.end(); ++iter) + for (typename MWWorld::CellRefList::List::iterator iter(actorList.mList.begin()); + iter != actorList.mList.end(); ++iter) { - MWWorld::Ptr actor (&*iter, cell); + MWWorld::Ptr actor(&*iter, cell); if (toIgnore.find(&*iter) != toIgnore.end()) continue; - if (actor.getClass().getCreatureStats (actor).matchesActorId (actorId) && actor.getRefData().getCount() > 0) + if (actor.getClass().getCreatureStats(actor).matchesActorId(actorId) && actor.getRefData().getCount() > 0) return actor; } return MWWorld::Ptr(); } - template - void writeReferenceCollection (ESM::ESMWriter& writer, - const MWWorld::CellRefList& collection) + template + void writeReferenceCollection(ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) { if (!collection.mList.empty()) { // references - for (typename MWWorld::CellRefList::List::const_iterator - iter (collection.mList.begin()); - iter!=collection.mList.end(); ++iter) + for (typename MWWorld::CellRefList::List::const_iterator iter(collection.mList.begin()); + iter != collection.mList.end(); ++iter) { if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile()) { // Reference that came from a content file and has not been changed -> ignore continue; } - if (iter->mData.getCount()==0 && !iter->mRef.hasContentFile()) + if (iter->mData.getCount() == 0 && !iter->mRef.hasContentFile()) { // Deleted reference that did not come from a content file -> ignore continue; } - - RecordType state; - iter->save (state); + using StateType = typename RecordToState::StateType; + StateType state; + iter->save(state); // recordId currently unused - writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId); + writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId); - state.save (writer); + state.save(writer); } } } - template + template void fixRestockingImpl(const T* base, RecordType& state) { // Workaround for old saves not containing negative quantities - for(const auto& baseItem : base->mInventory.mList) + for (const auto& baseItem : base->mInventory.mList) { - if(baseItem.mCount < 0) + if (baseItem.mCount < 0) { - for(auto& item : state.mInventory.mItems) + for (auto& item : state.mInventory.mItems) { - if(item.mCount > 0 && Misc::StringUtils::ciEqual(baseItem.mItem, item.mRef.mRefID)) + if (item.mCount > 0 && baseItem.mItem == item.mRef.mRefID) item.mCount = -item.mCount; } } } } - template + template void fixRestocking(const T* base, RecordType& state) - {} + { + } - template<> + template <> void fixRestocking<>(const ESM::Creature* base, ESM::CreatureState& state) { fixRestockingImpl(base, state); } - template<> + template <> void fixRestocking<>(const ESM::NPC* base, ESM::NpcState& state) { fixRestockingImpl(base, state); } - template<> + template <> void fixRestocking<>(const ESM::Container* base, ESM::ContainerState& state) { fixRestockingImpl(base, state); } - template - void readReferenceCollection (ESM::ESMReader& reader, - MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap, MWWorld::CellStore* cellstore) + template + void readReferenceCollection(ESM::ESMReader& reader, MWWorld::CellRefList& collection, const ESM::CellRef& cref, + const std::map& contentFileMap, const MWWorld::ESMStore& esmStore, MWWorld::CellStore* cellstore) { - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - - RecordType state; + using StateType = typename RecordToState::StateType; + StateType state; state.mRef = cref; state.load(reader); // If the reference came from a content file, make sure this content file is loaded if (state.mRef.mRefNum.hasContentFile()) { - std::map::const_iterator iter = - contentFileMap.find (state.mRef.mRefNum.mContentFile); + std::map::const_iterator iter = contentFileMap.find(state.mRef.mRefNum.mContentFile); - if (iter==contentFileMap.end()) + if (iter == contentFileMap.end()) return; // content file has been removed -> skip state.mRef.mRefNum.mContentFile = iter->second; } - if (!MWWorld::LiveCellRef::checkState (state)) + if (!MWWorld::LiveCellRef::checkState(state)) return; // not valid anymore with current content files -> skip - const T *record = esmStore.get().search (state.mRef.mRefID); + const T* record = esmStore.get().search(state.mRef.mRefID); if (!record) return; if (state.mVersion < 15) fixRestocking(record, state); + if (state.mVersion < 17) + { + if constexpr (std::is_same_v) + MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory); + else if constexpr (std::is_same_v) + MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); + } + else if (state.mVersion < 20) + { + if constexpr (std::is_same_v || std::is_same_v) + MWWorld::convertStats(state.mCreatureStats); + } if (state.mRef.mRefNum.hasContentFile()) { - for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); - iter!=collection.mList.end(); ++iter) - if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID) + for (typename MWWorld::CellRefList::List::iterator iter(collection.mList.begin()); + iter != collection.mList.end(); ++iter) + if (iter->mRef.getRefNum() == state.mRef.mRefNum && iter->mRef.getRefId() == state.mRef.mRefID) { // overwrite existing reference float oldscale = iter->mRef.getScale(); - iter->load (state); - const ESM::Position & oldpos = iter->mRef.getPosition(); - const ESM::Position & newpos = iter->mData.getPosition(); + iter->load(state); + const ESM::Position& oldpos = iter->mRef.getPosition(); + const ESM::Position& newpos = iter->mData.getPosition(); const MWWorld::Ptr ptr(&*iter, cellstore); - if ((oldscale != iter->mRef.getScale() || oldpos.asVec3() != newpos.asVec3() || oldpos.rot[0] != newpos.rot[0] || oldpos.rot[1] != newpos.rot[1] || oldpos.rot[2] != newpos.rot[2]) && !ptr.getClass().isActor()) - MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.pos[0], newpos.pos[1], newpos.pos[2]); + if ((oldscale != iter->mRef.getScale() || oldpos.asVec3() != newpos.asVec3() + || oldpos.rot[0] != newpos.rot[0] || oldpos.rot[1] != newpos.rot[1] + || oldpos.rot[2] != newpos.rot[2]) + && !ptr.getClass().isActor()) + MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.asVec3()); if (!iter->mData.isEnabled()) { iter->mData.enable(); - MWBase::Environment::get().getWorld()->disable(MWWorld::Ptr(&*iter, cellstore)); + MWBase::Environment::get().getWorld()->disable(ptr); } + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); return; } - Log(Debug::Warning) << "Warning: Dropping reference to " << state.mRef.mRefID << " (invalid content file link)"; + Log(Debug::Warning) << "Warning: Dropping reference to " << state.mRef.mRefID + << " (invalid content file link)"; return; } // new reference - MWWorld::LiveCellRef ref (record); - ref.load (state); - collection.mList.push_back (ref); + MWWorld::LiveCellRef ref(record); + ref.load(state); + collection.mList.push_back(ref); + + MWWorld::LiveCellRefBase* base = &collection.mList.back(); + MWBase::Environment::get().getWorldModel()->registerPtr(MWWorld::Ptr(base, cellstore)); + } + + // this function allows us to link a CellRefList to the associated recNameInt, and apply a function + template + static void recNameSwitcher(MWWorld::CellRefList& store, ESM::RecNameInts recnNameInt, Callable&& f) + { + if (RecordType::sRecordId == recnNameInt) + { + f(store); + } + } + + // helper function for forEachInternal + template + bool forEachImp(Visitor& visitor, List& list, MWWorld::CellStore* cellStore) + { + for (typename List::List::iterator iter(list.mList.begin()); iter != list.mList.end(); ++iter) + { + if (!MWWorld::CellStore::isAccessible(iter->mData, iter->mRef)) + continue; + if (!visitor(MWWorld::Ptr(&*iter, cellStore))) + return false; + } + return true; } } namespace MWWorld { + struct CellStoreImp + { + CellStoreTuple mRefLists; + + template + static void assignStoreToIndex(CellStore& stores, CellRefList& refList) + { + const std::size_t storeIndex = CellStore::getTypeIndex(); + if (stores.mCellRefLists.size() <= storeIndex) + stores.mCellRefLists.resize(storeIndex + 1); + + assert(&refList == &std::get>(stores.mCellStoreImp->mRefLists)); + + stores.mCellRefLists[storeIndex] = &refList; + } + + // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved + // objects are accounted for. + template + static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore) + { + bool returnValue = true; + + Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&visitor, &returnValue, &cellStore](auto& store) { + returnValue = returnValue && forEachImp(visitor, store, &cellStore); + }); + + return returnValue; + } + }; template - void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) + void CellRefList::load(ESM::CellRef& ref, bool deleted, const MWWorld::ESMStore& esmStore) { - const MWWorld::Store &store = esmStore.get(); + const MWWorld::Store& store = esmStore.get(); - if (const X *ptr = store.search (ref.mRefID)) + if (const X* ptr = store.search(ref.mRefID)) { - typename std::list::iterator iter = - std::find(mList.begin(), mList.end(), ref.mRefNum); + typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); - LiveRef liveCellRef (ref, ptr); + LiveRef liveCellRef(ref, ptr); if (deleted) liveCellRef.mData.setDeletedByContentFile(true); @@ -243,22 +379,44 @@ namespace MWWorld if (iter != mList.end()) *iter = liveCellRef; else - mList.push_back (liveCellRef); + mList.push_back(liveCellRef); } else { - Log(Debug::Warning) - << "Warning: could not resolve cell reference '" << ref.mRefID << "'" - << " (dropping reference)"; + Log(Debug::Warning) << "Warning: could not resolve cell reference " << ref.mRefID + << " (dropping reference)"; } } - template bool operator==(const LiveCellRef& ref, int pRefnum) + template + void CellRefList::load(const ESM4::Reference& ref, bool deleted, const MWWorld::ESMStore& esmStore) + { + + if constexpr (!ESM::isESM4Rec(X::sRecordId)) + return; + + const MWWorld::Store& store = esmStore.get(); + + if (const X* ptr = store.search(ref.mBaseObj)) + { + LiveRef liveCellRef(ref, ptr); + if (deleted) + liveCellRef.mData.setDeletedByContentFile(true); + mList.push_back(liveCellRef); + } + else + { + Log(Debug::Warning) << "Warning: could not resolve cell reference " << ref.mId << " (dropping reference)"; + } + } + + template + bool operator==(const LiveCellRef& ref, int pRefnum) { return (ref.mRef.mRefnum == pRefnum); } - Ptr CellStore::getCurrentPtr(LiveCellRefBase *ref) + Ptr CellStore::getCurrentPtr(LiveCellRefBase* ref) { MovedRefTracker::iterator found = mMovedToAnotherCell.find(ref); if (found != mMovedToAnotherCell.end()) @@ -266,7 +424,7 @@ namespace MWWorld return Ptr(ref, this); } - void CellStore::moveFrom(const Ptr &object, CellStore *from) + void CellStore::moveFrom(const Ptr& object, CellStore* from) { if (mState != State_Loaded) load(); @@ -276,6 +434,7 @@ namespace MWWorld if (found != mMovedToAnotherCell.end()) { // A cell we had previously moved an object to is returning it to us. +<<<<<<< HEAD /* Start of tes3mp addition @@ -296,24 +455,29 @@ namespace MWWorld */ assert (found->second == from); +======= + assert(found->second == from); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mMovedToAnotherCell.erase(found); } else { mMovedHere.insert(std::make_pair(object.getBase(), from)); } - updateMergedRefs(); + requestMergedRefsUpdate(); } - MWWorld::Ptr CellStore::moveTo(const Ptr &object, CellStore *cellToMoveTo) + MWWorld::Ptr CellStore::moveTo(const Ptr& object, CellStore* cellToMoveTo) { if (cellToMoveTo == this) throw std::runtime_error("moveTo: object is already in this cell"); // We assume that *this is in State_Loaded since we could hardly have reference to a live object otherwise. if (mState != State_Loaded) - throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); + throw std::runtime_error( + "moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); +<<<<<<< HEAD // Ensure that the object actually exists in the cell if (searchViaRefNum(object.getCellRef().getRefNum()).isEmpty()) throw std::runtime_error("moveTo: object is not in this cell"); @@ -339,6 +503,9 @@ namespace MWWorld /* End of tes3mp change (major) */ +======= + MWBase::Environment::get().getWorldModel()->registerPtr(MWWorld::Ptr(object.getBase(), cellToMoveTo)); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 MovedRefTracker::iterator found = mMovedHere.find(object.getBase()); if (found != mMovedHere.end()) @@ -346,7 +513,7 @@ namespace MWWorld // Special case - object didn't originate in this cell // Move it back to its original cell first CellStore* originalCell = found->second; - assert (originalCell != this); + assert(originalCell != this); originalCell->moveFrom(object, this); mMovedHere.erase(found); @@ -371,15 +538,19 @@ namespace MWWorld originalCell->moveTo(object, cellToMoveTo); } - updateMergedRefs(); + requestMergedRefsUpdate(); return MWWorld::Ptr(object.getBase(), cellToMoveTo); } cellToMoveTo->moveFrom(object, this); mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo)); - updateMergedRefs(); - return MWWorld::Ptr(object.getBase(), cellToMoveTo); + requestMergedRefsUpdate(); + MWWorld::Ptr ptr(object.getBase(), cellToMoveTo); + const Class& cls = ptr.getClass(); + if (cls.hasInventoryStore(ptr)) + cls.getInventoryStore(ptr).setActor(ptr); + return ptr; } /* @@ -415,15 +586,16 @@ namespace MWWorld struct MergeVisitor { - MergeVisitor(std::vector& mergeTo, const std::map& movedHere, - const std::map& movedToAnotherCell) + MergeVisitor(std::vector& mergeTo, + const std::map& movedHere, + const std::map& movedToAnotherCell) : mMergeTo(mergeTo) , mMovedHere(movedHere) , mMovedToAnotherCell(movedToAnotherCell) { } - bool operator() (const MWWorld::Ptr& ptr) + bool operator()(const MWWorld::Ptr& ptr) { if (mMovedToAnotherCell.find(ptr.getBase()) != mMovedToAnotherCell.end()) return true; @@ -433,7 +605,7 @@ namespace MWWorld void merge() { - for (const auto & [base, _] : mMovedHere) + for (const auto& [base, _] : mMovedHere) mMergeTo.push_back(base); } @@ -444,13 +616,19 @@ namespace MWWorld const std::map& mMovedToAnotherCell; }; - void CellStore::updateMergedRefs() + void CellStore::requestMergedRefsUpdate() + { + mRechargingItemsUpToDate = false; + mMergedRefsNeedsUpdate = true; + } + + void CellStore::updateMergedRefs() const { mMergedRefs.clear(); - mRechargingItemsUpToDate = false; MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell); - forEachInternal(visitor); + CellStoreImp::forEachInternal(visitor, const_cast(*this)); visitor.merge(); +<<<<<<< HEAD /* Start of tes3mp addition @@ -468,6 +646,9 @@ namespace MWWorld /* End of tes3mp addition */ +======= + mMergedRefsNeedsUpdate = false; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } bool CellStore::movedHere(const MWWorld::Ptr& ptr) const @@ -481,15 +662,26 @@ namespace MWWorld return false; } - CellStore::CellStore (const ESM::Cell *cell, const MWWorld::ESMStore& esmStore, std::vector& readerList) - : mStore(esmStore), mReader(readerList), mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0), mRechargingItemsUpToDate(false) + CellStore::CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers) + : mStore(esmStore) + , mReaders(readers) + , mCellVariant(std::move(cell)) + , mState(State_Unloaded) + , mHasState(false) + , mLastRespawn(0, 0) + , mCellStoreImp(std::make_unique()) + , mRechargingItemsUpToDate(false) { - mWaterLevel = cell->mWater; + + std::apply([this](auto&... x) { (CellStoreImp::assignStoreToIndex(*this, x), ...); }, mCellStoreImp->mRefLists); + mWaterLevel = mCellVariant.getWaterHeight(); } - const ESM::Cell *CellStore::getCell() const + CellStore::~CellStore() = default; + + const MWWorld::Cell* CellStore::getCell() const { - return mCell; + return &mCellVariant; } CellStore::State CellStore::getState() const @@ -497,7 +689,7 @@ namespace MWWorld return mState; } - const std::vector &CellStore::getPreloadedIds() const + const std::vector& CellStore::getPreloadedIds() const { return mIds; } @@ -507,25 +699,29 @@ namespace MWWorld return mHasState; } - bool CellStore::hasId (const std::string& id) const + bool CellStore::hasId(const ESM::RefId& id) const { - if (mState==State_Unloaded) + if (mState == State_Unloaded) return false; - if (mState==State_Preloaded) - return std::binary_search (mIds.begin(), mIds.end(), id); + if (mState == State_Preloaded) + return std::binary_search(mIds.begin(), mIds.end(), id); - return searchConst (id).isEmpty(); + return searchConst(id).isEmpty(); } template struct SearchVisitor { PtrType mFound; - const std::string *mIdToFind; + const ESM::RefId& mIdToFind; + SearchVisitor(const ESM::RefId& id) + : mIdToFind(id) + { + } bool operator()(const PtrType& ptr) { - if (*ptr.getCellRef().getRefIdPtr() == *mIdToFind) + if (ptr.getCellRef().getRefId() == mIdToFind) { mFound = ptr; return false; @@ -534,36 +730,34 @@ namespace MWWorld } }; - Ptr CellStore::search (const std::string& id) + Ptr CellStore::search(const ESM::RefId& id) { - SearchVisitor searchVisitor; - searchVisitor.mIdToFind = &id; + SearchVisitor searchVisitor(id); forEach(searchVisitor); return searchVisitor.mFound; } - ConstPtr CellStore::searchConst (const std::string& id) const + ConstPtr CellStore::searchConst(const ESM::RefId& id) const { - SearchVisitor searchVisitor; - searchVisitor.mIdToFind = &id; + SearchVisitor searchVisitor(id); forEachConst(searchVisitor); return searchVisitor.mFound; } - Ptr CellStore::searchViaActorId (int id) + Ptr CellStore::searchViaActorId(int id) { - if (Ptr ptr = ::searchViaActorId (mNpcs, id, this, mMovedToAnotherCell)) + if (Ptr ptr = ::searchViaActorId(get(), id, this, mMovedToAnotherCell); !ptr.isEmpty()) return ptr; - if (Ptr ptr = ::searchViaActorId (mCreatures, id, this, mMovedToAnotherCell)) + if (Ptr ptr = ::searchViaActorId(get(), id, this, mMovedToAnotherCell); !ptr.isEmpty()) return ptr; for (const auto& [base, _] : mMovedHere) { - MWWorld::Ptr actor (base, this); + MWWorld::Ptr actor(base, this); if (!actor.getClass().isActor()) continue; - if (actor.getClass().getCreatureStats (actor).matchesActorId (id) && actor.getRefData().getCount() > 0) + if (actor.getClass().getCreatureStats(actor).matchesActorId(id) && actor.getRefData().getCount() > 0) return actor; } @@ -573,8 +767,12 @@ namespace MWWorld class RefNumSearchVisitor { const ESM::RefNum& mRefNum; + public: - RefNumSearchVisitor(const ESM::RefNum& refNum) : mRefNum(refNum) {} + RefNumSearchVisitor(const ESM::RefNum& refNum) + : mRefNum(refNum) + { + } Ptr mFound; @@ -589,6 +787,7 @@ namespace MWWorld } }; +<<<<<<< HEAD Ptr CellStore::searchViaRefNum(const ESM::RefNum& refNum) { RefNumSearchVisitor searchVisitor(refNum); @@ -717,14 +916,16 @@ namespace MWWorld End of tes3mp addition */ +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 float CellStore::getWaterLevel() const { if (isExterior()) - return -1; + return getCell()->getWaterHeight(); return mWaterLevel; } - void CellStore::setWaterLevel (float level) + void CellStore::setWaterLevel(float level) { mWaterLevel = level; mHasState = true; @@ -732,159 +933,196 @@ namespace MWWorld std::size_t CellStore::count() const { + if (mMergedRefsNeedsUpdate) + updateMergedRefs(); return mMergedRefs.size(); } - void CellStore::load () + void CellStore::load() { - if (mState!=State_Loaded) + if (mState != State_Loaded) { - if (mState==State_Preloaded) + if (mState == State_Preloaded) mIds.clear(); - loadRefs (); + loadRefs(); mState = State_Loaded; } } - void CellStore::preload () + void CellStore::preload() { - if (mState==State_Unloaded) + if (mState == State_Unloaded) { - listRefs (); + listRefs(); mState = State_Preloaded; } } - void CellStore::listRefs() + void CellStore::listRefs(const ESM::Cell& cell) { - std::vector& esm = mReader; - - assert (mCell); - - if (mCell->mContextList.empty()) + if (cell.mContextList.empty()) return; // this is a dynamically generated cell -> skipping. // Load references from all plugins that do something with this cell. - for (size_t i = 0; i < mCell->mContextList.size(); i++) + for (size_t i = 0; i < cell.mContextList.size(); i++) { try { // Reopen the ESM reader and seek to the right position. - int index = mCell->mContextList[i].index; - mCell->restore (esm[index], i); + const std::size_t index = static_cast(cell.mContextList[i].index); + const ESM::ReadersCache::BusyItem reader = mReaders.get(index); + cell.restore(*reader, i); ESM::CellRef ref; // Get each reference in turn + ESM::MovedCellRef cMRef; bool deleted = false; - while (mCell->getNextRef (esm[index], ref, deleted)) + bool moved = false; + while (ESM::Cell::getNextRef( + *reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { - if (deleted) + if (deleted || moved) continue; // Don't list reference if it was moved to a different cell. - ESM::MovedCellRefTracker::const_iterator iter = - std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); - if (iter != mCell->mMovedRefs.end()) { + ESM::MovedCellRefTracker::const_iterator iter + = std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum); + if (iter != cell.mMovedRefs.end()) + { continue; } - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); mIds.push_back(std::move(ref.mRefID)); } } catch (std::exception& e) { - Log(Debug::Error) << "An error occurred listing references for cell " << getCell()->getDescription() << ": " << e.what(); + Log(Debug::Error) << "An error occurred listing references for cell " << getCell()->getDescription() + << ": " << e.what(); } } // List moved references, from separately tracked list. - for (const auto& [ref, deleted]: mCell->mLeasedRefs) + for (const auto& [ref, deleted] : cell.mLeasedRefs) { if (!deleted) - mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); + mIds.push_back(ref.mRefID); } - - std::sort (mIds.begin(), mIds.end()); } - void CellStore::loadRefs() + template + static void visitCell4References( + const ESM4::Cell& cell, const ESMStore& esmStore, ESM::ReadersCache& readers, ReferenceInvocable&& invocable) { - std::vector& esm = mReader; + for (const ESM4::Reference* ref : esmStore.get().getByCell(cell.mId)) + invocable(*ref); + } - assert (mCell); + void CellStore::listRefs(const ESM4::Cell& cell) + { + visitCell4References(cell, mStore, mReaders, [&](const ESM4::Reference& ref) { mIds.push_back(ref.mBaseObj); }); + } - if (mCell->mContextList.empty()) + void CellStore::listRefs() + { + ESM::visit([&](auto&& cell) { listRefs(cell); }, mCellVariant); + std::sort(mIds.begin(), mIds.end()); + } + + void CellStore::loadRefs(const ESM::Cell& cell, std::map& refNumToID) + { + if (cell.mContextList.empty()) return; // this is a dynamically generated cell -> skipping. - std::map refNumToID; // used to detect refID modifications - // Load references from all plugins that do something with this cell. - for (size_t i = 0; i < mCell->mContextList.size(); i++) + for (size_t i = 0; i < cell.mContextList.size(); i++) { try { // Reopen the ESM reader and seek to the right position. - int index = mCell->mContextList[i].index; - mCell->restore (esm[index], i); + const std::size_t index = static_cast(cell.mContextList[i].index); + const ESM::ReadersCache::BusyItem reader = mReaders.get(index); + cell.restore(*reader, i); ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; - // Get each reference in turn + ESM::MovedCellRef cMRef; bool deleted = false; - while(mCell->getNextRef(esm[index], ref, deleted)) + bool moved = false; + while (ESM::Cell::getNextRef( + *reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { + if (moved) + continue; + // Don't load reference if it was moved to a different cell. - ESM::MovedCellRefTracker::const_iterator iter = - std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); - if (iter != mCell->mMovedRefs.end()) { + ESM::MovedCellRefTracker::const_iterator iter + = std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum); + if (iter != cell.mMovedRefs.end()) + { continue; } - loadRef (ref, deleted, refNumToID); + loadRef(ref, deleted, refNumToID); } } catch (std::exception& e) { - Log(Debug::Error) << "An error occurred loading references for cell " << getCell()->getDescription() << ": " << e.what(); + Log(Debug::Error) << "An error occurred loading references for cell " << getCell()->getDescription() + << ": " << e.what(); } } - // Load moved references, from separately tracked list. - for (const auto& leasedRef : mCell->mLeasedRefs) + for (const auto& leasedRef : cell.mLeasedRefs) { - ESM::CellRef &ref = const_cast(leasedRef.first); + ESM::CellRef& ref = const_cast(leasedRef.first); bool deleted = leasedRef.second; - loadRef (ref, deleted, refNumToID); + loadRef(ref, deleted, refNumToID); } + } - updateMergedRefs(); + void CellStore::loadRefs(const ESM4::Cell& cell, std::map& refNumToID) + { + visitCell4References(cell, mStore, mReaders, [&](const ESM4::Reference& ref) { loadRef(ref, false); }); + } + + void CellStore::loadRefs() + { + std::map refNumToID; // used to detect refID modifications + + ESM::visit([&](auto&& cell) { loadRefs(cell, refNumToID); }, mCellVariant); + + requestMergedRefsUpdate(); } bool CellStore::isExterior() const { - return mCell->isExterior(); + return mCellVariant.isExterior(); } - Ptr CellStore::searchInContainer (const std::string& id) + bool CellStore::isQuasiExterior() const + { + return mCellVariant.isQuasiExterior(); + } + + Ptr CellStore::searchInContainer(const ESM::RefId& id) { bool oldState = mHasState; mHasState = true; - if (Ptr ptr = searchInContainerList (mContainers, id)) + if (Ptr ptr = searchInContainerList(get(), id); !ptr.isEmpty()) return ptr; - if (Ptr ptr = searchInContainerList (mCreatures, id)) + if (Ptr ptr = searchInContainerList(get(), id); !ptr.isEmpty()) return ptr; - if (Ptr ptr = searchInContainerList (mNpcs, id)) + if (Ptr ptr = searchInContainerList(get(), id); !ptr.isEmpty()) return ptr; mHasState = oldState; @@ -892,300 +1130,164 @@ namespace MWWorld return Ptr(); } - void CellStore::loadRef (ESM::CellRef& ref, bool deleted, std::map& refNumToID) + void CellStore::loadRef(const ESM4::Reference& ref, bool deleted) { - Misc::StringUtils::lowerCaseInPlace (ref.mRefID); - const MWWorld::ESMStore& store = mStore; - std::map::iterator it = refNumToID.find(ref.mRefNum); + ESM::RecNameInts foundType = static_cast(store.find(ref.mBaseObj)); + + Misc::tupleForEach(this->mCellStoreImp->mRefLists, [&ref, &deleted, &store, foundType](auto& x) { + recNameSwitcher( + x, foundType, [&ref, &deleted, &store](auto& storeIn) { storeIn.load(ref, deleted, store); }); + }); + } + + void CellStore::loadRef(ESM::CellRef& ref, bool deleted, std::map& refNumToID) + { + const MWWorld::ESMStore& store = mStore; + + auto it = refNumToID.find(ref.mRefNum); if (it != refNumToID.end()) { if (it->second != ref.mRefID) { // refID was modified, make sure we don't end up with duplicated refs - switch (store.find(it->second)) + ESM::RecNameInts foundType = static_cast(store.find(it->second)); + if (foundType != 0) { - case ESM::REC_ACTI: mActivators.remove(ref.mRefNum); break; - case ESM::REC_ALCH: mPotions.remove(ref.mRefNum); break; - case ESM::REC_APPA: mAppas.remove(ref.mRefNum); break; - case ESM::REC_ARMO: mArmors.remove(ref.mRefNum); break; - case ESM::REC_BOOK: mBooks.remove(ref.mRefNum); break; - case ESM::REC_CLOT: mClothes.remove(ref.mRefNum); break; - case ESM::REC_CONT: mContainers.remove(ref.mRefNum); break; - case ESM::REC_CREA: mCreatures.remove(ref.mRefNum); break; - case ESM::REC_DOOR: mDoors.remove(ref.mRefNum); break; - case ESM::REC_INGR: mIngreds.remove(ref.mRefNum); break; - case ESM::REC_LEVC: mCreatureLists.remove(ref.mRefNum); break; - case ESM::REC_LEVI: mItemLists.remove(ref.mRefNum); break; - case ESM::REC_LIGH: mLights.remove(ref.mRefNum); break; - case ESM::REC_LOCK: mLockpicks.remove(ref.mRefNum); break; - case ESM::REC_MISC: mMiscItems.remove(ref.mRefNum); break; - case ESM::REC_NPC_: mNpcs.remove(ref.mRefNum); break; - case ESM::REC_PROB: mProbes.remove(ref.mRefNum); break; - case ESM::REC_REPA: mRepairs.remove(ref.mRefNum); break; - case ESM::REC_STAT: mStatics.remove(ref.mRefNum); break; - case ESM::REC_WEAP: mWeapons.remove(ref.mRefNum); break; - case ESM::REC_BODY: mBodyParts.remove(ref.mRefNum); break; - default: - break; + Misc::tupleForEach(this->mCellStoreImp->mRefLists, [&ref, foundType](auto& x) { + recNameSwitcher(x, foundType, [&ref](auto& storeIn) { storeIn.remove(ref.mRefNum); }); + }); } } } - switch (store.find (ref.mRefID)) + ESM::RecNameInts foundType = static_cast(store.find(ref.mRefID)); + bool handledType = false; + if (foundType != 0) { - case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; - case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break; - case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; - case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; - case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; - case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break; - case ESM::REC_CONT: mContainers.load(ref, deleted, store); break; - case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break; - case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break; - case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break; - case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break; - case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break; - case ESM::REC_LIGH: mLights.load(ref, deleted, store); break; - case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break; - case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break; - case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; - case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; - case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; - case ESM::REC_STAT: - { - if (ref.mRefNum.fromGroundcoverFile()) return; - mStatics.load(ref, deleted, store); break; - } - case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; - case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; + Misc::tupleForEach( + this->mCellStoreImp->mRefLists, [&ref, &deleted, &store, foundType, &handledType](auto& x) { + recNameSwitcher(x, foundType, [&ref, &deleted, &store, &handledType](auto& storeIn) { + handledType = true; + storeIn.load(ref, deleted, store); + }); + }); + } + else + { + Log(Debug::Error) << "Cell reference " << ref.mRefID << " is not found!"; + return; + } - case 0: Log(Debug::Error) << "Cell reference '" + ref.mRefID + "' not found!"; return; - - default: - Log(Debug::Error) << "Error: Ignoring reference '" << ref.mRefID << "' of unhandled type"; - return; + if (!handledType) + { + Log(Debug::Error) << "Error: Ignoring reference " << ref.mRefID << " of unhandled type"; + return; } refNumToID[ref.mRefNum] = ref.mRefID; } - void CellStore::loadState (const ESM::CellState& state) + void CellStore::loadState(const ESM::CellState& state) { mHasState = true; - if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) + if (!mCellVariant.isExterior() && mCellVariant.hasWater()) mWaterLevel = state.mWaterLevel; mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn); } - void CellStore::saveState (ESM::CellState& state) const + void CellStore::saveState(ESM::CellState& state) const { - state.mId = mCell->getCellId(); + state.mId = mCellVariant.getId(); - if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) + if (!mCellVariant.isExterior() && mCellVariant.hasWater()) state.mWaterLevel = mWaterLevel; - + state.mIsInterior = !mCellVariant.isExterior(); state.mHasFogOfWar = (mFogState.get() ? 1 : 0); state.mLastRespawn = mLastRespawn.toEsm(); } - void CellStore::writeFog(ESM::ESMWriter &writer) const + void CellStore::writeFog(ESM::ESMWriter& writer) const { if (mFogState.get()) { - mFogState->save(writer, mCell->mData.mFlags & ESM::Cell::Interior); + mFogState->save(writer, !mCellVariant.isExterior()); } } - void CellStore::readFog(ESM::ESMReader &reader) + void CellStore::readFog(ESM::ESMReader& reader) { - mFogState.reset(new ESM::FogState()); + mFogState = std::make_unique(); mFogState->load(reader); } - void CellStore::writeReferences (ESM::ESMWriter& writer) const + void CellStore::writeReferences(ESM::ESMWriter& writer) const { - writeReferenceCollection (writer, mActivators); - writeReferenceCollection (writer, mPotions); - writeReferenceCollection (writer, mAppas); - writeReferenceCollection (writer, mArmors); - writeReferenceCollection (writer, mBooks); - writeReferenceCollection (writer, mClothes); - writeReferenceCollection (writer, mContainers); - writeReferenceCollection (writer, mCreatures); - writeReferenceCollection (writer, mDoors); - writeReferenceCollection (writer, mIngreds); - writeReferenceCollection (writer, mCreatureLists); - writeReferenceCollection (writer, mItemLists); - writeReferenceCollection (writer, mLights); - writeReferenceCollection (writer, mLockpicks); - writeReferenceCollection (writer, mMiscItems); - writeReferenceCollection (writer, mNpcs); - writeReferenceCollection (writer, mProbes); - writeReferenceCollection (writer, mRepairs); - writeReferenceCollection (writer, mStatics); - writeReferenceCollection (writer, mWeapons); - writeReferenceCollection (writer, mBodyParts); + Misc::tupleForEach(this->mCellStoreImp->mRefLists, + [&writer](auto& cellRefList) { writeReferenceCollection(writer, cellRefList); }); for (const auto& [base, store] : mMovedToAnotherCell) { ESM::RefNum refNum = base->mRef.getRefNum(); - ESM::CellId movedTo = store->getCell()->getCellId(); + ESM::RefId movedTo = store->getCell()->getId(); - refNum.save(writer, true, "MVRF"); - movedTo.save(writer); + writer.writeFormId(refNum, true, "MVRF"); + writer.writeCellId(movedTo); } } - void CellStore::readReferences (ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback) + void CellStore::readReferences( + ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback) { mHasState = true; - while (reader.isNextSub ("OBJE")) + while (reader.isNextSub("OBJE")) { unsigned int unused; - reader.getHT (unused); + reader.getHT(unused); // load the RefID first so we know what type of object it is ESM::CellRef cref; cref.loadId(reader, true); - int type = MWBase::Environment::get().getWorld()->getStore().find(cref.mRefID); + int type = mStore.find(cref.mRefID); if (type == 0) { - Log(Debug::Warning) << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)"; - reader.skipHSubUntil("OBJE"); + Log(Debug::Warning) << "Dropping reference to " << cref.mRefID << " (object no longer exists)"; + // Skip until the next OBJE or MVRF + while (reader.hasMoreSubs() && !reader.peekNextSub("OBJE") && !reader.peekNextSub("MVRF")) + { + reader.getSubName(); + reader.skipHSub(); + } continue; } - switch (type) + if (type != 0) { - case ESM::REC_ACTI: + bool foundCorrespondingStore = false; + Misc::tupleForEach(this->mCellStoreImp->mRefLists, + [&reader, this, &cref, &contentFileMap, &foundCorrespondingStore, type](auto&& x) { + recNameSwitcher(x, static_cast(type), + [&reader, this, &cref, &contentFileMap, &foundCorrespondingStore](auto& store) { + foundCorrespondingStore = true; + readReferenceCollection(reader, store, cref, contentFileMap, mStore, this); + }); + }); - readReferenceCollection (reader, mActivators, cref, contentFileMap, this); - break; - - case ESM::REC_ALCH: - - readReferenceCollection (reader, mPotions, cref, contentFileMap, this); - break; - - case ESM::REC_APPA: - - readReferenceCollection (reader, mAppas, cref, contentFileMap, this); - break; - - case ESM::REC_ARMO: - - readReferenceCollection (reader, mArmors, cref, contentFileMap, this); - break; - - case ESM::REC_BOOK: - - readReferenceCollection (reader, mBooks, cref, contentFileMap, this); - break; - - case ESM::REC_CLOT: - - readReferenceCollection (reader, mClothes, cref, contentFileMap, this); - break; - - case ESM::REC_CONT: - - readReferenceCollection (reader, mContainers, cref, contentFileMap, this); - break; - - case ESM::REC_CREA: - - readReferenceCollection (reader, mCreatures, cref, contentFileMap, this); - break; - - case ESM::REC_DOOR: - - readReferenceCollection (reader, mDoors, cref, contentFileMap, this); - break; - - case ESM::REC_INGR: - - readReferenceCollection (reader, mIngreds, cref, contentFileMap, this); - break; - - case ESM::REC_LEVC: - - readReferenceCollection (reader, mCreatureLists, cref, contentFileMap, this); - break; - - case ESM::REC_LEVI: - - readReferenceCollection (reader, mItemLists, cref, contentFileMap, this); - break; - - case ESM::REC_LIGH: - - readReferenceCollection (reader, mLights, cref, contentFileMap, this); - break; - - case ESM::REC_LOCK: - - readReferenceCollection (reader, mLockpicks, cref, contentFileMap, this); - break; - - case ESM::REC_MISC: - - readReferenceCollection (reader, mMiscItems, cref, contentFileMap, this); - break; - - case ESM::REC_NPC_: - - readReferenceCollection (reader, mNpcs, cref, contentFileMap, this); - break; - - case ESM::REC_PROB: - - readReferenceCollection (reader, mProbes, cref, contentFileMap, this); - break; - - case ESM::REC_REPA: - - readReferenceCollection (reader, mRepairs, cref, contentFileMap, this); - break; - - case ESM::REC_STAT: - - readReferenceCollection (reader, mStatics, cref, contentFileMap, this); - break; - - case ESM::REC_WEAP: - - readReferenceCollection (reader, mWeapons, cref, contentFileMap, this); - break; - - case ESM::REC_BODY: - - readReferenceCollection (reader, mBodyParts, cref, contentFileMap, this); - break; - - default: - - throw std::runtime_error ("unknown type in cell reference section"); + if (!foundCorrespondingStore) + throw std::runtime_error("unknown type in cell reference section"); } } - // Do another update here to make sure objects referred to by MVRF tags can be found - // This update is only needed for old saves that used the old copy&delete way of moving objects - updateMergedRefs(); - while (reader.isNextSub("MVRF")) { reader.cacheSubName(); - ESM::RefNum refnum; - ESM::CellId movedTo; - refnum.load(reader, true, "MVRF"); - movedTo.load(reader); - + ESM::RefNum refnum = reader.getFormId(true, "MVRF"); + ESM::RefId movedToId = reader.getCellId(); if (refnum.hasContentFile()) { auto iter = contentFileMap.find(refnum.mContentFile); @@ -1194,21 +1296,23 @@ namespace MWWorld } // Search for the reference. It might no longer exist if its content file was removed. - Ptr movedRef = searchViaRefNum(refnum); + Ptr movedRef = MWBase::Environment::get().getWorldModel()->getPtr(refnum); if (movedRef.isEmpty()) { - Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)"; + Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex + << " (moved object no longer exists)"; continue; } - CellStore* otherCell = callback->getCellStore(movedTo); + CellStore* otherCell = callback->getCellStore(movedToId); if (otherCell == nullptr) { Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId() - << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location."; - // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates. - // Restore original coordinates: + << " (target cell " << movedToId + << " no longer exists). Reference moved back to its original location."; + // Note by dropping tag the object will automatically re-appear in its original cell, though + // potentially at inapproriate coordinates. Restore original coordinates: movedRef.getRefData().setPosition(movedRef.getCellRef().getPosition()); continue; } @@ -1222,21 +1326,13 @@ namespace MWWorld moveTo(movedRef, otherCell); } + + requestMergedRefsUpdate(); } - bool operator== (const CellStore& left, const CellStore& right) + void CellStore::setFog(std::unique_ptr&& fog) { - return left.getCell()->getCellId()==right.getCell()->getCellId(); - } - - bool operator!= (const CellStore& left, const CellStore& right) - { - return !(left==right); - } - - void CellStore::setFog(ESM::FogState *fog) - { - mFogState.reset(fog); + mFogState = std::move(fog); } ESM::FogState* CellStore::getFog() const @@ -1244,14 +1340,14 @@ namespace MWWorld return mFogState.get(); } - void clearCorpse(const MWWorld::Ptr& ptr) + static void clearCorpse(const MWWorld::Ptr& ptr, const ESMStore& store) { const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); - static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->mValue.getFloat(); - if (creatureStats.isDead() && - creatureStats.isDeathAnimationFinished() && - !ptr.getClass().isPersistent(ptr) && - creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + static const float fCorpseClearDelay + = store.get().find("fCorpseClearDelay")->mValue.getFloat(); + if (creatureStats.isDead() && creatureStats.isDeathAnimationFinished() && !ptr.getClass().isPersistent(ptr) + && creatureStats.getTimeOfDeath() + fCorpseClearDelay + <= MWBase::Environment::get().getWorld()->getTimeStamp()) { MWBase::Environment::get().getWorld()->deleteObject(ptr); } @@ -1261,7 +1357,8 @@ namespace MWWorld { if (mState == State_Loaded) { - for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) @@ -1269,7 +1366,8 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } - for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) @@ -1287,7 +1385,8 @@ namespace MWWorld if (mState == State_Loaded) { - for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) @@ -1295,7 +1394,8 @@ namespace MWWorld ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } - for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) @@ -1303,11 +1403,12 @@ namespace MWWorld ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } - for (CellRefList::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0 - && ptr.getClass().getContainerStore(ptr).isResolved()) + && ptr.getClass().getContainerStore(ptr).isResolved()) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } @@ -1321,33 +1422,38 @@ namespace MWWorld { if (mState == State_Loaded) { - static const int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->mValue.getInteger(); - if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) + static const int iMonthsToRespawn + = mStore.get().find("iMonthsToRespawn")->mValue.getInteger(); + if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24 * 30 * iMonthsToRespawn) { mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); - for (CellRefList::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); ptr.getClass().respawn(ptr); } } - for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); - clearCorpse(ptr); + clearCorpse(ptr, mStore); ptr.getClass().respawn(ptr); } - for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); - clearCorpse(ptr); + clearCorpse(ptr, mStore); ptr.getClass().respawn(ptr); } - for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) + for (CellRefList::List::iterator it(get().mList.begin()); + it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); - // no need to clearCorpse, handled as part of mCreatures + // no need to clearCorpse, handled as part of get() ptr.getClass().respawn(ptr); } } @@ -1370,9 +1476,8 @@ namespace MWWorld { mRechargingItems.clear(); - const auto update = [this](auto& list) - { - for (auto & item : list) + const auto update = [this](auto& list) { + for (auto& item : list) { Ptr ptr = getCurrentPtr(&item); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) @@ -1382,27 +1487,63 @@ namespace MWWorld } }; - update(mWeapons.mList); - update(mArmors.mList); - update(mClothes.mList); - update(mBooks.mList); + update(get().mList); + update(get().mList); + update(get().mList); + update(get().mList); } - void MWWorld::CellStore::checkItem(Ptr ptr) + void MWWorld::CellStore::checkItem(const Ptr& ptr) { - if (ptr.getClass().getEnchantment(ptr).empty()) + const ESM::RefId& enchantmentId = ptr.getClass().getEnchantment(ptr); + if (enchantmentId.empty()) return; - std::string enchantmentId = ptr.getClass().getEnchantment(ptr); - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentId); + const ESM::Enchantment* enchantment = mStore.get().search(enchantmentId); if (!enchantment) { - Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << ptr.getCellRef().getRefId(); + Log(Debug::Warning) << "Warning: Can't find enchantment " << enchantmentId << " on item " + << ptr.getCellRef().getRefId(); return; } if (enchantment->mData.mType == ESM::Enchantment::WhenUsed - || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - mRechargingItems.emplace_back(ptr.getBase(), static_cast(enchantment->mData.mCharge)); + || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + mRechargingItems.emplace_back( + ptr.getBase(), static_cast(MWMechanics::getEnchantmentCharge(*enchantment))); + } + + Ptr MWWorld::CellStore::getMovedActor(int actorId) const + { + for (const auto& [cellRef, cell] : mMovedToAnotherCell) + { + if (cellRef->mClass->isActor() && cellRef->mData.getCustomData()) + { + Ptr actor(cellRef, cell); + if (actor.getClass().getCreatureStats(actor).getActorId() == actorId) + return actor; + } + } + return {}; + } + + Ptr CellStore::getPtr(ESM::RefId id) + { + if (mState == CellStore::State_Unloaded) + preload(); + + if (mState == CellStore::State_Preloaded) + { + if (!std::binary_search(mIds.begin(), mIds.end(), id)) + return Ptr(); + load(); + } + + Ptr ptr = search(id); + + if (!ptr.isEmpty() && isAccessible(ptr.getRefData(), ptr.getCellRef())) + return ptr; + + return Ptr(); } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 3e901f4b8..78de6841c 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -2,198 +2,187 @@ #define GAME_MWWORLD_CELLSTORE_H #include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include -#include "livecellref.hpp" +#include "cell.hpp" #include "cellreflist.hpp" +#include "livecellref.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -#include "timestamp.hpp" #include "ptr.hpp" +#include "timestamp.hpp" namespace ESM { + class ReadersCache; struct Cell; struct CellState; - struct FogState; - struct CellId; - struct RefNum; + struct FormId; + using RefNum = FormId; + struct Activator; + struct Potion; + struct Apparatus; + struct Armor; + struct Book; + struct Clothing; + struct Container; + struct Creature; + struct Door; + struct Ingredient; + struct CreatureLevList; + struct ItemLevList; + struct Light; + struct Lockpick; + struct Miscellaneous; + struct NPC; + struct Probe; + struct Repair; + struct Static; + struct Weapon; + struct BodyPart; + struct CellCommon; +} + +namespace ESM4 +{ + class Reader; + struct Cell; + struct Reference; + struct Static; + struct Light; + struct Activator; + struct Potion; + struct Ammunition; + struct Armor; + struct Book; + struct Clothing; + struct Container; + struct Door; + struct Furniture; + struct Ingredient; + struct MiscItem; + struct Tree; + struct Weapon; } namespace MWWorld { class ESMStore; + struct CellStoreImp; + + using CellStoreTuple = std::tuple, CellRefList, + CellRefList, CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, CellRefList, + + CellRefList, CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList>; /// \brief Mutable state of a cell class CellStore { - public: + public: + enum State + { + State_Unloaded, + State_Preloaded, + State_Loaded + }; - enum State - { - State_Unloaded, State_Preloaded, State_Loaded - }; + /// Should this reference be accessible to the outside world (i.e. to scripts / game logic)? + /// Determined based on the deletion flags. By default, objects deleted by content files are never accessible; + /// objects deleted by setCount(0) are still accessible *if* they came from a content file (needed for vanilla + /// scripting compatibility, and the fact that objects may be "un-deleted" in the original game). + static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref) + { + return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0); + } - private: + /// Moves object from this cell to the given cell. + /// @note automatically updates given cell by calling cellToMoveTo->moveFrom(...) + /// @note throws exception if cellToMoveTo == this + /// @return updated MWWorld::Ptr with the new CellStore pointer set. + MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); - const MWWorld::ESMStore& mStore; - std::vector& mReader; + void rest(double hours); + void recharge(float duration); - // Even though fog actually belongs to the player and not cells, - // it makes sense to store it here since we need it once for each cell. - // Note this is nullptr until the cell is explored to save some memory - std::shared_ptr mFogState; + /// Make a copy of the given object and insert it into this cell. + /// @note If you get a linker error here, this means the given type can not be inserted into a cell. + /// The supported types are defined at the bottom of this file. + template + LiveCellRefBase* insert(const LiveCellRef* ref) + { + mHasState = true; + CellRefList& list = get(); + LiveCellRefBase* ret = &list.insert(*ref); + requestMergedRefsUpdate(); + return ret; + } - const ESM::Cell *mCell; - State mState; - bool mHasState; - std::vector mIds; - float mWaterLevel; + /// @param readerList The readers to use for loading of the cell on-demand. + CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers); - MWWorld::TimeStamp mLastRespawn; + CellStore(const CellStore&) = delete; - // List of refs owned by this cell - CellRefList mActivators; - CellRefList mPotions; - CellRefList mAppas; - CellRefList mArmors; - CellRefList mBooks; - CellRefList mClothes; - CellRefList mContainers; - CellRefList mCreatures; - CellRefList mDoors; - CellRefList mIngreds; - CellRefList mCreatureLists; - CellRefList mItemLists; - CellRefList mLights; - CellRefList mLockpicks; - CellRefList mMiscItems; - CellRefList mNpcs; - CellRefList mProbes; - CellRefList mRepairs; - CellRefList mStatics; - CellRefList mWeapons; - CellRefList mBodyParts; + CellStore(CellStore&&) = delete; - typedef std::map MovedRefTracker; - // References owned by a different cell that have been moved here. - // - MovedRefTracker mMovedHere; - // References owned by this cell that have been moved to another cell. - // - MovedRefTracker mMovedToAnotherCell; + CellStore& operator=(const CellStore&) = delete; - // Merged list of ref's currently in this cell - i.e. with added refs from mMovedHere, removed refs from mMovedToAnotherCell - std::vector mMergedRefs; + CellStore& operator=(CellStore&&) = delete; - // Get the Ptr for the given ref which originated from this cell (possibly moved to another cell at this point). - Ptr getCurrentPtr(MWWorld::LiveCellRefBase* ref); + ~CellStore(); - /// Moves object from the given cell to this cell. - void moveFrom(const MWWorld::Ptr& object, MWWorld::CellStore* from); + const MWWorld::Cell* getCell() const; - /// Repopulate mMergedRefs. - void updateMergedRefs(); + State getState() const; - // (item, max charge) - typedef std::vector > TRechargingItems; - TRechargingItems mRechargingItems; + const std::vector& getPreloadedIds() const; + ///< Get Ids of objects in this cell, only valid in State_Preloaded - bool mRechargingItemsUpToDate; + bool hasState() const; + ///< Does this cell have state that needs to be stored in a saved game file? - void updateRechargingItems(); - void rechargeItems(float duration); - void checkItem(Ptr ptr); + bool hasId(const ESM::RefId& id) const; + ///< May return true for deleted IDs when in preload state. Will return false, if cell is + /// unloaded. + /// @note Will not account for moved references which may exist in Loaded state. Use search() instead if the + /// cell is loaded. - // helper function for forEachInternal - template - bool forEachImp (Visitor& visitor, List& list) - { - for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); - ++iter) - { - if (!isAccessible(iter->mData, iter->mRef)) - continue; - if (!visitor (MWWorld::Ptr(&*iter, this))) - return false; - } - return true; - } + Ptr search(const ESM::RefId& id); + ///< Will return an empty Ptr if cell is not loaded. Does not check references in + /// containers. + /// @note Triggers CellStore hasState flag. - // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved objects are accounted for. - template - bool forEachInternal (Visitor& visitor) - { - return - forEachImp (visitor, mActivators) && - forEachImp (visitor, mPotions) && - forEachImp (visitor, mAppas) && - forEachImp (visitor, mArmors) && - forEachImp (visitor, mBooks) && - forEachImp (visitor, mClothes) && - forEachImp (visitor, mContainers) && - forEachImp (visitor, mDoors) && - forEachImp (visitor, mIngreds) && - forEachImp (visitor, mItemLists) && - forEachImp (visitor, mLights) && - forEachImp (visitor, mLockpicks) && - forEachImp (visitor, mMiscItems) && - forEachImp (visitor, mProbes) && - forEachImp (visitor, mRepairs) && - forEachImp (visitor, mStatics) && - forEachImp (visitor, mWeapons) && - forEachImp (visitor, mBodyParts) && - forEachImp (visitor, mCreatures) && - forEachImp (visitor, mNpcs) && - forEachImp (visitor, mCreatureLists); - } + ConstPtr searchConst(const ESM::RefId& id) const; + ///< Will return an empty Ptr if cell is not loaded. Does not check references in + /// containers. + /// @note Does not trigger CellStore hasState flag. - /// @note If you get a linker error here, this means the given type can not be stored in a cell. The supported types are - /// defined at the bottom of this file. - template - CellRefList& get(); + Ptr searchViaActorId(int id); + ///< Will return an empty Ptr if cell is not loaded. - public: + float getWaterLevel() const; - /// Should this reference be accessible to the outside world (i.e. to scripts / game logic)? - /// Determined based on the deletion flags. By default, objects deleted by content files are never accessible; - /// objects deleted by setCount(0) are still accessible *if* they came from a content file (needed for vanilla - /// scripting compatibility, and the fact that objects may be "un-deleted" in the original game). - static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref) - { - return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0); - } + bool movedHere(const MWWorld::Ptr& ptr) const; - /// Moves object from this cell to the given cell. - /// @note automatically updates given cell by calling cellToMoveTo->moveFrom(...) - /// @note throws exception if cellToMoveTo == this - /// @return updated MWWorld::Ptr with the new CellStore pointer set. - MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); + void setWaterLevel(float level); +<<<<<<< HEAD /* Start of tes3mp addition @@ -207,17 +196,37 @@ namespace MWWorld void rest(double hours); void recharge(float duration); +======= + void setFog(std::unique_ptr&& fog); + ///< \note Takes ownership of the pointer +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - /// Make a copy of the given object and insert it into this cell. - /// @note If you get a linker error here, this means the given type can not be inserted into a cell. - /// The supported types are defined at the bottom of this file. - template - LiveCellRefBase* insert(const LiveCellRef* ref) - { - mHasState = true; - CellRefList& list = get(); - LiveCellRefBase* ret = &list.insert(*ref); + ESM::FogState* getFog() const; + + std::size_t count() const; + ///< Return total number of references, including deleted ones. + + void load(); + ///< Load references from content file. + + void preload(); + ///< Build ID list from content file. + + /// Call visitor (MWWorld::Ptr) for each reference. visitor must return a bool. Returning + /// false will abort the iteration. + /// \note Prefer using forEachConst when possible. + /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration + /// completed? + template + bool forEach(Visitor&& visitor) + { + if (mState != State_Loaded) + return false; + + if (mMergedRefsNeedsUpdate) updateMergedRefs(); +<<<<<<< HEAD return ret; } @@ -365,272 +374,225 @@ namespace MWWorld if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) return false; } +======= + if (mMergedRefs.empty()) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 return true; + + mHasState = true; + + for (unsigned int i = 0; i < mMergedRefs.size(); ++i) + { + if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) + continue; + + if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) + return false; + } + return true; + } + + /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning + /// false will abort the iteration. + /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration + /// completed? + template + bool forEachConst(Visitor&& visitor) const + { + if (mState != State_Loaded) + return false; + + if (mMergedRefsNeedsUpdate) + updateMergedRefs(); + + for (unsigned int i = 0; i < mMergedRefs.size(); ++i) + { + if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) + continue; + + if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) + return false; + } + return true; + } + + /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning + /// false will abort the iteration. + /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration + /// completed? + template + bool forEachType(Visitor& visitor) + { + if (mState != State_Loaded) + return false; + + if (mMergedRefsNeedsUpdate) + updateMergedRefs(); + if (mMergedRefs.empty()) + return true; + + mHasState = true; + + CellRefList& list = get(); + + for (typename CellRefList::List::iterator it(list.mList.begin()); it != list.mList.end(); ++it) + { + LiveCellRefBase* base = &*it; + if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end()) + continue; + if (!isAccessible(base->mData, base->mRef)) + continue; + if (!visitor(MWWorld::Ptr(base, this))) + return false; } - /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning - /// false will abort the iteration. - /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. - /// \attention This function also lists deleted (count 0) objects! - /// \return Iteration completed? - template - bool forEachConst (Visitor&& visitor) const + for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) { - if (mState != State_Loaded) - return false; - - for (unsigned int i=0; imData, mMergedRefs[i]->mRef)) - continue; - - if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) - return false; - } - return true; - } - - - /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning - /// false will abort the iteration. - /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour. - /// \attention This function also lists deleted (count 0) objects! - /// \return Iteration completed? - template - bool forEachType(Visitor& visitor) - { - if (mState != State_Loaded) - return false; - - if (mMergedRefs.empty()) - return true; - - mHasState = true; - - CellRefList& list = get(); - - for (typename CellRefList::List::iterator it (list.mList.begin()); it!=list.mList.end(); ++it) - { - LiveCellRefBase* base = &*it; - if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end()) - continue; - if (!isAccessible(base->mData, base->mRef)) - continue; + LiveCellRefBase* base = it->first; + if (dynamic_cast*>(base)) if (!visitor(MWWorld::Ptr(base, this))) return false; - } - - for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) - { - LiveCellRefBase* base = it->first; - if (dynamic_cast*>(base)) - if (!visitor(MWWorld::Ptr(base, this))) - return false; - } - return true; } + return true; + } - // NOTE: does not account for moved references - // Should be phased out when we have const version of forEach - inline const CellRefList& getReadOnlyDoors() const - { - return mDoors; - } - inline const CellRefList& getReadOnlyStatics() const - { - return mStatics; - } + // NOTE: does not account for moved references + // Should be phased out when we have const version of forEach + inline const CellRefList& getReadOnlyDoors() const { return get(); } + inline const CellRefList& getReadOnlyEsm4Doors() const { return get(); } + inline const CellRefList& getReadOnlyStatics() const { return get(); } + inline const CellRefList& getReadOnlyEsm4Statics() const { return get(); } - bool isExterior() const; + bool isExterior() const; - Ptr searchInContainer (const std::string& id); + bool isQuasiExterior() const; - void loadState (const ESM::CellState& state); + Ptr searchInContainer(const ESM::RefId& id); - void saveState (ESM::CellState& state) const; + void loadState(const ESM::CellState& state); - void writeFog (ESM::ESMWriter& writer) const; + void saveState(ESM::CellState& state) const; - void readFog (ESM::ESMReader& reader); + void writeFog(ESM::ESMWriter& writer) const; - void writeReferences (ESM::ESMWriter& writer) const; + void readFog(ESM::ESMReader& reader); - struct GetCellStoreCallback - { - public: - ///@note must return nullptr if the cell is not found - virtual CellStore* getCellStore(const ESM::CellId& cellId) = 0; - virtual ~GetCellStoreCallback() = default; - }; + void writeReferences(ESM::ESMWriter& writer) const; - /// @param callback to use for retrieving of additional CellStore objects by ID (required for resolving moved references) - void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback); + struct GetCellStoreCallback + { + ///@note must return nullptr if the cell is not found + virtual CellStore* getCellStore(const ESM::RefId& cellId) = 0; + virtual ~GetCellStoreCallback() = default; + }; - void respawn (); - ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. + /// @param callback to use for retrieving of additional CellStore objects by ID (required for resolving moved + /// references) + void readReferences( + ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback); - private: + void respawn(); + ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. - /// Run through references and store IDs - void listRefs(); + Ptr getMovedActor(int actorId) const; - void loadRefs(); + Ptr getPtr(ESM::RefId id); - void loadRef (ESM::CellRef& ref, bool deleted, std::map& refNumToID); - ///< Make case-adjustments to \a ref and insert it into the respective container. - /// - /// Invalid \a ref objects are silently dropped. + private: + friend struct CellStoreImp; + + const MWWorld::ESMStore& mStore; + ESM::ReadersCache& mReaders; + + // Even though fog actually belongs to the player and not cells, + // it makes sense to store it here since we need it once for each cell. + // Note this is nullptr until the cell is explored to save some memory + std::unique_ptr mFogState; + + MWWorld::Cell mCellVariant; + State mState; + bool mHasState; + std::vector mIds; + float mWaterLevel; + + MWWorld::TimeStamp mLastRespawn; + + template + static constexpr std::size_t getTypeIndex() + { + static_assert(Misc::TupleHasType, CellStoreTuple>::value); + return Misc::TupleTypeIndex, CellStoreTuple>::value; + } + + std::unique_ptr mCellStoreImp; + std::vector mCellRefLists; + + template + CellRefList& get() + { + mHasState = true; + return static_cast&>(*mCellRefLists[getTypeIndex()]); + } + + template + const CellRefList& get() const + { + return static_cast&>(*mCellRefLists[getTypeIndex()]); + } + + typedef std::map MovedRefTracker; + // References owned by a different cell that have been moved here. + // + MovedRefTracker mMovedHere; + // References owned by this cell that have been moved to another cell. + // + MovedRefTracker mMovedToAnotherCell; + + // Merged list of ref's currently in this cell - i.e. with added refs from mMovedHere, removed refs from + // mMovedToAnotherCell + mutable std::vector mMergedRefs; + mutable bool mMergedRefsNeedsUpdate = false; + + // Get the Ptr for the given ref which originated from this cell (possibly moved to another cell at this point). + Ptr getCurrentPtr(MWWorld::LiveCellRefBase* ref); + + /// Moves object from the given cell to this cell. + void moveFrom(const MWWorld::Ptr& object, MWWorld::CellStore* from); + + /// Repopulate mMergedRefs. + void requestMergedRefsUpdate(); + void updateMergedRefs() const; + + // (item, max charge) + typedef std::vector> TRechargingItems; + TRechargingItems mRechargingItems; + + bool mRechargingItemsUpToDate; + + void updateRechargingItems(); + void rechargeItems(float duration); + void checkItem(const Ptr& ptr); + + /// Run through references and store IDs + void listRefs(const ESM::Cell& cell); + void listRefs(const ESM4::Cell& cell); + void listRefs(); + + void loadRefs(const ESM::Cell& cell, std::map& refNumToID); + void loadRefs(const ESM4::Cell& cell, std::map& refNumToID); + + void loadRefs(); + + void loadRef(const ESM4::Reference& ref, bool deleted); + void loadRef(ESM::CellRef& ref, bool deleted, std::map& refNumToID); + ///< Make case-adjustments to \a ref and insert it into the respective container. + /// + /// Invalid \a ref objects are silently dropped. + /// }; - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mActivators; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mPotions; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mAppas; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mArmors; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mBooks; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mClothes; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mContainers; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mCreatures; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mDoors; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mIngreds; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mCreatureLists; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mItemLists; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mLights; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mLockpicks; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mMiscItems; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mNpcs; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mProbes; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mRepairs; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mStatics; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mWeapons; - } - - template<> - inline CellRefList& CellStore::get() - { - mHasState = true; - return mBodyParts; - } - - bool operator== (const CellStore& left, const CellStore& right); - bool operator!= (const CellStore& left, const CellStore& right); } #endif diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index e68b383b7..f4d606d02 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -1,29 +1,29 @@ #ifndef GAME_MWWORLD_CELLVISITORS_H #define GAME_MWWORLD_CELLVISITORS_H -#include #include +#include #include "ptr.hpp" - namespace MWWorld { struct ListAndResetObjectsVisitor { std::vector mObjects; - bool operator() (MWWorld::Ptr ptr) + bool operator()(const MWWorld::Ptr& ptr) { if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); - mObjects.push_back (ptr); } + mObjects.push_back(ptr); return true; } }; + } #endif diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 63eb20a17..9954e31db 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -13,64 +13,73 @@ */ #include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" -#include "ptr.hpp" -#include "refdata.hpp" -#include "nullaction.hpp" -#include "failedaction.hpp" #include "actiontake.hpp" #include "containerstore.hpp" +#include "failedaction.hpp" +#include "inventorystore.hpp" +#include "nullaction.hpp" +#include "ptr.hpp" +#include "worldmodel.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" namespace MWWorld { - std::map > Class::sClasses; - - Class::Class() {} - - Class::~Class() {} - - void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const + std::map& Class::getClasses() { - + static std::map values; + return values; } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const + void Class::insertObjectRendering( + const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { - } - bool Class::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const + void Class::insertObject( + const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const + { + } + + void Class::insertObjectPhysics( + const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const + { + } + + bool Class::consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const { return false; } - void Class::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const + void Class::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const { - throw std::runtime_error ("class does not represent an actor"); + throw std::runtime_error("class does not represent an actor"); } - bool Class::canSell (const MWWorld::ConstPtr& item, int npcServices) const + bool Class::canSell(const MWWorld::ConstPtr& item, int npcServices) const { return false; } - int Class::getServices(const ConstPtr &actor) const + int Class::getServices(const ConstPtr& actor) const { - throw std::runtime_error ("class does not have services"); + throw std::runtime_error("class does not have services"); } - MWMechanics::CreatureStats& Class::getCreatureStats (const Ptr& ptr) const + MWMechanics::CreatureStats& Class::getCreatureStats(const Ptr& ptr) const { +<<<<<<< HEAD /* Start of tes3mp addition @@ -84,19 +93,22 @@ namespace MWWorld */ throw std::runtime_error ("class does not have creature stats"); +======= + throw std::runtime_error("class does not have creature stats"); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - MWMechanics::NpcStats& Class::getNpcStats (const Ptr& ptr) const + MWMechanics::NpcStats& Class::getNpcStats(const Ptr& ptr) const { - throw std::runtime_error ("class does not have NPC stats"); + throw std::runtime_error("class does not have NPC stats"); } - bool Class::hasItemHealth (const ConstPtr& ptr) const + bool Class::hasItemHealth(const ConstPtr& ptr) const { return false; } - int Class::getItemHealth(const ConstPtr &ptr) const + int Class::getItemHealth(const ConstPtr& ptr) const { if (ptr.getCellRef().getCharge() == -1) return getItemMaxHealth(ptr); @@ -104,7 +116,7 @@ namespace MWWorld return ptr.getCellRef().getCharge(); } - float Class::getItemNormalizedHealth (const ConstPtr& ptr) const + float Class::getItemNormalizedHealth(const ConstPtr& ptr) const { if (getItemMaxHealth(ptr) == 0) { @@ -116,46 +128,54 @@ namespace MWWorld } } - int Class::getItemMaxHealth (const ConstPtr& ptr) const + int Class::getItemMaxHealth(const ConstPtr& ptr) const { - throw std::runtime_error ("class does not have item health"); + throw std::runtime_error("class does not have item health"); } - void Class::hit(const Ptr& ptr, float attackStrength, int type) const + bool Class::evaluateHit(const Ptr& ptr, Ptr& victim, osg::Vec3f& hitPosition) const { throw std::runtime_error("class cannot hit"); } - void Class::block(const Ptr &ptr) const + void Class::hit(const Ptr& ptr, float attackStrength, int type, const Ptr& victim, const osg::Vec3f& hitPosition, + bool success) const + { + throw std::runtime_error("class cannot hit"); + } + + void Class::block(const Ptr& ptr) const { throw std::runtime_error("class cannot block"); } - void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const + void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, + const osg::Vec3f& hitPosition, bool successful) const { throw std::runtime_error("class cannot be hit"); } - std::shared_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const + std::unique_ptr Class::activate(const Ptr& ptr, const Ptr& actor) const { - return std::shared_ptr (new NullAction); + return std::make_unique(); } - std::shared_ptr Class::use (const Ptr& ptr, bool force) const + std::unique_ptr Class::use(const Ptr& ptr, bool force) const { - return std::shared_ptr (new NullAction); + return std::make_unique(); } - ContainerStore& Class::getContainerStore (const Ptr& ptr) const + ContainerStore& Class::getContainerStore(const Ptr& ptr) const { - throw std::runtime_error ("class does not have a container store"); + throw std::runtime_error("class does not have a container store"); } - InventoryStore& Class::getInventoryStore (const Ptr& ptr) const + InventoryStore& Class::getInventoryStore(const Ptr& ptr) const { - throw std::runtime_error ("class does not have an inventory store"); + throw std::runtime_error("class does not have an inventory store"); } +<<<<<<< HEAD /* Start of tes3mp addition @@ -170,10 +190,14 @@ namespace MWWorld */ bool Class::hasInventoryStore(const Ptr &ptr) const +======= + bool Class::hasInventoryStore(const Ptr& ptr) const +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { return false; } +<<<<<<< HEAD /* Start of tes3mp addition @@ -188,145 +212,145 @@ namespace MWWorld */ bool Class::canLock(const ConstPtr &ptr) const +======= + bool Class::canLock(const ConstPtr& ptr) const +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { return false; } - void Class::setRemainingUsageTime (const Ptr& ptr, float duration) const + void Class::setRemainingUsageTime(const Ptr& ptr, float duration) const { - throw std::runtime_error ("class does not support time-based uses"); + throw std::runtime_error("class does not support time-based uses"); } - float Class::getRemainingUsageTime (const ConstPtr& ptr) const + float Class::getRemainingUsageTime(const ConstPtr& ptr) const { return -1; } - std::string Class::getScript (const ConstPtr& ptr) const + ESM::RefId Class::getScript(const ConstPtr& ptr) const { - return ""; + return ESM::RefId(); } - float Class::getMaxSpeed (const Ptr& ptr) const - { - return 0; - } - - float Class::getCurrentSpeed (const Ptr& ptr) const + float Class::getMaxSpeed(const Ptr& ptr) const { return 0; } - float Class::getJump (const Ptr& ptr) const + float Class::getCurrentSpeed(const Ptr& ptr) const { return 0; } - int Class::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const + float Class::getJump(const Ptr& ptr) const { - throw std::runtime_error ("class does not support enchanting"); + return 0; } - MWMechanics::Movement& Class::getMovementSettings (const Ptr& ptr) const + int Class::getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const { - throw std::runtime_error ("movement settings not supported by class"); + throw std::runtime_error("class does not support enchanting"); } - osg::Vec3f Class::getRotationVector (const Ptr& ptr) const + MWMechanics::Movement& Class::getMovementSettings(const Ptr& ptr) const { - return osg::Vec3f (0, 0, 0); + throw std::runtime_error("movement settings not supported by class"); } - std::pair, bool> Class::getEquipmentSlots (const ConstPtr& ptr) const + osg::Vec3f Class::getRotationVector(const Ptr& ptr) const { - return std::make_pair (std::vector(), false); + return osg::Vec3f(0, 0, 0); } - int Class::getEquipmentSkill (const ConstPtr& ptr) const + std::pair, bool> Class::getEquipmentSlots(const ConstPtr& ptr) const { - return -1; + return std::make_pair(std::vector(), false); } - int Class::getValue (const ConstPtr& ptr) const + ESM::RefId Class::getEquipmentSkill(const ConstPtr& ptr) const { - throw std::logic_error ("value not supported by this class"); + return {}; } - float Class::getCapacity (const MWWorld::Ptr& ptr) const + int Class::getValue(const ConstPtr& ptr) const { - throw std::runtime_error ("capacity not supported by this class"); + throw std::logic_error("value not supported by this class"); } - float Class::getWeight(const ConstPtr &ptr) const + float Class::getCapacity(const MWWorld::Ptr& ptr) const { - throw std::runtime_error ("weight not supported by this class"); + throw std::runtime_error("capacity not supported by this class"); } - float Class::getEncumbrance (const MWWorld::Ptr& ptr) const + float Class::getWeight(const ConstPtr& ptr) const { - throw std::runtime_error ("encumbrance not supported by class"); + throw std::runtime_error("weight not supported by this class"); } - bool Class::isEssential (const MWWorld::ConstPtr& ptr) const + float Class::getEncumbrance(const MWWorld::Ptr& ptr) const + { + throw std::runtime_error("encumbrance not supported by class"); + } + + bool Class::isEssential(const MWWorld::ConstPtr& ptr) const { return false; } - float Class::getArmorRating (const MWWorld::Ptr& ptr) const + float Class::getArmorRating(const MWWorld::Ptr& ptr) const { throw std::runtime_error("Class does not support armor rating"); } - const Class& Class::get (const std::string& key) + const Class& Class::get(unsigned int key) { - if (key.empty()) - throw std::logic_error ("Class::get(): attempting to get an empty key"); + const auto& classes = getClasses(); + auto iter = classes.find(key); - std::map >::const_iterator iter = sClasses.find (key); - - if (iter==sClasses.end()) - throw std::logic_error ("Class::get(): unknown class key: " + key); + if (iter == classes.end()) + throw std::logic_error("Class::get(): unknown class key: " + std::to_string(key)); return *iter->second; } - bool Class::isPersistent(const ConstPtr &ptr) const + bool Class::isPersistent(const ConstPtr& ptr) const { - throw std::runtime_error ("class does not support persistence"); + throw std::runtime_error("class does not support persistence"); } - void Class::registerClass(const std::string& key, std::shared_ptr instance) + void Class::registerClass(Class& instance) { - instance->mTypeName = key; - sClasses.insert(std::make_pair(key, instance)); + getClasses().emplace(instance.getType(), &instance); } - std::string Class::getUpSoundId (const ConstPtr& ptr) const + const ESM::RefId& Class::getUpSoundId(const ConstPtr& ptr) const { - throw std::runtime_error ("class does not have an up sound"); + throw std::runtime_error("class does not have an up sound"); } - std::string Class::getDownSoundId (const ConstPtr& ptr) const + const ESM::RefId& Class::getDownSoundId(const ConstPtr& ptr) const { - throw std::runtime_error ("class does not have an down sound"); + throw std::runtime_error("class does not have an down sound"); } - std::string Class::getSoundIdFromSndGen(const Ptr &ptr, const std::string &type) const + ESM::RefId Class::getSoundIdFromSndGen(const Ptr& ptr, std::string_view type) const { throw std::runtime_error("class does not support soundgen look up"); } - std::string Class::getInventoryIcon (const MWWorld::ConstPtr& ptr) const + const std::string& Class::getInventoryIcon(const MWWorld::ConstPtr& ptr) const { - throw std::runtime_error ("class does not have any inventory icon"); + throw std::runtime_error("class does not have any inventory icon"); } - MWGui::ToolTipInfo Class::getToolTipInfo (const ConstPtr& ptr, int count) const + MWGui::ToolTipInfo Class::getToolTipInfo(const ConstPtr& ptr, int count) const { - throw std::runtime_error ("class does not have a tool tip"); + throw std::runtime_error("class does not have a tool tip"); } - bool Class::showsInInventory (const ConstPtr& ptr) const + bool Class::showsInInventory(const ConstPtr& ptr) const { // NOTE: Don't show WerewolfRobe objects in the inventory, or allow them to be taken. // Vanilla likely uses a hack like this since there's no other way to prevent it from @@ -334,23 +358,21 @@ namespace MWWorld return (ptr.getCellRef().getRefId() != "werewolfrobe"); } - bool Class::hasToolTip (const ConstPtr& ptr) const + bool Class::hasToolTip(const ConstPtr& ptr) const { return true; } - std::string Class::getEnchantment (const ConstPtr& ptr) const + ESM::RefId Class::getEnchantment(const ConstPtr& ptr) const { - return ""; + return ESM::RefId(); } - void Class::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const - { - } + void Class::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const {} - std::string Class::getModel(const MWWorld::ConstPtr &ptr) const + std::string Class::getModel(const MWWorld::ConstPtr& ptr) const { - return ""; + return {}; } bool Class::useAnim() const @@ -358,115 +380,117 @@ namespace MWWorld return false; } - void Class::getModelsToPreload(const Ptr &ptr, std::vector &models) const + void Class::getModelsToPreload(const Ptr& ptr, std::vector& models) const { std::string model = getModel(ptr); if (!model.empty()) models.push_back(model); } - std::string Class::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + const ESM::RefId& Class::applyEnchantment( + const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, const std::string& newName) const { - throw std::runtime_error ("class can't be enchanted"); + throw std::runtime_error("class can't be enchanted"); } - std::pair Class::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + std::pair Class::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const { - return std::make_pair (1, ""); + return { 1, {} }; } - void Class::adjustPosition(const MWWorld::Ptr& ptr, bool force) const - { - } + void Class::adjustPosition(const MWWorld::Ptr& ptr, bool force) const {} - std::shared_ptr Class::defaultItemActivate(const Ptr &ptr, const Ptr &actor) const + std::unique_ptr Class::defaultItemActivate(const Ptr& ptr, const Ptr& actor) const { - if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) - return std::shared_ptr(new NullAction()); + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return std::make_unique(); - if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfItem"); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound* sound = store.get().searchRandom("WolfItem", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); - if(sound) action->setSound(sound->mId); + std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); + if (sound) + action->setSound(sound->mId); return action; } - std::shared_ptr action(new ActionTake(ptr)); + std::unique_ptr action = std::make_unique(ptr); action->setSound(getUpSoundId(ptr)); return action; } - MWWorld::Ptr - Class::copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const + MWWorld::Ptr Class::copyToCellImpl(const ConstPtr& ptr, CellStore& cell) const { throw std::runtime_error("unable to copy class to cell"); } - MWWorld::Ptr - Class::copyToCell(const ConstPtr &ptr, CellStore &cell, int count) const + MWWorld::Ptr Class::copyToCell(const ConstPtr& ptr, CellStore& cell, int count) const { Ptr newPtr = copyToCellImpl(ptr, cell); newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference newPtr.getRefData().setCount(count); + newPtr.getRefData().setLuaScripts(nullptr); + MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); + if (hasInventoryStore(newPtr)) + getInventoryStore(newPtr).setActor(newPtr); return newPtr; } - MWWorld::Ptr - Class::copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const + MWWorld::Ptr Class::moveToCell(const Ptr& ptr, CellStore& cell) const + { + Ptr newPtr = copyToCellImpl(ptr, cell); + ptr.getRefData().setLuaScripts(nullptr); + MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); + if (hasInventoryStore(newPtr)) + getInventoryStore(newPtr).setActor(newPtr); + return newPtr; + } + + MWWorld::Ptr Class::copyToCell(const ConstPtr& ptr, CellStore& cell, const ESM::Position& pos, int count) const { Ptr newPtr = copyToCell(ptr, cell, count); newPtr.getRefData().setPosition(pos); - return newPtr; } - bool Class::isBipedal(const ConstPtr &ptr) const + bool Class::isBipedal(const ConstPtr& ptr) const { return false; } - bool Class::canFly(const ConstPtr &ptr) const + bool Class::canFly(const ConstPtr& ptr) const { return false; } - bool Class::canSwim(const ConstPtr &ptr) const + bool Class::canSwim(const ConstPtr& ptr) const { return false; } - bool Class::canWalk(const ConstPtr &ptr) const + bool Class::canWalk(const ConstPtr& ptr) const { return false; } bool Class::isPureWaterCreature(const ConstPtr& ptr) const { - return canSwim(ptr) - && !isBipedal(ptr) - && !canFly(ptr) - && !canWalk(ptr); + return canSwim(ptr) && !isBipedal(ptr) && !canFly(ptr) && !canWalk(ptr); } bool Class::isPureFlyingCreature(const ConstPtr& ptr) const { - return canFly(ptr) - && !isBipedal(ptr) - && !canSwim(ptr) - && !canWalk(ptr); + return canFly(ptr) && !isBipedal(ptr) && !canSwim(ptr) && !canWalk(ptr); } bool Class::isPureLandCreature(const Ptr& ptr) const { - return canWalk(ptr) - && !isBipedal(ptr) - && !canFly(ptr) - && !canSwim(ptr); + return canWalk(ptr) && !isBipedal(ptr) && !canFly(ptr) && !canSwim(ptr); } bool Class::isMobile(const MWWorld::Ptr& ptr) const @@ -474,41 +498,41 @@ namespace MWWorld return canSwim(ptr) || canWalk(ptr) || canFly(ptr); } - float Class::getSkill(const MWWorld::Ptr& ptr, int skill) const + float Class::getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const { throw std::runtime_error("class does not support skills"); } - int Class::getBloodTexture (const MWWorld::ConstPtr& ptr) const + int Class::getBloodTexture(const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("class does not support gore"); } - void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} + void Class::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} - void Class::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const {} + void Class::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const {} int Class::getBaseGold(const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("class does not support base gold"); } - bool Class::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const + bool Class::isClass(const MWWorld::ConstPtr& ptr, std::string_view className) const { return false; } - MWWorld::DoorState Class::getDoorState (const MWWorld::ConstPtr &ptr) const + MWWorld::DoorState Class::getDoorState(const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("this is not a door"); } - void Class::setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const + void Class::setDoorState(const MWWorld::Ptr& ptr, MWWorld::DoorState state) const { throw std::runtime_error("this is not a door"); } - float Class::getNormalizedEncumbrance(const Ptr &ptr) const + float Class::getNormalizedEncumbrance(const Ptr& ptr) const { float capacity = getCapacity(ptr); float encumbrance = getEncumbrance(ptr); @@ -522,45 +546,46 @@ namespace MWWorld return encumbrance / capacity; } - std::string Class::getSound(const MWWorld::ConstPtr&) const + ESM::RefId Class::getSound(const MWWorld::ConstPtr&) const { - return std::string(); + return ESM::RefId(); } - int Class::getBaseFightRating(const ConstPtr &ptr) const + int Class::getBaseFightRating(const ConstPtr& ptr) const { throw std::runtime_error("class does not support fight rating"); } - std::string Class::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const + ESM::RefId Class::getPrimaryFaction(const MWWorld::ConstPtr& ptr) const { - return std::string(); + return ESM::RefId(); } - int Class::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const + int Class::getPrimaryFactionRank(const MWWorld::ConstPtr& ptr) const { return -1; } - float Class::getEffectiveArmorRating(const ConstPtr &armor, const Ptr &actor) const + float Class::getEffectiveArmorRating(const ConstPtr& armor, const Ptr& actor) const { throw std::runtime_error("class does not support armor ratings"); } osg::Vec4f Class::getEnchantmentColor(const MWWorld::ConstPtr& item) const { - osg::Vec4f result(1,1,1,1); - std::string enchantmentName = item.getClass().getEnchantment(item); + osg::Vec4f result(1, 1, 1, 1); + const ESM::RefId& enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) return result; - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentName); + const ESM::Enchantment* enchantment + = MWBase::Environment::get().getESMStore()->get().search(enchantmentName); if (!enchantment) return result; - assert (enchantment->mEffects.mList.size()); + assert(enchantment->mEffects.mList.size()); - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().search( - enchantment->mEffects.mList.front().mEffectID); + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().search( + enchantment->mEffects.mList.front().mEffectID); if (!magicEffect) return result; @@ -570,14 +595,14 @@ namespace MWWorld return result; } - void Class::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const + void Class::setBaseAISetting(const ESM::RefId&, MWMechanics::AiSetting setting, int value) const { - throw std::runtime_error ("class does not have creature stats"); + throw std::runtime_error("class does not have creature stats"); } - void Class::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + void Class::modifyBaseInventory(const ESM::RefId& actorId, const ESM::RefId& itemId, int amount) const { - throw std::runtime_error ("class does not have an inventory store"); + throw std::runtime_error("class does not have an inventory store"); } float Class::getWalkSpeed(const Ptr& /*ptr*/) const diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 621ebb4fc..b5d9e075c 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,11 +6,15 @@ #include #include +#include #include -#include "ptr.hpp" #include "doorstate.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "ptr.hpp" + +#include "../mwmechanics/aisetting.hpp" +#include +#include namespace ESM { @@ -31,6 +35,7 @@ namespace MWMechanics { class NpcStats; struct Movement; + class CreatureStats; } namespace MWGui @@ -53,108 +58,125 @@ namespace MWWorld /// \brief Base class for referenceable esm records class Class { - static std::map > sClasses; + const unsigned mType; - std::string mTypeName; + static std::map& getClasses(); - // not implemented - Class (const Class&); - Class& operator= (const Class&); + protected: + explicit Class(unsigned type) + : mType(type) + { + } - protected: + std::unique_ptr defaultItemActivate(const Ptr& ptr, const Ptr& actor) const; + ///< Generate default action for activating inventory items - Class(); + virtual Ptr copyToCellImpl(const ConstPtr& ptr, CellStore& cell) const; - std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; - ///< Generate default action for activating inventory items + public: + virtual ~Class() = default; + Class(const Class&) = delete; + Class& operator=(const Class&) = delete; - virtual Ptr copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const; + unsigned int getType() const { return mType; } - public: + virtual void insertObjectRendering( + const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const; + ///< Add reference into a cell for rendering (default implementation: don't render anything). + virtual void insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const; - virtual ~Class(); + virtual std::string_view getName(const ConstPtr& ptr) const = 0; + ///< \return name or ID; can return an empty string. - const std::string& getTypeName() const { - return mTypeName; - } + virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; + ///< Adjust position to stand on ground. Must be called post model load + /// @param force do this even if the ptr is flying - virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; - ///< Add reference into a cell for rendering (default implementation: don't render anything). + virtual MWMechanics::CreatureStats& getCreatureStats(const Ptr& ptr) const; + ///< Return creature stats or throw an exception, if class does not have creature stats + /// (default implementation: throw an exception) - virtual std::string getName (const ConstPtr& ptr) const = 0; - ///< \return name or ID; can return an empty string. + virtual bool hasToolTip(const ConstPtr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: true) - virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; - ///< Adjust position to stand on ground. Must be called post model load - /// @param force do this even if the ptr is flying + virtual MWGui::ToolTipInfo getToolTipInfo(const ConstPtr& ptr, int count) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual MWMechanics::CreatureStats& getCreatureStats (const Ptr& ptr) const; - ///< Return creature stats or throw an exception, if class does not have creature stats - /// (default implementation: throw an exception) + virtual bool showsInInventory(const ConstPtr& ptr) const; + ///< Return whether ptr shows in inventory views. + /// Hidden items are not displayed and cannot be (re)moved by the user. + /// \return True if shown, false if hidden. - virtual bool hasToolTip (const ConstPtr& ptr) const; - ///< @return true if this object has a tooltip when focused (default implementation: true) + virtual MWMechanics::NpcStats& getNpcStats(const Ptr& ptr) const; + ///< Return NPC stats or throw an exception, if class does not have NPC stats + /// (default implementation: throw an exception) - virtual MWGui::ToolTipInfo getToolTipInfo (const ConstPtr& ptr, int count) const; - ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual bool hasItemHealth(const ConstPtr& ptr) const; + ///< \return Item health data available? (default implementation: false) - virtual bool showsInInventory (const ConstPtr& ptr) const; - ///< Return whether ptr shows in inventory views. - /// Hidden items are not displayed and cannot be (re)moved by the user. - /// \return True if shown, false if hidden. + virtual int getItemHealth(const ConstPtr& ptr) const; + ///< Return current item health or throw an exception if class does not have item health - virtual MWMechanics::NpcStats& getNpcStats (const Ptr& ptr) const; - ///< Return NPC stats or throw an exception, if class does not have NPC stats - /// (default implementation: throw an exception) + virtual float getItemNormalizedHealth(const ConstPtr& ptr) const; + ///< Return current item health re-scaled to maximum health - virtual bool hasItemHealth (const ConstPtr& ptr) const; - ///< \return Item health data available? (default implementation: false) + virtual int getItemMaxHealth(const ConstPtr& ptr) const; + ///< Return item max health or throw an exception, if class does not have item health + /// (default implementation: throw an exception) - virtual int getItemHealth (const ConstPtr& ptr) const; - ///< Return current item health or throw an exception if class does not have item health + virtual bool evaluateHit(const Ptr& ptr, Ptr& victim, osg::Vec3f& hitPosition) const; + ///< Evaluate the victim of a melee hit produced by ptr in the current circumstances and return dice roll + ///< success. + /// (default implementation: throw an exception) - virtual float getItemNormalizedHealth (const ConstPtr& ptr) const; - ///< Return current item health re-scaled to maximum health + virtual void hit(const Ptr& ptr, float attackStrength, int type = -1, const Ptr& victim = Ptr(), + const osg::Vec3f& hitPosition = osg::Vec3f(), bool success = false) const; + ///< Execute a melee hit on the victim at hitPosition, using the current weapon. If the hit was successful, + ///< apply damage and process corresponding events. + /// \param attackStrength how long the attack was charged for, a value in 0-1 range. + /// \param type - type of attack, one of the MWMechanics::CreatureStats::AttackType + /// enums. ignored for creature attacks. + /// (default implementation: throw an exception) - virtual int getItemMaxHealth (const ConstPtr& ptr) const; - ///< Return item max health or throw an exception, if class does not have item health - /// (default implementation: throw an exception) + virtual void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const; + ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is + /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the + /// actor responsible for the attack, and \a successful specifies if the hit is + /// successful or not. - virtual void hit(const Ptr& ptr, float attackStrength, int type=-1) const; - ///< Execute a melee hit, using the current weapon. This will check the relevant skills - /// of the given attacker, and whoever is hit. - /// \param attackStrength how long the attack was charged for, a value in 0-1 range. - /// \param type - type of attack, one of the MWMechanics::CreatureStats::AttackType - /// enums. ignored for creature attacks. - /// (default implementation: throw an exception) + virtual void block(const Ptr& ptr) const; + ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield + /// (default implementation: throw an exception) - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const; - ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is - /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the - /// actor responsible for the attack, and \a successful specifies if the hit is - /// successful or not. + virtual std::unique_ptr activate(const Ptr& ptr, const Ptr& actor) const; + ///< Generate action for activation (default implementation: return a null action). - virtual void block (const Ptr& ptr) const; - ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield - /// (default implementation: throw an exception) + virtual std::unique_ptr use(const Ptr& ptr, bool force = false) const; + ///< Generate action for using via inventory menu (default implementation: return a + /// null action). - virtual std::shared_ptr activate (const Ptr& ptr, const Ptr& actor) const; - ///< Generate action for activation (default implementation: return a null action). + virtual ContainerStore& getContainerStore(const Ptr& ptr) const; + ///< Return container store or throw an exception, if class does not have a + /// container store (default implementation: throw an exception) - virtual std::shared_ptr use (const Ptr& ptr, bool force=false) - const; - ///< Generate action for using via inventory menu (default implementation: return a - /// null action). + virtual InventoryStore& getInventoryStore(const Ptr& ptr) const; + ///< Return inventory store or throw an exception, if class does not have a + /// inventory store (default implementation: throw an exception) - virtual ContainerStore& getContainerStore (const Ptr& ptr) const; - ///< Return container store or throw an exception, if class does not have a - /// container store (default implementation: throw an exception) + virtual bool hasInventoryStore(const Ptr& ptr) const; + ///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false) - virtual InventoryStore& getInventoryStore (const Ptr& ptr) const; - ///< Return inventory store or throw an exception, if class does not have a - /// inventory store (default implementation: throw an exception) + virtual bool canLock(const ConstPtr& ptr) const; + virtual void setRemainingUsageTime(const Ptr& ptr, float duration) const; + ///< Sets the remaining duration of the object, such as an equippable light + /// source. (default implementation: throw an exception) + +<<<<<<< HEAD /* Start of tes3mp addition @@ -168,9 +190,17 @@ namespace MWWorld virtual bool hasInventoryStore (const Ptr& ptr) const; ///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false) +======= + virtual float getRemainingUsageTime(const ConstPtr& ptr) const; + ///< Returns the remaining duration of the object, such as an equippable light + /// source. (default implementation: -1, i.e. infinite) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - virtual bool canLock (const ConstPtr& ptr) const; + virtual ESM::RefId getScript(const ConstPtr& ptr) const; + ///< Return name of the script attached to ptr (default implementation: return an empty + /// string). +<<<<<<< HEAD /* Start of tes3mp addition @@ -185,212 +215,203 @@ namespace MWWorld virtual void setRemainingUsageTime (const Ptr& ptr, float duration) const; ///< Sets the remaining duration of the object, such as an equippable light /// source. (default implementation: throw an exception) +======= + virtual float getWalkSpeed(const Ptr& ptr) const; + virtual float getRunSpeed(const Ptr& ptr) const; + virtual float getSwimSpeed(const Ptr& ptr) const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - virtual float getRemainingUsageTime (const ConstPtr& ptr) const; - ///< Returns the remaining duration of the object, such as an equippable light - /// source. (default implementation: -1, i.e. infinite) + /// Return maximal movement speed for the current state. + virtual float getMaxSpeed(const Ptr& ptr) const; - virtual std::string getScript (const ConstPtr& ptr) const; - ///< Return name of the script attached to ptr (default implementation: return an empty - /// string). + /// Return current movement speed. + virtual float getCurrentSpeed(const Ptr& ptr) const; - virtual float getWalkSpeed(const Ptr& ptr) const; - virtual float getRunSpeed(const Ptr& ptr) const; - virtual float getSwimSpeed(const Ptr& ptr) const; + virtual float getJump(const MWWorld::Ptr& ptr) const; + ///< Return jump velocity (not accounting for movement) - /// Return maximal movement speed for the current state. - virtual float getMaxSpeed(const Ptr& ptr) const; + virtual MWMechanics::Movement& getMovementSettings(const Ptr& ptr) const; + ///< Return desired movement. - /// Return current movement speed. - virtual float getCurrentSpeed(const Ptr& ptr) const; + virtual osg::Vec3f getRotationVector(const Ptr& ptr) const; + ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. - virtual float getJump(const MWWorld::Ptr &ptr) const; - ///< Return jump velocity (not accounting for movement) + virtual std::pair, bool> getEquipmentSlots(const ConstPtr& ptr) const; + ///< \return first: Return IDs of the slot this object can be equipped in; second: can object + /// stay stacked when equipped? + /// + /// Default implementation: return (empty vector, false). - virtual MWMechanics::Movement& getMovementSettings (const Ptr& ptr) const; - ///< Return desired movement. + virtual ESM::RefId getEquipmentSkill(const ConstPtr& ptr) const; + /// Return the index of the skill this item corresponds to when equipped. + /// (default implementation: return empty ref id) - virtual osg::Vec3f getRotationVector (const Ptr& ptr) const; - ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. + virtual int getValue(const ConstPtr& ptr) const; + ///< Return trade value of the object. Throws an exception, if the object can't be traded. + /// (default implementation: throws an exception) - virtual std::pair, bool> getEquipmentSlots (const ConstPtr& ptr) const; - ///< \return first: Return IDs of the slot this object can be equipped in; second: can object - /// stay stacked when equipped? - /// - /// Default implementation: return (empty vector, false). + virtual float getCapacity(const MWWorld::Ptr& ptr) const; + ///< Return total weight that fits into the object. Throws an exception, if the object can't + /// hold other objects. + /// (default implementation: throws an exception) - virtual int getEquipmentSkill (const ConstPtr& ptr) - const; - /// Return the index of the skill this item corresponds to when equipped or -1, if there is - /// no such skill. - /// (default implementation: return -1) + virtual float getEncumbrance(const MWWorld::Ptr& ptr) const; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. + /// (default implementation: throws an exception) - virtual int getValue (const ConstPtr& ptr) const; - ///< Return trade value of the object. Throws an exception, if the object can't be traded. - /// (default implementation: throws an exception) + virtual float getNormalizedEncumbrance(const MWWorld::Ptr& ptr) const; + ///< Returns encumbrance re-scaled to capacity - virtual float getCapacity (const MWWorld::Ptr& ptr) const; - ///< Return total weight that fits into the object. Throws an exception, if the object can't - /// hold other objects. - /// (default implementation: throws an exception) + virtual bool consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const; + ///< Consume an item, e. g. a potion. - virtual float getEncumbrance (const MWWorld::Ptr& ptr) const; - ///< Returns total weight of objects inside this object (including modifications from magic - /// effects). Throws an exception, if the object can't hold other objects. - /// (default implementation: throws an exception) + virtual void skillUsageSucceeded( + const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor = 1.f) const; + ///< Inform actor \a ptr that a skill use has succeeded. + /// + /// (default implementations: throws an exception) - virtual float getNormalizedEncumbrance (const MWWorld::Ptr& ptr) const; - ///< Returns encumbrance re-scaled to capacity + virtual bool isEssential(const MWWorld::ConstPtr& ptr) const; + ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) + /// + /// (default implementation: return false) - virtual bool apply (const MWWorld::Ptr& ptr, const std::string& id, - const MWWorld::Ptr& actor) const; - ///< Apply \a id on \a ptr. - /// \param actor Actor that is resposible for the ID being applied to \a ptr. - /// \return Any effect? - /// - /// (default implementation: ignore and return false) + virtual const ESM::RefId& getUpSoundId(const ConstPtr& ptr) const; + ///< Return the up sound ID of \a ptr or throw an exception, if class does not support ID retrieval + /// (default implementation: throw an exception) - virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor=1.f) const; - ///< Inform actor \a ptr that a skill use has succeeded. - /// - /// (default implementations: throws an exception) + virtual const ESM::RefId& getDownSoundId(const ConstPtr& ptr) const; + ///< Return the down sound ID of \a ptr or throw an exception, if class does not support ID retrieval + /// (default implementation: throw an exception) - virtual bool isEssential (const MWWorld::ConstPtr& ptr) const; - ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - /// - /// (default implementation: return false) + virtual ESM::RefId getSoundIdFromSndGen(const Ptr& ptr, std::string_view type) const; + ///< Returns the sound ID for \a ptr of the given soundgen \a type. - virtual std::string getUpSoundId (const ConstPtr& ptr) const; - ///< Return the up sound ID of \a ptr or throw an exception, if class does not support ID retrieval - /// (default implementation: throw an exception) + virtual float getArmorRating(const MWWorld::Ptr& ptr) const; + ///< @return combined armor rating of this actor - virtual std::string getDownSoundId (const ConstPtr& ptr) const; - ///< Return the down sound ID of \a ptr or throw an exception, if class does not support ID retrieval - /// (default implementation: throw an exception) + virtual const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const; + ///< Return name of inventory icon. - virtual std::string getSoundIdFromSndGen(const Ptr &ptr, const std::string &type) const; - ///< Returns the sound ID for \a ptr of the given soundgen \a type. + virtual ESM::RefId getEnchantment(const MWWorld::ConstPtr& ptr) const; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + /// (default implementation: return empty string) - virtual float getArmorRating (const MWWorld::Ptr& ptr) const; - ///< @return combined armor rating of this actor + virtual int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const; + ///< @return the number of enchantment points available for possible enchanting - virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; - ///< Return name of inventory icon. + virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh - virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; - ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - /// (default implementation: return empty string) + virtual bool canSell(const MWWorld::ConstPtr& item, int npcServices) const; + ///< Determine whether or not \a item can be sold to an npc with the given \a npcServices - virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; - ///< @return the number of enchantment points available for possible enchanting + virtual int getServices(const MWWorld::ConstPtr& actor) const; - virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; - /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh + virtual std::string getModel(const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - ///< Determine whether or not \a item can be sold to an npc with the given \a npcServices + virtual bool useAnim() const; + ///< Whether or not to use animated variant of model (default false) - virtual int getServices (const MWWorld::ConstPtr& actor) const; + virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: + ///< list getModel(). - virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; + virtual const ESM::RefId& applyEnchantment( + const MWWorld::ConstPtr& ptr, const ESM::RefId& enchId, int enchCharge, const std::string& newName) const; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual bool useAnim() const; - ///< Whether or not to use animated variant of model (default false) + virtual std::pair canBeEquipped( + const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const; + ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon + ///< conflicts with that. + /// Second item in the pair specifies the error message - virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; - ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). + virtual float getWeight(const MWWorld::ConstPtr& ptr) const; - virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; - ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. + virtual bool isPersistent(const MWWorld::ConstPtr& ptr) const; - virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; - ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. - /// Second item in the pair specifies the error message + virtual bool isKey(const MWWorld::ConstPtr& ptr) const { return false; } - virtual float getWeight (const MWWorld::ConstPtr& ptr) const; + virtual bool isGold(const MWWorld::ConstPtr& ptr) const { return false; } - virtual bool isPersistent (const MWWorld::ConstPtr& ptr) const; + virtual bool isSoulGem(const MWWorld::ConstPtr& ptr) const { return false; } - virtual bool isKey (const MWWorld::ConstPtr& ptr) const { return false; } + virtual bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const { return true; } + ///< Return whether this class of object can be activated with telekinesis - virtual bool isGold(const MWWorld::ConstPtr& ptr) const { return false; } + /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) + virtual int getBloodTexture(const MWWorld::ConstPtr& ptr) const; - virtual bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const { return true; } - ///< Return whether this class of object can be activated with telekinesis + virtual Ptr copyToCell(const ConstPtr& ptr, CellStore& cell, int count) const; - /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; + // Similar to `copyToCell`, but preserves RefNum and moves LuaScripts. + // The original is expected to be removed after calling this function, + // but this function itself doesn't remove the original. + virtual Ptr moveToCell(const Ptr& ptr, CellStore& cell) const; - virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, int count) const; + Ptr copyToCell(const ConstPtr& ptr, CellStore& cell, const ESM::Position& pos, int count) const; - virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const; + virtual bool isActivator() const { return false; } - virtual bool isActivator() const { - return false; - } + virtual bool isActor() const { return false; } - virtual bool isActor() const { - return false; - } + virtual bool isNpc() const { return false; } - virtual bool isNpc() const { - return false; - } + virtual bool isDoor() const { return false; } - virtual bool isDoor() const { - return false; - } + // True if it is an item that can be picked up. + virtual bool isItem(const MWWorld::ConstPtr& ptr) const { return false; } - virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; - virtual bool canFly(const MWWorld::ConstPtr& ptr) const; - virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; - virtual bool canWalk(const MWWorld::ConstPtr& ptr) const; - bool isPureWaterCreature(const MWWorld::ConstPtr& ptr) const; - bool isPureFlyingCreature(const MWWorld::ConstPtr& ptr) const; - bool isPureLandCreature(const MWWorld::Ptr& ptr) const; - bool isMobile(const MWWorld::Ptr& ptr) const; + virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; + virtual bool canFly(const MWWorld::ConstPtr& ptr) const; + virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; + virtual bool canWalk(const MWWorld::ConstPtr& ptr) const; + bool isPureWaterCreature(const MWWorld::ConstPtr& ptr) const; + bool isPureFlyingCreature(const MWWorld::ConstPtr& ptr) const; + bool isPureLandCreature(const MWWorld::Ptr& ptr) const; + bool isMobile(const MWWorld::Ptr& ptr) const; - virtual float getSkill(const MWWorld::Ptr& ptr, int skill) const; + virtual float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const; - virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const; - ///< Read additional state from \a state into \a ptr. + virtual void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; + ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) - const; - ///< Write additional state from \a ptr into \a state. + virtual void writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; + ///< Write additional state from \a ptr into \a state. - static const Class& get (const std::string& key); - ///< If there is no class for this \a key, an exception is thrown. + static const Class& get(unsigned int key); + ///< If there is no class for this \a key, an exception is thrown. - static void registerClass (const std::string& key, std::shared_ptr instance); + static void registerClass(Class& instance); - virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; + virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; - virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const; + virtual bool isClass(const MWWorld::ConstPtr& ptr, std::string_view className) const; - virtual DoorState getDoorState (const MWWorld::ConstPtr &ptr) const; - /// This does not actually cause the door to move. Use World::activateDoor instead. - virtual void setDoorState (const MWWorld::Ptr &ptr, DoorState state) const; + virtual DoorState getDoorState(const MWWorld::ConstPtr& ptr) const; + /// This does not actually cause the door to move. Use World::activateDoor instead. + virtual void setDoorState(const MWWorld::Ptr& ptr, DoorState state) const; - virtual void respawn (const MWWorld::Ptr& ptr) const {} + virtual void respawn(const MWWorld::Ptr& ptr) const {} - /// Returns sound id - virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; + /// Returns sound id + virtual ESM::RefId getSound(const MWWorld::ConstPtr& ptr) const; - virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const; + virtual int getBaseFightRating(const MWWorld::ConstPtr& ptr) const; - virtual std::string getPrimaryFaction (const MWWorld::ConstPtr& ptr) const; - virtual int getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const; + virtual ESM::RefId getPrimaryFaction(const MWWorld::ConstPtr& ptr) const; + virtual int getPrimaryFactionRank(const MWWorld::ConstPtr& ptr) const; - /// Get the effective armor rating, factoring in the actor's skills, for the given armor. - virtual float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; + /// Get the effective armor rating, factoring in the actor's skills, for the given armor. + virtual float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; - virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; + virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; - virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + virtual void setBaseAISetting(const ESM::RefId& id, MWMechanics::AiSetting setting, int value) const; - virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; + virtual void modifyBaseInventory(const ESM::RefId& actorId, const ESM::RefId& itemId, int amount) const; }; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 5bf25a2ba..78bfbb70a 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -1,7 +1,7 @@ #include "containerstore.hpp" +#include "inventorystore.hpp" #include -#include #include /* @@ -18,31 +18,37 @@ */ #include -#include +#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/levelledlist.hpp" -#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/recharge.hpp" +#include "../mwmechanics/spellutil.hpp" -#include "manualref.hpp" -#include "refdata.hpp" #include "class.hpp" +#include "esmstore.hpp" #include "localscripts.hpp" +#include "manualref.hpp" #include "player.hpp" +#include "refdata.hpp" +#include "worldmodel.hpp" namespace { void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell) { auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts(); - for(const auto&& ptr : store) + for (const auto&& ptr : store) { - const std::string& script = ptr.getClass().getScript(ptr); - if(!script.empty()) + const auto& script = ptr.getClass().getScript(ptr); + if (!script.empty()) { MWWorld::Ptr item = ptr; item.mCell = cell; @@ -51,33 +57,31 @@ namespace } } - template - float getTotalWeight (const MWWorld::CellRefList& cellRefList) + template + float getTotalWeight(const MWWorld::CellRefList& cellRefList) { float sum = 0; - for (const auto& iter : cellRefList.mList) + for (const MWWorld::LiveCellRef& liveCellRef : cellRefList.mList) { - if (iter.mData.getCount()>0) - sum += iter.mData.getCount()*iter.mBase->mData.mWeight; + if (const int count = liveCellRef.mData.getCount(); count > 0) + sum += count * liveCellRef.mBase->mData.mWeight; } return sum; } - template - MWWorld::Ptr searchId (MWWorld::CellRefList& list, const std::string& id, - MWWorld::ContainerStore *store) + template + MWWorld::Ptr searchId(MWWorld::CellRefList& list, const ESM::RefId& id, MWWorld::ContainerStore* store) { store->resolve(); - std::string id2 = Misc::StringUtils::lowerCase (id); - for (auto& iter : list.mList) + for (MWWorld::LiveCellRef& liveCellRef : list.mList) { - if (Misc::StringUtils::ciEqual(iter.mBase->mId, id2) && iter.mData.getCount()) + if ((liveCellRef.mBase->mId == id) && liveCellRef.mData.getCount()) { - MWWorld::Ptr ptr (&iter, nullptr); - ptr.setContainerStore (store); + MWWorld::Ptr ptr(&liveCellRef, nullptr); + ptr.setContainerStore(store); return ptr; } } @@ -92,88 +96,91 @@ MWWorld::ResolutionListener::~ResolutionListener() { mStore.unresolve(); } - catch(const std::exception& e) + catch (const std::exception& e) { Log(Debug::Error) << "Failed to clear temporary container contents: " << e.what(); } } -template -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList& collection, - const ESM::ObjectState& state) +template +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState( + CellRefList& collection, const ESM::ObjectState& state) { - if (!LiveCellRef::checkState (state)) - return ContainerStoreIterator (this); // not valid anymore with current content files -> skip + if (!LiveCellRef::checkState(state)) + return ContainerStoreIterator(this); // not valid anymore with current content files -> skip - const T *record = MWBase::Environment::get().getWorld()->getStore(). - get().search (state.mRef.mRefID); + const T* record = MWBase::Environment::get().getESMStore()->get().search(state.mRef.mRefID); if (!record) - return ContainerStoreIterator (this); + return ContainerStoreIterator(this); - LiveCellRef ref (record); - ref.load (state); - collection.mList.push_back (ref); + LiveCellRef ref(record); + ref.load(state); + collection.mList.push_back(ref); - return ContainerStoreIterator (this, --collection.mList.end()); + return ContainerStoreIterator(this, --collection.mList.end()); } -void MWWorld::ContainerStore::storeEquipmentState(const MWWorld::LiveCellRefBase &ref, int index, ESM::InventoryState &inventory) const +void MWWorld::ContainerStore::storeEquipmentState( + const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const { } -void MWWorld::ContainerStore::readEquipmentState(const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState &inventory) +void MWWorld::ContainerStore::readEquipmentState( + const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) { } -template -void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::ObjectState& state) const +template +void MWWorld::ContainerStore::storeState(const LiveCellRef& ref, ESM::ObjectState& state) const { - ref.save (state); + ref.save(state); } -template -void MWWorld::ContainerStore::storeStates (const CellRefList& collection, - ESM::InventoryState& inventory, int& index, bool equipable) const +template +void MWWorld::ContainerStore::storeStates( + const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const { - for (const auto& iter : collection.mList) + for (const LiveCellRef& liveCellRef : collection.mList) { - if (iter.mData.getCount() == 0) + if (liveCellRef.mData.getCount() == 0) continue; ESM::ObjectState state; - storeState (iter, state); + storeState(liveCellRef, state); if (equipable) - storeEquipmentState(iter, index, inventory); - inventory.mItems.push_back (state); + storeEquipmentState(liveCellRef, index, inventory); + inventory.mItems.push_back(std::move(state)); ++index; } } -const std::string MWWorld::ContainerStore::sGoldId = "gold_001"; +const ESM::RefId MWWorld::ContainerStore::sGoldId = ESM::RefId::stringRefId("gold_001"); MWWorld::ContainerStore::ContainerStore() : mListener(nullptr) , mRechargingItemsUpToDate(false) - , mCachedWeight (0) - , mWeightUpToDate (false) + , mCachedWeight(0) + , mWeightUpToDate(false) , mModified(false) , mResolved(false) , mSeed() - , mPtr() {} + , mPtr() +{ +} MWWorld::ContainerStore::~ContainerStore() {} -MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin (int mask) const +MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin(int mask) const { - return ConstContainerStoreIterator (mask, this); + return ConstContainerStoreIterator(mask, this); } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cend() const { - return ConstContainerStoreIterator (this); + return ConstContainerStoreIterator(this); } -MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::begin (int mask) const +MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::begin(int mask) const { return cbegin(mask); } @@ -183,42 +190,48 @@ MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::end() const return cend(); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::begin (int mask) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::begin(int mask) { - return ContainerStoreIterator (mask, this); + return ContainerStoreIterator(mask, this); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() { - return ContainerStoreIterator (this); + return ContainerStoreIterator(this); } -int MWWorld::ContainerStore::count(const std::string &id) const +int MWWorld::ContainerStore::count(const ESM::RefId& id) const { - int total=0; + int total = 0; for (const auto&& iter : *this) - if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) + if (iter.getCellRef().getRefId() == id) total += iter.getRefData().getCount(); return total; } +void MWWorld::ContainerStore::clearRefNums() +{ + for (const auto& iter : *this) + iter.getCellRef().unsetRefNum(); +} + MWWorld::ContainerStoreListener* MWWorld::ContainerStore::getContListener() const { return mListener; } - void MWWorld::ContainerStore::setContListener(MWWorld::ContainerStoreListener* listener) { mListener = listener; } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container, int count) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr, int count) { resolve(); if (ptr.getRefData().getCount() <= count) return end(); MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getRefData().getCount(false), count)); +<<<<<<< HEAD /* Start of tes3mp addition @@ -239,10 +252,13 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, */ const std::string script = it->getClass().getScript(*it); +======= + const ESM::RefId& script = it->getClass().getScript(*it); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); - remove(ptr, ptr.getRefData().getCount()-count, container); + remove(ptr, ptr.getRefData().getCount() - count); return it; } @@ -251,7 +267,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld:: { resolve(); MWWorld::ContainerStoreIterator retval = end(); - for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) { if (item == *iter) { @@ -263,11 +279,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld:: if (retval == end()) throw std::runtime_error("item is not from this container"); - for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) { if (stacks(*iter, item)) { - iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), item.getRefData().getCount(false))); + iter->getRefData().setCount( + addItems(iter->getRefData().getCount(false), item.getRefData().getCount(false))); item.getRefData().setCount(0); retval = iter; break; @@ -281,17 +298,19 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const MWWorld::Class& cls1 = ptr1.getClass(); const MWWorld::Class& cls2 = ptr2.getClass(); - if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefId(), ptr2.getCellRef().getRefId())) + if (!(ptr1.getCellRef().getRefId() == ptr2.getCellRef().getRefId())) return false; // If it has an enchantment, don't stack when some of the charge is already used if (!ptr1.getClass().getEnchantment(ptr1).empty()) { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - ptr1.getClass().getEnchantment(ptr1)); - float maxCharge = static_cast(enchantment->mData.mCharge); - float enchantCharge1 = ptr1.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr1.getCellRef().getEnchantmentCharge(); - float enchantCharge2 = ptr2.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr2.getCellRef().getEnchantmentCharge(); + const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().find( + ptr1.getClass().getEnchantment(ptr1)); + const float maxCharge = static_cast(MWMechanics::getEnchantmentCharge(*enchantment)); + float enchantCharge1 + = ptr1.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr1.getCellRef().getEnchantmentCharge(); + float enchantCharge2 + = ptr2.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr2.getCellRef().getEnchantmentCharge(); if (enchantCharge1 != maxCharge || enchantCharge2 != maxCharge) return false; } @@ -306,25 +325,28 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) && cls2.getScript(ptr2).empty() // item that is already partly used up never stacks - && (!cls1.hasItemHealth(ptr1) || ( - cls1.getItemHealth(ptr1) == cls1.getItemMaxHealth(ptr1) - && cls2.getItemHealth(ptr2) == cls2.getItemMaxHealth(ptr2))); + && (!cls1.hasItemHealth(ptr1) + || (cls1.getItemHealth(ptr1) == cls1.getItemMaxHealth(ptr1) + && cls2.getItemHealth(ptr2) == cls2.getItemMaxHealth(ptr2))); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const ESM::RefId& id, int count) { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); - return add(ref.getPtr(), count, actorPtr); + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), id, count); + return add(ref.getPtr(), count); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool /*allowAutoEquip*/, bool resolve) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( + const Ptr& itemPtr, int count, bool /*allowAutoEquip*/, bool resolve) { - Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::ContainerStoreIterator it = addImp(itemPtr, count, resolve); + itemPtr.getRefData().setLuaScripts(nullptr); // clear Lua scripts on the original (removed) item. // The copy of the original item we just made MWWorld::Ptr item = *it; + MWBase::Environment::get().getWorldModel()->registerPtr(item); /* Start of tes3mp addition @@ -352,7 +374,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr */ // we may have copied an item from the world, so reset a few things first - item.getRefData().setBaseNode(nullptr); // Especially important, otherwise scripts on the item could think that it's actually in a cell + item.getRefData().setBaseNode( + nullptr); // Especially important, otherwise scripts on the item could think that it's actually in a cell ESM::Position pos; pos.rot[0] = 0; pos.rot[1] = 0; @@ -363,19 +386,15 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr item.getCellRef().setPosition(pos); // We do not need to store owners for items in container stores - we do not use it anyway. - item.getCellRef().setOwner(""); + item.getCellRef().setOwner(ESM::RefId()); item.getCellRef().resetGlobalVariable(); - item.getCellRef().setFaction(""); + item.getCellRef().setFaction(ESM::RefId()); item.getCellRef().setFactionRank(-2); - // must reset the RefNum on the copied item, so that the RefNum on the original item stays unique - // maybe we should do this in the copy constructor instead? - item.getCellRef().unsetRefNum(); // destroy link to content file - - std::string script = item.getClass().getScript(item); + const ESM::RefId& script = item.getClass().getScript(item); if (!script.empty()) { - if (actorPtr == player) + if (mActor == player) { // Items in player's inventory have cell set to 0, so their scripts will never be removed item.mCell = nullptr; @@ -384,7 +403,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr { // Set mCell to the cell of the container/actor, so that the scripts are removed properly when // the cell of the container/actor goes inactive - item.mCell = actorPtr.getCell(); + if (!mPtr.isEmpty()) + item.mCell = mPtr.getCell(); + else if (!mActor.isEmpty()) + item.mCell = mActor.getCell(); } item.mContainerStore = this; @@ -393,16 +415,21 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr // Set OnPCAdd special variable, if it is declared // Make sure to do this *after* we have added the script to LocalScripts - if (actorPtr == player) + if (mActor == player) item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1); } +<<<<<<< HEAD /* Start of tes3mp change (major) Only fire inventory events for actors in loaded cells to avoid crashes */ if (mListener && !actorPtr.getClass().hasInventoryStore(actorPtr) && MWBase::Environment::get().getWorld()->isCellActive(*actorPtr.getCell()->getCell())) +======= + // we should not fire event for InventoryStore yet - it has some custom logic + if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor))) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mListener->itemAdded(item, count); /* End of tes3mp change (major) @@ -411,24 +438,24 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr return it; } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count, bool markModified) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp(const Ptr& ptr, int count, bool markModified) { - if(markModified) + if (markModified) resolve(); int type = getType(ptr); - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 - // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for detecting player gold) - if(ptr.getClass().isGold(ptr)) + // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for + // detecting player gold) + if (ptr.getClass().isGold(ptr)) { int realCount = count * ptr.getClass().getValue(ptr); - for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) + for (MWWorld::ContainerStoreIterator iter(begin(type)); iter != end(); ++iter) { - if (Misc::StringUtils::ciEqual((*iter).getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) + if (iter->getCellRef().getRefId() == MWWorld::ContainerStore::sGoldId) { iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); flagAsModified(); @@ -441,8 +468,13 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, } // determine whether to stack or not - for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) + for (MWWorld::ContainerStoreIterator iter(begin(type)); iter != end(); ++iter) { + // Don't stack with equipped items + if (!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor)) + if (mActor.getClass().getInventoryStore(mActor).isEquipped(*iter)) + continue; + if (stacks(*iter, ptr)) { // stack @@ -456,24 +488,60 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, return addNewStack(ptr, count); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const ConstPtr& ptr, int count) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack(const ConstPtr& ptr, int count) { ContainerStoreIterator it = begin(); switch (getType(ptr)) { - case Type_Potion: potions.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --potions.mList.end()); break; - case Type_Apparatus: appas.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --appas.mList.end()); break; - case Type_Armor: armors.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --armors.mList.end()); break; - case Type_Book: books.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --books.mList.end()); break; - case Type_Clothing: clothes.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --clothes.mList.end()); break; - case Type_Ingredient: ingreds.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --ingreds.mList.end()); break; - case Type_Light: lights.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --lights.mList.end()); break; - case Type_Lockpick: lockpicks.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --lockpicks.mList.end()); break; - case Type_Miscellaneous: miscItems.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --miscItems.mList.end()); break; - case Type_Probe: probes.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --probes.mList.end()); break; - case Type_Repair: repairs.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --repairs.mList.end()); break; - case Type_Weapon: weapons.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --weapons.mList.end()); break; + case Type_Potion: + potions.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --potions.mList.end()); + break; + case Type_Apparatus: + appas.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --appas.mList.end()); + break; + case Type_Armor: + armors.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --armors.mList.end()); + break; + case Type_Book: + books.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --books.mList.end()); + break; + case Type_Clothing: + clothes.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --clothes.mList.end()); + break; + case Type_Ingredient: + ingreds.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --ingreds.mList.end()); + break; + case Type_Light: + lights.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --lights.mList.end()); + break; + case Type_Lockpick: + lockpicks.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --lockpicks.mList.end()); + break; + case Type_Miscellaneous: + miscItems.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --miscItems.mList.end()); + break; + case Type_Probe: + probes.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --probes.mList.end()); + break; + case Type_Repair: + repairs.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --repairs.mList.end()); + break; + case Type_Weapon: + weapons.mList.push_back(*ptr.get()); + it = ContainerStoreIterator(this, --weapons.mList.end()); + break; } it->getRefData().setCount(count); @@ -505,32 +573,34 @@ void MWWorld::ContainerStore::updateRechargingItems() mRechargingItems.clear(); for (ContainerStoreIterator it = begin(); it != end(); ++it) { - const std::string& enchantmentId = it->getClass().getEnchantment(*it); + const auto& enchantmentId = it->getClass().getEnchantment(*it); if (!enchantmentId.empty()) { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentId); + const ESM::Enchantment* enchantment + = MWBase::Environment::get().getESMStore()->get().search(enchantmentId); if (!enchantment) { - Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId(); + Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " + << it->getCellRef().getRefId(); continue; } if (enchantment->mData.mType == ESM::Enchantment::WhenUsed - || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - mRechargingItems.emplace_back(it, static_cast(enchantment->mData.mCharge)); + || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + mRechargingItems.emplace_back(it, static_cast(MWMechanics::getEnchantmentCharge(*enchantment))); } } } -int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor, bool equipReplacement, bool resolveFirst) +int MWWorld::ContainerStore::remove(const ESM::RefId& itemId, int count, bool equipReplacement, bool resolveFirst) { - if(resolveFirst) + if (resolveFirst) resolve(); int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) - toRemove -= remove(*iter, toRemove, actor, equipReplacement, resolveFirst); + if (iter->getCellRef().getRefId() == itemId) + toRemove -= remove(*iter, toRemove, equipReplacement, resolveFirst); flagAsModified(); @@ -549,10 +619,10 @@ bool MWWorld::ContainerStore::hasVisibleItems() const return false; } -int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement, bool resolveFirst) +int MWWorld::ContainerStore::remove(const Ptr& item, int count, bool equipReplacement, bool resolveFirst) { assert(this == item.getContainerStore()); - if(resolveFirst) + if (resolveFirst) resolve(); /* @@ -590,6 +660,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor flagAsModified(); // we should not fire event for InventoryStore yet - it has some custom logic +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -597,6 +668,9 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor Only fire inventory events for actors in loaded cells to avoid crashes */ if (mListener && !actor.getClass().hasInventoryStore(actor) && MWBase::Environment::get().getWorld()->isCellActive(*actor.getCell()->getCell())) +======= + if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor))) +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 mListener->itemRemoved(item, count - toRemove); /* End of tes3mp change (major) @@ -606,47 +680,46 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor return count - toRemove; } -void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed) +void MWWorld::ContainerStore::fill(const ESM::InventoryList& items, const ESM::RefId& owner, Misc::Rng::Generator& prng) { for (const ESM::ContItem& iter : items.mList) { - std::string id = Misc::StringUtils::lowerCase(iter.mItem); - addInitialItem(id, owner, iter.mCount, &seed); + addInitialItem(iter.mItem, owner, iter.mCount, &prng); } flagAsModified(); mResolved = true; } -void MWWorld::ContainerStore::fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed) +void MWWorld::ContainerStore::fillNonRandom(const ESM::InventoryList& items, const ESM::RefId& owner, unsigned int seed) { mSeed = seed; for (const ESM::ContItem& iter : items.mList) { - std::string id = Misc::StringUtils::lowerCase(iter.mItem); - addInitialItem(id, owner, iter.mCount, nullptr); + addInitialItem(iter.mItem, owner, iter.mCount, nullptr); } flagAsModified(); mResolved = false; } -void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, - Misc::Rng::Seed* seed, bool topLevel) +void MWWorld::ContainerStore::addInitialItem( + const ESM::RefId& id, const ESM::RefId& owner, int count, Misc::Rng::Generator* prng, bool topLevel) { - if (count == 0) return; //Don't restock with nothing. + if (count == 0) + return; // Don't restock with nothing. try { - ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); + ManualRef ref(*MWBase::Environment::get().getESMStore(), id, count); if (ref.getPtr().getClass().getScript(ref.getPtr()).empty()) { - addInitialItemImp(ref.getPtr(), owner, count, seed, topLevel); + addInitialItemImp(ref.getPtr(), owner, count, prng, topLevel); } else { // Adding just one item per time to make sure there isn't a stack of scripted items for (int i = 0; i < std::abs(count); i++) - addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, seed, topLevel); + addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, prng, topLevel); } } catch (const std::exception& e) @@ -655,40 +728,40 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: } } -void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, int count, - Misc::Rng::Seed* seed, bool topLevel) +void MWWorld::ContainerStore::addInitialItemImp( + const MWWorld::Ptr& ptr, const ESM::RefId& owner, int count, Misc::Rng::Generator* prng, bool topLevel) { - if (ptr.getTypeName()==typeid (ESM::ItemLevList).name()) + if (ptr.getType() == ESM::ItemLevList::sRecordId) { - if(!seed) + if (!prng) return; const ESM::ItemLevList* levItemList = ptr.get()->mBase; if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { - for (int i=0; i 0 ? 1 : -1, seed, true); + for (int i = 0; i < std::abs(count); ++i) + addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, prng, true); return; } else { - std::string itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false, *seed); + const auto& itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false, *prng); if (itemId.empty()) return; - addInitialItem(itemId, owner, count, seed, false); + addInitialItem(itemId, owner, count, prng, false); } } else { ptr.getCellRef().setOwner(owner); - addImp (ptr, count, false); + addImp(ptr, count, false); } } void MWWorld::ContainerStore::clear() { for (auto&& iter : *this) - iter.getRefData().setCount (0); + iter.getRefData().setCount(0); flagAsModified(); mModified = true; @@ -721,12 +794,12 @@ void MWWorld::ContainerStore::setResolved(bool state) void MWWorld::ContainerStore::resolve() { - if(!mResolved && !mPtr.isEmpty()) + if (!mResolved && !mPtr.isEmpty()) { - for(const auto&& ptr : *this) + for (const auto&& ptr : *this) ptr.getRefData().setCount(0); - Misc::Rng::Seed seed{mSeed}; - fill(mPtr.get()->mBase->mInventory, "", seed); + Misc::Rng::Generator prng{ mSeed }; + fill(mPtr.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, mPtr.mCell); } mModified = true; @@ -734,23 +807,23 @@ void MWWorld::ContainerStore::resolve() MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() { - if(mModified) + if (mModified) return {}; std::shared_ptr listener = mResolutionListener.lock(); - if(!listener) + if (!listener) { listener = std::make_shared(*this); mResolutionListener = listener; } - if(!mResolved && !mPtr.isEmpty()) + if (!mResolved && !mPtr.isEmpty()) { - for(const auto&& ptr : *this) + for (const auto&& ptr : *this) ptr.getRefData().setCount(0); - Misc::Rng::Seed seed{mSeed}; - fill(mPtr.get()->mBase->mInventory, "", seed); + Misc::Rng::Generator prng{ mSeed }; + fill(mPtr.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, mPtr.mCell); } - return {listener}; + return { listener }; } void MWWorld::ContainerStore::unresolve() @@ -760,9 +833,9 @@ void MWWorld::ContainerStore::unresolve() if (mResolved && !mPtr.isEmpty()) { - for(const auto&& ptr : *this) + for (const auto&& ptr : *this) ptr.getRefData().setCount(0); - fillNonRandom(mPtr.get()->mBase->mInventory, "", mSeed); + fillNonRandom(mPtr.get()->mBase->mInventory, ESM::RefId(), mSeed); addScripts(*this, mPtr.mCell); mResolved = false; } @@ -774,18 +847,18 @@ float MWWorld::ContainerStore::getWeight() const { mCachedWeight = 0; - mCachedWeight += getTotalWeight (potions); - mCachedWeight += getTotalWeight (appas); - mCachedWeight += getTotalWeight (armors); - mCachedWeight += getTotalWeight (books); - mCachedWeight += getTotalWeight (clothes); - mCachedWeight += getTotalWeight (ingreds); - mCachedWeight += getTotalWeight (lights); - mCachedWeight += getTotalWeight (lockpicks); - mCachedWeight += getTotalWeight (miscItems); - mCachedWeight += getTotalWeight (probes); - mCachedWeight += getTotalWeight (repairs); - mCachedWeight += getTotalWeight (weapons); + mCachedWeight += getTotalWeight(potions); + mCachedWeight += getTotalWeight(appas); + mCachedWeight += getTotalWeight(armors); + mCachedWeight += getTotalWeight(books); + mCachedWeight += getTotalWeight(clothes); + mCachedWeight += getTotalWeight(ingreds); + mCachedWeight += getTotalWeight(lights); + mCachedWeight += getTotalWeight(lockpicks); + mCachedWeight += getTotalWeight(miscItems); + mCachedWeight += getTotalWeight(probes); + mCachedWeight += getTotalWeight(repairs); + mCachedWeight += getTotalWeight(weapons); mWeightUpToDate = true; } @@ -793,65 +866,63 @@ float MWWorld::ContainerStore::getWeight() const return mCachedWeight; } -int MWWorld::ContainerStore::getType (const ConstPtr& ptr) +int MWWorld::ContainerStore::getType(const ConstPtr& ptr) { if (ptr.isEmpty()) - throw std::runtime_error ("can't put a non-existent object into a container"); + throw std::runtime_error("can't put a non-existent object into a container"); - if (ptr.getTypeName()==typeid (ESM::Potion).name()) + if (ptr.getType() == ESM::Potion::sRecordId) return Type_Potion; - if (ptr.getTypeName()==typeid (ESM::Apparatus).name()) + if (ptr.getType() == ESM::Apparatus::sRecordId) return Type_Apparatus; - if (ptr.getTypeName()==typeid (ESM::Armor).name()) + if (ptr.getType() == ESM::Armor::sRecordId) return Type_Armor; - if (ptr.getTypeName()==typeid (ESM::Book).name()) + if (ptr.getType() == ESM::Book::sRecordId) return Type_Book; - if (ptr.getTypeName()==typeid (ESM::Clothing).name()) + if (ptr.getType() == ESM::Clothing::sRecordId) return Type_Clothing; - if (ptr.getTypeName()==typeid (ESM::Ingredient).name()) + if (ptr.getType() == ESM::Ingredient::sRecordId) return Type_Ingredient; - if (ptr.getTypeName()==typeid (ESM::Light).name()) + if (ptr.getType() == ESM::Light::sRecordId) return Type_Light; - if (ptr.getTypeName()==typeid (ESM::Lockpick).name()) + if (ptr.getType() == ESM::Lockpick::sRecordId) return Type_Lockpick; - if (ptr.getTypeName()==typeid (ESM::Miscellaneous).name()) + if (ptr.getType() == ESM::Miscellaneous::sRecordId) return Type_Miscellaneous; - if (ptr.getTypeName()==typeid (ESM::Probe).name()) + if (ptr.getType() == ESM::Probe::sRecordId) return Type_Probe; - if (ptr.getTypeName()==typeid (ESM::Repair).name()) + if (ptr.getType() == ESM::Repair::sRecordId) return Type_Repair; - if (ptr.getTypeName()==typeid (ESM::Weapon).name()) + if (ptr.getType() == ESM::Weapon::sRecordId) return Type_Weapon; - throw std::runtime_error ( - "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " can not be placed into a container"); + throw std::runtime_error("Object " + ptr.getCellRef().getRefId().toDebugString() + " of type " + + std::string(ptr.getTypeDescription()) + " can not be placed into a container"); } -MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) +MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const ESM::RefId& id) { MWWorld::Ptr item; int itemHealth = 1; for (auto&& iter : *this) { int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1; - if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) + if (iter.getCellRef().getRefId() == id) { // Prefer the stack with the lowest remaining uses // Try to get item with zero durability only if there are no other items found - if (item.isEmpty() || - (iterHealth > 0 && iterHealth < itemHealth) || - (itemHealth <= 0 && iterHealth > 0)) + if (item.isEmpty() || (iterHealth > 0 && iterHealth < itemHealth) || (itemHealth <= 0 && iterHealth > 0)) { item = iter; itemHealth = iterHealth; @@ -862,77 +933,77 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) return item; } -MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) +MWWorld::Ptr MWWorld::ContainerStore::search(const ESM::RefId& id) { resolve(); { - Ptr ptr = searchId (potions, id, this); + Ptr ptr = searchId(potions, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (appas, id, this); + Ptr ptr = searchId(appas, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (armors, id, this); + Ptr ptr = searchId(armors, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (books, id, this); + Ptr ptr = searchId(books, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (clothes, id, this); + Ptr ptr = searchId(clothes, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (ingreds, id, this); + Ptr ptr = searchId(ingreds, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (lights, id, this); + Ptr ptr = searchId(lights, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (lockpicks, id, this); + Ptr ptr = searchId(lockpicks, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (miscItems, id, this); + Ptr ptr = searchId(miscItems, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (probes, id, this); + Ptr ptr = searchId(probes, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (repairs, id, this); + Ptr ptr = searchId(repairs, id, this); if (!ptr.isEmpty()) return ptr; } { - Ptr ptr = searchId (weapons, id, this); + Ptr ptr = searchId(weapons, id, this); if (!ptr.isEmpty()) return ptr; } @@ -943,7 +1014,7 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) int MWWorld::ContainerStore::addItems(int count1, int count2) { int sum = std::abs(count1) + std::abs(count2); - if(count1 < 0 || count2 < 0) + if (count1 < 0 || count2 < 0) return -sum; return sum; } @@ -951,31 +1022,31 @@ int MWWorld::ContainerStore::addItems(int count1, int count2) int MWWorld::ContainerStore::subtractItems(int count1, int count2) { int sum = std::abs(count1) - std::abs(count2); - if(count1 < 0 || count2 < 0) + if (count1 < 0 || count2 < 0) return -sum; return sum; } -void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const +void MWWorld::ContainerStore::writeState(ESM::InventoryState& state) const { state.mItems.clear(); int index = 0; - storeStates (potions, state, index); - storeStates (appas, state, index); - storeStates (armors, state, index, true); - storeStates (books, state, index, true); // not equipable as such, but for selectedEnchantItem - storeStates (clothes, state, index, true); - storeStates (ingreds, state, index); - storeStates (lockpicks, state, index, true); - storeStates (miscItems, state, index); - storeStates (probes, state, index, true); - storeStates (repairs, state, index); - storeStates (weapons, state, index, true); - storeStates (lights, state, index, true); + storeStates(potions, state, index); + storeStates(appas, state, index); + storeStates(armors, state, index, true); + storeStates(books, state, index, true); // not equipable as such, but for selectedEnchantItem + storeStates(clothes, state, index, true); + storeStates(ingreds, state, index); + storeStates(lockpicks, state, index, true); + storeStates(miscItems, state, index); + storeStates(probes, state, index, true); + storeStates(repairs, state, index); + storeStates(weapons, state, index, true); + storeStates(lights, state, index, true); } -void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) +void MWWorld::ContainerStore::readState(const ESM::InventoryState& inventory) { clear(); mModified = true; @@ -984,26 +1055,51 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) int index = 0; for (const ESM::ObjectState& state : inventory.mItems) { - int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID); + int type = MWBase::Environment::get().getESMStore()->find(state.mRef.mRefID); int thisIndex = index++; switch (type) { - case ESM::REC_ALCH: getState (potions, state); break; - case ESM::REC_APPA: getState (appas, state); break; - case ESM::REC_ARMO: readEquipmentState (getState (armors, state), thisIndex, inventory); break; - case ESM::REC_BOOK: readEquipmentState (getState (books, state), thisIndex, inventory); break; // not equipable as such, but for selectedEnchantItem - case ESM::REC_CLOT: readEquipmentState (getState (clothes, state), thisIndex, inventory); break; - case ESM::REC_INGR: getState (ingreds, state); break; - case ESM::REC_LOCK: readEquipmentState (getState (lockpicks, state), thisIndex, inventory); break; - case ESM::REC_MISC: getState (miscItems, state); break; - case ESM::REC_PROB: readEquipmentState (getState (probes, state), thisIndex, inventory); break; - case ESM::REC_REPA: getState (repairs, state); break; - case ESM::REC_WEAP: readEquipmentState (getState (weapons, state), thisIndex, inventory); break; - case ESM::REC_LIGH: readEquipmentState (getState (lights, state), thisIndex, inventory); break; + case ESM::REC_ALCH: + getState(potions, state); + break; + case ESM::REC_APPA: + getState(appas, state); + break; + case ESM::REC_ARMO: + readEquipmentState(getState(armors, state), thisIndex, inventory); + break; + case ESM::REC_BOOK: + readEquipmentState(getState(books, state), thisIndex, inventory); + break; // not equipable as such, but for selectedEnchantItem + case ESM::REC_CLOT: + readEquipmentState(getState(clothes, state), thisIndex, inventory); + break; + case ESM::REC_INGR: + getState(ingreds, state); + break; + case ESM::REC_LOCK: + readEquipmentState(getState(lockpicks, state), thisIndex, inventory); + break; + case ESM::REC_MISC: + getState(miscItems, state); + break; + case ESM::REC_PROB: + readEquipmentState(getState(probes, state), thisIndex, inventory); + break; + case ESM::REC_REPA: + getState(repairs, state); + break; + case ESM::REC_WEAP: + readEquipmentState(getState(weapons, state), thisIndex, inventory); + break; + case ESM::REC_LIGH: + readEquipmentState(getState(lights, state), thisIndex, inventory); + break; case 0: - Log(Debug::Warning) << "Dropping inventory reference to '" << state.mRef.mRefID << "' (object no longer exists)"; + Log(Debug::Warning) << "Dropping inventory reference to '" << state.mRef.mRefID + << "' (object no longer exists)"; break; default: Log(Debug::Warning) << "Warning: Invalid item type in inventory state, refid " << state.mRef.mRefID; @@ -1012,9 +1108,9 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) } } -template -template -void MWWorld::ContainerStoreIteratorBase::copy (const ContainerStoreIteratorBase& src) +template +template +void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src) { mType = src.mType; mMask = src.mMask; @@ -1023,51 +1119,77 @@ void MWWorld::ContainerStoreIteratorBase::copy (const ContainerStoreIte switch (src.mType) { - case MWWorld::ContainerStore::Type_Potion: mPotion = src.mPotion; break; - case MWWorld::ContainerStore::Type_Apparatus: mApparatus = src.mApparatus; break; - case MWWorld::ContainerStore::Type_Armor: mArmor = src.mArmor; break; - case MWWorld::ContainerStore::Type_Book: mBook = src.mBook; break; - case MWWorld::ContainerStore::Type_Clothing: mClothing = src.mClothing; break; - case MWWorld::ContainerStore::Type_Ingredient: mIngredient = src.mIngredient; break; - case MWWorld::ContainerStore::Type_Light: mLight = src.mLight; break; - case MWWorld::ContainerStore::Type_Lockpick: mLockpick = src.mLockpick; break; - case MWWorld::ContainerStore::Type_Miscellaneous: mMiscellaneous = src.mMiscellaneous; break; - case MWWorld::ContainerStore::Type_Probe: mProbe = src.mProbe; break; - case MWWorld::ContainerStore::Type_Repair: mRepair = src.mRepair; break; - case MWWorld::ContainerStore::Type_Weapon: mWeapon = src.mWeapon; break; - case -1: break; - default: assert(0); + case MWWorld::ContainerStore::Type_Potion: + mPotion = src.mPotion; + break; + case MWWorld::ContainerStore::Type_Apparatus: + mApparatus = src.mApparatus; + break; + case MWWorld::ContainerStore::Type_Armor: + mArmor = src.mArmor; + break; + case MWWorld::ContainerStore::Type_Book: + mBook = src.mBook; + break; + case MWWorld::ContainerStore::Type_Clothing: + mClothing = src.mClothing; + break; + case MWWorld::ContainerStore::Type_Ingredient: + mIngredient = src.mIngredient; + break; + case MWWorld::ContainerStore::Type_Light: + mLight = src.mLight; + break; + case MWWorld::ContainerStore::Type_Lockpick: + mLockpick = src.mLockpick; + break; + case MWWorld::ContainerStore::Type_Miscellaneous: + mMiscellaneous = src.mMiscellaneous; + break; + case MWWorld::ContainerStore::Type_Probe: + mProbe = src.mProbe; + break; + case MWWorld::ContainerStore::Type_Repair: + mRepair = src.mRepair; + break; + case MWWorld::ContainerStore::Type_Weapon: + mWeapon = src.mWeapon; + break; + case -1: + break; + default: + assert(0); } } -template +template void MWWorld::ContainerStoreIteratorBase::incType() { - if (mType==0) + if (mType == 0) mType = 1; - else if (mType!=-1) + else if (mType != -1) { mType <<= 1; - if (mType>ContainerStore::Type_Last) + if (mType > ContainerStore::Type_Last) mType = -1; } } -template +template void MWWorld::ContainerStoreIteratorBase::nextType() { - while (mType!=-1) + while (mType != -1) { incType(); - if ((mType & mMask) && mType>0) + if ((mType & mMask) && mType > 0) if (resetIterator()) break; } } -template +template bool MWWorld::ContainerStoreIteratorBase::resetIterator() { switch (mType) @@ -1075,68 +1197,68 @@ bool MWWorld::ContainerStoreIteratorBase::resetIterator() case ContainerStore::Type_Potion: mPotion = mContainer->potions.mList.begin(); - return mPotion!=mContainer->potions.mList.end(); + return mPotion != mContainer->potions.mList.end(); case ContainerStore::Type_Apparatus: mApparatus = mContainer->appas.mList.begin(); - return mApparatus!=mContainer->appas.mList.end(); + return mApparatus != mContainer->appas.mList.end(); case ContainerStore::Type_Armor: mArmor = mContainer->armors.mList.begin(); - return mArmor!=mContainer->armors.mList.end(); + return mArmor != mContainer->armors.mList.end(); case ContainerStore::Type_Book: mBook = mContainer->books.mList.begin(); - return mBook!=mContainer->books.mList.end(); + return mBook != mContainer->books.mList.end(); case ContainerStore::Type_Clothing: mClothing = mContainer->clothes.mList.begin(); - return mClothing!=mContainer->clothes.mList.end(); + return mClothing != mContainer->clothes.mList.end(); case ContainerStore::Type_Ingredient: mIngredient = mContainer->ingreds.mList.begin(); - return mIngredient!=mContainer->ingreds.mList.end(); + return mIngredient != mContainer->ingreds.mList.end(); case ContainerStore::Type_Light: mLight = mContainer->lights.mList.begin(); - return mLight!=mContainer->lights.mList.end(); + return mLight != mContainer->lights.mList.end(); case ContainerStore::Type_Lockpick: mLockpick = mContainer->lockpicks.mList.begin(); - return mLockpick!=mContainer->lockpicks.mList.end(); + return mLockpick != mContainer->lockpicks.mList.end(); case ContainerStore::Type_Miscellaneous: mMiscellaneous = mContainer->miscItems.mList.begin(); - return mMiscellaneous!=mContainer->miscItems.mList.end(); + return mMiscellaneous != mContainer->miscItems.mList.end(); case ContainerStore::Type_Probe: mProbe = mContainer->probes.mList.begin(); - return mProbe!=mContainer->probes.mList.end(); + return mProbe != mContainer->probes.mList.end(); case ContainerStore::Type_Repair: mRepair = mContainer->repairs.mList.begin(); - return mRepair!=mContainer->repairs.mList.end(); + return mRepair != mContainer->repairs.mList.end(); case ContainerStore::Type_Weapon: mWeapon = mContainer->weapons.mList.begin(); - return mWeapon!=mContainer->weapons.mList.end(); + return mWeapon != mContainer->weapons.mList.end(); } return false; } -template +template bool MWWorld::ContainerStoreIteratorBase::incIterator() { switch (mType) @@ -1144,267 +1266,388 @@ bool MWWorld::ContainerStoreIteratorBase::incIterator() case ContainerStore::Type_Potion: ++mPotion; - return mPotion==mContainer->potions.mList.end(); + return mPotion == mContainer->potions.mList.end(); case ContainerStore::Type_Apparatus: ++mApparatus; - return mApparatus==mContainer->appas.mList.end(); + return mApparatus == mContainer->appas.mList.end(); case ContainerStore::Type_Armor: ++mArmor; - return mArmor==mContainer->armors.mList.end(); + return mArmor == mContainer->armors.mList.end(); case ContainerStore::Type_Book: ++mBook; - return mBook==mContainer->books.mList.end(); + return mBook == mContainer->books.mList.end(); case ContainerStore::Type_Clothing: ++mClothing; - return mClothing==mContainer->clothes.mList.end(); + return mClothing == mContainer->clothes.mList.end(); case ContainerStore::Type_Ingredient: ++mIngredient; - return mIngredient==mContainer->ingreds.mList.end(); + return mIngredient == mContainer->ingreds.mList.end(); case ContainerStore::Type_Light: ++mLight; - return mLight==mContainer->lights.mList.end(); + return mLight == mContainer->lights.mList.end(); case ContainerStore::Type_Lockpick: ++mLockpick; - return mLockpick==mContainer->lockpicks.mList.end(); + return mLockpick == mContainer->lockpicks.mList.end(); case ContainerStore::Type_Miscellaneous: ++mMiscellaneous; - return mMiscellaneous==mContainer->miscItems.mList.end(); + return mMiscellaneous == mContainer->miscItems.mList.end(); case ContainerStore::Type_Probe: ++mProbe; - return mProbe==mContainer->probes.mList.end(); + return mProbe == mContainer->probes.mList.end(); case ContainerStore::Type_Repair: ++mRepair; - return mRepair==mContainer->repairs.mList.end(); + return mRepair == mContainer->repairs.mList.end(); case ContainerStore::Type_Weapon: ++mWeapon; - return mWeapon==mContainer->weapons.mList.end(); + return mWeapon == mContainer->weapons.mList.end(); } return true; } - -template -template -bool MWWorld::ContainerStoreIteratorBase::isEqual (const ContainerStoreIteratorBase& other) const +template +template +bool MWWorld::ContainerStoreIteratorBase::isEqual(const ContainerStoreIteratorBase& other) const { - if (mContainer!=other.mContainer) + if (mContainer != other.mContainer) return false; - if (mType!=other.mType) + if (mType != other.mType) return false; switch (mType) { - case ContainerStore::Type_Potion: return mPotion==other.mPotion; - case ContainerStore::Type_Apparatus: return mApparatus==other.mApparatus; - case ContainerStore::Type_Armor: return mArmor==other.mArmor; - case ContainerStore::Type_Book: return mBook==other.mBook; - case ContainerStore::Type_Clothing: return mClothing==other.mClothing; - case ContainerStore::Type_Ingredient: return mIngredient==other.mIngredient; - case ContainerStore::Type_Light: return mLight==other.mLight; - case ContainerStore::Type_Lockpick: return mLockpick==other.mLockpick; - case ContainerStore::Type_Miscellaneous: return mMiscellaneous==other.mMiscellaneous; - case ContainerStore::Type_Probe: return mProbe==other.mProbe; - case ContainerStore::Type_Repair: return mRepair==other.mRepair; - case ContainerStore::Type_Weapon: return mWeapon==other.mWeapon; - case -1: return true; + case ContainerStore::Type_Potion: + return mPotion == other.mPotion; + case ContainerStore::Type_Apparatus: + return mApparatus == other.mApparatus; + case ContainerStore::Type_Armor: + return mArmor == other.mArmor; + case ContainerStore::Type_Book: + return mBook == other.mBook; + case ContainerStore::Type_Clothing: + return mClothing == other.mClothing; + case ContainerStore::Type_Ingredient: + return mIngredient == other.mIngredient; + case ContainerStore::Type_Light: + return mLight == other.mLight; + case ContainerStore::Type_Lockpick: + return mLockpick == other.mLockpick; + case ContainerStore::Type_Miscellaneous: + return mMiscellaneous == other.mMiscellaneous; + case ContainerStore::Type_Probe: + return mProbe == other.mProbe; + case ContainerStore::Type_Repair: + return mRepair == other.mRepair; + case ContainerStore::Type_Weapon: + return mWeapon == other.mWeapon; + case -1: + return true; } - return false; + return false; } -template -PtrType *MWWorld::ContainerStoreIteratorBase::operator->() const +template +PtrType* MWWorld::ContainerStoreIteratorBase::operator->() const { mPtr = **this; return &mPtr; } -template +template PtrType MWWorld::ContainerStoreIteratorBase::operator*() const { PtrType ptr; switch (mType) { - case ContainerStore::Type_Potion: ptr = PtrType (&*mPotion, nullptr); break; - case ContainerStore::Type_Apparatus: ptr = PtrType (&*mApparatus, nullptr); break; - case ContainerStore::Type_Armor: ptr = PtrType (&*mArmor, nullptr); break; - case ContainerStore::Type_Book: ptr = PtrType (&*mBook, nullptr); break; - case ContainerStore::Type_Clothing: ptr = PtrType (&*mClothing, nullptr); break; - case ContainerStore::Type_Ingredient: ptr = PtrType (&*mIngredient, nullptr); break; - case ContainerStore::Type_Light: ptr = PtrType (&*mLight, nullptr); break; - case ContainerStore::Type_Lockpick: ptr = PtrType (&*mLockpick, nullptr); break; - case ContainerStore::Type_Miscellaneous: ptr = PtrType (&*mMiscellaneous, nullptr); break; - case ContainerStore::Type_Probe: ptr = PtrType (&*mProbe, nullptr); break; - case ContainerStore::Type_Repair: ptr = PtrType (&*mRepair, nullptr); break; - case ContainerStore::Type_Weapon: ptr = PtrType (&*mWeapon, nullptr); break; + case ContainerStore::Type_Potion: + ptr = PtrType(&*mPotion, nullptr); + break; + case ContainerStore::Type_Apparatus: + ptr = PtrType(&*mApparatus, nullptr); + break; + case ContainerStore::Type_Armor: + ptr = PtrType(&*mArmor, nullptr); + break; + case ContainerStore::Type_Book: + ptr = PtrType(&*mBook, nullptr); + break; + case ContainerStore::Type_Clothing: + ptr = PtrType(&*mClothing, nullptr); + break; + case ContainerStore::Type_Ingredient: + ptr = PtrType(&*mIngredient, nullptr); + break; + case ContainerStore::Type_Light: + ptr = PtrType(&*mLight, nullptr); + break; + case ContainerStore::Type_Lockpick: + ptr = PtrType(&*mLockpick, nullptr); + break; + case ContainerStore::Type_Miscellaneous: + ptr = PtrType(&*mMiscellaneous, nullptr); + break; + case ContainerStore::Type_Probe: + ptr = PtrType(&*mProbe, nullptr); + break; + case ContainerStore::Type_Repair: + ptr = PtrType(&*mRepair, nullptr); + break; + case ContainerStore::Type_Weapon: + ptr = PtrType(&*mWeapon, nullptr); + break; } if (ptr.isEmpty()) - throw std::runtime_error ("invalid iterator"); + throw std::runtime_error("invalid iterator"); - ptr.setContainerStore (mContainer); + ptr.setContainerStore(mContainer); return ptr; } -template +template MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBase::operator++() { do { if (incIterator()) nextType(); - } - while (mType!=-1 && !(**this).getRefData().getCount()); + } while (mType != -1 && !(**this).getRefData().getCount()); return *this; } -template -MWWorld::ContainerStoreIteratorBase MWWorld::ContainerStoreIteratorBase::operator++ (int) +template +MWWorld::ContainerStoreIteratorBase MWWorld::ContainerStoreIteratorBase::operator++(int) { - ContainerStoreIteratorBase iter (*this); + ContainerStoreIteratorBase iter(*this); ++*this; return iter; } -template -MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBase::operator= (const ContainerStoreIteratorBase& rhs) +template +MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBase::operator=( + const ContainerStoreIteratorBase& rhs) { - if (this!=&rhs) + if (this != &rhs) { copy(rhs); } return *this; } -template +template int MWWorld::ContainerStoreIteratorBase::getType() const { return mType; } -template -const MWWorld::ContainerStore *MWWorld::ContainerStoreIteratorBase::getContainerStore() const +template +const MWWorld::ContainerStore* MWWorld::ContainerStoreIteratorBase::getContainerStore() const { return mContainer; } -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container) -: mType (-1), mMask (0), mContainer (container) -{} +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase(ContainerStoreType container) + : mType(-1) + , mMask(0) + , mContainer(container) +{ +} -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (int mask, ContainerStoreType container) -: mType (0), mMask (mask), mContainer (container) +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase(int mask, ContainerStoreType container) + : mType(0) + , mMask(mask) + , mContainer(container) { nextType(); - if (mType==-1 || (**this).getRefData().getCount()) + if (mType == -1 || (**this).getRefData().getCount()) return; ++*this; } -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Potion), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mPotion(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Apparatus), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mApparatus(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Armor), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mArmor(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Book), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mBook(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Clothing), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mClothing(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Ingredient), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mIngredient(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Light), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mLight(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Lockpick), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mLockpick(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Miscellaneous), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mMiscellaneous(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Probe), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mProbe(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Repair), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mRepair(iterator){} - -template -MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type iterator) - : mType(MWWorld::ContainerStore::Type_Weapon), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mWeapon(iterator){} - - -template -bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right) +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Potion) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mPotion(iterator) { - return left.isEqual (right); } -template -bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right) +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Apparatus) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mApparatus(iterator) { - return !(left==right); +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Armor) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mArmor(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Book) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mBook(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Clothing) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mClothing(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Ingredient) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mIngredient(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Light) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mLight(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Lockpick) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mLockpick(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Miscellaneous) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mMiscellaneous(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Probe) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mProbe(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Repair) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mRepair(iterator) +{ +} + +template +MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( + ContainerStoreType container, typename Iterator::type iterator) + : mType(MWWorld::ContainerStore::Type_Weapon) + , mMask(MWWorld::ContainerStore::Type_All) + , mContainer(container) + , mWeapon(iterator) +{ +} + +template +bool MWWorld::operator==(const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right) +{ + return left.isEqual(right); +} + +template +bool MWWorld::operator!=(const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right) +{ + return !(left == right); } template class MWWorld::ContainerStoreIteratorBase; template class MWWorld::ContainerStoreIteratorBase; -template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); -template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); -template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); -template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); -template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); -template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); -template bool MWWorld::operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); -template bool MWWorld::operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); +template bool MWWorld::operator==( + const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); +template bool MWWorld::operator!=( + const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); +template bool MWWorld::operator==( + const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); +template bool MWWorld::operator!=( + const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); +template bool MWWorld::operator==( + const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); +template bool MWWorld::operator!=( + const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); +template bool MWWorld::operator==( + const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); +template bool MWWorld::operator!=( + const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src); template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src); -template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src); +template void MWWorld::ContainerStoreIteratorBase::copy( + const ContainerStoreIteratorBase& src); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 10ba78852..281edbafe 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -6,23 +6,23 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include "ptr.hpp" #include "cellreflist.hpp" +#include "ptr.hpp" namespace ESM { @@ -39,7 +39,7 @@ namespace MWWorld { class ContainerStore; - template + template class ContainerStoreIteratorBase; typedef ContainerStoreIteratorBase ContainerStoreIterator; @@ -47,207 +47,231 @@ namespace MWWorld class ResolutionListener { - ContainerStore& mStore; - public: - ResolutionListener(ContainerStore& store) : mStore(store) {} - ~ResolutionListener(); + ContainerStore& mStore; + + public: + ResolutionListener(ContainerStore& store) + : mStore(store) + { + } + ~ResolutionListener(); }; class ResolutionHandle { - std::shared_ptr mListener; - public: - ResolutionHandle(std::shared_ptr listener) : mListener(listener) {} - ResolutionHandle() {} + std::shared_ptr mListener; + + public: + ResolutionHandle(std::shared_ptr listener) + : mListener(listener) + { + } + ResolutionHandle() = default; }; - + class ContainerStoreListener { - public: - virtual void itemAdded(const ConstPtr& item, int count) {} - virtual void itemRemoved(const ConstPtr& item, int count) {} - virtual ~ContainerStoreListener() = default; + public: + virtual void itemAdded(const ConstPtr& item, int count) {} + virtual void itemRemoved(const ConstPtr& item, int count) {} + virtual ~ContainerStoreListener() = default; }; class ContainerStore { - public: + public: + static constexpr int Type_Potion = 0x0001; + static constexpr int Type_Apparatus = 0x0002; + static constexpr int Type_Armor = 0x0004; + static constexpr int Type_Book = 0x0008; + static constexpr int Type_Clothing = 0x0010; + static constexpr int Type_Ingredient = 0x0020; + static constexpr int Type_Light = 0x0040; + static constexpr int Type_Lockpick = 0x0080; + static constexpr int Type_Miscellaneous = 0x0100; + static constexpr int Type_Probe = 0x0200; + static constexpr int Type_Repair = 0x0400; + static constexpr int Type_Weapon = 0x0800; - static constexpr int Type_Potion = 0x0001; - static constexpr int Type_Apparatus = 0x0002; - static constexpr int Type_Armor = 0x0004; - static constexpr int Type_Book = 0x0008; - static constexpr int Type_Clothing = 0x0010; - static constexpr int Type_Ingredient = 0x0020; - static constexpr int Type_Light = 0x0040; - static constexpr int Type_Lockpick = 0x0080; - static constexpr int Type_Miscellaneous = 0x0100; - static constexpr int Type_Probe = 0x0200; - static constexpr int Type_Repair = 0x0400; - static constexpr int Type_Weapon = 0x0800; + static constexpr int Type_Last = Type_Weapon; - static constexpr int Type_Last = Type_Weapon; + static constexpr int Type_All = 0xffff; - static constexpr int Type_All = 0xffff; + static const ESM::RefId sGoldId; - static const std::string sGoldId; + protected: + ContainerStoreListener* mListener; - protected: - ContainerStoreListener* mListener; + // Used in clone() to unset refnums of copies. + // (RefNum should be unique, copy can not have the same RefNum). + void clearRefNums(); - // (item, max charge) - typedef std::vector > TRechargingItems; - TRechargingItems mRechargingItems; + // (item, max charge) + typedef std::vector> TRechargingItems; + TRechargingItems mRechargingItems; - bool mRechargingItemsUpToDate; + bool mRechargingItemsUpToDate; - private: + // Non-empty only if is InventoryStore. + // The actor whose inventory it is. + // TODO: Consider merging mActor and mPtr. + MWWorld::Ptr mActor; - MWWorld::CellRefList potions; - MWWorld::CellRefList appas; - MWWorld::CellRefList armors; - MWWorld::CellRefList books; - MWWorld::CellRefList clothes; - MWWorld::CellRefList ingreds; - MWWorld::CellRefList lights; - MWWorld::CellRefList lockpicks; - MWWorld::CellRefList miscItems; - MWWorld::CellRefList probes; - MWWorld::CellRefList repairs; - MWWorld::CellRefList weapons; + private: + MWWorld::CellRefList potions; + MWWorld::CellRefList appas; + MWWorld::CellRefList armors; + MWWorld::CellRefList books; + MWWorld::CellRefList clothes; + MWWorld::CellRefList ingreds; + MWWorld::CellRefList lights; + MWWorld::CellRefList lockpicks; + MWWorld::CellRefList miscItems; + MWWorld::CellRefList probes; + MWWorld::CellRefList repairs; + MWWorld::CellRefList weapons; - mutable float mCachedWeight; - mutable bool mWeightUpToDate; + mutable float mCachedWeight; + mutable bool mWeightUpToDate; - bool mModified; - bool mResolved; - unsigned int mSeed; - MWWorld::Ptr mPtr; - std::weak_ptr mResolutionListener; + bool mModified; + bool mResolved; + unsigned int mSeed; + MWWorld::Ptr mPtr; // Container that contains this store. Set in MWClass::Container::getContainerStore + std::weak_ptr mResolutionListener; - ContainerStoreIterator addImp (const Ptr& ptr, int count, bool markModified = true); - void addInitialItem (const std::string& id, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true); - void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true); + ContainerStoreIterator addImp(const Ptr& ptr, int count, bool markModified = true); + void addInitialItem( + const ESM::RefId& id, const ESM::RefId& owner, int count, Misc::Rng::Generator* prng, bool topLevel = true); + void addInitialItemImp(const MWWorld::Ptr& ptr, const ESM::RefId& owner, int count, Misc::Rng::Generator* prng, + bool topLevel = true); - template - ContainerStoreIterator getState (CellRefList& collection, - const ESM::ObjectState& state); + template + ContainerStoreIterator getState(CellRefList& collection, const ESM::ObjectState& state); - template - void storeState (const LiveCellRef& ref, ESM::ObjectState& state) const; + template + void storeState(const LiveCellRef& ref, ESM::ObjectState& state) const; - template - void storeStates (const CellRefList& collection, - ESM::InventoryState& inventory, int& index, - bool equipable = false) const; + template + void storeStates( + const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable = false) const; - void updateRechargingItems(); + void updateRechargingItems(); - virtual void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const; + virtual void storeEquipmentState( + const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const; - virtual void readEquipmentState (const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory); + virtual void readEquipmentState( + const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory); - public: + public: + ContainerStore(); - ContainerStore(); + virtual ~ContainerStore(); - virtual ~ContainerStore(); + virtual std::unique_ptr clone() + { + auto res = std::make_unique(*this); + res->clearRefNums(); + return res; + } - virtual std::unique_ptr clone() { return std::make_unique(*this); } + ConstContainerStoreIterator cbegin(int mask = Type_All) const; + ConstContainerStoreIterator cend() const; + ConstContainerStoreIterator begin(int mask = Type_All) const; + ConstContainerStoreIterator end() const; - ConstContainerStoreIterator cbegin (int mask = Type_All) const; - ConstContainerStoreIterator cend() const; - ConstContainerStoreIterator begin (int mask = Type_All) const; - ConstContainerStoreIterator end() const; - - ContainerStoreIterator begin (int mask = Type_All); - ContainerStoreIterator end(); + ContainerStoreIterator begin(int mask = Type_All); + ContainerStoreIterator end(); - bool hasVisibleItems() const; + bool hasVisibleItems() const; - virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true); - ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) - /// - /// \note The item pointed to is not required to exist beyond this function call. - /// - /// \attention Do not add items to an existing stack by increasing the count instead of - /// calling this function! - /// - /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. + virtual ContainerStoreIterator add( + const Ptr& itemPtr, int count, bool allowAutoEquip = true, bool resolve = true); + ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) + /// + /// \note The item pointed to is not required to exist beyond this function call. + /// + /// \attention Do not add items to an existing stack by increasing the count instead of + /// calling this function! + /// + /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to + /// the newly inserted item. - ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr); - ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) + ContainerStoreIterator add(const ESM::RefId& id, int count); + ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) - int remove(const std::string& itemId, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true); - ///< Remove \a count item(s) designated by \a itemId from this container. - /// - /// @return the number of items actually removed + int remove(const ESM::RefId& itemId, int count, bool equipReplacement = 0, bool resolve = true); + ///< Remove \a count item(s) designated by \a itemId from this container. + /// + /// @return the number of items actually removed - virtual int remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true); - ///< Remove \a count item(s) designated by \a item from this inventory. - /// - /// @return the number of items actually removed + virtual int remove(const Ptr& item, int count, bool equipReplacement = 0, bool resolve = true); + ///< Remove \a count item(s) designated by \a item from this inventory. + /// + /// @return the number of items actually removed - void rechargeItems (float duration); - ///< Restore charge on enchanted items. Note this should only be done for the player. + void rechargeItems(float duration); + ///< Restore charge on enchanted items. Note this should only be done for the player. - ContainerStoreIterator unstack (const Ptr& ptr, const Ptr& container, int count = 1); - ///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added with (origCount-count). - /// - /// @return an iterator to the new stack, or end() if no new stack was created. + ContainerStoreIterator unstack(const Ptr& ptr, int count = 1); + ///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added + ///< with (origCount-count). + /// + /// @return an iterator to the new stack, or end() if no new stack was created. - MWWorld::ContainerStoreIterator restack (const MWWorld::Ptr& item); - ///< Attempt to re-stack an item in this container. - /// If a compatible stack is found, the item's count is added to that stack, then the original is deleted. - /// @return If the item was stacked, return the stack, otherwise return the old (untouched) item. + MWWorld::ContainerStoreIterator restack(const MWWorld::Ptr& item); + ///< Attempt to re-stack an item in this container. + /// If a compatible stack is found, the item's count is added to that stack, then the original is deleted. + /// @return If the item was stacked, return the stack, otherwise return the old (untouched) item. - int count (const std::string& id) const; - ///< @return How many items with refID \a id are in this container? + int count(const ESM::RefId& id) const; + ///< @return How many items with refID \a id are in this container? - ContainerStoreListener* getContListener() const; - void setContListener(ContainerStoreListener* listener); - protected: - ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count); - ///< Add the item to this container (do not try to stack it onto existing items) + ContainerStoreListener* getContListener() const; + void setContListener(ContainerStoreListener* listener); - virtual void flagAsModified(); + protected: + ContainerStoreIterator addNewStack(const ConstPtr& ptr, int count); + ///< Add the item to this container (do not try to stack it onto existing items) - /// + and - operations that can deal with negative stacks - /// Note that negativity is infectious - static int addItems(int count1, int count2); - static int subtractItems(int count1, int count2); - public: + virtual void flagAsModified(); - virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const; - ///< @return true if the two specified objects can stack with each other + /// + and - operations that can deal with negative stacks + /// Note that negativity is infectious + static int addItems(int count1, int count2); + static int subtractItems(int count1, int count2); - void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed = Misc::Rng::getSeed()); - ///< Insert items into *this. + public: + virtual bool stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const; + ///< @return true if the two specified objects can stack with each other - void fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed); - ///< Insert items into *this, excluding leveled items + void fill(const ESM::InventoryList& items, const ESM::RefId& owner, Misc::Rng::Generator& seed); + ///< Insert items into *this. - virtual void clear(); - ///< Empty container. + void fillNonRandom(const ESM::InventoryList& items, const ESM::RefId& owner, unsigned int seed); + ///< Insert items into *this, excluding leveled items - float getWeight() const; - ///< Return total weight of the items contained in *this. + virtual void clear(); + ///< Empty container. - static int getType (const ConstPtr& ptr); - ///< This function throws an exception, if ptr does not point to an object, that can be - /// put into a container. + float getWeight() const; + ///< Return total weight of the items contained in *this. - Ptr findReplacement(const std::string& id); - ///< Returns replacement for object with given id. Prefer used items (with low durability left). + static int getType(const ConstPtr& ptr); + ///< This function throws an exception, if ptr does not point to an object, that can be + /// put into a container. - Ptr search (const std::string& id); + Ptr findReplacement(const ESM::RefId& id); + ///< Returns replacement for object with given id. Prefer used items (with low durability left). - virtual void writeState (ESM::InventoryState& state) const; + Ptr search(const ESM::RefId& id); - virtual void readState (const ESM::InventoryState& state); + virtual void writeState(ESM::InventoryState& state) const; - bool isResolved() const; + virtual void readState(const ESM::InventoryState& state); +<<<<<<< HEAD /* Start of tes3mp addiition @@ -262,54 +286,59 @@ namespace MWWorld void resolve(); ResolutionHandle resolveTemporarily(); void unresolve(); +======= + bool isResolved() const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 - friend class ContainerStoreIteratorBase; - friend class ContainerStoreIteratorBase; - friend class ResolutionListener; - friend class MWClass::Container; + void resolve(); + ResolutionHandle resolveTemporarily(); + void unresolve(); + + friend class ContainerStoreIteratorBase; + friend class ContainerStoreIteratorBase; + friend class ResolutionListener; + friend class MWClass::Container; }; - - template + template class ContainerStoreIteratorBase - : public std::iterator { - template + template struct IsConvertible { static constexpr bool value = true; }; - template + template struct IsConvertible { static constexpr bool value = false; }; - template + template struct IteratorTrait { typedef typename MWWorld::CellRefList::List::iterator type; }; - template + template struct IteratorTrait { typedef typename MWWorld::CellRefList::List::const_iterator type; }; - template + template struct Iterator : IteratorTrait { }; - template + template struct ContainerStoreTrait { typedef ContainerStore* type; }; - - template + + template struct ContainerStoreTrait { typedef const ContainerStore* type; @@ -335,74 +364,80 @@ namespace MWWorld typename Iterator::type mRepair; typename Iterator::type mWeapon; - ContainerStoreIteratorBase (ContainerStoreType container); + ContainerStoreIteratorBase(ContainerStoreType container); ///< End-iterator - ContainerStoreIteratorBase (int mask, ContainerStoreType container); + ContainerStoreIteratorBase(int mask, ContainerStoreType container); ///< Begin-iterator // construct iterator using a CellRefList iterator - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); - ContainerStoreIteratorBase (ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); + ContainerStoreIteratorBase(ContainerStoreType container, typename Iterator::type); - template - void copy (const ContainerStoreIteratorBase& src); - - void incType (); - - void nextType (); + template + void copy(const ContainerStoreIteratorBase& src); - bool resetIterator (); + void incType(); + + void nextType(); + + bool resetIterator(); ///< Reset iterator for selected type. /// /// \return Type not empty? - bool incIterator (); + bool incIterator(); ///< Increment iterator for selected type. /// /// \return reached the end? - public: - template - ContainerStoreIteratorBase (const ContainerStoreIteratorBase& other) - { - char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible::value ? 1 : -1]; - ((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR); - copy (other); - } + public: + using iterator_category = std::forward_iterator_tag; + using value_type = PtrType; + using difference_type = std::ptrdiff_t; + using pointer = PtrType*; + using reference = PtrType&; - template - bool isEqual(const ContainerStoreIteratorBase& other) const; + template + ContainerStoreIteratorBase(const ContainerStoreIteratorBase& other) + { + char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible::value ? 1 : -1]; + ((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR); + copy(other); + } - PtrType *operator->() const; - PtrType operator*() const; + template + bool isEqual(const ContainerStoreIteratorBase& other) const; - ContainerStoreIteratorBase& operator++ (); - ContainerStoreIteratorBase operator++ (int); - ContainerStoreIteratorBase& operator= (const ContainerStoreIteratorBase& rhs); - ContainerStoreIteratorBase (const ContainerStoreIteratorBase& rhs) = default; + PtrType* operator->() const; + PtrType operator*() const; - int getType() const; - const ContainerStore *getContainerStore() const; + ContainerStoreIteratorBase& operator++(); + ContainerStoreIteratorBase operator++(int); + ContainerStoreIteratorBase& operator=(const ContainerStoreIteratorBase& rhs); + ContainerStoreIteratorBase(const ContainerStoreIteratorBase& rhs) = default; - friend class ContainerStore; - friend class ContainerStoreIteratorBase; - friend class ContainerStoreIteratorBase; + int getType() const; + const ContainerStore* getContainerStore() const; + + friend class ContainerStore; + friend class ContainerStoreIteratorBase; + friend class ContainerStoreIteratorBase; }; - template - bool operator== (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); - template - bool operator!= (const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); + template + bool operator==(const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); + template + bool operator!=(const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); } #endif diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b529ae9db..70150ec33 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -1,35 +1,22 @@ #ifndef CONTENTLOADER_HPP #define CONTENTLOADER_HPP -#include -#include +#include -#include -#include "components/loadinglistener/loadinglistener.hpp" +namespace Loading +{ + class Listener; +} namespace MWWorld { -struct ContentLoader -{ - ContentLoader(Loading::Listener& listener) - : mListener(listener) + struct ContentLoader { - } + virtual ~ContentLoader() = default; - virtual ~ContentLoader() - { - } - - virtual void load(const boost::filesystem::path& filepath, int& index) - { - Log(Debug::Info) << "Loading content file " << filepath.string(); - mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); - } - - protected: - Loading::Listener& mListener; -}; + virtual void load(const std::filesystem::path& filepath, int& index, Loading::Listener* listener) = 0; + }; } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/customdata.cpp b/apps/openmw/mwworld/customdata.cpp index 5080c0923..395d230a1 100644 --- a/apps/openmw/mwworld/customdata.cpp +++ b/apps/openmw/mwworld/customdata.cpp @@ -1,81 +1,80 @@ #include "customdata.hpp" -#include #include +#include #include namespace MWWorld { -MWClass::CreatureCustomData &CustomData::asCreatureCustomData() -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; - throw std::logic_error(error.str()); -} + MWClass::CreatureCustomData& CustomData::asCreatureCustomData() + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; + throw std::logic_error(error.str()); + } -const MWClass::CreatureCustomData &CustomData::asCreatureCustomData() const -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; - throw std::logic_error(error.str()); -} + const MWClass::CreatureCustomData& CustomData::asCreatureCustomData() const + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; + throw std::logic_error(error.str()); + } -MWClass::NpcCustomData &CustomData::asNpcCustomData() -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to NpcCustomData"; - throw std::logic_error(error.str()); -} + MWClass::NpcCustomData& CustomData::asNpcCustomData() + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to NpcCustomData"; + throw std::logic_error(error.str()); + } -const MWClass::NpcCustomData &CustomData::asNpcCustomData() const -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to NpcCustomData"; - throw std::logic_error(error.str()); -} + const MWClass::NpcCustomData& CustomData::asNpcCustomData() const + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to NpcCustomData"; + throw std::logic_error(error.str()); + } -MWClass::ContainerCustomData &CustomData::asContainerCustomData() -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; - throw std::logic_error(error.str()); -} + MWClass::ContainerCustomData& CustomData::asContainerCustomData() + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; + throw std::logic_error(error.str()); + } -const MWClass::ContainerCustomData &CustomData::asContainerCustomData() const -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; - throw std::logic_error(error.str()); -} + const MWClass::ContainerCustomData& CustomData::asContainerCustomData() const + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; + throw std::logic_error(error.str()); + } -MWClass::DoorCustomData &CustomData::asDoorCustomData() -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to DoorCustomData"; - throw std::logic_error(error.str()); -} + MWClass::DoorCustomData& CustomData::asDoorCustomData() + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to DoorCustomData"; + throw std::logic_error(error.str()); + } -const MWClass::DoorCustomData &CustomData::asDoorCustomData() const -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to DoorCustomData"; - throw std::logic_error(error.str()); -} + const MWClass::DoorCustomData& CustomData::asDoorCustomData() const + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to DoorCustomData"; + throw std::logic_error(error.str()); + } -MWClass::CreatureLevListCustomData &CustomData::asCreatureLevListCustomData() -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; - throw std::logic_error(error.str()); -} - -const MWClass::CreatureLevListCustomData &CustomData::asCreatureLevListCustomData() const -{ - std::stringstream error; - error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; - throw std::logic_error(error.str()); -} + MWClass::CreatureLevListCustomData& CustomData::asCreatureLevListCustomData() + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; + throw std::logic_error(error.str()); + } + const MWClass::CreatureLevListCustomData& CustomData::asCreatureLevListCustomData() const + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; + throw std::logic_error(error.str()); + } } diff --git a/apps/openmw/mwworld/customdata.hpp b/apps/openmw/mwworld/customdata.hpp index 7200e7684..9d9283f08 100644 --- a/apps/openmw/mwworld/customdata.hpp +++ b/apps/openmw/mwworld/customdata.hpp @@ -17,37 +17,33 @@ namespace MWWorld /// \brief Base class for the MW-class-specific part of RefData class CustomData { - public: + public: + virtual ~CustomData() {} - virtual ~CustomData() {} + virtual std::unique_ptr clone() const = 0; - virtual std::unique_ptr clone() const = 0; + // Fast version of dynamic_cast. Needs to be overridden in the respective class. - // Fast version of dynamic_cast. Needs to be overridden in the respective class. + virtual MWClass::CreatureCustomData& asCreatureCustomData(); + virtual const MWClass::CreatureCustomData& asCreatureCustomData() const; - virtual MWClass::CreatureCustomData& asCreatureCustomData(); - virtual const MWClass::CreatureCustomData& asCreatureCustomData() const; + virtual MWClass::NpcCustomData& asNpcCustomData(); + virtual const MWClass::NpcCustomData& asNpcCustomData() const; - virtual MWClass::NpcCustomData& asNpcCustomData(); - virtual const MWClass::NpcCustomData& asNpcCustomData() const; + virtual MWClass::ContainerCustomData& asContainerCustomData(); + virtual const MWClass::ContainerCustomData& asContainerCustomData() const; - virtual MWClass::ContainerCustomData& asContainerCustomData(); - virtual const MWClass::ContainerCustomData& asContainerCustomData() const; + virtual MWClass::DoorCustomData& asDoorCustomData(); + virtual const MWClass::DoorCustomData& asDoorCustomData() const; - virtual MWClass::DoorCustomData& asDoorCustomData(); - virtual const MWClass::DoorCustomData& asDoorCustomData() const; - - virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData(); - virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const; + virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData(); + virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const; }; template struct TypedCustomData : CustomData { - std::unique_ptr clone() const final - { - return std::make_unique(*static_cast(this)); - } + std::unique_ptr clone() const final { return std::make_unique(*static_cast(this)); } }; } diff --git a/apps/openmw/mwworld/datetimemanager.cpp b/apps/openmw/mwworld/datetimemanager.cpp index 0894c974d..2a347bd5a 100644 --- a/apps/openmw/mwworld/datetimemanager.cpp +++ b/apps/openmw/mwworld/datetimemanager.cpp @@ -1,8 +1,11 @@ #include "datetimemanager.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "duration.hpp" #include "esmstore.hpp" #include "globals.hpp" #include "timestamp.hpp" @@ -13,21 +16,33 @@ namespace { switch (month) { - case 0: return 31; - case 1: return 28; - case 2: return 31; - case 3: return 30; - case 4: return 31; - case 5: return 30; - case 6: return 31; - case 7: return 31; - case 8: return 30; - case 9: return 31; - case 10: return 30; - case 11: return 31; + case 0: + return 31; + case 1: + return 28; + case 2: + return 31; + case 3: + return 30; + case 4: + return 31; + case 5: + return 30; + case 6: + return 31; + case 7: + return 31; + case 8: + return 30; + case 9: + return 31; + case 10: + return 30; + case 11: + return 31; } - throw std::runtime_error ("month out of range"); + throw std::runtime_error("month out of range"); } } @@ -35,12 +50,12 @@ namespace MWWorld { void DateTimeManager::setup(Globals& globalVariables) { - mGameHour = globalVariables["gamehour"].getFloat(); - mDaysPassed = globalVariables["dayspassed"].getInteger(); - mDay = globalVariables["day"].getInteger(); - mMonth = globalVariables["month"].getInteger(); - mYear = globalVariables["year"].getInteger(); - mTimeScale = globalVariables["timescale"].getFloat(); + mGameHour = globalVariables[Globals::sGameHour].getFloat(); + mDaysPassed = globalVariables[Globals::sDaysPassed].getInteger(); + mDay = globalVariables[Globals::sDay].getInteger(); + mMonth = globalVariables[Globals::sMonth].getInteger(); + mYear = globalVariables[Globals::sYear].getInteger(); + mTimeScale = globalVariables[Globals::sTimeScale].getFloat(); } void DateTimeManager::setHour(double hour) @@ -48,11 +63,11 @@ namespace MWWorld if (hour < 0) hour = 0; - int days = static_cast(hour / 24); - hour = std::fmod(hour, 24); - mGameHour = static_cast(hour); + const Duration duration = Duration::fromHours(hour); - if (days > 0) + mGameHour = duration.getHours(); + + if (const int days = duration.getDays(); days > 0) setDay(days + mDay); } @@ -132,59 +147,63 @@ namespace MWWorld if (days > 0) mDaysPassed += days; - globalVariables["gamehour"].setFloat(mGameHour); - globalVariables["dayspassed"].setInteger(mDaysPassed); - globalVariables["day"].setInteger(mDay); - globalVariables["month"].setInteger(mMonth); - globalVariables["year"].setInteger(mYear); + globalVariables[Globals::sGameHour].setFloat(mGameHour); + globalVariables[Globals::sDaysPassed].setInteger(mDaysPassed); + globalVariables[Globals::sDay].setInteger(mDay); + globalVariables[Globals::sMonth].setInteger(mMonth); + globalVariables[Globals::sYear].setInteger(mYear); } - std::string DateTimeManager::getMonthName(int month) const + static std::vector getMonthNames() { + auto calendarL10n = MWBase::Environment::get().getL10nManager()->getContext("Calendar"); + std::string prefix = "month"; + std::vector months; + int count = 12; + months.reserve(count); + for (int i = 1; i <= count; ++i) + months.push_back(calendarL10n->formatMessage(prefix + std::to_string(i), {}, {})); + return months; + } + + std::string_view DateTimeManager::getMonthName(int month) const + { + static std::vector months = getMonthNames(); + if (month == -1) month = mMonth; - - const int months = 12; - if (month < 0 || month >= months) - return std::string(); - - static const char *monthNames[months] = - { - "sMonthMorningstar", "sMonthSunsdawn", "sMonthFirstseed", "sMonthRainshand", - "sMonthSecondseed", "sMonthMidyear", "sMonthSunsheight", "sMonthLastseed", - "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar" - }; - - const ESM::GameSetting *setting = MWBase::Environment::get().getWorld()->getStore().get().find(monthNames[month]); - return setting->mValue.getString(); + if (month < 0 || month >= static_cast(months.size())) + return {}; + else + return months[month]; } - bool DateTimeManager::updateGlobalFloat(const std::string& name, float value) + bool DateTimeManager::updateGlobalFloat(GlobalVariableName name, float value) { - if (name=="gamehour") + if (name == Globals::sGameHour) { setHour(value); return true; } - else if (name=="day") + else if (name == Globals::sDay) { setDay(static_cast(value)); return true; } - else if (name=="month") + else if (name == Globals::sMonth) { setMonth(static_cast(value)); return true; } - else if (name=="year") + else if (name == Globals::sYear) { mYear = static_cast(value); } - else if (name=="timescale") + else if (name == Globals::sTimeScale) { mTimeScale = value; } - else if (name=="dayspassed") + else if (name == Globals::sDaysPassed) { mDaysPassed = static_cast(value); } @@ -192,32 +211,32 @@ namespace MWWorld return false; } - bool DateTimeManager::updateGlobalInt(const std::string& name, int value) + bool DateTimeManager::updateGlobalInt(GlobalVariableName name, int value) { - if (name=="gamehour") + if (name == Globals::sGameHour) { setHour(static_cast(value)); return true; } - else if (name=="day") + else if (name == Globals::sDay) { setDay(value); return true; } - else if (name=="month") + else if (name == Globals::sMonth) { setMonth(value); return true; } - else if (name=="year") + else if (name == Globals::sYear) { mYear = value; } - else if (name=="timescale") + else if (name == Globals::sTimeScale) { mTimeScale = static_cast(value); } - else if (name=="dayspassed") + else if (name == Globals::sDaysPassed) { mDaysPassed = value; } diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp index b460be746..0ceaae958 100644 --- a/apps/openmw/mwworld/datetimemanager.hpp +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -1,7 +1,9 @@ #ifndef GAME_MWWORLD_DATETIMEMANAGER_H #define GAME_MWWORLD_DATETIMEMANAGER_H -#include +#include + +#include "globalvariablename.hpp" namespace ESM { @@ -27,7 +29,7 @@ namespace MWWorld void setMonth(int month); public: - std::string getMonthName(int month) const; + std::string_view getMonthName(int month) const; TimeStamp getTimeStamp() const; ESM::EpochTimeStamp getEpochTimeStamp() const; float getTimeScaleFactor() const; @@ -35,8 +37,8 @@ namespace MWWorld void advanceTime(double hours, Globals& globalVariables); void setup(Globals& globalVariables); - bool updateGlobalInt(const std::string& name, int value); - bool updateGlobalFloat(const std::string& name, float value); + bool updateGlobalInt(GlobalVariableName name, int value); + bool updateGlobalFloat(GlobalVariableName name, float value); }; } diff --git a/apps/openmw/mwworld/duration.hpp b/apps/openmw/mwworld/duration.hpp new file mode 100644 index 000000000..78693ca0d --- /dev/null +++ b/apps/openmw/mwworld/duration.hpp @@ -0,0 +1,39 @@ +#ifndef GAME_MWWORLD_DURATION_H +#define GAME_MWWORLD_DURATION_H + +#include +#include + +namespace MWWorld +{ + inline const double maxFloatHour = static_cast(std::nextafter(24.0f, 0.0f)); + + class Duration + { + public: + static Duration fromHours(double hours) + { + if (hours < 0) + throw std::runtime_error("Negative hours is not supported Duration"); + + return Duration( + static_cast(hours / 24), static_cast(std::min(std::fmod(hours, 24), maxFloatHour))); + } + + int getDays() const { return mDays; } + + float getHours() const { return mHours; } + + private: + int mDays; + float mHours; + + explicit Duration(int days, float hours) + : mDays(days) + , mHours(hours) + { + } + }; +} + +#endif diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index b12d646e7..e586a4c20 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -1,31 +1,78 @@ #include "esmloader.hpp" #include "esmstore.hpp" -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" namespace MWWorld { -EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& readers, - ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener) - : ContentLoader(listener) - , mEsm(readers) - , mStore(store) - , mEncoder(encoder) -{ -} + EsmLoader::EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, + std::vector& esmVersions) + : mReaders(readers) + , mStore(store) + , mEncoder(encoder) + , mDialogue(nullptr) // A content file containing INFO records without a DIAL record appends them to the + // previous file's dialogue + , mESMVersions(esmVersions) + { + } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index) -{ - ContentLoader::load(filepath.filename(), index); + void EsmLoader::load(const std::filesystem::path& filepath, int& index, Loading::Listener* listener) + { - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); -} + auto stream = Files::openBinaryInputFileStream(filepath); + const ESM::Format format = ESM::readFormat(*stream); + stream->seekg(0); + + switch (format) + { + case ESM::Format::Tes3: + { + const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast(index)); + reader->setEncoder(mEncoder); + reader->setIndex(index); + reader->open(filepath); + reader->resolveParentFileIndices(mReaders); + + assert(reader->getGameFiles().size() == reader->getParentFileIndices().size()); + for (std::size_t i = 0, n = reader->getParentFileIndices().size(); i < n; ++i) + if (i == static_cast(reader->getIndex())) + throw std::runtime_error("File " + Files::pathToUnicodeString(reader->getName()) + " asks for parent file " + + reader->getGameFiles()[i].name + + ", but it is not available or has been loaded in the wrong order. " + "Please run the launcher to fix this issue."); + + mESMVersions[index] = reader->getVer(); + mStore.load(*reader, listener, mDialogue); + + if (!mMasterFileFormat.has_value() + && (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm") + || Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame"))) + mMasterFileFormat = reader->getFormatVersion(); + break; + } + case ESM::Format::Tes4: + { + ESM4::Reader readerESM4(std::move(stream), filepath, + MWBase::Environment::get().getResourceSystem()->getVFS(), mReaders.getStatelessEncoder()); + readerESM4.setModIndex(index); + readerESM4.updateModIndices(mNameToIndex); + mStore.loadESM4(readerESM4); + break; + } + } + mNameToIndex[Misc::StringUtils::lowerCase(Files::pathToUnicodeString(filepath.filename()))] = index; + } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 506105beb..53bff939c 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -1,37 +1,46 @@ #ifndef ESMLOADER_HPP #define ESMLOADER_HPP +#include +#include #include #include "contentloader.hpp" namespace ToUTF8 { - class Utf8Encoder; + class Utf8Encoder; } namespace ESM { - class ESMReader; + class ReadersCache; + struct Dialogue; } namespace MWWorld { -class ESMStore; + class ESMStore; -struct EsmLoader : public ContentLoader -{ - EsmLoader(MWWorld::ESMStore& store, std::vector& readers, - ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); + struct EsmLoader : public ContentLoader + { + explicit EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, + std::vector& esmVersions); - void load(const boost::filesystem::path& filepath, int& index) override; + std::optional getMasterFileFormat() const { return mMasterFileFormat; } + + void load(const std::filesystem::path& filepath, int& index, Loading::Listener* listener) override; private: - std::vector& mEsm; - MWWorld::ESMStore& mStore; - ToUTF8::Utf8Encoder* mEncoder; -}; + ESM::ReadersCache& mReaders; + MWWorld::ESMStore& mStore; + ToUTF8::Utf8Encoder* mEncoder; + ESM::Dialogue* mDialogue; + std::optional mMasterFileFormat; + std::vector& mESMVersions; + std::map mNameToIndex; + }; } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index b1885edaf..00a229e04 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -1,16 +1,25 @@ #include "esmstore.hpp" #include -#include - -#include +#include +#include #include +#include +#include +#include +#include #include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include + #include "../mwmechanics/spelllist.hpp" namespace @@ -20,54 +29,67 @@ namespace ESM::RefNum mRefNum; std::size_t mRefID; - Ref(ESM::RefNum refNum, std::size_t refID) : mRefNum(refNum), mRefID(refID) {} + Ref(ESM::RefNum refNum, std::size_t refID) + : mRefNum(refNum) + , mRefID(refID) + { + } }; constexpr std::size_t deletedRefID = std::numeric_limits::max(); - void readRefs(const ESM::Cell& cell, std::vector& refs, std::vector& refIDs, std::vector& readers) + void readRefs(const ESM::Cell& cell, std::vector& refs, std::vector& refIDs, + std::set& keyIDs, ESM::ReadersCache& readers) { + // TODO: we have many similar copies of this code. for (size_t i = 0; i < cell.mContextList.size(); i++) { - size_t index = cell.mContextList[i].index; - if (readers.size() <= index) - readers.resize(index + 1); - cell.restore(readers[index], i); + const std::size_t index = static_cast(cell.mContextList[i].index); + const ESM::ReadersCache::BusyItem reader = readers.get(index); + cell.restore(*reader, i); ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; bool deleted = false; - while(cell.getNextRef(readers[index], ref, deleted)) + while (cell.getNextRef(*reader, ref, deleted)) { - if(deleted) + if (deleted) refs.emplace_back(ref.mRefNum, deletedRefID); - else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) == cell.mMovedRefs.end()) + else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) + == cell.mMovedRefs.end()) { + if (!ref.mKey.empty()) + keyIDs.insert(std::move(ref.mKey)); refs.emplace_back(ref.mRefNum, refIDs.size()); refIDs.push_back(std::move(ref.mRefID)); } } } - for(const auto& [value, deleted] : cell.mLeasedRefs) + for (const auto& [value, deleted] : cell.mLeasedRefs) { - if(deleted) + if (deleted) refs.emplace_back(value.mRefNum, deletedRefID); else { + if (!value.mKey.empty()) + keyIDs.insert(std::move(value.mKey)); refs.emplace_back(value.mRefNum, refIDs.size()); refIDs.push_back(value.mRefID); } } } - std::vector getNPCsToReplace(const MWWorld::Store& factions, const MWWorld::Store& classes, const std::map& npcs) + const ESM::RefId& getDefaultClass(const MWWorld::Store& classes) { - // Cache first class from store - we will use it if current class is not found - std::string defaultCls; auto it = classes.begin(); if (it != classes.end()) - defaultCls = it->mId; - else - throw std::runtime_error("List of NPC classes is empty!"); + return it->mId; + throw std::runtime_error("List of NPC classes is empty!"); + } + + std::vector getNPCsToReplace(const MWWorld::Store& factions, + const MWWorld::Store& classes, const std::unordered_map& npcs) + { + // Cache first class from store - we will use it if current class is not found + const ESM::RefId& defaultCls = getDefaultClass(classes); // Validate NPCs for non-existing class and faction. // We will replace invalid entries by fixed ones @@ -78,29 +100,28 @@ namespace ESM::NPC npc = npcIter.second; bool changed = false; - const std::string npcFaction = npc.mFaction; + const ESM::RefId& npcFaction = npc.mFaction; if (!npcFaction.empty()) { - const ESM::Faction *fact = factions.search(npcFaction); + const ESM::Faction* fact = factions.search(npcFaction); if (!fact) { - Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it."; - npc.mFaction.clear(); + Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent faction " + << npc.mFaction << ", ignoring it."; + npc.mFaction = ESM::RefId(); npc.mNpdt.mRank = 0; changed = true; } } - std::string npcClass = npc.mClass; - if (!npcClass.empty()) + const ESM::RefId& npcClass = npc.mClass; + const ESM::Class* cls = classes.search(npcClass); + if (!cls) { - const ESM::Class *cls = classes.search(npcClass); - if (!cls) - { - Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement."; - npc.mClass = defaultCls; - changed = true; - } + Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent class " + << npc.mClass << ", using " << defaultCls << " class as replacement."; + npc.mClass = defaultCls; + changed = true; } if (changed) @@ -109,286 +130,523 @@ namespace return npcsToReplace; } + + // Custom enchanted items can reference scripts that no longer exist, this doesn't necessarily mean the base item no + // longer exists however. So instead of removing the item altogether, we're only removing the script. + template + void removeMissingScripts(const MWWorld::Store& scripts, MapT& items) + { + for (auto& [id, item] : items) + { + if (!item.mScript.empty() && !scripts.search(item.mScript)) + { + item.mScript = ESM::RefId(); + Log(Debug::Verbose) << "Item " << id << " (" << item.mName << ") has nonexistent script " + << item.mScript << ", ignoring it."; + } + } + } } namespace MWWorld { + using IDMap = std::unordered_map; -static bool isCacheableRecord(int id) -{ - if (id == ESM::REC_ACTI || id == ESM::REC_ALCH || id == ESM::REC_APPA || id == ESM::REC_ARMO || - id == ESM::REC_BOOK || id == ESM::REC_CLOT || id == ESM::REC_CONT || id == ESM::REC_CREA || - id == ESM::REC_DOOR || id == ESM::REC_INGR || id == ESM::REC_LEVC || id == ESM::REC_LEVI || - id == ESM::REC_LIGH || id == ESM::REC_LOCK || id == ESM::REC_MISC || id == ESM::REC_NPC_ || - id == ESM::REC_PROB || id == ESM::REC_REPA || id == ESM::REC_STAT || id == ESM::REC_WEAP || - id == ESM::REC_BODY) + struct ESMStoreImp { - return true; - } - return false; -} + ESMStore::StoreTuple mStores; -void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) -{ - listener->setProgressRange(1000); + std::map mRecNameToStore; - ESM::Dialogue *dialogue = nullptr; + // Lookup of all IDs. Makes looking up references faster. Just + // maps the id name to the record type. + IDMap mIds; + IDMap mStaticIds; - // Land texture loading needs to use a separate internal store for each plugin. - // We set the number of plugins here to avoid continual resizes during loading, - // and so we can properly verify if valid plugin indices are being passed to the - // LandTexture Store retrieval methods. - mLandTextures.resize(esm.getGlobalReaderList()->size()); + template + static void assignStoreToIndex(ESMStore& stores, Store& store) + { + const std::size_t storeIndex = ESMStore::getTypeIndex(); + if (stores.mStores.size() <= storeIndex) + stores.mStores.resize(storeIndex + 1); - /// \todo Move this to somewhere else. ESMReader? - // Cache parent esX files by tracking their indices in the global list of - // all files/readers used by the engine. This will greaty accelerate - // refnumber mangling, as required for handling moved references. - const std::vector &masters = esm.getGameFiles(); - std::vector *allPlugins = esm.getGlobalReaderList(); - for (size_t j = 0; j < masters.size(); j++) { - const ESM::Header::MasterData &mast = masters[j]; - std::string fname = mast.name; - int index = ~0; - for (int i = 0; i < esm.getIndex(); i++) { - const std::string candidate = allPlugins->at(i).getContext().filename; - std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); - if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { - index = i; - break; + assert(&store == &std::get>(stores.mStoreImp->mStores)); + + stores.mStores[storeIndex] = &store; + if constexpr (std::is_convertible_v*, DynamicStore*>) + { + stores.mDynamicStores.push_back(&store); + constexpr ESM::RecNameInts recName = T::sRecordId; + if constexpr (recName != ESM::REC_INTERNAL_PLAYER) + { + stores.mStoreImp->mRecNameToStore[recName] = &store; + } } } - if (index == (int)~0) { - // Tried to load a parent file that has not been loaded yet. This is bad, - // the launcher should have taken care of this. - std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name - + ", but it has not been loaded yet. Please check your load order."; - esm.fail(fstring); + + template + static bool typedReadRecordESM4(ESM4::Reader& reader, Store& store) + { + auto recordType = static_cast(reader.hdr().record.typeId); + + ESM::RecNameInts esm4RecName = static_cast(ESM::esm4Recname(recordType)); + if constexpr (HasRecordId::value) + { + if constexpr (ESM::isESM4Rec(T::sRecordId)) + { + if (T::sRecordId == esm4RecName) + { + reader.getRecordData(); + T value; + value.load(reader); + store.insertStatic(value); + return true; + } + } + } + return false; } - esm.addParentFileIndex(index); + + static bool readRecord(ESM4::Reader& reader, ESMStore& store) + { + return std::apply( + [&reader](auto&... x) { return (typedReadRecordESM4(reader, x) || ...); }, store.mStoreImp->mStores); + } + }; + + int ESMStore::find(const ESM::RefId& id) const + { + IDMap::const_iterator it = mStoreImp->mIds.find(id); + if (it == mStoreImp->mIds.end()) + { + return 0; + } + return it->second; } - // Loop through all records - while(esm.hasMoreRecs()) + int ESMStore::findStatic(const ESM::RefId& id) const { - ESM::NAME n = esm.getRecName(); - esm.getRecHeader(); + IDMap::const_iterator it = mStoreImp->mStaticIds.find(id); + if (it == mStoreImp->mStaticIds.end()) + { + return 0; + } + return it->second; + } - // Look up the record type. - std::map::iterator it = mStores.find(n.intval); + ESMStore::ESMStore() + { + mStoreImp = std::make_unique(); + std::apply([this](auto&... x) { (ESMStoreImp::assignStoreToIndex(*this, x), ...); }, mStoreImp->mStores); + mDynamicCount = 0; + getWritable().setCells(getWritable()); + } - if (it == mStores.end()) { - if (n.intval == ESM::REC_INFO) { - if (dialogue) + ESMStore::~ESMStore() = default; + + void ESMStore::clearDynamic() + { + for (const auto& store : mDynamicStores) + store->clearDynamic(); + mStoreImp->mIds = mStoreImp->mStaticIds; + + movePlayerRecord(); + } + + static bool isCacheableRecord(int id) + { + switch (id) + { + case ESM::REC_ACTI: + case ESM::REC_ALCH: + case ESM::REC_APPA: + case ESM::REC_ARMO: + case ESM::REC_BOOK: + case ESM::REC_CLOT: + case ESM::REC_CONT: + case ESM::REC_CREA: + case ESM::REC_DOOR: + case ESM::REC_INGR: + case ESM::REC_LEVC: + case ESM::REC_LEVI: + case ESM::REC_LIGH: + case ESM::REC_LOCK: + case ESM::REC_MISC: + case ESM::REC_NPC_: + case ESM::REC_PROB: + case ESM::REC_REPA: + case ESM::REC_STAT: + case ESM::REC_WEAP: + case ESM::REC_BODY: + case ESM::REC_STAT4: + case ESM::REC_LIGH4: + case ESM::REC_ACTI4: + case ESM::REC_ALCH4: + case ESM::REC_AMMO4: + case ESM::REC_ARMO4: + case ESM::REC_BOOK4: + case ESM::REC_CONT4: + case ESM::REC_DOOR4: + case ESM::REC_FURN4: + case ESM::REC_INGR4: + case ESM::REC_MISC4: + case ESM::REC_TREE4: + case ESM::REC_WEAP4: + return true; + break; + } + return false; + } + + void ESMStore::load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue) + { + if (listener != nullptr) + listener->setProgressRange(::EsmLoader::fileProgress); + + // Land texture loading needs to use a separate internal store for each plugin. + // We set the number of plugins here so we can properly verify if valid plugin + // indices are being passed to the LandTexture Store retrieval methods. + getWritable().resize(esm.getIndex() + 1); + + // Loop through all records + while (esm.hasMoreRecs()) + { + ESM::NAME n = esm.getRecName(); + esm.getRecHeader(); + if (esm.getRecordFlags() & ESM::FLAG_Ignored) + { + esm.skipRecord(); + continue; + } + + // Look up the record type. + ESM::RecNameInts recName = static_cast(n.toInt()); + const auto& it = mStoreImp->mRecNameToStore.find(recName); + + if (it == mStoreImp->mRecNameToStore.end()) + { + if (recName == ESM::REC_INFO) { - dialogue->readInfo(esm, esm.getIndex() != 0); + if (dialogue) + { + dialogue->readInfo(esm); + } + else + { + Log(Debug::Error) << "Error: info record without dialog"; + esm.skipRecord(); + } + } + else if (n.toInt() == ESM::REC_MGEF) + { + getWritable().load(esm); + } + else if (n.toInt() == ESM::REC_SKIL) + { + getWritable().load(esm); + } + else if (n.toInt() == ESM::REC_FILT || n.toInt() == ESM::REC_DBGP) + { + // ignore project file only records + esm.skipRecord(); + } + else if (n.toInt() == ESM::REC_LUAL) + { + ESM::LuaScriptsCfg cfg; + cfg.load(esm); + cfg.adjustRefNums(esm); + mLuaContent.push_back(std::move(cfg)); } else { - Log(Debug::Error) << "Error: info record without dialog"; - esm.skipRecord(); + throw std::runtime_error("Unknown record: " + n.toString()); } - } else if (n.intval == ESM::REC_MGEF) { - mMagicEffects.load (esm); - } else if (n.intval == ESM::REC_SKIL) { - mSkills.load (esm); } - else if (n.intval==ESM::REC_FILT || n.intval == ESM::REC_DBGP) + else { - // ignore project file only records - esm.skipRecord(); - } - else { - std::stringstream error; - error << "Unknown record: " << n.toString(); - throw std::runtime_error(error.str()); - } - } else { - RecordId id = it->second->load(esm); - if (id.mIsDeleted) - { - it->second->eraseStatic(id.mId); - continue; - } + RecordId id = it->second->load(esm); + if (id.mIsDeleted) + { + it->second->eraseStatic(id.mId); + continue; + } - if (n.intval==ESM::REC_DIAL) { - dialogue = const_cast(mDialogs.find(id.mId)); - } else { - dialogue = nullptr; + if (n.toInt() == ESM::REC_DIAL) + { + dialogue = const_cast(getWritable().find(id.mId)); + } + else + { + dialogue = nullptr; + } } + if (listener != nullptr) + listener->setProgress(::EsmLoader::fileProgress * esm.getFileOffset() / esm.getFileSize()); } - listener->setProgress(static_cast(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); } -} -void ESMStore::setUp(bool validateRecords) -{ - mIds.clear(); + void ESMStore::loadESM4(ESM4::Reader& reader) + { + auto visitorRec = [this](ESM4::Reader& reader) { return ESMStoreImp::readRecord(reader, *this); }; + ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {}); + } - std::map::iterator storeIt = mStores.begin(); - for (; storeIt != mStores.end(); ++storeIt) { - storeIt->second->setUp(); + void ESMStore::setIdType(const ESM::RefId& id, ESM::RecNameInts type) + { + mStoreImp->mIds[id] = type; + } - if (isCacheableRecord(storeIt->first)) + ESM::LuaScriptsCfg ESMStore::getLuaScriptsCfg() const + { + ESM::LuaScriptsCfg cfg; + for (const LuaContent& c : mLuaContent) { - std::vector identifiers; - storeIt->second->listIdentifier(identifiers); + if (std::holds_alternative(c)) + { + // *.omwscripts are intentionally reloaded every time when `getLuaScriptsCfg` is called. + // It is important for the `reloadlua` console command. + try + { + auto file = std::ifstream(std::get(c)); + std::string fileContent(std::istreambuf_iterator(file), {}); + LuaUtil::parseOMWScripts(cfg, fileContent); + } + catch (std::exception& e) + { + Log(Debug::Error) << e.what(); + } + } + else + { + const ESM::LuaScriptsCfg& addition = std::get(c); + cfg.mScripts.insert(cfg.mScripts.end(), addition.mScripts.begin(), addition.mScripts.end()); + } + } + return cfg; + } - for (std::vector::const_iterator record = identifiers.begin(); record != identifiers.end(); ++record) - mIds[*record] = storeIt->first; + void ESMStore::setUp() + { + if (mIsSetUpDone) + throw std::logic_error("ESMStore::setUp() is called twice"); + mIsSetUpDone = true; + + for (const auto& [_, store] : mStoreImp->mRecNameToStore) + store->setUp(); + + getWritable().setUp(get()); + getWritable().setUp(); + getWritable().setUp(get()); + getWritable().updateLandPositions(get()); + getWritable().preprocessReferences(get()); + + rebuildIdsIndex(); + mStoreImp->mStaticIds = mStoreImp->mIds; + } + + void ESMStore::rebuildIdsIndex() + { + mStoreImp->mIds.clear(); + for (const auto& [recordType, store] : mStoreImp->mRecNameToStore) + { + if (isCacheableRecord(recordType)) + { + std::vector identifiers; + store->listIdentifier(identifiers); + for (auto& record : identifiers) + mStoreImp->mIds[record] = recordType; + } } } - if (mStaticIds.empty()) - mStaticIds = mIds; - - mSkills.setUp(); - mMagicEffects.setUp(); - mAttributes.setUp(); - mDialogs.setUp(); - - if (validateRecords) + void ESMStore::validateRecords(ESM::ReadersCache& readers) { validate(); - countRecords(); + countAllCellRefsAndMarkKeys(readers); } -} -void ESMStore::countRecords() -{ - if(!mRefCount.empty()) - return; - std::vector refs; - std::vector refIDs; - std::vector readers; - for(auto it = mCells.intBegin(); it != mCells.intEnd(); it++) - readRefs(*it, refs, refIDs, readers); - for(auto it = mCells.extBegin(); it != mCells.extEnd(); it++) - readRefs(*it, refs, refIDs, readers); - const auto lessByRefNum = [] (const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; }; - std::stable_sort(refs.begin(), refs.end(), lessByRefNum); - const auto equalByRefNum = [] (const Ref& l, const Ref& r) { return l.mRefNum == r.mRefNum; }; - const auto incrementRefCount = [&] (const Ref& value) + void ESMStore::countAllCellRefsAndMarkKeys(ESM::ReadersCache& readers) { - if (value.mRefID != deletedRefID) + // TODO: We currently need to read entire files here again. + // We should consider consolidating or deferring this reading. + if (!mRefCount.empty()) + return; + std::vector refs; + std::set keyIDs; + std::vector refIDs; + Store Cells = get(); + for (auto it = Cells.intBegin(); it != Cells.intEnd(); ++it) + readRefs(*it, refs, refIDs, keyIDs, readers); + for (auto it = Cells.extBegin(); it != Cells.extEnd(); ++it) + readRefs(*it, refs, refIDs, keyIDs, readers); + const auto lessByRefNum = [](const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; }; + std::stable_sort(refs.begin(), refs.end(), lessByRefNum); + const auto equalByRefNum = [](const Ref& l, const Ref& r) { return l.mRefNum == r.mRefNum; }; + const auto incrementRefCount = [&](const Ref& value) { + if (value.mRefID != deletedRefID) + { + ESM::RefId& refId = refIDs[value.mRefID]; + ++mRefCount[std::move(refId)]; + } + }; + Misc::forEachUnique(refs.rbegin(), refs.rend(), equalByRefNum, incrementRefCount); + auto& store = getWritable().mStatic; + for (const auto& id : keyIDs) { - std::string& refId = refIDs[value.mRefID]; - Misc::StringUtils::lowerCaseInPlace(refId); - ++mRefCount[std::move(refId)]; + auto it = store.find(id); + if (it != store.end()) + it->second.mData.mFlags |= ESM::Miscellaneous::Key; } - }; - Misc::forEachUnique(refs.rbegin(), refs.rend(), equalByRefNum, incrementRefCount); -} - -int ESMStore::getRefCount(const std::string& id) const -{ - const std::string lowerId = Misc::StringUtils::lowerCase(id); - auto it = mRefCount.find(lowerId); - if(it == mRefCount.end()) - return 0; - return it->second; -} - -void ESMStore::validate() -{ - std::vector npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic); - - for (const ESM::NPC &npc : npcsToReplace) - { - mNpcs.eraseStatic(npc.mId); - mNpcs.insertStatic(npc); } - // Validate spell effects for invalid arguments - std::vector spellsToReplace; - for (ESM::Spell spell : mSpells) + int ESMStore::getRefCount(const ESM::RefId& id) const { - if (spell.mEffects.mList.empty()) - continue; + auto it = mRefCount.find(id); + if (it == mRefCount.end()) + return 0; + return it->second; + } - bool changed = false; - auto iter = spell.mEffects.mList.begin(); - while (iter != spell.mEffects.mList.end()) + void ESMStore::validate() + { + auto& npcs = getWritable(); + std::vector npcsToReplace + = getNPCsToReplace(getWritable(), getWritable(), npcs.mStatic); + + for (const ESM::NPC& npc : npcsToReplace) { - const ESM::MagicEffect* mgef = mMagicEffects.search(iter->mEffectID); - if (!mgef) - { - Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index " << iter->mEffectID << ") present. Dropping the effect."; - iter = spell.mEffects.mList.erase(iter); - changed = true; - continue; - } + npcs.eraseStatic(npc.mId); + npcs.insertStatic(npc); + } - if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) + // Validate spell effects for invalid arguments + std::vector spellsToReplace; + auto& spells = getWritable(); + for (ESM::Spell spell : spells) + { + if (spell.mEffects.mList.empty()) + continue; + + bool changed = false; + auto iter = spell.mEffects.mList.begin(); + while (iter != spell.mEffects.mList.end()) { - if (iter->mAttribute != -1) + const ESM::MagicEffect* mgef = getWritable().search(iter->mEffectID); + if (!mgef) { - iter->mAttribute = -1; - Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << - " effect of spell '" << spell.mId << "' has an attribute argument present. Dropping the argument."; + Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index " + << iter->mEffectID << ") present. Dropping the effect."; + iter = spell.mEffects.mList.erase(iter); changed = true; + continue; } - } - else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) - { - if (iter->mSkill != -1) + + if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) + { + if (iter->mAttribute != -1) + { + iter->mAttribute = -1; + Log(Debug::Verbose) + << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId + << "' has an attribute argument present. Dropping the argument."; + changed = true; + } + } + else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) + { + if (iter->mSkill != -1) + { + iter->mSkill = -1; + Log(Debug::Verbose) + << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId + << "' has a skill argument present. Dropping the argument."; + changed = true; + } + } + else if (iter->mSkill != -1 || iter->mAttribute != -1) { iter->mSkill = -1; - Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << - " effect of spell '" << spell.mId << "' has a skill argument present. Dropping the argument."; + iter->mAttribute = -1; + Log(Debug::Verbose) << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" + << spell.mId << "' has argument(s) present. Dropping the argument(s)."; changed = true; } - } - else if (iter->mSkill != -1 || iter->mAttribute != -1) - { - iter->mSkill = -1; - iter->mAttribute = -1; - Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << - " effect of spell '" << spell.mId << "' has argument(s) present. Dropping the argument(s)."; - changed = true; + + ++iter; } - ++iter; + if (changed) + spellsToReplace.emplace_back(spell); } - if (changed) - spellsToReplace.emplace_back(spell); + for (const ESM::Spell& spell : spellsToReplace) + { + spells.eraseStatic(spell.mId); + spells.insertStatic(spell); + } } - for (const ESM::Spell &spell : spellsToReplace) + void ESMStore::movePlayerRecord() { - mSpells.eraseStatic(spell.mId); - mSpells.insertStatic(spell); + auto& npcs = getWritable(); + auto player = npcs.find(ESM::RefId::stringRefId("Player")); + npcs.insert(*player); } -} -void ESMStore::validateDynamic() -{ - std::vector npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mDynamic); + void ESMStore::validateDynamic() + { + auto& npcs = getWritable(); + auto& scripts = getWritable(); - for (const ESM::NPC &npc : npcsToReplace) - mNpcs.insert(npc); -} + std::vector npcsToReplace + = getNPCsToReplace(getWritable(), getWritable(), npcs.mDynamic); + + for (const ESM::NPC& npc : npcsToReplace) + npcs.insert(npc); + + removeMissingScripts(scripts, getWritable().mDynamic); + removeMissingScripts(scripts, getWritable().mDynamic); + removeMissingScripts(scripts, getWritable().mDynamic); + removeMissingScripts(scripts, getWritable().mDynamic); + + removeMissingObjects(getWritable()); + removeMissingObjects(getWritable()); + } + + // Leveled lists can be modified by scripts. This removes items that no longer exist (presumably because the + // plugin was removed) from modified lists + template + void ESMStore::removeMissingObjects(Store& store) + { + for (auto& entry : store.mDynamic) + { + auto first = std::remove_if(entry.second.mList.begin(), entry.second.mList.end(), [&](const auto& item) { + if (!find(item.mId)) + { + Log(Debug::Verbose) << "Leveled list " << entry.first << " has nonexistent object " << item.mId + << ", ignoring it."; + return true; + } + return false; + }); + entry.second.mList.erase(first, entry.second.mList.end()); + } + } int ESMStore::countSavedGameRecords() const { return 1 // DYNA (dynamic name counter) - +mPotions.getDynamicSize() - +mArmors.getDynamicSize() - +mBooks.getDynamicSize() - +mClasses.getDynamicSize() - +mClothes.getDynamicSize() - +mEnchants.getDynamicSize() - +mNpcs.getDynamicSize() - +mSpells.getDynamicSize() - +mWeapons.getDynamicSize() - +mCreatureLists.getDynamicSize() - +mItemLists.getDynamicSize() - +mCreatures.getDynamicSize() - +mContainers.getDynamicSize(); + + get().getDynamicSize() + get().getDynamicSize() + + get().getDynamicSize() + get().getDynamicSize() + + get().getDynamicSize() + get().getDynamicSize() + + get().getDynamicSize() + get().getDynamicSize() + + get().getDynamicSize() + get().getDynamicSize() + + get().getDynamicSize() + get().getDynamicSize() + + get().getDynamicSize() + get().getDynamicSize() + + get().getDynamicSize(); } - void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const + void ESMStore::write(ESM::ESMWriter& writer, Loading::Listener& progress) const { writer.startRecord(ESM::REC_DYNA); writer.startSubRecord("COUN"); @@ -396,26 +654,31 @@ void ESMStore::validateDynamic() writer.endRecord("COUN"); writer.endRecord(ESM::REC_DYNA); - mPotions.write (writer, progress); - mArmors.write (writer, progress); - mBooks.write (writer, progress); - mClasses.write (writer, progress); - mClothes.write (writer, progress); - mEnchants.write (writer, progress); - mSpells.write (writer, progress); - mWeapons.write (writer, progress); - mNpcs.write (writer, progress); - mItemLists.write (writer, progress); - mCreatureLists.write (writer, progress); - mCreatures.write (writer, progress); - mContainers.write (writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); + get().write(writer, progress); } - bool ESMStore::readRecord (ESM::ESMReader& reader, uint32_t type) + bool ESMStore::readRecord(ESM::ESMReader& reader, uint32_t type_id) { + ESM::RecNameInts type = (ESM::RecNameInts)type_id; switch (type) { case ESM::REC_ALCH: + case ESM::REC_MISC: + case ESM::REC_ACTI: case ESM::REC_ARMO: case ESM::REC_BOOK: case ESM::REC_CLAS: @@ -425,12 +688,12 @@ void ESMStore::validateDynamic() case ESM::REC_WEAP: case ESM::REC_LEVI: case ESM::REC_LEVC: - mStores[type]->read (reader); + mStoreImp->mRecNameToStore[type]->read(reader); return true; case ESM::REC_NPC_: case ESM::REC_CREA: case ESM::REC_CONT: - mStores[type]->read (reader, true); + mStoreImp->mRecNameToStore[type]->read(reader, true); return true; case ESM::REC_DYNA: @@ -446,18 +709,14 @@ void ESMStore::validateDynamic() void ESMStore::checkPlayer() { - setUp(); + const ESM::NPC* player = get().find(ESM::RefId::stringRefId("Player")); - const ESM::NPC *player = mNpcs.find ("player"); - - if (!mRaces.find (player->mRace) || - !mClasses.find (player->mClass)) - throw std::runtime_error ("Invalid player record (race or class unavailable"); + if (!get().find(player->mRace) || !get().find(player->mClass)) + throw std::runtime_error("Invalid player record (race or class unavailable"); } - std::pair, bool> ESMStore::getSpellList(const std::string& originalId) const + std::pair, bool> ESMStore::getSpellList(const ESM::RefId& id) const { - const std::string id = Misc::StringUtils::lowerCase(originalId); auto result = mSpellListCache.find(id); std::shared_ptr ptr; if (result != mSpellListCache.end()) @@ -469,9 +728,37 @@ void ESMStore::validateDynamic() if (result != mSpellListCache.end()) result->second = ptr; else - mSpellListCache.insert({id, ptr}); - return {ptr, false}; + mSpellListCache.insert({ id, ptr }); + return { ptr, false }; } - return {ptr, true}; + return { ptr, true }; } + + template <> + const ESM::Cell* ESMStore::insert(const ESM::Cell& cell) + { + return getWritable().insert(cell); + } + + template <> + const ESM::NPC* ESMStore::insert(const ESM::NPC& npc) + { + + auto& npcs = getWritable(); + if (npc.mId == "Player") + { + return npcs.insert(npc); + } + const ESM::RefId id = ESM::RefId::generated(mDynamicCount++); + if (npcs.search(id) != nullptr) + throw std::runtime_error("Try to override existing record: " + id.toDebugString()); + ESM::NPC record = npc; + + record.mId = id; + + ESM::NPC* ptr = npcs.insert(record); + mStoreImp->mIds[ptr->mId] = ESM::REC_NPC_; + return ptr; + } + } // end namespace diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index f9498e06a..358bde418 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -1,12 +1,17 @@ #ifndef OPENMW_MWWORLD_ESMSTORE_H #define OPENMW_MWWORLD_ESMSTORE_H +#include #include -#include #include +#include #include -#include +#include +#include +#include +#include + #include "store.hpp" namespace Loading @@ -19,260 +24,270 @@ namespace MWMechanics class SpellList; } +namespace ESM4 +{ + class Reader; + struct Static; + struct Cell; + struct Light; + struct Reference; + struct Activator; + struct Potion; + struct Ammunition; + struct Armor; + struct Book; + struct Clothing; + struct Container; + struct Door; + struct Furniture; + struct Ingredient; + struct MiscItem; + struct Tree; + struct Weapon; + struct World; + struct Land; +} + +namespace ESM +{ + class ReadersCache; + struct Activator; + struct Potion; + struct Apparatus; + struct Armor; + struct BodyPart; + struct Book; + struct BirthSign; + struct Class; + struct Clothing; + struct Container; + struct Creature; + struct Dialogue; + struct Door; + struct Enchantment; + struct Faction; + struct Global; + struct Ingredient; + struct CreatureLevList; + struct ItemLevList; + struct Light; + struct Lockpick; + struct Miscellaneous; + struct NPC; + struct Probe; + struct Race; + struct Region; + struct Repair; + struct SoundGenerator; + struct Sound; + struct Spell; + struct StartScript; + struct Static; + struct Weapon; + struct GameSetting; + class Script; + struct Cell; + struct Land; + struct LandTexture; + struct Pathgrid; + struct MagicEffect; + struct Skill; + struct Attribute; +} + namespace MWWorld { + struct ESMStoreImp; + class ESMStore { - Store mActivators; - Store mPotions; - Store mAppas; - Store mArmors; - Store mBodyParts; - Store mBooks; - Store mBirthSigns; - Store mClasses; - Store mClothes; - Store mContainers; - Store mCreatures; - Store mDialogs; - Store mDoors; - Store mEnchants; - Store mFactions; - Store mGlobals; - Store mIngreds; - Store mCreatureLists; - Store mItemLists; - Store mLights; - Store mLockpicks; - Store mMiscItems; - Store mNpcs; - Store mProbes; - Store mRaces; - Store mRegions; - Store mRepairs; - Store mSoundGens; - Store mSounds; - Store mSpells; - Store mStartScripts; - Store mStatics; - Store mWeapons; + friend struct ESMStoreImp; // This allows StoreImp to extend esmstore without beeing included everywhere + public: + using StoreTuple = std::tuple, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, + Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, + Store, Store, Store, Store, + Store, - Store mGameSettings; - Store mScripts; + // Lists that need special rules + Store, Store, Store, Store, - // Lists that need special rules - Store mCells; - Store mLands; - Store mLandTextures; - Store mPathgrids; + Store, Store, - Store mMagicEffects; - Store mSkills; + // Special entry which is hardcoded and not loaded from an ESM + Store, - // Special entry which is hardcoded and not loaded from an ESM - Store mAttributes; + Store, Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, + Store, Store, Store, Store, Store>; - // Lookup of all IDs. Makes looking up references faster. Just - // maps the id name to the record type. - std::map mIds; - std::map mStaticIds; + private: + template + static constexpr std::size_t getTypeIndex() + { + static_assert(Misc::TupleHasType, StoreTuple>::value); + return Misc::TupleTypeIndex, StoreTuple>::value; + } - std::unordered_map mRefCount; + std::unique_ptr mStoreImp; - std::map mStores; + std::unordered_map mRefCount; + + std::vector mStores; + std::vector mDynamicStores; unsigned int mDynamicCount; - mutable std::map > mSpellListCache; + mutable std::unordered_map> mSpellListCache; + + template + Store& getWritable() + { + return static_cast&>(*mStores[getTypeIndex()]); + } /// Validate entries in store after setup void validate(); - void countRecords(); + void countAllCellRefsAndMarkKeys(ESM::ReadersCache& readers); + + template + void removeMissingObjects(Store& store); + + void setIdType(const ESM::RefId& id, ESM::RecNameInts type); + + using LuaContent = std::variant; // path to an omwscripts file + std::vector mLuaContent; + + bool mIsSetUpDone = false; + public: + void addOMWScripts(std::filesystem::path filePath) { mLuaContent.push_back(std::move(filePath)); } + ESM::LuaScriptsCfg getLuaScriptsCfg() const; + /// \todo replace with SharedIterator - typedef std::map::const_iterator iterator; + typedef std::vector::const_iterator iterator; - iterator begin() const { - return mStores.begin(); - } + iterator begin() const { return mDynamicStores.begin(); } - iterator end() const { - return mStores.end(); - } + iterator end() const { return mDynamicStores.end(); } /// Look up the given ID in 'all'. Returns 0 if not found. - /// \note id must be in lower case. - int find(const std::string &id) const - { - std::map::const_iterator it = mIds.find(id); - if (it == mIds.end()) { - return 0; - } - return it->second; - } - int findStatic(const std::string &id) const - { - std::map::const_iterator it = mStaticIds.find(id); - if (it == mStaticIds.end()) { - return 0; - } - return it->second; - } + int find(const ESM::RefId& id) const; - ESMStore() - : mDynamicCount(0) - { - mStores[ESM::REC_ACTI] = &mActivators; - mStores[ESM::REC_ALCH] = &mPotions; - mStores[ESM::REC_APPA] = &mAppas; - mStores[ESM::REC_ARMO] = &mArmors; - mStores[ESM::REC_BODY] = &mBodyParts; - mStores[ESM::REC_BOOK] = &mBooks; - mStores[ESM::REC_BSGN] = &mBirthSigns; - mStores[ESM::REC_CELL] = &mCells; - mStores[ESM::REC_CLAS] = &mClasses; - mStores[ESM::REC_CLOT] = &mClothes; - mStores[ESM::REC_CONT] = &mContainers; - mStores[ESM::REC_CREA] = &mCreatures; - mStores[ESM::REC_DIAL] = &mDialogs; - mStores[ESM::REC_DOOR] = &mDoors; - mStores[ESM::REC_ENCH] = &mEnchants; - mStores[ESM::REC_FACT] = &mFactions; - mStores[ESM::REC_GLOB] = &mGlobals; - mStores[ESM::REC_GMST] = &mGameSettings; - mStores[ESM::REC_INGR] = &mIngreds; - mStores[ESM::REC_LAND] = &mLands; - mStores[ESM::REC_LEVC] = &mCreatureLists; - mStores[ESM::REC_LEVI] = &mItemLists; - mStores[ESM::REC_LIGH] = &mLights; - mStores[ESM::REC_LOCK] = &mLockpicks; - mStores[ESM::REC_LTEX] = &mLandTextures; - mStores[ESM::REC_MISC] = &mMiscItems; - mStores[ESM::REC_NPC_] = &mNpcs; - mStores[ESM::REC_PGRD] = &mPathgrids; - mStores[ESM::REC_PROB] = &mProbes; - mStores[ESM::REC_RACE] = &mRaces; - mStores[ESM::REC_REGN] = &mRegions; - mStores[ESM::REC_REPA] = &mRepairs; - mStores[ESM::REC_SCPT] = &mScripts; - mStores[ESM::REC_SNDG] = &mSoundGens; - mStores[ESM::REC_SOUN] = &mSounds; - mStores[ESM::REC_SPEL] = &mSpells; - mStores[ESM::REC_SSCR] = &mStartScripts; - mStores[ESM::REC_STAT] = &mStatics; - mStores[ESM::REC_WEAP] = &mWeapons; + int findStatic(const ESM::RefId& id) const; - mPathgrids.setCells(mCells); - } + ESMStore(); + ~ESMStore(); - void clearDynamic () - { - for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) - it->second->clearDynamic(); + void clearDynamic(); + void rebuildIdsIndex(); - movePlayerRecord(); - } - - void movePlayerRecord () - { - auto player = mNpcs.find("player"); - mNpcs.insert(*player); - } + void movePlayerRecord(); /// Validate entries in store after loading a save void validateDynamic(); - void load(ESM::ESMReader &esm, Loading::Listener* listener); + void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); + void loadESM4(ESM4::Reader& esm); template - const Store &get() const { - throw std::runtime_error("Storage for this type not exist"); + const Store& get() const + { + return static_cast&>(*mStores[getTypeIndex()]); } /// Insert a custom record (i.e. with a generated ID that will not clash will pre-existing records) + /// \return pointer to created record template - const T *insert(const T &x) + const T* insert(const T& x) { - const std::string id = "$dynamic" + std::to_string(mDynamicCount++); + const ESM::RefId id = ESM::RefId::generated(mDynamicCount++); - Store &store = const_cast &>(get()); + Store& store = getWritable(); if (store.search(id) != nullptr) - { - const std::string msg = "Try to override existing record '" + id + "'"; - throw std::runtime_error(msg); - } + throw std::runtime_error("Try to override existing record: " + id.toDebugString()); T record = x; record.mId = id; - T *ptr = store.insert(record); - for (iterator it = mStores.begin(); it != mStores.end(); ++it) { - if (it->second == &store) { - mIds[ptr->mId] = it->first; - } + T* ptr = store.insert(record); + if constexpr (std::is_convertible_v*, DynamicStore*>) + { + setIdType(ptr->mId, T::sRecordId); } return ptr; } /// Insert a record with set ID, and allow it to override a pre-existing static record. template - const T *overrideRecord(const T &x) { - Store &store = const_cast &>(get()); + const T* overrideRecord(const T& x) + { + Store& store = getWritable(); - T *ptr = store.insert(x); - for (iterator it = mStores.begin(); it != mStores.end(); ++it) { - if (it->second == &store) { - mIds[ptr->mId] = it->first; - } + T* ptr = store.insert(x); + if constexpr (std::is_convertible_v*, DynamicStore*>) + { + setIdType(ptr->mId, T::sRecordId); } return ptr; } template - const T *insertStatic(const T &x) + const T* insertStatic(const T& x) { - const std::string id = "$dynamic" + std::to_string(mDynamicCount++); + Store& store = getWritable(); + if (store.search(x.mId) != nullptr) + throw std::runtime_error("Try to override existing record " + x.mId.toDebugString()); - Store &store = const_cast &>(get()); - if (store.search(id) != nullptr) + T* ptr = store.insertStatic(x); + if constexpr (std::is_convertible_v*, DynamicStore*>) { - const std::string msg = "Try to override existing record '" + id + "'"; - throw std::runtime_error(msg); - } - T record = x; - - T *ptr = store.insertStatic(record); - for (iterator it = mStores.begin(); it != mStores.end(); ++it) { - if (it->second == &store) { - mIds[ptr->mId] = it->first; - } + setIdType(ptr->mId, T::sRecordId); } return ptr; } // This method must be called once, after loading all master/plugin files. This can only be done // from the outside, so it must be public. - void setUp(bool validateRecords = false); + void setUp(); + void validateRecords(ESM::ReadersCache& readers); int countSavedGameRecords() const; - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, uint32_t type); + bool readRecord(ESM::ESMReader& reader, uint32_t type); ///< \return Known type? // To be called when we are done with dynamic record loading void checkPlayer(); /// @return The number of instances defined in the base files. Excludes changes from the save file. - int getRefCount(const std::string& id) const; + int getRefCount(const ESM::RefId& id) const; /// Actors with the same ID share spells, abilities, etc. /// @return The shared spell list to use for this actor and whether or not it has already been initialized. - std::pair, bool> getSpellList(const std::string& id) const; + std::pair, bool> getSpellList(const ESM::RefId& id) const; + }; + template <> + const ESM::Cell* ESMStore::insert(const ESM::Cell& cell); + + template <> + const ESM::NPC* ESMStore::insert(const ESM::NPC& npc); + + template > + struct HasRecordId : std::false_type + { }; +<<<<<<< HEAD /* Start of tes3mp addition @@ -308,236 +323,12 @@ namespace MWWorld template <> inline const ESM::NPC *ESMStore::insert(const ESM::NPC &npc) +======= + template + struct HasRecordId> : std::true_type +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 { - const std::string id = "$dynamic" + std::to_string(mDynamicCount++); - - if (Misc::StringUtils::ciEqual(npc.mId, "player")) - { - return mNpcs.insert(npc); - } - else if (mNpcs.search(id) != nullptr) - { - const std::string msg = "Try to override existing record '" + id + "'"; - throw std::runtime_error(msg); - } - ESM::NPC record = npc; - - record.mId = id; - - ESM::NPC *ptr = mNpcs.insert(record); - mIds[ptr->mId] = ESM::REC_NPC_; - return ptr; - } - - template <> - inline const Store &ESMStore::get() const { - return mActivators; - } - - template <> - inline const Store &ESMStore::get() const { - return mPotions; - } - - template <> - inline const Store &ESMStore::get() const { - return mAppas; - } - - template <> - inline const Store &ESMStore::get() const { - return mArmors; - } - - template <> - inline const Store &ESMStore::get() const { - return mBodyParts; - } - - template <> - inline const Store &ESMStore::get() const { - return mBooks; - } - - template <> - inline const Store &ESMStore::get() const { - return mBirthSigns; - } - - template <> - inline const Store &ESMStore::get() const { - return mClasses; - } - - template <> - inline const Store &ESMStore::get() const { - return mClothes; - } - - template <> - inline const Store &ESMStore::get() const { - return mContainers; - } - - template <> - inline const Store &ESMStore::get() const { - return mCreatures; - } - - template <> - inline const Store &ESMStore::get() const { - return mDialogs; - } - - template <> - inline const Store &ESMStore::get() const { - return mDoors; - } - - template <> - inline const Store &ESMStore::get() const { - return mEnchants; - } - - template <> - inline const Store &ESMStore::get() const { - return mFactions; - } - - template <> - inline const Store &ESMStore::get() const { - return mGlobals; - } - - template <> - inline const Store &ESMStore::get() const { - return mIngreds; - } - - template <> - inline const Store &ESMStore::get() const { - return mCreatureLists; - } - - template <> - inline const Store &ESMStore::get() const { - return mItemLists; - } - - template <> - inline const Store &ESMStore::get() const { - return mLights; - } - - template <> - inline const Store &ESMStore::get() const { - return mLockpicks; - } - - template <> - inline const Store &ESMStore::get() const { - return mMiscItems; - } - - template <> - inline const Store &ESMStore::get() const { - return mNpcs; - } - - template <> - inline const Store &ESMStore::get() const { - return mProbes; - } - - template <> - inline const Store &ESMStore::get() const { - return mRaces; - } - - template <> - inline const Store &ESMStore::get() const { - return mRegions; - } - - template <> - inline const Store &ESMStore::get() const { - return mRepairs; - } - - template <> - inline const Store &ESMStore::get() const { - return mSoundGens; - } - - template <> - inline const Store &ESMStore::get() const { - return mSounds; - } - - template <> - inline const Store &ESMStore::get() const { - return mSpells; - } - - template <> - inline const Store &ESMStore::get() const { - return mStartScripts; - } - - template <> - inline const Store &ESMStore::get() const { - return mStatics; - } - - template <> - inline const Store &ESMStore::get() const { - return mWeapons; - } - - template <> - inline const Store &ESMStore::get() const { - return mGameSettings; - } - - template <> - inline const Store &ESMStore::get() const { - return mScripts; - } - - template <> - inline const Store &ESMStore::get() const { - return mCells; - } - - template <> - inline const Store &ESMStore::get() const { - return mLands; - } - - template <> - inline const Store &ESMStore::get() const { - return mLandTextures; - } - - template <> - inline const Store &ESMStore::get() const { - return mPathgrids; - } - - template <> - inline const Store &ESMStore::get() const { - return mMagicEffects; - } - - template <> - inline const Store &ESMStore::get() const { - return mSkills; - } - - template <> - inline const Store &ESMStore::get() const { - return mAttributes; - } + }; } #endif diff --git a/apps/openmw/mwworld/failedaction.cpp b/apps/openmw/mwworld/failedaction.cpp index ec8314712..05bec1207 100644 --- a/apps/openmw/mwworld/failedaction.cpp +++ b/apps/openmw/mwworld/failedaction.cpp @@ -7,13 +7,15 @@ namespace MWWorld { - FailedAction::FailedAction(const std::string &msg, const Ptr& target) - : Action(false, target), mMessage(msg) - { } - - void FailedAction::executeImp(const Ptr &actor) + FailedAction::FailedAction(std::string_view msg, const Ptr& target) + : Action(false, target) + , mMessage(msg) { - if(actor == MWMechanics::getPlayer() && !mMessage.empty()) + } + + void FailedAction::executeImp(const Ptr& actor) + { + if (actor == MWMechanics::getPlayer() && !mMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(mMessage); } } diff --git a/apps/openmw/mwworld/failedaction.hpp b/apps/openmw/mwworld/failedaction.hpp index 2a201cdb3..329a59443 100644 --- a/apps/openmw/mwworld/failedaction.hpp +++ b/apps/openmw/mwworld/failedaction.hpp @@ -8,12 +8,12 @@ namespace MWWorld { class FailedAction : public Action { - std::string mMessage; + std::string_view mMessage; - void executeImp(const Ptr &actor) override; + void executeImp(const Ptr& actor) override; public: - FailedAction(const std::string &message = std::string(), const Ptr& target = Ptr()); + FailedAction(std::string_view message = {}, const Ptr& target = Ptr()); }; } diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index b840e1e11..c289f8672 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -2,35 +2,34 @@ #include -#include -#include -#include +#include +#include #include "esmstore.hpp" namespace MWWorld { - Globals::Collection::const_iterator Globals::find (const std::string& name) const + Globals::Collection::const_iterator Globals::find(std::string_view name) const { - Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); + Collection::const_iterator iter = mVariables.find(name); - if (iter==mVariables.end()) - throw std::runtime_error ("unknown global variable: " + name); + if (iter == mVariables.end()) + throw std::runtime_error("unknown global variable: " + std::string{ name }); return iter; } - Globals::Collection::iterator Globals::find (const std::string& name) + Globals::Collection::iterator Globals::find(std::string_view name) { - Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); + Collection::iterator iter = mVariables.find(name); - if (iter==mVariables.end()) - throw std::runtime_error ("unknown global variable: " + name); + if (iter == mVariables.end()) + throw std::runtime_error("unknown global variable: " + std::string{ name }); return iter; } - void Globals::fill (const MWWorld::ESMStore& store) + void Globals::fill(const MWWorld::ESMStore& store) { mVariables.clear(); @@ -38,34 +37,38 @@ namespace MWWorld for (const ESM::Global& esmGlobal : globals) { - mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (esmGlobal.mId), esmGlobal)); + mVariables.emplace(esmGlobal.mId, esmGlobal); } } - const ESM::Variant& Globals::operator[] (const std::string& name) const + const ESM::Variant& Globals::operator[](GlobalVariableName name) const { - return find (Misc::StringUtils::lowerCase (name))->second.mValue; + return find(name.getValue())->second.mValue; } - ESM::Variant& Globals::operator[] (const std::string& name) + ESM::Variant& Globals::operator[](GlobalVariableName name) { - return find (Misc::StringUtils::lowerCase (name))->second.mValue; + return find(name.getValue())->second.mValue; } - char Globals::getType (const std::string& name) const + char Globals::getType(GlobalVariableName name) const { - Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); + Collection::const_iterator iter = mVariables.find(name.getValue()); - if (iter==mVariables.end()) + if (iter == mVariables.end()) return ' '; switch (iter->second.mValue.getType()) { - case ESM::VT_Short: return 's'; - case ESM::VT_Long: return 'l'; - case ESM::VT_Float: return 'f'; + case ESM::VT_Short: + return 's'; + case ESM::VT_Long: + return 'l'; + case ESM::VT_Float: + return 'f'; - default: return ' '; + default: + return ' '; } } @@ -74,19 +77,19 @@ namespace MWWorld return mVariables.size(); } - void Globals::write (ESM::ESMWriter& writer, Loading::Listener& progress) const + void Globals::write(ESM::ESMWriter& writer, Loading::Listener& progress) const { - for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) + for (const auto& variable : mVariables) { - writer.startRecord (ESM::REC_GLOB); - iter->second.save (writer); - writer.endRecord (ESM::REC_GLOB); + writer.startRecord(ESM::REC_GLOB); + variable.second.save(writer); + writer.endRecord(ESM::REC_GLOB); } } - bool Globals::readRecord (ESM::ESMReader& reader, uint32_t type) + bool Globals::readRecord(ESM::ESMReader& reader, uint32_t type) { - if (type==ESM::REC_GLOB) + if (type == ESM::REC_GLOB) { ESM::Global global; bool isDeleted = false; @@ -94,10 +97,8 @@ namespace MWWorld // This readRecord() method is used when reading a saved game. // Deleted globals can't appear there, so isDeleted will be ignored here. global.load(reader, isDeleted); - Misc::StringUtils::lowerCaseInPlace(global.mId); - Collection::iterator iter = mVariables.find (global.mId); - if (iter!=mVariables.end()) + if (const auto iter = mVariables.find(global.mId); iter != mVariables.end()) iter->second = global; return true; diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index 9ad3f7475..32135b32e 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -1,13 +1,15 @@ #ifndef GAME_MWWORLD_GLOBALS_H #define GAME_MWWORLD_GLOBALS_H -#include -#include +#include #include +#include +#include -#include +#include +#include -#include +#include "globalvariablename.hpp" namespace ESM { @@ -26,28 +28,45 @@ namespace MWWorld class Globals { - private: + private: + using Collection = std::map>; - typedef std::map Collection; + Collection mVariables; // type, value - Collection mVariables; // type, value + Collection::const_iterator find(std::string_view name) const; - Collection::const_iterator find (const std::string& name) const; + Collection::iterator find(std::string_view name); - Collection::iterator find (const std::string& name); + public: + static constexpr GlobalVariableName sDaysPassed{ "dayspassed" }; + static constexpr GlobalVariableName sGameHour{ "gamehour" }; + static constexpr GlobalVariableName sDay{ "day" }; + static constexpr GlobalVariableName sMonth{ "month" }; + static constexpr GlobalVariableName sYear{ "year" }; + static constexpr GlobalVariableName sTimeScale{ "timescale" }; + static constexpr GlobalVariableName sCharGenState{ "chargenstate" }; + static constexpr GlobalVariableName sPCHasCrimeGold{ "pchascrimegold" }; + static constexpr GlobalVariableName sPCHasGoldDiscount{ "pchasgolddiscount" }; + static constexpr GlobalVariableName sCrimeGoldDiscount{ "crimegolddiscount" }; + static constexpr GlobalVariableName sCrimeGoldTurnIn{ "crimegoldturnin" }; + static constexpr GlobalVariableName sPCHasTurnIn{ "pchasturnin" }; + static constexpr GlobalVariableName sPCKnownWerewolf{ "pcknownwerewolf" }; + static constexpr GlobalVariableName sWerewolfClawMult{ "werewolfclawmult" }; + static constexpr GlobalVariableName sPCRace{ "pcrace" }; - public: + const ESM::Variant& operator[](GlobalVariableName name) const; - const ESM::Variant& operator[] (const std::string& name) const; + ESM::Variant& operator[](GlobalVariableName name); - ESM::Variant& operator[] (const std::string& name); + char getType(GlobalVariableName name) const; + ///< If there is no global variable with this name, ' ' is returned. - char getType (const std::string& name) const; - ///< If there is no global variable with this name, ' ' is returned. + void fill(const MWWorld::ESMStore& store); + ///< Replace variables with variables from \a store with default values. - void fill (const MWWorld::ESMStore& store); - ///< Replace variables with variables from \a store with default values. + int countSavedGameRecords() const; +<<<<<<< HEAD int countSavedGameRecords() const; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; @@ -76,7 +95,14 @@ namespace MWWorld /* End of tes3mp addition */ +======= + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 + bool readRecord(ESM::ESMReader& reader, uint32_t type); + ///< Records for variables that do not exist are dropped silently. + /// + /// \return Known type? }; } diff --git a/apps/openmw/mwworld/globalvariablename.hpp b/apps/openmw/mwworld/globalvariablename.hpp new file mode 100644 index 000000000..50052da69 --- /dev/null +++ b/apps/openmw/mwworld/globalvariablename.hpp @@ -0,0 +1,44 @@ +#ifndef OPENMW_MWWORLD_GLOBALVARIABLENAME_H +#define OPENMW_MWWORLD_GLOBALVARIABLENAME_H + +#include +#include +#include + +namespace MWWorld +{ + class Globals; + + class GlobalVariableName + { + public: + GlobalVariableName(const std::string& value) + : mValue(value) + { + } + + GlobalVariableName(std::string_view value) + : mValue(value) + { + } + + std::string_view getValue() const { return mValue; } + + friend bool operator==(const GlobalVariableName& lhs, const GlobalVariableName& rhs) noexcept + { + return lhs.mValue == rhs.mValue; + } + + private: + std::string_view mValue; + + explicit constexpr GlobalVariableName(const char* value) + : mValue(value) + { + } + + friend Globals; + }; +} + +#endif diff --git a/apps/openmw/mwworld/groundcoverstore.cpp b/apps/openmw/mwworld/groundcoverstore.cpp new file mode 100644 index 000000000..85b9376f0 --- /dev/null +++ b/apps/openmw/mwworld/groundcoverstore.cpp @@ -0,0 +1,76 @@ +#include "groundcoverstore.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "store.hpp" + +namespace MWWorld +{ + void GroundcoverStore::init(const Store& statics, const Files::Collections& fileCollections, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) + { + ::EsmLoader::Query query; + query.mLoadStatics = true; + query.mLoadCells = true; + + ESM::ReadersCache readers; + const ::EsmLoader::EsmData content + = ::EsmLoader::loadEsmData(query, groundcoverFiles, fileCollections, readers, encoder, listener); + + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + static constexpr std::string_view prefix = "grass\\"; + for (const ESM::Static& stat : statics) + { + std::string model = Misc::StringUtils::lowerCase(stat.mModel); + std::replace(model.begin(), model.end(), '/', '\\'); + if (!model.starts_with(prefix)) + continue; + mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model, vfs); + } + + for (const ESM::Static& stat : content.mStatics) + { + std::string model = Misc::StringUtils::lowerCase(stat.mModel); + std::replace(model.begin(), model.end(), '/', '\\'); + if (!model.starts_with(prefix)) + continue; + mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model, vfs); + } + + for (const ESM::Cell& cell : content.mCells) + { + if (!cell.isExterior()) + continue; + auto cellIndex = std::make_pair(cell.getGridX(), cell.getGridY()); + mCellContexts[cellIndex] = std::move(cell.mContextList); + } + } + + std::string GroundcoverStore::getGroundcoverModel(const ESM::RefId& id) const + { + auto search = mMeshCache.find(id); + if (search == mMeshCache.end()) + return std::string(); + + return search->second; + } + + void GroundcoverStore::initCell(ESM::Cell& cell, int cellX, int cellY) const + { + cell.blank(); + + auto searchCell = mCellContexts.find(std::make_pair(cellX, cellY)); + if (searchCell != mCellContexts.end()) + cell.mContextList = searchCell->second; + } +} diff --git a/apps/openmw/mwworld/groundcoverstore.hpp b/apps/openmw/mwworld/groundcoverstore.hpp new file mode 100644 index 000000000..2f34aa967 --- /dev/null +++ b/apps/openmw/mwworld/groundcoverstore.hpp @@ -0,0 +1,52 @@ +#ifndef GAME_MWWORLD_GROUNDCOVER_STORE_H +#define GAME_MWWORLD_GROUNDCOVER_STORE_H + +#include +#include +#include +#include + +namespace ESM +{ + struct ESM_Context; + struct Static; + struct Cell; +} + +namespace Loading +{ + class Listener; +} + +namespace Files +{ + class Collections; +} + +namespace ToUTF8 +{ + class Utf8Encoder; +} + +namespace MWWorld +{ + template + class Store; + + class GroundcoverStore + { + private: + std::map mMeshCache; + std::map, std::vector> mCellContexts; + + public: + void init(const Store& statics, const Files::Collections& fileCollections, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, + Loading::Listener* listener); + + std::string getGroundcoverModel(const ESM::RefId& id) const; + void initCell(ESM::Cell& cell, int cellX, int cellY) const; + }; +} + +#endif diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index b7f4e2f74..e2b2193d2 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -1,12 +1,9 @@ #include "inventorystore.hpp" -#include #include +#include -#include -#include -#include -#include +#include /* Start of tes3mp addition @@ -23,61 +20,60 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/spellresistance.hpp" -#include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/weapontype.hpp" -#include "esmstore.hpp" #include "class.hpp" +#include "esmstore.hpp" -void MWWorld::InventoryStore::copySlots (const InventoryStore& store) +void MWWorld::InventoryStore::copySlots(const InventoryStore& store) { // some const-trickery, required because of a flaw in the handling of MW-references and the // resulting workarounds - for (std::vector::const_iterator iter ( - const_cast (store).mSlots.begin()); - iter!=const_cast (store).mSlots.end(); ++iter) + for (std::vector::const_iterator iter(const_cast(store).mSlots.begin()); + iter != const_cast(store).mSlots.end(); ++iter) { - std::size_t distance = std::distance (const_cast (store).begin(), *iter); + std::size_t distance = std::distance(const_cast(store).begin(), *iter); ContainerStoreIterator slot = begin(); - std::advance (slot, distance); + std::advance(slot, distance); - mSlots.push_back (slot); + mSlots.push_back(slot); } // some const-trickery, required because of a flaw in the handling of MW-references and the // resulting workarounds - std::size_t distance = std::distance (const_cast (store).begin(), const_cast (store).mSelectedEnchantItem); + std::size_t distance = std::distance( + const_cast(store).begin(), const_cast(store).mSelectedEnchantItem); ContainerStoreIterator slot = begin(); - std::advance (slot, distance); + std::advance(slot, distance); mSelectedEnchantItem = slot; } -void MWWorld::InventoryStore::initSlots (TSlots& slots_) +void MWWorld::InventoryStore::initSlots(TSlots& slots_) { - for (int i=0; i (mSlots.size()); ++i) - if (mSlots[i].getType()!=-1 && mSlots[i]->getBase()==&ref) + for (int i = 0; i < static_cast(mSlots.size()); ++i) + if (mSlots[i].getType() != -1 && mSlots[i]->getBase() == &ref) { inventory.mEquipmentSlots[index] = i; } - if (mSelectedEnchantItem.getType()!=-1 && mSelectedEnchantItem->getBase() == &ref) + if (mSelectedEnchantItem.getType() != -1 && mSelectedEnchantItem->getBase() == &ref) inventory.mSelectedEnchantItem = index; } -void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIterator &iter, int index, const ESM::InventoryState &inventory) +void MWWorld::InventoryStore::readEquipmentState( + const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) { if (index == inventory.mSelectedEnchantItem) mSelectedEnchantItem = iter; @@ -110,55 +106,53 @@ void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIt } MWWorld::InventoryStore::InventoryStore() - : ContainerStore() - , mInventoryListener(nullptr) - , mUpdatesEnabled (true) - , mFirstAutoEquip(true) - , mSelectedEnchantItem(end()) + : ContainerStore() + , mInventoryListener(nullptr) + , mUpdatesEnabled(true) + , mFirstAutoEquip(true) + , mSelectedEnchantItem(end()) { - initSlots (mSlots); + initSlots(mSlots); } -MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) - : ContainerStore (store) - , mMagicEffects(store.mMagicEffects) - , mInventoryListener(store.mInventoryListener) - , mUpdatesEnabled(store.mUpdatesEnabled) - , mFirstAutoEquip(store.mFirstAutoEquip) - , mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes) - , mSelectedEnchantItem(end()) +MWWorld::InventoryStore::InventoryStore(const InventoryStore& store) + : ContainerStore(store) + , mInventoryListener(store.mInventoryListener) + , mUpdatesEnabled(store.mUpdatesEnabled) + , mFirstAutoEquip(store.mFirstAutoEquip) + , mSelectedEnchantItem(end()) { - copySlots (store); + copySlots(store); } -MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) +MWWorld::InventoryStore& MWWorld::InventoryStore::operator=(const InventoryStore& store) { if (this == &store) return *this; mListener = store.mListener; mInventoryListener = store.mInventoryListener; - mMagicEffects = store.mMagicEffects; mFirstAutoEquip = store.mFirstAutoEquip; - mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; mRechargingItemsUpToDate = false; - ContainerStore::operator= (store); + ContainerStore::operator=(store); mSlots.clear(); - copySlots (store); + copySlots(store); return *this; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip, bool resolve) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add( + const Ptr& itemPtr, int count, bool allowAutoEquip, bool resolve) { - const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, allowAutoEquip, resolve); + const MWWorld::ContainerStoreIterator& retVal + = MWWorld::ContainerStore::add(itemPtr, count, allowAutoEquip, resolve); // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves - if (allowAutoEquip && actorPtr != MWMechanics::getPlayer() - && actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()) + if (allowAutoEquip && mActor != MWMechanics::getPlayer() && mActor.getClass().isNpc() + && !mActor.getClass().getNpcStats(mActor).isWerewolf()) { - std::string type = itemPtr.getTypeName(); - if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) - autoEquip(actorPtr); + auto type = itemPtr.getType(); + if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) + autoEquip(); } /* @@ -175,76 +169,75 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, return retVal; } -void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor) +void MWWorld::InventoryStore::equip(int slot, const ContainerStoreIterator& iterator) { if (iterator == end()) - throw std::runtime_error ("can't equip end() iterator, use unequip function instead"); + throw std::runtime_error("can't equip end() iterator, use unequip function instead"); - if (slot<0 || slot>=static_cast (mSlots.size())) - throw std::runtime_error ("slot number out of range"); + if (slot < 0 || slot >= static_cast(mSlots.size())) + throw std::runtime_error("slot number out of range"); - if (iterator.getContainerStore()!=this) - throw std::runtime_error ("attempt to equip an item that is not in the inventory"); + if (iterator.getContainerStore() != this) + throw std::runtime_error("attempt to equip an item that is not in the inventory"); std::pair, bool> slots_; - slots_ = iterator->getClass().getEquipmentSlots (*iterator); + slots_ = iterator->getClass().getEquipmentSlots(*iterator); - if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) - throw std::runtime_error ("invalid slot"); + if (std::find(slots_.first.begin(), slots_.first.end(), slot) == slots_.first.end()) + throw std::runtime_error("invalid slot"); if (mSlots[slot] != end()) - unequipSlot(slot, actor); + unequipSlot(slot); // unstack item pointed to by iterator if required - if (iterator!=end() && !slots_.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped + if (iterator != end() && !slots_.second + && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped { - unstack(*iterator, actor); + unstack(*iterator); } mSlots[slot] = iterator; flagAsModified(); - fireEquipmentChangedEvent(actor); - - updateMagicEffects(actor); + fireEquipmentChangedEvent(); } -void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) +void MWWorld::InventoryStore::unequipAll() { mUpdatesEnabled = false; - for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) - unequipSlot(slot, actor); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + unequipSlot(slot); mUpdatesEnabled = true; - fireEquipmentChangedEvent(actor); - updateMagicEffects(actor); + fireEquipmentChangedEvent(); } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot(int slot) { - return findSlot (slot); + return findSlot(slot); } -MWWorld::ConstContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) const +MWWorld::ConstContainerStoreIterator MWWorld::InventoryStore::getSlot(int slot) const { - return findSlot (slot); + return findSlot(slot); } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot (int slot) const +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot(int slot) const { - if (slot<0 || slot>=static_cast (mSlots.size())) - throw std::runtime_error ("slot number out of range"); + if (slot < 0 || slot >= static_cast(mSlots.size())) + throw std::runtime_error("slot number out of range"); - if (mSlots[slot]==end()) + if (mSlots[slot] == end()) return mSlots[slot]; - if (mSlots[slot]->getRefData().getCount()<1) + if (mSlots[slot]->getRefData().getCount() < 1) { // Object has been deleted // This should no longer happen, since the new remove function will unequip first +<<<<<<< HEAD /* Start of tes3mp change (major) @@ -258,32 +251,35 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot (int slot) con /* End of tes3mp change (major) */ +======= + throw std::runtime_error( + "Invalid slot, make sure you are not calling RefData::setCount for a container object"); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } return mSlots[slot]; } -void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots& slots_) +void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) { - if (!actor.getClass().isNpc()) + if (!mActor.getClass().isNpc()) { // In original game creatures do not autoequip weapon, but we need it for weapon sheathing. // The only case when the difference is noticable - when this creature sells weapon. // So just disable weapon autoequipping for creatures which sells weapon. - int services = actor.getClass().getServices(actor); - bool sellsWeapon = services & (ESM::NPC::Weapon|ESM::NPC::MagicItems); + int services = mActor.getClass().getServices(mActor); + bool sellsWeapon = services & (ESM::NPC::Weapon | ESM::NPC::MagicItems); if (sellsWeapon) return; } - static const ESM::Skill::SkillEnum weaponSkills[] = - { + static const ESM::RefId weaponSkills[] = { ESM::Skill::LongBlade, ESM::Skill::Axe, ESM::Skill::Spear, ESM::Skill::ShortBlade, ESM::Skill::Marksman, - ESM::Skill::BluntWeapon + ESM::Skill::BluntWeapon, }; const size_t weaponSkillsLength = sizeof(weaponSkills) / sizeof(weaponSkills[0]); @@ -296,7 +292,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots ContainerStoreIterator bolt(end()); // rate ammo - for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter!=end(); ++iter) + for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter != end(); ++iter) { const ESM::Weapon* esmWeapon = iter->get()->mBase; @@ -326,7 +322,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots for (int j = 0; j < static_cast(weaponSkillsLength); ++j) { - float skillValue = actor.getClass().getSkill(actor, static_cast(weaponSkills[j])); + float skillValue = mActor.getClass().getSkill(mActor, weaponSkills[j]); if (skillValue > max && !weaponSkillVisited[j]) { max = skillValue; @@ -340,7 +336,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots max = 0; ContainerStoreIterator weapon(end()); - for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter!=end(); ++iter) + for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter != end(); ++iter) { const ESM::Weapon* esmWeapon = iter->get()->mBase; @@ -369,11 +365,11 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots } } - if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first) + if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, mActor).first) { // Do not equip ranged weapons, if there is no suitable ammo bool hasAmmo = true; - const MWWorld::LiveCellRef *ref = weapon->get(); + const MWWorld::LiveCellRef* ref = weapon->get(); int type = ref->mBase->mData.mType; int ammotype = MWMechanics::getWeaponType(type)->mAmmoType; if (ammotype == ESM::Weapon::Arrow) @@ -393,7 +389,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots if (hasAmmo) { - std::pair, bool> itemsSlots = weapon->getClass().getEquipmentSlots (*weapon); + std::pair, bool> itemsSlots = weapon->getClass().getEquipmentSlots(*weapon); if (!itemsSlots.first.empty()) { @@ -401,7 +397,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots { if (weapon->getRefData().getCount() > 1) { - unstack(*weapon, actor); + unstack(*weapon); } } @@ -420,30 +416,30 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots } } -void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& slots_) +void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) { // Only NPCs can wear armor for now. // For creatures we equip only shields. - if (!actor.getClass().isNpc()) + if (!mActor.getClass().isNpc()) { - autoEquipShield(actor, slots_); + autoEquipShield(slots_); return; } - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); + const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); - float unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored); + float unarmoredSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Unarmored); float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); - for (ContainerStoreIterator iter (begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter!=end(); ++iter) + for (ContainerStoreIterator iter(begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter != end(); + ++iter) { Ptr test = *iter; - switch(test.getClass().canBeEquipped (test, actor).first) + switch (test.getClass().canBeEquipped(test, mActor).first) { case 0: continue; @@ -451,34 +447,34 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& break; } - if (iter.getType() == ContainerStore::Type_Armor && - test.getClass().getEffectiveArmorRating(test, actor) <= std::max(unarmoredRating, 0.f)) + if (iter.getType() == ContainerStore::Type_Armor + && test.getClass().getEffectiveArmorRating(test, mActor) <= std::max(unarmoredRating, 0.f)) { continue; } - std::pair, bool> itemsSlots = - iter->getClass().getEquipmentSlots (*iter); + std::pair, bool> itemsSlots = iter->getClass().getEquipmentSlots(*iter); // checking if current item pointed by iter can be equipped for (int slot : itemsSlots.first) { // if true then it means slot is equipped already // check if slot may require swapping if current item is more valuable - if (slots_.at (slot)!=end()) + if (slots_.at(slot) != end()) { - Ptr old = *slots_.at (slot); + Ptr old = *slots_.at(slot); if (iter.getType() == ContainerStore::Type_Armor) { - if (old.getTypeName() == typeid(ESM::Armor).name()) + if (old.getType() == ESM::Armor::sRecordId) { if (old.get()->mBase->mData.mType < test.get()->mBase->mData.mType) continue; if (old.get()->mBase->mData.mType == test.get()->mBase->mData.mType) { - if (old.getClass().getEffectiveArmorRating(old, actor) >= test.getClass().getEffectiveArmorRating(test, actor)) + if (old.getClass().getEffectiveArmorRating(old, mActor) + >= test.getClass().getEffectiveArmorRating(test, mActor)) // old armor had better armor rating continue; } @@ -500,15 +496,15 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& Ptr rightRing = *slots_.at(Slot_RightRing); // we want to swap cheaper ring only if both are equipped - if (old.getClass().getValue (old) >= rightRing.getClass().getValue (rightRing)) + if (old.getClass().getValue(old) >= rightRing.getClass().getValue(rightRing)) continue; } } - if (old.getTypeName() == typeid(ESM::Clothing).name()) + if (old.getType() == ESM::Clothing::sRecordId) { // check value - if (old.getClass().getValue (old) >= test.getClass().getValue (test)) + if (old.getClass().getValue(old) >= test.getClass().getValue(test)) // old clothing was more valuable continue; } @@ -523,7 +519,7 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& // unstack item pointed to by iterator if required if (iter->getRefData().getCount() > 1) { - unstack(*iter, actor); + unstack(*iter); } } @@ -534,20 +530,19 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& } } -void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_) +void MWWorld::InventoryStore::autoEquipShield(TSlots& slots_) { for (ContainerStoreIterator iter(begin(ContainerStore::Type_Armor)); iter != end(); ++iter) { if (iter->get()->mBase->mData.mType != ESM::Armor::Shield) continue; - if (iter->getClass().canBeEquipped(*iter, actor).first != 1) + if (iter->getClass().canBeEquipped(*iter, mActor).first != 1) continue; - std::pair, bool> shieldSlots = - iter->getClass().getEquipmentSlots(*iter); + std::pair, bool> shieldSlots = iter->getClass().getEquipmentSlots(*iter); int slot = shieldSlots.first[0]; const ContainerStoreIterator& shield = slots_[slot]; - if (shield != end() - && shield.getType() == Type_Armor && shield->get()->mBase->mData.mType == ESM::Armor::Shield) + if (shield != end() && shield.getType() == Type_Armor + && shield->get()->mBase->mData.mType == ESM::Armor::Shield) { if (shield->getClass().getItemHealth(*shield) >= iter->getClass().getItemHealth(*iter)) continue; @@ -556,7 +551,7 @@ void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots& } } -void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) +void MWWorld::InventoryStore::autoEquip() { /* Start of tes3mp addition @@ -571,7 +566,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) */ TSlots slots_; - initSlots (slots_); + initSlots(slots_); // Disable model update during auto-equip mUpdatesEnabled = false; @@ -580,12 +575,12 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) // Equipping lights is handled in Actors::updateEquippedLight based on environment light. // Note: creatures ignore equipment armor rating and only equip shields // Use custom logic for them - select shield based on its health instead of armor rating - autoEquipWeapon(actor, slots_); - autoEquipArmor(actor, slots_); + autoEquipWeapon(slots_); + autoEquipArmor(slots_); bool changed = false; - for (std::size_t i=0; igetClass().getEnchantment (**iter); - - if (!enchantmentId.empty()) - { - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - std::vector params; - - bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) != mPermanentMagicEffectMagnitudes.end()); - if (!existed) - { - params.resize(enchantment.mEffects.mList.size()); - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - int delta = effect.mMagnMax - effect.mMagnMin; - // Roll some dice, one for each effect - if (delta) - params[i].mRandom = Misc::Rng::rollDice(delta + 1) / static_cast(delta); - // Try resisting each effect - params[i].mMultiplier = MWMechanics::getEffectMultiplier(effect.mEffectID, actor, actor); - ++i; - } - - // Note that using the RefID as a key here is not entirely correct. - // Consider equipping the same item twice (e.g. a ring) - // However, permanent enchantments with a random magnitude are kind of an exploit anyway, - // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. - mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()] = params; - } - else - params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()]; - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effect.mEffectID); - - // Fully resisted or can't be applied to target? - if (params[i].mMultiplier == 0 || !MWMechanics::checkEffectTarget(effect.mEffectID, actor, actor, actor == MWMechanics::getPlayer())) - { - i++; - continue; - } - - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params[i].mRandom; - magnitude *= params[i].mMultiplier; - - if (!existed) - { - // During first auto equip, we don't play any sounds. - // Basically we don't want sounds when the actor is first loaded, - // the items should appear as if they'd always been equipped. - mInventoryListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip); - } - - if (magnitude) - mMagicEffects.add (effect, magnitude); - - i++; - } - } - } - - // Now drop expired effects - for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin(); - it != mPermanentMagicEffectMagnitudes.end();) - { - bool found = false; - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter == end()) - continue; - if ((**iter).getCellRef().getRefId() == it->first) - { - found = true; - } - } - if (!found) - mPermanentMagicEffectMagnitudes.erase(it++); - else - ++it; - } - - // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping - MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); - - mFirstAutoEquip = false; -} - bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const { bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); @@ -742,8 +613,7 @@ bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) return false; // don't stack if either item is currently equipped - for (TSlots::const_iterator iter (mSlots.begin()); - iter!=mSlots.end(); ++iter) + for (TSlots::const_iterator iter(mSlots.begin()); iter != mSlots.end(); ++iter) { if (*iter != end() && (ptr1 == **iter || ptr2 == **iter)) { @@ -766,21 +636,21 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem( return mSelectedEnchantItem; } -int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement, bool resolve) +int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplacement, bool resolve) { - int retCount = ContainerStore::remove(item, count, actor, equipReplacement, resolve); + int retCount = ContainerStore::remove(item, count, equipReplacement, resolve); bool wasEquipped = false; if (!item.getRefData().getCount()) { - for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { if (mSlots[slot] == end()) continue; if (*mSlots[slot] == item) { - unequipSlot(slot, actor); + unequipSlot(slot); wasEquipped = true; break; } @@ -790,16 +660,15 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor // If an armor/clothing item is removed, try to find a replacement, // but not for the player nor werewolves, and not if the RemoveItem script command // was used (equipReplacement is false) - if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) - && actor.getClass().isNpc() && !actor.getClass().getNpcStats(actor).isWerewolf()) + if (equipReplacement && wasEquipped && (mActor != MWMechanics::getPlayer()) && mActor.getClass().isNpc() + && !mActor.getClass().getNpcStats(mActor).isWerewolf()) { - std::string type = item.getTypeName(); - if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) - autoEquip(actor); + auto type = item.getType(); + if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) + autoEquip(); } - if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() - && *mSelectedEnchantItem == item) + if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item) { mSelectedEnchantItem = end(); } @@ -818,10 +687,10 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor return retCount; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool applyUpdates) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, bool applyUpdates) { - if (slot<0 || slot>=static_cast (mSlots.size())) - throw std::runtime_error ("slot number out of range"); + if (slot < 0 || slot >= static_cast(mSlots.size())) + throw std::runtime_error("slot number out of range"); ContainerStoreIterator it = mSlots[slot]; @@ -836,11 +705,11 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c { retval = restack(*it); - if (actor == MWMechanics::getPlayer()) + if (mActor == MWMechanics::getPlayer()) { // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared - const std::string& script = it->getClass().getScript(*it); - if (script != "") + const ESM::RefId& script = it->getClass().getScript(*it); + if (!script.empty()) (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); } @@ -852,8 +721,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c if (applyUpdates) { - fireEquipmentChangedEvent(actor); - updateMagicEffects(actor); + fireEquipmentChangedEvent(); } return retval; @@ -862,33 +730,33 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c return it; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item, const MWWorld::Ptr& actor) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item) { - for (int slot=0; slot item.getRefData().getCount()) - throw std::runtime_error ("attempt to unequip more items than equipped"); + throw std::runtime_error("attempt to unequip more items than equipped"); if (count == item.getRefData().getCount()) - return unequipItem(item, actor); + return unequipItem(item); // Move items to an existing stack if possible, otherwise split count items out into a new stack. // Moving counts manually here, since ContainerStore's restack can't target unequipped stacks. - for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) { if (stacks(*iter, item) && !isEquipped(*iter)) { @@ -898,21 +766,20 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con } } - return unstack(item, actor, item.getRefData().getCount() - count); + return unstack(item, item.getRefData().getCount() - count); } -MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() +MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const { return mInventoryListener; } -void MWWorld::InventoryStore::setInvListener(InventoryStoreListener *listener, const Ptr& actor) +void MWWorld::InventoryStore::setInvListener(InventoryStoreListener* listener) { mInventoryListener = listener; - updateMagicEffects(actor); } -void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) +void MWWorld::InventoryStore::fireEquipmentChangedEvent() { if (!mUpdatesEnabled) return; @@ -934,122 +801,23 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) // if player, update inventory window /* - if (actor == MWMechanics::getPlayer()) + if (mActor == MWMechanics::getPlayer()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } */ } -void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) -{ - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter==end()) - continue; - - std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); - if (enchantmentId.empty()) - continue; - - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) == mPermanentMagicEffectMagnitudes.end()) - continue; - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - i++; - // Don't get spell icon display information for enchantments that weren't actually applied - if (mMagicEffects.get(MWMechanics::EffectKey(effect)).getMagnitude() == 0) - continue; - const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i-1]; - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params.mRandom; - magnitude *= params.mMultiplier; - if (magnitude > 0) - visitor.visit(MWMechanics::EffectKey(effect), i-1, (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude); - } - } -} - -void MWWorld::InventoryStore::purgeEffect(short effectId, bool wholeSpell) -{ - for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it) - { - if (*it != end()) - purgeEffect(effectId, (*it)->getCellRef().getRefId(), wholeSpell); - } -} - -void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell, int effectIndex) -{ - TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId); - if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end()) - return; - - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter==end()) - continue; - - if ((*iter)->getCellRef().getRefId() != sourceId) - continue; - - std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); - - if (!enchantmentId.empty()) - { - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - std::vector& params = effectMagnitudeIt->second; - - int i=0; - for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) - { - if (effectIt->mEffectID != effectId) - continue; - - if (effectIndex >= 0 && effectIndex != i) - continue; - - if (wholeSpell) - { - mPermanentMagicEffectMagnitudes.erase(sourceId); - return; - } - - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; - magnitude *= params[i].mMultiplier; - - if (magnitude) - mMagicEffects.add (*effectIt, -magnitude); - - params[i].mMultiplier = 0; - } - } - } -} - void MWWorld::InventoryStore::clear() { mSlots.clear(); - initSlots (mSlots); + initSlots(mSlots); ContainerStore::clear(); } -bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item) +bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr& item) { - for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) + for (int i = 0; i < MWWorld::InventoryStore::Slots; ++i) { if (getSlot(i) != end() && *getSlot(i) == item) return true; @@ -1057,38 +825,9 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item) return false; } -void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const +bool MWWorld::InventoryStore::isFirstEquip() { - MWWorld::ContainerStore::writeState(state); - - for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) - { - std::vector > params; - for (std::vector::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) - { - params.emplace_back(pIt->mRandom, pIt->mMultiplier); - } - - state.mPermanentMagicEffectMagnitudes[it->first] = params; - } -} - -void MWWorld::InventoryStore::readState(const ESM::InventoryState &state) -{ - MWWorld::ContainerStore::readState(state); - - for (ESM::InventoryState::TEffectMagnitudes::const_iterator it = state.mPermanentMagicEffectMagnitudes.begin(); - it != state.mPermanentMagicEffectMagnitudes.end(); ++it) - { - std::vector params; - for (std::vector >::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) - { - EffectParams p; - p.mRandom = pIt->first; - p.mMultiplier = pIt->second; - params.push_back(p); - } - - mPermanentMagicEffectMagnitudes[it->first] = params; - } + bool first = mFirstAutoEquip; + mFirstAutoEquip = false; + return first; } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 32dc0d2e9..78d7e3527 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -3,8 +3,6 @@ #include "containerstore.hpp" -#include "../mwmechanics/magiceffects.hpp" - namespace ESM { struct MagicEffect; @@ -23,16 +21,7 @@ namespace MWWorld /** * Fired when items are equipped or unequipped */ - virtual void equipmentChanged () {} - - /** - * @param effect - * @param isNew Is this effect new (e.g. the item for it was just now manually equipped) - * or was it loaded from a savegame / initial game state? \n - * If it isn't new, non-looping VFX should not be played. - * @param playSound Play effect sound? - */ - virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew) {} + virtual void equipmentChanged() {} virtual ~InventoryStoreListener() = default; }; @@ -40,180 +29,159 @@ namespace MWWorld ///< \brief Variant of the ContainerStore for NPCs class InventoryStore : public ContainerStore { - public: + public: + static constexpr int Slot_Helmet = 0; + static constexpr int Slot_Cuirass = 1; + static constexpr int Slot_Greaves = 2; + static constexpr int Slot_LeftPauldron = 3; + static constexpr int Slot_RightPauldron = 4; + static constexpr int Slot_LeftGauntlet = 5; + static constexpr int Slot_RightGauntlet = 6; + static constexpr int Slot_Boots = 7; + static constexpr int Slot_Shirt = 8; + static constexpr int Slot_Pants = 9; + static constexpr int Slot_Skirt = 10; + static constexpr int Slot_Robe = 11; + static constexpr int Slot_LeftRing = 12; + static constexpr int Slot_RightRing = 13; + static constexpr int Slot_Amulet = 14; + static constexpr int Slot_Belt = 15; + static constexpr int Slot_CarriedRight = 16; + static constexpr int Slot_CarriedLeft = 17; + static constexpr int Slot_Ammunition = 18; - static constexpr int Slot_Helmet = 0; - static constexpr int Slot_Cuirass = 1; - static constexpr int Slot_Greaves = 2; - static constexpr int Slot_LeftPauldron = 3; - static constexpr int Slot_RightPauldron = 4; - static constexpr int Slot_LeftGauntlet = 5; - static constexpr int Slot_RightGauntlet = 6; - static constexpr int Slot_Boots = 7; - static constexpr int Slot_Shirt = 8; - static constexpr int Slot_Pants = 9; - static constexpr int Slot_Skirt = 10; - static constexpr int Slot_Robe = 11; - static constexpr int Slot_LeftRing = 12; - static constexpr int Slot_RightRing = 13; - static constexpr int Slot_Amulet = 14; - static constexpr int Slot_Belt = 15; - static constexpr int Slot_CarriedRight = 16; - static constexpr int Slot_CarriedLeft = 17; - static constexpr int Slot_Ammunition = 18; + static constexpr int Slots = 19; - static constexpr int Slots = 19; + static constexpr int Slot_NoSlot = -1; - static constexpr int Slot_NoSlot = -1; + private: + InventoryStoreListener* mInventoryListener; - private: + // Enables updates of magic effects and actor model whenever items are equipped or unequipped. + // This is disabled during autoequip to avoid excessive updates + bool mUpdatesEnabled; - MWMechanics::MagicEffects mMagicEffects; + bool mFirstAutoEquip; - InventoryStoreListener* mInventoryListener; + typedef std::vector TSlots; - // Enables updates of magic effects and actor model whenever items are equipped or unequipped. - // This is disabled during autoequip to avoid excessive updates - bool mUpdatesEnabled; + TSlots mSlots; - bool mFirstAutoEquip; + void autoEquipWeapon(TSlots& slots_); + void autoEquipArmor(TSlots& slots_); + void autoEquipShield(TSlots& slots_); - // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. - // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. - struct EffectParams - { - // Modifier to scale between min and max magnitude - float mRandom; - // Multiplier for when an effect was fully or partially resisted - float mMultiplier; - }; + // selected magic item (for using enchantments of type "Cast once" or "Cast when used") + ContainerStoreIterator mSelectedEnchantItem; - typedef std::map > TEffectMagnitudes; - TEffectMagnitudes mPermanentMagicEffectMagnitudes; + void copySlots(const InventoryStore& store); - typedef std::vector TSlots; + void initSlots(TSlots& slots_); - TSlots mSlots; + void fireEquipmentChangedEvent(); - void autoEquipWeapon(const MWWorld::Ptr& actor, TSlots& slots_); - void autoEquipArmor(const MWWorld::Ptr& actor, TSlots& slots_); - void autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_); + void storeEquipmentState( + const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; + void readEquipmentState( + const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) override; - // selected magic item (for using enchantments of type "Cast once" or "Cast when used") - ContainerStoreIterator mSelectedEnchantItem; + ContainerStoreIterator findSlot(int slot) const; - void copySlots (const InventoryStore& store); + public: + InventoryStore(); - void initSlots (TSlots& slots_); + InventoryStore(const InventoryStore& store); - void updateMagicEffects(const Ptr& actor); + InventoryStore& operator=(const InventoryStore& store); - void fireEquipmentChangedEvent(const Ptr& actor); + const MWWorld::Ptr& getActor() const { return mActor; } + void setActor(const MWWorld::Ptr& actor) { mActor = actor; } - void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; - void readEquipmentState (const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) override; + std::unique_ptr clone() override + { + auto res = std::make_unique(*this); + res->clearRefNums(); + return res; + } - ContainerStoreIterator findSlot (int slot) const; + ContainerStoreIterator add( + const Ptr& itemPtr, int count, bool allowAutoEquip = true, bool resolve = true) override; + ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) + /// Auto-equip items if specific conditions are fulfilled and allowAutoEquip is true (see the implementation). + /// + /// \note The item pointed to is not required to exist beyond this function call. + /// + /// \attention Do not add items to an existing stack by increasing the count instead of + /// calling this function! + /// + /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to + /// the newly inserted item. - public: + void equip(int slot, const ContainerStoreIterator& iterator); + ///< \warning \a iterator can not be an end()-iterator, use unequip function instead - InventoryStore(); + bool isEquipped(const MWWorld::ConstPtr& item); + ///< Utility function, returns true if the given item is equipped in any slot - InventoryStore (const InventoryStore& store); + void setSelectedEnchantItem(const ContainerStoreIterator& iterator); + ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used") + /// \note to unset the selected item, call this method with end() iterator - InventoryStore& operator= (const InventoryStore& store); + ContainerStoreIterator getSelectedEnchantItem(); + ///< @return selected magic item (for using enchantments of type "Cast once" or "Cast when used") + /// \note if no item selected, return end() iterator - std::unique_ptr clone() override { return std::make_unique(*this); } + ContainerStoreIterator getSlot(int slot); + ConstContainerStoreIterator getSlot(int slot) const; - ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true) override; - ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) - /// Auto-equip items if specific conditions are fulfilled and allowAutoEquip is true (see the implementation). - /// - /// \note The item pointed to is not required to exist beyond this function call. - /// - /// \attention Do not add items to an existing stack by increasing the count instead of - /// calling this function! - /// - /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. + ContainerStoreIterator getPreferredShield(); - void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); - ///< \warning \a iterator can not be an end()-iterator, use unequip function instead + void unequipAll(); + ///< Unequip all currently equipped items. - bool isEquipped(const MWWorld::ConstPtr& item); - ///< Utility function, returns true if the given item is equipped in any slot + void autoEquip(); + ///< Auto equip items according to stats and item value. - void setSelectedEnchantItem(const ContainerStoreIterator& iterator); - ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used") - /// \note to unset the selected item, call this method with end() iterator + bool stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const override; + ///< @return true if the two specified objects can stack with each other - ContainerStoreIterator getSelectedEnchantItem(); - ///< @return selected magic item (for using enchantments of type "Cast once" or "Cast when used") - /// \note if no item selected, return end() iterator + using ContainerStore::remove; + int remove(const Ptr& item, int count, bool equipReplacement = 0, bool resolve = true) override; + ///< Remove \a count item(s) designated by \a item from this inventory. + /// + /// @return the number of items actually removed - ContainerStoreIterator getSlot (int slot); - ConstContainerStoreIterator getSlot(int slot) const; + ContainerStoreIterator unequipSlot(int slot, bool applyUpdates = true); + ///< Unequip \a slot. + /// + /// @return an iterator to the item that was previously in the slot - ContainerStoreIterator getPreferredShield(const MWWorld::Ptr& actor); + ContainerStoreIterator unequipItem(const Ptr& item); + ///< Unequip an item identified by its Ptr. An exception is thrown + /// if the item is not currently equipped. + /// + /// @return an iterator to the item that was previously in the slot + /// (it can be re-stacked so its count may be different than when it + /// was equipped). - void unequipAll(const MWWorld::Ptr& actor); - ///< Unequip all currently equipped items. + ContainerStoreIterator unequipItemQuantity(const Ptr& item, int count); + ///< Unequip a specific quantity of an item identified by its Ptr. + /// An exception is thrown if the item is not currently equipped, + /// if count <= 0, or if count > the item stack size. + /// + /// @return an iterator to the unequipped items that were previously + /// in the slot (they can be re-stacked so its count may be different + /// than the requested count). - void autoEquip (const MWWorld::Ptr& actor); - ///< Auto equip items according to stats and item value. + void setInvListener(InventoryStoreListener* listener); + ///< Set a listener for various events, see \a InventoryStoreListener - const MWMechanics::MagicEffects& getMagicEffects() const; - ///< Return magic effects from worn items. + InventoryStoreListener* getInvListener() const; - bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const override; - ///< @return true if the two specified objects can stack with each other + void clear() override; + ///< Empty container. - using ContainerStore::remove; - int remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true) override; - ///< Remove \a count item(s) designated by \a item from this inventory. - /// - /// @return the number of items actually removed - - ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool applyUpdates = true); - ///< Unequip \a slot. - /// - /// @return an iterator to the item that was previously in the slot - - ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor); - ///< Unequip an item identified by its Ptr. An exception is thrown - /// if the item is not currently equipped. - /// - /// @return an iterator to the item that was previously in the slot - /// (it can be re-stacked so its count may be different than when it - /// was equipped). - - ContainerStoreIterator unequipItemQuantity(const Ptr& item, const Ptr& actor, int count); - ///< Unequip a specific quantity of an item identified by its Ptr. - /// An exception is thrown if the item is not currently equipped, - /// if count <= 0, or if count > the item stack size. - /// - /// @return an iterator to the unequipped items that were previously - /// in the slot (they can be re-stacked so its count may be different - /// than the requested count). - - void setInvListener (InventoryStoreListener* listener, const Ptr& actor); - ///< Set a listener for various events, see \a InventoryStoreListener - - InventoryStoreListener* getInvListener(); - - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); - - void purgeEffect (short effectId, bool wholeSpell = false); - ///< Remove a magic effect - - void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false, int effectIndex=-1); - ///< Remove a magic effect - - void clear() override; - ///< Empty container. - - void writeState (ESM::InventoryState& state) const override; - - void readState (const ESM::InventoryState& state) override; + bool isFirstEquip(); }; } diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 9cf8a0fe0..30b413daa 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -1,71 +1,112 @@ #include "livecellref.hpp" +#include + #include -#include +#include +#include +#include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/world.hpp" -#include "ptr.hpp" #include "class.hpp" #include "esmstore.hpp" +#include "ptr.hpp" -MWWorld::LiveCellRefBase::LiveCellRefBase(const std::string& type, const ESM::CellRef &cref) - : mClass(&Class::get(type)), mRef(cref), mData(cref) +MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef& cref) + : mClass(&Class::get(type)) + , mRef(cref) + , mData(cref) { } -void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) +MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::Reference& cref) + : mClass(&Class::get(type)) + , mRef(cref) + , mData(cref) { - mRef = state.mRef; - mData = RefData (state, mData.isDeletedByContentFile()); +} - Ptr ptr (this); +void MWWorld::LiveCellRefBase::loadImp(const ESM::ObjectState& state) +{ + mRef = MWWorld::CellRef(state.mRef); + mData = RefData(state, mData.isDeletedByContentFile()); + + Ptr ptr(this); if (state.mHasLocals) { - std::string scriptId = mClass->getScript (ptr); + const ESM::RefId& scriptId = mClass->getScript(ptr); // Make sure we still have a script. It could have been coming from a content file that is no longer active. if (!scriptId.empty()) { - if (const ESM::Script* script = MWBase::Environment::get().getWorld()->getStore().get().search (scriptId)) + if (const ESM::Script* script + = MWBase::Environment::get().getESMStore()->get().search(scriptId)) { try { - mData.setLocals (*script); - mData.getLocals().read (state.mLocals, scriptId); + mData.setLocals(*script); + mData.getLocals().read(state.mLocals, scriptId); } catch (const std::exception& exception) { - Log(Debug::Error) - << "Error: failed to load state for local script " << scriptId - << " because an exception has been thrown: " << exception.what(); + Log(Debug::Error) << "Error: failed to load state for local script " << scriptId + << " because an exception has been thrown: " << exception.what(); } } } } - mClass->readAdditionalState (ptr, state); + mClass->readAdditionalState(ptr, state); - if (!mRef.getSoul().empty() && !MWBase::Environment::get().getWorld()->getStore().get().search(mRef.getSoul())) + if (!mRef.getSoul().empty() + && !MWBase::Environment::get().getESMStore()->get().search(mRef.getSoul())) { Log(Debug::Warning) << "Soul '" << mRef.getSoul() << "' not found, removing the soul from soul gem"; - mRef.setSoul(std::string()); + mRef.setSoul(ESM::RefId()); } + + MWBase::Environment::get().getLuaManager()->loadLocalScripts(ptr, state.mLuaScripts); } -void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const +void MWWorld::LiveCellRefBase::saveImp(ESM::ObjectState& state) const { mRef.writeState(state); - ConstPtr ptr (this); + ConstPtr ptr(this); - mData.write (state, mClass->getScript (ptr)); + mData.write(state, mClass->getScript(ptr)); + MWBase::Environment::get().getLuaManager()->saveLocalScripts( + Ptr(const_cast(this)), state.mLuaScripts); - mClass->writeAdditionalState (ptr, state); + mClass->writeAdditionalState(ptr, state); } -bool MWWorld::LiveCellRefBase::checkStateImp (const ESM::ObjectState& state) +bool MWWorld::LiveCellRefBase::checkStateImp(const ESM::ObjectState& state) { return true; } + +unsigned int MWWorld::LiveCellRefBase::getType() const +{ + return mClass->getType(); +} + +namespace MWWorld +{ + std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType) + { + std::stringstream message; + + message << "Bad LiveCellRef cast to " << recordType << " from "; + + if (value != nullptr) + message << value->getTypeDescription(); + else + message << "an empty object"; + + return message.str(); + } +} diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 414fde42b..8a3121e9c 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -1,12 +1,12 @@ #ifndef GAME_MWWORLD_LIVECELLREF_H #define GAME_MWWORLD_LIVECELLREF_H -#include - #include "cellref.hpp" #include "refdata.hpp" +#include + namespace ESM { struct ObjectState; @@ -18,10 +18,13 @@ namespace MWWorld class ESMStore; class Class; + template + struct LiveCellRef; + /// Used to create pointers to hold any type of LiveCellRef<> object. struct LiveCellRefBase { - const Class *mClass; + const Class* mClass; /** Information about this instance, such as 3D location and rotation * and individual type-dependent data. @@ -31,40 +34,71 @@ namespace MWWorld /** runtime-data */ RefData mData; - LiveCellRefBase(const std::string& type, const ESM::CellRef &cref=ESM::CellRef()); + LiveCellRefBase(unsigned int type, const ESM::CellRef& cref = ESM::CellRef()); + LiveCellRefBase(unsigned int type, const ESM4::Reference& cref); /* Need this for the class to be recognized as polymorphic */ - virtual ~LiveCellRefBase() { } + virtual ~LiveCellRefBase() {} - virtual void load (const ESM::ObjectState& state) = 0; + virtual void load(const ESM::ObjectState& state) = 0; ///< Load state into a LiveCellRef, that has already been initialised with base and class. /// /// \attention Must not be called with an invalid \a state. - virtual void save (ESM::ObjectState& state) const = 0; + virtual void save(ESM::ObjectState& state) const = 0; ///< Save LiveCellRef state into \a state. - protected: + virtual std::string_view getTypeDescription() const = 0; - void loadImp (const ESM::ObjectState& state); - ///< Load state into a LiveCellRef, that has already been initialised with base and - /// class. - /// - /// \attention Must not be called with an invalid \a state. + unsigned int getType() const; + ///< @see MWWorld::Class::getType - void saveImp (ESM::ObjectState& state) const; - ///< Save LiveCellRef state into \a state. + template + static const LiveCellRef* dynamicCast(const LiveCellRefBase* value); - static bool checkStateImp (const ESM::ObjectState& state); - ///< Check if state is valid and report errors. - /// - /// \return Valid? - /// - /// \note Does not check if the RefId exists. + template + static LiveCellRef* dynamicCast(LiveCellRefBase* value); + + protected: + void loadImp(const ESM::ObjectState& state); + ///< Load state into a LiveCellRef, that has already been initialised with base and + /// class. + /// + /// \attention Must not be called with an invalid \a state. + + void saveImp(ESM::ObjectState& state) const; + ///< Save LiveCellRef state into \a state. + + static bool checkStateImp(const ESM::ObjectState& state); + ///< Check if state is valid and report errors. + /// + /// \return Valid? + /// + /// \note Does not check if the RefId exists. }; - inline bool operator== (const LiveCellRefBase& cellRef, const ESM::RefNum refNum) + inline bool operator==(const LiveCellRefBase& cellRef, const ESM::RefNum refNum) { - return cellRef.mRef.getRefNum()==refNum; + return cellRef.mRef.getRefNum() == refNum; + } + + std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType); + + template + const LiveCellRef* LiveCellRefBase::dynamicCast(const LiveCellRefBase* value) + { + if (const LiveCellRef* ref = dynamic_cast*>(value)) + return ref; + throw std::runtime_error( + makeDynamicCastErrorMessage(value, ESM::getRecNameString(T::sRecordId).toStringView())); + } + + template + LiveCellRef* LiveCellRefBase::dynamicCast(LiveCellRefBase* value) + { + if (LiveCellRef* ref = dynamic_cast*>(value)) + return ref; + throw std::runtime_error( + makeDynamicCastErrorMessage(value, ESM::getRecNameString(T::sRecordId).toStringView())); } /// A reference to one object (of any type) in a cell. @@ -77,25 +111,46 @@ namespace MWWorld struct LiveCellRef : public LiveCellRefBase { LiveCellRef(const ESM::CellRef& cref, const X* b = nullptr) - : LiveCellRefBase(typeid(X).name(), cref), mBase(b) - {} + : LiveCellRefBase(X::sRecordId, cref) + , mBase(b) + { + } + + LiveCellRef(const ESM4::Reference& cref, const X* b = nullptr) + : LiveCellRefBase(X::sRecordId, cref) + , mBase(b) + { + } LiveCellRef(const X* b = nullptr) - : LiveCellRefBase(typeid(X).name()), mBase(b) - {} + : LiveCellRefBase(X::sRecordId) + , mBase(b) + { + } // The object that this instance is based on. const X* mBase; - void load (const ESM::ObjectState& state) override; + void load(const ESM::ObjectState& state) override; ///< Load state into a LiveCellRef, that has already been initialised with base and class. /// /// \attention Must not be called with an invalid \a state. - void save (ESM::ObjectState& state) const override; + void save(ESM::ObjectState& state) const override; ///< Save LiveCellRef state into \a state. - static bool checkState (const ESM::ObjectState& state); + std::string_view getTypeDescription() const override + { + if constexpr (ESM::isESM4Rec(X::sRecordId)) + { + static constexpr ESM::FixedString<6> name = ESM::getRecNameString(X::sRecordId); + return name.toStringView(); + } + else + return X::getRecordType(); + } + + static bool checkState(const ESM::ObjectState& state); ///< Check if state is valid and report errors. /// /// \return Valid? @@ -104,23 +159,22 @@ namespace MWWorld }; template - void LiveCellRef::load (const ESM::ObjectState& state) + void LiveCellRef::load(const ESM::ObjectState& state) { - loadImp (state); + loadImp(state); } template - void LiveCellRef::save (ESM::ObjectState& state) const + void LiveCellRef::save(ESM::ObjectState& state) const { - saveImp (state); + saveImp(state); } template - bool LiveCellRef::checkState (const ESM::ObjectState& state) + bool LiveCellRef::checkState(const ESM::ObjectState& state) { - return checkStateImp (state); + return checkStateImp(state); } - } #endif diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 1661d6b9f..8d9a28279 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -1,11 +1,15 @@ #include "localscripts.hpp" #include +#include +#include +#include +#include -#include "esmstore.hpp" #include "cellstore.hpp" #include "class.hpp" #include "containerstore.hpp" +#include "esmstore.hpp" namespace { @@ -23,7 +27,7 @@ namespace if (ptr.getRefData().isDeleted()) return true; - std::string script = ptr.getClass().getScript(ptr); + const ESM::RefId& script = ptr.getClass().getScript(ptr); if (!script.empty()) mScripts.add(script, ptr); @@ -43,19 +47,19 @@ namespace bool operator()(const MWWorld::Ptr& containerPtr) { // Ignore containers without generated content - if (containerPtr.getTypeName() == typeid(ESM::Container).name() && - containerPtr.getRefData().getCustomData() == nullptr) + if (containerPtr.getType() == ESM::Container::sRecordId + && containerPtr.getRefData().getCustomData() == nullptr) return true; MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); - for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) + for (const auto& ptr : container) { - std::string script = it->getClass().getScript(*it); - if(script != "") + const ESM::RefId& script = ptr.getClass().getScript(ptr); + if (!script.empty()) { - MWWorld::Ptr item = *it; + MWWorld::Ptr item = ptr; item.mCell = containerPtr.getCell(); - mScripts.add (script, item); + mScripts.add(script, item); } } return true; @@ -64,7 +68,8 @@ namespace } -MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) +MWWorld::LocalScripts::LocalScripts(const MWWorld::ESMStore& store) + : mStore(store) { mIter = mScripts.end(); } @@ -74,49 +79,46 @@ void MWWorld::LocalScripts::startIteration() mIter = mScripts.begin(); } -bool MWWorld::LocalScripts::getNext(std::pair& script) +bool MWWorld::LocalScripts::getNext(std::pair& script) { - if (mIter!=mScripts.end()) + if (mIter != mScripts.end()) { - std::list >::iterator iter = mIter++; + auto iter = mIter++; script = *iter; return true; } return false; } -void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) +void MWWorld::LocalScripts::add(const ESM::RefId& scriptName, const Ptr& ptr) { - if (const ESM::Script *script = mStore.get().search (scriptName)) + if (const ESM::Script* script = mStore.get().search(scriptName)) { try { - ptr.getRefData().setLocals (*script); + ptr.getRefData().setLocals(*script); - for (std::list >::iterator iter = mScripts.begin(); iter!=mScripts.end(); ++iter) - if (iter->second==ptr) + for (auto iter = mScripts.begin(); iter != mScripts.end(); ++iter) + if (iter->second == ptr) { Log(Debug::Warning) << "Error: tried to add local script twice for " << ptr.getCellRef().getRefId(); remove(ptr); break; } - mScripts.emplace_back (scriptName, ptr); + mScripts.emplace_back(scriptName, ptr); } catch (const std::exception& exception) { - Log(Debug::Error) - << "failed to add local script " << scriptName - << " because an exception has been thrown: " << exception.what(); + Log(Debug::Error) << "failed to add local script " << scriptName + << " because an exception has been thrown: " << exception.what(); } } else - Log(Debug::Warning) - << "failed to add local script " << scriptName - << " because the script does not exist."; + Log(Debug::Warning) << "failed to add local script " << scriptName << " because the script does not exist."; } -void MWWorld::LocalScripts::addCell (CellStore *cell) +void MWWorld::LocalScripts::addCell(CellStore* cell) { AddScriptsVisitor addScriptsVisitor(*this); cell->forEach(addScriptsVisitor); @@ -132,48 +134,46 @@ void MWWorld::LocalScripts::clear() mScripts.clear(); } -void MWWorld::LocalScripts::clearCell (CellStore *cell) +void MWWorld::LocalScripts::clearCell(CellStore* cell) { - std::list >::iterator iter = mScripts.begin(); + auto iter = mScripts.begin(); - while (iter!=mScripts.end()) + while (iter != mScripts.end()) { - if (iter->second.mCell==cell) + if (iter->second.mCell == cell) { - if (iter==mIter) - ++mIter; + if (iter == mIter) + ++mIter; - mScripts.erase (iter++); + mScripts.erase(iter++); } else ++iter; } } -void MWWorld::LocalScripts::remove (RefData *ref) +void MWWorld::LocalScripts::remove(RefData* ref) { - for (std::list >::iterator iter = mScripts.begin(); - iter!=mScripts.end(); ++iter) + for (auto iter = mScripts.begin(); iter != mScripts.end(); ++iter) if (&(iter->second.getRefData()) == ref) { - if (iter==mIter) + if (iter == mIter) ++mIter; - mScripts.erase (iter); + mScripts.erase(iter); break; } } -void MWWorld::LocalScripts::remove (const Ptr& ptr) +void MWWorld::LocalScripts::remove(const Ptr& ptr) { - for (std::list >::iterator iter = mScripts.begin(); - iter!=mScripts.end(); ++iter) - if (iter->second==ptr) + for (auto iter = mScripts.begin(); iter != mScripts.end(); ++iter) + if (iter->second == ptr) { - if (iter==mIter) + if (iter == mIter) ++mIter; - mScripts.erase (iter); + mScripts.erase(iter); break; } } diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index c698daf04..4bd2abeb8 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -15,37 +15,36 @@ namespace MWWorld /// \brief List of active local scripts class LocalScripts { - std::list > mScripts; - std::list >::iterator mIter; - const MWWorld::ESMStore& mStore; + std::list> mScripts; + std::list>::iterator mIter; + const MWWorld::ESMStore& mStore; - public: + public: + LocalScripts(const MWWorld::ESMStore& store); - LocalScripts (const MWWorld::ESMStore& store); + void startIteration(); + ///< Set the iterator to the begin of the script list. - void startIteration(); - ///< Set the iterator to the begin of the script list. + bool getNext(std::pair& script); + ///< Get next local script + /// @return Did we get a script? - bool getNext(std::pair& script); - ///< Get next local script - /// @return Did we get a script? + void add(const ESM::RefId& scriptName, const Ptr& ptr); + ///< Add script to collection of active local scripts. - void add (const std::string& scriptName, const Ptr& ptr); - ///< Add script to collection of active local scripts. + void addCell(CellStore* cell); + ///< Add all local scripts in a cell. - void addCell (CellStore *cell); - ///< Add all local scripts in a cell. + void clear(); + ///< Clear active local scripts collection. - void clear(); - ///< Clear active local scripts collection. + void clearCell(CellStore* cell); + ///< Remove all scripts belonging to \a cell. - void clearCell (CellStore *cell); - ///< Remove all scripts belonging to \a cell. - - void remove (RefData *ref); + void remove(RefData* ref); - void remove (const Ptr& ptr); - ///< Remove script for given reference (ignored if reference does not have a script listed). + void remove(const Ptr& ptr); + ///< Remove script for given reference (ignored if reference does not have a script listed). }; } diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp new file mode 100644 index 000000000..51c902a8f --- /dev/null +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -0,0 +1,236 @@ +#include "magiceffects.hpp" +#include "esmstore.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/magiceffects.hpp" + +namespace +{ + template + void getEnchantedItem(const ESM::RefId& id, ESM::RefId& enchantment, std::string& itemName) + { + const T* item = MWBase::Environment::get().getESMStore()->get().search(id); + if (item) + { + enchantment = item->mEnchant; + itemName = item->mName; + } + } +} + +namespace MWWorld +{ + void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats) + { + const auto& store = *MWBase::Environment::get().getESMStore(); + // Convert corprus to format 10 + for (const auto& [id, oldStats] : creatureStats.mSpells.mCorprusSpells) + { + const ESM::Spell* spell = store.get().search(id); + if (!spell) + continue; + + ESM::CreatureStats::CorprusStats stats; + stats.mNextWorsening = oldStats.mNextWorsening; + stats.mWorsenings.fill(0); + + for (auto& effect : spell->mEffects.mList) + { + if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; + } + creatureStats.mCorprusSpells[id] = stats; + } + // Convert to format 17 + for (const auto& [id, oldParams] : creatureStats.mSpells.mSpellParams) + { + const ESM::Spell* spell = store.get().search(id); + if (!spell || spell->mData.mType == ESM::Spell::ST_Spell || spell->mData.mType == ESM::Spell::ST_Power) + continue; + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = id; + params.mDisplayName = spell->mName; + params.mCasterActorId = creatureStats.mActorId; + if (spell->mData.mType == ESM::Spell::ST_Ability) + params.mType = ESM::ActiveSpells::Type_Ability; + else + params.mType = ESM::ActiveSpells::Type_Permanent; + params.mWorsenings = -1; + params.mNextWorsening = ESM::TimeStamp(); + int effectIndex = 0; + for (const auto& enam : spell->mEffects.mList) + { + if (oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) + { + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effect.mEffectIndex = effectIndex; + auto rand = oldParams.mEffectRands.find(effectIndex); + if (rand != oldParams.mEffectRands.end()) + { + float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; + effect.mMagnitude = magnitude; + effect.mMinMagnitude = magnitude; + effect.mMaxMagnitude = magnitude; + // Prevent recalculation of resistances and don't reflect or absorb the effect + effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances + | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; + } + else + { + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mMagnMin; + effect.mMaxMagnitude = enam.mMagnMax; + effect.mFlags = ESM::ActiveEffect::Flag_None; + } + params.mEffects.emplace_back(effect); + } + effectIndex++; + } + creatureStats.mActiveSpells.mSpells.emplace_back(params); + } + std::multimap equippedItems; + for (std::size_t i = 0; i < inventory.mItems.size(); ++i) + { + const ESM::ObjectState& item = inventory.mItems[i]; + auto slot = inventory.mEquipmentSlots.find(i); + if (slot != inventory.mEquipmentSlots.end()) + equippedItems.emplace(item.mRef.mRefID, slot->second); + } + for (const auto& [id, oldMagnitudes] : inventory.mPermanentMagicEffectMagnitudes) + { + ESM::RefId eId; + std::string name; + switch (store.find(id)) + { + case ESM::REC_ARMO: + getEnchantedItem(id, eId, name); + break; + case ESM::REC_CLOT: + getEnchantedItem(id, eId, name); + break; + case ESM::REC_WEAP: + getEnchantedItem(id, eId, name); + break; + } + if (eId.empty()) + continue; + const ESM::Enchantment* enchantment = store.get().search(eId); + if (!enchantment) + continue; + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = id; + params.mDisplayName = name; + params.mCasterActorId = creatureStats.mActorId; + params.mType = ESM::ActiveSpells::Type_Enchantment; + params.mWorsenings = -1; + params.mNextWorsening = ESM::TimeStamp(); + for (std::size_t effectIndex = 0; + effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) + { + const auto& enam = enchantment->mEffects.mList[effectIndex]; + auto [random, multiplier] = oldMagnitudes[effectIndex]; + float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; + magnitude *= multiplier; + if (magnitude <= 0) + continue; + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mMagnitude = magnitude; + effect.mMinMagnitude = magnitude; + effect.mMaxMagnitude = magnitude; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effect.mEffectIndex = static_cast(effectIndex); + // Prevent recalculation of resistances and don't reflect or absorb the effect + effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect + | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; + params.mEffects.emplace_back(effect); + } + auto [begin, end] = equippedItems.equal_range(id); + for (auto it = begin; it != end; ++it) + { + params.mItem = { static_cast(it->second), 0 }; + creatureStats.mActiveSpells.mSpells.emplace_back(params); + } + } + for (const auto& spell : creatureStats.mCorprusSpells) + { + auto it + = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), + [&](const auto& params) { return params.mId == spell.first; }); + if (it != creatureStats.mActiveSpells.mSpells.end()) + { + it->mNextWorsening = spell.second.mNextWorsening; + int worsenings = 0; + for (const auto& worsening : spell.second.mWorsenings) + worsenings = std::max(worsening, worsenings); + it->mWorsenings = worsenings; + } + } + for (const auto& [key, actorId] : creatureStats.mSummonedCreatureMap) + { + if (actorId == -1) + continue; + for (auto& params : creatureStats.mActiveSpells.mSpells) + { + if (params.mId == key.mSourceId) + { + bool found = false; + for (auto& effect : params.mEffects) + { + if (effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex) + { + effect.mArg = actorId; + effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; + found = true; + break; + } + } + if (found) + break; + } + } + } + // Reset modifiers that were previously recalculated each frame + for (auto& attribute : creatureStats.mAttributes) + attribute.mMod = 0.f; + for (auto& dynamic : creatureStats.mDynamic) + { + dynamic.mCurrent -= dynamic.mMod - dynamic.mBase; + dynamic.mMod = 0.f; + } + for (auto& setting : creatureStats.mAiSettings) + setting.mMod = 0.f; + if (npcStats) + { + for (auto& skill : npcStats->mSkills) + skill.mMod = 0.f; + } + } + + // Versions 17-19 wrote different modifiers to the savegame depending on whether the save had upgraded from a pre-17 + // version or not + void convertStats(ESM::CreatureStats& creatureStats) + { + for (auto& dynamic : creatureStats.mDynamic) + dynamic.mMod = 0.f; + for (auto& setting : creatureStats.mAiSettings) + setting.mMod = 0.f; + } +} diff --git a/apps/openmw/mwworld/magiceffects.hpp b/apps/openmw/mwworld/magiceffects.hpp new file mode 100644 index 000000000..aefcd056e --- /dev/null +++ b/apps/openmw/mwworld/magiceffects.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_MWWORLD_MAGICEFFECTS_H +#define OPENMW_MWWORLD_MAGICEFFECTS_H + +namespace ESM +{ + struct CreatureStats; + struct InventoryState; + struct NpcStats; +} + +namespace MWWorld +{ + void convertMagicEffects( + ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr); + + void convertStats(ESM::CreatureStats& creatureStats); +} + +#endif diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp index 4caff58c4..f4721a71a 100644 --- a/apps/openmw/mwworld/manualref.cpp +++ b/apps/openmw/mwworld/manualref.cpp @@ -1,27 +1,20 @@ #include "manualref.hpp" +#include +#include #include "esmstore.hpp" namespace { - template - void create(const MWWorld::Store& list, const std::string& name, boost::any& refValue, MWWorld::Ptr& ptrValue) + template + void create(const MWWorld::Store& list, const ESM::RefId& name, std::any& refValue, MWWorld::Ptr& ptrValue) { const T* base = list.find(name); ESM::CellRef cellRef; - cellRef.mRefNum.unset(); + cellRef.blank(); cellRef.mRefID = name; - cellRef.mScale = 1; - cellRef.mFactionRank = 0; - cellRef.mChargeInt = -1; - cellRef.mChargeIntRemainder = 0.0f; - cellRef.mGoldValue = 1; - cellRef.mEnchantmentCharge = -1; - cellRef.mTeleport = false; - cellRef.mLockLevel = 0; - cellRef.mReferenceBlocked = 0; /* Start of tes3mp addition @@ -36,42 +29,85 @@ namespace MWWorld::LiveCellRef ref(cellRef, base); refValue = ref; - ptrValue = MWWorld::Ptr(&boost::any_cast&>(refValue), nullptr); + ptrValue = MWWorld::Ptr(&std::any_cast&>(refValue), nullptr); } } -MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const std::string& name, const int count) +MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const ESM::RefId& name, const int count) { - std::string lowerName = Misc::StringUtils::lowerCase(name); - switch (store.find(lowerName)) + switch (store.find(name)) { - case ESM::REC_ACTI: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_ALCH: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_APPA: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_ARMO: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_BOOK: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_CLOT: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_CONT: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_CREA: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_DOOR: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_INGR: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_LEVC: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_LEVI: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_LIGH: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_LOCK: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_MISC: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_NPC_: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_PROB: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_REPA: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_STAT: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_WEAP: create(store.get(), lowerName, mRef, mPtr); break; - case ESM::REC_BODY: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_ACTI: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_ALCH: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_APPA: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_ARMO: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_BOOK: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_CLOT: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_CONT: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_CREA: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_DOOR: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_INGR: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_LEVC: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_LEVI: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_LIGH: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_LOCK: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_MISC: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_NPC_: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_PROB: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_REPA: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_STAT: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_WEAP: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_BODY: + create(store.get(), name, mRef, mPtr); + break; + case ESM::REC_STAT4: + create(store.get(), name, mRef, mPtr); + break; + case 0: + throw std::logic_error("failed to create manual cell ref for " + name.toDebugString() + " (unknown ID)"); - case 0: - throw std::logic_error("failed to create manual cell ref for " + lowerName + " (unknown ID)"); - - default: - throw std::logic_error("failed to create manual cell ref for " + lowerName + " (unknown type)"); + default: + throw std::logic_error("failed to create manual cell ref for " + name.toDebugString() + " (unknown type)"); } mPtr.getRefData().setCount(count); diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 2fc599471..8356fd0a0 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWWORLD_MANUALREF_H #define GAME_MWWORLD_MANUALREF_H -#include +#include #include "ptr.hpp" @@ -10,19 +10,16 @@ namespace MWWorld /// \brief Manually constructed live cell ref class ManualRef { - boost::any mRef; - Ptr mPtr; + std::any mRef; + Ptr mPtr; - ManualRef (const ManualRef&); - ManualRef& operator= (const ManualRef&); + ManualRef(const ManualRef&); + ManualRef& operator=(const ManualRef&); - public: - ManualRef(const MWWorld::ESMStore& store, const std::string& name, const int count = 1); + public: + ManualRef(const MWWorld::ESMStore& store, const ESM::RefId& name, const int count = 1); - const Ptr& getPtr() const - { - return mPtr; - } + const Ptr& getPtr() const { return mPtr; } }; } diff --git a/apps/openmw/mwworld/nullaction.hpp b/apps/openmw/mwworld/nullaction.hpp index 4ee1115e5..2499655ef 100644 --- a/apps/openmw/mwworld/nullaction.hpp +++ b/apps/openmw/mwworld/nullaction.hpp @@ -8,9 +8,9 @@ namespace MWWorld /// \brief Action: do nothing class NullAction : public Action { - void executeImp (const Ptr& actor) override {} + void executeImp(const Ptr& actor) override {} - bool isNullAction() override { return true; } + bool isNullAction() override { return true; } }; } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 8bd91dfd6..2af0ec142 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -4,6 +4,7 @@ #include +<<<<<<< HEAD /* Start of tes3mp addition @@ -19,43 +20,52 @@ #include #include #include +======= +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 #include -#include +#include +#include +#include +#include +#include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/magiceffects.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellutil.hpp" +#include "../mwrender/camera.hpp" +#include "../mwrender/renderingmanager.hpp" + +#include "cellstore.hpp" #include "class.hpp" #include "ptr.hpp" -#include "cellstore.hpp" namespace MWWorld { - Player::Player (const ESM::NPC *player) - : mCellStore(nullptr), - mLastKnownExteriorPosition(0,0,0), - mMarkedPosition(ESM::Position()), - mMarkedCell(nullptr), - mAutoMove(false), - mForwardBackward(0), - mTeleported(false), - mCurrentCrimeId(-1), - mPaidCrimeId(-1), - mAttackingOrSpell(false), - mJumping(false) + Player::Player(const ESM::NPC* player) + : mCellStore(nullptr) + , mLastKnownExteriorPosition(0, 0, 0) + , mMarkedPosition(ESM::Position()) + , mMarkedCell(nullptr) + , mTeleported(false) + , mCurrentCrimeId(-1) + , mPaidCrimeId(-1) + , mJumping(false) { ESM::CellRef cellRef; cellRef.blank(); - cellRef.mRefID = "player"; + cellRef.mRefID = ESM::RefId::stringRefId("Player"); mPlayer = LiveCellRef(cellRef, player); ESM::Position playerPos = mPlayer.mData.getPosition(); @@ -67,150 +77,98 @@ namespace MWWorld { MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); - for (int i=0; i(i)).getModified(); } void Player::restoreStats() { - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const auto& store = MWBase::Environment::get().getESMStore(); + const MWWorld::Store& gmst = store->get(); MWMechanics::CreatureStats& creatureStats = getPlayer().getClass().getCreatureStats(getPlayer()); MWMechanics::NpcStats& npcStats = getPlayer().getClass().getNpcStats(getPlayer()); MWMechanics::DynamicStat health = creatureStats.getDynamic(0); - creatureStats.setHealth(int(health.getBase() / gmst.find("fWereWolfHealth")->mValue.getFloat())); - for (int i=0; imValue.getFloat()); + for (size_t i = 0; i < mSaveSkills.size(); ++i) + { + auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(i)); + skill.restore(skill.getDamage()); + skill.setModifier(mSaveSkills[i] - skill.getBase()); + } + for (size_t i = 0; i < mSaveAttributes.size(); ++i) + { + auto id = static_cast(i); + auto attribute = npcStats.getAttribute(id); + attribute.restore(attribute.getDamage()); + attribute.setModifier(mSaveAttributes[i] - attribute.getBase()); + npcStats.setAttribute(id, attribute); + } } void Player::setWerewolfStats() { - const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const auto& store = MWBase::Environment::get().getESMStore(); + const MWWorld::Store& gmst = store->get(); MWMechanics::CreatureStats& creatureStats = getPlayer().getClass().getCreatureStats(getPlayer()); MWMechanics::NpcStats& npcStats = getPlayer().getClass().getNpcStats(getPlayer()); MWMechanics::DynamicStat health = creatureStats.getDynamic(0); - creatureStats.setHealth(int(health.getBase() * gmst.find("fWereWolfHealth")->mValue.getFloat())); - for(size_t i = 0;i < ESM::Attribute::Length;++i) + creatureStats.setHealth(health.getBase() * gmst.find("fWereWolfHealth")->mValue.getFloat()); + for (const auto& attribute : store->get()) { - // Oh, Bethesda. It's "Intelligence". - std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") : - ESM::Attribute::sAttributeNames[i]); - - MWMechanics::AttributeValue value = npcStats.getAttribute(i); - value.setBase(int(gmst.find(name)->mValue.getFloat())); - npcStats.setAttribute(i, value); + MWMechanics::AttributeValue value = npcStats.getAttribute(attribute.mId); + value.setModifier(attribute.mWerewolfValue - value.getModified()); + npcStats.setAttribute(attribute.mId, value); } - for(size_t i = 0;i < ESM::Skill::Length;i++) + for (const auto& skill : store->get()) { // Acrobatics is set separately for some reason. - if(i == ESM::Skill::Acrobatics) + if (skill.mId == ESM::Skill::Acrobatics) continue; - // "Mercantile"! >_< - std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : - ESM::Skill::sSkillNames[i]); - - MWMechanics::SkillValue value = npcStats.getSkill(i); - value.setBase(int(gmst.find(name)->mValue.getFloat())); - npcStats.setSkill(i, value); + MWMechanics::SkillValue& value = npcStats.getSkill(skill.mId); + value.setModifier(skill.mWerewolfValue - value.getModified()); } } - void Player::set(const ESM::NPC *player) + void Player::set(const ESM::NPC* player) { mPlayer.mBase = player; } - void Player::setCell (MWWorld::CellStore *cellStore) + void Player::setCell(MWWorld::CellStore* cellStore) { mCellStore = cellStore; } MWWorld::Ptr Player::getPlayer() { - MWWorld::Ptr ptr (&mPlayer, mCellStore); + MWWorld::Ptr ptr(&mPlayer, mCellStore); return ptr; } MWWorld::ConstPtr Player::getConstPlayer() const { - MWWorld::ConstPtr ptr (&mPlayer, mCellStore); + MWWorld::ConstPtr ptr(&mPlayer, mCellStore); return ptr; } - void Player::setBirthSign (const std::string &sign) + void Player::setBirthSign(const ESM::RefId& sign) { mSign = sign; } - const std::string& Player::getBirthSign() const + const ESM::RefId& Player::getBirthSign() const { return mSign; } - void Player::setDrawState (MWMechanics::DrawState_ state) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getNpcStats(ptr).setDrawState (state); - } - - bool Player::getAutoMove() const - { - return mAutoMove; - } - - void Player::setAutoMove (bool enable) + void Player::setDrawState(MWMechanics::DrawState state) { MWWorld::Ptr ptr = getPlayer(); - - mAutoMove = enable; - - int value = mForwardBackward; - - if (mAutoMove) - value = 1; - - ptr.getClass().getMovementSettings(ptr).mPosition[1] = value; - } - - void Player::setLeftRight (float value) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getMovementSettings(ptr).mPosition[0] = value; - } - - void Player::setForwardBackward (float value) - { - MWWorld::Ptr ptr = getPlayer(); - - mForwardBackward = value; - - if (mAutoMove) - value = 1; - - ptr.getClass().getMovementSettings(ptr).mPosition[1] = value; - } - - void Player::setUpDown(int value) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getMovementSettings(ptr).mPosition[2] = static_cast(value); - } - - void Player::setRunState(bool run) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, run); - } - - void Player::setSneak(bool sneak) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, sneak); + ptr.getClass().getNpcStats(ptr).setDrawState(state); } void Player::yaw(float yaw) @@ -229,10 +187,10 @@ namespace MWWorld ptr.getClass().getMovementSettings(ptr).mRotation[1] += roll; } - MWMechanics::DrawState_ Player::getDrawState() + MWMechanics::DrawState Player::getDrawState() { - MWWorld::Ptr ptr = getPlayer(); - return ptr.getClass().getNpcStats(ptr).getDrawState(); + MWWorld::Ptr ptr = getPlayer(); + return ptr.getClass().getNpcStats(ptr).getDrawState(); } void Player::activate() @@ -241,7 +199,7 @@ namespace MWWorld return; MWWorld::Ptr player = getPlayer(); - const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player); + const MWMechanics::NpcStats& playerStats = player.getClass().getNpcStats(player); bool godmode = MWBase::Environment::get().getWorld()->getGodModeState(); if ((!godmode && playerStats.isParalyzed()) || playerStats.getKnockedDown() || playerStats.isDead()) return; @@ -290,16 +248,6 @@ namespace MWWorld mTeleported = teleported; } - void Player::setAttackingOrSpell(bool attackingOrSpell) - { - mAttackingOrSpell = attackingOrSpell; - } - - bool Player::getAttackingOrSpell() const - { - return mAttackingOrSpell; - } - void Player::setJumping(bool jumping) { mJumping = jumping; @@ -310,7 +258,8 @@ namespace MWWorld return mJumping; } - bool Player::isInCombat() { + bool Player::isInCombat() + { return MWBase::Environment::get().getMechanicsManager()->getActorsFighting(getPlayer()).size() != 0; } @@ -319,13 +268,13 @@ namespace MWWorld return MWBase::Environment::get().getMechanicsManager()->getEnemiesNearby(getPlayer()).size() != 0; } - void Player::markPosition(CellStore *markedCell, const ESM::Position& markedPosition) + void Player::markPosition(CellStore* markedCell, const ESM::Position& markedPosition) { mMarkedCell = markedCell; mMarkedPosition = markedPosition; } - void Player::getMarkedPosition(CellStore*& markedCell, ESM::Position &markedPosition) const + void Player::getMarkedPosition(CellStore*& markedCell, ESM::Position& markedPosition) const { markedCell = mMarkedCell; if (mMarkedCell) @@ -335,29 +284,17 @@ namespace MWWorld void Player::clear() { mCellStore = nullptr; - mSign.clear(); + mSign = ESM::RefId(); mMarkedCell = nullptr; - mAutoMove = false; - mForwardBackward = 0; mTeleported = false; - mAttackingOrSpell = false; mJumping = false; mCurrentCrimeId = -1; mPaidCrimeId = -1; mPreviousItems.clear(); - mLastKnownExteriorPosition = osg::Vec3f(0,0,0); + mLastKnownExteriorPosition = osg::Vec3f(0, 0, 0); - for (int i=0; igetCell()->getCellId(); + mPlayer.save(player.mObject); + player.mCellId = mCellStore->getCell()->getId(); player.mCurrentCrimeId = mCurrentCrimeId; player.mPaidCrimeId = mPaidCrimeId; @@ -387,35 +324,40 @@ namespace MWWorld { player.mHasMark = true; player.mMarkedPosition = mMarkedPosition; - player.mMarkedCell = mMarkedCell->getCell()->getCellId(); + player.mMarkedCell = mMarkedCell->getCell()->getId(); } else player.mHasMark = false; - for (int i=0; iapplyWerewolfAcrobatics(getPlayer()); + } } getPlayer().getClass().getCreatureStats(getPlayer()).getAiSequence().clear(); MWBase::World& world = *MWBase::Environment::get().getWorld(); - try - { - mCellStore = world.getCell (player.mCellId); - } - catch (...) - { - Log(Debug::Warning) << "Warning: Player cell '" << player.mCellId.mWorldspace << "' no longer exists"; - // Cell no longer exists. The loader will have to choose a default cell. - mCellStore = nullptr; - } + mCellStore = MWBase::Environment::get().getWorldModel()->findCell(player.mCellId); + if (mCellStore == nullptr) + Log(Debug::Warning) << "Player cell " << player.mCellId << " no longer exists"; if (!player.mBirthsign.empty()) { - const ESM::BirthSign* sign = world.getStore().get().search (player.mBirthsign); + const ESM::BirthSign* sign = world.getStore().get().search(player.mBirthsign); if (!sign) - throw std::runtime_error ("invalid player state record (birthsign does not exist)"); + throw std::runtime_error("invalid player state record (birthsign does not exist)"); } mCurrentCrimeId = player.mCurrentCrimeId; @@ -467,26 +411,22 @@ namespace MWWorld mLastKnownExteriorPosition.y() = player.mLastKnownExteriorPosition[1]; mLastKnownExteriorPosition.z() = player.mLastKnownExteriorPosition[2]; - if (player.mHasMark && !player.mMarkedCell.mPaged) + if (player.mHasMark) { - // interior cell -> need to check if it exists (exterior cell will be - // generated on the fly) - - if (!world.getStore().get().search (player.mMarkedCell.mWorldspace)) + if (!world.getStore().get().search(player.mMarkedCell)) player.mHasMark = false; // drop mark silently } if (player.mHasMark) { mMarkedPosition = player.mMarkedPosition; - mMarkedCell = world.getCell (player.mMarkedCell); + mMarkedCell = &MWBase::Environment::get().getWorldModel()->getCell(player.mMarkedCell); } else { mMarkedCell = nullptr; } - mForwardBackward = 0; mTeleported = false; mPreviousItems = player.mPreviousItems; @@ -512,22 +452,22 @@ namespace MWWorld return mPaidCrimeId; } - void Player::setPreviousItem(const std::string& boundItemId, const std::string& previousItemId) + void Player::setPreviousItem(const ESM::RefId& boundItemId, const ESM::RefId& previousItemId) { mPreviousItems[boundItemId] = previousItemId; } - std::string Player::getPreviousItem(const std::string& boundItemId) + ESM::RefId Player::getPreviousItem(const ESM::RefId& boundItemId) { return mPreviousItems[boundItemId]; } - void Player::erasePreviousItem(const std::string& boundItemId) + void Player::erasePreviousItem(const ESM::RefId& boundItemId) { mPreviousItems.erase(boundItemId); } - void Player::setSelectedSpell(const std::string& spellId) + void Player::setSelectedSpell(const ESM::RefId& spellId) { Ptr player = getPlayer(); InventoryStore& store = player.getClass().getInventoryStore(player); @@ -536,4 +476,57 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, castChance); MWBase::Environment::get().getWindowManager()->updateSpellWindow(); } + + void Player::update() + { + auto player = getPlayer(); + const auto world = MWBase::Environment::get().getWorld(); + const auto rendering = world->getRenderingManager(); + auto& store = world->getStore(); + auto& playerClass = player.getClass(); + const auto windowMgr = MWBase::Environment::get().getWindowManager(); + + if (player.getCell()->isExterior()) + { + ESM::Position pos = player.getRefData().getPosition(); + setLastKnownExteriorPosition(pos.asVec3()); + } + + bool isWerewolf = playerClass.getNpcStats(player).isWerewolf(); + bool isFirstPerson = world->isFirstPerson(); + if (isWerewolf && isFirstPerson) + { + float werewolfFov = Fallback::Map::getFloat("General_Werewolf_FOV"); + if (werewolfFov != 0) + rendering->overrideFieldOfView(werewolfFov); + windowMgr->setWerewolfOverlay(true); + } + else + { + rendering->resetFieldOfView(); + windowMgr->setWerewolfOverlay(false); + } + + // Sink the camera while sneaking + bool sneaking = playerClass.getCreatureStats(player).getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool swimming = world->isSwimming(player); + bool flying = world->isFlying(player); + + static const float i1stPersonSneakDelta + = store.get().find("i1stPersonSneakDelta")->mValue.getFloat(); + if (sneaking && !swimming && !flying) + rendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); + else + rendering->getCamera()->setSneakOffset(0.f); + + int blind = 0; + const auto& magicEffects = playerClass.getCreatureStats(player).getMagicEffects(); + if (!world->getGodModeState()) + blind = static_cast(magicEffects.getOrDefault(ESM::MagicEffect::Blind).getModifier()); + windowMgr->setBlindness(std::clamp(blind, 0, 100)); + + int nightEye = static_cast(magicEffects.getOrDefault(ESM::MagicEffect::NightEye).getMagnitude()); + rendering->setNightEyeFactor(std::min(1.f, (nightEye / 100.f))); + } + } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 1e4b0ffdf..0d56833df 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -1,20 +1,20 @@ #ifndef GAME_MWWORLD_PLAYER_H #define GAME_MWWORLD_PLAYER_H +#include #include -#include "../mwworld/refdata.hpp" #include "../mwworld/livecellref.hpp" #include "../mwmechanics/drawstate.hpp" -#include "../mwmechanics/stat.hpp" -#include #include +#include +#include +#include namespace ESM { - struct NPC; class ESMWriter; class ESMReader; } @@ -32,78 +32,63 @@ namespace MWWorld /// \brief NPC object representing the player and additional player data class Player { - LiveCellRef mPlayer; - MWWorld::CellStore *mCellStore; - std::string mSign; + LiveCellRef mPlayer; + MWWorld::CellStore* mCellStore; + ESM::RefId mSign; osg::Vec3f mLastKnownExteriorPosition; - ESM::Position mMarkedPosition; + ESM::Position mMarkedPosition; // If no position was marked, this is nullptr - CellStore* mMarkedCell; + CellStore* mMarkedCell; - bool mAutoMove; - float mForwardBackward; - bool mTeleported; + bool mTeleported; - int mCurrentCrimeId; // the id assigned witnesses - int mPaidCrimeId; // the last id paid off (0 bounty) + int mCurrentCrimeId; // the id assigned witnesses + int mPaidCrimeId; // the last id paid off (0 bounty) - typedef std::map PreviousItems; // previous equipped items, needed for bound spells + typedef std::map PreviousItems; // previous equipped items, needed for bound spells PreviousItems mPreviousItems; // Saved stats prior to becoming a werewolf - MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; - MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; + std::array mSaveSkills; + std::array mSaveAttributes; - bool mAttackingOrSpell; bool mJumping; public: - - Player(const ESM::NPC *player); + Player(const ESM::NPC* player); void saveStats(); void restoreStats(); void setWerewolfStats(); // For mark/recall magic effects - void markPosition (CellStore* markedCell, const ESM::Position& markedPosition); - void getMarkedPosition (CellStore*& markedCell, ESM::Position& markedPosition) const; + void markPosition(CellStore* markedCell, const ESM::Position& markedPosition); + void getMarkedPosition(CellStore*& markedCell, ESM::Position& markedPosition) const; /// Interiors can not always be mapped to a world position. However /// world position is still required for divine / almsivi magic effects /// and the player arrow on the global map. - void setLastKnownExteriorPosition (const osg::Vec3f& position) { mLastKnownExteriorPosition = position; } + void setLastKnownExteriorPosition(const osg::Vec3f& position) { mLastKnownExteriorPosition = position; } osg::Vec3f getLastKnownExteriorPosition() const { return mLastKnownExteriorPosition; } - void set (const ESM::NPC *player); + void set(const ESM::NPC* player); - void setCell (MWWorld::CellStore *cellStore); + void setCell(MWWorld::CellStore* cellStore); MWWorld::Ptr getPlayer(); MWWorld::ConstPtr getConstPlayer() const; - void setBirthSign(const std::string &sign); - const std::string &getBirthSign() const; + void setBirthSign(const ESM::RefId& sign); + const ESM::RefId& getBirthSign() const; - void setDrawState (MWMechanics::DrawState_ state); - MWMechanics::DrawState_ getDrawState(); /// \todo constness + void setDrawState(MWMechanics::DrawState state); + MWMechanics::DrawState getDrawState(); /// \todo constness /// Activate the object under the crosshair, if any void activate(); - bool getAutoMove() const; - void setAutoMove (bool enable); - - void setLeftRight (float value); - - void setForwardBackward (float value); - void setUpDown(int value); - - void setRunState(bool run); - void setSneak(bool sneak); - void yaw(float yaw); void pitch(float pitch); void roll(float roll); @@ -111,32 +96,31 @@ namespace MWWorld bool wasTeleported() const; void setTeleported(bool teleported); - void setAttackingOrSpell(bool attackingOrSpell); - bool getAttackingOrSpell() const; - void setJumping(bool jumping); bool getJumping() const; - ///Checks all nearby actors to see if anyone has an aipackage against you + /// Checks all nearby actors to see if anyone has an aipackage against you bool isInCombat(); bool enemiesNearby(); void clear(); - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, uint32_t type); + bool readRecord(ESM::ESMReader& reader, uint32_t type); - int getNewCrimeId(); // get new id for witnesses + int getNewCrimeId(); // get new id for witnesses void recordCrimeId(); // record the paid crime id when bounty is 0 - int getCrimeId() const; // get the last paid crime id + int getCrimeId() const; // get the last paid crime id - void setPreviousItem(const std::string& boundItemId, const std::string& previousItemId); - std::string getPreviousItem(const std::string& boundItemId); - void erasePreviousItem(const std::string& boundItemId); + void setPreviousItem(const ESM::RefId& boundItemId, const ESM::RefId& previousItemId); + ESM::RefId getPreviousItem(const ESM::RefId& boundItemId); + void erasePreviousItem(const ESM::RefId& boundItemId); - void setSelectedSpell(const std::string& spellId); + void setSelectedSpell(const ESM::RefId& spellId); + + void update(); }; } #endif diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index df4b6e732..9f519071b 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -1,46 +1,54 @@ #include "projectilemanager.hpp" #include - #include #include +#include + #include #include -#include -#include +#include +#include +#include +#include +#include #include #include +#include #include #include #include -#include #include +#include +#include + +#include -#include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/manualref.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellcasting.hpp" -#include "../mwmechanics/actorutil.hpp" -#include "../mwmechanics/aipackage.hpp" #include "../mwmechanics/weapontype.hpp" #include "../mwrender/animation.hpp" -#include "../mwrender/vismask.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/util.hpp" +#include "../mwrender/vismask.hpp" #include "../mwsound/sound.hpp" @@ -59,9 +67,10 @@ namespace { - ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::set& sounds, float& speed, std::string& texture, std::string& sourceName, const std::string& id) + ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::set& sounds, float& speed, + std::string& texture, std::string& sourceName, const ESM::RefId& id) { - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); const ESM::EffectList* effects; if (const ESM::Spell* spell = esmStore.get().search(id)) // check if it's a spell { @@ -80,11 +89,11 @@ namespace int count = 0; speed = 0.0f; ESM::EffectList projectileEffects; - for (std::vector::const_iterator iter (effects->mList.begin()); - iter!=effects->mList.end(); ++iter) + for (std::vector::const_iterator iter(effects->mList.begin()); iter != effects->mList.end(); + ++iter) { - const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(iter->mEffectID); // Speed of multi-effect projectiles should be the average of the constituent effects, // based on observation of the original engine. @@ -95,35 +104,38 @@ namespace continue; if (magicEffect->mBolt.empty()) - projectileIDs.emplace_back("VFX_DefaultBolt"); + projectileIDs.emplace_back(ESM::RefId::stringRefId("VFX_DefaultBolt")); else projectileIDs.push_back(magicEffect->mBolt); - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; if (!magicEffect->mBoltSound.empty()) sounds.emplace(magicEffect->mBoltSound); else - sounds.emplace(schools[magicEffect->mData.mSchool] + " bolt"); + sounds.emplace(MWBase::Environment::get() + .getESMStore() + ->get() + .find(magicEffect->mData.mSchool) + ->mSchool->mBoltSound); projectileEffects.mList.push_back(*iter); } - + if (count != 0) speed /= count; // the particle texture is only used if there is only one projectile - if (projectileEffects.mList.size() == 1) + if (projectileEffects.mList.size() == 1) { - const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - effects->mList.begin()->mEffectID); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find( + effects->mList.begin()->mEffectID); texture = magicEffect->mParticle; } - - if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects + + if (projectileEffects.mList.size() + > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects { - const std::string ID = "VFX_Multiple" + std::to_string(effects->mList.size()); - std::vector::iterator it; + const ESM::RefId ID = ESM::RefId::stringRefId("VFX_Multiple" + std::to_string(effects->mList.size())); + std::vector::iterator it; it = projectileIDs.begin(); it = projectileIDs.insert(it, ID); } @@ -137,20 +149,18 @@ namespace float lightDiffuseRed = 0.0f; float lightDiffuseGreen = 0.0f; float lightDiffuseBlue = 0.0f; - for (std::vector::const_iterator iter(effects.mList.begin()); - iter != effects.mList.end(); ++iter) + for (std::vector::const_iterator iter(effects.mList.begin()); iter != effects.mList.end(); + ++iter) { - const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( - iter->mEffectID); + const ESM::MagicEffect* magicEffect + = MWBase::Environment::get().getESMStore()->get().find(iter->mEffectID); lightDiffuseRed += (static_cast(magicEffect->mData.mRed) / 255.f); lightDiffuseGreen += (static_cast(magicEffect->mData.mGreen) / 255.f); lightDiffuseBlue += (static_cast(magicEffect->mData.mBlue) / 255.f); } int numberOfEffects = effects.mList.size(); - lightDiffuseColor = osg::Vec4(lightDiffuseRed / numberOfEffects - , lightDiffuseGreen / numberOfEffects - , lightDiffuseBlue / numberOfEffects - , 1.0f); + lightDiffuseColor = osg::Vec4(lightDiffuseRed / numberOfEffects, lightDiffuseGreen / numberOfEffects, + lightDiffuseBlue / numberOfEffects, 1.0f); return lightDiffuseColor; } @@ -160,34 +170,31 @@ namespace MWWorld { ProjectileManager::ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem, - MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics) + MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics) : mParent(parent) , mResourceSystem(resourceSystem) , mRendering(rendering) , mPhysics(physics) , mCleanupTimer(0.0f) { - } /// Rotates an osg::PositionAttitudeTransform over time. - class RotateCallback : public osg::NodeCallback + class RotateCallback : public SceneUtil::NodeCallback { public: - RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2) + RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0, -1, 0), float rotateSpeed = osg::PI * 2) : mAxis(axis) , mRotateSpeed(rotateSpeed) { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::PositionAttitudeTransform* node, osg::NodeVisitor* nv) { - osg::PositionAttitudeTransform* transform = static_cast(node); - double time = nv->getFrameStamp()->getSimulationTime(); osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis); - transform->setAttitude(orient); + node->setAttitude(orient); traverse(node, nv); } @@ -197,9 +204,8 @@ namespace MWWorld float mRotateSpeed; }; - - void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, - bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) + void ProjectileManager::createModel(State& state, const std::string& model, const osg::Vec3f& pos, + const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) { state.mNode = new osg::PositionAttitudeTransform; state.mNode->setNodeMask(MWRender::Mask_Effect); @@ -210,7 +216,7 @@ namespace MWWorld if (rotate) { - osg::ref_ptr rotateNode (new osg::PositionAttitudeTransform); + osg::ref_ptr rotateNode(new osg::PositionAttitudeTransform); rotateNode->addUpdateCallback(new RotateCallback()); state.mNode->addChild(rotateNode); attachTo = rotateNode; @@ -219,16 +225,23 @@ namespace MWWorld osg::ref_ptr projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo); if (state.mIdMagic.size() > 1) + { + const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { std::ostringstream nodeName; nodeName << "Dummy" << std::setw(2) << std::setfill('0') << iter; - const ESM::Weapon* weapon = MWBase::Environment::get().getWorld()->getStore().get().find (state.mIdMagic.at(iter)); - SceneUtil::FindByNameVisitor findVisitor(nodeName.str()); + const ESM::Weapon* weapon + = MWBase::Environment::get().getESMStore()->get().find(state.mIdMagic.at(iter)); + std::string nameToFind = nodeName.str(); + SceneUtil::FindByNameVisitor findVisitor(nameToFind); attachTo->accept(findVisitor); if (findVisitor.mFoundNode) - mResourceSystem->getSceneManager()->getInstance("meshes\\" + weapon->mModel, findVisitor.mFoundNode); + mResourceSystem->getSceneManager()->getInstance( + Misc::ResourceHelpers::correctMeshPath(weapon->mModel, vfs), findVisitor.mFoundNode); } + } if (createLight) { @@ -240,25 +253,22 @@ namespace MWWorld projectileLight->setLinearAttenuation(0.1f); projectileLight->setQuadraticAttenuation(0.f); projectileLight->setPosition(osg::Vec4(pos, 1.0)); - + SceneUtil::LightSource* projectileLightSource = new SceneUtil::LightSource; projectileLightSource->setNodeMask(MWRender::Mask_Lighting); projectileLightSource->setRadius(66.f); - + state.mNode->addChild(projectileLightSource); projectileLightSource->setLight(projectileLight); } - - SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; - state.mNode->accept(disableFreezeOnCullVisitor); state.mNode->addCullCallback(new SceneUtil::LightListCallback); mParent->addChild(state.mNode); - state.mEffectAnimationTime.reset(new MWRender::EffectAnimationTime); + state.mEffectAnimationTime = std::make_shared(); - SceneUtil::AssignControllerSourcesVisitor assignVisitor (state.mEffectAnimationTime); + SceneUtil::AssignControllerSourcesVisitor assignVisitor(state.mEffectAnimationTime); state.mNode->accept(assignVisitor); MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); @@ -269,24 +279,27 @@ namespace MWWorld state.mEffectAnimationTime->addTime(duration); } - void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection) + void ProjectileManager::launchMagicBolt( + const ESM::RefId& spellId, const Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) { osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) { - // Note: we ignore the collision box offset, this is required to make some flying creatures work as intended. + // Note: we ignore the collision box offset, this is required to make some flying creatures work as + // intended. pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight; } - if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible + if (MWBase::Environment::get().getWorld()->isUnderwater( + caster.getCell(), pos)) // Underwater casting not possible return; osg::Quat orient; if (caster.getClass().isActor()) - orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) - * osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); + orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) + * osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); else - orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection)); + orient.makeRotate(osg::Vec3f(0, 1, 0), osg::Vec3f(fallbackDirection)); /* Start of tes3mp addition @@ -327,6 +340,7 @@ namespace MWWorld MagicBoltState state; state.mSpellId = spellId; state.mCasterHandle = caster; + state.mSlot = slot; if (caster.getClass().isActor()) state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else @@ -334,7 +348,8 @@ namespace MWWorld std::string texture; - state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); + state.mEffects = getMagicBoltData( + state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); // Non-projectile should have been removed by getMagicBoltData if (state.mEffects.mList.empty()) @@ -346,7 +361,7 @@ namespace MWWorld return; } - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); @@ -354,40 +369,50 @@ namespace MWWorld auto model = ptr.getClass().getModel(ptr); createModel(state, model, pos, orient, true, true, lightDiffuseColor, texture); - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - for (const std::string &soundid : state.mSoundIds) + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + for (const auto& soundid : state.mSoundIds) { - MWBase::Sound *sound = sndMgr->playSound3D(pos, soundid, 1.0f, 1.0f, - MWSound::Type::Sfx, MWSound::PlayMode::Loop); + MWBase::Sound* sound + = sndMgr->playSound3D(pos, soundid, 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop); if (sound) state.mSounds.push_back(sound); } - // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape + // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics + // shape if (state.mIdMagic.size() > 1) +<<<<<<< HEAD model = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get().find(state.mIdMagic.at(1))->mModel; +======= + { + model = Misc::ResourceHelpers::correctMeshPath( + MWBase::Environment::get().getESMStore()->get().find(state.mIdMagic[1])->mModel, + MWBase::Environment::get().getResourceSystem()->getVFS()); + } +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; mMagicBolts.push_back(state); } - void ProjectileManager::launchProjectile(Ptr actor, ConstPtr projectile, const osg::Vec3f &pos, const osg::Quat &orient, Ptr bow, float speed, float attackStrength) + void ProjectileManager::launchProjectile(const Ptr& actor, const ConstPtr& projectile, const osg::Vec3f& pos, + const osg::Quat& orient, const Ptr& bow, float speed, float attackStrength) { ProjectileState state; state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); state.mBowId = bow.getCellRef().getRefId(); - state.mVelocity = orient * osg::Vec3f(0,1,0) * speed; + state.mVelocity = orient * osg::Vec3f(0, 1, 0) * speed; state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; int type = projectile.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown; - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); const auto model = ptr.getClass().getModel(ptr); - createModel(state, model, pos, orient, false, false, osg::Vec4(0,0,0,0)); + createModel(state, model, pos, orient, false, false, osg::Vec4(0, 0, 0, 0)); if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); @@ -403,7 +428,8 @@ namespace MWWorld for (auto& state : mMagicBolts) { - // casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified back. + // casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified + // back. // TODO: should object-type caster be restored from savegame? if (state.mActorId == -1) continue; @@ -433,11 +459,10 @@ namespace MWWorld { mCleanupTimer = 2.0f; - auto isCleanable = [](const ProjectileManager::State& state) -> bool - { + auto isCleanable = [](const ProjectileManager::State& state) -> bool { const float farawayThreshold = 72000.0f; osg::Vec3 playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); - return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold; + return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold * farawayThreshold; }; for (auto& projectileState : mProjectiles) @@ -456,6 +481,7 @@ namespace MWWorld void ProjectileManager::moveMagicBolts(float duration) { + const bool normaliseRaceSpeed = Settings::game().mNormaliseRaceSpeed; for (auto& magicBoltState : mMagicBolts) { if (magicBoltState.mToDelete) @@ -475,23 +501,29 @@ namespace MWWorld } } + const auto& store = *MWBase::Environment::get().getESMStore(); osg::Quat orient = magicBoltState.mNode->getAttitude(); - static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() - .find("fTargetSpellMaxSpeed")->mValue.getFloat(); + static float fTargetSpellMaxSpeed + = store.get().find("fTargetSpellMaxSpeed")->mValue.getFloat(); float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; - osg::Vec3f direction = orient * osg::Vec3f(0,1,0); + if (!normaliseRaceSpeed && !caster.isEmpty() && caster.getClass().isNpc()) + { + const auto npc = caster.get()->mBase; + const auto race = store.get().find(npc->mRace); + speed *= npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; + } + osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); direction.normalize(); - osg::Vec3f newPos = projectile->getPosition() + direction * duration * speed; + projectile->setVelocity(direction * speed); update(magicBoltState, duration); - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit + // result. std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - - mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); } } @@ -507,15 +539,17 @@ namespace MWWorld continue; // gravity constant - must be way lower than the gravity affecting actors, since we're not // simulating aerodynamics at all - projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; + projectileState.mVelocity + -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration; + projectile->setVelocity(projectileState.mVelocity); - // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. + // rotation does not work well for throwing projectiles - their roll angle will depend on shooting + // direction. if (!projectileState.mThrown) { osg::Quat orient; - orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity); + orient.makeRotate(osg::Vec3f(0, 1, 0), projectileState.mVelocity); projectileState.mNode->setAttitude(orient); } @@ -523,13 +557,12 @@ namespace MWWorld MWWorld::Ptr caster = projectileState.getCaster(); - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit + // result. std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - - mPhysics->updateProjectile(projectileState.mProjectileId, newPos); } } @@ -542,7 +575,11 @@ namespace MWWorld auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); +<<<<<<< HEAD const auto pos = projectile->getPosition(); +======= + const auto pos = projectile->getSimulationPosition(); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 projectileState.mNode->setPosition(pos); if (projectile->isActive()) @@ -556,21 +593,28 @@ namespace MWWorld caster = target; // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); + MWWorld::ManualRef projectileRef(*MWBase::Environment::get().getESMStore(), projectileState.mIdArrow); MWWorld::Ptr bow = projectileRef.getPtr(); if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) { MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) + if (invIt != inv.end() && invIt->getCellRef().getRefId() == projectileState.mBowId) bow = *invIt; } if (projectile->getHitWater()) mRendering->emitWaterRipple(pos); - MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); - cleanupProjectile(projectileState); + const auto hitPosition = Misc::Convert::toOsg(projectile->getHitPosition()); + + if (projectile->getHitWater()) + mRendering->emitWaterRipple(hitPosition); + + MWMechanics::projectileHit( + caster, target, bow, projectileRef.getPtr(), hitPosition, projectileState.mAttackStrength); + projectileState.mToDelete = true; } + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); for (auto& magicBoltState : mMagicBolts) { if (magicBoltState.mToDelete) @@ -578,7 +622,7 @@ namespace MWWorld auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); - const auto pos = projectile->getPosition(); + const auto pos = projectile->getSimulationPosition(); magicBoltState.mNode->setPosition(pos); for (const auto& sound : magicBoltState.mSounds) sound->setPosition(pos); @@ -591,19 +635,42 @@ namespace MWWorld assert(target != caster); MWMechanics::CastSpell cast(caster, target); - cast.mHitPosition = pos; + cast.mHitPosition = Misc::Convert::toOsg(projectile->getHitPosition()); cast.mId = magicBoltState.mSpellId; cast.mSourceName = magicBoltState.mSourceName; - cast.mStack = false; - cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); + cast.mSlot = magicBoltState.mSlot; + // Grab original effect list so the indices are correct + const ESM::EffectList* effects; + if (const ESM::Spell* spell = esmStore.get().search(magicBoltState.mSpellId)) + effects = &spell->mEffects; + else + { + MWWorld::ManualRef ref(esmStore, magicBoltState.mSpellId); + const MWWorld::Ptr& ptr = ref.getPtr(); + effects = &esmStore.get().find(ptr.getClass().getEnchantment(ptr))->mEffects; + } + cast.inflict(target, *effects, ESM::RT_Target); - MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); - cleanupMagicBolt(magicBoltState); + magicBoltState.mToDelete = true; } - mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), - mProjectiles.end()); - mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }), - mMagicBolts.end()); + + for (auto& projectileState : mProjectiles) + { + if (projectileState.mToDelete) + cleanupProjectile(projectileState); + } + + for (auto& magicBoltState : mMagicBolts) + { + if (magicBoltState.mToDelete) + cleanupMagicBolt(magicBoltState); + } + mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), + [](const State& state) { return state.mToDelete; }), + mProjectiles.end()); + mMagicBolts.erase( + std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }), + mMagicBolts.end()); } void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) @@ -635,7 +702,7 @@ namespace MWWorld mMagicBolts.clear(); } - void ProjectileManager::write(ESM::ESMWriter &writer, Loading::Listener &progress) const + void ProjectileManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) const { for (std::vector::const_iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) { @@ -665,7 +732,7 @@ namespace MWWorld state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; - + state.mSlot = it->mSlot; state.mSpellId = it->mSpellId; state.mSpeed = it->mSpeed; @@ -675,7 +742,7 @@ namespace MWWorld } } - bool ProjectileManager::readRecord(ESM::ESMReader &reader, uint32_t type) + bool ProjectileManager::readRecord(ESM::ESMReader& reader, uint32_t type) { if (type == ESM::REC_PROJ) { @@ -693,20 +760,26 @@ namespace MWWorld std::string model; try { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; +<<<<<<< HEAD state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false); +======= + state.mProjectileId + = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 } - catch(...) + catch (...) { return true; } - createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false, false, osg::Vec4(0,0,0,0)); + createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false, false, + osg::Vec4(0, 0, 0, 0)); mProjectiles.push_back(state); return true; @@ -721,15 +794,18 @@ namespace MWWorld state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; state.mToDelete = false; + state.mSlot = esm.mSlot; std::string texture; try { - state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); + state.mEffects = getMagicBoltData( + state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); } - catch(...) + catch (...) { - Log(Debug::Warning) << "Warning: Failed to recreate magic projectile from saved data (id \"" << state.mSpellId << "\" no longer exists?)"; + Log(Debug::Warning) << "Warning: Failed to recreate magic projectile from saved data (id \"" + << state.mSpellId << "\" no longer exists?)"; return true; } @@ -741,24 +817,29 @@ namespace MWWorld std::string model; try { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); + MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); } - catch(...) + catch (...) { return true; } osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); +<<<<<<< HEAD createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); +======= + createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, + lightDiffuseColor, texture); +>>>>>>> 8a33edd64a6f0e9fe3962c88618e8b27aad1b7a7 state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true); - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - for (const std::string &soundid : state.mSoundIds) + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + for (const auto& soundid : state.mSoundIds) { - MWBase::Sound *sound = sndMgr->playSound3D(esm.mPosition, soundid, 1.0f, 1.0f, - MWSound::Type::Sfx, MWSound::PlayMode::Loop); + MWBase::Sound* sound = sndMgr->playSound3D( + esm.mPosition, soundid, 1.0f, 1.0f, MWSound::Type::Sfx, MWSound::PlayMode::Loop); if (sound) state.mSounds.push_back(sound); } diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index e4bcae1ae..3e9d70893 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -3,10 +3,10 @@ #include -#include #include +#include -#include +#include #include "../mwbase/soundmanager.hpp" @@ -45,14 +45,15 @@ namespace MWWorld class ProjectileManager { public: - ProjectileManager (osg::Group* parent, Resource::ResourceSystem* resourceSystem, - MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); + ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem, + MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. - void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection); + void launchMagicBolt( + const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot); - void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, - const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); + void launchProjectile(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& projectile, const osg::Vec3f& pos, + const osg::Quat& orient, const MWWorld::Ptr& bow, float speed, float attackStrength); void updateCasters(); @@ -63,8 +64,8 @@ namespace MWWorld /// Removes all current projectiles. Should be called when switching to a new worldspace. void clear(); - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, uint32_t type); + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; + bool readRecord(ESM::ESMReader& reader, uint32_t type); int countSavedGameRecords() const; private: @@ -89,17 +90,17 @@ namespace MWWorld MWWorld::Ptr getCaster(); // MW-ids of a magic projectile - std::vector mIdMagic; + std::vector mIdMagic; // MW-id of an arrow projectile - std::string mIdArrow; + ESM::RefId mIdArrow; bool mToDelete; }; struct MagicBoltState : public State { - std::string mSpellId; + ESM::RefId mSpellId; // Name of item to display as effect source in magic menu (in case we casted an enchantment) std::string mSourceName; @@ -107,15 +108,16 @@ namespace MWWorld ESM::EffectList mEffects; float mSpeed; + int mSlot; std::vector mSounds; - std::set mSoundIds; + std::set mSoundIds; }; struct ProjectileState : public State { // RefID of the bow or crossbow the actor was using when this projectile was fired (may be empty) - std::string mBowId; + ESM::RefId mBowId; osg::Vec3f mVelocity; float mAttackStrength; @@ -132,9 +134,9 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, - bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); - void update (State& state, float duration); + void createModel(State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, + bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); + void update(State& state, float duration); void operator=(const ProjectileManager&); ProjectileManager(const ProjectileManager&); diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index e16a19629..1421b51a2 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -1,89 +1,49 @@ #include "ptr.hpp" -#include +#include "apps/openmw/mwbase/environment.hpp" -#include "containerstore.hpp" -#include "class.hpp" -#include "livecellref.hpp" +#include "worldmodel.hpp" -const std::string& MWWorld::Ptr::getTypeName() const +namespace MWWorld { - if(mRef != nullptr) - return mRef->mClass->getTypeName(); - throw std::runtime_error("Can't get type name from an empty object."); -} - -MWWorld::LiveCellRefBase *MWWorld::Ptr::getBase() const -{ - if (!mRef) - throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); - - return mRef; -} - -MWWorld::CellRef& MWWorld::Ptr::getCellRef() const -{ - assert(mRef); - - return mRef->mRef; -} - -MWWorld::RefData& MWWorld::Ptr::getRefData() const -{ - assert(mRef); - - return mRef->mData; -} - -void MWWorld::Ptr::setContainerStore (ContainerStore *store) -{ - assert (store); - assert (!mCell); - - mContainerStore = store; -} - -MWWorld::ContainerStore *MWWorld::Ptr::getContainerStore() const -{ - return mContainerStore; -} - -MWWorld::Ptr::operator const void *() -{ - return mRef; -} - -// ------------------------------------------------------------------------------- - -const std::string &MWWorld::ConstPtr::getTypeName() const -{ - if(mRef != nullptr) - return mRef->mClass->getTypeName(); - throw std::runtime_error("Can't get type name from an empty object."); -} - -const MWWorld::LiveCellRefBase *MWWorld::ConstPtr::getBase() const -{ - if (!mRef) - throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); - - return mRef; -} - -void MWWorld::ConstPtr::setContainerStore (const ContainerStore *store) -{ - assert (store); - assert (!mCell); - - mContainerStore = store; -} - -const MWWorld::ContainerStore *MWWorld::ConstPtr::getContainerStore() const -{ - return mContainerStore; -} - -MWWorld::ConstPtr::operator const void *() -{ - return mRef; + + std::string Ptr::toString() const + { + std::string res = "object"; + if (getRefData().isDeleted()) + res = "deleted object"; + res.append(getCellRef().getRefNum().toString()); + res.append(" ("); + res.append(getTypeDescription()); + res.append(", "); + res.append(getCellRef().getRefId().toDebugString()); + res.append(")"); + return res; + } + + SafePtr::SafePtr(const Ptr& ptr) + : mId(ptr.getCellRef().getRefNum()) + , mPtr(ptr) + , mLastUpdate(MWBase::Environment::get().getWorldModel()->getPtrRegistryRevision()) + { + } + + std::string SafePtr::toString() const + { + update(); + if (mPtr.isEmpty()) + return "object" + mId.toString() + " (not found)"; + else + return mPtr.toString(); + } + + void SafePtr::update() const + { + const WorldModel& worldModel = *MWBase::Environment::get().getWorldModel(); + if (mLastUpdate != worldModel.getPtrRegistryRevision()) + { + mPtr = worldModel.getPtr(mId); + mLastUpdate = worldModel.getPtrRegistryRevision(); + } + } } diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 6f269ab3f..2fda183ac 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -2,9 +2,9 @@ #define GAME_MWWORLD_PTR_H #include - #include -#include +#include +#include #include "livecellref.hpp" @@ -15,6 +15,7 @@ namespace MWWorld struct LiveCellRefBase; /// \brief Pointer to a LiveCellRef +<<<<<<< HEAD class Ptr { @@ -97,41 +98,48 @@ namespace MWWorld /// \brief Pointer to a const LiveCellRef /// @note a Ptr can be implicitely converted to a ConstPtr, but you can not convert a ConstPtr to a Ptr. class ConstPtr +======= + /// @note PtrBase is never used directly and needed only to define Ptr and ConstPtr + template